diff --git a/.gitignore b/.gitignore
index 2309cc8..85e3640 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index a23d371..0000000
--- a/LICENSE
+++ /dev/null
@@ -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.
diff --git a/README.md b/README.md
index 7ae96f4..a61f7ad 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,541 @@
-# plataforma-edu
+# Decoda - Plataforma Educacional
-Repositório da plataforma de Educação do Núcleo de Técnologoia
\ No newline at end of file
+> 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**
diff --git a/app/.env.offline b/app/.env.offline
new file mode 100644
index 0000000..ec45a98
--- /dev/null
+++ b/app/.env.offline
@@ -0,0 +1 @@
+VITE_IS_OFFLINE=true
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..9a3f2f0
--- /dev/null
+++ b/app/.gitignore
@@ -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
\ No newline at end of file
diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json
new file mode 100644
index 0000000..388adce
--- /dev/null
+++ b/app/.vscode/settings.json
@@ -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
+ }
+}
diff --git a/app/Dockerfile b/app/Dockerfile
new file mode 100644
index 0000000..4cc47da
--- /dev/null
+++ b/app/Dockerfile
@@ -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
diff --git a/app/Makefile b/app/Makefile
new file mode 100644
index 0000000..c1f1390
--- /dev/null
+++ b/app/Makefile
@@ -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
diff --git a/app/README.md b/app/README.md
new file mode 100644
index 0000000..1883566
--- /dev/null
+++ b/app/README.md
@@ -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**
diff --git a/app/THIRD_PARTY_NOTICES.md b/app/THIRD_PARTY_NOTICES.md
new file mode 100644
index 0000000..320a387
--- /dev/null
+++ b/app/THIRD_PARTY_NOTICES.md
@@ -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.
diff --git a/app/assets/icon.png b/app/assets/icon.png
new file mode 100644
index 0000000..8d584d2
Binary files /dev/null and b/app/assets/icon.png differ
diff --git a/app/eslint-report.json b/app/eslint-report.json
new file mode 100644
index 0000000..804c61c
--- /dev/null
+++ b/app/eslint-report.json
@@ -0,0 +1 @@
+[{"filePath":"/home/rui/src/new/plataforma-edu/app/coverage/block-navigation.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/coverage/prettify.js","messages":[],"suppressedMessages":[{"ruleId":"no-redeclare","severity":2,"message":"'ar' is already defined.","line":2,"column":3893,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":3895,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ar' is already defined.","line":2,"column":4061,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4063,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'at' is already defined.","line":2,"column":4089,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4091,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ak' is already defined.","line":2,"column":4604,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4606,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ak' is already defined.","line":2,"column":4665,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4667,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'am' is already defined.","line":2,"column":4670,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4672,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ag' is already defined.","line":2,"column":4691,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4693,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'af' is already defined.","line":2,"column":4789,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4791,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ak' is already defined.","line":2,"column":4854,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4856,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'am' is already defined.","line":2,"column":4859,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4861,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ak' is already defined.","line":2,"column":4949,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4951,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ag' is already defined.","line":2,"column":4970,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":4972,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'V' is already defined.","line":2,"column":5216,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":5217,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'U' is already defined.","line":2,"column":5220,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":5221,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'ae' is already defined.","line":2,"column":5244,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":5246,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-prototype-builtins","severity":2,"message":"Do not access Object.prototype method 'hasOwnProperty' from target object.","line":2,"column":6621,"nodeType":"CallExpression","messageId":"prototypeBuildIn","endLine":2,"endColumn":6635,"suggestions":[{"messageId":"callObjectPrototype","data":{"prop":"hasOwnProperty"},"fix":{"range":[6638,6656],"text":"Object.prototype.hasOwnProperty.call(ag, "},"desc":"Call Object.prototype.hasOwnProperty explicitly."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7590,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7591,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7610,7611],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7610,7610],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7592,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7593,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7612,7613],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7612,7612],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7594,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7595,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7614,7615],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7614,7614],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7601,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7602,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7621,7622],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7621,7621],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7616,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7617,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7636,7637],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7636,7636],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7628,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7629,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7648,7649],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7648,7648],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7637,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7638,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7657,7658],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7657,7657],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7639,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7640,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7659,7660],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7659,7659],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7641,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7642,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7661,7662],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7661,7661],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7647,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7648,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7667,7668],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7667,7667],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7649,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7650,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7669,7670],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7669,7669],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7651,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7652,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7671,7672],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7671,7671],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7658,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7659,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7678,7679],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7678,7678],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7673,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7674,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7693,7694],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7693,7693],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7685,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7686,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7705,7706],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7705,7705],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7694,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7695,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7714,7715],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7714,7714],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7696,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7697,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7716,7717],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7716,7716],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7698,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7699,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7718,7719],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7718,7718],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7704,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7705,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7724,7725],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7724,7724],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7713,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7714,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7733,7734],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7733,7733],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7730,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7731,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7750,7751],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7750,7750],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7736,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7737,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7756,7757],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7756,7756],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7745,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7746,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7765,7766],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7765,7765],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7762,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7763,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7782,7783],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7782,7782],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7826,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7827,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7846,7847],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7846,7846],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7835,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7836,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7855,7856],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7855,7855],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7852,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7853,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7872,7873],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7872,7872],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7858,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7859,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7878,7879],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7878,7878],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7867,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7868,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7887,7888],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7887,7887],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7884,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7885,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7904,7905],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7904,7904],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\`.","line":2,"column":7890,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7891,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7910,7911],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7910,7910],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\`.","line":2,"column":7899,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7900,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7919,7920],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7919,7919],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\`.","line":2,"column":7916,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7917,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7936,7937],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7936,7936],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7958,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7959,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7978,7979],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7978,7978],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7967,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7968,"suggestions":[{"messageId":"removeEscape","fix":{"range":[7987,7988],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[7987,7987],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":7983,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7984,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8003,8004],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8003,8003],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7989,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7990,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8009,8010],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8009,8009],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":7998,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":7999,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8018,8019],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8018,8018],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8014,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8015,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8034,8035],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8034,8034],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8071,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8072,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8091,8092],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8091,8091],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8078,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8079,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8098,8099],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8098,8098],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8082,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8083,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8102,8103],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8102,8102],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8084,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8085,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8104,8105],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8104,8104],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":8091,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":8092,"suggestions":[{"messageId":"removeEscape","fix":{"range":[8111,8112],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[8111,8111],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\..","line":2,"column":9231,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9232,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9251,9252],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9251,9251],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":9235,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9236,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9255,9256],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9255,9255],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":9237,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9238,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9257,9258],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9257,9257],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\`.","line":2,"column":9239,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9240,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9259,9260],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9259,9259],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":2,"column":9241,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9242,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9261,9262],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9261,9261],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\#.","line":2,"column":9243,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":9244,"suggestions":[{"messageId":"removeEscape","fix":{"range":[9263,9264],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[9263,9263],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'Y' is already defined.","line":2,"column":10812,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":10813,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ae' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":2,"column":11414,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11416,"suggestions":[{"messageId":"removeVar","data":{"varName":"ae"},"fix":{"range":[11430,11442],"text":""},"desc":"Remove unused variable 'ae'."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":1,"message":"'af' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":2,"column":11438,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11440,"suggestions":[{"messageId":"removeVar","data":{"varName":"af"},"fix":{"range":[11454,11468],"text":""},"desc":"Remove unused variable 'af'."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ag' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":2,"column":11471,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11473,"suggestions":[{"messageId":"removeVar","data":{"varName":"ag"},"fix":{"range":[11487,11499],"text":""},"desc":"Remove unused variable 'ag'."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-redeclare","severity":2,"message":"'W' is already defined.","line":2,"column":11501,"nodeType":"Identifier","messageId":"redeclared","endLine":2,"endColumn":11502,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-prototype-builtins","severity":2,"message":"Do not access Object.prototype method 'hasOwnProperty' from target object.","line":2,"column":11964,"nodeType":"CallExpression","messageId":"prototypeBuildIn","endLine":2,"endColumn":11978,"suggestions":[{"messageId":"callObjectPrototype","data":{"prop":"hasOwnProperty"},"fix":{"range":[11982,11999],"text":"Object.prototype.hasOwnProperty.call(t, "},"desc":"Call Object.prototype.hasOwnProperty explicitly."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-prototype-builtins","severity":2,"message":"Do not access Object.prototype method 'hasOwnProperty' from target object.","line":2,"column":12097,"nodeType":"CallExpression","messageId":"prototypeBuildIn","endLine":2,"endColumn":12111,"suggestions":[{"messageId":"callObjectPrototype","data":{"prop":"hasOwnProperty"},"fix":{"range":[12115,12132],"text":"Object.prototype.hasOwnProperty.call(t, "},"desc":"Call Object.prototype.hasOwnProperty explicitly."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\!.","line":2,"column":12253,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12254,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12273,12274],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12273,12273],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":2,"column":12269,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12270,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12289,12290],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12289,12289],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12685,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12686,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12705,12706],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12705,12705],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12689,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12690,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12709,12710],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12709,12709],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12693,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12694,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12713,12714],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12713,12713],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12697,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12698,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12717,12718],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12717,12717],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12701,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12702,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12721,12722],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12721,12721],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12705,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12706,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12725,12726],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12725,12725],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12835,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12836,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12855,12856],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12855,12855],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12837,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12838,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12857,12858],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12857,12857],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12849,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12850,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12869,12870],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12869,12869],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12851,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12852,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12871,12872],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12871,12871],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":2,"column":12855,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12856,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12875,12876],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12875,12875],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":2,"column":12881,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12882,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12901,12902],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12901,12901],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12913,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12914,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12933,12934],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12933,12933],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12918,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12919,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12938,12939],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12938,12938],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12923,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12924,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12943,12944],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12943,12943],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12954,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12955,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12974,12975],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12974,12974],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12959,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12960,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12979,12980],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12979,12979],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":12964,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12965,"suggestions":[{"messageId":"removeEscape","fix":{"range":[12984,12985],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[12984,12984],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":12998,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":12999,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13018,13019],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13018,13018],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":13000,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13001,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13020,13021],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13020,13020],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":13038,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13039,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13058,13059],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13058,13058],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":13043,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13044,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13063,13064],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13063,13063],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":13048,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13049,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13068,13069],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13068,13068],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":13080,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13081,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13100,13101],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13100,13100],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":13085,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13086,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13105,13106],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13105,13105],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":13090,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13091,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13110,13111],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13110,13110],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":13125,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13126,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13145,13146],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13145,13145],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":13127,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":13128,"suggestions":[{"messageId":"removeEscape","fix":{"range":[13147,13148],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[13147,13147],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ae' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":2,"column":14757,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14759,"suggestions":[{"messageId":"removeVar","data":{"varName":"ae"},"fix":{"range":[14773,14798],"text":""},"desc":"Remove unused variable 'ae'."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":15786,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":15788,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":15809,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":15811,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":15835,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":15837,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":15875,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":15877,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\!.","line":2,"column":15892,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":15893,"suggestions":[{"messageId":"removeEscape","fix":{"range":[15912,15913],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[15912,15912],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":2,"column":15908,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":15909,"suggestions":[{"messageId":"removeEscape","fix":{"range":[15928,15929],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[15928,15928],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":15918,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":15920,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16362,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16364,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16410,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16412,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16453,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16455,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16498,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16500,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16551,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16553,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16574,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16576,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16597,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16599,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16646,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16648,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":16661,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16662,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16681,16682],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16681,16681],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":16676,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16677,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16696,16697],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16696,16696],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":16708,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16709,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16728,16729],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16728,16728],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16719,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16721,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":16734,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16735,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16754,16755],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16754,16754],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":16749,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16750,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16769,16770],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16769,16769],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":16781,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16782,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16801,16802],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16801,16801],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\).","line":2,"column":16817,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16818,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16837,16838],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16837,16837],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":16819,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16820,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16839,16840],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16839,16839],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":16821,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16822,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16841,16842],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16841,16841],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":16833,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":16835,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\!.","line":2,"column":16860,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16861,"suggestions":[{"messageId":"removeEscape","fix":{"range":[16880,16881],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[16880,16880],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":2,"column":16991,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":16992,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17011,17012],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17011,17011],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17026,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17028,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\/.","line":2,"column":17059,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17060,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17079,17080],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17079,17079],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17079,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17081,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17111,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17113,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17161,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17163,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17203,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17205,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":2,"column":17256,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17257,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17276,17277],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17276,17276],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17285,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17287,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":17311,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17312,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17331,17332],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17331,17331],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":17313,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17314,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17333,17334],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17333,17333],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17331,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17333,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17354,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17356,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17380,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17382,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":2,"column":17435,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17436,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17455,17456],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17455,17455],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17477,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17479,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17500,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17502,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'PR' is not defined.","line":2,"column":17526,"nodeType":"Identifier","messageId":"undef","endLine":2,"endColumn":17528,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\).","line":2,"column":17543,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17544,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17563,17564],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17563,17563],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\\".","line":2,"column":17545,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17546,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17565,17566],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17565,17565],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\'.","line":2,"column":17547,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":2,"endColumn":17548,"suggestions":[{"messageId":"removeEscape","fix":{"range":[17567,17568],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[17567,17567],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}],"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/coverage/sorter.js","messages":[],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":36,"column":18,"nodeType":"Identifier","messageId":"unusedVar","endLine":36,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/eslint.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/postcss.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/App.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Suspense' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"Suspense"},"fix":{"range":[13,23],"text":""},"desc":"Remove unused variable 'Suspense'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Router' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":27,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":33,"suggestions":[{"messageId":"removeVar","data":{"varName":"Router"},"fix":{"range":[49,73],"text":""},"desc":"Remove unused variable 'Router'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Routes' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":35,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":41,"suggestions":[{"messageId":"removeVar","data":{"varName":"Routes"},"fix":{"range":[72,80],"text":""},"desc":"Remove unused variable 'Routes'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Route' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":43,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":48,"suggestions":[{"messageId":"removeVar","data":{"varName":"Route"},"fix":{"range":[80,87],"text":""},"desc":"Remove unused variable 'Route'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'HomePage' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"HomePage"},"fix":{"range":[142,156],"text":""},"desc":"Remove unused variable 'HomePage'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'LabPython' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"LabPython"},"fix":{"range":[192,207],"text":""},"desc":"Remove unused variable 'LabPython'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Playground' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":7,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"Playground"},"fix":{"range":[239,310],"text":""},"desc":"Remove unused variable 'Playground'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'About' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":8,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":12,"suggestions":[{"messageId":"removeVar","data":{"varName":"About"},"fix":{"range":[311,367],"text":""},"desc":"Remove unused variable 'About'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Faq' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":9,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":10,"suggestions":[{"messageId":"removeVar","data":{"varName":"Faq"},"fix":{"range":[368,418],"text":""},"desc":"Remove unused variable 'Faq'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Atividades' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":10,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"Atividades"},"fix":{"range":[419,490],"text":""},"desc":"Remove unused variable 'Atividades'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Educadores' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":11,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":11,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"Educadores"},"fix":{"range":[491,562],"text":""},"desc":"Remove unused variable 'Educadores'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'AutomatoGame' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":14,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":14,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"AutomatoGame"},"fix":{"range":[577,650],"text":""},"desc":"Remove unused variable 'AutomatoGame'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SemaforoGame' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":15,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":15,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"SemaforoGame"},"fix":{"range":[651,724],"text":""},"desc":"Remove unused variable 'SemaforoGame'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'MoleMashGame' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":16,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":16,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"MoleMashGame"},"fix":{"range":[725,795],"text":""},"desc":"Remove unused variable 'MoleMashGame'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'TurtleGame' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":17,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":17,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"TurtleGame"},"fix":{"range":[796,863],"text":""},"desc":"Remove unused variable 'TurtleGame'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CriptoGame' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":18,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":18,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"CriptoGame"},"fix":{"range":[864,931],"text":""},"desc":"Remove unused variable 'CriptoGame'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'LoadingFallback' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":20,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":20,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"LoadingFallback"},"fix":{"range":[933,1639],"text":""},"desc":"Remove unused variable 'LoadingFallback'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":17,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { lazy, Suspense } from \"react\";\nimport { BrowserRouter as Router, Routes, Route } from \"react-router-dom\";\nimport \"./App.css\";\nimport HomePage from \"./pages/HomePage/HomePage\";\nimport LabPython from \"./pages/LabPython/LabPython\";\n\nconst Playground = lazy(() => import(\"./pages/Playground/Playground\"));\nconst About = lazy(() => import(\"./pages/About/About\"));\nconst Faq = lazy(() => import(\"./pages/Faq/Faq\"));\nconst Atividades = lazy(() => import(\"./pages/Atividades/Atividades\"));\nconst Educadores = lazy(() => import(\"./pages/Educadores/Educadores\"));\n\n//Atividades\nconst AutomatoGame = lazy(() => import(\"./games/automato/AutomatoGame\"));\nconst SemaforoGame = lazy(() => import(\"./games/semaforo/SemaforoGame\"));\nconst MoleMashGame = lazy(() => import(\"./games/mole-mash/MoleMash\"));\nconst TurtleGame = lazy(() => import(\"./games/turtle/TurtleGame\"));\nconst CriptoGame = lazy(() => import(\"./games/cripto/CriptoGame\"));\n\nconst LoadingFallback = () => (\n
\n);\n\nexport default function App() {\n return (\n \n }>\n \n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n } />\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/Navbar.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[0,40],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Menu' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Menu"},"fix":{"range":[50,55],"text":""},"desc":"Remove unused variable 'Menu'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":17,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[54,57],"text":""},"desc":"Remove unused variable 'X'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ChevronDown' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":19,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":30,"suggestions":[{"messageId":"removeVar","data":{"varName":"ChevronDown"},"fix":{"range":[57,70],"text":""},"desc":"Remove unused variable 'ChevronDown'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'scrolled' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":8,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"scrolled"},"fix":{"range":[273,281],"text":""},"desc":"Remove unused variable 'scrolled'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'openDropdown' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":9,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"openDropdown"},"fix":{"range":[324,336],"text":""},"desc":"Remove unused variable 'openDropdown'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'handleMouseEnter' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":23,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":23,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"handleMouseEnter"},"fix":{"range":[731,900],"text":""},"desc":"Remove unused variable 'handleMouseEnter'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'handleMouseLeave' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":31,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":31,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"handleMouseLeave"},"fix":{"range":[904,1052],"text":""},"desc":"Remove unused variable 'handleMouseLeave'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Link } from \"react-router-dom\";\nimport { Menu, X, ChevronDown } from \"lucide-react\";\nimport { useState, useEffect } from \"react\";\nimport logo from \"../assets/logo_decoda.svg\";\n\nconst Navbar = () => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [scrolled, setScrolled] = useState(false);\n const [openDropdown, setOpenDropdown] = useState(null);\n const [closeTimeout, setCloseTimeout] = useState(null);\n\n useEffect(() => {\n const handleScroll = () => {\n setScrolled(window.scrollY > 50);\n };\n\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n handleScroll(); // Check initial state\n\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n const handleMouseEnter = (dropdown) => {\n if (closeTimeout) {\n clearTimeout(closeTimeout);\n setCloseTimeout(null);\n }\n setOpenDropdown(dropdown);\n };\n\n const handleMouseLeave = () => {\n const timeout = setTimeout(() => {\n setOpenDropdown(null);\n }, 150);\n setCloseTimeout(timeout);\n };\n\n return (\n \n \n {/* Logo - Esquerda */}\n
\n
\n
\n \n
\n\n {/* Espaço Central vazio para empurrar o menu para a direita */}\n
\n\n {/* Menu Desktop - Direita */}\n
\n
\n {/* Links diretos - Atividades */}\n \n Atividades\n \n\n {/* Links diretos - Laboratório de Blocos */}\n \n Laboratório de Blocos\n \n\n {/* Links diretos - Laboratório Pytohn */}\n \n Laboratório Python\n \n\n \n Quem somos\n \n\n \n Perguntas frequentes\n \n\n \n Para educadores\n \n
\n\n {/* Botão Mobile Menu */}\n
setIsMenuOpen(!isMenuOpen)}\n className=\"md:hidden p-2 rounded-lg hover:bg-white/10 transition-colors\"\n aria-label=\"Toggle menu\"\n >\n {isMenuOpen ? (\n \n ) : (\n \n )}\n \n
\n
\n\n {/* Menu Mobile */}\n {isMenuOpen && (\n \n
\n {/* Links diretos Mobile */}\n setIsMenuOpen(false)}\n >\n Atividades\n \n setIsMenuOpen(false)}\n >\n Laboratório\n \n setIsMenuOpen(false)}\n >\n Quem somos\n \n setIsMenuOpen(false)}\n >\n Perguntas frequentes\n \n setIsMenuOpen(false)}\n >\n Para educadores\n \n
\n
\n )}\n \n );\n};\n\nexport default Navbar;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/ConfettiOverlay.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useRef } from \"react\";\nimport PropTypes from \"prop-types\";\nimport { confetti } from \"@tsparticles/confetti\";\n\nconst ConfettiOverlay = ({ isActive, onComplete }) => {\n const canvasRef = useRef(null);\n const animationRef = useRef(null);\n\n useEffect(() => {\n if (isActive && canvasRef.current) {\n const canvas = canvasRef.current;\n\n const randomInRange = (min, max) => {\n return Math.random() * (max - min) + min;\n };\n\n const triggerConfettiBlast = async () => {\n const defaultOptions = {\n angle: randomInRange(55, 125),\n spread: randomInRange(50, 70),\n particleCount: randomInRange(50, 100),\n origin: { y: 0.6 },\n canvas: canvas,\n };\n\n await confetti(defaultOptions);\n };\n\n triggerConfettiBlast();\n\n animationRef.current = setTimeout(() => {\n if (onComplete) {\n onComplete();\n }\n }, 500);\n } else if (!isActive && canvasRef.current) {\n const canvas = canvasRef.current;\n const ctx = canvas.getContext(\"2d\");\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n }\n\n return () => {\n if (animationRef.current) {\n clearTimeout(animationRef.current);\n }\n };\n }, [isActive, onComplete]);\n\n useEffect(() => {\n const updateCanvasSize = () => {\n if (canvasRef.current) {\n canvasRef.current.width = window.innerWidth;\n canvasRef.current.height = window.innerHeight;\n }\n };\n\n updateCanvasSize();\n window.addEventListener(\"resize\", updateCanvasSize);\n return () => window.removeEventListener(\"resize\", updateCanvasSize);\n }, []);\n\n return (\n \n );\n};\n\nConfettiOverlay.propTypes = {\n isActive: PropTypes.bool.isRequired,\n onComplete: PropTypes.func,\n};\n\nexport default ConfettiOverlay;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/ConfirmacaoModal.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[40,73],"text":""},"desc":"Remove unused variable 'X'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"// components/game/ConfirmacaoModal.jsx\nimport { X } from \"lucide-react\";\n\nexport default function ConfirmacaoModal({\n isVisible,\n onClose,\n onConfirm,\n titulo,\n mensagem,\n}) {\n if (!isVisible) return null;\n\n return (\n \n
e.stopPropagation()}\n >\n {/* Header */}\n
\n
{titulo} \n \n \n \n \n\n {/* Mensagem */}\n
{mensagem}
\n\n {/* Ações */}\n
\n \n Cancelar\n \n {\n onConfirm();\n onClose();\n }}\n >\n Confirmar\n \n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/FalhaModal.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'RefreshCw' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"RefreshCw"},"fix":{"range":[63,104],"text":""},"desc":"Remove unused variable 'RefreshCw'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ModalBase' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"ModalBase"},"fix":{"range":[106,152],"text":""},"desc":"Remove unused variable 'ModalBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ModalHeader' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"ModalHeader"},"fix":{"range":[153,203],"text":""},"desc":"Remove unused variable 'ModalHeader'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeArea' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeArea"},"fix":{"range":[204,248],"text":""},"desc":"Remove unused variable 'CodeArea'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'FeedbackBox' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"FeedbackBox"},"fix":{"range":[249,299],"text":""},"desc":"Remove unused variable 'FeedbackBox'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { RefreshCw } from \"lucide-react\";\n\nimport { ModalBase } from \"./modal/ModalBase\";\nimport { ModalHeader } from \"./modal/ModalHeader\";\nimport { CodeArea } from \"./modal/CodeArea\";\nimport { FeedbackBox } from \"./modal/FeedbackBox\";\n\nconst FalhaModal = ({\n isOpen,\n onClose,\n onRetry,\n mensagemCustomizada,\n currentPhase,\n codigoGerado,\n}) => {\n const handleRetry = () => {\n onClose();\n if (onRetry) {\n onRetry();\n }\n };\n\n const mensagemExibida =\n mensagemCustomizada ||\n \"Ops! Parece que algo não funcionou como esperado. Tente novamente!\";\n\n return (\n \n \n\n \n
\n
\n\n
\n\n
\n \n Revise o enunciado. \n Verifique se os blocos estão corretamente conectados. \n \n Certifique-se de que a lógica atende a todos os requisitos da\n fase.\n \n \n \n
\n
\n\n {/* 3. Rodapé (Botões de Ação) */}\n \n \n Fechar\n \n\n \n \n Tentar Novamente \n \n
\n \n );\n};\n\nFalhaModal.propTypes = {\n isOpen: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n onRetry: PropTypes.func,\n mensagemCustomizada: PropTypes.string,\n currentPhase: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n codigoGerado: PropTypes.string,\n};\n\nexport default FalhaModal;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameArea.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'ConfettiOverlay' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":23,"suggestions":[{"messageId":"removeVar","data":{"varName":"ConfettiOverlay"},"fix":{"range":[192,213],"text":""},"desc":"Remove unused variable 'ConfettiOverlay'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useEffect, useState, useRef } from \"react\";\nimport { useGameState, GAME_STATES } from \"../../contexts/GameStateContext\";\nimport { gameEventBus } from \"../../utils/gameEvents\";\nimport ConfettiOverlay from \"./ConfettiOverlay\";\n\nexport default function GameArea({\n children,\n blocosRestantes = null,\n faseId = null,\n}) {\n const {\n executionState,\n generatedCode,\n finalizeWithSuccess,\n finalizeWithFailure,\n } = useGameState();\n\n const [showConfetti, setShowConfetti] = useState(false);\n const [isTransitioning, setIsTransitioning] = useState(false);\n const previousFaseId = useRef(faseId);\n\n useEffect(() => {\n if (faseId !== null && faseId !== previousFaseId.current) {\n setIsTransitioning(true);\n\n const timer = setTimeout(() => {\n setIsTransitioning(false);\n previousFaseId.current = faseId;\n }, 600);\n\n return () => clearTimeout(timer);\n }\n }, [faseId]);\n\n useEffect(() => {\n const handleGameSuccess = () => {\n finalizeWithSuccess();\n setShowConfetti(true);\n };\n\n const handleGameFailure = () => {\n finalizeWithFailure();\n };\n\n gameEventBus.addEventListener(\"gameSuccess\", handleGameSuccess);\n gameEventBus.addEventListener(\"gameFailure\", handleGameFailure);\n\n return () => {\n gameEventBus.removeEventListener(\"gameSuccess\", handleGameSuccess);\n gameEventBus.removeEventListener(\"gameFailure\", handleGameFailure);\n };\n }, [finalizeWithSuccess, finalizeWithFailure]);\n\n useEffect(() => {\n switch (executionState) {\n case GAME_STATES.EXECUTANDO:\n if (generatedCode) {\n const codigo =\n typeof generatedCode === \"string\"\n ? generatedCode\n : generatedCode.codigo;\n const ws =\n typeof generatedCode === \"object\" ? generatedCode.workspace : null;\n\n gameEventBus.executeCode(codigo, ws);\n }\n break;\n case GAME_STATES.PARADO:\n gameEventBus.resetGame();\n setShowConfetti(false);\n break;\n }\n }, [executionState, generatedCode]);\n\n return (\n \n {/* Confetti de sucesso de uma fase */}\n
\n\n {/* Overlay de transição */}\n
\n\n {/* Indicador de blocos restantes */}\n {blocosRestantes !== null && !isNaN(blocosRestantes) && (\n
\n
\n Blocos restantes:{\" \"}\n {blocosRestantes} \n
\n
\n )}\n\n
\n {children}\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameBase.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Panel' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":15,"suggestions":[{"messageId":"removeVar","data":{"varName":"Panel"},"fix":{"range":[111,117],"text":""},"desc":"Remove unused variable 'Panel'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PanelGroup' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"PanelGroup"},"fix":{"range":[116,128],"text":""},"desc":"Remove unused variable 'PanelGroup'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameNavBar' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameNavBar"},"fix":{"range":[169,185],"text":""},"desc":"Remove unused variable 'GameNavBar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameFaseInfo' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameFaseInfo"},"fix":{"range":[208,226],"text":""},"desc":"Remove unused variable 'GameFaseInfo'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameArea' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameArea"},"fix":{"range":[251,265],"text":""},"desc":"Remove unused variable 'GameArea'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameFooter' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameFooter"},"fix":{"range":[286,302],"text":""},"desc":"Remove unused variable 'GameFooter'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SeletorDeFases' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"SeletorDeFases"},"fix":{"range":[325,345],"text":""},"desc":"Remove unused variable 'SeletorDeFases'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SucessoModal' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"SucessoModal"},"fix":{"range":[372,390],"text":""},"desc":"Remove unused variable 'SucessoModal'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'FalhaModal' is defined but never used. Allowed unused vars must match /^_/u.","line":10,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"FalhaModal"},"fix":{"range":[415,431],"text":""},"desc":"Remove unused variable 'FalhaModal'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ResizeHandle' is defined but never used. Allowed unused vars must match /^_/u.","line":11,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":11,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ResizeHandle"},"fix":{"range":[454,472],"text":""},"desc":"Remove unused variable 'ResizeHandle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'EditorProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":14,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":14,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"EditorProvider"},"fix":{"range":[622,684],"text":""},"desc":"Remove unused variable 'EditorProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBaseContent' is defined but never used. Allowed unused vars must match /^_/u.","line":16,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":16,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBaseContent"},"fix":{"range":[686,7736],"text":""},"desc":"Remove unused variable 'GameBaseContent'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'title' is defined but never used. Allowed unused args must match /^_/u.","line":271,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":271,"endColumn":8,"suggestions":[{"messageId":"removeVar","data":{"varName":"title"},"fix":{"range":[7800,7809],"text":""},"desc":"Remove unused variable 'title'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":13,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useRef, useCallback, useState } from \"react\";\nimport Phaser from \"phaser\";\nimport { Panel, PanelGroup } from \"react-resizable-panels\";\nimport GameNavBar from \"./GameNavBar\";\nimport GameFaseInfo from \"./GameFaseInfo\";\nimport GameArea from \"./GameArea\";\nimport GameFooter from \"./GameFooter\";\nimport SeletorDeFases from \"./SeletorDeFases\";\nimport SucessoModal from \"./SucessoModal\";\nimport FalhaModal from \"./FalhaModal\";\nimport ResizeHandle from \"./ResizeHandle\";\nimport { useIsMobile } from \"../../hooks/useIsMobile\";\nimport { useGameState, GAME_STATES } from \"../../contexts/GameStateContext\";\nimport { EditorProvider } from \"../../contexts/EditorContext\";\n\nfunction GameBaseContent({\n gameFactory,\n gameConfig,\n children,\n onHelpClick,\n customFailureHandler,\n}) {\n const gameContainerRef = useRef(null);\n const gameInstanceRef = useRef(null);\n const isInitializingRef = useRef(false);\n const isMobile = useIsMobile();\n const [modalFasesAberto, setModalFasesAberto] = useState(false);\n const [modalSucessoAberto, setModalSucessoAberto] = useState(false);\n const [modalFalhaAberto, setModalFalhaAberto] = useState(false);\n const [blocosRestantesCount, setBlocosRestantesCount] = useState(null);\n\n const {\n currentPhase,\n setCurrentPhase,\n resetProgress,\n executionState,\n generatedCode,\n failureMessage,\n restart,\n setOnWorkspaceChange,\n } = useGameState();\n\n const phaseConfig = gameConfig.fases[currentPhase - 1];\n const usaModalFalha = !!customFailureHandler;\n\n useEffect(() => {\n if (gameInstanceRef.current && gameContainerRef.current) {\n const phaserScale = gameInstanceRef.current.scale;\n phaserScale.resize(\n gameContainerRef.current.clientWidth,\n gameContainerRef.current.clientHeight,\n );\n }\n }, [isMobile]);\n\n useEffect(() => {\n if (isInitializingRef.current) {\n return;\n }\n\n isInitializingRef.current = true;\n\n if (gameInstanceRef.current) {\n try {\n gameInstanceRef.current.destroy(true);\n } catch (error) {\n console.warn(\"Erro ao destruir Phaser:\", error);\n }\n gameInstanceRef.current = null;\n }\n\n // Aguardar um frame para garantir que o cleanup foi concluído\n const timeoutId = setTimeout(() => {\n try {\n if (!gameContainerRef.current) {\n console.warn(\"Container do jogo não disponível\");\n isInitializingRef.current = false;\n return;\n }\n\n const config = gameFactory(\n gameContainerRef.current,\n phaseConfig,\n customFailureHandler,\n currentPhase,\n gameConfig,\n );\n gameInstanceRef.current = new Phaser.Game(config);\n } catch (error) {\n console.error(\"Erro ao inicializar Phaser:\", error);\n } finally {\n isInitializingRef.current = false;\n }\n }, 10); // Pequeno delay para garantir cleanup\n\n return () => {\n clearTimeout(timeoutId);\n\n if (gameInstanceRef.current) {\n try {\n gameInstanceRef.current.destroy(true);\n } catch (error) {\n console.warn(\"Erro durante cleanup do Phaser:\", error);\n }\n gameInstanceRef.current = null;\n }\n\n isInitializingRef.current = false;\n };\n }, [gameFactory, currentPhase, customFailureHandler]);\n\n const handleWorkspaceChange = useCallback(\n (blockCount) => {\n if (phaseConfig.maxBlocks === Infinity) {\n setBlocosRestantesCount(null);\n return;\n }\n const blocosUsados = typeof blockCount === \"number\" ? blockCount : 0;\n const blocosRestantes = phaseConfig.maxBlocks - blocosUsados;\n setBlocosRestantesCount(blocosRestantes);\n },\n [phaseConfig.maxBlocks],\n );\n\n useEffect(() => {\n if (setOnWorkspaceChange) {\n setOnWorkspaceChange(() => handleWorkspaceChange);\n }\n return () => {\n if (setOnWorkspaceChange) {\n setOnWorkspaceChange(null);\n }\n };\n }, [setOnWorkspaceChange, handleWorkspaceChange]);\n\n const handleResetProgresso = () => {\n resetProgress();\n\n window.dispatchEvent(\n new CustomEvent(\"resetBlocklyWorkspace\", {\n detail: { gameId: gameConfig.gameId },\n }),\n );\n };\n\n useEffect(() => {\n if (executionState === GAME_STATES.SUCESSO) {\n setModalSucessoAberto(true);\n }\n\n if (executionState === GAME_STATES.FALHA && usaModalFalha) {\n setModalFalhaAberto(true);\n }\n }, [executionState, usaModalFalha]);\n\n const handleProximaFase = () => {\n const proximaFase = currentPhase + 1;\n if (proximaFase <= gameConfig.fases.length) {\n setCurrentPhase(proximaFase);\n }\n\n setModalSucessoAberto(false);\n restart();\n };\n\n const handleFecharModalSucesso = () => {\n setModalSucessoAberto(false);\n };\n\n const handleFecharModalFalha = () => {\n setModalFalhaAberto(false);\n };\n\n const handleTentarNovamente = () => {\n setModalFalhaAberto(false);\n restart();\n };\n\n const codigoParaExibir = React.useMemo(() => {\n if (!generatedCode) return \"Nenhum código gerado\";\n\n let codigo = \"\";\n\n if (typeof generatedCode === \"string\") {\n codigo = generatedCode;\n } else if (typeof generatedCode === \"object\" && generatedCode.codigo) {\n codigo = generatedCode.codigo;\n } else {\n return \"Código não disponível\";\n }\n\n const codigoLimpo = codigo\n .split(\"\\n\")\n .filter((linha) => !linha.trim().startsWith(\"highlightBlock(\"))\n .join(\"\\n\")\n .trim();\n\n return codigoLimpo || codigo;\n }, [generatedCode]);\n\n return (\n \n
\n
\n
\n
\n \n \n {children}\n \n \n \n \n \n
\n \n \n \n
\n\n
setModalFasesAberto(true)}\n onHelpClick={onHelpClick}\n />\n\n setModalFasesAberto(false)}\n faseAtual={currentPhase}\n gameConfig={gameConfig}\n onMudarFase={(fase) => {\n setCurrentPhase(fase);\n setModalFasesAberto(false);\n }}\n onResetProgresso={handleResetProgresso}\n />\n\n \n\n \n \n );\n}\n\nexport default function GameBase({\n gameFactory,\n gameConfig,\n title,\n children,\n onHelpClick,\n customFailureHandler,\n}) {\n return (\n \n {children}\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameEditor.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Play' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Play"},"fix":{"range":[9,14],"text":""},"desc":"Remove unused variable 'Play'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Loader' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"Loader"},"fix":{"range":[13,21],"text":""},"desc":"Remove unused variable 'Loader'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'RotateCcw' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":24,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":33,"suggestions":[{"messageId":"removeVar","data":{"varName":"RotateCcw"},"fix":{"range":[21,32],"text":""},"desc":"Remove unused variable 'RotateCcw'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CircleAlert' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":35,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":46,"suggestions":[{"messageId":"removeVar","data":{"varName":"CircleAlert"},"fix":{"range":[32,45],"text":""},"desc":"Remove unused variable 'CircleAlert'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Play, Loader, RotateCcw, CircleAlert } from \"lucide-react\";\nimport { useGameState, GAME_STATES } from \"../../contexts/GameStateContext\";\n\nexport default function GameEditor({\n children,\n textoExecutar = \"Executar\",\n textoReiniciar = \"Reiniciar\",\n}) {\n const {\n executionState,\n execute,\n restart,\n stop,\n currentBlockCount,\n editorType,\n } = useGameState();\n\n const isExecuting = executionState === GAME_STATES.EXECUTANDO;\n const needsRestart =\n executionState === GAME_STATES.SUCESSO ||\n executionState === GAME_STATES.FALHA;\n const noBlocks = currentBlockCount === 0;\n\n const handleClick = () => {\n if (noBlocks && !isExecuting && !needsRestart) {\n return; // Não faz nada se não há blocos\n }\n\n if (isExecuting) {\n stop();\n return;\n }\n\n if (needsRestart) {\n restart();\n } else {\n execute();\n }\n };\n\n const setStyle = () => {\n const style =\n \"game-controls-custom flex items-center space-x-2 px-6 py-3 rounded-full font-medium transition-all duration-200 shadow-md\";\n\n if (noBlocks) {\n return `${style} bg-gradient-to-r from-yellow-700 to-yellow-500 cursor-not-allowed text-white`;\n }\n\n if (isExecuting) {\n return `${style} bg-gradient-to-r from-blue-500 to-green-600 hover:from-blue-600 hover:to-green-700 text-white`;\n }\n\n if (needsRestart) {\n return `${style} bg-gradient-to-r from-orange-500 to-red-600 hover:from-orange-600 hover:to-red-700 text-white`;\n }\n\n return `${style} bg-gradient-to-r from-red-500 via-pink-500 to-purple-600 hover:from-red-600 hover:via-pink-600 hover:to-purple-700 text-white`;\n };\n\n const getEmptyStateText = () => {\n return editorType === \"code\"\n ? \"Adicione código para executar\"\n : \"Adicione blocos para executar\";\n };\n\n return (\n \n
{children}
\n
\n
\n {isExecuting ? (\n <>\n \n Executando, clique para interromper... \n >\n ) : needsRestart ? (\n <>\n \n {textoReiniciar} \n >\n ) : noBlocks ? (\n <>\n \n {getEmptyStateText()} \n >\n ) : (\n <>\n \n {textoExecutar} \n >\n )}\n \n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameFaseInfo.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\n\nfunction obterDificuldade(dadosFase) {\n // Usa a dificuldade da fase, se existir, senão calcula pelo número\n if (dadosFase?.dificuldade) {\n switch (dadosFase.dificuldade) {\n case \"Fácil\":\n return { nivel: \"Fácil\", cor: \"bg-green-500\", emoji: \"😊\" };\n case \"Médio\":\n return { nivel: \"Médio\", cor: \"bg-yellow-500\", emoji: \"🤔\" };\n case \"Difícil\":\n return { nivel: \"Difícil\", cor: \"bg-orange-500\", emoji: \"😤\" };\n case \"Extremo\":\n return { nivel: \"Extremo\", cor: \"bg-red-500\", emoji: \"🔥\" };\n default:\n return null;\n }\n }\n return null;\n}\n\nfunction GameFaseInfo({ dadosFase = {}, numeroFase }) {\n const dificuldade = obterDificuldade(dadosFase);\n\n return (\n \n {dadosFase && dadosFase.nome ? (\n
\n {/* Número da fase */}\n
\n {numeroFase}\n
\n {/* Título/Subtítulo */}\n
\n
\n {dadosFase.nome}\n \n {dadosFase.descricao && (\n
\n {dadosFase.descricao}\n
\n )}\n
\n {/* Dificuldade */}\n
\n {dificuldade && (\n
\n {dificuldade.emoji} \n {dificuldade.nivel} \n
\n )}\n
\n
\n ) : (\n
\n
\n ?\n
\n
\n
\n Selecione uma fase para começar\n
\n
\n
\n
\n )}\n
\n );\n}\n\nexport default GameFaseInfo;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameFooter.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\n\nexport default function GameFooter({\n gameConfig,\n faseAtual,\n onAbrirSeletor,\n onHelpClick,\n}) {\n const totalFases = gameConfig.fases.length;\n\n const ajuda = () => {\n if (onHelpClick) {\n onHelpClick();\n } else {\n alert(\"Recurso de ajuda será implementado em breve!\");\n }\n };\n\n return (\n \n
\n {/* Lado esquerdo - Botão de Ajuda */}\n
\n \n Ajuda\n \n
\n {/* Centro - Indicador de Fase Atual/Total */}\n
\n
\n \n {faseAtual} \n / \n {totalFases} \n \n
\n
\n {/* Lado direito - Botão do Seletor de Fases */}\n
\n \n Fases\n \n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/GameNavBar.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Code' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Code"},"fix":{"range":[140,145],"text":""},"desc":"Remove unused variable 'Code'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Puzzle' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"Puzzle"},"fix":{"range":[144,152],"text":""},"desc":"Remove unused variable 'Puzzle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowLeft' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowLeft"},"fix":{"range":[176,217],"text":""},"desc":"Remove unused variable 'ArrowLeft'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport logo from \"../../assets/logo_decoda.svg\";\nimport { Code, Puzzle } from \"lucide-react\";\nimport { ArrowLeft } from \"lucide-react\";\n\nexport default function GameNavBar({ title, type = \"blocks\" }) {\n const navigate = useNavigate();\n const [menuOpen, setMenuOpen] = useState(false);\n\n const renderIcon = (size = \"w-6 h-6 lg:w-8 lg:h-8\") => {\n if (type === \"code\") {\n return ;\n } else {\n return ;\n }\n };\n\n return (\n <>\n {/* Navbar normal - oculto em telas pequenas */}\n \n \n
\n
navigate(\"/\")}\n title=\"Ir para Home\"\n className=\"header-logo group cursor-pointer flex items-center space-x-2\"\n >\n
\n
\n
\n
\n
\n
\n {renderIcon(\"w-8 h-8\")}\n
{title} \n \n
\n
\n \n\n {/* Botão menu flutuante - só aparece em telas pequenas */}\n setMenuOpen(true)}\n >\n \n \n \n \n \n \n\n {/* Overlay do menu mobile */}\n {menuOpen && (\n \n
\n
\n
\n setMenuOpen(false)}\n className=\"text-brand-500 text-3xl\"\n aria-label=\"Fechar menu\"\n >\n ×\n \n
\n
\n {type === \"code\" &&
}\n {type === \"blocks\" && (\n
\n )}\n
{title} \n
\n
{\n setMenuOpen(false);\n navigate(\"/\");\n }}\n className=\"flex items-center gap-5 mt-10\"\n >\n \n Voltar \n \n
\n
{\n setMenuOpen(false);\n navigate(\"/\");\n }}\n >\n {\n e.target.style.display = \"none\";\n e.target.nextSibling.style.display = \"inline\";\n }}\n />\n \n
\n
\n )}\n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/OptionsEditor.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/ResizeHandle.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'PanelResizeHandle' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"PanelResizeHandle"},"fix":{"range":[0,59],"text":""},"desc":"Remove unused variable 'PanelResizeHandle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { PanelResizeHandle } from \"react-resizable-panels\";\n\nexport default function ResizeHandle({\n direction = \"horizontal\",\n theme = \"light\",\n disabled = false,\n}) {\n const isHorizontal = direction === \"horizontal\";\n const isDark = theme === \"dark\";\n\n return (\n \n \n \n \n \n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/SeletorDeFases.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ConfirmacaoModal' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"ConfirmacaoModal"},"fix":{"range":[59,81],"text":""},"desc":"Remove unused variable 'ConfirmacaoModal'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Lock' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Lock"},"fix":{"range":[112,117],"text":""},"desc":"Remove unused variable 'Lock'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CheckCircle' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"CheckCircle"},"fix":{"range":[116,129],"text":""},"desc":"Remove unused variable 'CheckCircle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Star' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":29,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":33,"suggestions":[{"messageId":"removeVar","data":{"varName":"Star"},"fix":{"range":[129,135],"text":""},"desc":"Remove unused variable 'Star'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":35,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":36,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[135,138],"text":""},"desc":"Remove unused variable 'X'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useState } from \"react\";\nimport ConfirmacaoModal from \"./ConfirmacaoModal\";\nimport { Lock, CheckCircle, Star, X } from \"lucide-react\";\n\nexport default function SeletorDeFases({\n isVisible,\n onClose,\n gameConfig,\n faseAtual,\n onMudarFase,\n onResetProgresso,\n}) {\n const gameId = gameConfig.gameId;\n const totalFases = gameConfig.fases.length;\n const storageKey = `${gameId}-fases-concluidas`;\n const [fasesCompletadas, setFasesCompletadas] = useState([]);\n const [mostrarConfirmacaoReset, setMostrarConfirmacaoReset] = useState(false);\n\n useEffect(() => {\n if (!isVisible) return;\n const salvo = localStorage.getItem(storageKey);\n if (salvo) {\n try {\n const fases = JSON.parse(salvo);\n setFasesCompletadas(fases);\n } catch {\n setFasesCompletadas([]);\n }\n } else {\n setFasesCompletadas([]);\n }\n }, [isVisible, storageKey]);\n\n const fasesLiberadas = (() => {\n if (fasesCompletadas.length === 0) return [1];\n const maxCompleta = Math.max(...fasesCompletadas);\n return Array.from(\n { length: Math.min(maxCompleta + 1, totalFases) },\n (_, i) => i + 1,\n );\n })();\n\n const selecionarFase = (numero) => {\n if (!fasesLiberadas.includes(numero)) return;\n onMudarFase(numero);\n onClose();\n };\n\n if (!isVisible) return null;\n\n return (\n \n
e.stopPropagation()}\n >\n
\n
\n
\n \n
\n
\n Selecionar Fase - {gameConfig.gameName || \"Jogo\"}\n \n
\n
\n \n \n
\n\n
\n
\n {Array.from({ length: totalFases }, (_, i) => {\n const numeroFase = i + 1;\n const estaLiberada = fasesLiberadas.includes(numeroFase);\n const foiCompletada = fasesCompletadas.includes(numeroFase);\n const ehAtual = faseAtual === numeroFase;\n const dadosFase = gameConfig.fases[i];\n\n return (\n
estaLiberada && selecionarFase(numeroFase)}\n >\n
\n
\n
\n {foiCompletada ? (\n \n ) : !estaLiberada ? (\n \n ) : (\n numeroFase\n )}\n
\n
\n {ehAtual && (\n
\n Atual\n \n )}\n
\n\n
\n
\n Fase {numeroFase}\n \n
\n {dadosFase.nome}\n \n
\n {dadosFase.descricao}\n
\n
\n {dadosFase.dificuldade}\n \n
\n
\n );\n })}\n
\n
\n\n
\n setMostrarConfirmacaoReset(true)}\n >\n Resetar TODO o progresso do jogo\n \n
\n\n
setMostrarConfirmacaoReset(false)}\n onConfirm={() => {\n onResetProgresso();\n setMostrarConfirmacaoReset(false);\n onClose();\n }}\n titulo=\"Resetar progresso\"\n mensagem=\"Tem certeza que deseja apagar TODO o progresso e blocos salvos deste jogo? Esta ação não pode ser desfeita.\"\n />\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/SucessoModal.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ChevronRight' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":22,"suggestions":[{"messageId":"removeVar","data":{"varName":"ChevronRight"},"fix":{"range":[63,107],"text":""},"desc":"Remove unused variable 'ChevronRight'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ModalBase' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"ModalBase"},"fix":{"range":[109,155],"text":""},"desc":"Remove unused variable 'ModalBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ModalHeader' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"ModalHeader"},"fix":{"range":[156,206],"text":""},"desc":"Remove unused variable 'ModalHeader'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeArea' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeArea"},"fix":{"range":[207,251],"text":""},"desc":"Remove unused variable 'CodeArea'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'FeedbackBox' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"FeedbackBox"},"fix":{"range":[252,302],"text":""},"desc":"Remove unused variable 'FeedbackBox'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { ModalBase } from \"./modal/ModalBase\";\nimport { ModalHeader } from \"./modal/ModalHeader\";\nimport { CodeArea } from \"./modal/CodeArea\";\nimport { FeedbackBox } from \"./modal/FeedbackBox\";\n\nconst SucessoModal = ({\n isOpen,\n onClose,\n onNextPhase,\n codigoGerado,\n canGoNext,\n}) => {\n const handleNextPhase = () => {\n onClose();\n if (onNextPhase) {\n onNextPhase();\n }\n };\n\n return (\n \n \n\n \n
\n
\n\n
\n \n Os blocos que você conectou foram convertidos em código JavaScript\n real. Este é o mesmo tipo de código que os programadores usam para\n criar aplicações!\n
\n \n
\n
\n\n {/* 3. Rodapé (Botões de Ação) */}\n \n \n Fechar\n \n\n {canGoNext && (\n \n Próxima Fase \n \n \n )}\n
\n \n );\n};\n\nSucessoModal.propTypes = {\n isOpen: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n onNextPhase: PropTypes.func,\n codigoGerado: PropTypes.any,\n canGoNext: PropTypes.bool.isRequired,\n};\n\nexport default SucessoModal;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/editors/BlocklyEditor.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Hammer' is defined but never used. Allowed unused vars must match /^_/u.","line":21,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":21,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"Hammer"},"fix":{"range":[617,655],"text":""},"desc":"Remove unused variable 'Hammer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'LoadingSpinner' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":25,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":25,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"LoadingSpinner"},"fix":{"range":[722,873],"text":""},"desc":"Remove unused variable 'LoadingSpinner'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":52,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":52,"endColumn":21},{"ruleId":"no-unused-vars","severity":1,"message":"'currentBlockCount' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":78,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":78,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"currentBlockCount"},"fix":{"range":[2390,2407],"text":""},"desc":"Remove unused variable 'currentBlockCount'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, {\n useEffect,\n useRef,\n forwardRef,\n useImperativeHandle,\n useState,\n useCallback,\n useMemo,\n} from \"react\";\nimport * as Blockly from \"blockly/core\";\nimport Theme from \"@blockly/theme-modern\";\nimport { javascriptGenerator } from \"blockly/javascript\";\nimport { useGameState } from \"../../../contexts/GameStateContext\";\nimport { validateBlocklyWorkspace } from \"../../../utils/blocklyValidation\";\nimport { useEditor } from \"../../../contexts/EditorContext\";\nimport {\n loadWorkspace,\n createDebouncedSave,\n} from \"../../../services/blockstorage\";\nimport { getCategoryIcon } from \"./toolboxIcons\";\nimport { Hammer } from \"lucide-react\";\nimport \"./custom_category\";\nimport \"./BlocklyEditor.mobile.css\";\n\nconst LoadingSpinner = () => (\n \n
Carregando Editor...
\n
\n);\n\nconst BlocklyEditor = forwardRef(function BlocklyEditor(\n { toolboxGenerator, debugSolutions = null, starterBlocks = null },\n ref,\n) {\n const { registerExecutionFunction, onWorkspaceChange } = useGameState();\n const { gameConfig, faseAtual, editorData, updateEditorData, gameNameKey } =\n useEditor();\n\n const hasSolution = debugSolutions && debugSolutions[faseAtual];\n\n const handleLoadSolution = () => {\n if (!workspaceRef.current) {\n alert(\"Editor não está pronto\");\n return;\n }\n\n if (window.confirm(\"Deseja carregar a solução desta fase?\")) {\n try {\n const solution = debugSolutions[faseAtual];\n workspaceRef.current.clear();\n Blockly.serialization.workspaces.load(solution, workspaceRef.current);\n } catch (error) {\n alert(\"Erro ao carregar a solução\");\n }\n }\n };\n\n useEffect(() => {\n if (!gameConfig || !toolboxGenerator) return;\n\n const currentPhase = gameConfig.fases[faseAtual - 1];\n const toolbox = toolboxGenerator(currentPhase.allowedBlocks);\n const max = currentPhase.maxBlocks;\n\n updateEditorData({\n toolboxJson: toolbox,\n maxBlocks: max,\n });\n }, [gameConfig, faseAtual, toolboxGenerator, updateEditorData]);\n\n const { toolboxJson, maxBlocks } = editorData;\n\n const blocklyDiv = useRef(null);\n const workspaceRef = useRef(null);\n const [initialJson, setInitialJson] = useState(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const isInitializedRef = useRef(false);\n const [currentBlockCount, setCurrentBlockCount] = useState(0);\n\n const debouncedSave = useMemo(() => createDebouncedSave(1000), []);\n const stableToolboxJson = useMemo(() => toolboxJson, [toolboxJson]);\n\n const updateCategoriesState = useCallback((limitReached) => {\n if (!workspaceRef.current) return;\n\n const toolbox = workspaceRef.current.getToolbox();\n if (!toolbox) return;\n\n const categories = toolbox.getToolboxItems();\n\n categories.forEach((category) => {\n category.setDisabled(limitReached);\n });\n }, []);\n\n const workspaceChange = useCallback(() => {\n if (!workspaceRef.current) return;\n const blockCount = workspaceRef.current.getAllBlocks().length;\n setCurrentBlockCount(blockCount);\n\n const limitReached =\n maxBlocks !== undefined &&\n maxBlocks !== Infinity &&\n maxBlocks <= blockCount;\n updateCategoriesState(limitReached);\n\n if (onWorkspaceChange) {\n onWorkspaceChange(blockCount);\n }\n }, [onWorkspaceChange, maxBlocks, updateCategoriesState]);\n\n useEffect(() => {\n updateCategoriesState();\n }, [updateCategoriesState]);\n\n useEffect(() => {\n const generateAndValidateCode = () => {\n if (!workspaceRef.current) {\n return { codigo: null, workspace: null };\n }\n const validation = validateBlocklyWorkspace(workspaceRef.current, {\n allowMultipleTopBlocks: false,\n preferredStartBlocks: [\"start\", \"when_run\", \"main\"],\n });\n if (!validation.isValid) {\n return { codigo: null, workspace: null };\n }\n const codigo = javascriptGenerator.workspaceToCode(workspaceRef.current);\n return { codigo, workspace: workspaceRef.current };\n };\n registerExecutionFunction(generateAndValidateCode);\n return () => {\n registerExecutionFunction(null);\n };\n }, [registerExecutionFunction]);\n\n useEffect(() => {\n setIsLoading(true);\n const loadedData = loadWorkspace(gameNameKey);\n setInitialJson(loadedData);\n setIsLoading(false);\n }, [gameNameKey]);\n\n useImperativeHandle(ref, () => workspaceRef.current, []);\n\n useEffect(() => {\n if (\n isLoading ||\n isInitializedRef.current ||\n !blocklyDiv.current ||\n !stableToolboxJson\n ) {\n return;\n }\n isInitializedRef.current = true;\n\n const toolboxWithIcons = {\n ...stableToolboxJson,\n contents: stableToolboxJson.contents.map((cat) => ({\n ...cat,\n \"css-icon\": getCategoryIcon(cat.name),\n })),\n };\n\n workspaceRef.current = Blockly.inject(blocklyDiv.current, {\n toolbox: toolboxWithIcons,\n trashcan: true,\n scrollbars: true,\n renderer: \"zelos\",\n theme: Theme,\n grid: { spacing: 25, length: 3, colour: \"#ccc\", snap: true },\n zoom: { controls: false, wheel: true, startScale: 0.7 },\n });\n\n // Criar variáveis pré-definidas para o jogo Cripto\n if (gameConfig?.gameId === \"cripto\") {\n [\"entrada\", \"saida\", \"pos\"].forEach((varName) => {\n const variableMap = workspaceRef.current.getVariableMap();\n if (!variableMap.getVariable(varName)) {\n workspaceRef.current.createVariable(varName);\n }\n });\n }\n\n // Carregar workspace: prioridade = localStorage > starterBlocks > vazio\n if (initialJson) {\n try {\n Blockly.serialization.workspaces.load(\n initialJson,\n workspaceRef.current,\n );\n } catch (error) {\n console.error(\n \"Falha ao carregar workspace do localStorage, limpando.\",\n error,\n );\n workspaceRef.current.clear();\n }\n } else if (starterBlocks && starterBlocks[faseAtual]) {\n // Se não há nada salvo, carregar blocos iniciais da fase\n try {\n console.log(`Carregando blocos iniciais para fase ${faseAtual}`);\n Blockly.serialization.workspaces.load(\n starterBlocks[faseAtual],\n workspaceRef.current,\n );\n } catch (error) {\n console.error(\"Falha ao carregar blocos iniciais.\", error);\n }\n }\n\n workspaceChange();\n\n const listener = (event) => {\n if (!workspaceRef.current || event.isUiEvent) {\n return;\n }\n\n if (event.type === Blockly.Events.BLOCK_CREATE) {\n const currentCount = workspaceRef.current.getAllBlocks().length;\n if (\n maxBlocks !== undefined &&\n maxBlocks !== Infinity &&\n currentCount > maxBlocks\n ) {\n const blockToRemove = workspaceRef.current.getBlockById(\n event.blockId,\n );\n if (blockToRemove) {\n console.warn(\n `Limite de blocos atingido (${maxBlocks}). Removendo bloco extra: ${event.blockId}`,\n );\n\n Blockly.Events.disable();\n blockToRemove.dispose(false);\n Blockly.Events.enable();\n return;\n }\n }\n }\n\n workspaceChange();\n const currentState = Blockly.serialization.workspaces.save(\n workspaceRef.current,\n );\n debouncedSave(gameNameKey, currentState);\n };\n\n workspaceRef.current.addChangeListener(listener);\n\n const observer = new ResizeObserver(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n });\n observer.observe(blocklyDiv.current);\n\n setTimeout(() => {\n updateCategoriesState();\n const bg = document.querySelector(\".blocklyToolboxBackground\");\n if (bg) {\n bg.setAttribute(\"fill\", \"none\");\n bg.setAttribute(\"stroke\", \"none\");\n }\n }, 100);\n\n return () => {\n debouncedSave.cancel();\n if (workspaceRef.current) {\n workspaceRef.current.removeChangeListener(listener);\n try {\n workspaceRef.current.dispose();\n } catch (error) {\n console.warn(\"Erro ao fazer dispose do workspace:\", error);\n }\n workspaceRef.current = null;\n }\n observer.disconnect();\n isInitializedRef.current = false;\n };\n }, [\n isLoading,\n initialJson,\n gameNameKey,\n stableToolboxJson,\n debouncedSave,\n workspaceChange,\n maxBlocks,\n updateCategoriesState,\n ]);\n\n if (isLoading || !toolboxJson) {\n return ;\n }\n\n return (\n \n {hasSolution && (\n
\n \n \n )}\n
\n
\n );\n});\n\nexport default React.memo(BlocklyEditor);\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/editors/CodeEditor.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeMirror' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeMirror"},"fix":{"range":[68,84],"text":""},"desc":"Remove unused variable 'CodeMirror'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'insertTab' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":25,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":34,"suggestions":[{"messageId":"removeVar","data":{"varName":"insertTab"},"fix":{"range":[248,259],"text":""},"desc":"Remove unused variable 'insertTab'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect, useMemo } from \"react\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport { javascript } from \"@codemirror/lang-javascript\";\nimport { autocompletion } from \"@codemirror/autocomplete\";\nimport { indentWithTab, insertTab } from \"@codemirror/commands\";\nimport { keymap } from \"@codemirror/view\";\nimport { indentOnInput, indentUnit } from \"@codemirror/language\";\nimport { useGameState } from \"../../../contexts/GameStateContext\";\nimport { useEditor } from \"../../../contexts/EditorContext\";\nimport {\n loadCode,\n createDebouncedCodeSave,\n} from \"../../../services/codestorage\";\n\nconst createGameCompletion = (gameConfig) => {\n return autocompletion({\n override: [\n (context) => {\n const word = context.matchBefore(/\\w*/);\n if (!word || (word.from === word.to && !context.explicit)) return null;\n\n const allowedFunctions = gameConfig?.allowedFunctions || [];\n const functionDocs = gameConfig?.functionDocumentation || {};\n const allowedStructures = gameConfig?.allowedControlStructures || [];\n\n const functionOptions = allowedFunctions.map((funcName) => {\n const doc = functionDocs[funcName] || {};\n return {\n label: funcName,\n type: \"function\",\n info: doc.description || `Função ${funcName}`,\n detail: doc.syntax || `${funcName}()`,\n apply: doc.example || `${funcName}()`,\n };\n });\n\n const structureOptions = allowedStructures.map((structure) => {\n const templates = {\n if: \"if (${condition}) {\\n ${code}\\n}\",\n else: \"else {\\n ${code}\\n}\",\n while: \"while (${condition}) {\\n ${code}\\n}\",\n for: \"for (${init}; ${condition}; ${update}) {\\n ${code}\\n}\",\n var: \"var ${name} = ${value};\",\n function: \"function ${name}() {\\n ${code}\\n}\",\n };\n\n return {\n label: structure,\n type: \"keyword\",\n info: `Estrutura de controle ${structure}`,\n detail: templates[structure] || structure,\n apply: templates[structure] || structure,\n };\n });\n\n return {\n from: word.from,\n options: [...functionOptions, ...structureOptions],\n };\n },\n ],\n });\n};\n\nexport default function CodeEditor() {\n const {\n registerCodeEditorFunction,\n onCodeEditorChange,\n gameConfig,\n currentPhase,\n } = useGameState();\n const faseAtual = currentPhase;\n const { gameNameKey } = useEditor();\n\n const faseConfig = gameConfig?.fases?.find((fase) => fase.id === faseAtual);\n const initialCode = faseConfig?.initialCode || \"// Digite seu código aqui\";\n\n const [code, setCode] = useState(initialCode);\n const [isLoading, setIsLoading] = useState(true);\n\n const debouncedCodeSave = useMemo(() => createDebouncedCodeSave(1000), []);\n const codeStorageKey = `${gameNameKey}-code`;\n\n useEffect(() => {\n setIsLoading(true);\n\n const savedCode = loadCode(codeStorageKey);\n if (savedCode !== null) {\n setCode(savedCode);\n } else {\n setCode(initialCode);\n }\n\n setIsLoading(false);\n }, [codeStorageKey, initialCode]);\n\n useEffect(() => {\n const getCodeFromEditor = () => {\n return code;\n };\n\n registerCodeEditorFunction(getCodeFromEditor);\n\n return () => {\n registerCodeEditorFunction(null);\n };\n }, [code, registerCodeEditorFunction]);\n\n useEffect(() => {\n if (!isLoading) {\n onCodeEditorChange(code);\n }\n }, [code, onCodeEditorChange, isLoading]);\n\n const handleChange = (value) => {\n setCode(value);\n\n if (!isLoading) {\n debouncedCodeSave(codeStorageKey, value);\n }\n };\n\n useEffect(() => {\n return () => {\n debouncedCodeSave.cancel();\n };\n }, [debouncedCodeSave]);\n\n return (\n \n {\n const { from } = state.selection.main;\n const line = state.doc.lineAt(from);\n const lineText = line.text;\n const indent = lineText.match(/^\\s*/)[0];\n\n let newIndent = indent;\n if (lineText.trim().endsWith(\"{\")) {\n newIndent += \" \";\n }\n\n dispatch(\n state.update({\n changes: {\n from,\n insert: \"\\n\" + newIndent,\n },\n selection: { anchor: from + newIndent.length + 1 },\n }),\n );\n\n return true;\n },\n },\n ]),\n ]}\n onChange={handleChange}\n basicSetup={{\n lineNumbers: true,\n highlightActiveLineGutter: true,\n highlightSpecialChars: true,\n history: true,\n foldGutter: true,\n drawSelection: true,\n dropCursor: true,\n allowMultipleSelections: true,\n indentOnInput: true,\n bracketMatching: true,\n closeBrackets: true,\n autocompletion: true,\n highlightSelectionMatches: false,\n }}\n className=\"h-full\"\n />\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/editors/OptionsEditor.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/editors/custom_category.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/editors/toolboxIcons.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/modal/CodeArea.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Code' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Code"},"fix":{"range":[27,63],"text":""},"desc":"Remove unused variable 'Code'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport { Code } from \"lucide-react\";\n\nexport const CodeArea = ({\n code,\n title = \"Código Gerado\",\n variant = \"success\",\n}) => {\n const textColors = {\n success: \"text-green-400\",\n failure: \"text-red-300\",\n };\n\n return (\n \n
\n \n
{title} \n \n\n
\n
\n {code || \"Nenhum código disponível\"}\n \n
\n
\n );\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/modal/FeedbackBox.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\n\nexport const FeedbackBox = ({ title, children, variant = \"success\" }) => {\n const styles = {\n success: \"bg-blue-50 border-blue-200 text-blue-800\",\n failure: \"bg-amber-50 border-amber-200 text-amber-800\",\n };\n\n const titleStyles = {\n success: \"text-blue-900\",\n failure: \"text-amber-900\",\n };\n\n return (\n \n
\n {variant === \"success\" ? \"💡\" : \"⚠️\"} {title}\n \n
{children}
\n
\n );\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/modal/ModalBase.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[27,60],"text":""},"desc":"Remove unused variable 'X'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport { X } from \"lucide-react\";\n\nexport const ModalBase = ({ isOpen, onClose, children }) => {\n if (!isOpen) return null;\n\n return (\n e.target === e.currentTarget && onClose()}\n >\n
\n {children}\n
\n
\n );\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/components/game/modal/ModalHeader.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[36,38],"text":""},"desc":"Remove unused variable 'X'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'isSuccess' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":10,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"isSuccess"},"fix":{"range":[183,223],"text":""},"desc":"Remove unused variable 'isSuccess'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Icon' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":26,"column":31,"nodeType":"Identifier","messageId":"unusedVar","endLine":26,"endColumn":35,"suggestions":[{"messageId":"removeVar","data":{"varName":"Icon"},"fix":{"range":[540,546],"text":""},"desc":"Remove unused variable 'Icon'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport { X, CheckCircle, AlertCircle } from \"lucide-react\";\n\nexport const ModalHeader = ({\n title,\n subTitle,\n variant = \"success\",\n onClose,\n}) => {\n const isSuccess = variant === \"success\";\n\n // Mapeamento de estilos por variante\n const config = {\n success: {\n bgColor: \"bg-green-100\",\n iconColor: \"text-green-600\",\n Icon: CheckCircle,\n },\n failure: {\n bgColor: \"bg-red-100\",\n iconColor: \"text-red-600\",\n Icon: AlertCircle,\n },\n };\n\n const { bgColor, iconColor, Icon } = config[variant];\n\n return (\n \n
\n
\n \n
\n
\n
{title} \n
{subTitle}
\n
\n
\n
\n \n \n
\n );\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/config/blocklyConfig.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/config/categories.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/config/difficulty.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/config/gameRegistry.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/config/type.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/contexts/EditorContext.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/contexts/GameStateContext.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, {\n createContext,\n useContext,\n useEffect,\n useState,\n useCallback,\n useRef,\n} from \"react\";\n\nexport const GAME_STATES = {\n PARADO: \"parado\",\n EXECUTANDO: \"executando\",\n SUCESSO: \"sucesso\",\n FALHA: \"falha\",\n};\n\nconst GameStateContext = createContext();\n\nexport function GameStateProvider({ children, gameConfig }) {\n const [executionState, setExecutionState] = useState(GAME_STATES.PARADO);\n const [generatedCode, setGeneratedCode] = useState(\"\");\n const [currentBlockCount, setCurrentBlockCount] = useState(0);\n const [onWorkspaceChangeCallback, setOnWorkspaceChangeCallback] =\n useState(null);\n const [editorType, setEditorType] = useState(\"blockly\"); // 'blockly' ou 'code'\n const [codeEditorContent, setCodeEditorContent] = useState(\"\");\n const [failureMessage, setFailureMessage] = useState(\"\");\n const [isDebugMode, setIsDebugMode] = useState(false);\n \n const [currentPhase, setCurrentPhase] = useState(1);\n const [completedPhases, setCompletedPhases] = useState([]);\n\n const storageKey = `${gameConfig.gameId}-fases-concluidas`;\n const initialized = useRef(false);\n\n const getCodeFromWorkspace = useRef(null);\n const getCodeFromEditor = useRef(null);\n\n useEffect(() => {\n const urlParams = new URLSearchParams(window.location.search);\n const debugKey = Array.from(urlParams.keys()).find(\n (key) => key.toLowerCase() === \"debug\",\n );\n const isDebug = urlParams.get(debugKey)?.toLowerCase() === \"true\";\n\n if (isDebug) {\n setIsDebugMode(true);\n const todas = Array.from(\n { length: gameConfig.fases.length },\n (_, i) => i + 1,\n );\n setCompletedPhases(todas);\n localStorage.setItem(storageKey, JSON.stringify(todas));\n setCurrentPhase(gameConfig.fases.length);\n return;\n }\n\n if (initialized.current) return;\n\n const saved = localStorage.getItem(storageKey);\n\n if (saved) {\n const fasesSalvas = JSON.parse(saved);\n\n setCompletedPhases(fasesSalvas);\n\n if (fasesSalvas.length > 0) {\n const ultimaFaseConcluida = Math.max(...fasesSalvas);\n const proximaFase = ultimaFaseConcluida + 1;\n const faseParaSetar =\n proximaFase <= gameConfig.fases.length\n ? proximaFase\n : ultimaFaseConcluida;\n setCurrentPhase(faseParaSetar);\n }\n }\n initialized.current = true;\n }, [storageKey, gameConfig.fases.length]);\n\n useEffect(() => {\n if (!initialized.current) return;\n\n localStorage.setItem(storageKey, JSON.stringify(completedPhases));\n }, [completedPhases, storageKey]);\n\n const execute = () => {\n if (editorType === \"code\") {\n if (getCodeFromEditor.current) {\n const codigo = getCodeFromEditor.current();\n\n if (codigo && codigo.trim()) {\n setGeneratedCode(codigo);\n setExecutionState(GAME_STATES.EXECUTANDO);\n } else {\n console.error(\n \"CodeEditor ainda não registrou sua função de execução.\",\n );\n }\n }\n } else {\n if (getCodeFromWorkspace.current) {\n const { codigo, workspace } = getCodeFromWorkspace.current();\n if (codigo && workspace) {\n setGeneratedCode({ codigo, workspace });\n setExecutionState(GAME_STATES.EXECUTANDO);\n }\n } else {\n console.error(\n \"BlocklyEditor ainda não registrou sua função de execução.\",\n );\n }\n }\n };\n\n const finalizeWithSuccess = () => {\n setExecutionState(GAME_STATES.SUCESSO);\n\n if (!completedPhases.includes(currentPhase)) {\n setCompletedPhases([...completedPhases, currentPhase]);\n }\n };\n\n const finalizeWithFailure = () => {\n setExecutionState(GAME_STATES.FALHA);\n };\n\n const restart = () => {\n setExecutionState(GAME_STATES.PARADO);\n setGeneratedCode(\"\");\n };\n\n const resetProgress = () => {\n setCompletedPhases([]);\n setCurrentPhase(1);\n localStorage.removeItem(storageKey);\n };\n\n const changePhase = (numeroFase) => {\n setCurrentPhase(numeroFase);\n setExecutionState(GAME_STATES.PARADO);\n setGeneratedCode(\"\");\n setCurrentBlockCount(0);\n setCodeEditorContent(\"\");\n };\n\n const stop = () => {\n setExecutionState(GAME_STATES.PARADO);\n setGeneratedCode(\"\");\n };\n\n const registerExecutionFunction = useCallback((func) => {\n getCodeFromWorkspace.current = func;\n }, []);\n\n const registerCodeEditorFunction = useCallback((func) => {\n getCodeFromEditor.current = func;\n }, []);\n\n const onWorkspaceChange = useCallback(\n (blockCount) => {\n setCurrentBlockCount(blockCount);\n if (onWorkspaceChangeCallback) {\n onWorkspaceChangeCallback(blockCount);\n }\n },\n [onWorkspaceChangeCallback],\n );\n\n const onCodeEditorChange = useCallback((content) => {\n setCodeEditorContent(content);\n setCurrentBlockCount(content.trim() ? 1 : 0);\n }, []);\n\n useEffect(() => {\n if (editorType === \"code\" && getCodeFromEditor.current) {\n setCurrentBlockCount(\n codeEditorContent && codeEditorContent.trim() ? 1 : 0,\n );\n } else {\n setCodeEditorContent(0);\n }\n }, [editorType, codeEditorContent]);\n\n return (\n \n {children}\n \n );\n}\n\nexport function useGameState() {\n const context = useContext(GameStateContext);\n if (!context) {\n throw new Error(\"useGameState deve ser usado dentro de GameStateProvider\");\n }\n return context;\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/AutomatoGame.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBase' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBase"},"fix":{"range":[58,72],"text":""},"desc":"Remove unused variable 'GameBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameEditor"},"fix":{"range":[113,129],"text":""},"desc":"Remove unused variable 'GameEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BlocklyEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"BlocklyEditor"},"fix":{"range":[172,191],"text":""},"desc":"Remove unused variable 'BlocklyEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameStateProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameStateProvider"},"fix":{"range":[406,424],"text":""},"desc":"Remove unused variable 'GameStateProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'AutomatoGameContent' is defined but never used. Allowed unused vars must match /^_/u.","line":17,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":17,"endColumn":29,"suggestions":[{"messageId":"removeVar","data":{"varName":"AutomatoGameContent"},"fix":{"range":[687,1432],"text":""},"desc":"Remove unused variable 'AutomatoGameContent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useMemo } from \"react\";\nimport GameBase from \"../../components/game/GameBase\";\nimport GameEditor from \"../../components/game/GameEditor\";\nimport BlocklyEditor from \"../../components/game/editors/BlocklyEditor\";\nimport { createGame } from \"./game\";\nimport { gameConfig } from \"./config/config\";\nimport { registerBlocks, generateDynamicToolbox } from \"./blocks/blocks\";\nimport {\n GameStateProvider,\n useGameState,\n} from \"../../contexts/GameStateContext\";\nimport { useAutomatoTour } from \"./hooks/useAutomatoTour\";\nimport { debugSolutions } from \"./config/debugSolutions\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\n\nfunction AutomatoGameContent() {\n const { isDebugMode, setFailureMessage } = useGameState();\n const { startTour } = useAutomatoTour();\n\n useEffect(() => {\n registerBlocks();\n }, []);\n\n const toolboxGenerator = useMemo(() => {\n return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);\n }, []);\n\n const renderEditor = () => {\n return (\n \n );\n };\n\n return (\n \n {renderEditor()} \n \n );\n}\n\nexport default function AutomatoGame() {\n return (\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/__tests__/integration.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/blocks/blocks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/config/config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/config/debugSolutions.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/game.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/hooks/interpreterSetup.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/hooks/useAutomatoTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/automato/validation/validators.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/CriptoGame.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBase' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBase"},"fix":{"range":[58,72],"text":""},"desc":"Remove unused variable 'GameBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameEditor"},"fix":{"range":[113,129],"text":""},"desc":"Remove unused variable 'GameEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BlocklyEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"BlocklyEditor"},"fix":{"range":[172,191],"text":""},"desc":"Remove unused variable 'BlocklyEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameStateProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameStateProvider"},"fix":{"range":[406,424],"text":""},"desc":"Remove unused variable 'GameStateProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CriptoContent' is defined but never used. Allowed unused vars must match /^_/u.","line":18,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":18,"endColumn":23,"suggestions":[{"messageId":"removeVar","data":{"varName":"CriptoContent"},"fix":{"range":[739,1406],"text":""},"desc":"Remove unused variable 'CriptoContent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useMemo } from \"react\";\nimport GameBase from \"../../components/game/GameBase\";\nimport GameEditor from \"../../components/game/GameEditor\";\nimport BlocklyEditor from \"../../components/game/editors/BlocklyEditor\";\nimport { createGame } from \"./game\";\nimport { gameConfig } from \"./config/config\";\nimport { generateDynamicToolbox, registerBlocks } from \"./blocks/blocks\";\nimport {\n GameStateProvider,\n useGameState,\n} from \"../../contexts/GameStateContext\";\nimport { starterBlocks } from \"./config/starterBlocks\";\nimport { useCriptoTour } from \"./hooks/useCriptoTour\";\nimport { debugSolutions } from \"./config/debugSolutions\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\n\nfunction CriptoContent() {\n const { setFailureMessage, isDebugMode } = useGameState();\n useCriptoTour();\n\n useEffect(() => {\n registerBlocks();\n }, []);\n\n const toolboxGenerator = useMemo(() => {\n return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);\n }, []);\n\n return (\n \n \n \n \n \n );\n}\n\nexport default function CriptoGame() {\n return (\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/blocks/blocks.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":628,"column":61,"nodeType":"Identifier","messageId":"unusedVar","endLine":628,"endColumn":66,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[16272,16277],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":634,"column":59,"nodeType":"Identifier","messageId":"unusedVar","endLine":634,"endColumn":64,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[16466,16471],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":640,"column":62,"nodeType":"Identifier","messageId":"unusedVar","endLine":640,"endColumn":67,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[16664,16669],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":646,"column":56,"nodeType":"Identifier","messageId":"unusedVar","endLine":646,"endColumn":61,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[16853,16858],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":652,"column":64,"nodeType":"Identifier","messageId":"unusedVar","endLine":652,"endColumn":69,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[17064,17069],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":708,"column":59,"nodeType":"Identifier","messageId":"unusedVar","endLine":708,"endColumn":64,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[18825,18830],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":723,"column":61,"nodeType":"Identifier","messageId":"unusedVar","endLine":723,"endColumn":66,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[19249,19254],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":738,"column":66,"nodeType":"Identifier","messageId":"unusedVar","endLine":738,"endColumn":71,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[19695,19700],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":753,"column":64,"nodeType":"Identifier","messageId":"unusedVar","endLine":753,"endColumn":69,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[20139,20144],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":768,"column":59,"nodeType":"Identifier","messageId":"unusedVar","endLine":768,"endColumn":64,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[20560,20565],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":783,"column":67,"nodeType":"Identifier","messageId":"unusedVar","endLine":783,"endColumn":72,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[21009,21014],"text":""},"desc":"Remove unused variable 'block'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'block' is defined but never used. Allowed unused args must match /^_/u.","line":798,"column":58,"nodeType":"Identifier","messageId":"unusedVar","endLine":798,"endColumn":63,"suggestions":[{"messageId":"removeVar","data":{"varName":"block"},"fix":{"range":[21429,21434],"text":""},"desc":"Remove unused variable 'block'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":12,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use strict\";\n\nimport * as Blockly from \"blockly/core\";\nimport \"blockly/blocks\";\nimport { javascriptGenerator } from \"blockly/javascript\";\n\nconst HUE_LOGICA = 210;\nconst HUE_MATEMATICA = 230;\nconst HUE_TEXTO = 160;\nconst HUE_REPETICAO = 120;\nconst HUE_VARIAVEIS = 330;\n\nexport const registerBlocks = () => {\n defineBlocks();\n defineGenerators();\n};\n\nexport const generateDynamicToolbox = (allowedBlocks = []) => {\n const blockDefinitions = {\n // Matemática\n math_number: {\n kind: \"block\",\n type: \"math_number\",\n },\n math_arithmetic: {\n kind: \"block\",\n type: \"math_arithmetic\",\n },\n math_modulo: {\n kind: \"block\",\n type: \"math_modulo\",\n },\n\n // Texto\n text: {\n kind: \"block\",\n type: \"text\",\n },\n text_indexOf: {\n kind: \"block\",\n type: \"text_indexOf\",\n },\n text_charAt: {\n kind: \"block\",\n type: \"text_charAt\",\n },\n text_join: {\n kind: \"block\",\n type: \"text_join\",\n },\n text_length: {\n kind: \"block\",\n type: \"text_length\",\n },\n alfabeto: {\n kind: \"block\",\n type: \"alfabeto\",\n },\n alfabeto_secreto: {\n kind: \"block\",\n type: \"alfabeto_secreto\",\n },\n\n // Lógica\n controls_if: {\n kind: \"block\",\n type: \"controls_if\",\n },\n logic_compare: {\n kind: \"block\",\n type: \"logic_compare\",\n },\n\n // Repetição\n controls_whileUntil: {\n kind: \"block\",\n type: \"controls_whileUntil\",\n },\n definir_contador: {\n kind: \"block\",\n type: \"definir_contador\",\n },\n obter_contador: {\n kind: \"block\",\n type: \"obter_contador\",\n },\n // Blocos Customizados de Entrada/Saída\n definir_entrada: {\n kind: \"block\",\n type: \"definir_entrada\",\n },\n definir_saida: {\n kind: \"block\",\n type: \"definir_saida\",\n },\n concatenar_saida: {\n kind: \"block\",\n type: \"concatenar_saida\",\n },\n obter_entrada: {\n kind: \"block\",\n type: \"obter_entrada\",\n },\n obter_saida: {\n kind: \"block\",\n type: \"obter_saida\",\n },\n\n // Variáveis Customizadas\n definir_letra: {\n kind: \"block\",\n type: \"definir_letra\",\n },\n obter_letra: {\n kind: \"block\",\n type: \"obter_letra\",\n },\n definir_posicao: {\n kind: \"block\",\n type: \"definir_posicao\",\n },\n obter_posicao: {\n kind: \"block\",\n type: \"obter_posicao\",\n },\n definir_nova_posicao: {\n kind: \"block\",\n type: \"definir_nova_posicao\",\n },\n obter_nova_posicao: {\n kind: \"block\",\n type: \"obter_nova_posicao\",\n },\n definir_nova_letra: {\n kind: \"block\",\n type: \"definir_nova_letra\",\n },\n obter_nova_letra: {\n kind: \"block\",\n type: \"obter_nova_letra\",\n },\n definir_chave: {\n kind: \"block\",\n type: \"definir_chave\",\n },\n obter_chave: {\n kind: \"block\",\n type: \"obter_chave\",\n },\n definir_letra_secreta: {\n kind: \"block\",\n type: \"definir_letra_secreta\",\n },\n obter_letra_secreta: {\n kind: \"block\",\n type: \"obter_letra_secreta\",\n },\n definir_soma: {\n kind: \"block\",\n type: \"definir_soma\",\n },\n obter_soma: {\n kind: \"block\",\n type: \"obter_soma\",\n },\n };\n\n const toolboxContents = {\n kind: \"categoryToolbox\",\n contents: [\n {\n kind: \"category\",\n name: \"Entrada/Saída\",\n colour: HUE_VARIAVEIS,\n contents: [],\n cssConfig: { container: \"variaveis\" },\n },\n {\n kind: \"category\",\n name: \"Lógica\",\n colour: HUE_LOGICA,\n contents: [],\n cssConfig: { container: \"logica\" },\n },\n {\n kind: \"category\",\n name: \"Repetição\",\n colour: HUE_REPETICAO,\n contents: [],\n cssConfig: { container: \"repeticao\" },\n },\n {\n kind: \"category\",\n name: \"Texto\",\n colour: HUE_TEXTO,\n contents: [],\n cssConfig: { container: \"texto\" },\n },\n {\n kind: \"category\",\n name: \"Matemática\",\n colour: HUE_MATEMATICA,\n contents: [],\n cssConfig: { container: \"matematica\" },\n },\n ],\n };\n\n allowedBlocks.forEach((blockId) => {\n const blockDef = blockDefinitions[blockId];\n\n if (!blockDef) {\n console.warn(`Bloco não encontrado: ${blockId}`);\n return;\n }\n\n const categoryMap = {\n obter_entrada: 0,\n obter_saida: 0,\n definir_entrada: 0,\n definir_saida: 0,\n concatenar_saida: 0,\n definir_letra: 0,\n obter_letra: 0,\n definir_posicao: 0,\n obter_posicao: 0,\n definir_nova_posicao: 0,\n obter_nova_posicao: 0,\n definir_nova_letra: 0,\n obter_nova_letra: 0,\n definir_chave: 0,\n obter_chave: 0,\n definir_letra_secreta: 0,\n obter_letra_secreta: 0,\n definir_soma: 0,\n obter_soma: 0,\n controls_if: 1,\n logic_compare: 1,\n controls_whileUntil: 2,\n definir_contador: 2,\n obter_contador: 2,\n text: 3,\n text_charAt: 3,\n text_join: 3,\n text_length: 3,\n text_indexOf: 3,\n alfabeto: 3,\n alfabeto_secreto: 3,\n math_number: 4,\n math_arithmetic: 4,\n math_modulo: 4,\n };\n\n const categoryIndex = categoryMap[blockId];\n\n if (\n categoryIndex !== undefined &&\n categoryIndex >= 0 &&\n toolboxContents.contents[categoryIndex]\n ) {\n if (!toolboxContents.contents[categoryIndex].contents) {\n toolboxContents.contents[categoryIndex].contents = [];\n }\n toolboxContents.contents[categoryIndex].contents.push(blockDef);\n }\n });\n\n return toolboxContents;\n};\n\nconst defineBlocks = () => {\n Blockly.Blocks[\"text_charAt\"] = {\n init: function () {\n this.setHelpUrl(Blockly.Msg[\"TEXT_CHARAT_HELPURL\"]);\n this.setColour(HUE_TEXTO);\n this.setOutput(true, \"String\");\n this.appendValueInput(\"VALUE\").setCheck(\"String\").appendField(\"no texto\");\n this.appendValueInput(\"AT\").setCheck(\"Number\").appendField(\"obter letra\");\n this.setInputsInline(true);\n this.setTooltip(Blockly.Msg[\"TEXT_CHARAT_TOOLTIP\"]);\n },\n };\n\n // Bloco: Definir Contador\n Blockly.Blocks[\"definir_contador\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir CONTADOR como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_REPETICAO);\n this.setTooltip(\"Define o valor do CONTADOR\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir Entrada\n Blockly.Blocks[\"definir_entrada\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir ENTRADA como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da ENTRADA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir Saída\n Blockly.Blocks[\"definir_saida\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir SAÍDA como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da SAÍDA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Concatenar Saída\n Blockly.Blocks[\"concatenar_saida\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"adicionar à SAÍDA\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Adiciona um valor ao final da saída\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter Entrada\n Blockly.Blocks[\"obter_entrada\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"ENTRADA\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor atual da entrada\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter Saída\n Blockly.Blocks[\"obter_saida\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"SAÍDA\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor atual da saída\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter Contador\n Blockly.Blocks[\"obter_contador\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"CONTADOR\");\n this.setOutput(true, null);\n this.setColour(HUE_REPETICAO);\n this.setTooltip(\"Obtém o valor atual do CONTADOR\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Alfabeto (constante)\n Blockly.Blocks[\"alfabeto\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"ALFABETO\");\n this.setOutput(true, \"String\");\n this.setColour(HUE_TEXTO);\n this.setTooltip(\n \"Retorna o alfabeto completo: ABCDEFGHIJKLMNOPQRSTUVWXYZ\",\n );\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Alfabeto Secreto (constante)\n Blockly.Blocks[\"alfabeto_secreto\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"ALFABETO SECRETO\");\n this.setOutput(true, \"String\");\n this.setColour(HUE_TEXTO);\n this.setTooltip(\n \"Retorna o alfabeto embaralhado: QWERTYUIOPASDFGHJKLZXCVBNM\",\n );\n this.setHelpUrl(\"\");\n },\n };\n\n // ============ BLOCOS DE VARIÁVEIS CUSTOMIZADAS ============\n\n // Bloco: Definir letra\n Blockly.Blocks[\"definir_letra\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir LETRA como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável LETRA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter letra\n Blockly.Blocks[\"obter_letra\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"LETRA\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável LETRA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir posicao\n Blockly.Blocks[\"definir_posicao\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir POSIÇÃO como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável POSIÇÃO\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter posicao\n Blockly.Blocks[\"obter_posicao\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"POSIÇÃO\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável POSIÇÃO\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir nova_posicao\n Blockly.Blocks[\"definir_nova_posicao\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir nova_posicao como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável nova_posicao\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter nova_posicao\n Blockly.Blocks[\"obter_nova_posicao\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"nova_posicao\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável nova_posicao\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir nova_letra\n Blockly.Blocks[\"definir_nova_letra\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir nova_letra como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável nova_letra\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter nova_letra\n Blockly.Blocks[\"obter_nova_letra\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"nova_letra\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável nova_letra\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir chave\n Blockly.Blocks[\"definir_chave\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir chave como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\n \"Define o valor da variável chave (deslocamento da cifra)\",\n );\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter chave\n Blockly.Blocks[\"obter_chave\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"chave\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável chave\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir letra_secreta\n Blockly.Blocks[\"definir_letra_secreta\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir LETRA_SECRETA como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável LETRA_SECRETA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter letra_secreta\n Blockly.Blocks[\"obter_letra_secreta\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"LETRA_SECRETA\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável LETRA_SECRETA\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Definir soma\n Blockly.Blocks[\"definir_soma\"] = {\n init: function () {\n this.appendValueInput(\"VALUE\")\n .setCheck(null)\n .appendField(\"definir SOMA como\");\n this.setPreviousStatement(true, null);\n this.setNextStatement(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Define o valor da variável SOMA (acumulador)\");\n this.setHelpUrl(\"\");\n },\n };\n\n // Bloco: Obter soma\n Blockly.Blocks[\"obter_soma\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"SOMA\");\n this.setOutput(true, null);\n this.setColour(HUE_VARIAVEIS);\n this.setTooltip(\"Obtém o valor da variável SOMA\");\n this.setHelpUrl(\"\");\n },\n };\n};\n\nconst defineGenerators = () => {\n javascriptGenerator.STATEMENT_PREFIX = \"highlightBlock(%1);\\n\";\n javascriptGenerator.addReservedWords(\"highlightBlock\");\n\n // Gerador: Definir Entrada\n javascriptGenerator.forBlock[\"definir_entrada\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"definirEntrada(\" + value + \");\\n\";\n };\n\n // Gerador: Definir Saída\n javascriptGenerator.forBlock[\"definir_saida\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"definirSaida(\" + value + \");\\n\";\n };\n\n // Gerador: Definir Contador\n javascriptGenerator.forBlock[\"definir_contador\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"definirContador(\" + value + \");\\n\";\n };\n\n // Gerador: Concatenar Saída\n javascriptGenerator.forBlock[\"concatenar_saida\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"concatenarSaida(\" + value + \");\\n\";\n };\n\n // Gerador: Obter Entrada\n javascriptGenerator.forBlock[\"obter_entrada\"] = function (block) {\n const code = \"obterEntrada()\";\n return [code, javascriptGenerator.ORDER_FUNCTION_CALL];\n };\n\n // Gerador: Obter Saída\n javascriptGenerator.forBlock[\"obter_saida\"] = function (block) {\n const code = \"obterSaida()\";\n return [code, javascriptGenerator.ORDER_FUNCTION_CALL];\n };\n\n // Gerador: Obter Contador\n javascriptGenerator.forBlock[\"obter_contador\"] = function (block) {\n const code = \"obterContador()\";\n return [code, javascriptGenerator.ORDER_FUNCTION_CALL];\n };\n\n // Gerador: Alfabeto\n javascriptGenerator.forBlock[\"alfabeto\"] = function (block) {\n const code = '\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"';\n return [code, javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Gerador: Alfabeto Secreto\n javascriptGenerator.forBlock[\"alfabeto_secreto\"] = function (block) {\n const code = '\"QWERTYUIOPASDFGHJKLZXCVBNM\"';\n return [code, javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Gerador customizado: text_charAt (0-based, não subtrai 1)\n // Assume que todos os índices fornecidos já são 0-based (compatível com CONTADOR = 0)\n javascriptGenerator.forBlock[\"text_charAt\"] = function (block) {\n const text =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_MEMBER,\n ) || \"''\";\n const at =\n javascriptGenerator.valueToCode(\n block,\n \"AT\",\n javascriptGenerator.ORDER_NONE,\n ) || \"0\";\n const code = text + \".charAt(\" + at + \")\";\n return [code, javascriptGenerator.ORDER_MEMBER];\n };\n\n // Gerador customizado: text_indexOf (0-based, não adiciona 1)\n // Retorna o índice 0-based direto, compatível com charAt e arrays JavaScript\n javascriptGenerator.forBlock[\"text_indexOf\"] = function (block) {\n const text =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_MEMBER,\n ) || \"''\";\n const search =\n javascriptGenerator.valueToCode(\n block,\n \"FIND\",\n javascriptGenerator.ORDER_NONE,\n ) || \"''\";\n const code = text + \".indexOf(\" + search + \")\";\n return [code, javascriptGenerator.ORDER_MEMBER];\n };\n\n // ============ GERADORES PARA VARIÁVEIS CUSTOMIZADAS ============\n\n // Geradores: letra\n javascriptGenerator.forBlock[\"definir_letra\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"var letra = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_letra\"] = function (block) {\n return [\"letra\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: posicao\n javascriptGenerator.forBlock[\"definir_posicao\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"0\";\n return \"var posicao = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_posicao\"] = function (block) {\n return [\"posicao\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: nova_posicao\n javascriptGenerator.forBlock[\"definir_nova_posicao\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"0\";\n return \"var nova_posicao = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_nova_posicao\"] = function (block) {\n return [\"nova_posicao\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: nova_letra\n javascriptGenerator.forBlock[\"definir_nova_letra\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"var nova_letra = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_nova_letra\"] = function (block) {\n return [\"nova_letra\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: chave\n javascriptGenerator.forBlock[\"definir_chave\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"0\";\n return \"var chave = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_chave\"] = function (block) {\n return [\"chave\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: letra_secreta\n javascriptGenerator.forBlock[\"definir_letra_secreta\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"''\";\n return \"var letra_secreta = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_letra_secreta\"] = function (block) {\n return [\"letra_secreta\", javascriptGenerator.ORDER_ATOMIC];\n };\n\n // Geradores: soma\n javascriptGenerator.forBlock[\"definir_soma\"] = function (block) {\n const value =\n javascriptGenerator.valueToCode(\n block,\n \"VALUE\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"0\";\n return \"var soma = \" + value + \";\\n\";\n };\n\n javascriptGenerator.forBlock[\"obter_soma\"] = function (block) {\n return [\"soma\", javascriptGenerator.ORDER_ATOMIC];\n };\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/config/codeValidations.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/config/config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/config/debugSolutions.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/config/starterBlocks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/game.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/hooks/interpreterSetup.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/hooks/useCriptoTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/CRTMonitor.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/GridBackground.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/MatrixEffect.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/animations.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/constants.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/index.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/ui/layout.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/cripto/validation/validators.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/MoleMash.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBase' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBase"},"fix":{"range":[58,72],"text":""},"desc":"Remove unused variable 'GameBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameEditor"},"fix":{"range":[113,129],"text":""},"desc":"Remove unused variable 'GameEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BlocklyEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"BlocklyEditor"},"fix":{"range":[172,191],"text":""},"desc":"Remove unused variable 'BlocklyEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameStateProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameStateProvider"},"fix":{"range":[406,424],"text":""},"desc":"Remove unused variable 'GameStateProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'MoleMashContent' is defined but never used. Allowed unused vars must match /^_/u.","line":17,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":17,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"MoleMashContent"},"fix":{"range":[687,1370],"text":""},"desc":"Remove unused variable 'MoleMashContent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useMemo } from \"react\";\nimport GameBase from \"../../components/game/GameBase\";\nimport GameEditor from \"../../components/game/GameEditor\";\nimport BlocklyEditor from \"../../components/game/editors/BlocklyEditor\";\nimport { createGame } from \"./game\";\nimport { gameConfig } from \"./config/config\";\nimport { registerBlocks, generateDynamicToolbox } from \"./blocks/blocks\";\nimport {\n GameStateProvider,\n useGameState,\n} from \"../../contexts/GameStateContext\";\nimport { useMoleMashTour } from \"./hooks/useMoleMashTour\";\nimport { debugSolutions } from \"./config/debugSolutions\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\n\nfunction MoleMashContent() {\n const { startTour } = useMoleMashTour();\n const { setFailureMessage, isDebugMode } = useGameState();\n\n useEffect(() => {\n registerBlocks();\n }, []);\n\n const toolboxGenerator = useMemo(() => {\n return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);\n }, []);\n\n return (\n \n \n \n \n \n );\n}\n\nexport default function MoleMashGame() {\n return (\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/__tests__/integration.test.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'l' is defined but never used. Allowed unused args must match /^_/u.","line":227,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":227,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"l"},"fix":{"range":[5669,5671],"text":""},"desc":"Remove unused variable 'l'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'c' is defined but never used. Allowed unused args must match /^_/u.","line":227,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":227,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"c"},"fix":{"range":[5670,5673],"text":""},"desc":"Remove unused variable 'c'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'segundos' is defined but never used. Allowed unused args must match /^_/u.","line":238,"column":12,"nodeType":"Identifier","messageId":"unusedVar","endLine":238,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"segundos"},"fix":{"range":[5962,5970],"text":""},"desc":"Remove unused variable 'segundos'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport { GameInterpreter } from \"../../../interpreters/GameInterpreter\";\nimport { setupMoleMashAPI } from \"../hooks/interpreterSetup\";\nimport { validateSolution } from \"../validation/validators\";\n\n// Mock de soluções para teste (movido de config/solutions.js)\nconst SOLUTIONS = {\n fase1: `\n moverToupeira(1, 1);\n moverToupeira(1, 2);\n moverToupeira(1, 3);\n `,\n fase1_fail: `\n moverToupeira(1, 1);\n moverToupeira(1, 3);\n `,\n fase2: `\n moverToupeira(1, 1);\n moverToupeira(1, 3);\n moverToupeira(3, 3);\n moverToupeira(3, 1);\n `,\n fase2_fail: `\n moverToupeira(1, 1);\n moverToupeira(2, 2);\n `,\n fase3: `\n for (var count = 0; count < 5; count++) {\n moverToupeira(2, 2);\n moverToupeira(1, 2);\n }\n `,\n fase3_fail: `\n for (var count = 0; count < 5; count++) {\n moverToupeira(2, 2);\n moverToupeira(2, 2); \n }\n `,\n fase4: `\n var linha, coluna;\n linha = 3;\n for (var count2 = 0; count2 < 3; count2++) {\n coluna = 3;\n for (var count = 0; count < 3; count++) {\n moverToupeira(linha, coluna);\n coluna = coluna - 1;\n }\n linha = (typeof linha === 'number' ? linha : 0) + -1;\n }\n `,\n fase4_fail: `\n for (var i = 0; i < 10; i++) {\n moverToupeira(1, 1);\n moverToupeira(1, 2);\n }\n `,\n fase5: `\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 10; count++) {\n moverToupeira(2, (mathRandomInt(1, 3)));\n }\n `,\n fase5_fail: `\n for (var count = 0; count < 5; count++) {\n moverToupeira(1, 2);\n }\n `,\n fase6: `\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 20; count++) {\n moverToupeira(mathRandomInt(1, 3), mathRandomInt(1, 3));\n }\n `,\n fase7: `\n var x, y;\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 20; count++) {\n x = mathRandomInt(1, 10);\n y = mathRandomInt(1, 3);\n if (x <= 5) {\n moverToupeira(1, y);\n } else {\n moverToupeira(3, y);\n }\n }\n `,\n fase8: `\n var linha_atual, coluna, ultima_linha;\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 10; count++) {\n linha_atual = mathRandomInt(1, 1);\n coluna = mathRandomInt(1, 3);\n if (linha_atual == ultima_linha) {\n moverToupeira(2, coluna);\n } else {\n moverToupeira(linha_atual, coluna);\n }\n ultima_linha = linha_atual;\n }\n `,\n fase8_fail: `\n moverToupeira(1, 1);\n moverToupeira(1, 2);\n `,\n fase9: `\n var linha, coluna, ultima_linha, ultima_coluna;\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 30; count++) {\n linha = mathRandomInt(1, 3);\n coluna = mathRandomInt(1, 3);\n while (linha == ultima_linha && coluna == ultima_coluna) {\n linha = mathRandomInt(1, 3);\n coluna = mathRandomInt(1, 3);\n }\n moverToupeira(linha, coluna);\n ultima_linha = linha;\n ultima_coluna = coluna;\n }\n `,\n fase10: `\n var l, c, ult_l, ult_c;\n function mathRandomInt(a, b) {\n if (a > b) { var c = a; a = b; b = c; }\n return Math.floor(Math.random() * (b - a + 1) + a);\n }\n for (var count = 0; count < 30; count++) {\n l = mathRandomInt(1, 3);\n c = mathRandomInt(1, 3);\n while (l == ult_l && c == ult_c) {\n l = mathRandomInt(1, 3);\n c = mathRandomInt(1, 3);\n }\n if (ult_l == 2) {\n if (mathRandomInt(1, 2) == 1) {\n l = 1;\n } else {\n l = 3;\n }\n }\n moverToupeira(l, c);\n ult_l = l;\n ult_c = c;\n aguardar(0.1);\n }\n `,\n fase10_fail: `\n moverToupeira(2, 1);\n moverToupeira(2, 3);\n `,\n};\n\n// MOCK DA CONFIGURAÇÃO (Para não depender do gameConfig externo)\n// Replicamos aqui apenas as regras que o validador consome.\nconst MOCK_GAME_CONFIG = {\n mensagens: {\n semMovimento: \"Sem mov\",\n foraTabuleiro: \"Fora!\",\n caminhoErrado: \"Caminho errado\",\n faltamSaltos: \"Falta salto\",\n saltoInsuficiente: \"Insuficiente\",\n saltoErrado: \"Padrão errado no salto {numero}: esperado {local}\",\n buracosFaltando: \"Não visitou tudo ({visitados} visitados)\",\n linhaErrada: \"Linha errada\",\n repeticaoLinha: \"Repetiu linha\",\n mesmoLugar: \"Mesmo lugar\",\n linhaCentral: \"Centro repetido\",\n },\n fases: [\n {\n id: 1,\n expectedSequence: [\n { l: 1, c: 1 },\n { l: 1, c: 2 },\n { l: 1, c: 3 },\n ],\n },\n {\n id: 2,\n expectedSequence: [\n { l: 1, c: 1 },\n { l: 1, c: 3 },\n { l: 3, c: 3 },\n { l: 3, c: 1 },\n ],\n },\n { id: 3 },\n { id: 4 },\n { id: 5 },\n { id: 6 },\n { id: 7 },\n { id: 8 },\n { id: 9 },\n { id: 10 },\n ],\n};\n\n// CENA HEADLESS (Simula o Phaser sem gráficos)\nclass HeadlessMoleMashScene {\n constructor() {\n this.historico = [];\n this.toupeira = {\n setPosition: vi.fn(),\n setVisible: vi.fn(),\n play: vi.fn(),\n scene: true,\n };\n // Mock do som\n this.sound = { play: vi.fn() };\n }\n\n // Utilitário usado pela API\n getGridPixels(l, c) {\n return { x: 0, y: 0 };\n }\n\n // Implementação da API Visual dentro da cena\n moverToupeira(linha, coluna) {\n this.historico.push({ l: linha, c: coluna, t: Date.now() });\n this.toupeira.setPosition(0, 0);\n }\n\n // Simula aguardar (resolve imediatamente no teste)\n aguardar(segundos) {\n return Promise.resolve();\n }\n}\n\ndescribe(\"Mole Mash - Integração de Lógica (Código -> Validação)\", () => {\n let scene;\n let interpreter;\n\n beforeEach(() => {\n scene = new HeadlessMoleMashScene();\n // StepDelay 0 para rodar instantâneo\n interpreter = new GameInterpreter({ stepDelay: 0, pauseExec: false });\n });\n\n const runFlow = async (code, phaseId) => {\n const api = setupMoleMashAPI(scene);\n\n // Executa o código (popula scene.historico)\n await interpreter.executeCode(code, api);\n\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === phaseId);\n\n // Valida o histórico gerado\n return validateSolution(scene.historico, configFase, MOCK_GAME_CONFIG);\n };\n\n // --- TESTES DE FASES ---\n\n it(\"Fase 1: Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase1, 1);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 1: Deve reprovar solução errada\", async () => {\n const result = await runFlow(SOLUTIONS.fase1_fail, 1);\n expect(result.success).toBe(false);\n expect(result.reason).toBe(MOCK_GAME_CONFIG.mensagens.caminhoErrado);\n });\n\n it(\"Fase 2: Deve aprovar solução correta (Cantos)\", async () => {\n const result = await runFlow(SOLUTIONS.fase2, 2);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 2: Deve reprovar solução errada\", async () => {\n const result = await runFlow(SOLUTIONS.fase2_fail, 2);\n expect(result.success).toBe(false);\n });\n\n it(\"Fase 3: Deve aprovar loop correto\", async () => {\n const result = await runFlow(SOLUTIONS.fase3, 3);\n expect(result.success).toBe(true);\n expect(scene.historico.length).toBe(10);\n });\n\n it(\"Fase 3: Deve reprovar loop incorreto\", async () => {\n const result = await runFlow(SOLUTIONS.fase3_fail, 3);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Padrão errado\");\n });\n\n it(\"Fase 4: Deve aprovar varredura completa (Nested Loops)\", async () => {\n const result = await runFlow(SOLUTIONS.fase4, 4);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 4: Deve reprovar varredura incompleta\", async () => {\n const result = await runFlow(SOLUTIONS.fase4_fail, 4);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Não visitou tudo\");\n });\n\n it(\"Fase 5: Deve aprovar aleatório na linha 2\", async () => {\n const result = await runFlow(SOLUTIONS.fase5, 5);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 5: Deve reprovar linha errada\", async () => {\n const result = await runFlow(SOLUTIONS.fase5_fail, 5);\n expect(result.success).toBe(false);\n expect(result.reason).toBe(MOCK_GAME_CONFIG.mensagens.linhaErrada);\n });\n\n // Fase 6 e 7 e 9 não possuem validadores específicos complexos no momento,\n // validam apenas sanidade e limites. Testamos apenas o sucesso da execução.\n it(\"Fase 6: Deve executar com sucesso (Aleatório Total)\", async () => {\n const result = await runFlow(SOLUTIONS.fase6, 6);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 7: Deve executar com sucesso (Condicionais)\", async () => {\n const result = await runFlow(SOLUTIONS.fase7, 7);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 8: Deve aprovar lógica de não repetição de linha\", async () => {\n const result = await runFlow(SOLUTIONS.fase8, 8);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 8: Deve reprovar repetição explícita\", async () => {\n const result = await runFlow(SOLUTIONS.fase8_fail, 8);\n expect(result.success).toBe(false);\n expect(result.reason).toBe(MOCK_GAME_CONFIG.mensagens.repeticaoLinha);\n });\n\n it(\"Fase 9: Deve executar com sucesso (While loop)\", async () => {\n const result = await runFlow(SOLUTIONS.fase9, 9);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 10: Deve aprovar lógica complexa\", async () => {\n const result = await runFlow(SOLUTIONS.fase10, 10);\n expect(result.success).toBe(true);\n });\n\n it(\"Fase 10: Deve reprovar lógica errada (Centro Repetido)\", async () => {\n const result = await runFlow(SOLUTIONS.fase10_fail, 10);\n expect(result.success).toBe(false);\n expect(result.reason).toBe(MOCK_GAME_CONFIG.mensagens.linhaCentral);\n });\n}, 60000);\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/blocks/blocks.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'index' is defined but never used. Allowed unused args must match /^_/u.","line":261,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":261,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"index"},"fix":{"range":[6792,6799],"text":""},"desc":"Remove unused variable 'index'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as Blockly from \"blockly/core\";\nimport \"blockly/blocks\";\nimport { javascriptGenerator } from \"blockly/javascript\";\n\nconst HUE_CONDICIONAIS = \"#5B80A5\";\nconst HUE_MOVIMENTO = \"#8B4513\";\nconst HUE_LOGICA = \"#59C059\";\nconst HUE_MATEMATICA = \"#8BC34A\";\nconst HUE_VARIAVEIS = \"#9C27B0\";\nconst HUE_REPETICAO = \"#FF9800\";\nconst HUE_TEMPO = \"#FF5722\";\n\nexport const registerBlocks = () => {\n defineBlocks();\n defineGenerators();\n};\n\nconst defineBlocks = () => {\n Blockly.Blocks[\"mover_toupeira\"] = {\n init: function () {\n this.jsonInit({\n message0: \"mover para Linha %1 Coluna %2\",\n args0: [\n { type: \"input_value\", name: \"LINHA\", check: \"Number\" },\n { type: \"input_value\", name: \"COLUNA\", check: \"Number\" },\n ],\n inputsInline: true,\n previousStatement: null,\n nextStatement: null,\n colour: HUE_MOVIMENTO,\n tooltip:\n \"Move a toupeira para uma posição específica (1 a 3) na matriz.\",\n });\n },\n };\n\n Blockly.Blocks[\"aguardar\"] = {\n init: function () {\n this.jsonInit({\n message0: \"aguardar %1 segundos\",\n args0: [\n {\n type: \"field_number\",\n name: \"TEMPO\",\n value: 1,\n min: 0,\n max: 10,\n },\n ],\n previousStatement: null,\n nextStatement: null,\n colour: HUE_TEMPO,\n tooltip: \"Pausa a execução antes do próximo movimento.\",\n });\n },\n };\n\n Blockly.Blocks[\"repetir_sempre\"] = {\n init: function () {\n this.appendDummyInput().appendField(\"repetir para sempre\");\n this.appendStatementInput(\"STACK\").setCheck(null).appendField(\"faça\");\n this.setPreviousStatement(true, null);\n this.setColour(HUE_REPETICAO);\n this.setTooltip(\"Executa os blocos dentro dele em um ciclo infinito.\");\n },\n };\n};\n\nconst defineGenerators = () => {\n javascriptGenerator.STATEMENT_PREFIX = \"highlightBlock(%1);\\n\";\n javascriptGenerator.addReservedWords(\"highlightBlock\");\n\n javascriptGenerator.forBlock[\"mover_toupeira\"] = (block) => {\n const linha =\n javascriptGenerator.valueToCode(\n block,\n \"LINHA\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"1\";\n const coluna =\n javascriptGenerator.valueToCode(\n block,\n \"COLUNA\",\n javascriptGenerator.ORDER_ATOMIC,\n ) || \"1\";\n return `moverToupeira(${linha}, ${coluna});\\n`;\n };\n\n javascriptGenerator.forBlock[\"aguardar\"] = (block) => {\n const tempo = block.getFieldValue(\"TEMPO\");\n return `aguardar(${tempo * 1000});\\n`;\n };\n\n javascriptGenerator.forBlock[\"repetir_sempre\"] = (block) => {\n const branch = javascriptGenerator.statementToCode(block, \"STACK\");\n return `while (true) {\\n${branch} await esperar(10); // Segurança para não travar\\n}\\n`;\n };\n};\n\nexport const generateDynamicToolbox = (allowedBlocks = []) => {\n const blockDefinitions = {\n // Movimento\n mover_toupeira: {\n kind: \"block\",\n type: \"mover_toupeira\",\n },\n // Repetição\n repetir_sempre: {\n kind: \"block\",\n type: \"repetir_sempre\",\n },\n controls_repeat_ext: {\n kind: \"block\",\n type: \"controls_repeat_ext\",\n },\n controls_whileUntil: {\n kind: \"block\",\n type: \"controls_whileUntil\",\n },\n // Tempo\n aguardar: {\n kind: \"block\",\n type: \"aguardar\",\n },\n // Lógica\n logic_compare: {\n kind: \"block\",\n type: \"logic_compare\",\n },\n logic_operation: {\n kind: \"block\",\n type: \"logic_operation\",\n },\n logic_negate: {\n kind: \"block\",\n type: \"logic_negate\",\n },\n logic_boolean: {\n kind: \"block\",\n type: \"logic_boolean\",\n },\n controls_if: {\n kind: \"block\",\n type: \"controls_if\",\n },\n controls_ifelse: {\n kind: \"block\",\n type: \"controls_ifelse\",\n },\n // Matemática\n math_number: {\n kind: \"block\",\n type: \"math_number\",\n },\n math_arithmetic: {\n kind: \"block\",\n type: \"math_arithmetic\",\n },\n math_random_int: {\n kind: \"block\",\n type: \"math_random_int\",\n inputs: {\n FROM: { shadow: { type: \"math_number\", fields: { NUM: 1 } } },\n TO: { shadow: { type: \"math_number\", fields: { NUM: 3 } } },\n },\n },\n };\n\n const toolboxContents = {\n kind: \"categoryToolbox\",\n contents: [\n {\n kind: \"category\",\n name: \"Movimento\",\n colour: HUE_MOVIMENTO,\n contents: [],\n cssConfig: { container: \"movimento\" },\n },\n {\n kind: \"category\",\n name: \"Repetição\",\n colour: HUE_REPETICAO,\n contents: [],\n cssConfig: { container: \"repeticao\" },\n },\n {\n kind: \"category\",\n name: \"Lógica\",\n colour: HUE_LOGICA,\n contents: [],\n cssConfig: { container: \"logica\" },\n },\n {\n kind: \"category\",\n name: \"Condicionais\",\n colour: HUE_CONDICIONAIS,\n contents: [],\n cssConfig: { container: \"condicionais\" },\n },\n {\n kind: \"category\",\n name: \"Matemática\",\n colour: HUE_MATEMATICA,\n contents: [],\n cssConfig: { container: \"matematica\" },\n },\n {\n kind: \"category\",\n name: \"Tempo\",\n colour: HUE_TEMPO,\n contents: [],\n cssConfig: { container: \"tempo\" },\n },\n {\n kind: \"category\",\n name: \"Variáveis\",\n colour: HUE_VARIAVEIS,\n custom: \"VARIABLE\",\n cssConfig: { container: \"variaveis\" },\n },\n ],\n };\n\n allowedBlocks.forEach((blockId) => {\n const blockDef = blockDefinitions[blockId];\n\n if (blockId === \"mover_toupeira\") {\n toolboxContents.contents[0].contents.push(blockDef);\n } else if (\n [\"repetir_sempre\", \"controls_repeat_ext\", \"controls_whileUntil\"].includes(\n blockId,\n )\n ) {\n toolboxContents.contents[1].contents.push(blockDef);\n } else if (\n [\n \"logic_compare\",\n \"logic_operation\",\n \"logic_negate\",\n \"logic_boolean\",\n ].includes(blockId)\n ) {\n toolboxContents.contents[2].contents.push(blockDef);\n } else if ([\"controls_if\", \"controls_ifelse\"].includes(blockId)) {\n toolboxContents.contents[3].contents.push(blockDef);\n } else if (\n [\"math_number\", \"math_random_int\", \"math_arithmetic\"].includes(blockId)\n ) {\n toolboxContents.contents[4].contents.push(blockDef);\n } else if (blockId === \"aguardar\") {\n toolboxContents.contents[5].contents.push(blockDef);\n }\n });\n\n const hasVariables =\n allowedBlocks.includes(\"variables_set\") ||\n allowedBlocks.includes(\"variables_get\") ||\n allowedBlocks.includes(\"variables\");\n\n toolboxContents.contents = toolboxContents.contents.filter(\n (category, index) => {\n if (category.custom === \"VARIABLE\") {\n return hasVariables;\n }\n return category.contents && category.contents.length > 0;\n },\n );\n\n return toolboxContents;\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/config/config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/config/debugSolutions.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/game.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/hooks/interpreterSetup.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'options' is assigned a value but never used. Allowed unused args must match /^_/u.","line":3,"column":49,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":56,"suggestions":[{"messageId":"removeVar","data":{"varName":"options"},"fix":{"range":[113,127],"text":""},"desc":"Remove unused variable 'options'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ApiHelpers } from \"../../../interpreters/ApiHelpers.js\";\n\nexport const setupMoleMashAPI = (sceneInstance, options = {}) => {\n return (interpreter, globalObject) => {\n const highlightWrapper = function (id, callback) {\n sceneInstance.highlightBlock(id).then(callback);\n };\n\n const moverToupeiraWrapper = function (linha, coluna, callback) {\n sceneInstance.moverToupeira(linha, coluna);\n setTimeout(callback, 800);\n };\n\n const aguardarWrapper = function (ms, callback) {\n setTimeout(callback, ms);\n };\n\n ApiHelpers.registerMultipleFunctions(interpreter, globalObject, [\n { name: \"highlightBlock\", wrapper: highlightWrapper, isAsync: true },\n { name: \"moverToupeira\", wrapper: moverToupeiraWrapper, isAsync: true },\n { name: \"aguardar\", wrapper: aguardarWrapper, isAsync: true },\n ]);\n };\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/hooks/useMoleMashTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/mole-mash/validation/validators.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/SemaforoGame.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBase' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBase"},"fix":{"range":[58,72],"text":""},"desc":"Remove unused variable 'GameBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameEditor"},"fix":{"range":[113,129],"text":""},"desc":"Remove unused variable 'GameEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BlocklyEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"BlocklyEditor"},"fix":{"range":[172,191],"text":""},"desc":"Remove unused variable 'BlocklyEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameStateProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameStateProvider"},"fix":{"range":[406,424],"text":""},"desc":"Remove unused variable 'GameStateProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SemaforoContent' is defined but never used. Allowed unused vars must match /^_/u.","line":17,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":17,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"SemaforoContent"},"fix":{"range":[687,1370],"text":""},"desc":"Remove unused variable 'SemaforoContent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useMemo } from \"react\";\nimport GameBase from \"../../components/game/GameBase\";\nimport GameEditor from \"../../components/game/GameEditor\";\nimport BlocklyEditor from \"../../components/game/editors/BlocklyEditor\";\nimport { createGame } from \"./game\";\nimport { gameConfig } from \"./config/config\";\nimport { generateDynamicToolbox, registerBlocks } from \"./blocks/blocks\";\nimport {\n GameStateProvider,\n useGameState,\n} from \"../../contexts/GameStateContext\";\nimport { useSemaforoTour } from \"./hooks/useSemaforoTour\";\nimport { debugSolutions } from \"./config/debugSolutions\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\n\nfunction SemaforoContent() {\n const { startTour } = useSemaforoTour();\n const { setFailureMessage, isDebugMode } = useGameState();\n\n useEffect(() => {\n registerBlocks();\n }, []);\n\n const toolboxGenerator = useMemo(() => {\n return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);\n }, []);\n\n return (\n \n \n \n \n \n );\n}\n\nexport default function SemaforoGame() {\n return (\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/__tests__/integration.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/blocks/blocks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/config/config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/config/debugSolutions.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/game.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/hooks/interpreterSetup.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'config' is assigned a value but never used. Allowed unused args must match /^_/u.","line":3,"column":41,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":47,"suggestions":[{"messageId":"removeVar","data":{"varName":"config"},"fix":{"range":[105,118],"text":""},"desc":"Remove unused variable 'config'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ApiHelpers } from \"../../../interpreters/ApiHelpers.js\";\n\nexport const setupSemaforoAPI = (scene, config = {}) => {\n const getAnimationDelay = () => {\n if (scene.executionSpeed >= 75) return 1;\n if (scene.executionSpeed >= 50) return 2;\n if (scene.executionSpeed >= 25) return 5;\n return 10;\n };\n\n return (interpreter, globalScope) => {\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"mudarSemaforo\",\n ApiHelpers.createActionWrapper(\n scene,\n \"mudarSemaforoCarros\",\n getAnimationDelay(),\n ),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"mudarSemaforoPedestre\",\n ApiHelpers.createActionWrapper(\n scene,\n \"mudarSemaforoPedestre\",\n getAnimationDelay(),\n ),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"aguardarSegundos\",\n ApiHelpers.createActionWrapper(scene, \"aguardarSegundos\"),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"piscarLuzPedestre\",\n ApiHelpers.createActionWrapper(scene, \"piscarLuzPedestre\"),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"tocarSom\",\n ApiHelpers.createActionWrapper(scene, \"tocarSom\"),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"pararSom\",\n ApiHelpers.createActionWrapper(scene, \"pararSom\"),\n true,\n );\n\n ApiHelpers.registerFunction(\n interpreter,\n globalScope,\n \"highlightBlock\",\n ApiHelpers.createHighlightWrapper(scene),\n false,\n );\n };\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/hooks/useSemaforoTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/semaforo/validation/validators.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'sceneRef' is defined but never used. Allowed unused args must match /^_/u.","line":32,"column":46,"nodeType":"Identifier","messageId":"unusedVar","endLine":32,"endColumn":54,"suggestions":[{"messageId":"removeVar","data":{"varName":"sceneRef"},"fix":{"range":[1299,1309],"text":""},"desc":"Remove unused variable 'sceneRef'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { BaseGameValidator } from \"../../../shared/BaseGameValidator\";\n\n/**\n * Validador para o jogo Semáforo\n *\n * @class SemaforoValidator\n * @extends BaseGameValidator\n *\n * @description\n * Responsável por validar as ações do usuário em cada fase do jogo.\n * Utiliza validação baseada em histórico de comandos executados,\n * verificando sequências de cores, tempos de espera, sincronização\n * de semáforos, piscar de luzes e controle de sons.\n *\n * Validações por fase:\n * - Fase 1: Sequência básica de cores (verde → amarelo → vermelho)\n * - Fase 2: Sequência com pausas entre mudanças\n * - Fase 3: Sincronização de semáforos de carros e pedestres\n * - Fase 4: Adiciona piscar da luz de pedestre\n * - Fase 5: Adiciona controle de sons\n */\nexport class SemaforoValidator extends BaseGameValidator {\n /**\n * Método principal de validação - delega para métodos específicos por fase\n *\n * @param {Array} history - Histórico de comandos executados\n * @param {Object} config - Configuração da fase atual\n * @param {Object} gameConfig - Configuração geral do jogo (inclui mensagens)\n * @param {Object} sceneRef - Referência à cena do jogo (opcional)\n * @returns {Object} Resultado da validação {success: boolean, reason?: string}\n */\n validatePhase(history, config, gameConfig, sceneRef) {\n // Verificar se tem histórico\n if (!history || history.length === 0) {\n return this.failure(\n gameConfig.mensagens?.semComandos || \"Nenhum comando executado\",\n );\n }\n\n // Roteamento por fase\n const phaseMethod = this[`validateFase${config.id}`];\n if (phaseMethod) {\n return phaseMethod.call(this, history, config, gameConfig);\n }\n\n return this.success();\n }\n\n /**\n * Fase 1: Sequência básica de cores do semáforo\n * Valida: verde → amarelo → vermelho\n *\n * @param {Array} history - Histórico de comandos\n * @param {Object} config - Configuração da fase (contém expectedSequence)\n * @param {Object} gameConfig - Config do jogo (contém mensagens)\n * @returns {Object} Resultado da validação\n *\n * @note Fase 1 injeta automaticamente aguardarSegundos(2) entre cada mudança\n * para melhorar a visualização, então ignoramos comandos 'aguardar' na validação\n */\n validateFase1(history, config, gameConfig) {\n // Extrair apenas comandos de semáforo (ignorar aguardares automáticos)\n const cores = history\n .filter((h) => h.tipo === \"semaforo\")\n .map((h) => h.cor);\n\n const esperado = config.expectedSequence || [\n \"vermelho\",\n \"amarelo\",\n \"verde\",\n ];\n\n if (cores.length !== esperado.length) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta || \"Sequência incompleta\",\n );\n }\n\n const match = cores.every((cor, i) => cor === esperado[i]);\n return match\n ? this.success()\n : this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Sequência de cores incorreta\",\n );\n }\n\n /**\n * Fase 2: Sequência com aguardar\n * Valida: sequência + pausas entre mudanças\n */\n validateFase2(history, config, gameConfig) {\n const esperado = config.expectedCommands;\n\n if (history.length !== esperado.length) {\n return this.failure(\n gameConfig.mensagens?.faltaAguardar ||\n \"Comandos faltando ou em excesso\",\n );\n }\n\n for (let i = 0; i < esperado.length; i++) {\n if (history[i].tipo !== esperado[i].tipo) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Sequência de comandos incorreta\",\n );\n }\n\n if (\n esperado[i].tipo === \"semaforo\" &&\n history[i].cor !== esperado[i].cor\n ) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Cor do semáforo incorreta\",\n );\n }\n }\n\n return this.success();\n }\n\n /**\n * Fase 3: Carros + pedestres\n * Valida: sincronização entre semáforos de carros e pedestres\n */\n validateFase3(history, config, gameConfig) {\n const esperado = config.expectedCommands;\n\n if (history.length !== esperado.length) {\n return this.failure(\n gameConfig.mensagens?.pedestreErrado ||\n \"Comandos faltando ou em excesso\",\n );\n }\n\n for (let i = 0; i < esperado.length; i++) {\n if (history[i].tipo !== esperado[i].tipo) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Tipo de comando incorreto\",\n );\n }\n\n if (\n (history[i].tipo === \"semaforo\" || history[i].tipo === \"pedestre\") &&\n history[i].cor !== esperado[i].cor\n ) {\n return this.failure(\n gameConfig.mensagens?.pedestreErrado || \"Cor incorreta\",\n );\n }\n\n if (\n history[i].tipo === \"aguardar\" &&\n history[i].seg !== esperado[i].seg\n ) {\n return this.failure(\n gameConfig.mensagens?.faltaAguardar || \"Tempo de espera incorreto\",\n );\n }\n }\n\n return this.success();\n }\n\n /**\n * Fase 4: + piscar\n * Valida: inclui piscar da luz do pedestre\n */\n validateFase4(history, config, gameConfig) {\n const esperado = config.expectedCommands;\n\n if (history.length !== esperado.length) {\n return this.failure(\n gameConfig.mensagens?.semPiscar || \"Comandos faltando ou em excesso\",\n );\n }\n\n for (let i = 0; i < esperado.length; i++) {\n if (history[i].tipo !== esperado[i].tipo) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Tipo de comando incorreto\",\n );\n }\n\n if (\n (history[i].tipo === \"semaforo\" ||\n history[i].tipo === \"pedestre\" ||\n history[i].tipo === \"piscar\") &&\n history[i].cor !== esperado[i].cor\n ) {\n return this.failure(gameConfig.mensagens?.semPiscar || \"Cor incorreta\");\n }\n\n if (\n (history[i].tipo === \"aguardar\" || history[i].tipo === \"piscar\") &&\n history[i].seg !== esperado[i].seg\n ) {\n return this.failure(\n gameConfig.mensagens?.faltaAguardar || \"Tempo incorreto\",\n );\n }\n }\n\n return this.success();\n }\n\n /**\n * Fase 5: + sons\n * Valida: inclui controle de sons (tocar e parar)\n */\n validateFase5(history, config, gameConfig) {\n const esperado = config.expectedCommands;\n\n if (history.length !== esperado.length) {\n return this.failure(\n gameConfig.mensagens?.semSom || \"Comandos faltando ou em excesso\",\n );\n }\n\n for (let i = 0; i < esperado.length; i++) {\n if (history[i].tipo !== esperado[i].tipo) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta ||\n \"Tipo de comando incorreto\",\n );\n }\n\n if (\n (history[i].tipo === \"semaforo\" ||\n history[i].tipo === \"pedestre\" ||\n history[i].tipo === \"piscar\") &&\n history[i].cor !== esperado[i].cor\n ) {\n return this.failure(\n gameConfig.mensagens?.sequenciaIncorreta || \"Cor incorreta\",\n );\n }\n\n if (\n (history[i].tipo === \"aguardar\" || history[i].tipo === \"piscar\") &&\n history[i].seg !== esperado[i].seg\n ) {\n return this.failure(\n gameConfig.mensagens?.faltaAguardar || \"Tempo incorreto\",\n );\n }\n\n if (\n (history[i].tipo === \"tocar\" || history[i].tipo === \"parar\") &&\n history[i].som !== esperado[i].som\n ) {\n return this.failure(gameConfig.mensagens?.semSom || \"Som incorreto\");\n }\n }\n\n return this.success();\n }\n}\n\n/**\n * Função de validação exportada para uso no gameController\n */\nexport function validateSolution(history, config, gameConfig, sceneRef) {\n const validator = new SemaforoValidator();\n return validator.validatePhase(history, config, gameConfig, sceneRef);\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/TurtleGame.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameBase' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameBase"},"fix":{"range":[58,72],"text":""},"desc":"Remove unused variable 'GameBase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameEditor"},"fix":{"range":[113,129],"text":""},"desc":"Remove unused variable 'GameEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BlocklyEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"BlocklyEditor"},"fix":{"range":[172,191],"text":""},"desc":"Remove unused variable 'BlocklyEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeEditor"},"fix":{"range":[245,261],"text":""},"desc":"Remove unused variable 'CodeEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameStateProvider' is defined but never used. Allowed unused vars must match /^_/u.","line":14,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":14,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameStateProvider"},"fix":{"range":[495,513],"text":""},"desc":"Remove unused variable 'GameStateProvider'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'TurtleGameContent' is defined but never used. Allowed unused vars must match /^_/u.","line":22,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":22,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"TurtleGameContent"},"fix":{"range":[772,2081],"text":""},"desc":"Remove unused variable 'TurtleGameContent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useMemo } from \"react\";\nimport GameBase from \"../../components/game/GameBase\";\nimport GameEditor from \"../../components/game/GameEditor\";\nimport BlocklyEditor from \"../../components/game/editors/BlocklyEditor\";\nimport CodeEditor from \"../../components/game/editors/CodeEditor\";\nimport { createGame } from \"./game\";\nimport { gameConfig } from \"./config/config\";\nimport {\n registerBlocks,\n turtleToolbox,\n generateDynamicToolbox,\n} from \"./blocks/blocks\";\nimport {\n GameStateProvider,\n useGameState,\n} from \"../../contexts/GameStateContext\";\nimport { useTurtleTour } from \"./hooks/useTurtleTour\";\nimport { debugSolutions } from \"./config/debugSolutions\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\n\nfunction TurtleGameContent() {\n const { currentPhase, setEditorType, isDebugMode, setFailureMessage } =\n useGameState();\n const { startTour } = useTurtleTour();\n\n useEffect(() => {\n registerBlocks();\n }, []);\n\n useEffect(() => {\n if (currentPhase === 10) {\n setEditorType(\"code\");\n } else {\n setEditorType(\"blockly\");\n }\n }, [currentPhase, setEditorType]);\n\n const toolboxGenerator = useMemo(() => {\n const currentPhaseConfig = gameConfig.fases.find(\n (fase) => fase.id === currentPhase,\n );\n\n return () => {\n if (\n currentPhaseConfig?.allowedBlocks &&\n currentPhaseConfig.allowedBlocks.length > 0\n ) {\n return generateDynamicToolbox(currentPhaseConfig.allowedBlocks);\n }\n return turtleToolbox;\n };\n }, [currentPhase]);\n\n const renderEditor = () => {\n if (currentPhase === 10) {\n return ;\n }\n return (\n \n );\n };\n\n return (\n \n {renderEditor()} \n \n );\n}\n\nexport default function TurtleGame() {\n return (\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/__tests__/integration.test.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'id' is defined but never used. Allowed unused args must match /^_/u.","line":556,"column":18,"nodeType":"Identifier","messageId":"unusedVar","endLine":556,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"id"},"fix":{"range":[12264,12266],"text":""},"desc":"Remove unused variable 'id'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'freeDrawCode' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":869,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":869,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"freeDrawCode"},"fix":{"range":[22309,22424],"text":""},"desc":"Remove unused variable 'freeDrawCode'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport { GameInterpreter } from \"../../../interpreters/GameInterpreter\";\nimport { setupTurtleAPI } from \"../hooks/interpreterSetup\";\nimport { validateSolution } from \"../validation/validators\";\n\n// Mock de soluções para teste (movido de config/solutions.js)\nconst SOLUTIONS = {\n fase1: `\n var count0 = 0;\n while (count0 < 4) {\n move(100);\n turn(90);\n count0 = count0 + 1;\n }\n `,\n fase1_fail: `\n var count0 = 0;\n while (count0 < 3) {\n move(100);\n turn(90);\n count0 = count0 + 1;\n }\n `,\n fase2: `\n var count1 = 0;\n while (count1 < 5) {\n move(100);\n turn(72);\n count1 = count1 + 1;\n }\n `,\n fase2_fail: `\n var count1 = 0;\n while (count1 < 5) {\n move(100);\n turn(90);\n count1 = count1 + 1;\n }\n `,\n fase3: `\n penColour('#ffff00');\n var count5 = 0;\n while (count5 < 5) {\n move(100);\n turn(144);\n count5 = count5 + 1;\n }\n `,\n fase3_fail: `\n penColour('#ff0000');\n var count5 = 0;\n while (count5 < 5) {\n move(100);\n turn(144);\n count5 = count5 + 1;\n }\n `,\n fase4: `\n penColour('#ffff00');\n var count7 = 0;\n while (count7 < 5) {\n move(50);\n turn(144);\n count7 = count7 + 1;\n }\n penDown(false);\n move(150);\n penDown(true);\n move(20);\n `,\n fase4_fail: `\n penColour('#ffff00');\n var count7 = 0;\n while (count7 < 5) {\n move(50);\n turn(144);\n count7 = count7 + 1;\n }\n move(20);\n `,\n fase5: `\n penColour('#ffff00');\n var count9 = 0;\n while (count9 < 4) {\n var count8 = 0;\n while (count8 < 5) {\n move(50);\n turn(144);\n count8 = count8 + 1;\n }\n penDown(false);\n move(150);\n turn(90);\n penDown(true);\n count9 = count9 + 1;\n }\n `,\n fase5_fail: `\n penColour('#ffff00');\n var count9 = 0;\n while (count9 < 3) {\n var count8 = 0;\n while (count8 < 5) {\n move(50);\n turn(144);\n count8 = count8 + 1;\n }\n penDown(false);\n move(150);\n turn(90);\n penDown(true);\n count9 = count9 + 1;\n }\n `,\n fase6: `\n penColour('#ffff00');\n var count20 = 0;\n while (count20 < 3) {\n var count19 = 0;\n while (count19 < 5) {\n move(50);\n turn(144);\n count19 = count19 + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n count20 = count20 + 1;\n }\n penColour('#ffffff');\n penDown(false);\n var count21 = 0;\n while (count21 < 3) {\n turn(90);\n count21 = count21 + 1;\n }\n move(100);\n penDown(true);\n move(50);\n `,\n fase6_fail: `\n penColour('#ffff00');\n var count20 = 0;\n while (count20 < 3) {\n var count19 = 0;\n while (count19 < 5) {\n move(50);\n turn(144);\n count19 = count19 + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n count20 = count20 + 1;\n }\n `,\n fase7: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 3) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n penDown(false);\n var countTurn = 0;\n while (countTurn < 3) {\n turn(90);\n countTurn = countTurn + 1;\n }\n move(100);\n penDown(true);\n penColour('#ffffff');\n var countRays = 0;\n while (countRays < 4) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(45);\n countRays = countRays + 1;\n }\n `,\n fase7_fail: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 3) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n `,\n fase8: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 3) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n penDown(false);\n var countTurn = 0;\n while (countTurn < 3) {\n turn(90);\n countTurn = countTurn + 1;\n }\n move(100);\n penDown(true);\n penColour('#ffffff');\n var countCircle = 0;\n while (countCircle < 360) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(1);\n countCircle = countCircle + 1;\n }\n `,\n fase8_fail: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 3) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n penDown(false);\n var countTurn = 0;\n while (countTurn < 3) {\n turn(90);\n countTurn = countTurn + 1;\n }\n move(100);\n penDown(true);\n penColour('#ffffff');\n var countCircle = 0;\n while (countCircle < 180) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(1);\n countCircle = countCircle + 1;\n }\n `,\n fase9: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 3) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n penDown(false);\n var countTurn = 0;\n while (countTurn < 3) {\n turn(90);\n countTurn = countTurn + 1;\n }\n move(100);\n penDown(true);\n penColour('#ffffff');\n var countCircle1 = 0;\n while (countCircle1 < 360) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(1);\n countCircle1 = countCircle1 + 1;\n }\n turn(120);\n move(20);\n penColour('#000000');\n var countCircle2 = 0;\n while (countCircle2 < 360) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(1);\n countCircle2 = countCircle2 + 1;\n }\n `,\n fase9_fail: `\n penColour('#ffff00');\n var countStars = 0;\n while (countStars < 2) {\n var countPoints = 0;\n while (countPoints < 5) {\n move(50);\n turn(144);\n countPoints = countPoints + 1;\n }\n penDown(false);\n move(150);\n turn(120);\n penDown(true);\n countStars = countStars + 1;\n }\n penDown(false);\n var countTurn = 0;\n while (countTurn < 3) {\n turn(90);\n countTurn = countTurn + 1;\n }\n move(100);\n penDown(true);\n penColour('#ffffff');\n var countCircle1 = 0;\n while (countCircle1 < 360) {\n move(50);\n turn(90);\n turn(90);\n move(50);\n turn(90);\n turn(90);\n turn(1);\n countCircle1 = countCircle1 + 1;\n }\n `,\n fase10: null,\n fase10_fail: null,\n};\n\n// MOCK DA CONFIGURAÇÃO DO JOGO\n// Replicamos aqui apenas as regras que o validador consome\nconst MOCK_GAME_CONFIG = {\n mensagens: {\n semDesenho:\n \"Você não desenhou nada! Certifique-se de usar os comandos move() com a caneta abaixada.\",\n desenhoNaoConfere:\n \"O desenho não está correto. Verifique a forma, posição e cores.\",\n erroGeral: \"Erro técnico durante a validação.\",\n sucessoGenerico: \"Perfeito! Seu desenho está correto.\",\n timeoutExcedido: \"Tempo limite excedido!\",\n },\n fases: [\n { id: 1, nome: \"O Quadrado\", requiredDrawingMatch: true },\n { id: 2, nome: \"O Pentágono\", requiredDrawingMatch: true },\n { id: 3, nome: \"A Estrela\", requiredDrawingMatch: true },\n { id: 4, nome: \"Estrela Colorida\", requiredDrawingMatch: true },\n { id: 5, nome: \"Três Estrelas\", requiredDrawingMatch: true },\n { id: 6, nome: \"Composição\", requiredDrawingMatch: true },\n { id: 7, nome: \"Três Estrelas e Raios\", requiredDrawingMatch: true },\n { id: 8, nome: \"Três Estrelas e um Círculo\", requiredDrawingMatch: true },\n { id: 9, nome: \"Lua Crescente\", requiredDrawingMatch: true },\n { id: 10, nome: \"Desafio Livre\", requiredDrawingMatch: false },\n ],\n};\n\n/**\n * Mock do Graphics do Phaser\n * Simula o comportamento de desenho sem renderizar na tela\n */\nclass MockGraphics {\n constructor() {\n this.commandBuffer = [];\n this.lineStyle = vi.fn();\n this.fillStyle = vi.fn();\n this.beginPath = vi.fn();\n this.moveTo = vi.fn((x, y) => {\n this.commandBuffer.push({ type: \"moveTo\", x, y });\n });\n this.lineTo = vi.fn((x, y) => {\n this.commandBuffer.push({ type: \"lineTo\", x, y });\n });\n this.strokePath = vi.fn();\n this.fillRect = vi.fn();\n this.clear = vi.fn(() => {\n this.commandBuffer = [];\n });\n }\n\n // Simula extração de pontos para validação\n getPoints() {\n return this.commandBuffer\n .filter((cmd) => cmd.type === \"lineTo\" || cmd.type === \"moveTo\")\n .map((cmd) => ({ x: cmd.x, y: cmd.y }));\n }\n}\n\n/**\n * Cena Headless do Turtle Game (sem renderização gráfica)\n * Simula toda a lógica de desenho e estado necessária para os testes\n */\nclass HeadlessTurtleScene {\n constructor() {\n // Estado da tartaruga\n this.turtleX = 400;\n this.turtleY = 300;\n this.turtleAngle = -90; // Começa apontando para cima\n this.penIsDown = true;\n this.penColor = \"#FFFFFF\";\n\n // Graphics\n this.playerGraphics = new MockGraphics();\n this.validationGraphics = new MockGraphics();\n this.activeGraphics = this.playerGraphics;\n\n // Histórico de ações\n this.historico = [];\n\n // Velocidade de execução\n this.executionSpeed = 50;\n\n // Mock do som\n this.sound = { play: vi.fn() };\n }\n\n // ==================== API PÚBLICA ====================\n\n /**\n * Move a tartaruga para frente\n */\n move(distance) {\n this.historico.push({\n action: \"move\",\n distance,\n timestamp: Date.now(),\n });\n\n if (this.penIsDown) {\n // Calcula nova posição\n const radians = (this.turtleAngle * Math.PI) / 180;\n const startX = this.turtleX;\n const startY = this.turtleY;\n const endX = this.turtleX + Math.cos(radians) * distance;\n const endY = this.turtleY + Math.sin(radians) * distance;\n\n // Desenha linha\n this.activeGraphics.moveTo(startX, startY);\n this.activeGraphics.lineTo(endX, endY);\n\n // Atualiza posição\n this.turtleX = endX;\n this.turtleY = endY;\n } else {\n // Move sem desenhar\n const radians = (this.turtleAngle * Math.PI) / 180;\n this.turtleX += Math.cos(radians) * distance;\n this.turtleY += Math.sin(radians) * distance;\n }\n\n // Retorna promise resolvida imediatamente (sem delay, como no jogo real quando gera solução)\n return Promise.resolve();\n }\n\n /**\n * Gira a tartaruga\n */\n turn(angle) {\n this.historico.push({\n action: \"turn\",\n angle,\n timestamp: Date.now(),\n });\n\n this.turtleAngle = (this.turtleAngle + angle) % 360;\n return Promise.resolve();\n }\n\n /**\n * Controla se a caneta está abaixada (desenha) ou levantada (não desenha)\n */\n penDown(isDown) {\n this.historico.push({\n action: \"penDown\",\n isDown,\n timestamp: Date.now(),\n });\n\n this.penIsDown = isDown;\n }\n\n /**\n * Define a cor da caneta\n */\n penColour(color) {\n this.historico.push({\n action: \"penColour\",\n color,\n timestamp: Date.now(),\n });\n\n this.penColor = color;\n }\n\n /**\n * Pinta o fundo (não usado na validação)\n */\n pintarFundo(color) {\n this.historico.push({\n action: \"pintarFundo\",\n color,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Mock do highlightBlock (não faz nada nos testes)\n */\n highlightBlock(id) {\n // No-op nos testes\n }\n\n // ==================== VALIDAÇÃO ====================\n\n /**\n * Compara o desenho do jogador com o desenho de validação (solução)\n * Retorna true se os desenhos são suficientemente similares\n */\n compareDrawings() {\n const playerPoints = this.playerGraphics.getPoints();\n const validationPoints = this.validationGraphics.getPoints();\n\n // Se não há pontos no desenho do jogador, falha\n if (playerPoints.length === 0) {\n return false;\n }\n\n // Se não há desenho de validação, assume que não há comparação necessária\n if (validationPoints.length === 0) {\n return playerPoints.length > 0;\n }\n\n // Verifica se ambos têm aproximadamente o mesmo número de pontos\n // (tolerância de 20% para variações de implementação)\n const pointRatio = playerPoints.length / validationPoints.length;\n if (pointRatio < 0.8 || pointRatio > 1.2) {\n return false;\n }\n\n // Compara assinaturas topológicas simplificadas\n const playerSignature = this._getSimpleSignature(playerPoints);\n const validationSignature = this._getSimpleSignature(validationPoints);\n\n return this._compareSignatures(playerSignature, validationSignature);\n }\n\n /**\n * Gera uma assinatura simplificada do desenho\n * (bounding box + contagem de segmentos)\n */\n _getSimpleSignature(points) {\n if (points.length === 0)\n return { minX: 0, maxX: 0, minY: 0, maxY: 0, segments: 0 };\n\n const xs = points.map((p) => p.x);\n const ys = points.map((p) => p.y);\n\n return {\n minX: Math.min(...xs),\n maxX: Math.max(...xs),\n minY: Math.min(...ys),\n maxY: Math.max(...ys),\n segments: points.length - 1,\n };\n }\n\n /**\n * Compara duas assinaturas com tolerância\n */\n _compareSignatures(sig1, sig2) {\n const tolerance = 0.15; // 15% de tolerância\n\n // Compara dimensões\n const width1 = sig1.maxX - sig1.minX;\n const height1 = sig1.maxY - sig1.minY;\n const width2 = sig2.maxX - sig2.minX;\n const height2 = sig2.maxY - sig2.minY;\n\n const widthRatio = Math.abs(width1 - width2) / Math.max(width1, width2);\n const heightRatio =\n Math.abs(height1 - height2) / Math.max(height1, height2);\n\n if (widthRatio > tolerance || heightRatio > tolerance) {\n return false;\n }\n\n // Compara número de segmentos\n const segmentRatio =\n Math.abs(sig1.segments - sig2.segments) /\n Math.max(sig1.segments, sig2.segments);\n if (segmentRatio > tolerance) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Gera o desenho de validação executando o código da solução\n */\n async generateValidationDrawing(solutionCode) {\n if (!solutionCode) return;\n\n // Salva estado atual\n const savedGraphics = this.activeGraphics;\n const savedX = this.turtleX;\n const savedY = this.turtleY;\n const savedAngle = this.turtleAngle;\n const savedPenDown = this.penIsDown;\n\n // Reseta para desenho de validação\n this.activeGraphics = this.validationGraphics;\n this.validationGraphics.clear();\n this.turtleX = 400;\n this.turtleY = 300;\n this.turtleAngle = -90;\n this.penIsDown = true;\n\n // Executa o código da solução\n const api = setupTurtleAPI(this);\n const interpreter = new GameInterpreter({ stepDelay: 0, pauseExec: false });\n\n try {\n await interpreter.executeCode(solutionCode, api);\n } catch (error) {\n console.error(\"Erro ao gerar desenho de validação:\", error);\n }\n\n // Restaura estado\n this.activeGraphics = savedGraphics;\n this.turtleX = savedX;\n this.turtleY = savedY;\n this.turtleAngle = savedAngle;\n this.penIsDown = savedPenDown;\n }\n\n /**\n * Reseta o estado da tartaruga\n */\n resetTurtle() {\n this.turtleX = 400;\n this.turtleY = 300;\n this.turtleAngle = -90;\n this.penIsDown = true;\n this.penColor = \"#FFFFFF\";\n this.playerGraphics.clear();\n this.historico = [];\n }\n}\n\n// ==================== SUITE DE TESTES ====================\n\ndescribe(\"Turtle Game - Integração de Lógica (Código -> Desenho -> Validação)\", () => {\n let scene;\n let interpreter;\n\n beforeEach(() => {\n scene = new HeadlessTurtleScene();\n interpreter = new GameInterpreter({ stepDelay: 0, pauseExec: false });\n });\n\n /**\n * Fluxo completo de teste:\n * 1. Executa código do aluno (popula playerGraphics)\n * 2. Gera desenho de validação (solução)\n * 3. Valida comparando os desenhos\n */\n const runFlow = async (playerCode, solutionCode, phaseId) => {\n const api = setupTurtleAPI(scene);\n\n // 1. Executa código do aluno (desenha no playerGraphics)\n scene.activeGraphics = scene.playerGraphics;\n scene.resetTurtle(); // Garante estado limpo\n await interpreter.executeCode(playerCode, api);\n\n // 2. Gera desenho de validação\n await scene.generateValidationDrawing(solutionCode);\n\n // 3. Valida\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === phaseId);\n return validateSolution(\n scene.historico,\n configFase,\n MOCK_GAME_CONFIG,\n scene,\n );\n };\n\n // ==================== TESTES DE FASES ====================\n\n describe(\"Fase 1: O Quadrado\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase1, SOLUTIONS.fase1, 1);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta (3 lados ao invés de 4)\", async () => {\n const result = await runFlow(SOLUTIONS.fase1_fail, SOLUTIONS.fase1, 1);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"não está correto\");\n });\n\n it(\"Deve registrar ações no histórico\", async () => {\n const api = setupTurtleAPI(scene);\n await interpreter.executeCode(SOLUTIONS.fase1, api);\n\n expect(scene.historico.length).toBeGreaterThan(0);\n expect(scene.historico.some((h) => h.action === \"move\")).toBe(true);\n expect(scene.historico.some((h) => h.action === \"turn\")).toBe(true);\n });\n });\n\n describe(\"Fase 2: O Pentágono\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase2, SOLUTIONS.fase2, 2);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta (ângulos errados)\", async () => {\n const result = await runFlow(SOLUTIONS.fase2_fail, SOLUTIONS.fase2, 2);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"não está correto\");\n });\n });\n\n describe(\"Fase 3: A Estrela\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase3, SOLUTIONS.fase3, 3);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta (4 pontas ao invés de 5)\", async () => {\n // Desenha uma estrela com 4 pontas ao invés de 5\n const wrongCode = `\n var count = 0;\n while (count < 4) {\n move(100);\n turn(144);\n count = count + 1;\n }\n `;\n const result = await runFlow(wrongCode, SOLUTIONS.fase3, 3);\n expect(result.success).toBe(false);\n });\n });\n\n describe(\"Fase 4: Estrela Colorida\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase4, SOLUTIONS.fase4, 4);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta\", async () => {\n const result = await runFlow(SOLUTIONS.fase4_fail, SOLUTIONS.fase4, 4);\n expect(result.success).toBe(false);\n });\n });\n\n describe(\"Fase 5: Três Estrelas\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase5, SOLUTIONS.fase5, 5);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta\", async () => {\n const result = await runFlow(SOLUTIONS.fase5_fail, SOLUTIONS.fase5, 5);\n expect(result.success).toBe(false);\n });\n }, 8000);\n describe(\"Fase 6: Composição\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase6, SOLUTIONS.fase6, 6);\n expect(result.success).toBe(true);\n });\n\n it(\"Deve reprovar solução incorreta\", async () => {\n const result = await runFlow(SOLUTIONS.fase6_fail, SOLUTIONS.fase6, 6);\n expect(result.success).toBe(false);\n });\n });\n\n // Fases 7, 8, 9 agora têm soluções implementadas\n\n describe(\"Fase 7: Três Estrelas e Raios\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase7, SOLUTIONS.fase7, 7);\n expect(result.success).toBe(true);\n }, 8000);\n\n it(\"Deve reprovar solução incorreta (sem raios brancos)\", async () => {\n const result = await runFlow(SOLUTIONS.fase7_fail, SOLUTIONS.fase7, 7);\n expect(result.success).toBe(false);\n }, 8000);\n });\n\n describe(\"Fase 8: Três Estrelas e um Círculo\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase8, SOLUTIONS.fase8, 8);\n expect(result.success).toBe(true);\n }, 180000); // 3 minutos - círculo com 360 iterações\n\n it(\"Deve reprovar solução incorreta (meio círculo)\", async () => {\n const result = await runFlow(SOLUTIONS.fase8_fail, SOLUTIONS.fase8, 8);\n expect(result.success).toBe(false);\n }, 180000);\n });\n\n describe(\"Fase 9: Lua Crescente\", () => {\n it(\"Deve aprovar solução correta\", async () => {\n const result = await runFlow(SOLUTIONS.fase9, SOLUTIONS.fase9, 9);\n expect(result.success).toBe(true);\n }, 180000); // 3 minutos - dois círculos com 360 iterações cada\n\n it(\"Deve reprovar solução incorreta (2 estrelas ao invés de 3)\", async () => {\n const result = await runFlow(SOLUTIONS.fase9_fail, SOLUTIONS.fase9, 9);\n expect(result.success).toBe(false);\n }, 180000);\n });\n\n describe(\"Fase 10: Desafio Livre\", () => {\n it(\"Deve sempre aprovar qualquer código (sem validação)\", async () => {\n const freeDrawCode = `\n move(50);\n turn(45);\n move(50);\n `;\n\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === 10);\n const result = validateSolution(\n scene.historico,\n configFase,\n MOCK_GAME_CONFIG,\n scene,\n );\n\n expect(result.success).toBe(true);\n });\n\n it(\"Deve aprovar mesmo sem desenho algum\", async () => {\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === 10);\n const result = validateSolution([], configFase, MOCK_GAME_CONFIG, scene);\n\n expect(result.success).toBe(true);\n });\n });\n\n // ==================== TESTES DE COMPORTAMENTO ====================\n\n describe(\"Comportamento da API\", () => {\n it(\"Deve desenhar apenas quando caneta está abaixada\", async () => {\n const api = setupTurtleAPI(scene);\n\n // Caneta levantada\n scene.penDown(false);\n await interpreter.executeCode(\"move(100);\", api);\n const pointsWithPenUp = scene.playerGraphics.commandBuffer.length;\n\n // Limpa e testa com caneta abaixada\n scene.resetTurtle();\n scene.penDown(true);\n await interpreter.executeCode(\"move(100);\", api);\n const pointsWithPenDown = scene.playerGraphics.commandBuffer.length;\n\n expect(pointsWithPenUp).toBe(0);\n expect(pointsWithPenDown).toBeGreaterThan(0);\n });\n\n it(\"Deve atualizar posição da tartaruga corretamente\", async () => {\n const api = setupTurtleAPI(scene);\n const initialY = scene.turtleY;\n\n // Move para cima (ângulo -90 = norte), então Y deve diminuir\n await interpreter.executeCode(\"move(100);\", api);\n\n // X permanece o mesmo (movimento vertical), Y muda\n expect(scene.turtleY).not.toBe(initialY);\n expect(scene.turtleY).toBeLessThan(initialY); // Moveu para cima\n });\n\n it(\"Deve registrar todas as ações no histórico\", async () => {\n const api = setupTurtleAPI(scene);\n\n await interpreter.executeCode(\n `\n move(100);\n turn(90);\n penDown(false);\n penColour('red');\n `,\n api,\n );\n\n expect(scene.historico).toHaveLength(4);\n expect(scene.historico[0].action).toBe(\"move\");\n expect(scene.historico[1].action).toBe(\"turn\");\n expect(scene.historico[2].action).toBe(\"penDown\");\n expect(scene.historico[3].action).toBe(\"penColour\");\n });\n });\n\n describe(\"Validação Visual\", () => {\n it(\"Deve falhar quando não há desenho do jogador\", async () => {\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === 1);\n\n // Não executa código, então não há desenho\n const result = validateSolution([], configFase, MOCK_GAME_CONFIG, scene);\n\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"não desenhou nada\");\n });\n\n it(\"Deve comparar desenhos usando geometria simplificada\", async () => {\n // Desenha dois quadrados idênticos em locais diferentes do código\n const code1 = `\n for (var i = 0; i < 4; i++) {\n move(100);\n turn(90);\n }\n `;\n\n const code2 = `\n var count = 0;\n while (count < 4) {\n move(100);\n turn(90);\n count++;\n }\n `;\n\n const result = await runFlow(code1, code2, 1);\n expect(result.success).toBe(true);\n });\n });\n\n describe(\"Casos Especiais\", () => {\n it(\"Deve tratar configuração de fase ausente\", () => {\n const result = validateSolution([], {}, MOCK_GAME_CONFIG, scene);\n\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Erro técnico\");\n });\n\n it(\"Deve tratar ausência de referência à cena\", () => {\n const configFase = MOCK_GAME_CONFIG.fases.find((f) => f.id === 1);\n const result = validateSolution([], configFase, MOCK_GAME_CONFIG, null);\n\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Erro técnico\");\n });\n });\n});\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/blocks/blocks.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/config/config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/config/debugSolutions.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/game.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":586,"column":14,"nodeType":"Identifier","messageId":"unusedVar","endLine":586,"endColumn":15},{"ruleId":"no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":617,"column":14,"nodeType":"Identifier","messageId":"unusedVar","endLine":617,"endColumn":15}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Phaser from \"phaser\";\nimport { BaseGameScene } from \"../../shared/BaseGameScene.js\";\nimport { GameInterpreter } from \"../../interpreters/GameInterpreter.js\";\nimport { setupTurtleAPI } from \"./hooks/interpreterSetup.js\";\nimport { validateSolution } from \"./validation/validators.js\";\nimport turtleImg1 from \"./assets/1.png\";\nimport turtleImg2 from \"./assets/2.png\";\nimport win from \"./assets/win.mp3\";\nimport fail from \"./assets/fail.mp3\";\n\nconst ASSETS = {\n IMG: { TURTLE1: \"turtle1\", TURTLE2: \"turtle2\" },\n AUDIO: { WIN: \"win\", FAIL: \"fail\" },\n};\n\nconst CONSTANTES = {\n LARGURA: 640,\n ALTURA: 480,\n};\n\nclass TurtleScene extends BaseGameScene {\n constructor() {\n super(\"TurtleScene\");\n\n this.turtleState = null;\n this.turtleSprite = null;\n this.solutionGraphics = null;\n this.playerGraphics = null;\n this.validationGraphics = null;\n this.activeGraphics = null;\n this.backgroundRect = null;\n this.playerColors = [];\n this.solutionColors = [];\n this.executionSpeed = 50;\n this.sliderWidth = 0;\n this.sliderFill = null;\n this.sliderHandle = null;\n this.speedText = null;\n this.playerRT = null;\n this.validationRT = null;\n this.resultadoJogada = \"em_andamento\";\n }\n\n preload() {\n this.preloadGlobalAssets();\n this.load.image(ASSETS.IMG.TURTLE1, turtleImg1);\n this.load.image(ASSETS.IMG.TURTLE2, turtleImg2);\n this.load.audio(ASSETS.AUDIO.WIN, win);\n this.load.audio(ASSETS.AUDIO.FAIL, fail);\n }\n\n init(data) {\n super.init(data);\n this.executionSpeed = 50;\n this.turtleState = {\n x: 0,\n y: 0,\n angle: 0,\n penDown: true,\n penColour: 0xffffff,\n };\n this.playerColors = [];\n this.solutionColors = [];\n this.resultadoJogada = \"em_andamento\";\n }\n\n setExecutionSpeed(speed) {\n this.executionSpeed = Math.max(1, Math.min(100, speed));\n\n if (typeof this.updateSpeedUI === \"function\") {\n this.updateSpeedUI();\n }\n }\n\n updateSpeedUI() {\n if (this.speedText) {\n this.speedText.setText(`Velocidade: ${this.executionSpeed}%`);\n }\n if (this.sliderFill) {\n this.sliderFill.setSize(\n (this.executionSpeed / 100) * this.sliderWidth,\n 12,\n );\n }\n if (this.sliderHandle) {\n this.sliderHandle.x =\n (this.executionSpeed / 100 - 0.5) * this.sliderWidth;\n }\n }\n\n createSpeedControl() {\n const gameWidth = this.game.config.width;\n const gameHeight = this.game.config.height;\n\n this.sliderWidth = Math.min(gameWidth - 40, 300);\n const controlHeight = 50;\n const centerX = gameWidth / 2;\n const bottomY = gameHeight - 35;\n\n const controlsContainer = this.add.container(centerX, bottomY);\n\n const bg = this.add.rectangle(\n 0,\n 0,\n this.sliderWidth + 80,\n controlHeight,\n 0x000000,\n 0.85,\n );\n bg.setOrigin(0.5, 0.5);\n controlsContainer.add(bg);\n\n const sliderBg = this.add.rectangle(0, 8, this.sliderWidth, 12, 0x444444);\n controlsContainer.add(sliderBg);\n\n this.sliderFill = this.add.rectangle(\n -this.sliderWidth / 2,\n 8,\n (this.executionSpeed / 100) * this.sliderWidth,\n 28,\n 0x00ff88,\n );\n this.sliderFill.setOrigin(0, 0.5);\n controlsContainer.add(this.sliderFill);\n\n this.sliderHandle = this.add.circle(\n (this.executionSpeed / 100 - 0.5) * this.sliderWidth,\n 8,\n 16,\n 0xffffff,\n );\n this.sliderHandle.setInteractive({ useHandCursor: true });\n this.sliderHandle.setStrokeStyle(2, 0x333333);\n controlsContainer.add(this.sliderHandle);\n\n this.speedText = this.add.text(\n 0,\n -12,\n `Velocidade: ${this.executionSpeed}%`,\n {\n fontSize: \"14px\",\n fill: \"#ffffff\",\n fontFamily: \"Arial\",\n },\n );\n this.speedText.setOrigin(0.5, 0.5);\n controlsContainer.add(this.speedText);\n\n let isDragging = false;\n\n this.sliderHandle.on(\"pointerdown\", () => {\n isDragging = true;\n });\n\n this.input.on(\"pointermove\", (pointer) => {\n if (isDragging) {\n const localX = pointer.x - centerX;\n const clampedX = Math.max(\n -this.sliderWidth / 2,\n Math.min(this.sliderWidth / 2, localX),\n );\n const newSpeed = Math.round(\n ((clampedX + this.sliderWidth / 2) / this.sliderWidth) * 100,\n );\n this.setExecutionSpeed(newSpeed);\n }\n });\n\n this.input.on(\"pointerup\", () => {\n isDragging = false;\n });\n\n sliderBg.setInteractive();\n sliderBg.on(\"pointerdown\", (pointer) => {\n const localX = pointer.x - centerX;\n const clampedX = Math.max(\n -this.sliderWidth / 2,\n Math.min(this.sliderWidth / 2, localX),\n );\n const newSpeed = Math.round(\n ((clampedX + this.sliderWidth / 2) / this.sliderWidth) * 100,\n );\n this.setExecutionSpeed(newSpeed);\n });\n\n controlsContainer.setDepth(9999);\n }\n\n calcDuration() {\n return 500 + ((1 - 500) / (100 - 1)) * (this.executionSpeed - 1);\n }\n\n resetTurtle() {\n this.turtleState.x = this.game.config.width / 2;\n this.turtleState.y = this.game.config.height / 2;\n this.turtleState.angle = 0;\n this.turtleState.penDown = true;\n this.turtleState.penColour = 0xffffff;\n\n if (this.turtleSprite) {\n this.turtleSprite.setPosition(this.turtleState.x, this.turtleState.y);\n this.turtleSprite.setAngle(0);\n this.turtleSprite.stop();\n this.turtleSprite.setTexture(ASSETS.IMG.TURTLE1);\n }\n }\n\n pintarFundo(color) {\n let colorValue;\n\n if (typeof color === \"string\") {\n if (color.startsWith(\"#\")) {\n colorValue = parseInt(color.substring(1), 16);\n } else {\n const colorMap = {\n vermelho: 0xff0000,\n azul: 0x0000ff,\n verde: 0x00ff00,\n amarelo: 0xffff00,\n branco: 0xffffff,\n preto: 0x000000,\n };\n colorValue = colorMap[color.toLowerCase()] || 0x000000;\n }\n } else {\n colorValue = color;\n }\n\n if (this.backgroundRect) {\n this.backgroundRect.destroy();\n }\n\n this.backgroundRect = this.add.rectangle(\n this.game.config.width / 2,\n this.game.config.height / 2,\n this.game.config.width,\n this.game.config.height,\n colorValue,\n );\n this.backgroundRect.setDepth(-1000);\n }\n\n addColorToActiveGraphics() {\n const colorArray =\n this.activeGraphics === this.playerGraphics\n ? this.playerColors\n : this.solutionColors;\n\n if (!colorArray.includes(this.turtleState.penColour)) {\n colorArray.push(this.turtleState.penColour);\n }\n }\n\n _moveInstant(distance) {\n const lastX = this.turtleState.x;\n const lastY = this.turtleState.y;\n const angleRad = Phaser.Math.DegToRad(this.turtleState.angle);\n\n this.turtleState.x += Math.cos(angleRad) * distance;\n this.turtleState.y -= Math.sin(angleRad) * distance;\n\n if (this.turtleState.penDown && this.activeGraphics) {\n this.activeGraphics.lineStyle(4, this.turtleState.penColour, 1);\n this.activeGraphics.lineBetween(\n lastX,\n lastY,\n this.turtleState.x,\n this.turtleState.y,\n );\n this.addColorToActiveGraphics();\n }\n }\n\n _turnInstant(angle) {\n this.turtleState.angle += angle;\n this.turtleState.angle = this.turtleState.angle % 360;\n if (this.turtleState.angle < 0) {\n this.turtleState.angle += 360;\n }\n }\n\n highlightBlock(id) {\n if (this.workspace) this.workspace.highlightBlock(id);\n this.highlightPause = true;\n }\n\n move(distance) {\n this.historico.push({ tipo: \"move\", distancia: distance });\n\n return new Promise((resolve) => {\n const duration = this.calcDuration();\n const startX = this.turtleState.x;\n const startY = this.turtleState.y;\n const angleRad = Phaser.Math.DegToRad(this.turtleState.angle);\n const targetX = this.turtleState.x + Math.cos(angleRad) * distance;\n const targetY = this.turtleState.y - Math.sin(angleRad) * distance;\n\n this.turtleState.x = targetX;\n this.turtleState.y = targetY;\n\n this.turtleSprite.play(\"turtle_walk\");\n\n this.tweens.add({\n targets: this.turtleSprite,\n x: targetX,\n y: targetY,\n duration: duration,\n ease: \"Linear\",\n onComplete: () => {\n this.turtleSprite.stop();\n this.turtleSprite.setTexture(ASSETS.IMG.TURTLE1);\n\n if (this.turtleState.penDown && this.activeGraphics) {\n this.activeGraphics.lineStyle(4, this.turtleState.penColour, 1);\n this.activeGraphics.lineBetween(\n startX,\n startY,\n this.turtleState.x,\n this.turtleState.y,\n );\n this.addColorToActiveGraphics();\n }\n\n resolve();\n },\n });\n });\n }\n\n turn(angle) {\n this.historico.push({ tipo: \"turn\", angulo: angle });\n\n return new Promise((resolve) => {\n const duration = this.calcDuration();\n this._turnInstant(angle);\n\n const currentSpriteAngle = this.turtleSprite.angle;\n const targetSpriteAngle = -this.turtleState.angle;\n\n let angleDiff = targetSpriteAngle - currentSpriteAngle;\n\n while (angleDiff > 180) angleDiff -= 360;\n while (angleDiff < -180) angleDiff += 360;\n\n const finalAngle = currentSpriteAngle + angleDiff;\n\n this.tweens.add({\n targets: this.turtleSprite,\n angle: finalAngle,\n duration: duration,\n ease: \"Linear\",\n onComplete: () => {\n resolve();\n },\n });\n });\n }\n\n penDown(isDown) {\n this.historico.push({ tipo: \"penDown\", valor: isDown });\n this.turtleState.penDown = isDown;\n }\n\n penColour(color) {\n this.historico.push({ tipo: \"penColour\", cor: color });\n\n if (typeof color === \"string\") {\n if (color.startsWith(\"#\")) {\n this.turtleState.penColour = parseInt(color.substring(1), 16);\n } else {\n const colorMap = {\n vermelho: 0xff0000,\n azul: 0x0000ff,\n verde: 0x00ff00,\n amarelo: 0xffff00,\n branco: 0xffffff,\n preto: 0x000000,\n };\n this.turtleState.penColour = colorMap[color.toLowerCase()] || 0xffffff;\n }\n } else {\n this.turtleState.penColour = color;\n }\n\n this.addColorToActiveGraphics();\n }\n\n extractPointsFromGraphics(graphics) {\n const points = [];\n let i = 0;\n\n while (i < graphics.commandBuffer.length) {\n const cmd = graphics.commandBuffer[i];\n if (\n cmd === 6 &&\n i + 11 < graphics.commandBuffer.length &&\n graphics.commandBuffer[i + 1] === 4 &&\n graphics.commandBuffer[i + 5] === 5 &&\n graphics.commandBuffer[i + 8] === 4 &&\n graphics.commandBuffer[i + 11] === 9\n ) {\n const x1 = graphics.commandBuffer[i + 6];\n const y1 = graphics.commandBuffer[i + 7];\n const x2 = graphics.commandBuffer[i + 9];\n const y2 = graphics.commandBuffer[i + 10];\n\n points.push({ x: x1, y: y1 });\n points.push({ x: x2, y: y2 });\n i += 12;\n } else {\n i++;\n }\n }\n\n return points;\n }\n\n getDrawingSignature(graphics) {\n if (!graphics.commandBuffer || graphics.commandBuffer.length === 0) {\n return null;\n }\n\n const points = this.extractPointsFromGraphics(graphics);\n if (points.length === 0) {\n return null;\n }\n\n let sumX = 0,\n sumY = 0;\n let minX = Infinity,\n minY = Infinity;\n let maxX = -Infinity,\n maxY = -Infinity;\n\n points.forEach((p) => {\n sumX += p.x;\n sumY += p.y;\n minX = Math.min(minX, p.x);\n minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x);\n maxY = Math.max(maxY, p.y);\n });\n\n const pointCount = points.length;\n const centroid = { x: sumX / pointCount, y: sumY / pointCount };\n const normalizedCentroid = {\n x: centroid.x - minX,\n y: centroid.y - minY,\n };\n\n return {\n bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY },\n centroid: normalizedCentroid,\n };\n }\n\n compareColors(playerColors, solutionColors) {\n if (playerColors.length !== solutionColors.length) return false;\n\n const sortedPlayer = [...playerColors].sort();\n const sortedSolution = [...solutionColors].sort();\n\n return sortedSolution.every(\n (color, index) => sortedPlayer[index] === color,\n );\n }\n\n getTopologicalSignature(graphics) {\n if (!graphics.commandBuffer || graphics.commandBuffer.length === 0) {\n return null;\n }\n\n const points = this.extractPointsFromGraphics(graphics);\n\n if (points.length < 3) {\n return { curvatureSum: 0, pointCount: points.length };\n }\n\n let curvatureSum = 0;\n for (let j = 1; j < points.length - 1; j++) {\n const p0 = points[j - 1];\n const p1 = points[j];\n const p2 = points[j + 1];\n\n const v1 = { x: p1.x - p0.x, y: p1.y - p0.y };\n const v2 = { x: p2.x - p1.x, y: p2.y - p1.y };\n\n const crossProduct = v1.x * v2.y - v1.y * v2.x;\n curvatureSum += crossProduct;\n }\n\n return {\n curvatureSum: curvatureSum,\n pointCount: points.length,\n isClockwise: curvatureSum < 0,\n absoluteCurvature: Math.abs(curvatureSum),\n };\n }\n\n compareDrawings() {\n if (!this.compareColors(this.playerColors, this.solutionColors)) {\n return false;\n }\n\n const playerSig = this.getDrawingSignature(this.playerGraphics);\n const solutionSig = this.getDrawingSignature(this.validationGraphics);\n\n if (!playerSig || !solutionSig) {\n return false;\n }\n\n const pb = playerSig.bounds;\n const sb = solutionSig.bounds;\n\n const dimensionTolerance = Math.max(sb.width, sb.height, 1) * 0.05;\n if (\n Math.abs(pb.width - sb.width) > dimensionTolerance ||\n Math.abs(pb.height - sb.height) > dimensionTolerance\n ) {\n return false;\n }\n\n const posX_diff = Math.abs(pb.x - sb.x);\n const posY_diff = Math.abs(pb.y - sb.y);\n if (posX_diff > 1.5 || posY_diff > 1.5) {\n return false;\n }\n\n const centroidTolerance = 1.5;\n const centroidDeltaX = Math.abs(\n playerSig.centroid.x - solutionSig.centroid.x,\n );\n const centroidDeltaY = Math.abs(\n playerSig.centroid.y - solutionSig.centroid.y,\n );\n\n if (\n centroidDeltaX > centroidTolerance ||\n centroidDeltaY > centroidTolerance\n ) {\n return false;\n }\n\n const playerTopo = this.getTopologicalSignature(this.playerGraphics);\n const solutionTopo = this.getTopologicalSignature(this.validationGraphics);\n\n if (!playerTopo || !solutionTopo) {\n return false;\n }\n\n if (\n playerTopo.isClockwise !== solutionTopo.isClockwise &&\n Math.abs(playerTopo.absoluteCurvature) > 10\n ) {\n return false;\n }\n\n return true;\n }\n\n drawSolution() {\n const configFase = this.registry.get(\"configFase\");\n if (!configFase || !configFase.solutionCode) {\n return;\n }\n\n this.solutionColors = [];\n this.activeGraphics = this.solutionGraphics;\n this.resetTurtle();\n this.turtleState.penColour = 0xffffff;\n\n const move = this._moveInstant.bind(this);\n const turn = this._turnInstant.bind(this);\n const penDown = this.penDown.bind(this);\n const penColour = this.penColour.bind(this);\n\n try {\n const fn = new Function(\n \"move\",\n \"turn\",\n \"penDown\",\n \"penColour\",\n configFase.solutionCode,\n );\n fn(move, turn, penDown, penColour);\n } catch (e) {\n // Erro silencioso - desenho de solução não é crítico\n }\n }\n\n generateValidationDrawing() {\n const configFase = this.registry.get(\"configFase\");\n if (!configFase || !configFase.solutionCode) {\n return;\n }\n\n this.validationGraphics.clear();\n this.solutionColors = [];\n this.activeGraphics = this.validationGraphics;\n this.resetTurtle();\n this.turtleState.penColour = 0xffffff;\n\n const move = this._moveInstant.bind(this);\n const turn = this._turnInstant.bind(this);\n const penDown = this.penDown.bind(this);\n const penColour = this.penColour.bind(this);\n\n try {\n const fn = new Function(\n \"move\",\n \"turn\",\n \"penDown\",\n \"penColour\",\n configFase.solutionCode,\n );\n fn(move, turn, penDown, penColour);\n } catch (e) {\n // Erro silencioso - desenho de validação não é crítico\n }\n }\n\n onBeforeRun() {\n this.playerGraphics.clear();\n this.activeGraphics = this.playerGraphics;\n this.resetTurtle();\n this.playerColors = [];\n this.resultadoJogada = \"em_andamento\";\n this.solutionGraphics.clear();\n this.drawSolution();\n this.generateValidationDrawing();\n this.activeGraphics = this.playerGraphics;\n this.resetTurtle();\n }\n\n onReset() {\n this.playerGraphics.clear();\n this.solutionGraphics.clear();\n this.resetTurtle();\n this.activeGraphics = this.playerGraphics;\n this.drawSolution();\n this.playerColors = [];\n this.solutionColors = [];\n this.resultadoJogada = \"em_andamento\";\n }\n\n onSuccess() {\n const configFase = this.registry.get(\"configFase\");\n if (configFase && configFase.id === 10) return;\n this.playAudio(ASSETS.AUDIO.WIN);\n }\n\n onFailure() {\n this.playAudio(ASSETS.AUDIO.FAIL);\n }\n\n create() {\n this.createSpeedControl();\n\n this.anims.create({\n key: \"turtle_walk\",\n frames: [{ key: ASSETS.IMG.TURTLE1 }, { key: ASSETS.IMG.TURTLE2 }],\n frameRate: 8,\n repeat: -1,\n });\n\n this.solutionGraphics = this.add.graphics();\n this.playerGraphics = this.add.graphics();\n this.solutionGraphics.setAlpha(0.3);\n this.validationGraphics = this.add.graphics().setVisible(false);\n this.playerRT = this.add\n .renderTexture(0, 0, CONSTANTES.LARGURA, CONSTANTES.ALTURA)\n .setVisible(false);\n this.validationRT = this.add\n .renderTexture(0, 0, CONSTANTES.LARGURA, CONSTANTES.ALTURA)\n .setVisible(false);\n this.activeGraphics = this.playerGraphics;\n\n this.turtleSprite = this.add.sprite(\n CONSTANTES.LARGURA / 2,\n CONSTANTES.ALTURA / 2,\n ASSETS.IMG.TURTLE1,\n );\n this.turtleSprite.setOrigin(0.5, 0.5);\n this.turtleSprite.setAngle(0);\n this.turtleSprite.setDepth(1000);\n\n this.gameInterpreter = new GameInterpreter({ stepDelay: 500 });\n this.setExecutionSpeed(this.executionSpeed);\n this.drawSolution();\n\n this.setupStandardController(\n setupTurtleAPI,\n (history, config, gameConfig) =>\n validateSolution(history, config, gameConfig, this),\n );\n }\n}\n\nexport const createGame = (\n parentElement,\n configFaseAtual,\n customFailureHandler = null,\n idFaseAtual = null,\n gameConfig = null,\n) => {\n const config =\n idFaseAtual && gameConfig\n ? gameConfig.fases[idFaseAtual - 1]\n : configFaseAtual;\n\n return {\n type: Phaser.AUTO,\n width: CONSTANTES.LARGURA,\n height: CONSTANTES.ALTURA,\n backgroundColor: \"#171616\",\n antialias: true,\n roundPixels: true,\n pixelArt: false,\n parent: parentElement,\n scale: {\n mode: Phaser.Scale.FIT,\n autoCenter: Phaser.Scale.CENTER_BOTH,\n zoom: 5,\n },\n scene: [TurtleScene],\n callbacks: {\n preBoot: (game) => {\n game.registry.merge({\n configFase: config,\n gameConfig: gameConfig,\n customFailureHandler: customFailureHandler,\n stepDelay: 500,\n });\n },\n },\n };\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/hooks/interpreterSetup.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/hooks/useTurtleTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/games/turtle/validation/validators.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":83,"column":14,"nodeType":"Identifier","messageId":"unusedVar","endLine":83,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { BaseGameValidator } from \"../../../shared/BaseGameValidator\";\n\n/**\n * Validador específico do Turtle Game\n *\n * Diferente de outros jogos, o Turtle valida principalmente através de\n * comparação visual de desenhos, não apenas por sequência de ações.\n */\nclass TurtleValidator extends BaseGameValidator {\n /**\n * Valida a solução do aluno baseado no desenho produzido\n *\n * @param {Array} history - Histórico de ações (para debug/estatísticas)\n * @param {Object} config - Configuração da fase atual\n * @param {Object} gameConfig - Configuração global do jogo\n * @param {Object} sceneRef - Referência à cena Phaser (para acessar métodos de comparação)\n * @returns {Object} { success: boolean, reason?: string }\n */\n validatePhase(history, config, gameConfig, sceneRef) {\n // 1. Fail-Safe: Verificar se a configuração foi passada\n if (!config || Object.keys(config).length === 0) {\n return this.failure(\n gameConfig?.mensagens?.erroGeral ||\n \"Erro técnico: Fase não configurada.\",\n );\n }\n\n // 2. Fase 10 (Desafio Livre) - Sempre sucesso\n // Também aceita fases com requiredDrawingMatch = false\n if (config.id === 10 || config.requiredDrawingMatch === false) {\n return this.success();\n }\n\n // 3. Verificar se há referência à cena (necessária para comparação visual)\n if (!sceneRef) {\n return this.failure(\n gameConfig?.mensagens?.erroGeral || \"Erro técnico na validação.\",\n );\n }\n\n // 4. Fases 1-9: Validar desenho produzido\n return this._validateDrawing(sceneRef, config, gameConfig);\n }\n\n /**\n * Valida se o desenho do jogador corresponde à solução esperada\n * Método privado chamado apenas por validatePhase() para fases 1-9\n *\n * @param {Object} scene - Referência à cena Phaser\n * @param {Object} config - Configuração da fase\n * @param {Object} gameConfig - Configuração global\n * @returns {Object} { success: boolean, reason?: string }\n * @private\n */\n _validateDrawing(scene, config, gameConfig) {\n try {\n // 1. Verificar se o jogador desenhou algo\n const hasPlayerDrawing = this._hasPlayerDrawing(scene);\n\n if (!hasPlayerDrawing) {\n return this.failure(\n config?.mensagens?.semDesenho ||\n gameConfig?.mensagens?.semDesenho ||\n \"Você não desenhou nada! Certifique-se de usar os comandos move() com a caneta abaixada.\",\n );\n }\n\n // 2. Comparar desenhos usando método da scene (compareDrawings)\n // O método scene.compareDrawings() acessa validationGraphics e playerGraphics\n // que já foram preparados no onBeforeRun() através de generateValidationDrawing()\n const drawingsMatch = scene.compareDrawings();\n\n if (!drawingsMatch) {\n return this.failure(\n config?.mensagens?.desenhoNaoConfere ||\n gameConfig?.mensagens?.desenhoNaoConfere ||\n \"O desenho não está correto. Verifique a forma, posição e cores.\",\n );\n }\n\n // 3. Sucesso - desenho corresponde à solução!\n return this.success();\n } catch (error) {\n return this.failure(\n gameConfig?.mensagens?.erroGeral ||\n \"Erro inesperado durante a validação.\",\n );\n }\n }\n\n /**\n * Verifica se o jogador produziu algum desenho\n * Checa se o commandBuffer do playerGraphics possui comandos de desenho\n *\n * @param {Object} scene - Referência à cena Phaser\n * @returns {boolean} true se há desenho, false caso contrário\n * @private\n */\n _hasPlayerDrawing(scene) {\n return (\n scene.playerGraphics &&\n scene.playerGraphics.commandBuffer &&\n scene.playerGraphics.commandBuffer.length > 0\n );\n }\n\n // --- Métodos Herdados da Base ---\n // success() - retorna { success: true }\n // failure(reason) - retorna { success: false, reason }\n}\n\n// Singleton para reutilização\nconst validatorInstance = new TurtleValidator();\n\n/**\n * Função exportada para validação de soluções do Turtle Game\n *\n * @param {Array} history - Histórico de ações\n * @param {Object} config - Configuração da fase\n * @param {Object} gameConfig - Configuração global\n * @param {Object} sceneRef - Referência à cena (necessário para comparação visual)\n * @returns {Object} { success: boolean, reason?: string }\n */\nexport const validateSolution = (history, config, gameConfig, sceneRef) => {\n return validatorInstance.validatePhase(history, config, gameConfig, sceneRef);\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/hooks/useGameTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/hooks/useIsMobile.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/hooks/useTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/interpreters/ApiHelpers.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/interpreters/GameInterpreter.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/main.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'App' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"App"},"fix":{"range":[160,169],"text":""},"desc":"Remove unused variable 'App'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport \"./config/blocklyConfig.js\"; // Configuração global do Blockly (locale PT-BR)\nimport App from \"./App.jsx\";\n\nReactDOM.createRoot(document.getElementById(\"root\")).render( );\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/About/About.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowRight"},"fix":{"range":[9,20],"text":""},"desc":"Remove unused variable 'ArrowRight'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Users' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":22,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"Users"},"fix":{"range":[19,26],"text":""},"desc":"Remove unused variable 'Users'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Heart' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":29,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":34,"suggestions":[{"messageId":"removeVar","data":{"varName":"Heart"},"fix":{"range":[26,33],"text":""},"desc":"Remove unused variable 'Heart'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Lightbulb' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":36,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":45,"suggestions":[{"messageId":"removeVar","data":{"varName":"Lightbulb"},"fix":{"range":[33,44],"text":""},"desc":"Remove unused variable 'Lightbulb'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Globe' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":47,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":52,"suggestions":[{"messageId":"removeVar","data":{"varName":"Globe"},"fix":{"range":[44,51],"text":""},"desc":"Remove unused variable 'Globe'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Navbar' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Navbar"},"fix":{"range":[82,94],"text":""},"desc":"Remove unused variable 'Navbar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Footer' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Footer"},"fix":{"range":[128,140],"text":""},"desc":"Remove unused variable 'Footer'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ArrowRight, Users, Heart, Lightbulb, Globe } from \"lucide-react\";\nimport Navbar from \"../../components/Navbar\";\nimport Footer from \"../HomePage/Footer\";\n\nexport default function About() {\n return (\n <>\n {/* Navegação */}\n \n\n {/* Background Gradient */}\n
\n\n \n {/* Container principal */}\n \n
\n {/* Conteúdo textual */}\n
\n
\n Somos Educadores do Núcleo de Tecnologia do MTST\n \n\n
\n Somos parte do Núcleo de Tecnologia do MTST, e somos\n comprometidos com a democratização do ensino de programação e\n tecnologia. Acreditamos que a educação digital deve ser\n acessível, gratuita e transformadora para todas as pessoas.\n
\n\n
\n Nossa missão é empoderar estudantes e professores através de\n ferramentas pedagógicas abertas, baseadas em metodologias\n críticas e participativas. Trabalhamos para que a tecnologia\n seja um instrumento de inclusão social e desenvolvimento\n comunitário.\n
\n\n
\n O Decoda nasceu dessa visão: uma plataforma educacional que\n valoriza o pensamento computacional como ferramenta de\n transformação social, respeitando a diversidade de saberes e\n promovendo a autonomia pedagógica.\n
\n\n {/* Valores */}\n
\n
\n
\n
\n
\n Educação Popular\n \n
\n Valorizamos o diálogo, a participação coletiva e a\n construção colaborativa do conhecimento.\n
\n
\n
\n\n
\n
\n
\n
\n Compromisso Social\n \n
\n Lutamos por uma tecnologia inclusiva que reduza\n desigualdades e promova justiça social.\n
\n
\n
\n\n
\n
\n
\n
\n Inovação Pedagógica\n \n
\n Criamos métodos de ensino contextualizados, críticos e\n adaptados à realidade brasileira.\n
\n
\n
\n
\n\n {/* CTA */}\n
\n
\n\n {/* Grid de imagens representativas */}\n
\n {/* Card 1: Educação Popular */}\n
\n
\n
\n
\n
\n Educação Popular\n
\n
\n Construção coletiva do saber\n
\n
\n
\n
\n\n {/* Card 2: Inclusão Digital */}\n
\n
\n
\n
\n
\n Inclusão Digital\n
\n
\n Tecnologia para todos\n
\n
\n
\n
\n\n {/* Card 3: Pensamento Crítico */}\n
\n
\n
\n
\n
\n Pensamento Crítico\n
\n
\n Autonomia e reflexão\n
\n
\n
\n
\n\n {/* Card 4: Programação Visual */}\n
\n
\n
\n
\n
\n Programação Visual\n
\n
\n Aprender fazendo\n
\n
\n
\n
\n\n {/* Card 5: Transformação Social */}\n
\n
\n
\n
\n
\n Transformação Social\n
\n
\n Tecnologia para mudar\n
\n
\n
\n
\n
\n
\n
\n \n\n {/* Footer */}\n \n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Atividades/Atividades.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Navbar' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Navbar"},"fix":{"range":[105,117],"text":""},"desc":"Remove unused variable 'Navbar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Footer' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Footer"},"fix":{"range":[151,163],"text":""},"desc":"Remove unused variable 'Footer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ChevronLeft' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"ChevronLeft"},"fix":{"range":[419,431],"text":""},"desc":"Remove unused variable 'ChevronLeft'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ChevronRight' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":23,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":35,"suggestions":[{"messageId":"removeVar","data":{"varName":"ChevronRight"},"fix":{"range":[430,444],"text":""},"desc":"Remove unused variable 'ChevronRight'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useMemo } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport Navbar from \"../../components/Navbar\";\nimport Footer from \"../HomePage/Footer\";\nimport { gameRegistryUtils } from \"../../config/gameRegistry\";\nimport { difficultyLevels } from \"../../config/difficulty\";\nimport { gameCategory } from \"../../config/categories\";\nimport { gameType } from \"../../config/type\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { useAtividadesTour } from \"./hooks/useAtividadesTour\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\nimport { calculateProgress } from \"./utils/progress\";\n\nconst Atividades = () => {\n const navigate = useNavigate();\n\n // Inicializa o tour guiado\n useAtividadesTour();\n const [selectedCategory, setSelectedCategory] = useState(\"all\");\n const [selectedDifficulty, setSelectedDifficulty] = useState(\"all\");\n const [selectedType, setSelectedType] = useState(\"all\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [currentPage, setCurrentPage] = useState(1);\n const [itemsPerPage, setItemsPerPage] = useState(10);\n\n // Obter jogos filtrados\n const filteredGames = useMemo(() => {\n const games = gameRegistryUtils.getActiveGames();\n\n // Mapeamos os jogos injetando o progresso calculado ANTES de filtrar\n const gamesWithProgress = games.map((game) => ({\n ...game,\n progress: calculateProgress(game.gameId || game.id, game.fases),\n }));\n\n return gamesWithProgress.filter((game) => {\n const matchesCategory =\n selectedCategory === \"all\" || game.categoria === selectedCategory;\n const matchesDifficulty =\n selectedDifficulty === \"all\" || game.dificuldade === selectedDifficulty;\n const matchesType = selectedType === \"all\" || game.type === selectedType;\n\n const query = searchQuery.toLowerCase().trim();\n const matchesSearch =\n !query ||\n game.gameName.toLowerCase().includes(query) ||\n game.descricao.toLowerCase().includes(query) ||\n (game.conceitos &&\n game.conceitos.some((c) => c.toLowerCase().includes(query)));\n\n return (\n matchesCategory && matchesDifficulty && matchesType && matchesSearch\n );\n });\n }, [selectedCategory, selectedDifficulty, selectedType, searchQuery]);\n\n // Paginação\n const totalPages = Math.ceil(filteredGames.length / itemsPerPage);\n const startIndex = (currentPage - 1) * itemsPerPage;\n const endIndex = startIndex + itemsPerPage;\n const currentGames = filteredGames.slice(startIndex, endIndex);\n\n // Reset para página 1 quando filtros mudam\n React.useEffect(() => {\n setCurrentPage(1);\n }, [\n selectedCategory,\n selectedDifficulty,\n selectedType,\n searchQuery,\n itemsPerPage,\n ]);\n\n const getDifficultyColor = (difficulty) => {\n return difficultyLevels[difficulty]?.color || \"#6c757d\";\n };\n\n const getCategoryColor = (category) => {\n return gameCategory[category]?.color || \"#6c757d\";\n };\n\n const getTypeColor = (type) => {\n return gameType[type]?.color || \"#6c757d\";\n };\n\n const getNameFromType = (type) => {\n return gameType[type]?.name || type;\n };\n\n const handleGameSelect = (game) => {\n if (game.route) {\n navigate(game.route);\n } else {\n navigate(`/games/${game.gameId || game.id}`);\n }\n };\n\n const handleClearFilters = () => {\n setSelectedCategory(\"all\");\n setSelectedDifficulty(\"all\");\n setSelectedType(\"all\");\n setSearchQuery(\"\");\n };\n\n return (\n <>\n \n\n {/* Background Gradient */}\n
\n\n \n {/* Container principal */}\n \n {/* Header */}\n
\n
\n
\n Atividades e Jogos\n \n
\n Explore nossos jogos educativos para aprender programação.\n
\n
\n
\n\n {/* Filtros */}\n
\n
\n {/* Busca */}\n
\n
\n Buscar atividades\n \n
\n
setSearchQuery(e.target.value)}\n />\n
\n
\n
\n\n {/* Categoria */}\n
\n \n Categoria\n \n setSelectedCategory(e.target.value)}\n >\n Todas as categorias \n {Object.entries(gameCategory).map(([key, category]) => (\n \n {category.name}\n \n ))}\n \n
\n\n {/* Tipo */}\n
\n \n Tipo\n \n setSelectedType(e.target.value)}\n >\n Todos os tipos \n {Object.entries(gameType).map(([key, type]) => (\n \n {type.name}\n \n ))}\n \n
\n\n {/* Dificuldade */}\n
\n \n Dificuldade\n \n setSelectedDifficulty(e.target.value)}\n >\n Todas as dificuldades \n {Object.entries(difficultyLevels).map(([key, level]) => (\n \n {level.name}\n \n ))}\n \n
\n\n {/* Itens por página */}\n
\n \n Itens por página\n \n setItemsPerPage(Number(e.target.value))}\n >\n 10 atividades \n 20 atividades \n 30 atividades \n \n
\n
\n\n {/* Botão limpar filtros */}\n {(selectedCategory !== \"all\" ||\n selectedDifficulty !== \"all\" ||\n selectedType !== \"all\" ||\n searchQuery) && (\n
\n \n Limpar todos os filtros\n \n
\n )}\n
\n\n {/* Resultado e contador */}\n
\n
\n {searchQuery\n ? `Resultados para \"${searchQuery}\"`\n : \"Todas as Atividades\"}\n \n
\n {filteredGames.length}{\" \"}\n {filteredGames.length === 1 ? \"atividade\" : \"atividades\"}\n
\n
\n\n {/* Grid de Cards */}\n {currentGames.length > 0 ? (\n <>\n
\n {currentGames.map((game) => (\n
handleGameSelect(game)}\n className=\"relative bg-white/50 backdrop-blur-lg shadow-md hover:shadow-xl rounded-xl border border-gray-200 p-6 flex flex-col md:flex-row gap-6 cursor-pointer transition-all duration-300 hover:scale-[1.02] group data-[prd-ready=false]:opacity-50\"\n >\n {/* Ícone */}\n
\n\n {/* Conteúdo */}\n
\n
\n
\n
\n {game.gameName}\n \n\n {/* Badges */}\n
\n \n {gameCategory[game.categoria]?.name}\n \n
\n
\n\n
\n {game.descricao}\n
\n\n {/* Informações adicionais */}\n
\n \n ⏱️ {game.tempoEstimado}\n \n \n 🎯 {game.faixaEtaria}\n \n \n 📊 {game.fases?.length || 0} fases\n \n
\n\n {/* Conceitos */}\n {game.conceitos && game.conceitos.length > 0 && (\n
\n {game.conceitos.slice(0, 3).map((concept) => (\n \n {concept}\n \n ))}\n {game.conceitos.length > 3 && (\n \n +{game.conceitos.length - 3}\n \n )}\n
\n )}\n
\n\n {/* Badges de tipo e dificuldade */}\n
\n
\n \n {getNameFromType(game.type)}\n \n \n {difficultyLevels[game.dificuldade]?.name}\n \n
\n
\n
\n {/* BARRA DE PROGRESSO SUTIL NA BORDA INFERIOR */}\n {game.progress > 0 && (\n
\n )}\n
\n ))}\n
\n\n {/* Paginação */}\n {totalPages > 1 && (\n
\n
\n setCurrentPage((prev) => Math.max(1, prev - 1))\n }\n disabled={currentPage === 1}\n className=\"p-2 rounded-lg bg-white border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all\"\n >\n \n \n\n
\n {Array.from({ length: totalPages }, (_, i) => i + 1).map(\n (page) => (\n setCurrentPage(page)}\n className={`px-4 py-2 rounded-lg font-semibold transition-all ${\n currentPage === page\n ? \"bg-gradient-to-r from-red-600 via-pink-600 to-purple-600 text-white shadow-lg\"\n : \"bg-white border border-gray-200 text-gray-900 hover:bg-gray-50\"\n }`}\n >\n {page}\n \n ),\n )}\n
\n\n
\n setCurrentPage((prev) => Math.min(totalPages, prev + 1))\n }\n disabled={currentPage === totalPages}\n className=\"p-2 rounded-lg bg-white border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all\"\n >\n \n \n
\n )}\n >\n ) : (\n // Estado vazio\n
\n
🎮
\n
\n Nenhuma atividade encontrada\n \n
\n Não encontramos atividades que correspondam aos seus critérios.\n Que tal tentar uma busca diferente?\n
\n
\n Ver todas as atividades\n \n
\n )}\n
\n \n\n {/* Footer */}\n \n >\n );\n};\n\nexport default Atividades;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Atividades/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Atividades/hooks/useAtividadesTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Atividades/utils/progress.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Educadores/Educadores.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Navbar' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Navbar"},"fix":{"range":[7,19],"text":""},"desc":"Remove unused variable 'Navbar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Footer' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Footer"},"fix":{"range":[53,65],"text":""},"desc":"Remove unused variable 'Footer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Gamepad2' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"Gamepad2"},"fix":{"range":[149,158],"text":""},"desc":"Remove unused variable 'Gamepad2'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BookOpen' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":28,"suggestions":[{"messageId":"removeVar","data":{"varName":"BookOpen"},"fix":{"range":[157,167],"text":""},"desc":"Remove unused variable 'BookOpen'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Gift' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":30,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":34,"suggestions":[{"messageId":"removeVar","data":{"varName":"Gift"},"fix":{"range":[167,173],"text":""},"desc":"Remove unused variable 'Gift'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Users' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":36,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":41,"suggestions":[{"messageId":"removeVar","data":{"varName":"Users"},"fix":{"range":[173,180],"text":""},"desc":"Remove unused variable 'Users'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Navbar from \"../../components/Navbar\";\nimport Footer from \"../HomePage/Footer\";\nimport educadoresImg from \"./assets/educadores.png\";\nimport { Gamepad2, BookOpen, Gift, Users } from \"lucide-react\";\n\nconst stepsData = [\n {\n id: 1,\n title: \"Explore a Plataforma\",\n description:\n \"Navegue pelos jogos educativos e atividades. Teste você mesmo para entender a experiência dos alunos.\",\n },\n {\n id: 2,\n title: \"Acesse a Documentação\",\n description:\n \"Consulte nossos guias pedagógicos completos com planos de aula, estratégias de ensino e atividades desplugadas.\",\n },\n {\n id: 3,\n title: \"Prepare sua Aula\",\n description:\n \"Escolha as atividades adequadas ao nível da turma e planeje a sequência didática usando nossos recursos.\",\n },\n {\n id: 4,\n title: \"Ensine e Inspire\",\n description:\n \"Aplique em sala de aula, acompanhe o progresso dos alunos e ajuste conforme necessário. Sua turma vai adorar!\",\n },\n];\n\nexport default function Educadores() {\n return (\n <>\n {/* Navegação */}\n \n\n \n \n
\n {/* Título Principal */}\n
\n
\n Seja um Educador Popular de Tecnologia\n \n
\n Saiba como utilizar o Decoda para transformar suas aulas de\n programação em experiências envolventes e eficazes.\n
\n
\n\n
\n
\n
\n
\n
\n
\n
\n\n
\n
\n\n
\n {stepsData.map((step) => (\n
\n
\n {step.id}\n
\n
\n {step.title}\n \n
\n {step.description}\n
\n
\n ))}\n
\n
\n\n {/* CTA Button */}\n
\n\n {/* Seção de Benefícios Extras */}\n
\n
\n Por que escolher o Decoda?\n \n
\n
\n
\n
\n \n
\n
\n Jogos Educativos Envolventes\n \n
\n
\n Aprenda programação através de desafios divertidos e\n contextualizados que mantêm os alunos motivados.\n
\n
\n
\n
\n
\n \n
\n
\n Material Pedagógico Completo\n \n
\n
\n Guias detalhados, planos de aula prontos e atividades\n desplugadas para complementar suas aulas.\n
\n
\n
\n
\n
\n \n
\n
\n 100% Gratuito\n \n
\n
\n Sem cadastro, sem instalação, sem custos. Acesse direto do\n navegador e comece a ensinar hoje mesmo.\n
\n
\n
\n
\n
\n \n
\n
\n Aprendizagem Colaborativa\n \n
\n
\n Promova discussões em grupo e desenvolvimento do pensamento\n crítico através de desafios compartilhados.\n
\n
\n
\n
\n
\n
\n \n\n {/* Footer */}\n \n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Faq/Faq.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Navbar' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Navbar"},"fix":{"range":[7,19],"text":""},"desc":"Remove unused variable 'Navbar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Footer' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Footer"},"fix":{"range":[53,65],"text":""},"desc":"Remove unused variable 'Footer'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Navbar from \"../../components/Navbar\";\nimport Footer from \"../HomePage/Footer\";\n\nconst faqData = [\n {\n id: 1,\n question: \"O que é o Decoda?\",\n answer:\n \"O Decoda é uma plataforma educacional de programação visual desenvolvida especialmente para o ensino de lógica de programação. Utilizamos blocos visuais arrastar-e-soltar baseados no Google Blockly, eliminando a barreira da sintaxe complexa e focando no raciocínio lógico através de jogos educativos contextualizados.\",\n },\n {\n id: 2,\n question: \"Por que usar programação com blocos ao invés de código texto?\",\n answer:\n 'Os blocos visuais eliminam erros de sintaxe (não há como \"escrever errado\"), permitem foco total na lógica de programação, são intuitivos e acessíveis para todas as idades, e oferecem feedback visual imediato. Os blocos só encaixam onde fazem sentido logicamente, permitindo que estudantes se concentrem em resolver problemas ao invés de decorar sintaxe.',\n },\n {\n id: 3,\n question: \"A plataforma é gratuita?\",\n answer:\n \"Sim! O Decoda é 100% gratuito. Não requer cadastro, instalação ou pagamento. Todos as atividades, recursos pedagógicos e documentação estão disponíveis gratuitamente para educadores e alunos. Acreditamos que a educação em programação deve ser acessível a todos.\",\n },\n {\n id: 4,\n question: \"O que é Computação Desplugada?\",\n answer:\n \"É uma metodologia que ensina conceitos de programação e pensamento computacional sem usar computadores, através de atividades práticas, jogos e brincadeiras. Nossa documentação oferece diversas atividades desplugadas que educadores podem aplicar antes de usar a plataforma digital, facilitando o entendimento dos conceitos e reduzindo a ansiedade tecnológica.\",\n },\n {\n id: 5,\n question: \"Quais conceitos de programação são ensinados?\",\n answer:\n \"Baseamos nosso ensino nos 4 fundamentos da programação e nos pilares do pensamento computacional:\",\n list: [\n \"Sequências: Ordem lógica de comandos\",\n \"Condicionais: Decisões SE/ENTÃO\",\n \"Repetição: Estruturas de repetição\",\n \"Funções: Agrupamento e reutilização\",\n \"Variáveis: Armazenamento de dados\",\n \"Eventos: Interações do usuário\",\n ],\n },\n {\n id: 6,\n question: \"Preciso instalar algum programa?\",\n answer:\n \"Não! O Decoda é uma plataforma web que funciona diretamente no navegador. Basta acessar o site e começar a programar. Não requer instalação, downloads ou configurações complexas. Funciona em computadores, tablets e smartphones com qualquer navegador moderno.\",\n },\n {\n id: 7,\n question: \"A plataforma oferece recursos para educadores?\",\n answer: \"Sim! Oferecemos documentação completa para educadores com:\",\n list: [\n \"Guias pedagógicos detalhados\",\n \"Estratégias de ensino para sala de aula\",\n \"Planos de aula prontos\",\n \"Atividades de computação desplugada\",\n \"Ferramentas de avaliação de aprendizado\",\n \"Dicas de gestão e organização da sala\",\n \"Resolução de problemas comuns\",\n ],\n },\n {\n id: 8,\n question: \"Meu progresso é salvo automaticamente?\",\n answer:\n \"Sim! O Decoda salva automaticamente seu progresso no navegador. Você pode fechar a aba e voltar depois que seu trabalho estará preservado. Isso funciona sem necessidade de cadastro ou login, mantendo a privacidade e simplicidade da plataforma.\",\n },\n {\n id: 9,\n question: \"Posso usar o Decoda em sala de aula?\",\n answer:\n \"Absolutamente! O Decoda foi desenvolvido especificamente para uso educacional em sala de aula. A plataforma promove aprendizagem colaborativa, discussão coletiva de estratégias e desenvolvimento do pensamento crítico. Não requer cadastro de alunos, facilitando a adoção em ambientes escolares.\",\n },\n {\n id: 10,\n question: \"A plataforma funciona em dispositivos móveis?\",\n answer:\n \"Sim! O Decoda possui design responsivo e funciona em tablets e smartphones. A interface se adapta automaticamente ao tamanho da tela, permitindo programação por toque em dispositivos móveis. Recomendamos tablets para uma experiência mais confortável, mas smartphones também são suportados.\",\n },\n];\n\nexport default function Faq() {\n return (\n <>\n {/* Navegação */}\n \n\n \n {/* Container principal */}\n \n
\n
\n Perguntas Frequentes\n \n\n
\n {faqData.map((faq, index) => (\n \n
\n
\n \n {faq.question}\n \n \n \n \n \n \n \n \n \n \n
\n \n {faq.answer}\n
\n {faq.list && (\n \n {faq.list.map((item, idx) => (\n \n ))}\n \n )}\n \n
\n ))}\n \n
\n
\n \n\n {/* Footer */}\n \n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/About.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[0,40],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SectionTitle' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"SectionTitle"},"fix":{"range":[93,111],"text":""},"desc":"Remove unused variable 'SectionTitle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Link } from \"react-router-dom\";\nimport GroupImage from \"./assets/group.png\";\nimport SectionTitle from \"./SectionTitle\";\nconst About = () => {\n return (\n \n
\n
\n
\n
\n
\n
\n
\n DECODA é a plataforma do\n NT-MTST que facilita o processo de educação popular em programação\n \n
\n Voltada tanto para alunos, quanto para professores com interesse em\n aprender e ensinar programação. Inspirada na pedagogia freiriana,\n tem como origem ser material de apoio as aulas correntes do Núcleo\n de Tecnologia do MTST. Desde 2020, o Núcleo de Tecnologia do\n MTST promove mutirões e formações nas periferias, ensinando\n programação, inclusão digital e acesso a direitos.\n
\n
\n
\n
\n
\n );\n};\n\nexport default About;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/CtaPlayground.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'inComment' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":78,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":78,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"inComment"},"fix":{"range":[1836,1858],"text":""},"desc":"Remove unused variable 'inComment'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useEffect, useRef } from \"react\";\nimport fatorialSvg from \"./assets/fatorial.svg\";\n\nconst CtaPlayground = () => {\n const [activeTab, setActiveTab] = useState(\"blocos\");\n const [displayedCode, setDisplayedCode] = useState(\"\");\n const [isTyping, setIsTyping] = useState(true);\n const typingTimeoutRef = useRef(null);\n const terminalRef = useRef(null);\n\n // Função para aplicar syntax highlighting\n const highlightCode = (code, language) => {\n if (language === \"console\") {\n // Highlight para console\n return code.split(\"\\n\").map((line, i) => {\n if (line.startsWith(\">\")) {\n return (\n \n > \n {line.substring(1)} \n
\n );\n }\n return (\n \n {line}\n
\n );\n });\n }\n\n // Palavras-chave por linguagem\n const keywords = {\n javascript: [\n \"var\",\n \"for\",\n \"count\",\n \"if\",\n \"else\",\n \"function\",\n \"return\",\n \"const\",\n \"let\",\n \"typeof\",\n \"window\",\n \"alert\",\n ],\n python: [\n \"from\",\n \"import\",\n \"for\",\n \"in\",\n \"range\",\n \"int\",\n \"if\",\n \"else\",\n \"def\",\n \"return\",\n \"print\",\n \"isinstance\",\n \"str\",\n ],\n };\n\n const types = {\n javascript: [\"Number\"],\n python: [\"Number\", \"numbers\"],\n };\n\n const currentKeywords = keywords[language] || [];\n const currentTypes = types[language] || [];\n\n // Tokenizar o código\n const tokens = [];\n let currentToken = \"\";\n let inString = false;\n let stringChar = \"\";\n let inComment = false;\n\n for (let i = 0; i < code.length; i++) {\n const char = code[i];\n const nextChar = code[i + 1];\n\n // Detectar início/fim de string\n if ((char === '\"' || char === \"'\") && code[i - 1] !== \"\\\\\") {\n if (!inString) {\n if (currentToken) {\n tokens.push({ type: \"text\", value: currentToken });\n currentToken = \"\";\n }\n inString = true;\n stringChar = char;\n currentToken = char;\n } else if (char === stringChar) {\n currentToken += char;\n tokens.push({ type: \"string\", value: currentToken });\n currentToken = \"\";\n inString = false;\n stringChar = \"\";\n } else {\n currentToken += char;\n }\n continue;\n }\n\n if (inString) {\n currentToken += char;\n continue;\n }\n\n // Detectar comentários\n if (char === \"/\" && nextChar === \"/\" && language === \"javascript\") {\n if (currentToken) {\n tokens.push({ type: \"text\", value: currentToken });\n currentToken = \"\";\n }\n // Pegar resto da linha\n const endOfLine = code.indexOf(\"\\n\", i);\n const comment = code.substring(\n i,\n endOfLine !== -1 ? endOfLine : code.length,\n );\n tokens.push({ type: \"comment\", value: comment });\n i = endOfLine !== -1 ? endOfLine - 1 : code.length;\n continue;\n }\n\n if (char === \"#\" && language === \"python\") {\n if (currentToken) {\n tokens.push({ type: \"text\", value: currentToken });\n currentToken = \"\";\n }\n const endOfLine = code.indexOf(\"\\n\", i);\n const comment = code.substring(\n i,\n endOfLine !== -1 ? endOfLine : code.length,\n );\n tokens.push({ type: \"comment\", value: comment });\n i = endOfLine !== -1 ? endOfLine - 1 : code.length;\n continue;\n }\n\n // Separadores\n if (/\\s|[(){}.,;]/.test(char) || char === \"[\" || char === \"]\") {\n if (currentToken) {\n tokens.push({ type: \"text\", value: currentToken });\n currentToken = \"\";\n }\n tokens.push({ type: \"separator\", value: char });\n continue;\n }\n\n // Operadores\n if (/[+\\-*/<>=!:]/.test(char)) {\n if (currentToken) {\n tokens.push({ type: \"text\", value: currentToken });\n currentToken = \"\";\n }\n tokens.push({ type: \"operator\", value: char });\n continue;\n }\n\n currentToken += char;\n }\n\n if (currentToken) {\n if (inString) {\n tokens.push({ type: \"string\", value: currentToken });\n } else {\n tokens.push({ type: \"text\", value: currentToken });\n }\n }\n\n // Renderizar tokens com cores\n return tokens.map((token, i) => {\n if (token.type === \"string\") {\n return (\n \n {token.value}\n \n );\n }\n if (token.type === \"comment\") {\n return (\n \n {token.value}\n \n );\n }\n if (token.type === \"operator\") {\n return (\n \n {token.value}\n \n );\n }\n if (token.type === \"separator\") {\n return (\n \n {token.value}\n \n );\n }\n if (token.type === \"text\") {\n // Verificar se é palavra-chave\n if (currentKeywords.includes(token.value)) {\n return (\n \n {token.value}\n \n );\n }\n // Verificar se é tipo\n if (currentTypes.includes(token.value)) {\n return (\n \n {token.value}\n \n );\n }\n // Verificar se é número\n if (/^\\d+$/.test(token.value)) {\n return (\n \n {token.value}\n \n );\n }\n // Verificar se é booleano ou null\n if (\n [\n \"True\",\n \"False\",\n \"None\",\n \"null\",\n \"undefined\",\n \"true\",\n \"false\",\n ].includes(token.value)\n ) {\n return (\n \n {token.value}\n \n );\n }\n return (\n \n {token.value}\n \n );\n }\n return {token.value} ;\n });\n };\n\n // Código do exemplo de fatorial\n const codes = {\n javascript: `var numero, fatorial, i;\n\nnumero = 5;\nfatorial = 1;\ni = 1;\nfor (var count = 0; count < numero; count++) {\n fatorial = fatorial * i;\n i = (typeof i === 'number' ? i : 0) + 1;\n}\nwindow.alert([numero,'! = ',fatorial].join(''));`,\n\n python: `from numbers import Number\n\nnumero = None\nfatorial = None\ni = None\n\nnumero = 5\nfatorial = 1\ni = 1\nfor count in range(int(numero)):\n fatorial = fatorial * i\n i = (i if isinstance(i, Number) else 0) + 1\nprint(''.join([str(x) for x in [numero, '! = ', fatorial]]))`,\n\n console: `> Executando programa...\n> \n> Calculando 5!\n> 5! = 120\n> \n> Programa finalizado com sucesso.`,\n };\n\n // Efeito de digitação (typing effect) - apenas para abas de código\n useEffect(() => {\n if (activeTab === \"blocos\") {\n setIsTyping(false);\n setDisplayedCode(\"\");\n return;\n }\n\n setDisplayedCode(\"\");\n setIsTyping(true);\n\n const currentCode = codes[activeTab];\n let currentIndex = 0;\n\n const typeCharacter = () => {\n if (currentIndex < currentCode.length) {\n setDisplayedCode(currentCode.substring(0, currentIndex + 1));\n currentIndex++;\n\n // Auto-scroll para acompanhar o texto\n if (terminalRef.current) {\n terminalRef.current.scrollTop = terminalRef.current.scrollHeight;\n }\n\n // Velocidade variável para simular digitação humana\n const delay = Math.random() * 30 + 10; // 10-40ms entre caracteres\n typingTimeoutRef.current = setTimeout(typeCharacter, delay);\n } else {\n setIsTyping(false);\n }\n };\n\n // Pequeno delay antes de começar a digitar\n const startDelay = setTimeout(() => {\n typeCharacter();\n }, 300);\n\n // Cleanup\n return () => {\n clearTimeout(startDelay);\n if (typingTimeoutRef.current) {\n clearTimeout(typingTimeoutRef.current);\n }\n };\n }, [activeTab]);\n\n return (\n \n {/* Keyframe animation for cursor blink */}\n \n\n {/* Background com gradiente igual ao Hero */}\n
\n\n {/* Container principal */}\n \n
\n {/* Terminal de Código (Esquerda) */}\n
\n
\n
\n {/* Header do Terminal */}\n
\n
\n\n {/* Tabs */}\n
\n setActiveTab(\"blocos\")}\n className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${\n activeTab === \"blocos\"\n ? \"bg-blue-600 text-white\"\n : \"text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400\"\n }`}\n >\n Blocos\n \n setActiveTab(\"javascript\")}\n className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${\n activeTab === \"javascript\"\n ? \"bg-blue-600 text-white\"\n : \"text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400\"\n }`}\n >\n JavaScript\n \n setActiveTab(\"python\")}\n className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${\n activeTab === \"python\"\n ? \"bg-blue-600 text-white\"\n : \"text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400\"\n }`}\n >\n Python\n \n setActiveTab(\"console\")}\n className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${\n activeTab === \"console\"\n ? \"bg-blue-600 text-white\"\n : \"text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400\"\n }`}\n >\n Console\n \n
\n
\n\n {/* Conteúdo do Terminal */}\n
\n {activeTab === \"blocos\" ? (\n
\n
\n
\n ) : (\n
\n \n {highlightCode(displayedCode, activeTab)}\n {isTyping && (\n \n )}\n \n \n )}\n
\n
\n
\n
\n
\n
\n \n );\n};\n\nexport default CtaPlayground;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/CtaVideoPreview.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport atividadesVideo from \"./assets/atividades.mp4\";\n\nconst CtaVideoPreview = () => {\n return (\n \n {/* Background com gradiente igual ao CtaPlayground */}\n
\n\n {/* Container principal */}\n \n
\n {/* Terminal de Vídeo */}\n
\n
\n
\n {/* Header do Terminal */}\n
\n\n {/* Conteúdo do Vídeo */}\n
\n \n \n Seu navegador não suporta a reprodução de vídeos.\n \n
\n
\n
\n
\n
\n
\n \n );\n};\n\nexport default CtaVideoPreview;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/Features.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Check' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":15,"suggestions":[{"messageId":"removeVar","data":{"varName":"Check"},"fix":{"range":[9,15],"text":""},"desc":"Remove unused variable 'Check'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Sparkles' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"Sparkles"},"fix":{"range":[14,24],"text":""},"desc":"Remove unused variable 'Sparkles'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":27,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":37,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowRight"},"fix":{"range":[24,36],"text":""},"desc":"Remove unused variable 'ArrowRight'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Check, Sparkles, ArrowRight } from \"lucide-react\";\n\nexport default function Features() {\n return (\n \n {/* Background com gradiente escuro */}\n
\n\n {/* Container principal */}\n \n
\n {/* Header da seção */}\n
\n
\n Tudo que você precisa para ensinar e aprender programação\n \n
\n Nossa plataforma oferece uma abordagem completa para educadores e\n alunos, combinando programação visual (usando blocos) e jogos\n educativos contextualizados.\n
\n
\n\n {/* Container das features */}\n
\n {/* SEÇÃO 1: Para Alunos */}\n
\n
\n
\n
\n Para Alunos\n \n\n
\n \n \n Programação visual com blocos intuitivos\n \n \n \n Jogos educativos contextualizados\n \n \n \n Feedback imediato e visual\n \n \n \n Sem erros de sintaxe ou barreiras técnicas\n \n \n \n Aprendizado por tentativa e experimentação\n \n \n \n Desenvolvimento da criatividade e autonomia\n \n \n \n Interface amigável e responsiva\n \n \n \n Progressão natural de dificuldade\n \n \n \n Facilidade de uso por meio de Blocos\n \n \n
\n
\n\n {/* SEÇÃO 2: Para Educadores */}\n
\n
\n
\n
\n Para Educadores\n \n\n
\n \n \n Guias pedagógicos completos e práticos\n \n \n \n Estratégias de ensino para sala de aula\n \n \n \n Atividades de computação desplugada\n \n \n \n Planos de aula e preparação facilitada\n \n \n \n Ferramentas de avaliação de aprendizado\n \n \n \n Gestão e organização da sala de aula\n \n \n \n Resolução de problemas e desafios comuns\n \n \n \n Desenvolvimento do pensamento crítico\n \n \n \n Aprendizagem colaborativa e inclusiva\n \n \n
\n
\n
\n\n {/* Divider */}\n
\n\n {/* FAQ Call to Action */}\n
\n
\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/Footer.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[0,40],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Github' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"Github"},"fix":{"range":[50,57],"text":""},"desc":"Remove unused variable 'Github'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BookOpen' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":18,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":26,"suggestions":[{"messageId":"removeVar","data":{"varName":"BookOpen"},"fix":{"range":[56,66],"text":""},"desc":"Remove unused variable 'BookOpen'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Mail' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":28,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":32,"suggestions":[{"messageId":"removeVar","data":{"varName":"Mail"},"fix":{"range":[66,72],"text":""},"desc":"Remove unused variable 'Mail'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Heart' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":34,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":39,"suggestions":[{"messageId":"removeVar","data":{"varName":"Heart"},"fix":{"range":[72,79],"text":""},"desc":"Remove unused variable 'Heart'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Link } from \"react-router-dom\";\nimport { Github, BookOpen, Mail, Heart } from \"lucide-react\";\nimport logo from \"../../assets/logo_decoda.svg\";\n\nconst Footer = () => {\n const currentYear = new Date().getFullYear();\n\n return (\n \n );\n};\n\nexport default Footer;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/ForWhom.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'SectionTitle' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"SectionTitle"},"fix":{"range":[183,201],"text":""},"desc":"Remove unused variable 'SectionTitle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import FirstPersonImage from \"./assets/first-person.png\";\nimport SecondPersonImage from \"./assets/second-person.png\";\nimport ThirdPersonImage from \"./assets/third-person.png\";\nimport SectionTitle from \"./SectionTitle\";\n\nconst ForWhom = () => {\n return (\n \n
\n
\n
\n
\n
\n Estudantes iniciantes\n \n
\n Aprender a lógica da programação da base com programação com códigos\n
\n
\n
\n
\n
\n Estudantes avançados\n \n
\n Conseguem prototipar seus primeiros apps já com código.\n
\n
\n
\n
\n
Educadores \n
\n Materiais para desenvolver suas aulas de programação\n
\n
\n
\n
\n );\n};\n\nexport default ForWhom;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/Hero.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[0,40],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowRight"},"fix":{"range":[50,61],"text":""},"desc":"Remove unused variable 'ArrowRight'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Gamepad2' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":22,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":30,"suggestions":[{"messageId":"removeVar","data":{"varName":"Gamepad2"},"fix":{"range":[60,70],"text":""},"desc":"Remove unused variable 'Gamepad2'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Trophy' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":32,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":38,"suggestions":[{"messageId":"removeVar","data":{"varName":"Trophy"},"fix":{"range":[70,78],"text":""},"desc":"Remove unused variable 'Trophy'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'scrollToFeatures' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":6,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"scrollToFeatures"},"fix":{"range":[178,408],"text":""},"desc":"Remove unused variable 'scrollToFeatures'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Link } from \"react-router-dom\";\nimport { ArrowRight, Gamepad2, Trophy } from \"lucide-react\";\nimport heroImage from \"./assets/puzzled-image.png\";\n\nconst Hero = () => {\n const scrollToFeatures = (e) => {\n e.preventDefault();\n const featuresSection = document.getElementById(\"features-section\");\n if (featuresSection) {\n featuresSection.scrollIntoView({ behavior: \"smooth\" });\n }\n };\n\n return (\n \n {/* Background Gradient */}\n
{\" \"}\n {/* Container Principal */}\n \n {/* Coluna Esquerda - Texto */}\n
\n {/* Título Principal */}\n
\n Programe com desafios{\" \"}\n \n
\n em contexto\n \n\n {/* Subtítulo */}\n
\n Descubra o mundo da programação através de jogos educativos e\n interativos. \n Desenvolva habilidades essenciais enquanto se diverte!\n
\n\n {/* Botões de Ação */}\n
\n \n \n Comece as atividades\n \n \n
\n
\n\n {/* Coluna Direita - Imagem Hero (oculta no mobile) */}\n
\n
\n
\n
\n
\n
\n \n );\n};\n\nexport default Hero;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/HomePage.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Navbar' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Navbar"},"fix":{"range":[7,19],"text":""},"desc":"Remove unused variable 'Navbar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Hero' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":12,"suggestions":[{"messageId":"removeVar","data":{"varName":"Hero"},"fix":{"range":[53,63],"text":""},"desc":"Remove unused variable 'Hero'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Features' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"Features"},"fix":{"range":[80,94],"text":""},"desc":"Remove unused variable 'Features'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CtaPlayground' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"CtaPlayground"},"fix":{"range":[115,134],"text":""},"desc":"Remove unused variable 'CtaPlayground'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Footer' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Footer"},"fix":{"range":[160,172],"text":""},"desc":"Remove unused variable 'Footer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'About' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"About"},"fix":{"range":[191,202],"text":""},"desc":"Remove unused variable 'About'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ForWhom' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":15,"suggestions":[{"messageId":"removeVar","data":{"varName":"ForWhom"},"fix":{"range":[220,233],"text":""},"desc":"Remove unused variable 'ForWhom'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'StudentsMaterials' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"StudentsMaterials"},"fix":{"range":[253,276],"text":""},"desc":"Remove unused variable 'StudentsMaterials'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'TeachersMaterials' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"TeachersMaterials"},"fix":{"range":[306,329],"text":""},"desc":"Remove unused variable 'TeachersMaterials'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import Navbar from \"../../components/Navbar\";\nimport Hero from \"./Hero\";\nimport Features from \"./Features\";\nimport CtaPlayground from \"./CtaPlayground\";\nimport Footer from \"./Footer\";\nimport About from \"./About\";\nimport ForWhom from \"./ForWhom\";\nimport StudentsMaterials from \"./StudentsMaterials\";\nimport TeachersMaterials from \"./TeachersMaterials\";\n\nconst HomePage = () => {\n return (\n \n {/* Navegação */}\n
\n\n {/* Hero Section */}\n
\n\n {/* Quem Somos */}\n
\n\n {/* Para quem */}\n
\n\n {/* Materiais para estudantes */}\n
\n\n {/* Materiais para educadores */}\n
\n\n
\n
\n );\n};\n\nexport default HomePage;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/SectionTitle.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/StudentsMaterials.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowRight"},"fix":{"range":[0,42],"text":""},"desc":"Remove unused variable 'ArrowRight'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[43,83],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CtaPlayground' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"CtaPlayground"},"fix":{"range":[91,110],"text":""},"desc":"Remove unused variable 'CtaPlayground'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CtaVideoPreview' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":23,"suggestions":[{"messageId":"removeVar","data":{"varName":"CtaVideoPreview"},"fix":{"range":[136,157],"text":""},"desc":"Remove unused variable 'CtaVideoPreview'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SectionTitle' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"SectionTitle"},"fix":{"range":[185,203],"text":""},"desc":"Remove unused variable 'SectionTitle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ArrowRight } from \"lucide-react\";\nimport { Link } from \"react-router-dom\";\nimport CtaPlayground from \"./CtaPlayground\";\nimport CtaVideoPreview from \"./CtaVideoPreview\";\nimport SectionTitle from \"./SectionTitle\";\n\nconst StudentsMaterials = () => {\n return (\n \n
\n
PARA ESTUDANTES \n\n
\n
\n
\n Está iniciando em programação e quer entender melhor?\n \n
\n Acesse nossas atividades e dê os primeiros passos no mundo da\n programação! Começe a programar de forma divertida e interativa com\n nossos materiais feitos especialmente para iniciantes.\n
\n
\n
\n
\n Acesse nossas Atividades\n \n \n \n
\n
\n
\n \n
\n
\n
\n
\n \n
\n
\n
\n Já tem a base de programação e busca algo mais avançado?\n \n
\n Acesse nosso Laboratório e comece a criar seus próprios programas\n usando blocos visuais. Converta para JavaScript ou Python e aprenda\n programação de verdade enquanto se diverte.\n
\n
\n
\n
\n Acesse nosso Laboratório\n \n \n \n
\n
\n
\n
\n );\n};\n\nexport default StudentsMaterials;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/HomePage/TeachersMaterials.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ArrowRight"},"fix":{"range":[0,42],"text":""},"desc":"Remove unused variable 'ArrowRight'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Link' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Link"},"fix":{"range":[43,83],"text":""},"desc":"Remove unused variable 'Link'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CtaPlayground' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"CtaPlayground"},"fix":{"range":[91,110],"text":""},"desc":"Remove unused variable 'CtaPlayground'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'SectionTitle' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"SectionTitle"},"fix":{"range":[136,154],"text":""},"desc":"Remove unused variable 'SectionTitle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ArrowRight } from \"lucide-react\";\nimport { Link } from \"react-router-dom\";\nimport CtaPlayground from \"./CtaPlayground\";\nimport SectionTitle from \"./SectionTitle\";\n\nconst TeachersMaterials = () => {\n return (\n \n
\n
\n PARA EDUCADORES\n \n\n
\n
\n
\n Quem entender como você pode usar nossa plataforma para ensinar?\n \n
\n Acesse nosso material de documentação e comece a usar o Decoda para\n potencializar suas aulas e engajar seus alunos com atividades\n interativas.\n
\n
\n
\n
\n Acesse o material para Educadores\n \n \n \n
\n
\n
\n
\n );\n};\n\nexport default TeachersMaterials;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/LabPython/LabPython.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'GameNavBar' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameNavBar"},"fix":{"range":[7,23],"text":""},"desc":"Remove unused variable 'GameNavBar'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import GameNavBar from \"../../components/game/GameNavBar\";\n\nexport default function LabPython() {\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/Playground.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PanelGroup' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"PanelGroup"},"fix":{"range":[63,74],"text":""},"desc":"Remove unused variable 'PanelGroup'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Panel' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":22,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"Panel"},"fix":{"range":[73,80],"text":""},"desc":"Remove unused variable 'Panel'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Moon' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Moon"},"fix":{"range":[123,128],"text":""},"desc":"Remove unused variable 'Moon'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Sun' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"Sun"},"fix":{"range":[127,132],"text":""},"desc":"Remove unused variable 'Sun'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'HelpCircle' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":21,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":31,"suggestions":[{"messageId":"removeVar","data":{"varName":"HelpCircle"},"fix":{"range":[132,144],"text":""},"desc":"Remove unused variable 'HelpCircle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'GameNavBar' is defined but never used. Allowed unused vars must match /^_/u.","line":6,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameNavBar"},"fix":{"range":[241,257],"text":""},"desc":"Remove unused variable 'GameNavBar'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ResizeHandle' is defined but never used. Allowed unused vars must match /^_/u.","line":7,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"ResizeHandle"},"fix":{"range":[300,318],"text":""},"desc":"Remove unused variable 'ResizeHandle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PlaygroundEditor' is defined but never used. Allowed unused vars must match /^_/u.","line":8,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"PlaygroundEditor"},"fix":{"range":[363,385],"text":""},"desc":"Remove unused variable 'PlaygroundEditor'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PlaygroundViewer' is defined but never used. Allowed unused vars must match /^_/u.","line":9,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"PlaygroundViewer"},"fix":{"range":[425,447],"text":""},"desc":"Remove unused variable 'PlaygroundViewer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PlaygroundModal' is defined but never used. Allowed unused vars must match /^_/u.","line":10,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":23,"suggestions":[{"messageId":"removeVar","data":{"varName":"PlaygroundModal"},"fix":{"range":[487,508],"text":""},"desc":"Remove unused variable 'PlaygroundModal'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":11,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState, useCallback } from \"react\";\nimport { PanelGroup, Panel } from \"react-resizable-panels\";\nimport { Moon, Sun, HelpCircle } from \"lucide-react\";\nimport * as Blockly from \"blockly/core\";\nimport \"blockly/blocks\";\nimport GameNavBar from \"../../components/game/GameNavBar\";\nimport ResizeHandle from \"../../components/game/ResizeHandle\";\nimport PlaygroundEditor from \"./components/PlaygroundEditor\";\nimport PlaygroundViewer from \"./components/PlaygroundViewer\";\nimport PlaygroundModal from \"./components/PlaygroundModal\";\nimport { useCodeGeneration } from \"./hooks/useCodeGeneration\";\nimport { useCodeExecution } from \"./hooks/useCodeExecution\";\nimport { usePlaygroundTour } from \"./hooks/usePlaygroundTour\";\nimport { useIsMobile } from \"../../hooks/useIsMobile\";\nimport \"shepherd.js/dist/css/shepherd.css\";\nimport \"../../styles/shepherd-theme.css\";\nimport \"./Playground.css\";\n\nconst Playground = () => {\n const [workspace, setWorkspace] = useState(null);\n const [workspaceVersion, setWorkspaceVersion] = useState(0);\n const [theme, setTheme] = useState(\"light\");\n const [modal, setModal] = useState({\n isOpen: false,\n type: \"success\",\n title: \"\",\n message: \"\",\n details: \"\",\n });\n const isMobile = useIsMobile();\n const generatedCode = useCodeGeneration(workspace, workspaceVersion);\n const { output, isRunning, runCode, clearOutput } = useCodeExecution();\n const { startTour } = usePlaygroundTour();\n\n const blockCount = generatedCode.javascript\n ? generatedCode.javascript.split(\"\\n\").filter((line) => line.trim()).length\n : 0;\n\n const handleWorkspaceChange = useCallback((newWorkspace) => {\n setWorkspace(newWorkspace);\n setWorkspaceVersion((v) => v + 1);\n }, []);\n\n const handleRunCode = useCallback(() => {\n if (generatedCode.javascript) {\n runCode(generatedCode.javascript);\n }\n }, [generatedCode.javascript, runCode]);\n\n const toggleTheme = useCallback(() => {\n setTheme((prev) => (prev === \"light\" ? \"dark\" : \"light\"));\n }, []);\n\n const handleHelp = useCallback(() => {\n startTour();\n }, [startTour]);\n\n const closeModal = useCallback(() => {\n setModal({\n isOpen: false,\n type: \"success\",\n title: \"\",\n message: \"\",\n details: \"\",\n });\n }, []);\n\n const handleDownloadWorkspace = useCallback(() => {\n if (!workspace) {\n setModal({\n isOpen: true,\n type: \"error\",\n title: \"Erro ao Salvar\",\n message: \"Nenhum workspace disponível para salvar.\",\n details: \"\",\n });\n return;\n }\n\n try {\n const json = Blockly.serialization.workspaces.save(workspace);\n const dataStr = JSON.stringify(json, null, 2);\n const dataBlob = new Blob([dataStr], { type: \"application/json\" });\n const url = URL.createObjectURL(dataBlob);\n const link = document.createElement(\"a\");\n link.href = url;\n const fileName = `playground-${new Date().toISOString().slice(0, 10)}.json`;\n link.download = fileName;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n\n setModal({\n isOpen: true,\n type: \"success\",\n title: \"Blocos Salvos com Sucesso!\",\n message: \"Seu workspace foi exportado com sucesso.\",\n details: `Arquivo: ${fileName}\\nLocalização: Pasta de Downloads`,\n });\n } catch (error) {\n console.error(\"Erro ao baixar workspace:\", error);\n setModal({\n isOpen: true,\n type: \"error\",\n title: \"Erro ao Salvar\",\n message: \"Ocorreu um erro ao exportar o workspace.\",\n details: error.message,\n });\n }\n }, [workspace]);\n\n const handleUploadWorkspace = useCallback(() => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".json\";\n\n input.onchange = (e) => {\n const file = e.target.files[0];\n if (!file) return;\n\n const reader = new FileReader();\n reader.onload = (event) => {\n try {\n const json = JSON.parse(event.target.result);\n\n if (!workspace) {\n setModal({\n isOpen: true,\n type: \"error\",\n title: \"Erro ao Carregar\",\n message: \"O workspace não está pronto para receber dados.\",\n details: \"\",\n });\n return;\n }\n\n // Limpar workspace atual\n workspace.clear();\n\n // Carregar novo workspace\n Blockly.serialization.workspaces.load(json, workspace);\n\n // Atualizar versão para forçar regeneração do código\n setWorkspaceVersion((v) => v + 1);\n\n setModal({\n isOpen: true,\n type: \"success\",\n title: \"Blocos Carregados com Sucesso!\",\n message: \"O workspace foi carregado e está pronto para uso.\",\n details: `Arquivo: ${file.name}`,\n });\n } catch (error) {\n console.error(\"Erro ao carregar workspace:\", error);\n setModal({\n isOpen: true,\n type: \"error\",\n title: \"Erro ao Carregar\",\n message:\n \"Não foi possível carregar o arquivo. Verifique se é um arquivo JSON válido.\",\n details: error.message,\n });\n }\n };\n\n reader.readAsText(file);\n };\n\n input.click();\n }, [workspace]);\n\n return (\n \n
\n\n
\n
\n \n \n
\n
\n Editor de Blocos\n \n {!isMobile && (\n
\n \n \n \n
\n )}\n
\n
\n
\n \n\n \n\n \n \n
\n
\n Código\n \n \n {theme === \"light\" ? (\n \n ) : (\n \n )}\n \n \n
\n
\n \n \n
\n\n
\n
\n );\n};\n\nexport default Playground;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/CodeViewer.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Copy' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"Copy"},"fix":{"range":[50,55],"text":""},"desc":"Remove unused variable 'Copy'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Check' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"Check"},"fix":{"range":[54,61],"text":""},"desc":"Remove unused variable 'Check'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeMirror' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeMirror"},"fix":{"range":[92,108],"text":""},"desc":"Remove unused variable 'CodeMirror'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'language' is assigned a value but never used. Allowed unused args must match /^_/u.","line":9,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"language"},"fix":{"range":[247,274],"text":""},"desc":"Remove unused variable 'language'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'title' is assigned a value but never used. Allowed unused args must match /^_/u.","line":10,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":8,"suggestions":[{"messageId":"removeVar","data":{"varName":"title"},"fix":{"range":[274,294],"text":""},"desc":"Remove unused variable 'title'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState } from \"react\";\nimport { Copy, Check } from \"lucide-react\";\nimport CodeMirror from \"@uiw/react-codemirror\";\nimport { javascript } from \"@codemirror/lang-javascript\";\nimport \"./CodeViewer.css\";\n\nconst CodeViewer = ({\n code,\n language = \"javascript\",\n title = \"Código\",\n theme = \"light\",\n}) => {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (error) {\n console.error(\"Erro ao copiar:\", error);\n }\n };\n\n return (\n \n
\n {copied ? (\n <>\n \n Copiado! \n >\n ) : (\n <>\n \n Copiar \n >\n )}\n \n\n
\n \n
\n
\n );\n};\n\nexport default CodeViewer;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/ExamplesModal.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":4,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[38,40],"text":""},"desc":"Remove unused variable 'X'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BookOpen' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"BookOpen"},"fix":{"range":[39,51],"text":""},"desc":"Remove unused variable 'BookOpen'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Play' is defined but never used. Allowed unused vars must match /^_/u.","line":5,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":7,"suggestions":[{"messageId":"removeVar","data":{"varName":"Play"},"fix":{"range":[51,59],"text":""},"desc":"Remove unused variable 'Play'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'IconComponent' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":126,"column":21,"nodeType":"Identifier","messageId":"unusedVar","endLine":126,"endColumn":34,"suggestions":[{"messageId":"removeVar","data":{"varName":"IconComponent"},"fix":{"range":[3764,3799],"text":""},"desc":"Remove unused variable 'IconComponent'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport {\n X,\n BookOpen,\n Play,\n Hash,\n User,\n RotateCw,\n Plus,\n Dices,\n BarChart3,\n Thermometer,\n Calculator,\n} from \"lucide-react\";\n\nconst ExamplesModal = ({ isOpen, onClose, onSelectExample }) => {\n if (!isOpen) return null;\n\n const examples = [\n {\n id: \"contador\",\n name: \"Contador de 1 a 10\",\n description: \"Um loop que conta de 1 a 10 e imprime cada valor\",\n file: \"/examples/contador.json\",\n icon: Hash,\n },\n {\n id: \"nome\",\n name: \"Imprimir Texto\",\n description: \"Um programa simples que imprime um texto\",\n file: \"/examples/nome.json\",\n icon: User,\n },\n {\n id: \"fibonacci\",\n name: \"Fibonacci\",\n description: \"Um exemplo de sequência de Fibonacci em blocos\",\n file: \"/examples/fibonacci.json\",\n icon: RotateCw,\n },\n {\n id: \"soma-lista\",\n name: \"Soma de Lista\",\n description: \"Percorre uma lista de números e calcula a soma total\",\n file: \"/examples/soma-lista.json\",\n icon: Plus,\n },\n {\n id: \"par-impar\",\n name: \"Par ou Ímpar\",\n description:\n \"Verifica se um número é par ou ímpar usando operador módulo\",\n file: \"/examples/par-impar.json\",\n icon: Dices,\n },\n {\n id: \"maior-numero\",\n name: \"Maior Número\",\n description: \"Encontra o maior número em uma lista usando comparação\",\n file: \"/examples/maior-numero.json\",\n icon: BarChart3,\n },\n {\n id: \"temperatura\",\n name: \"Conversor de Temperatura\",\n description: \"Converte temperatura de Celsius para Fahrenheit\",\n file: \"/examples/temperatura.json\",\n icon: Thermometer,\n },\n {\n id: \"fatorial\",\n name: \"Cálculo Fatorial\",\n description: \"Calcula o fatorial de um número usando loop\",\n file: \"/examples/fatorial.json\",\n icon: Calculator,\n },\n ];\n\n const handleExampleClick = async (example) => {\n try {\n const response = await fetch(example.file);\n if (!response.ok) {\n throw new Error(\"Erro ao carregar exemplo\");\n }\n const data = await response.json();\n\n // Extrair apenas os dados do workspace (blocks e variables)\n const workspaceData = {\n blocks: data.blocks,\n ...(data.variables && { variables: data.variables }),\n };\n\n onSelectExample(workspaceData);\n onClose();\n } catch (error) {\n console.error(\"Erro ao carregar exemplo:\", error);\n alert(\"Erro ao carregar exemplo. Tente novamente.\");\n }\n };\n\n return (\n \n
\n {/* Header */}\n
\n\n {/* Content */}\n
\n
\n {examples.map((example) => {\n const IconComponent = example.icon;\n return (\n
handleExampleClick(example)}\n className=\"group relative flex items-start gap-4 p-5 bg-white/80 backdrop-blur-sm border border-white/30 rounded-xl shadow-lg hover:border-blue-300 transition-all duration-200 text-left\"\n >\n {/* Icon */}\n \n \n
\n\n {/* Content */}\n \n
\n {example.name}\n \n
\n {example.description}\n
\n
\n\n {/* Arrow Icon */}\n \n \n );\n })}\n
\n
\n
\n
\n );\n};\n\nexport default ExamplesModal;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/PlaygroundConsole.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Trash2' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":16,"suggestions":[{"messageId":"removeVar","data":{"varName":"Trash2"},"fix":{"range":[50,88],"text":""},"desc":"Remove unused variable 'Trash2'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useEffect, useRef } from \"react\";\nimport { Trash2 } from \"lucide-react\";\nimport \"./PlaygroundConsole.css\";\n\nconst PlaygroundConsole = ({ output, onClear, theme = \"light\" }) => {\n const consoleRef = useRef(null);\n\n useEffect(() => {\n if (consoleRef.current) {\n consoleRef.current.scrollTop = consoleRef.current.scrollHeight;\n }\n }, [output]);\n\n const formatTimestamp = (date) => {\n return date.toLocaleTimeString(\"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n fractionalSecondDigits: 3,\n });\n };\n\n const getIcon = (type) => {\n switch (type) {\n case \"error\":\n return \"❌\";\n case \"success\":\n return \"✅\";\n case \"alert\":\n return \"⚠️\";\n default:\n return \"\";\n }\n };\n\n return (\n \n
\n \n Console ({output.length})\n \n \n \n Limpar \n \n
\n
\n {output.length === 0 ? (\n
\n Nenhuma saída ainda. Execute o código para ver os resultados.\n
\n ) : (\n output.map((entry) => (\n
\n \n {formatTimestamp(entry.timestamp)}\n \n \n {getIcon(entry.type)}\n \n \n {entry.message}\n \n
\n ))\n )}\n
\n
\n );\n};\n\nexport default PlaygroundConsole;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/PlaygroundEditor.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Download' is defined but never used. Allowed unused vars must match /^_/u.","line":10,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"Download"},"fix":{"range":[145,154],"text":""},"desc":"Remove unused variable 'Download'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Upload' is defined but never used. Allowed unused vars must match /^_/u.","line":11,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":11,"endColumn":9,"suggestions":[{"messageId":"removeVar","data":{"varName":"Upload"},"fix":{"range":[153,163],"text":""},"desc":"Remove unused variable 'Upload'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Play' is defined but never used. Allowed unused vars must match /^_/u.","line":12,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":12,"endColumn":7,"suggestions":[{"messageId":"removeVar","data":{"varName":"Play"},"fix":{"range":[163,171],"text":""},"desc":"Remove unused variable 'Play'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Loader' is defined but never used. Allowed unused vars must match /^_/u.","line":13,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":13,"endColumn":9,"suggestions":[{"messageId":"removeVar","data":{"varName":"Loader"},"fix":{"range":[171,181],"text":""},"desc":"Remove unused variable 'Loader'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CircleAlert' is defined but never used. Allowed unused vars must match /^_/u.","line":14,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":14,"endColumn":14,"suggestions":[{"messageId":"removeVar","data":{"varName":"CircleAlert"},"fix":{"range":[181,196],"text":""},"desc":"Remove unused variable 'CircleAlert'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'BookOpen' is defined but never used. Allowed unused vars must match /^_/u.","line":15,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":15,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"BookOpen"},"fix":{"range":[196,208],"text":""},"desc":"Remove unused variable 'BookOpen'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'HelpCircle' is defined but never used. Allowed unused vars must match /^_/u.","line":16,"column":3,"nodeType":"Identifier","messageId":"unusedVar","endLine":16,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"HelpCircle"},"fix":{"range":[208,222],"text":""},"desc":"Remove unused variable 'HelpCircle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ExamplesModal' is defined but never used. Allowed unused vars must match /^_/u.","line":25,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":25,"endColumn":21,"suggestions":[{"messageId":"removeVar","data":{"varName":"ExamplesModal"},"fix":{"range":[529,548],"text":""},"desc":"Remove unused variable 'ExamplesModal'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, {\n useEffect,\n useRef,\n useCallback,\n useMemo,\n useState,\n} from \"react\";\nimport * as Blockly from \"blockly/core\";\nimport {\n Download,\n Upload,\n Play,\n Loader,\n CircleAlert,\n BookOpen,\n HelpCircle,\n} from \"lucide-react\";\nimport { playgroundToolbox } from \"../config/playgroundToolbox\";\nimport { defaultWorkspace } from \"../config/defaultWorkspace\";\nimport {\n loadWorkspace,\n createDebouncedSave,\n} from \"../services/playgroundStorage\";\nimport { useIsMobile } from \"../../../hooks/useIsMobile\";\nimport ExamplesModal from \"./ExamplesModal\";\nimport \"./PlaygroundEditor.css\";\n\n// Tema claro personalizado\nconst lightTheme = Blockly.Theme.defineTheme(\"light\", {\n base: Blockly.Themes.Classic,\n componentStyles: {\n workspaceBackgroundColour: \"#f9fafb\",\n toolboxBackgroundColour: \"#e5e7eb\",\n toolboxForegroundColour: \"#374151\",\n flyoutBackgroundColour: \"#f3f4f6\",\n flyoutForegroundColour: \"#111827\",\n flyoutOpacity: 0.95,\n scrollbarColour: \"#9ca3af\",\n scrollbarOpacity: 0.5,\n },\n});\n\n// Tema escuro personalizado\nconst darkTheme = Blockly.Theme.defineTheme(\"dark\", {\n base: Blockly.Themes.Classic,\n componentStyles: {\n workspaceBackgroundColour: \"#1e1e1e\",\n toolboxBackgroundColour: \"#252526\",\n toolboxForegroundColour: \"#cccccc\",\n flyoutBackgroundColour: \"#2d2d30\",\n flyoutForegroundColour: \"#ffffff\",\n flyoutOpacity: 0.95,\n scrollbarColour: \"#6b7280\",\n scrollbarOpacity: 0.5,\n },\n});\n\nconst PlaygroundEditor = ({\n onWorkspaceChange,\n onDownloadWorkspace,\n onUploadWorkspace,\n onRunCode,\n onHelp,\n isRunning,\n blockCount = 0,\n theme = \"light\",\n}) => {\n const blocklyDiv = useRef(null);\n const workspaceRef = useRef(null);\n const isInitializedRef = useRef(false);\n const isMobile = useIsMobile();\n const debouncedSave = useMemo(() => createDebouncedSave(1000), []);\n const [showExamples, setShowExamples] = useState(false);\n const [isVisible, setIsVisible] = useState(true);\n\n // Monitorar visibilidade da página\n useEffect(() => {\n const handleVisibilityChange = () => {\n const visible = !document.hidden;\n setIsVisible(visible);\n\n if (visible && workspaceRef.current) {\n // Forçar resize quando a página voltar a ficar visível\n requestAnimationFrame(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n });\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n return () =>\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n }, []);\n\n // Forçar resize quando isVisible mudar\n useEffect(() => {\n if (isVisible && workspaceRef.current) {\n const timer = setTimeout(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n }, 100);\n return () => clearTimeout(timer);\n }\n }, [isVisible]);\n\n const handleWorkspaceChange = useCallback(() => {\n if (!workspaceRef.current) return;\n\n debouncedSave(workspaceRef.current);\n\n if (onWorkspaceChange) {\n onWorkspaceChange(workspaceRef.current);\n }\n }, [onWorkspaceChange, debouncedSave]);\n\n useEffect(() => {\n if (isInitializedRef.current || !blocklyDiv.current) return;\n\n isInitializedRef.current = true;\n\n const toolboxWithIcons = {\n ...playgroundToolbox,\n contents: playgroundToolbox.contents.map((cat) => ({\n ...cat,\n \"css-icon\": cat.cssIcon || \"fa fa-cube\",\n })),\n };\n\n workspaceRef.current = Blockly.inject(blocklyDiv.current, {\n toolbox: toolboxWithIcons,\n theme: theme === \"light\" ? lightTheme : darkTheme,\n grid: {\n spacing: 25,\n length: 3,\n colour: theme === \"light\" ? \"#ccc\" : \"#3a3a3a\",\n snap: true,\n },\n zoom: {\n controls: false,\n wheel: true,\n startScale: 1.0,\n maxScale: 3,\n minScale: 0.3,\n scaleSpeed: 1.2,\n },\n trashcan: true,\n scrollbars: true,\n renderer: \"zelos\",\n });\n\n const savedData = loadWorkspace();\n const workspaceToLoad = savedData || defaultWorkspace;\n\n try {\n Blockly.serialization.workspaces.load(\n workspaceToLoad,\n workspaceRef.current,\n );\n } catch (error) {\n console.error(\"Erro ao carregar workspace:\", error);\n // Se falhar, tenta carregar o workspace padrão\n try {\n Blockly.serialization.workspaces.load(\n defaultWorkspace,\n workspaceRef.current,\n );\n } catch (fallbackError) {\n console.error(\"Erro ao carregar workspace padrão:\", fallbackError);\n }\n }\n\n handleWorkspaceChange();\n\n const listener = (event) => {\n if (event.isUiEvent) return;\n handleWorkspaceChange();\n };\n\n workspaceRef.current.addChangeListener(listener);\n\n // Fazer zoom para ajustar todos os blocos na tela (especialmente útil em mobile)\n setTimeout(() => {\n if (workspaceRef.current && isMobile) {\n const metrics = workspaceRef.current.getMetrics();\n if (metrics) {\n // Calcular escala para caber todos os blocos\n workspaceRef.current.zoomToFit();\n }\n }\n }, 200);\n\n // Adicionar ResizeObserver para garantir que o workspace se redimensione corretamente\n const resizeObserver = new ResizeObserver(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n });\n\n if (blocklyDiv.current) {\n resizeObserver.observe(blocklyDiv.current);\n }\n\n // Listener para quando a página voltar a ficar visível (alt+tab, mudança de aba)\n const handleVisibilityChange = () => {\n if (!document.hidden && workspaceRef.current) {\n // Pequeno delay para garantir que o layout está estável\n setTimeout(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n }, 50);\n }\n };\n\n // Listener para quando a janela receber foco novamente\n const handleWindowFocus = () => {\n if (workspaceRef.current) {\n setTimeout(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n }, 50);\n }\n };\n\n // Listener para resize da janela\n const handleWindowResize = () => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n window.addEventListener(\"focus\", handleWindowFocus);\n window.addEventListener(\"resize\", handleWindowResize);\n\n // Forçar um resize inicial após um pequeno delay\n setTimeout(() => {\n if (workspaceRef.current) {\n Blockly.svgResize(workspaceRef.current);\n }\n }, 100);\n\n return () => {\n resizeObserver.disconnect();\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n window.removeEventListener(\"focus\", handleWindowFocus);\n window.removeEventListener(\"resize\", handleWindowResize);\n if (workspaceRef.current) {\n workspaceRef.current.dispose();\n workspaceRef.current = null;\n }\n };\n }, [handleWorkspaceChange]);\n\n // Efeito para trocar o tema do Blockly dinamicamente\n useEffect(() => {\n if (!workspaceRef.current) return;\n\n const newTheme = theme === \"light\" ? lightTheme : darkTheme;\n workspaceRef.current.setTheme(newTheme);\n\n // Atualizar a cor do grid\n const gridColour = theme === \"light\" ? \"#ccc\" : \"#3a3a3a\";\n workspaceRef.current.getTheme().componentStyles.gridColour = gridColour;\n\n // Forçar atualização visual\n workspaceRef.current.refreshTheme();\n }, [theme]);\n\n const naoTemBlocos = blockCount === 0;\n\n const handleLoadExample = useCallback(\n (exampleData) => {\n if (!workspaceRef.current) return;\n\n try {\n // Limpar workspace atual\n workspaceRef.current.clear();\n\n // Carregar o exemplo\n Blockly.serialization.workspaces.load(\n exampleData,\n workspaceRef.current,\n );\n\n // Fazer zoom para ajustar todos os blocos na tela (especialmente útil em mobile)\n setTimeout(() => {\n if (workspaceRef.current && isMobile) {\n workspaceRef.current.zoomToFit();\n }\n }, 150);\n\n // Notificar mudança\n handleWorkspaceChange();\n } catch (error) {\n console.error(\"Erro ao carregar exemplo:\", error);\n alert(\"Erro ao carregar exemplo. Tente novamente.\");\n }\n },\n [handleWorkspaceChange, isMobile],\n );\n\n const getRunButtonClasses = () => {\n const baseClasses = `flex items-center gap-2 ${isMobile ? \"px-3 py-3\" : \"px-6 py-3\"} rounded-full text-sm font-medium text-white transition-all duration-200 shadow-md hover:shadow-lg`;\n\n if (naoTemBlocos) {\n return `${baseClasses} bg-gradient-to-r from-amber-700 to-amber-600 cursor-not-allowed opacity-100`;\n }\n\n if (isRunning) {\n return `${baseClasses} bg-gradient-to-r from-blue-500 to-emerald-500 hover:from-blue-600 hover:to-emerald-600 hover:-translate-y-0.5`;\n }\n\n return `${baseClasses} bg-gradient-to-r from-red-500 via-pink-500 to-purple-500 hover:from-red-600 hover:via-pink-600 hover:to-purple-600 hover:-translate-y-0.5`;\n };\n\n return (\n \n
\n
\n {isRunning ? (\n <>\n \n {!isMobile && Executando... }\n >\n ) : naoTemBlocos ? (\n <>\n \n {!isMobile && Adicione blocos para executar }\n >\n ) : (\n <>\n \n {!isMobile && Executar }\n >\n )}\n \n\n
\n \n {!isMobile && Salvar Blocos }\n \n\n
\n \n {!isMobile && Carregar Blocos }\n \n\n
setShowExamples(true)}\n className={`flex items-center gap-2 ${isMobile ? \"px-3 py-3\" : \"px-6 py-3\"} bg-gradient-to-r from-green-500 to-teal-600 text-white rounded-full text-sm font-medium transition-all duration-200 shadow-md hover:shadow-lg hover:from-green-600 hover:to-teal-700 hover:-translate-y-0.5 active:translate-y-0`}\n data-tour=\"examples-button\"\n title=\"Ver exemplos de código\"\n >\n \n {!isMobile && Exemplos }\n \n\n {isMobile && onHelp && (\n
\n \n \n )}\n
\n
\n\n
setShowExamples(false)}\n onSelectExample={handleLoadExample}\n />\n \n );\n};\n\nexport default PlaygroundEditor;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/PlaygroundModal.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,18],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'X' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":11,"suggestions":[{"messageId":"removeVar","data":{"varName":"X"},"fix":{"range":[36,38],"text":""},"desc":"Remove unused variable 'X'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CheckCircle' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"CheckCircle"},"fix":{"range":[37,50],"text":""},"desc":"Remove unused variable 'CheckCircle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'AlertCircle' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":26,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":37,"suggestions":[{"messageId":"removeVar","data":{"varName":"AlertCircle"},"fix":{"range":[50,63],"text":""},"desc":"Remove unused variable 'AlertCircle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from \"react\";\nimport { X, CheckCircle, AlertCircle } from \"lucide-react\";\n\nconst PlaygroundModal = ({\n isOpen,\n onClose,\n type = \"success\",\n title,\n message,\n details,\n}) => {\n if (!isOpen) return null;\n\n const getIcon = () => {\n switch (type) {\n case \"success\":\n return ;\n case \"error\":\n return ;\n default:\n return null;\n }\n };\n\n const getStyles = () => {\n switch (type) {\n case \"success\":\n return {\n border: \"border-green-500\",\n bg: \"bg-green-50\",\n titleColor: \"text-green-900\",\n messageColor: \"text-green-700\",\n };\n case \"error\":\n return {\n border: \"border-red-500\",\n bg: \"bg-red-50\",\n titleColor: \"text-red-900\",\n messageColor: \"text-red-700\",\n };\n default:\n return {\n border: \"border-gray-500\",\n bg: \"bg-gray-50\",\n titleColor: \"text-gray-900\",\n messageColor: \"text-gray-700\",\n };\n }\n };\n\n const styles = getStyles();\n\n return (\n \n
\n {/* Header com gradiente */}\n
\n
\n {getIcon()}\n
\n {title}\n \n \n
\n \n \n
\n\n {/* Conteúdo */}\n
\n
{message}
\n\n {details && (\n
\n )}\n
\n\n {/* Footer */}\n
\n \n Fechar\n \n
\n
\n
\n );\n};\n\nexport default PlaygroundModal;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/components/PlaygroundViewer.jsx","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'React' is defined but never used. Allowed unused vars must match /^_/u.","line":1,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":13,"suggestions":[{"messageId":"removeVar","data":{"varName":"React"},"fix":{"range":[7,13],"text":""},"desc":"Remove unused variable 'React'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PanelGroup' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"PanelGroup"},"fix":{"range":[50,61],"text":""},"desc":"Remove unused variable 'PanelGroup'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'Panel' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":22,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":27,"suggestions":[{"messageId":"removeVar","data":{"varName":"Panel"},"fix":{"range":[60,67],"text":""},"desc":"Remove unused variable 'Panel'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PanelResizeHandle' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":29,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":46,"suggestions":[{"messageId":"removeVar","data":{"varName":"PanelResizeHandle"},"fix":{"range":[67,86],"text":""},"desc":"Remove unused variable 'PanelResizeHandle'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'CodeViewer' is defined but never used. Allowed unused vars must match /^_/u.","line":3,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":18,"suggestions":[{"messageId":"removeVar","data":{"varName":"CodeViewer"},"fix":{"range":[127,143],"text":""},"desc":"Remove unused variable 'CodeViewer'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'PlaygroundConsole' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"PlaygroundConsole"},"fix":{"range":[166,189],"text":""},"desc":"Remove unused variable 'PlaygroundConsole'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'ResizeHandle' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":6,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":19,"suggestions":[{"messageId":"removeVar","data":{"varName":"ResizeHandle"},"fix":{"range":[213,1425],"text":""},"desc":"Remove unused variable 'ResizeHandle'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React, { useState } from \"react\";\nimport { PanelGroup, Panel, PanelResizeHandle } from \"react-resizable-panels\";\nimport CodeViewer from \"./CodeViewer\";\nimport PlaygroundConsole from \"./PlaygroundConsole\";\n\nconst ResizeHandle = ({ theme }) => {\n return (\n \n \n \n \n \n
\n \n );\n};\n\nconst PlaygroundViewer = ({\n generatedCode,\n output,\n onClearConsole,\n theme = \"light\",\n}) => {\n const [activeTab, setActiveTab] = useState(\"javascript\");\n\n const tabs = [\n { id: \"javascript\", label: \"JavaScript\", icon: \"JS\" },\n { id: \"python\", label: \"Python\", icon: \"PY\" },\n ];\n\n const renderContent = () => {\n switch (activeTab) {\n case \"javascript\":\n return (\n \n );\n\n case \"python\":\n return (\n \n );\n\n default:\n return null;\n }\n };\n\n return (\n \n
\n {tabs.map((tab) => (\n setActiveTab(tab.id)}\n title={tab.label}\n >\n {tab.icon} \n {tab.label} \n \n ))}\n
\n\n
\n
\n \n \n {renderContent()}\n
\n \n\n \n\n \n \n \n \n
\n
\n );\n};\n\nexport default PlaygroundViewer;\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/config/defaultWorkspace.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/config/playgroundToolbox.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/config/tourSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/hooks/useCodeExecution.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/hooks/useCodeGeneration.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'Blockly' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":20,"suggestions":[{"messageId":"removeVar","data":{"varName":"Blockly"},"fix":{"range":[40,58],"text":""},"desc":"Remove unused variable 'Blockly'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useMemo } from \"react\";\nimport * as Blockly from \"blockly/core\";\nimport { javascriptGenerator } from \"blockly/javascript\";\nimport { pythonGenerator } from \"blockly/python\";\n\nexport const useCodeGeneration = (workspace, version = 0) => {\n return useMemo(() => {\n if (!workspace) {\n return {\n javascript: \"\",\n python: \"\",\n };\n }\n\n try {\n javascriptGenerator.STATEMENT_PREFIX = null;\n javascriptGenerator.STATEMENT_SUFFIX = null;\n javascriptGenerator.INFINITE_LOOP_TRAP = null;\n\n pythonGenerator.STATEMENT_PREFIX = null;\n pythonGenerator.STATEMENT_SUFFIX = null;\n pythonGenerator.INFINITE_LOOP_TRAP = null;\n\n const javascript = javascriptGenerator.workspaceToCode(workspace);\n const python = pythonGenerator.workspaceToCode(workspace);\n\n return {\n javascript,\n python,\n };\n } catch (error) {\n console.error(\"Erro ao gerar código:\", error);\n return {\n javascript: `// Erro ao gerar código: ${error.message}`,\n python: `# Erro ao gerar código: ${error.message}`,\n };\n }\n }, [workspace, version]);\n};\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/hooks/usePlaygroundTour.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/pages/Playground/services/playgroundStorage.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/services/blockstorage.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/services/codestorage.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/shared/BaseGameScene.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/shared/BaseGameValidator.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'history' is defined but never used. Allowed unused args must match /^_/u.","line":35,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":35,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"history"},"fix":{"range":[1234,1242],"text":""},"desc":"Remove unused variable 'history'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'configFase' is defined but never used. Allowed unused args must match /^_/u.","line":35,"column":26,"nodeType":"Identifier","messageId":"unusedVar","endLine":35,"endColumn":36,"suggestions":[{"messageId":"removeVar","data":{"varName":"configFase"},"fix":{"range":[1241,1253],"text":""},"desc":"Remove unused variable 'configFase'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'gameConfig' is defined but never used. Allowed unused args must match /^_/u.","line":35,"column":38,"nodeType":"Identifier","messageId":"unusedVar","endLine":35,"endColumn":48,"suggestions":[{"messageId":"removeVar","data":{"varName":"gameConfig"},"fix":{"range":[1253,1265],"text":""},"desc":"Remove unused variable 'gameConfig'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export class BaseGameValidator {\n /**\n * Valida a solução do aluno.\n */\n validate(history, configFase, gameConfig) {\n // 1. Fail-Safe Técnico: A config da fase chegou?\n if (!configFase || Object.keys(configFase).length === 0) {\n console.error(\"Validação falhou: Configuração da fase não encontrada.\");\n return this.failure(\"Erro Técnico: Fase não configurada.\");\n }\n\n // 2. Sanity Check: O histórico está vazio? (Genérico para qualquer jogo)\n const sanityCheck = this.checkSanity(history, gameConfig);\n if (!sanityCheck.success) return sanityCheck;\n\n // 3. Validação de Sequência (Opcional, ativado via config)\n // Muitos jogos usam \"siga estes passos exatos\", então vale manter na base.\n if (configFase.expectedSequence) {\n const sequenceCheck = this.checkSequence(\n history,\n configFase.expectedSequence,\n gameConfig,\n );\n if (!sequenceCheck.success) return sequenceCheck;\n }\n\n // 4. Validação Específica (Polimorfismo)\n // Aqui entra a lógica de Negócio do Jogo (Limites, Regras, etc)\n return this.validatePhase(history, configFase, gameConfig);\n }\n\n /**\n * Método abstrato - DEVE ser implementado pelo jogo filho\n */\n validatePhase(history, configFase, gameConfig) {\n console.warn(\"BaseGameValidator: validatePhase não implementado.\");\n return this.success();\n }\n\n // --- Verificações Genéricas ---\n\n checkSanity(history, gameConfig) {\n if (!history || history.length === 0) {\n return this.failure(\n gameConfig?.mensagens?.semMovimento || \"Nenhum movimento detectado.\",\n );\n }\n return this.success();\n }\n\n checkSequence(history, expected, gameConfig) {\n // Compara se o histórico bate com a sequência esperada, propriedade por propriedade\n const match = expected.every((step, i) => {\n const action = history[i];\n if (!action) return false;\n // Verifica se todas as chaves do passo esperado estão presentes e iguais na ação\n return Object.keys(step).every((key) => step[key] === action[key]);\n });\n\n return match\n ? this.success()\n : this.failure(\n gameConfig?.mensagens?.caminhoErrado || \"Sequência incorreta.\",\n );\n }\n\n // --- Helpers de Retorno (Syntax Sugar) ---\n\n success() {\n return { success: true };\n }\n\n failure(reason) {\n return { success: false, reason };\n }\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/shared/__tests__/BaseGameScene.test.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'GameInterpreter' is defined but never used. Allowed unused vars must match /^_/u.","line":4,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":25,"suggestions":[{"messageId":"removeVar","data":{"varName":"GameInterpreter"},"fix":{"range":[168,237],"text":""},"desc":"Remove unused variable 'GameInterpreter'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'data' is defined but never used. Allowed unused args must match /^_/u.","line":154,"column":32,"nodeType":"Identifier","messageId":"unusedVar","endLine":154,"endColumn":36,"suggestions":[{"messageId":"removeVar","data":{"varName":"data"},"fix":{"range":[3970,3974],"text":""},"desc":"Remove unused variable 'data'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'reason' is defined but never used. Allowed unused args must match /^_/u.","line":445,"column":39,"nodeType":"Identifier","messageId":"unusedVar","endLine":445,"endColumn":45,"suggestions":[{"messageId":"removeVar","data":{"varName":"reason"},"fix":{"range":[11935,11941],"text":""},"desc":"Remove unused variable 'reason'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport { BaseGameScene } from \"../BaseGameScene\";\nimport { gameEventBus } from \"../../utils/gameEvents\";\nimport { GameInterpreter } from \"../../interpreters/GameInterpreter\";\n\n// Mock dos módulos externos\nvi.mock(\"../../utils/gameEvents\", () => ({\n gameEventBus: {\n gameSuccess: vi.fn(),\n gameFailure: vi.fn(),\n },\n}));\n\nvi.mock(\"../../interpreters/GameInterpreter\", () => ({\n GameInterpreter: class {\n constructor(config) {\n this.config = config;\n this.executeCode = vi.fn();\n this.stop = vi.fn();\n }\n },\n}));\n\nvi.mock(\"../gameController\", () => ({\n setupGameController: vi.fn(),\n}));\n\n// Mock do Phaser\nvi.mock(\"phaser\", () => ({\n default: {\n Scene: class {\n constructor(key) {\n this.key = key;\n this.time = {\n delayedCall: vi.fn((delay, callback) => callback()),\n };\n this.sound = {\n get: vi.fn(),\n play: vi.fn(),\n stop: vi.fn(),\n stopByKey: vi.fn(),\n context: {\n state: \"running\",\n resume: vi.fn().mockResolvedValue(),\n },\n };\n this.registry = {\n values: {},\n };\n }\n },\n },\n}));\n\ndescribe(\"BaseGameScene\", () => {\n let scene;\n\n beforeEach(() => {\n vi.clearAllMocks();\n scene = new BaseGameScene(\"test-scene\");\n });\n\n describe(\"Constructor\", () => {\n it(\"deve inicializar com valores padrão\", () => {\n expect(scene.historico).toEqual([]);\n expect(scene.isRunning).toBe(false);\n expect(scene.workspace).toBeNull();\n });\n\n it(\"deve armazenar a chave da cena\", () => {\n expect(scene.key).toBe(\"test-scene\");\n });\n });\n\n describe(\"init()\", () => {\n it(\"deve inicializar com configurações vazias\", () => {\n scene.init({});\n\n expect(scene.configFase).toEqual({});\n expect(scene.gameConfig).toEqual({});\n expect(scene.historico).toEqual([]);\n expect(scene.isRunning).toBe(false);\n });\n\n it(\"deve armazenar configurações fornecidas\", () => {\n const data = {\n configFase: { nivel: 1 },\n gameConfig: { pontos: 100 },\n customFailureHandler: function () {},\n stepDelay: 100,\n };\n\n scene.init(data);\n\n expect(scene.configFase).toEqual({ nivel: 1 });\n expect(scene.gameConfig).toEqual({ pontos: 100 });\n expect(scene.customFailureHandler).toBe(data.customFailureHandler);\n });\n\n it(\"deve usar dados do registry quando não passados diretamente\", () => {\n scene.registry.values = {\n configFase: { nivel: 2 },\n gameConfig: { pontos: 200 },\n stepDelay: 75,\n };\n\n scene.init({});\n\n expect(scene.configFase).toEqual({ nivel: 2 });\n expect(scene.gameConfig).toEqual({ pontos: 200 });\n expect(scene.gameInterpreter.config.stepDelay).toBe(75);\n });\n\n it(\"deve priorizar dados passados direto sobre registry\", () => {\n scene.registry.values = {\n configFase: { nivel: 2 },\n gameConfig: { pontos: 200 },\n stepDelay: 75,\n };\n\n const data = {\n configFase: { nivel: 1 },\n gameConfig: { pontos: 100 },\n stepDelay: 100,\n };\n\n scene.init(data);\n\n expect(scene.configFase).toEqual({ nivel: 1 });\n expect(scene.gameConfig).toEqual({ pontos: 100 });\n expect(scene.gameInterpreter.config.stepDelay).toBe(100);\n });\n\n it(\"deve criar uma instância de GameInterpreter\", () => {\n scene.init({ stepDelay: 200 });\n\n expect(scene.gameInterpreter).toBeDefined();\n expect(scene.gameInterpreter.config).toEqual({\n stepDelay: 200,\n pauseExec: true,\n });\n });\n\n it(\"deve usar stepDelay padrão de 50\", () => {\n scene.init({});\n\n expect(scene.gameInterpreter.config).toEqual({\n stepDelay: 50,\n pauseExec: true,\n });\n });\n\n it(\"deve chamar onInit se definido\", () => {\n scene.onInit = function (data) {};\n scene.onInit = vi.fn(scene.onInit);\n const data = { configFase: {} };\n\n scene.init(data);\n\n expect(scene.onInit).toHaveBeenCalledWith(data);\n });\n });\n\n describe(\"aguardar()\", () => {\n it(\"deve retornar uma Promise\", () => {\n const result = scene.aguardar(1);\n expect(result).toBeInstanceOf(Promise);\n });\n\n it(\"deve chamar time.delayedCall com tempo correto\", async () => {\n scene.aguardar(2);\n\n expect(scene.time.delayedCall).toHaveBeenCalledWith(\n 2000,\n expect.any(Function),\n );\n });\n });\n\n describe(\"highlightBlock()\", () => {\n it(\"deve chamar workspace.highlightBlock quando workspace existe\", () => {\n scene.workspace = {\n highlightBlock: vi.fn(),\n };\n\n scene.highlightBlock(\"block-123\");\n\n expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(\"block-123\");\n });\n\n it(\"não deve lançar erro quando workspace é null\", () => {\n scene.workspace = null;\n\n expect(() => scene.highlightBlock(\"block-123\")).not.toThrow();\n });\n });\n\n describe(\"playAudio()\", () => {\n it(\"deve reproduzir áudio\", () => {\n scene.playAudio(\"som-teste\", { volume: 0.5 });\n\n expect(scene.sound.play).toHaveBeenCalledWith(\"som-teste\", {\n volume: 0.5,\n });\n });\n\n it(\"deve aceitar config vazia\", () => {\n scene.playAudio(\"som-teste\");\n\n expect(scene.sound.play).toHaveBeenCalledWith(\"som-teste\", {});\n });\n\n it(\"não deve lançar erro se falhar\", () => {\n scene.sound.play.mockImplementation(() => {\n throw new Error(\"Audio error\");\n });\n\n expect(() => scene.playAudio(\"som-teste\")).not.toThrow();\n });\n });\n\n describe(\"stopAudio()\", () => {\n it(\"deve chamar stopByKey\", () => {\n scene.stopAudio(\"loop\");\n expect(scene.sound.stopByKey).toHaveBeenCalledWith(\"loop\");\n });\n\n it(\"não deve lançar erro se falhar\", () => {\n scene.sound.stopByKey.mockImplementation(() => {\n throw new Error(\"Stop audio error\");\n });\n\n expect(() => scene.stopAudio(\"ghost\")).not.toThrow();\n });\n });\n\n describe(\"checkRegex()\", () => {\n it(\"deve retornar true quando não há regex configurada\", () => {\n scene.configFase = {};\n\n const result = scene.checkRegex(\"qualquer codigo\");\n\n expect(result).toBe(true);\n });\n\n it(\"deve validar código com regex string\", () => {\n scene.configFase = {\n validationRegex: \"function\\\\s+\\\\w+\",\n };\n\n expect(scene.checkRegex(\"function teste() {}\")).toBe(true);\n expect(scene.checkRegex(\"const x = 5\")).toBe(false);\n });\n\n it(\"deve validar código com objeto RegExp\", () => {\n scene.configFase = {\n validationRegex: /function\\s+\\w+/,\n };\n\n expect(scene.checkRegex(\"function teste() {}\")).toBe(true);\n expect(scene.checkRegex(\"const x = 5\")).toBe(false);\n });\n\n it(\"deve retornar true em caso de regex inválida\", () => {\n scene.configFase = {\n validationRegex: \"[invalid regex(\",\n };\n\n const result = scene.checkRegex(\"codigo qualquer\");\n\n expect(result).toBe(true);\n });\n });\n\n describe(\"handleExecutionStart()\", () => {\n it(\"deve resetar estado de execução\", () => {\n const workspace = { highlightBlock: vi.fn() };\n scene.historico = [\"item1\", \"item2\"];\n scene.isRunning = false;\n\n scene.handleExecutionStart(workspace);\n\n expect(scene.workspace).toBe(workspace);\n expect(scene.historico).toEqual([]);\n expect(scene.isRunning).toBe(true);\n });\n\n it(\"deve limpar highlight\", () => {\n const workspace = { highlightBlock: vi.fn() };\n\n scene.handleExecutionStart(workspace);\n\n expect(workspace.highlightBlock).toHaveBeenCalledWith(null);\n });\n });\n\n describe(\"BaseGameScene (Assets Globais)\", () => {\n let scene;\n\n beforeEach(() => {\n vi.useFakeTimers();\n scene = new BaseGameScene(\"TestScene\");\n\n // Mock do Loader do Phaser\n scene.load = {\n audio: vi.fn(),\n };\n\n // Mock do Sound Manager\n scene.sound = {\n get: vi.fn().mockReturnValue(true),\n play: vi.fn(),\n stop: vi.fn(),\n };\n\n scene.init({});\n });\n\n it(\"preloadGlobalAssets: deve carregar os audios padrão de win/fail\", () => {\n scene.preloadGlobalAssets();\n\n // Verifica se carregou com as chaves reservadas\n expect(scene.load.audio).toHaveBeenCalledWith(\n \"global_win\",\n expect.stringContaining(\"win.mp3\"),\n );\n expect(scene.load.audio).toHaveBeenCalledWith(\n \"global_fail\",\n expect.stringContaining(\"fail.mp3\"),\n );\n });\n });\n\n describe(\"handleValidation()\", () => {\n beforeEach(() => {\n scene.init({});\n scene.workspace = { highlightBlock: vi.fn() };\n scene.isRunning = true;\n });\n\n it(\"não deve validar se não está executando\", () => {\n scene.isRunning = false;\n const validator = function () {};\n const validatorSpy = vi.fn(validator);\n\n scene.handleValidation(validatorSpy);\n\n expect(validatorSpy).not.toHaveBeenCalled();\n });\n\n it(\"deve chamar gameSuccess quando validação passa\", () => {\n scene.onSuccess = function () {};\n scene.onSuccess = vi.fn(scene.onSuccess);\n const validator = function () {\n return { success: true };\n };\n const validatorSpy = vi.fn(validator);\n\n scene.handleValidation(validatorSpy);\n\n expect(validatorSpy).toHaveBeenCalledWith(\n scene.historico,\n scene.configFase,\n scene.gameConfig,\n );\n expect(scene.onSuccess).toHaveBeenCalled();\n expect(gameEventBus.gameSuccess).toHaveBeenCalled();\n expect(scene.isRunning).toBe(false);\n });\n\n it(\"deve chamar handleFailure quando validação falha\", () => {\n scene.handleFailure = function () {};\n scene.handleFailure = vi.fn(scene.handleFailure);\n const validator = function () {\n return {\n success: false,\n reason: \"Teste falhou\",\n };\n };\n const validatorSpy = vi.fn(validator);\n\n scene.handleValidation(validatorSpy);\n\n expect(scene.handleFailure).toHaveBeenCalledWith(\"Teste falhou\");\n });\n\n it(\"deve limpar highlight após validação\", () => {\n const validator = function () {\n return { success: true };\n };\n const validatorSpy = vi.fn(validator);\n\n scene.handleValidation(validatorSpy);\n\n expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);\n });\n\n it(\"deve tocar som global_win automaticamente no sucesso\", () => {\n // Setup\n scene.init({});\n scene.isRunning = true;\n scene.playAudio = vi.fn(); // Espião no método playAudio\n\n const validator = () => ({ success: true });\n\n scene.handleValidation(validator);\n\n expect(scene.playAudio).toHaveBeenCalledWith(\"global_win\");\n expect(gameEventBus.gameSuccess).toHaveBeenCalled();\n });\n\n it(\"deve tocar som global_fail automaticamente na falha\", () => {\n // Setup\n scene.init({});\n scene.isRunning = true;\n scene.playAudio = vi.fn(); // Espião no método playAudio\n\n const validator = () => ({ success: false, reason: \"Erro\" });\n\n scene.handleValidation(validator);\n\n expect(scene.playAudio).toHaveBeenCalledWith(\"global_fail\");\n // Pequeno avanço de tempo pois o failure tem delay\n vi.advanceTimersByTime(100);\n expect(gameEventBus.gameFailure).toHaveBeenCalled();\n });\n });\n\n describe(\"handleFailure()\", () => {\n beforeEach(() => {\n scene.init({});\n scene.isRunning = true;\n });\n\n it(\"deve chamar onFailure se definido\", () => {\n scene.onFailure = function () {};\n scene.onFailure = vi.fn(scene.onFailure);\n\n scene.handleFailure(\"Erro teste\");\n\n expect(scene.onFailure).toHaveBeenCalled();\n });\n\n it(\"deve chamar customFailureHandler se definido\", () => {\n const customHandler = function (reason) {};\n scene.init({ customFailureHandler: vi.fn(customHandler) });\n\n scene.handleFailure(\"Erro teste\");\n\n expect(scene.customFailureHandler).toHaveBeenCalledWith(\"Erro teste\");\n });\n\n it(\"deve chamar gameEventBus.gameFailure\", () => {\n scene.handleFailure(\"Erro teste\");\n\n expect(gameEventBus.gameFailure).toHaveBeenCalled();\n });\n\n it(\"deve definir isRunning como false\", () => {\n scene.handleFailure(\"Erro teste\");\n\n expect(scene.isRunning).toBe(false);\n });\n });\n\n describe(\"cleanupExecution()\", () => {\n beforeEach(() => {\n scene.init({});\n scene.workspace = { highlightBlock: vi.fn() };\n scene.historico = [\"item1\", \"item2\"];\n scene.isRunning = true;\n });\n\n it(\"deve parar o interpretador\", () => {\n scene.cleanupExecution();\n\n expect(scene.gameInterpreter.stop).toHaveBeenCalled();\n });\n\n it(\"deve limpar highlight\", () => {\n scene.cleanupExecution();\n\n expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);\n });\n\n it(\"deve resetar estado\", () => {\n scene.cleanupExecution();\n\n expect(scene.isRunning).toBe(false);\n expect(scene.historico).toEqual([]);\n });\n });\n\n describe(\"setupStandardController()\", () => {\n let apiFactory;\n let validatorFunc;\n let setupGameController;\n\n beforeEach(async () => {\n const { setupGameController: mockSetup } =\n await import(\"../gameController\");\n setupGameController = mockSetup;\n\n scene.init({\n configFase: { timeout: 5 },\n });\n\n apiFactory = function () {\n return {\n funcao1: vi.fn(),\n funcao2: vi.fn(),\n };\n };\n apiFactory = vi.fn(apiFactory);\n\n validatorFunc = function () {\n return { success: true };\n };\n validatorFunc = vi.fn(validatorFunc);\n\n scene.setupStandardController(apiFactory, validatorFunc);\n });\n\n it(\"deve chamar setupGameController\", () => {\n expect(setupGameController).toHaveBeenCalledWith(\n scene,\n expect.objectContaining({\n onExecuteCode: expect.any(Function),\n onReset: expect.any(Function),\n onCheckSuccess: expect.any(Function),\n }),\n );\n });\n\n it(\"handler de reset deve chamar cleanupExecution\", () => {\n scene.cleanupExecution = function () {};\n scene.cleanupExecution = vi.fn(scene.cleanupExecution);\n\n const callArgs = setupGameController.mock.calls[0][1];\n callArgs.onReset();\n\n expect(scene.cleanupExecution).toHaveBeenCalled();\n });\n\n it(\"handler de reset deve chamar onReset se definido\", () => {\n scene.onReset = function () {};\n scene.onReset = vi.fn(scene.onReset);\n\n const callArgs = setupGameController.mock.calls[0][1];\n callArgs.onReset();\n\n expect(scene.onReset).toHaveBeenCalled();\n });\n });\n\n describe(\"setupStandardController - executeHandler\", () => {\n let apiFactory;\n let validatorFunc;\n let executeHandler;\n\n beforeEach(async () => {\n const { setupGameController } = await import(\"../gameController\");\n\n scene.init({\n configFase: {\n timeout: 5,\n delayValidacao: 500,\n },\n });\n\n apiFactory = function () {\n return {};\n };\n validatorFunc = function () {\n return { success: true };\n };\n\n scene.setupStandardController(apiFactory, validatorFunc);\n\n executeHandler = setupGameController.mock.calls[0][1].onExecuteCode;\n });\n\n it(\"deve executar código sem validação regex\", async () => {\n const codigo = 'console.log(\"teste\")';\n const workspace = { highlightBlock: vi.fn() };\n\n scene.gameInterpreter.executeCode.mockResolvedValue();\n\n await executeHandler(codigo, workspace);\n\n expect(scene.gameInterpreter.executeCode).toHaveBeenCalledWith(\n codigo,\n expect.any(Object),\n );\n });\n\n it(\"deve validar regex antes de executar\", async () => {\n scene.configFase.validationRegex = /function/;\n scene.handleFailure = function () {};\n scene.handleFailure = vi.fn(scene.handleFailure);\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"const x = 5\", workspace);\n\n expect(scene.handleFailure).toHaveBeenCalledWith(\n expect.stringContaining(\"estrutural\"),\n );\n expect(scene.gameInterpreter.executeCode).not.toHaveBeenCalled();\n });\n\n it(\"deve chamar onBeforeRun se definido\", async () => {\n scene.onBeforeRun = function () {\n return Promise.resolve();\n };\n scene.onBeforeRun = vi.fn(scene.onBeforeRun);\n scene.gameInterpreter.executeCode.mockResolvedValue();\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n expect(scene.onBeforeRun).toHaveBeenCalled();\n });\n\n it(\"deve tratar timeout de execução\", async () => {\n scene.configFase.timeout = 0.001; // 1ms\n scene.handleFailure = function () {};\n scene.handleFailure = vi.fn(scene.handleFailure);\n scene.gameInterpreter.executeCode.mockImplementation(\n () => new Promise((resolve) => setTimeout(resolve, 1000)),\n );\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n expect(scene.handleFailure).toHaveBeenCalledWith(\n expect.stringContaining(\"Tempo limite\"),\n );\n });\n\n it(\"deve usar timeout padrão de 15 segundos\", async () => {\n delete scene.configFase.timeout;\n scene.gameInterpreter.executeCode.mockResolvedValue();\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n // Verifica que não deu timeout imediato\n expect(scene.gameInterpreter.executeCode).toHaveBeenCalled();\n });\n\n it(\"deve tratar erros inesperados\", async () => {\n scene.handleFailure = function () {};\n scene.handleFailure = vi.fn(scene.handleFailure);\n scene.gameInterpreter.executeCode.mockRejectedValue(\n new Error(\"Erro customizado\"),\n );\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n expect(scene.handleFailure).toHaveBeenCalledWith(\n expect.stringContaining(\"inesperado\"),\n );\n });\n\n it(\"deve aguardar delay antes de validar\", async () => {\n scene.configFase.delayValidacao = 1000;\n scene.gameInterpreter.executeCode.mockResolvedValue();\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n expect(scene.time.delayedCall).toHaveBeenCalledWith(\n 1000,\n expect.any(Function),\n );\n });\n\n it(\"deve usar delay padrão de 1000ms\", async () => {\n delete scene.configFase.delayValidacao;\n scene.gameInterpreter.executeCode.mockResolvedValue();\n const workspace = { highlightBlock: vi.fn() };\n\n await executeHandler(\"codigo\", workspace);\n\n expect(scene.time.delayedCall).toHaveBeenCalledWith(\n 1000,\n expect.any(Function),\n );\n });\n });\n});\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/shared/__tests__/BaseGameValidator.test.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'history' is defined but never used. Allowed unused args must match /^_/u.","line":6,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":24,"suggestions":[{"messageId":"removeVar","data":{"varName":"history"},"fix":{"range":[255,263],"text":""},"desc":"Remove unused variable 'history'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'config' is defined but never used. Allowed unused args must match /^_/u.","line":6,"column":26,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":32,"suggestions":[{"messageId":"removeVar","data":{"varName":"config"},"fix":{"range":[262,270],"text":""},"desc":"Remove unused variable 'config'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'gameConfig' is defined but never used. Allowed unused args must match /^_/u.","line":6,"column":34,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":44,"suggestions":[{"messageId":"removeVar","data":{"varName":"gameConfig"},"fix":{"range":[270,282],"text":""},"desc":"Remove unused variable 'gameConfig'."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { BaseGameValidator } from \"../BaseGameValidator\";\n\n// Subclasse Fake apenas para testar a Base sem depender do MoleMash\nclass TestValidator extends BaseGameValidator {\n validatePhase(history, config, gameConfig) {\n // Simula uma validação específica que sempre passa se chegar aqui\n return this.success();\n }\n}\n\ndescribe(\"BaseGameValidator\", () => {\n let validator;\n const mockGameConfig = {\n mensagens: {\n semMovimento: \"Nada feito\",\n caminhoErrado: \"Caminho ruim\",\n foraTabuleiro: \"Saiu da borda\",\n },\n };\n\n beforeEach(() => {\n validator = new TestValidator();\n });\n\n describe(\"1. Fail-Safe (Configuração)\", () => {\n it(\"deve falhar se a config da fase for undefined\", () => {\n const result = validator.validate([], undefined, mockGameConfig);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Erro Técnico\");\n });\n\n it(\"deve falhar se a config da fase for vazia\", () => {\n const result = validator.validate([], {}, mockGameConfig);\n expect(result.success).toBe(false);\n expect(result.reason).toContain(\"Erro Técnico\");\n });\n });\n\n describe(\"2. Sanity Check (Histórico Vazio)\", () => {\n it(\"deve falhar se o histórico for vazio\", () => {\n const result = validator.validate([], { id: 1 }, mockGameConfig);\n expect(result.success).toBe(false);\n expect(result.reason).toBe(\"Nada feito\");\n });\n\n it(\"deve falhar se o histórico for null\", () => {\n const result = validator.validate(null, { id: 1 }, mockGameConfig);\n expect(result.success).toBe(false);\n });\n });\n\n describe(\"3. Validação de Sequência (ExpectedSequence)\", () => {\n const configSequencia = {\n id: 1,\n expectedSequence: [\n { l: 1, c: 1 },\n { l: 1, c: 2 },\n ],\n };\n\n it(\"deve passar se o histórico bater com a sequência\", () => {\n const history = [\n { l: 1, c: 1, t: 100 }, // t (tempo) deve ser ignorado na comparação\n { l: 1, c: 2, t: 200 },\n ];\n const result = validator.validate(\n history,\n configSequencia,\n mockGameConfig,\n );\n expect(result.success).toBe(true);\n });\n\n it(\"deve falhar se um passo for diferente\", () => {\n const history = [\n { l: 1, c: 1 },\n { l: 1, c: 3 }, // Errado\n ];\n const result = validator.validate(\n history,\n configSequencia,\n mockGameConfig,\n );\n expect(result.success).toBe(false);\n expect(result.reason).toBe(\"Caminho ruim\");\n });\n\n it(\"deve falhar se o tamanho for menor que a sequência\", () => {\n const history = [{ l: 1, c: 1 }];\n const result = validator.validate(\n history,\n configSequencia,\n mockGameConfig,\n );\n expect(result.success).toBe(false);\n });\n });\n\n describe(\"4. Delegação (Template Method)\", () => {\n it(\"deve chamar validatePhase se passar nas validações básicas\", () => {\n // Espiona o método da subclasse\n const spy = vi.spyOn(validator, \"validatePhase\");\n\n const history = [{ x: 1 }];\n const config = { id: 99 }; // Sem sequence, deve ir pro validatePhase\n\n const result = validator.validate(history, config, mockGameConfig);\n\n expect(result.success).toBe(true);\n expect(spy).toHaveBeenCalledWith(history, config, mockGameConfig);\n });\n });\n});\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/shared/gameController.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/blocklyConfigureMobileToolbox.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/blocklySetup.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/blocklyValidation.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"'index' is defined but never used. Allowed unused args must match /^_/u.","line":84,"column":31,"nodeType":"Identifier","messageId":"unusedVar","endLine":84,"endColumn":36,"suggestions":[{"messageId":"removeVar","data":{"varName":"index"},"fix":{"range":[2256,2263],"text":""},"desc":"Remove unused variable 'index'."}]},{"ruleId":"no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":104,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":104,"endColumn":25},{"ruleId":"no-unused-vars","severity":1,"message":"'error' is defined but never used.","line":108,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":108,"endColumn":21}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"export class BlocklyWorkspaceValidator {\n constructor(options = {}) {\n this.options = {\n allowMultipleTopBlocks: false,\n preferredStartBlocks: [\"start\", \"when_run\", \"main\"],\n ...options,\n };\n }\n\n /**\n * Valida e limpa a workspace antes da execução\n * @param {Blockly.Workspace} workspace - A workspace do Blockly\n * @returns {Object} Resultado da validação\n */\n validateAndClean(workspace) {\n if (!workspace) {\n return { isValid: false, warnings: [\"Workspace não disponível\"] };\n }\n\n const topBlocks = workspace.getTopBlocks(false);\n const warnings = [];\n\n if (topBlocks.length === 0) {\n return {\n isValid: false,\n warnings: [\"Nenhum bloco encontrado na workspace\"],\n };\n }\n\n if (topBlocks.length === 1) {\n return {\n isValid: true,\n warnings: [],\n mainBlock: topBlocks[0],\n reEnableBlocks: null, // Não há blocos para reabilitar\n };\n }\n\n warnings.push(\n `Encontrados ${topBlocks.length} programas separados. Apenas um será executado.`,\n );\n\n if (!this.options.allowMultipleTopBlocks) {\n const result = this.handleMultipleTopBlocks(topBlocks, warnings);\n return result;\n }\n\n return {\n isValid: true,\n warnings,\n mainBlock: topBlocks[0],\n reEnableBlocks: null,\n };\n }\n\n /**\n * Verifica se um bloco tem o método setDisabledReason\n * @param {Object} block - Bloco do Blockly\n * @returns {boolean} Se o bloco é válido para controle\n */\n isBlockControllable(block) {\n return (\n block && typeof block.setDisabledReason === \"function\" && !block.disposed\n );\n }\n\n /**\n * Lida com múltiplos blocos top-level\n * @param {Array} topBlocks - Array de blocos top-level\n * @param {Array} warnings - Array de warnings acumulados\n * @returns {Object} Resultado do processamento\n */\n handleMultipleTopBlocks(topBlocks, warnings) {\n const mainBlock = this.selectMainBlock(topBlocks);\n\n if (mainBlock) {\n warnings.push(`Programa principal selecionado: \"${mainBlock.type}\"`);\n }\n\n // Desabilitar todos os outros blocos top-level (diferentes do mainBlock)\n const disabledBlocks = [];\n const reEnableCallbacks = [];\n\n topBlocks.forEach((block, index) => {\n // Pular o bloco principal\n if (block === mainBlock) {\n return;\n }\n\n if (!this.isBlockControllable(block)) {\n return;\n }\n\n try {\n block.setDisabledReason(true, \"MANUALLY_DISABLED\");\n disabledBlocks.push(block);\n warnings.push(`Programa \"${block.type}\" desabilitado temporariamente`);\n\n reEnableCallbacks.push(() => {\n try {\n if (this.isBlockControllable(block)) {\n block.setDisabledReason(false);\n }\n } catch (error) {\n // Falhou ao reabilitar, mas não precisamos fazer nada\n }\n });\n } catch (error) {\n // Falhou ao desabilitar, mas não precisamos fazer nada\n }\n });\n\n const reEnableAllBlocks = () => {\n if (reEnableCallbacks.length > 0) {\n reEnableCallbacks.forEach((callback) => callback());\n }\n };\n\n return {\n isValid: true,\n warnings,\n mainBlock,\n disabledBlocks,\n disabledCount: disabledBlocks.length,\n reEnableBlocks: disabledBlocks.length > 0 ? reEnableAllBlocks : null,\n };\n }\n\n /**\n * Seleciona o bloco principal entre múltiplos blocos top-level\n * @param {Array} topBlocks - Array de blocos top-level\n * @returns {Object} Bloco principal selecionado\n */\n selectMainBlock(topBlocks) {\n const validBlocks = topBlocks.filter((block) => block && !block.disposed);\n\n if (validBlocks.length === 0) {\n return null;\n }\n\n for (const preferredType of this.options.preferredStartBlocks) {\n const startBlock = validBlocks.find(\n (block) =>\n block.type === preferredType || block.type.includes(preferredType),\n );\n if (startBlock) {\n return startBlock;\n }\n }\n\n return validBlocks[0];\n }\n}\n\nexport const defaultValidator = new BlocklyWorkspaceValidator({\n allowMultipleTopBlocks: false,\n preferredStartBlocks: [\"start\", \"when_run\", \"main\", \"begin\"],\n});\n\n/**\n * Função de conveniência para validação rápida\n * @param {Blockly.Workspace} workspace - A workspace do Blockly\n * @param {Object} options - Opções de validação\n * @returns {Object} Resultado da validação\n */\nexport function validateBlocklyWorkspace(workspace, options = {}) {\n const validator = new BlocklyWorkspaceValidator(options);\n return validator.validateAndClean(workspace);\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/drawAxes.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/fieldAngleLoader.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/gameEvents.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/phaserResize.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/src/utils/tourHelpers.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/tailwind.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/rui/src/new/plataforma-edu/app/vite.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]
diff --git a/app/eslint.config.js b/app/eslint.config.js
new file mode 100644
index 0000000..8ce8608
--- /dev/null
+++ b/app/eslint.config.js
@@ -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 }
+ ],
+ },
+ },
+]);
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..7869230
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Decoda
+
+
+
+
+
+
diff --git a/app/licenses/APACHE-2.0.txt b/app/licenses/APACHE-2.0.txt
new file mode 100644
index 0000000..9f7ab76
--- /dev/null
+++ b/app/licenses/APACHE-2.0.txt
@@ -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.
diff --git a/app/licenses/README.md b/app/licenses/README.md
new file mode 100644
index 0000000..55bf1de
--- /dev/null
+++ b/app/licenses/README.md
@@ -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`.
diff --git a/app/licenses/js-interpreter-LICENSE.txt b/app/licenses/js-interpreter-LICENSE.txt
new file mode 100644
index 0000000..9f7ab76
--- /dev/null
+++ b/app/licenses/js-interpreter-LICENSE.txt
@@ -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.
diff --git a/app/main.cjs b/app/main.cjs
new file mode 100644
index 0000000..6a2c402
--- /dev/null
+++ b/app/main.cjs
@@ -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();
+ }
+});
\ No newline at end of file
diff --git a/app/nginx-spa.conf b/app/nginx-spa.conf
new file mode 100644
index 0000000..7b7cb6c
--- /dev/null
+++ b/app/nginx-spa.conf
@@ -0,0 +1,9 @@
+server {
+ listen 80;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
\ No newline at end of file
diff --git a/app/package.json b/app/package.json
new file mode 100644
index 0000000..7a27d14
--- /dev/null
+++ b/app/package.json
@@ -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"
+ }
+}
diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml
new file mode 100644
index 0000000..e42c896
--- /dev/null
+++ b/app/pnpm-lock.yaml
@@ -0,0 +1,10176 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@blockly/field-angle':
+ specifier: ^6.0.3
+ version: 6.0.3(blockly@12.3.1)
+ '@blockly/theme-modern':
+ specifier: ^7.0.1
+ version: 7.0.1(blockly@12.3.1)
+ '@codemirror/autocomplete':
+ specifier: 6.20.1
+ version: 6.20.1
+ '@codemirror/commands':
+ specifier: 6.10.3
+ version: 6.10.3
+ '@codemirror/lang-javascript':
+ specifier: 6.2.5
+ version: 6.2.5
+ '@codemirror/language':
+ specifier: 6.12.3
+ version: 6.12.3
+ '@codemirror/state':
+ specifier: 6.6.0
+ version: 6.6.0
+ '@codemirror/view':
+ specifier: 6.41.0
+ version: 6.41.0
+ '@fortawesome/fontawesome-free':
+ specifier: ^6.5.2
+ version: 6.7.2
+ '@tsparticles/confetti':
+ specifier: ^3.9.1
+ version: 3.9.1
+ '@uiw/react-codemirror':
+ specifier: 4.25.9
+ version: 4.25.9(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.41.0)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ acorn:
+ specifier: ^8.15.0
+ version: 8.15.0
+ blockly:
+ specifier: ^12.3.1
+ version: 12.3.1
+ js-interpreter:
+ specifier: ^6.0.1
+ version: 6.0.1
+ lodash.debounce:
+ specifier: ^4.0.8
+ version: 4.0.8
+ lucide:
+ specifier: ^0.546.0
+ version: 0.546.0
+ lucide-react:
+ specifier: ^0.546.0
+ version: 0.546.0(react@19.2.0)
+ phaser:
+ specifier: ^3.90.0
+ version: 3.90.0
+ phaser3-rex-plugins:
+ specifier: ^1.80.16
+ version: 1.80.16(graphology-types@0.24.8)
+ prop-types:
+ specifier: ^15.8.1
+ version: 15.8.1
+ react:
+ specifier: ^19.2.0
+ version: 19.2.0
+ react-confetti:
+ specifier: ^6.4.0
+ version: 6.4.0(react@19.2.0)
+ react-dom:
+ specifier: ^19.2.0
+ version: 19.2.0(react@19.2.0)
+ react-resizable-panels:
+ specifier: ^3.0.6
+ version: 3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react-router-dom:
+ specifier: ^7.9.4
+ version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ react-shepherd:
+ specifier: ^6.1.9
+ version: 6.1.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
+ sentiment:
+ specifier: ^5.0.2
+ version: 5.0.2
+ shepherd.js:
+ specifier: ^14.5.1
+ version: 14.5.1
+ svgo:
+ specifier: ^4.0.0
+ version: 4.0.0
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.38.0
+ version: 9.38.0
+ '@tailwindcss/typography':
+ specifier: ^0.5.19
+ version: 0.5.19(tailwindcss@3.4.18(yaml@2.8.0))
+ '@testing-library/jest-dom':
+ specifier: ^6.9.1
+ version: 6.9.1
+ '@testing-library/react':
+ specifier: ^16.3.1
+ version: 16.3.1(@testing-library/dom@10.4.1)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@testing-library/user-event':
+ specifier: ^14.6.1
+ version: 14.6.1(@testing-library/dom@10.4.1)
+ '@types/react':
+ specifier: ^19.2.2
+ version: 19.2.2
+ '@types/react-dom':
+ specifier: ^19.2.2
+ version: 19.2.2(@types/react@19.2.2)
+ '@vitejs/plugin-react':
+ specifier: ^5.0.4
+ version: 5.0.4(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))
+ '@vitest/coverage-v8':
+ specifier: ^4.0.18
+ version: 4.0.18(vitest@4.0.16)
+ '@vitest/ui':
+ specifier: 4.0.16
+ version: 4.0.16(vitest@4.0.16)
+ autoprefixer:
+ specifier: ^10.4.21
+ version: 10.4.21(postcss@8.5.6)
+ baseline-browser-mapping:
+ specifier: ^2.9.11
+ version: 2.10.14
+ concurrently:
+ specifier: ^9.2.1
+ version: 9.2.1
+ cpr:
+ specifier: ^3.0.1
+ version: 3.0.1
+ electron:
+ specifier: ^42.0.1
+ version: 42.0.1
+ electron-builder:
+ specifier: ^26.8.1
+ version: 26.8.1(electron-builder-squirrel-windows@26.8.1)
+ electron-is-dev:
+ specifier: ^3.0.1
+ version: 3.0.1
+ eslint:
+ specifier: ^9.38.0
+ version: 9.38.0(jiti@1.21.7)
+ eslint-plugin-react-hooks:
+ specifier: ^7.0.0
+ version: 7.0.0(eslint@9.38.0(jiti@1.21.7))
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.24
+ version: 0.4.24(eslint@9.38.0(jiti@1.21.7))
+ fs-extra:
+ specifier: ^11.3.4
+ version: 11.3.4
+ globals:
+ specifier: ^16.4.0
+ version: 16.4.0
+ jsdom:
+ specifier: ^27.4.0
+ version: 27.4.0
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.6
+ prettier:
+ specifier: ^3.8.1
+ version: 3.8.1
+ rollup-plugin-visualizer:
+ specifier: ^6.0.5
+ version: 6.0.5(rollup@4.60.3)
+ serve:
+ specifier: ^14.2.5
+ version: 14.2.5
+ sharp:
+ specifier: ^0.34.4
+ version: 0.34.4
+ tailwindcss:
+ specifier: ^3.4.18
+ version: 3.4.18(yaml@2.8.0)
+ vite:
+ specifier: ^7.1.10
+ version: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+ vite-plugin-image-optimizer:
+ specifier: ^2.0.2
+ version: 2.0.2(sharp@0.34.4)(svgo@4.0.0)(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))
+ vite-plugin-pwa:
+ specifier: ^0.20.1
+ version: 0.20.5(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))(workbox-build@7.4.1(@types/babel__core@7.20.5))(workbox-window@7.4.1)
+ vitest:
+ specifier: ^4.0.16
+ version: 4.0.16(@types/node@24.9.0)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+
+packages:
+
+ 7zip-bin@5.2.0:
+ resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==}
+
+ '@acemir/cssom@0.9.30':
+ resolution: {integrity: sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==}
+
+ '@adobe/css-tools@4.4.4':
+ resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@apideck/better-ajv-errors@0.3.7':
+ resolution: {integrity: sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ ajv: '>=8'
+
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
+ '@asamuzakjp/css-color@4.1.1':
+ resolution: {integrity: sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==}
+
+ '@asamuzakjp/dom-selector@6.7.6':
+ resolution: {integrity: sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==}
+
+ '@asamuzakjp/nwsapi@2.3.9':
+ resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.4':
+ resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.3':
+ resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.4':
+ resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.3':
+ resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-create-class-features-plugin@7.29.3':
+ resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-create-regexp-features-plugin@7.28.5':
+ resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-define-polyfill-provider@0.6.8':
+ resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-remap-async-to-generator@7.27.1':
+ resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-replace-supers@7.28.6':
+ resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.27.1':
+ resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-wrap-function@7.28.6':
+ resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.4':
+ resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/parser@7.29.0':
+ resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5':
+ resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1':
+ resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1':
+ resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3':
+ resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1':
+ resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.13.0
+
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6':
+ resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
+ resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-assertions@7.28.6':
+ resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-attributes@7.28.6':
+ resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6':
+ resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-arrow-functions@7.27.1':
+ resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-async-generator-functions@7.29.0':
+ resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-async-to-generator@7.28.6':
+ resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-block-scoped-functions@7.27.1':
+ resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-block-scoping@7.28.6':
+ resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-class-properties@7.28.6':
+ resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-class-static-block@7.28.6':
+ resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.12.0
+
+ '@babel/plugin-transform-classes@7.28.6':
+ resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-computed-properties@7.28.6':
+ resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-destructuring@7.28.5':
+ resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-dotall-regex@7.28.6':
+ resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-duplicate-keys@7.27.1':
+ resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0':
+ resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-dynamic-import@7.27.1':
+ resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-explicit-resource-management@7.28.6':
+ resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-exponentiation-operator@7.28.6':
+ resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-export-namespace-from@7.27.1':
+ resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-for-of@7.27.1':
+ resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-function-name@7.27.1':
+ resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-json-strings@7.28.6':
+ resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-literals@7.27.1':
+ resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-logical-assignment-operators@7.28.6':
+ resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-member-expression-literals@7.27.1':
+ resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-amd@7.27.1':
+ resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-commonjs@7.28.6':
+ resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-systemjs@7.29.4':
+ resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-modules-umd@7.27.1':
+ resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-named-capturing-groups-regex@7.29.0':
+ resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-new-target@7.27.1':
+ resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-nullish-coalescing-operator@7.28.6':
+ resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-numeric-separator@7.28.6':
+ resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-object-rest-spread@7.28.6':
+ resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-object-super@7.27.1':
+ resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-optional-catch-binding@7.28.6':
+ resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-optional-chaining@7.28.6':
+ resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-parameters@7.27.7':
+ resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-private-methods@7.28.6':
+ resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-private-property-in-object@7.28.6':
+ resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-property-literals@7.27.1':
+ resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-regenerator@7.29.0':
+ resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-regexp-modifiers@7.28.6':
+ resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/plugin-transform-reserved-words@7.27.1':
+ resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-shorthand-properties@7.27.1':
+ resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-spread@7.28.6':
+ resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-sticky-regex@7.27.1':
+ resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-template-literals@7.27.1':
+ resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-typeof-symbol@7.27.1':
+ resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-escapes@7.27.1':
+ resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-property-regex@7.28.6':
+ resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-regex@7.27.1':
+ resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-unicode-sets-regex@7.28.6':
+ resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/preset-env@7.29.5':
+ resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/preset-modules@0.1.6-no-external-plugins':
+ resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
+
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.4':
+ resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.4':
+ resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.0':
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+
+ '@bcoe/v8-coverage@1.0.2':
+ resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
+ engines: {node: '>=18'}
+
+ '@blockly/field-angle@6.0.3':
+ resolution: {integrity: sha512-LFzeSKTu3is6ofqI1UZ9iFBl9RmtyojhvJHjy1hgDMmXvtvsNnBgIlpEGC6KVAMhCAotl+d83rH8OVTK9ll0AA==}
+ engines: {node: '>=8.0.0'}
+ peerDependencies:
+ blockly: ^12.0.0
+
+ '@blockly/theme-modern@7.0.1':
+ resolution: {integrity: sha512-aMI3OBp8KCbLU1O14FLUlocK7IeMOyiSenlTJ4lwGcBmZntM2OIcx6o89oAIeq6HkmaH7vMlK+/AgqdB3k0y3A==}
+ engines: {node: '>=8.17.0'}
+ peerDependencies:
+ blockly: ^12.0.0
+
+ '@codemirror/autocomplete@6.20.1':
+ resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
+
+ '@codemirror/commands@6.10.3':
+ resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
+
+ '@codemirror/lang-javascript@6.2.5':
+ resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
+
+ '@codemirror/language@6.12.3':
+ resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==}
+
+ '@codemirror/lint@6.9.0':
+ resolution: {integrity: sha512-wZxW+9XDytH3SKvS8cQzMyQCaaazH8XL1EMHleHe00wVzsv7NBQKVW2yzEHrRhmM7ZOhVdItPbvlRBvMp9ej7A==}
+
+ '@codemirror/search@6.5.11':
+ resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==}
+
+ '@codemirror/state@6.6.0':
+ resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
+
+ '@codemirror/theme-one-dark@6.1.3':
+ resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
+
+ '@codemirror/view@6.41.0':
+ resolution: {integrity: sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==}
+
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-syntax-patches-for-csstree@1.0.22':
+ resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@develar/schema-utils@2.6.5':
+ resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==}
+ engines: {node: '>= 8.9.0'}
+
+ '@electron/asar@3.4.1':
+ resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==}
+ engines: {node: '>=10.12.0'}
+ hasBin: true
+
+ '@electron/fuses@1.8.0':
+ resolution: {integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==}
+ hasBin: true
+
+ '@electron/get@3.1.0':
+ resolution: {integrity: sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==}
+ engines: {node: '>=14'}
+
+ '@electron/get@5.0.0':
+ resolution: {integrity: sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==}
+ engines: {node: '>=22.12.0'}
+
+ '@electron/notarize@2.5.0':
+ resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==}
+ engines: {node: '>= 10.0.0'}
+
+ '@electron/osx-sign@1.3.3':
+ resolution: {integrity: sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==}
+ engines: {node: '>=12.0.0'}
+ hasBin: true
+
+ '@electron/rebuild@4.0.4':
+ resolution: {integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==}
+ engines: {node: '>=22.12.0'}
+ hasBin: true
+
+ '@electron/universal@2.0.3':
+ resolution: {integrity: sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==}
+ engines: {node: '>=16.4'}
+
+ '@electron/windows-sign@1.2.2':
+ resolution: {integrity: sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==}
+ engines: {node: '>=14.14'}
+ hasBin: true
+
+ '@emnapi/runtime@1.5.0':
+ resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
+
+ '@esbuild/aix-ppc64@0.25.11':
+ resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.11':
+ resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.11':
+ resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.11':
+ resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.11':
+ resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.11':
+ resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.11':
+ resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.11':
+ resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.11':
+ resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.11':
+ resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.11':
+ resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.11':
+ resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.11':
+ resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.11':
+ resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.11':
+ resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.11':
+ resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.11':
+ resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.11':
+ resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.11':
+ resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.11':
+ resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.11':
+ resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.11':
+ resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.11':
+ resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.11':
+ resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.11':
+ resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.11':
+ resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.9.0':
+ resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.1':
+ resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.1':
+ resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.1':
+ resolution: {integrity: sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.16.0':
+ resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.1':
+ resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.38.0':
+ resolution: {integrity: sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.0':
+ resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@exodus/bytes@1.8.0':
+ resolution: {integrity: sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ '@exodus/crypto': ^1.0.0-rc.4
+ peerDependenciesMeta:
+ '@exodus/crypto':
+ optional: true
+
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
+ '@fortawesome/fontawesome-free@6.7.2':
+ resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==}
+ engines: {node: '>=6'}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.4':
+ resolution: {integrity: sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.4':
+ resolution: {integrity: sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
+ resolution: {integrity: sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.3':
+ resolution: {integrity: sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.3':
+ resolution: {integrity: sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.3':
+ resolution: {integrity: sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
+ resolution: {integrity: sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.3':
+ resolution: {integrity: sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.3':
+ resolution: {integrity: sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
+ resolution: {integrity: sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
+ resolution: {integrity: sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.4':
+ resolution: {integrity: sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.4':
+ resolution: {integrity: sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.4':
+ resolution: {integrity: sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.4':
+ resolution: {integrity: sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.4':
+ resolution: {integrity: sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.4':
+ resolution: {integrity: sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.4':
+ resolution: {integrity: sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.4':
+ resolution: {integrity: sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.4':
+ resolution: {integrity: sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.4':
+ resolution: {integrity: sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.4':
+ resolution: {integrity: sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@isaacs/cliui@9.0.0':
+ resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
+ engines: {node: '>=18'}
+
+ '@isaacs/fs-minipass@4.0.1':
+ resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
+ engines: {node: '>=18.0.0'}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/source-map@0.3.11':
+ resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@lezer/common@1.5.1':
+ resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
+
+ '@lezer/highlight@1.2.3':
+ resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
+
+ '@lezer/javascript@1.5.4':
+ resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
+
+ '@lezer/lr@1.4.8':
+ resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
+
+ '@malept/cross-spawn-promise@2.0.0':
+ resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==}
+ engines: {node: '>= 12.13.0'}
+
+ '@malept/flatpak-bundler@0.4.0':
+ resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==}
+ engines: {node: '>= 10.0.0'}
+
+ '@marijn/find-cluster-break@1.0.2':
+ resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
+ '@rolldown/pluginutils@1.0.0-beta.38':
+ resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==}
+
+ '@rollup/plugin-babel@6.1.0':
+ resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ '@types/babel__core': ^7.1.9
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ '@types/babel__core':
+ optional: true
+ rollup:
+ optional: true
+
+ '@rollup/plugin-node-resolve@16.0.3':
+ resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^2.78.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-replace@6.0.3':
+ resolution: {integrity: sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/plugin-terser@1.0.0':
+ resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ rollup: ^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/pluginutils@5.3.0':
+ resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.52.5':
+ resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm-eabi@4.60.3':
+ resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.52.5':
+ resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.60.3':
+ resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.52.5':
+ resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-arm64@4.60.3':
+ resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.52.5':
+ resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.60.3':
+ resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.52.5':
+ resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-arm64@4.60.3':
+ resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.52.5':
+ resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.60.3':
+ resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+ resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.3':
+ resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+ resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.3':
+ resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
+ resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.3':
+ resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
+ resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.60.3':
+ resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
+ resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.3':
+ resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.60.3':
+ resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+ resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.3':
+ resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.3':
+ resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+ resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.3':
+ resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
+ resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.3':
+ resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
+ resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.3':
+ resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.60.3':
+ resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.52.5':
+ resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.60.3':
+ resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.60.3':
+ resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.52.5':
+ resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-openharmony-arm64@4.60.3':
+ resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
+ resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.3':
+ resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
+ resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.3':
+ resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
+ resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.60.3':
+ resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
+ resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.60.3':
+ resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@scarf/scarf@1.4.0':
+ resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==}
+
+ '@sindresorhus/is@4.6.0':
+ resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
+ engines: {node: '>=10'}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@szmarczak/http-timer@4.0.6':
+ resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
+ engines: {node: '>=10'}
+
+ '@tailwindcss/typography@0.5.19':
+ resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@testing-library/dom@10.4.1':
+ resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
+ engines: {node: '>=18'}
+
+ '@testing-library/jest-dom@6.9.1':
+ resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
+ engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+ '@testing-library/react@16.3.1':
+ resolution: {integrity: sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@testing-library/dom': ^10.0.0
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@testing-library/user-event@14.6.1':
+ resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==}
+ engines: {node: '>=12', npm: '>=6'}
+ peerDependencies:
+ '@testing-library/dom': '>=7.21.4'
+
+ '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1':
+ resolution: {integrity: sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==}
+ engines: {node: '>=12'}
+
+ '@tsparticles/basic@3.9.1':
+ resolution: {integrity: sha512-ijr2dHMx0IQHqhKW3qA8tfwrR2XYbbWYdaJMQuBo2CkwBVIhZ76U+H20Y492j/NXpd1FUnt2aC0l4CEVGVGdeQ==}
+
+ '@tsparticles/confetti@3.9.1':
+ resolution: {integrity: sha512-S0Q6iBqQvcCaOzmnddmh41RHeLwzSdkLq8hU3Ryokmb9eqoS9MQdYz2tjUHHdTap+YLPlp64SZ15sC4C9ulFbA==}
+
+ '@tsparticles/engine@3.9.1':
+ resolution: {integrity: sha512-DpdgAhWMZ3Eh2gyxik8FXS6BKZ8vyea+Eu5BC4epsahqTGY9V3JGGJcXC6lRJx6cPMAx1A0FaQAojPF3v6rkmQ==}
+
+ '@tsparticles/move-base@3.9.1':
+ resolution: {integrity: sha512-X4huBS27d8srpxwOxliWPUt+NtCwY+8q/cx1DvQxyqmTA8VFCGpcHNwtqiN+9JicgzOvSuaORVqUgwlsc7h4pQ==}
+
+ '@tsparticles/plugin-emitters@3.9.1':
+ resolution: {integrity: sha512-h7opR8SoFWBmVHceDLJUerLENaPfkJSh2zQYvzmLj2L+V3VLS1QDgty+4QZVeZfqNROmgQw2eLFA5El1E0sqqw==}
+
+ '@tsparticles/plugin-hex-color@3.9.1':
+ resolution: {integrity: sha512-vZgZ12AjUicJvk7AX4K2eAmKEQX/D1VEjEPFhyjbgI7A65eX72M465vVKIgNA6QArLZ1DLs7Z787LOE6GOBWsg==}
+
+ '@tsparticles/plugin-hsl-color@3.9.1':
+ resolution: {integrity: sha512-jJd1iGgRwX6eeNjc1zUXiJivaqC5UE+SC2A3/NtHwwoQrkfxGWmRHOsVyLnOBRcCPgBp/FpdDe6DIDjCMO715w==}
+
+ '@tsparticles/plugin-motion@3.9.1':
+ resolution: {integrity: sha512-I/356NHCiMUgFzWjAHYKO7YvBqKtHSktIPgTRruqlruyrAcwzjkT55ZQ1K5EcJLWETkF1bfG2VpJBRu8ksf9mw==}
+
+ '@tsparticles/plugin-rgb-color@3.9.1':
+ resolution: {integrity: sha512-SBxk7f1KBfXeTnnklbE2Hx4jBgh6I6HOtxb+Os1gTp0oaghZOkWcCD2dP4QbUu7fVNCMOcApPoMNC8RTFcy9wQ==}
+
+ '@tsparticles/shape-cards@3.9.1':
+ resolution: {integrity: sha512-/tQtGh6xC3UmKU2WO7VM5RoAnsvFvPkXcCJJHAQ6AIyWUKVWBrVuewF0ZbJQlNhWCEW/aqE199LuDAewqYAQ5A==}
+
+ '@tsparticles/shape-circle@3.9.1':
+ resolution: {integrity: sha512-DqZFLjbuhVn99WJ+A9ajz9YON72RtCcvubzq6qfjFmtwAK7frvQeb6iDTp6Ze9FUipluxVZWVRG4vWTxi2B+/g==}
+
+ '@tsparticles/shape-emoji@3.9.1':
+ resolution: {integrity: sha512-ifvY63usuT+hipgVHb8gelBHSeF6ryPnMxAAEC1RGHhhXfpSRWMtE6ybr+pSsYU52M3G9+TF84v91pSwNrb9ZQ==}
+
+ '@tsparticles/shape-heart@3.9.1':
+ resolution: {integrity: sha512-h1aYiBVCUAJ14zyK792EuX0332Hus6OgYy/4dk6PhfgdFTQaHk+FzGJjw+jEB8vpxOYtWeysT9uoofPZeDrqBQ==}
+
+ '@tsparticles/shape-image@3.9.1':
+ resolution: {integrity: sha512-fCA5eme8VF3oX8yNVUA0l2SLDKuiZObkijb0z3Ky0qj1HUEVlAuEMhhNDNB9E2iELTrWEix9z7BFMePp2CC7AA==}
+
+ '@tsparticles/shape-polygon@3.9.1':
+ resolution: {integrity: sha512-dA77PgZdoLwxnliH6XQM/zF0r4jhT01pw5y7XTeTqws++hg4rTLV9255k6R6eUqKq0FPSW1/WBsBIl7q/MmrqQ==}
+
+ '@tsparticles/shape-square@3.9.1':
+ resolution: {integrity: sha512-DKGkDnRyZrAm7T2ipqNezJahSWs6xd9O5LQLe5vjrYm1qGwrFxJiQaAdlb00UNrexz1/SA7bEoIg4XKaFa7qhQ==}
+
+ '@tsparticles/shape-star@3.9.1':
+ resolution: {integrity: sha512-kdMJpi8cdeb6vGrZVSxTG0JIjCwIenggqk0EYeKAwtOGZFBgL7eHhF2F6uu1oq8cJAbXPujEoabnLsz6mW8XaA==}
+
+ '@tsparticles/updater-color@3.9.1':
+ resolution: {integrity: sha512-XGWdscrgEMA8L5E7exsE0f8/2zHKIqnTrZymcyuFBw2DCB6BIV+5z6qaNStpxrhq3DbIxxhqqcybqeOo7+Alpg==}
+
+ '@tsparticles/updater-life@3.9.1':
+ resolution: {integrity: sha512-Oi8aF2RIwMMsjssUkCB6t3PRpENHjdZf6cX92WNfAuqXtQphr3OMAkYFJFWkvyPFK22AVy3p/cFt6KE5zXxwAA==}
+
+ '@tsparticles/updater-opacity@3.9.1':
+ resolution: {integrity: sha512-w778LQuRZJ+IoWzeRdrGykPYSSaTeWfBvLZ2XwYEkh/Ss961InOxZKIpcS6i5Kp/Zfw0fS1ZAuqeHwuj///Osw==}
+
+ '@tsparticles/updater-out-modes@3.9.1':
+ resolution: {integrity: sha512-cKQEkAwbru+hhKF+GTsfbOvuBbx2DSB25CxOdhtW2wRvDBoCnngNdLw91rs+0Cex4tgEeibkebrIKFDDE6kELg==}
+
+ '@tsparticles/updater-roll@3.9.1':
+ resolution: {integrity: sha512-zl4JeM3gUBJ0uttmIsond3lrZ3f3AkItFeS0Lhj/7jiCKfUoRyyOMrcBk8R1AhW7lI+7ko1iBs3jhO0jnxz9vg==}
+
+ '@tsparticles/updater-rotate@3.9.1':
+ resolution: {integrity: sha512-9BfKaGfp28JN82MF2qs6Ae/lJr9EColMfMTHqSKljblwbpVDHte4umuwKl3VjbRt87WD9MGtla66NTUYl+WxuQ==}
+
+ '@tsparticles/updater-size@3.9.1':
+ resolution: {integrity: sha512-3NSVs0O2ApNKZXfd+y/zNhTXSFeG1Pw4peI8e6z/q5+XLbmue9oiEwoPy/tQLaark3oNj3JU7Q903ZijPyXSzw==}
+
+ '@tsparticles/updater-tilt@3.9.1':
+ resolution: {integrity: sha512-PB2yaoyXRmSk4iIVgjtRrzOxXMK9mjeAQHIJGtT4faq46Z8cbIIEFgjTwqrUV8qOrNg/h4sm5NE/s0qsTYjp1Q==}
+
+ '@tsparticles/updater-wobble@3.9.1':
+ resolution: {integrity: sha512-c99Ogy9q4QWO+zsDXol0UnpUwZiY2UucFb8ltuDv9AlbGUeprygoub8jhgT5pEDv+GdzWOJGSgq7rfgv9cHBrg==}
+
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/cacheable-request@6.0.3':
+ resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/debug@4.1.13':
+ resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/fs-extra@9.0.13':
+ resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
+
+ '@types/http-cache-semantics@4.2.0':
+ resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/keyv@3.1.4':
+ resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
+
+ '@types/ms@2.1.0':
+ resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+
+ '@types/node@24.9.0':
+ resolution: {integrity: sha512-MKNwXh3seSK8WurXF7erHPJ2AONmMwkI7zAMrXZDPIru8jRqkk6rGDBVbw4mLwfqA+ZZliiDPg05JQ3uW66tKQ==}
+
+ '@types/plist@3.0.5':
+ resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==}
+
+ '@types/react-dom@19.2.2':
+ resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.2':
+ resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
+
+ '@types/resolve@1.20.2':
+ resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
+
+ '@types/responselike@1.0.3':
+ resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==}
+
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
+ '@types/verror@1.10.11':
+ resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==}
+
+ '@types/yauzl@2.10.3':
+ resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
+
+ '@uiw/codemirror-extensions-basic-setup@4.25.9':
+ resolution: {integrity: sha512-QFAqr+pu6lDmNpAlecODcF49TlsrZ0bj15zPzfhiqSDl+Um3EsDLFLppixC7kFLn+rdDM2LTvVjn5CPvefpRgw==}
+ peerDependencies:
+ '@codemirror/autocomplete': '>=6.0.0'
+ '@codemirror/commands': '>=6.0.0'
+ '@codemirror/language': '>=6.0.0'
+ '@codemirror/lint': '>=6.0.0'
+ '@codemirror/search': '>=6.0.0'
+ '@codemirror/state': '>=6.0.0'
+ '@codemirror/view': '>=6.0.0'
+
+ '@uiw/react-codemirror@4.25.9':
+ resolution: {integrity: sha512-HftqCBUYShAOH0pGi1CHP8vfm5L8fQ3+0j0VI6lQD6QpK+UBu3J7nxfEN5O/BXMilMNf9ZyFJRvRcuMMOLHMng==}
+ peerDependencies:
+ '@babel/runtime': '>=7.11.0'
+ '@codemirror/state': '>=6.0.0'
+ '@codemirror/theme-one-dark': '>=6.0.0'
+ '@codemirror/view': '>=6.0.0'
+ codemirror: '>=6.0.0'
+ react: '>=17.0.0'
+ react-dom: '>=17.0.0'
+
+ '@vitejs/plugin-react@5.0.4':
+ resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ '@vitest/coverage-v8@4.0.18':
+ resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==}
+ peerDependencies:
+ '@vitest/browser': 4.0.18
+ vitest: 4.0.18
+ peerDependenciesMeta:
+ '@vitest/browser':
+ optional: true
+
+ '@vitest/expect@4.0.16':
+ resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==}
+
+ '@vitest/mocker@4.0.16':
+ resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.0.16':
+ resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==}
+
+ '@vitest/pretty-format@4.0.18':
+ resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
+
+ '@vitest/runner@4.0.16':
+ resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==}
+
+ '@vitest/snapshot@4.0.16':
+ resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==}
+
+ '@vitest/spy@4.0.16':
+ resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==}
+
+ '@vitest/ui@4.0.16':
+ resolution: {integrity: sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==}
+ peerDependencies:
+ vitest: 4.0.16
+
+ '@vitest/utils@4.0.16':
+ resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==}
+
+ '@vitest/utils@4.0.18':
+ resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
+
+ '@xmldom/xmldom@0.8.13':
+ resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
+ engines: {node: '>=10.0.0'}
+
+ '@xmldom/xmldom@0.9.10':
+ resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==}
+ engines: {node: '>=14.6'}
+
+ '@zeit/schemas@2.36.0':
+ resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==}
+
+ abbrev@4.0.0:
+ resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv-keywords@3.5.2:
+ resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
+ peerDependencies:
+ ajv: ^6.9.1
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ajv@8.12.0:
+ resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+
+ ansi-align@3.0.1:
+ resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ app-builder-bin@5.0.0-alpha.12:
+ resolution: {integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==}
+
+ app-builder-lib@26.8.1:
+ resolution: {integrity: sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ dmg-builder: 26.8.1
+ electron-builder-squirrel-windows: 26.8.1
+
+ arch@2.2.0:
+ resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ assert-plus@1.0.0:
+ resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
+ engines: {node: '>=0.8'}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ ast-v8-to-istanbul@0.3.11:
+ resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==}
+
+ astral-regex@2.0.0:
+ resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
+ engines: {node: '>=8'}
+
+ async-exit-hook@2.0.1:
+ resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==}
+ engines: {node: '>=0.12.0'}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ at-least-node@1.0.0:
+ resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
+ engines: {node: '>= 4.0.0'}
+
+ autoprefixer@10.4.21:
+ resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ babel-plugin-polyfill-corejs2@0.4.17:
+ resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-corejs3@0.14.2:
+ resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ babel-plugin-polyfill-regenerator@0.6.8:
+ resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==}
+ peerDependencies:
+ '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ baseline-browser-mapping@2.10.14:
+ resolution: {integrity: sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ bidi-js@1.0.3:
+ resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
+ blockly@12.3.1:
+ resolution: {integrity: sha512-BbWUcpqroY241XgSxTuAiEMHeIZ6u3+oD2zOATf3Fi+0NMWJ/MdMtuSkOcDCSk6Nc7WR3z5n9GHKqz2L+3kQOQ==}
+ engines: {node: '>=18'}
+
+ boolbase@1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+ boolean@3.2.0:
+ resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
+ deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
+
+ boxen@7.0.0:
+ resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
+ engines: {node: '>=14.16'}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ brace-expansion@5.0.6:
+ resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
+ engines: {node: 18 || 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.26.3:
+ resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ browserslist@4.28.2:
+ resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer-crc32@0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ buffer@5.7.1:
+ resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+
+ builder-util-runtime@9.5.1:
+ resolution: {integrity: sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==}
+ engines: {node: '>=12.0.0'}
+
+ builder-util@26.8.1:
+ resolution: {integrity: sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==}
+
+ bytes@3.0.0:
+ resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
+ engines: {node: '>= 0.8'}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ cacheable-lookup@5.0.4:
+ resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
+ engines: {node: '>=10.6.0'}
+
+ cacheable-request@7.0.4:
+ resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==}
+ engines: {node: '>=8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.9:
+ resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
+ camelcase@7.0.1:
+ resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
+ engines: {node: '>=14.16'}
+
+ caniuse-lite@1.0.30001751:
+ resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==}
+
+ caniuse-lite@1.0.30001792:
+ resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==}
+
+ chai@6.2.2:
+ resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+ engines: {node: '>=18'}
+
+ chalk-template@0.4.0:
+ resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
+ engines: {node: '>=12'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chalk@5.0.1:
+ resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ chownr@3.0.0:
+ resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
+ engines: {node: '>=18'}
+
+ chromium-pickle-js@0.2.0:
+ resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==}
+
+ ci-info@4.3.1:
+ resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==}
+ engines: {node: '>=8'}
+
+ ci-info@4.4.0:
+ resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
+ engines: {node: '>=8'}
+
+ cli-boxes@3.0.0:
+ resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
+ engines: {node: '>=10'}
+
+ cli-truncate@2.1.0:
+ resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==}
+ engines: {node: '>=8'}
+
+ clipboardy@3.0.0:
+ resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clone-response@1.0.3:
+ resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
+
+ codemirror@6.0.2:
+ resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@11.1.0:
+ resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
+ engines: {node: '>=16'}
+
+ commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ commander@5.1.0:
+ resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
+ engines: {node: '>= 6'}
+
+ commander@9.5.0:
+ resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
+ engines: {node: ^12.20.0 || >=14}
+
+ common-tags@1.8.2:
+ resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
+ engines: {node: '>=4.0.0'}
+
+ compare-version@0.1.2:
+ resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==}
+ engines: {node: '>=0.10.0'}
+
+ compressible@2.0.18:
+ resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
+ engines: {node: '>= 0.6'}
+
+ compression@1.8.1:
+ resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==}
+ engines: {node: '>= 0.8.0'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ concurrently@9.2.1:
+ resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ content-disposition@0.5.2:
+ resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==}
+ engines: {node: '>= 0.6'}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie@1.0.2:
+ resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
+ engines: {node: '>=18'}
+
+ core-js-compat@3.49.0:
+ resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==}
+
+ core-util-is@1.0.2:
+ resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
+
+ cpr@3.0.1:
+ resolution: {integrity: sha512-Xch4PXQ/KC8lJ+KfJ9JI6eG/nmppLrPPWg5Q+vh65Qr9EjuJEubxh/H/Le1TmCZ7+Xv7iJuNRqapyOFZB+wsxA==}
+ hasBin: true
+
+ crc@3.8.0:
+ resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==}
+
+ crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+
+ cross-dirname@0.1.0:
+ resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==}
+
+ cross-fetch@4.0.0:
+ resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ crypto-random-string@2.0.0:
+ resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
+ engines: {node: '>=8'}
+
+ css-select@5.2.2:
+ resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
+
+ css-tree@2.2.1:
+ resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+ css-tree@3.1.0:
+ resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+ css-what@6.2.2:
+ resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
+ engines: {node: '>= 6'}
+
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csso@5.0.5:
+ resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
+ cssstyle@5.3.6:
+ resolution: {integrity: sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==}
+ engines: {node: '>=20'}
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ dagre@0.8.5:
+ resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==}
+
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
+ data-urls@6.0.0:
+ resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==}
+ engines: {node: '>=20'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ decompress-response@6.0.0:
+ resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+ engines: {node: '>=10'}
+
+ deep-extend@0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge-ts@7.1.5:
+ resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
+ engines: {node: '>=16.0.0'}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ defer-to-connect@2.0.1:
+ resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
+ engines: {node: '>=10'}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-lazy-prop@2.0.0:
+ resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
+ engines: {node: '>=8'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ detect-node@2.1.0:
+ resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==}
+
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ dir-compare@4.2.0:
+ resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+ dmg-builder@26.8.1:
+ resolution: {integrity: sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==}
+
+ dmg-license@1.0.11:
+ resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==}
+ engines: {node: '>=8'}
+ os: [darwin]
+ hasBin: true
+
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-accessibility-api@0.6.3:
+ resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
+ dotenv-expand@11.0.7:
+ resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
+ engines: {node: '>=12'}
+
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ ejs@3.1.10:
+ resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+
+ electron-builder-squirrel-windows@26.8.1:
+ resolution: {integrity: sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==}
+
+ electron-builder@26.8.1:
+ resolution: {integrity: sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ electron-is-dev@3.0.1:
+ resolution: {integrity: sha512-8TjjAh8Ec51hUi3o4TaU0mD3GMTOESi866oRNavj9A3IQJ7pmv+MJVmdZBFGw4GFT36X7bkqnuDNYvkQgvyI8Q==}
+ engines: {node: '>=18'}
+
+ electron-publish@26.8.1:
+ resolution: {integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==}
+
+ electron-to-chromium@1.5.237:
+ resolution: {integrity: sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==}
+
+ electron-to-chromium@1.5.353:
+ resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==}
+
+ electron-winstaller@5.4.0:
+ resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
+ engines: {node: '>=8.0.0'}
+
+ electron@42.0.1:
+ resolution: {integrity: sha512-d8HnycE970DGESe91Nj30eonFBUcAI9EZ1TwUGJVzSAnJZdh0BkFEinAXjdklvDYst+bVDc8HsksCuqVLrnqdg==}
+ engines: {node: '>= 22.12.0'}
+ hasBin: true
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ end-of-stream@1.4.5:
+ resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
+ env-paths@3.0.0:
+ resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ err-code@2.0.3:
+ resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
+
+ es-abstract@1.24.2:
+ resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ es6-error@4.1.1:
+ resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
+
+ esbuild@0.25.11:
+ resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-plugin-react-hooks@7.0.0:
+ resolution: {integrity: sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react-refresh@0.4.24:
+ resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
+ peerDependencies:
+ eslint: '>=8.40'
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.38.0:
+ resolution: {integrity: sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ eta@4.6.0:
+ resolution: {integrity: sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==}
+ engines: {node: '>=20'}
+
+ eventemitter3@3.1.2:
+ resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==}
+
+ eventemitter3@5.0.1:
+ resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
+
+ events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
+ exponential-backoff@3.1.3:
+ resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==}
+
+ extract-zip@2.0.1:
+ resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
+ engines: {node: '>= 10.17.0'}
+ hasBin: true
+
+ extsprintf@1.4.1:
+ resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
+ engines: {'0': node >=0.6.0}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.19.1:
+ resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
+
+ fd-slicer@1.1.0:
+ resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ filelist@1.0.6:
+ resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ foreground-child@3.3.1:
+ resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
+ engines: {node: '>=14'}
+
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ engines: {node: '>= 6'}
+
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+ fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+
+ fs-extra@11.3.4:
+ resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==}
+ engines: {node: '>=14.14'}
+
+ fs-extra@7.0.1:
+ resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
+ engines: {node: '>=6 <7 || >=8'}
+
+ fs-extra@8.1.0:
+ resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
+ engines: {node: '>=6 <7 || >=8'}
+
+ fs-extra@9.1.0:
+ resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
+ engines: {node: '>=10'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-own-enumerable-property-symbols@3.0.2:
+ resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@5.2.0:
+ resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
+ engines: {node: '>=8'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
+ glob@11.1.0:
+ resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
+ engines: {node: 20 || >=22}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+
+ global-agent@3.0.0:
+ resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
+ engines: {node: '>=10.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@16.4.0:
+ resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ got@11.8.6:
+ resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==}
+ engines: {node: '>=10.19.0'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphlib@2.1.8:
+ resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==}
+
+ graphology-types@0.24.8:
+ resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==}
+
+ graphology@0.25.4:
+ resolution: {integrity: sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==}
+ peerDependencies:
+ graphology-types: '>=0.24.0'
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
+ hosted-git-info@4.1.0:
+ resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
+ engines: {node: '>=10'}
+
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ html-encoding-sniffer@6.0.0:
+ resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+ http-cache-semantics@4.2.0:
+ resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ http2-wrapper@1.0.3:
+ resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
+ engines: {node: '>=10.19.0'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
+ i18next-http-backend@2.7.3:
+ resolution: {integrity: sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==}
+
+ i18next@22.5.1:
+ resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==}
+
+ iconv-corefoundation@1.1.7:
+ resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
+ engines: {node: ^8.11.2 || >=10}
+ os: [darwin]
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ idb@7.1.1:
+ resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-docker@2.2.1:
+ resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-module@1.0.0:
+ resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-obj@1.0.1:
+ resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
+ engines: {node: '>=0.10.0'}
+
+ is-port-reachable@4.0.0:
+ resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-regexp@1.0.0:
+ resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
+ engines: {node: '>=0.10.0'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ is-wsl@2.2.0:
+ resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
+ engines: {node: '>=8'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isbinaryfile@4.0.10:
+ resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
+ engines: {node: '>= 8.0.0'}
+
+ isbinaryfile@5.0.7:
+ resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==}
+ engines: {node: '>= 18.0.0'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ isexe@3.1.5:
+ resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
+ engines: {node: '>=18'}
+
+ isexe@4.0.0:
+ resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==}
+ engines: {node: '>=20'}
+
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+ engines: {node: '>=8'}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jackspeak@4.2.3:
+ resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
+ engines: {node: 20 || >=22}
+
+ jake@10.9.4:
+ resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
+ jiti@2.7.0:
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+ hasBin: true
+
+ js-interpreter@6.0.1:
+ resolution: {integrity: sha512-XfPw6y1FzFwHcGYB62jzPUoSCoCSIL+dICMjRJx6f8V/AmTczeodDOaVxWc4GU4p7qeN7ieuMXNKxScoaBkJ6A==}
+ hasBin: true
+
+ js-tokens@10.0.0:
+ resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsdom@26.1.0:
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsdom@27.4.0:
+ resolution: {integrity: sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==}
+ engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json-stringify-safe@5.0.1:
+ resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
+ jsonpointer@5.0.1:
+ resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+ engines: {node: '>=0.10.0'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ lazy-val@1.0.5:
+ resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
+
+ leven@3.1.0:
+ resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+ engines: {node: '>=6'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lightningcss-darwin-arm64@1.30.1:
+ resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.1:
+ resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.1:
+ resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.1:
+ resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.1:
+ resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
+ engines: {node: '>= 12.0.0'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.debounce@4.0.8:
+ resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash.sortby@4.7.0:
+ resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lowercase-keys@2.0.0:
+ resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
+ engines: {node: '>=8'}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@11.2.4:
+ resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==}
+ engines: {node: 20 || >=22}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+
+ lucide-react@0.546.0:
+ resolution: {integrity: sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ lucide@0.546.0:
+ resolution: {integrity: sha512-YJES3MM/naQS4wJ0JLzTY3anooqWw5iTsPCffHbSMncdxJT2C5tmkCDAwIHMHG8TMtaQcu40KREMPss2qH/+yA==}
+
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ magicast@0.5.2:
+ resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==}
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
+ matcher@3.0.0:
+ resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==}
+ engines: {node: '>=10'}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ mdn-data@2.0.28:
+ resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
+
+ mdn-data@2.12.2:
+ resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.33.0:
+ resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.18:
+ resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
+ engines: {node: '>= 0.6'}
+
+ mime@2.6.0:
+ resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
+ engines: {node: '>=4.0.0'}
+ hasBin: true
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ mimic-response@1.0.1:
+ resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
+ engines: {node: '>=4'}
+
+ mimic-response@3.1.0:
+ resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+ engines: {node: '>=10'}
+
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@5.1.9:
+ resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==}
+ engines: {node: '>=10'}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minizlib@3.1.0:
+ resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
+ engines: {node: '>= 18'}
+
+ mkdirp@0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
+ ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mustache@4.2.0:
+ resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
+ hasBin: true
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@0.6.4:
+ resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
+ engines: {node: '>= 0.6'}
+
+ node-abi@4.31.0:
+ resolution: {integrity: sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==}
+ engines: {node: '>=22.12.0'}
+
+ node-addon-api@1.7.2:
+ resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
+
+ node-api-version@0.2.1:
+ resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==}
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-gyp@12.3.0:
+ resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+ hasBin: true
+
+ node-releases@2.0.25:
+ resolution: {integrity: sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA==}
+
+ node-releases@2.0.44:
+ resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==}
+
+ nopt@9.0.0:
+ resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+ hasBin: true
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
+ normalize-url@6.1.0:
+ resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
+ engines: {node: '>=10'}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ nth-check@2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+ nwsapi@2.2.22:
+ resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ obliterator@2.0.5:
+ resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==}
+
+ obug@2.1.1:
+ resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+
+ on-headers@1.1.0:
+ resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ open@8.4.2:
+ resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
+ engines: {node: '>=12'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-cancelable@2.1.1:
+ resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
+ engines: {node: '>=8'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ papaparse@5.5.3:
+ resolution: {integrity: sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ parse5@8.0.0:
+ resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-is-inside@1.0.2:
+ resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ path-scurry@2.0.2:
+ resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
+ engines: {node: 18 || 20 || >=22}
+
+ path-to-regexp@3.3.0:
+ resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pe-library@0.4.1:
+ resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ pend@1.2.0:
+ resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
+
+ phaser3-rex-plugins@1.80.16:
+ resolution: {integrity: sha512-c34SfEjdK7rz8EbM7bDmJwB/xCb/YR77IaAaowzqkCLb7pLeI3E8PfLivm882iirN11XOHjYT2uYlvtdbNAtMA==}
+
+ phaser@3.90.0:
+ resolution: {integrity: sha512-/cziz/5ZIn02uDkC9RzN8VF9x3Gs3XdFFf9nkiMEQT3p7hQlWuyjy4QWosU802qqno2YSLn2BfqwOKLv/sSVfQ==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ plist@3.1.0:
+ resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
+ engines: {node: '>=10.4.0'}
+
+ plist@3.1.1:
+ resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==}
+ engines: {node: '>=10.4.0'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.1.0:
+ resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@6.0.1:
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postject@1.0.0-alpha.6:
+ resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier@3.8.1:
+ resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ pretty-bytes@5.6.0:
+ resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
+ engines: {node: '>=6'}
+
+ pretty-bytes@6.1.1:
+ resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
+ engines: {node: ^14.13.1 || >=16.0.0}
+
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ proc-log@6.1.0:
+ resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+
+ progress@2.0.3:
+ resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
+ engines: {node: '>=0.4.0'}
+
+ promise-retry@2.0.1:
+ resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
+ engines: {node: '>=10'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ proper-lockfile@4.1.2:
+ resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
+
+ pump@3.0.4:
+ resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ quick-lru@5.1.1:
+ resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+ engines: {node: '>=10'}
+
+ range-parser@1.2.0:
+ resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==}
+ engines: {node: '>= 0.6'}
+
+ rc@1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+
+ react-confetti@6.4.0:
+ resolution: {integrity: sha512-5MdGUcqxrTU26I2EU7ltkWPwxvucQTuqMm8dUz72z2YMqTD6s9vMcDUysk7n9jnC+lXuCPeJJ7Knf98VEYE9Rg==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ react: ^16.3.0 || ^17.0.1 || ^18.0.0 || ^19.0.0
+
+ react-dom@19.2.0:
+ resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==}
+ peerDependencies:
+ react: ^19.2.0
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-resizable-panels@3.0.6:
+ resolution: {integrity: sha512-b3qKHQ3MLqOgSS+FRYKapNkJZf5EQzuf6+RLiq1/IlTHw99YrZ2NJZLk4hQIzTnnIkRg2LUqyVinu6YWWpUYew==}
+ peerDependencies:
+ react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+
+ react-router-dom@7.9.4:
+ resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.9.4:
+ resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react-shepherd@6.1.9:
+ resolution: {integrity: sha512-kSFs7ER9+tDAQ9a80CGTaWHpuNf/6RNnnAqtPxFqZSt5NnlKi6T8/E93sYMPOibhvdtpG5pIZpeT3JI1+Ppqiw==}
+ peerDependencies:
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ typescript: ^5.0.0
+
+ react@19.2.0:
+ resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
+ engines: {node: '>=0.10.0'}
+
+ read-binary-file-arch@1.0.6:
+ resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==}
+ hasBin: true
+
+ read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regenerate-unicode-properties@10.2.2:
+ resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==}
+ engines: {node: '>=4'}
+
+ regenerate@1.4.2:
+ resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ regexpu-core@6.4.0:
+ resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==}
+ engines: {node: '>=4'}
+
+ registry-auth-token@3.3.2:
+ resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
+
+ registry-url@3.1.0:
+ resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
+ engines: {node: '>=0.10.0'}
+
+ regjsgen@0.8.0:
+ resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==}
+
+ regjsparser@0.13.1:
+ resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==}
+ hasBin: true
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ resedit@1.7.2:
+ resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ resolve-alpn@1.2.1:
+ resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve@1.22.10:
+ resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@1.22.12:
+ resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ responselike@2.0.1:
+ resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
+
+ retry@0.12.0:
+ resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
+ engines: {node: '>= 4'}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rimraf@2.6.3:
+ resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rimraf@2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ roarr@2.15.4:
+ resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
+ engines: {node: '>=8.0'}
+
+ rollup-plugin-visualizer@6.0.5:
+ resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+ peerDependencies:
+ rolldown: 1.x || ^1.0.0-beta
+ rollup: 2.x || 3.x || 4.x
+ peerDependenciesMeta:
+ rolldown:
+ optional: true
+ rollup:
+ optional: true
+
+ rollup@4.52.5:
+ resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rollup@4.60.3:
+ resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
+ safe-array-concat@1.1.4:
+ resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ sanitize-filename@1.6.4:
+ resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==}
+
+ sax@1.4.1:
+ resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver-compare@1.0.0:
+ resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
+
+ semver@5.7.2:
+ resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
+ hasBin: true
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sentiment@5.0.2:
+ resolution: {integrity: sha512-ZeC3y0JsOYTdwujt5uOd7ILJNilbgFzUtg/LEG4wUv43LayFNLZ28ec8+Su+h3saHlJmIwYxBzfDHHZuiMA15g==}
+ engines: {node: '>=8.0'}
+
+ serialize-error@7.0.1:
+ resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
+ engines: {node: '>=10'}
+
+ serialize-javascript@7.0.5:
+ resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==}
+ engines: {node: '>=20.0.0'}
+
+ serve-handler@6.1.6:
+ resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==}
+
+ serve@14.2.5:
+ resolution: {integrity: sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==}
+ engines: {node: '>= 14'}
+ hasBin: true
+
+ set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ sharp@0.34.4:
+ resolution: {integrity: sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ shell-quote@1.8.3:
+ resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
+ engines: {node: '>= 0.4'}
+
+ shepherd.js@14.5.1:
+ resolution: {integrity: sha512-VuvPvLG1QjNOLP7AIm2HGyfmxEIz8QdskvWOHwUcxLDibYWjLRBmCWd8LSL5FlwhBW7D/GU+3gNVC/ASxAWdxg==}
+ engines: {node: 18.* || >= 20}
+
+ side-channel-list@1.0.1:
+ resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ simple-update-notifier@2.0.0:
+ resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
+ engines: {node: '>=10'}
+
+ sirv@3.0.2:
+ resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
+ engines: {node: '>=18'}
+
+ slice-ansi@3.0.0:
+ resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
+ engines: {node: '>=8'}
+
+ smart-buffer@4.2.0:
+ resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
+ engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+
+ smob@1.6.1:
+ resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==}
+ engines: {node: '>=20.0.0'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-support@0.5.21:
+ resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.7.6:
+ resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+ engines: {node: '>= 12'}
+
+ source-map@0.8.0-beta.0:
+ resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
+ engines: {node: '>= 8'}
+ deprecated: The work that was done in this beta branch won't be included in future versions
+
+ sprintf-js@1.1.3:
+ resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ stat-mode@1.0.0:
+ resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==}
+ engines: {node: '>= 6'}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ stringify-object@3.3.0:
+ resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
+ engines: {node: '>=4'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ engines: {node: '>=12'}
+
+ strip-comments@2.0.1:
+ resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
+ engines: {node: '>=10'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ style-mod@4.1.3:
+ resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
+
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ sumchecker@3.0.1:
+ resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
+ engines: {node: '>= 8.0'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ svgo@4.0.0:
+ resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ tailwindcss@3.4.18:
+ resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ tar@7.5.15:
+ resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==}
+ engines: {node: '>=18'}
+
+ temp-dir@2.0.0:
+ resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==}
+ engines: {node: '>=8'}
+
+ temp-file@3.4.0:
+ resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==}
+
+ temp@0.9.4:
+ resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==}
+ engines: {node: '>=6.0.0'}
+
+ tempy@0.6.0:
+ resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==}
+ engines: {node: '>=10'}
+
+ terser@5.44.0:
+ resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ tiny-async-pool@1.3.0:
+ resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@1.0.2:
+ resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.0.3:
+ resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
+ engines: {node: '>=14.0.0'}
+
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ tldts-core@7.0.19:
+ resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==}
+
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
+ tldts@7.0.19:
+ resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==}
+ hasBin: true
+
+ tmp-promise@3.0.3:
+ resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
+
+ tmp@0.2.5:
+ resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}
+ engines: {node: '>=14.14'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
+ tough-cookie@6.0.0:
+ resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
+ engines: {node: '>=16'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ tr46@1.0.1:
+ resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
+
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
+ tr46@6.0.0:
+ resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==}
+ engines: {node: '>=20'}
+
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
+ truncate-utf8-bytes@1.0.2:
+ resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tween-functions@1.2.0:
+ resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-fest@0.13.1:
+ resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
+ engines: {node: '>=10'}
+
+ type-fest@0.16.0:
+ resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==}
+ engines: {node: '>=10'}
+
+ type-fest@2.19.0:
+ resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
+ engines: {node: '>=12.20'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ undici@6.25.0:
+ resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==}
+ engines: {node: '>=18.17'}
+
+ undici@7.25.0:
+ resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==}
+ engines: {node: '>=20.18.1'}
+
+ unicode-canonical-property-names-ecmascript@2.0.1:
+ resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
+ engines: {node: '>=4'}
+
+ unicode-match-property-ecmascript@2.0.0:
+ resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
+ engines: {node: '>=4'}
+
+ unicode-match-property-value-ecmascript@2.2.1:
+ resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==}
+ engines: {node: '>=4'}
+
+ unicode-property-aliases-ecmascript@2.2.0:
+ resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==}
+ engines: {node: '>=4'}
+
+ unique-string@2.0.0:
+ resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
+ engines: {node: '>=8'}
+
+ universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ upath@1.2.0:
+ resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
+ engines: {node: '>=4'}
+
+ update-browserslist-db@1.1.3:
+ resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ update-check@1.5.4:
+ resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ utf8-byte-length@1.0.5:
+ resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ verror@1.10.1:
+ resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==}
+ engines: {node: '>=0.6.0'}
+
+ vite-plugin-image-optimizer@2.0.2:
+ resolution: {integrity: sha512-BYK27SpSScRIaveJVjbP7EjSrawuCc+ffESGvKVRhByAu6RGvwE3EyGg9ZeqQiLUE8e1hKSCr8v5ZfvQNiqvJQ==}
+ engines: {node: '>=18.17.0'}
+ peerDependencies:
+ sharp: '>=0.34.0'
+ svgo: '>=4'
+ vite: '>=5'
+ peerDependenciesMeta:
+ sharp:
+ optional: true
+ svgo:
+ optional: true
+
+ vite-plugin-pwa@0.20.5:
+ resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ '@vite-pwa/assets-generator': ^0.2.6
+ vite: ^3.1.0 || ^4.0.0 || ^5.0.0
+ workbox-build: ^7.1.0
+ workbox-window: ^7.1.0
+ peerDependenciesMeta:
+ '@vite-pwa/assets-generator':
+ optional: true
+
+ vite@7.1.10:
+ resolution: {integrity: sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@4.0.16:
+ resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.0.16
+ '@vitest/browser-preview': 4.0.16
+ '@vitest/browser-webdriverio': 4.0.16
+ '@vitest/ui': 4.0.16
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ webfontloader@1.6.28:
+ resolution: {integrity: sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ webidl-conversions@4.0.2:
+ resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ webidl-conversions@8.0.1:
+ resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
+ engines: {node: '>=20'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
+ whatwg-url@15.1.0:
+ resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
+ engines: {node: '>=20'}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ whatwg-url@7.1.0:
+ resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-typed-array@1.1.20:
+ resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+ engines: {node: '>= 0.4'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ which@5.0.0:
+ resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
+ engines: {node: ^18.17.0 || >=20.5.0}
+ hasBin: true
+
+ which@6.0.1:
+ resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==}
+ engines: {node: ^20.17.0 || >=22.9.0}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ widest-line@4.0.1:
+ resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
+ engines: {node: '>=12'}
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ workbox-background-sync@7.4.1:
+ resolution: {integrity: sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==}
+
+ workbox-broadcast-update@7.4.1:
+ resolution: {integrity: sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==}
+
+ workbox-build@7.4.1:
+ resolution: {integrity: sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==}
+ engines: {node: '>=20.0.0'}
+
+ workbox-cacheable-response@7.4.1:
+ resolution: {integrity: sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==}
+
+ workbox-core@7.4.1:
+ resolution: {integrity: sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==}
+
+ workbox-expiration@7.4.1:
+ resolution: {integrity: sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==}
+
+ workbox-google-analytics@7.4.1:
+ resolution: {integrity: sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==}
+
+ workbox-navigation-preload@7.4.1:
+ resolution: {integrity: sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==}
+
+ workbox-precaching@7.4.1:
+ resolution: {integrity: sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==}
+
+ workbox-range-requests@7.4.1:
+ resolution: {integrity: sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==}
+
+ workbox-recipes@7.4.1:
+ resolution: {integrity: sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==}
+
+ workbox-routing@7.4.1:
+ resolution: {integrity: sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==}
+
+ workbox-strategies@7.4.1:
+ resolution: {integrity: sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==}
+
+ workbox-streams@7.4.1:
+ resolution: {integrity: sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==}
+
+ workbox-sw@7.4.1:
+ resolution: {integrity: sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==}
+
+ workbox-window@7.4.1:
+ resolution: {integrity: sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlbuilder@15.1.1:
+ resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
+ engines: {node: '>=8.0'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ yallist@5.0.0:
+ resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
+ engines: {node: '>=18'}
+
+ yaml@2.8.0:
+ resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yauzl@2.10.0:
+ resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.1.12:
+ resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
+
+snapshots:
+
+ 7zip-bin@5.2.0: {}
+
+ '@acemir/cssom@0.9.30': {}
+
+ '@adobe/css-tools@4.4.4': {}
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@apideck/better-ajv-errors@0.3.7(ajv@8.12.0)':
+ dependencies:
+ ajv: 8.12.0
+ jsonpointer: 5.0.1
+ leven: 3.1.0
+
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
+ '@asamuzakjp/css-color@4.1.1':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 11.2.4
+
+ '@asamuzakjp/dom-selector@6.7.6':
+ dependencies:
+ '@asamuzakjp/nwsapi': 2.3.9
+ bidi-js: 1.0.3
+ css-tree: 3.1.0
+ is-potential-custom-element-name: 1.0.1
+ lru-cache: 11.2.4
+
+ '@asamuzakjp/nwsapi@2.3.9': {}
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.4': {}
+
+ '@babel/compat-data@7.29.3': {}
+
+ '@babel/core@7.28.4':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.3
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.4
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.3':
+ dependencies:
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-annotate-as-pure@7.27.3':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.4
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.26.3
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.29.3
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.26.3
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.4)
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/traverse': 7.29.0
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ regexpu-core: 6.4.0
+ semver: 6.3.1
+
+ '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ debug: 4.4.3
+ lodash.debounce: 4.0.8
+ resolve: 1.22.12
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-member-expression-to-functions@7.28.5':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+ '@babel/traverse': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-optimise-call-expression@7.27.1':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-wrap-function': 7.28.6
+ '@babel/traverse': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-member-expression-to-functions': 7.28.5
+ '@babel/helper-optimise-call-expression': 7.27.1
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-skip-transparent-expression-wrappers@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helper-wrap-function@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.4
+
+ '@babel/parser@7.28.4':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@babel/parser@7.29.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+
+ '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4)
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-globals': 7.28.0
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.4)
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/template': 7.28.6
+
+ '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/traverse': 7.28.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4)
+ '@babel/traverse': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-annotate-as-pure': 7.27.3
+ '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4)
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/preset-env@7.29.5(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/compat-data': 7.29.3
+ '@babel/core': 7.28.4
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.4)
+ '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.28.4)
+ '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4)
+ '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.28.4)
+ '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4)
+ '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.4)
+ '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.28.4)
+ '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.4)
+ '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4)
+ '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.28.4)
+ '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.4)
+ '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.4)
+ '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.4)
+ babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.28.4)
+ babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.28.4)
+ babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.28.4)
+ core-js-compat: 3.49.0
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.4)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-plugin-utils': 7.28.6
+ '@babel/types': 7.29.0
+ esutils: 2.0.3
+
+ '@babel/runtime@7.28.4': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.28.4':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.3
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.4
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.4':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.27.1
+
+ '@babel/types@7.29.0':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@bcoe/v8-coverage@1.0.2': {}
+
+ '@blockly/field-angle@6.0.3(blockly@12.3.1)':
+ dependencies:
+ blockly: 12.3.1
+
+ '@blockly/theme-modern@7.0.1(blockly@12.3.1)':
+ dependencies:
+ blockly: 12.3.1
+
+ '@codemirror/autocomplete@6.20.1':
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+
+ '@codemirror/commands@6.10.3':
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+
+ '@codemirror/lang-javascript@6.2.5':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.0
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+ '@lezer/javascript': 1.5.4
+
+ '@codemirror/language@6.12.3':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+ style-mod: 4.1.3
+
+ '@codemirror/lint@6.9.0':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ crelt: 1.0.6
+
+ '@codemirror/search@6.5.11':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ crelt: 1.0.6
+
+ '@codemirror/state@6.6.0':
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.2
+
+ '@codemirror/theme-one-dark@6.1.3':
+ dependencies:
+ '@codemirror/language': 6.12.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+ '@lezer/highlight': 1.2.3
+
+ '@codemirror/view@6.41.0':
+ dependencies:
+ '@codemirror/state': 6.6.0
+ crelt: 1.0.6
+ style-mod: 4.1.3
+ w3c-keyname: 2.2.8
+
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-syntax-patches-for-csstree@1.0.22': {}
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
+ '@develar/schema-utils@2.6.5':
+ dependencies:
+ ajv: 6.12.6
+ ajv-keywords: 3.5.2(ajv@6.12.6)
+
+ '@electron/asar@3.4.1':
+ dependencies:
+ commander: 5.1.0
+ glob: 7.2.3
+ minimatch: 3.1.2
+
+ '@electron/fuses@1.8.0':
+ dependencies:
+ chalk: 4.1.2
+ fs-extra: 9.1.0
+ minimist: 1.2.8
+
+ '@electron/get@3.1.0':
+ dependencies:
+ debug: 4.4.3
+ env-paths: 2.2.1
+ fs-extra: 8.1.0
+ got: 11.8.6
+ progress: 2.0.3
+ semver: 6.3.1
+ sumchecker: 3.0.1
+ optionalDependencies:
+ global-agent: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/get@5.0.0':
+ dependencies:
+ debug: 4.4.3
+ env-paths: 3.0.0
+ graceful-fs: 4.2.11
+ progress: 2.0.3
+ semver: 7.7.3
+ sumchecker: 3.0.1
+ optionalDependencies:
+ undici: 7.25.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/notarize@2.5.0':
+ dependencies:
+ debug: 4.4.3
+ fs-extra: 9.1.0
+ promise-retry: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/osx-sign@1.3.3':
+ dependencies:
+ compare-version: 0.1.2
+ debug: 4.4.3
+ fs-extra: 10.1.0
+ isbinaryfile: 4.0.10
+ minimist: 1.2.8
+ plist: 3.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/rebuild@4.0.4':
+ dependencies:
+ '@malept/cross-spawn-promise': 2.0.0
+ debug: 4.4.3
+ node-abi: 4.31.0
+ node-api-version: 0.2.1
+ node-gyp: 12.3.0
+ read-binary-file-arch: 1.0.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/universal@2.0.3':
+ dependencies:
+ '@electron/asar': 3.4.1
+ '@malept/cross-spawn-promise': 2.0.0
+ debug: 4.4.3
+ dir-compare: 4.2.0
+ fs-extra: 11.3.4
+ minimatch: 9.0.5
+ plist: 3.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/windows-sign@1.2.2':
+ dependencies:
+ cross-dirname: 0.1.0
+ debug: 4.4.3
+ fs-extra: 11.3.4
+ minimist: 1.2.8
+ postject: 1.0.0-alpha.6
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ '@emnapi/runtime@1.5.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.11':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/android-arm@0.25.11':
+ optional: true
+
+ '@esbuild/android-x64@0.25.11':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.11':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.11':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.11':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.11':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.11':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.11':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.11':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.11':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.11':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.11':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.11':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.11':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.11':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.11':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.11':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.11':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@1.21.7))':
+ dependencies:
+ eslint: 9.38.0(jiti@1.21.7)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
+
+ '@eslint/config-array@0.21.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.1':
+ dependencies:
+ '@eslint/core': 0.16.0
+
+ '@eslint/core@0.16.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.1':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.38.0': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.0':
+ dependencies:
+ '@eslint/core': 0.16.0
+ levn: 0.4.1
+
+ '@exodus/bytes@1.8.0': {}
+
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
+ '@floating-ui/utils@0.2.10': {}
+
+ '@fortawesome/fontawesome-free@6.7.2': {}
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@img/colour@1.0.0': {}
+
+ '@img/sharp-darwin-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.3':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.3
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
+ optional: true
+
+ '@img/sharp-wasm32@0.34.4':
+ dependencies:
+ '@emnapi/runtime': 1.5.0
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.4':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.4':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.4':
+ optional: true
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.2
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@isaacs/cliui@9.0.0': {}
+
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.2
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/source-map@0.3.11':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@lezer/common@1.5.1': {}
+
+ '@lezer/highlight@1.2.3':
+ dependencies:
+ '@lezer/common': 1.5.1
+
+ '@lezer/javascript@1.5.4':
+ dependencies:
+ '@lezer/common': 1.5.1
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.8
+
+ '@lezer/lr@1.4.8':
+ dependencies:
+ '@lezer/common': 1.5.1
+
+ '@malept/cross-spawn-promise@2.0.0':
+ dependencies:
+ cross-spawn: 7.0.6
+
+ '@malept/flatpak-bundler@0.4.0':
+ dependencies:
+ debug: 4.4.3
+ fs-extra: 9.1.0
+ lodash: 4.17.21
+ tmp-promise: 3.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@marijn/find-cluster-break@1.0.2': {}
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.1
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@polka/url@1.0.0-next.29': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.38': {}
+
+ '@rollup/plugin-babel@6.1.0(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@4.60.3)':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-module-imports': 7.27.1
+ '@rollup/pluginutils': 5.3.0(rollup@4.60.3)
+ optionalDependencies:
+ '@types/babel__core': 7.20.5
+ rollup: 4.60.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@rollup/plugin-node-resolve@16.0.3(rollup@4.60.3)':
+ dependencies:
+ '@rollup/pluginutils': 5.3.0(rollup@4.60.3)
+ '@types/resolve': 1.20.2
+ deepmerge: 4.3.1
+ is-module: 1.0.0
+ resolve: 1.22.10
+ optionalDependencies:
+ rollup: 4.60.3
+
+ '@rollup/plugin-replace@6.0.3(rollup@4.60.3)':
+ dependencies:
+ '@rollup/pluginutils': 5.3.0(rollup@4.60.3)
+ magic-string: 0.30.21
+ optionalDependencies:
+ rollup: 4.60.3
+
+ '@rollup/plugin-terser@1.0.0(rollup@4.60.3)':
+ dependencies:
+ serialize-javascript: 7.0.5
+ smob: 1.6.1
+ terser: 5.44.0
+ optionalDependencies:
+ rollup: 4.60.3
+
+ '@rollup/pluginutils@5.3.0(rollup@4.60.3)':
+ dependencies:
+ '@types/estree': 1.0.8
+ estree-walker: 2.0.2
+ picomatch: 4.0.3
+ optionalDependencies:
+ rollup: 4.60.3
+
+ '@rollup/rollup-android-arm-eabi@4.52.5':
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.60.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.52.5':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.60.3':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.52.5':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.60.3':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.60.3':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.60.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.60.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.52.5':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.60.3':
+ optional: true
+
+ '@scarf/scarf@1.4.0': {}
+
+ '@sindresorhus/is@4.6.0': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@szmarczak/http-timer@4.0.6':
+ dependencies:
+ defer-to-connect: 2.0.1
+
+ '@tailwindcss/typography@0.5.19(tailwindcss@3.4.18(yaml@2.8.0))':
+ dependencies:
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 3.4.18(yaml@2.8.0)
+
+ '@testing-library/dom@10.4.1':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/runtime': 7.28.4
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ picocolors: 1.1.1
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@6.9.1':
+ dependencies:
+ '@adobe/css-tools': 4.4.4
+ aria-query: 5.3.2
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.6.3
+ picocolors: 1.1.1
+ redent: 3.0.0
+
+ '@testing-library/react@16.3.1(@testing-library/dom@10.4.1)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@testing-library/dom': 10.4.1
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ optionalDependencies:
+ '@types/react': 19.2.2
+ '@types/react-dom': 19.2.2(@types/react@19.2.2)
+
+ '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
+ dependencies:
+ '@testing-library/dom': 10.4.1
+
+ '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1':
+ dependencies:
+ ejs: 3.1.10
+ json5: 2.2.3
+ magic-string: 0.30.21
+ string.prototype.matchall: 4.0.12
+
+ '@tsparticles/basic@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+ '@tsparticles/move-base': 3.9.1
+ '@tsparticles/plugin-hex-color': 3.9.1
+ '@tsparticles/plugin-hsl-color': 3.9.1
+ '@tsparticles/plugin-rgb-color': 3.9.1
+ '@tsparticles/shape-circle': 3.9.1
+ '@tsparticles/updater-color': 3.9.1
+ '@tsparticles/updater-opacity': 3.9.1
+ '@tsparticles/updater-out-modes': 3.9.1
+ '@tsparticles/updater-size': 3.9.1
+
+ '@tsparticles/confetti@3.9.1':
+ dependencies:
+ '@tsparticles/basic': 3.9.1
+ '@tsparticles/engine': 3.9.1
+ '@tsparticles/plugin-emitters': 3.9.1
+ '@tsparticles/plugin-motion': 3.9.1
+ '@tsparticles/shape-cards': 3.9.1
+ '@tsparticles/shape-emoji': 3.9.1
+ '@tsparticles/shape-heart': 3.9.1
+ '@tsparticles/shape-image': 3.9.1
+ '@tsparticles/shape-polygon': 3.9.1
+ '@tsparticles/shape-square': 3.9.1
+ '@tsparticles/shape-star': 3.9.1
+ '@tsparticles/updater-life': 3.9.1
+ '@tsparticles/updater-roll': 3.9.1
+ '@tsparticles/updater-rotate': 3.9.1
+ '@tsparticles/updater-tilt': 3.9.1
+ '@tsparticles/updater-wobble': 3.9.1
+
+ '@tsparticles/engine@3.9.1': {}
+
+ '@tsparticles/move-base@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/plugin-emitters@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/plugin-hex-color@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/plugin-hsl-color@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/plugin-motion@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/plugin-rgb-color@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-cards@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-circle@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-emoji@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-heart@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-image@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-polygon@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-square@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/shape-star@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-color@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-life@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-opacity@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-out-modes@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-roll@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-rotate@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-size@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-tilt@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@tsparticles/updater-wobble@3.9.1':
+ dependencies:
+ '@tsparticles/engine': 3.9.1
+
+ '@types/aria-query@5.0.4': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.4
+
+ '@types/cacheable-request@6.0.3':
+ dependencies:
+ '@types/http-cache-semantics': 4.2.0
+ '@types/keyv': 3.1.4
+ '@types/node': 24.9.0
+ '@types/responselike': 1.0.3
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/debug@4.1.13':
+ dependencies:
+ '@types/ms': 2.1.0
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/fs-extra@9.0.13':
+ dependencies:
+ '@types/node': 24.9.0
+
+ '@types/http-cache-semantics@4.2.0': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/keyv@3.1.4':
+ dependencies:
+ '@types/node': 24.9.0
+
+ '@types/ms@2.1.0': {}
+
+ '@types/node@24.9.0':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/plist@3.0.5':
+ dependencies:
+ '@types/node': 24.9.0
+ xmlbuilder: 15.1.1
+ optional: true
+
+ '@types/react-dom@19.2.2(@types/react@19.2.2)':
+ dependencies:
+ '@types/react': 19.2.2
+
+ '@types/react@19.2.2':
+ dependencies:
+ csstype: 3.1.3
+
+ '@types/resolve@1.20.2': {}
+
+ '@types/responselike@1.0.3':
+ dependencies:
+ '@types/node': 24.9.0
+
+ '@types/trusted-types@2.0.7': {}
+
+ '@types/verror@1.10.11':
+ optional: true
+
+ '@types/yauzl@2.10.3':
+ dependencies:
+ '@types/node': 24.9.0
+ optional: true
+
+ '@uiw/codemirror-extensions-basic-setup@4.25.9(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.6.0)(@codemirror/view@6.41.0)':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/commands': 6.10.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.0
+ '@codemirror/search': 6.5.11
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+
+ '@uiw/react-codemirror@4.25.9(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.6.0)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.41.0)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@codemirror/commands': 6.10.3
+ '@codemirror/state': 6.6.0
+ '@codemirror/theme-one-dark': 6.1.3
+ '@codemirror/view': 6.41.0
+ '@uiw/codemirror-extensions-basic-setup': 4.25.9(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.0)(@codemirror/search@6.5.11)(@codemirror/state@6.6.0)(@codemirror/view@6.41.0)
+ codemirror: 6.0.2
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ transitivePeerDependencies:
+ - '@codemirror/autocomplete'
+ - '@codemirror/language'
+ - '@codemirror/lint'
+ - '@codemirror/search'
+
+ '@vitejs/plugin-react@5.0.4(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))':
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4)
+ '@rolldown/pluginutils': 1.0.0-beta.38
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitest/coverage-v8@4.0.18(vitest@4.0.16)':
+ dependencies:
+ '@bcoe/v8-coverage': 1.0.2
+ '@vitest/utils': 4.0.18
+ ast-v8-to-istanbul: 0.3.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-reports: 3.2.0
+ magicast: 0.5.2
+ obug: 2.1.1
+ std-env: 3.10.0
+ tinyrainbow: 3.0.3
+ vitest: 4.0.16(@types/node@24.9.0)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+
+ '@vitest/expect@4.0.16':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.0.16
+ '@vitest/utils': 4.0.16
+ chai: 6.2.2
+ tinyrainbow: 3.0.3
+
+ '@vitest/mocker@4.0.16(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))':
+ dependencies:
+ '@vitest/spy': 4.0.16
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+
+ '@vitest/pretty-format@4.0.16':
+ dependencies:
+ tinyrainbow: 3.0.3
+
+ '@vitest/pretty-format@4.0.18':
+ dependencies:
+ tinyrainbow: 3.0.3
+
+ '@vitest/runner@4.0.16':
+ dependencies:
+ '@vitest/utils': 4.0.16
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.0.16':
+ dependencies:
+ '@vitest/pretty-format': 4.0.16
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.0.16': {}
+
+ '@vitest/ui@4.0.16(vitest@4.0.16)':
+ dependencies:
+ '@vitest/utils': 4.0.16
+ fflate: 0.8.2
+ flatted: 3.3.3
+ pathe: 2.0.3
+ sirv: 3.0.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.0.3
+ vitest: 4.0.16(@types/node@24.9.0)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+
+ '@vitest/utils@4.0.16':
+ dependencies:
+ '@vitest/pretty-format': 4.0.16
+ tinyrainbow: 3.0.3
+
+ '@vitest/utils@4.0.18':
+ dependencies:
+ '@vitest/pretty-format': 4.0.18
+ tinyrainbow: 3.0.3
+
+ '@xmldom/xmldom@0.8.13': {}
+
+ '@xmldom/xmldom@0.9.10':
+ optional: true
+
+ '@zeit/schemas@2.36.0': {}
+
+ abbrev@4.0.0: {}
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ acorn@8.16.0: {}
+
+ agent-base@7.1.4: {}
+
+ ajv-keywords@3.5.2(ajv@6.12.6):
+ dependencies:
+ ajv: 6.12.6
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.12.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+ uri-js: 4.4.1
+
+ ansi-align@3.0.1:
+ dependencies:
+ string-width: 4.2.3
+
+ ansi-colors@4.1.3: {}
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ ansi-styles@6.2.3: {}
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ app-builder-bin@5.0.0-alpha.12: {}
+
+ app-builder-lib@26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ '@develar/schema-utils': 2.6.5
+ '@electron/asar': 3.4.1
+ '@electron/fuses': 1.8.0
+ '@electron/get': 3.1.0
+ '@electron/notarize': 2.5.0
+ '@electron/osx-sign': 1.3.3
+ '@electron/rebuild': 4.0.4
+ '@electron/universal': 2.0.3
+ '@malept/flatpak-bundler': 0.4.0
+ '@types/fs-extra': 9.0.13
+ async-exit-hook: 2.0.1
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chromium-pickle-js: 0.2.0
+ ci-info: 4.3.1
+ debug: 4.4.3
+ dmg-builder: 26.8.1(electron-builder-squirrel-windows@26.8.1)
+ dotenv: 16.6.1
+ dotenv-expand: 11.0.7
+ ejs: 3.1.10
+ electron-builder-squirrel-windows: 26.8.1(dmg-builder@26.8.1)
+ electron-publish: 26.8.1
+ fs-extra: 10.1.0
+ hosted-git-info: 4.1.0
+ isbinaryfile: 5.0.7
+ jiti: 2.7.0
+ js-yaml: 4.1.0
+ json5: 2.2.3
+ lazy-val: 1.0.5
+ minimatch: 10.2.5
+ plist: 3.1.0
+ proper-lockfile: 4.1.2
+ resedit: 1.7.2
+ semver: 7.7.3
+ tar: 7.5.15
+ temp-file: 3.4.0
+ tiny-async-pool: 1.3.0
+ which: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ arch@2.2.0: {}
+
+ arg@5.0.2: {}
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ assert-plus@1.0.0:
+ optional: true
+
+ assertion-error@2.0.1: {}
+
+ ast-v8-to-istanbul@0.3.11:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ estree-walker: 3.0.3
+ js-tokens: 10.0.0
+
+ astral-regex@2.0.0:
+ optional: true
+
+ async-exit-hook@2.0.1: {}
+
+ async-function@1.0.0: {}
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ at-least-node@1.0.0: {}
+
+ autoprefixer@10.4.21(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.26.3
+ caniuse-lite: 1.0.30001751
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.28.4):
+ dependencies:
+ '@babel/compat-data': 7.29.3
+ '@babel/core': 7.28.4
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.28.4)
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.28.4):
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.28.4)
+ core-js-compat: 3.49.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.28.4):
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.28.4)
+ transitivePeerDependencies:
+ - supports-color
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ base64-js@1.5.1: {}
+
+ baseline-browser-mapping@2.10.14: {}
+
+ bidi-js@1.0.3:
+ dependencies:
+ require-from-string: 2.0.2
+
+ binary-extensions@2.3.0: {}
+
+ blockly@12.3.1:
+ dependencies:
+ jsdom: 26.1.0
+ transitivePeerDependencies:
+ - bufferutil
+ - canvas
+ - supports-color
+ - utf-8-validate
+
+ boolbase@1.0.0: {}
+
+ boolean@3.2.0:
+ optional: true
+
+ boxen@7.0.0:
+ dependencies:
+ ansi-align: 3.0.1
+ camelcase: 7.0.1
+ chalk: 5.0.1
+ cli-boxes: 3.0.0
+ string-width: 5.1.2
+ type-fest: 2.19.0
+ widest-line: 4.0.1
+ wrap-ansi: 8.1.0
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.6:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.26.3:
+ dependencies:
+ baseline-browser-mapping: 2.10.14
+ caniuse-lite: 1.0.30001751
+ electron-to-chromium: 1.5.237
+ node-releases: 2.0.25
+ update-browserslist-db: 1.1.3(browserslist@4.26.3)
+
+ browserslist@4.28.2:
+ dependencies:
+ baseline-browser-mapping: 2.10.14
+ caniuse-lite: 1.0.30001792
+ electron-to-chromium: 1.5.353
+ node-releases: 2.0.44
+ update-browserslist-db: 1.2.3(browserslist@4.28.2)
+
+ buffer-crc32@0.2.13: {}
+
+ buffer-from@1.1.2: {}
+
+ buffer@5.7.1:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+ optional: true
+
+ builder-util-runtime@9.5.1:
+ dependencies:
+ debug: 4.4.3
+ sax: 1.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ builder-util@26.8.1:
+ dependencies:
+ 7zip-bin: 5.2.0
+ '@types/debug': 4.1.13
+ app-builder-bin: 5.0.0-alpha.12
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ fs-extra: 10.1.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ js-yaml: 4.1.0
+ sanitize-filename: 1.6.4
+ source-map-support: 0.5.21
+ stat-mode: 1.0.0
+ temp-file: 3.4.0
+ tiny-async-pool: 1.3.0
+ transitivePeerDependencies:
+ - supports-color
+
+ bytes@3.0.0: {}
+
+ bytes@3.1.2: {}
+
+ cacheable-lookup@5.0.4: {}
+
+ cacheable-request@7.0.4:
+ dependencies:
+ clone-response: 1.0.3
+ get-stream: 5.2.0
+ http-cache-semantics: 4.2.0
+ keyv: 4.5.4
+ lowercase-keys: 2.0.0
+ normalize-url: 6.1.0
+ responselike: 2.0.1
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.9:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camelcase-css@2.0.1: {}
+
+ camelcase@7.0.1: {}
+
+ caniuse-lite@1.0.30001751: {}
+
+ caniuse-lite@1.0.30001792: {}
+
+ chai@6.2.2: {}
+
+ chalk-template@0.4.0:
+ dependencies:
+ chalk: 4.1.2
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@5.0.1: {}
+
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ chownr@3.0.0: {}
+
+ chromium-pickle-js@0.2.0: {}
+
+ ci-info@4.3.1: {}
+
+ ci-info@4.4.0: {}
+
+ cli-boxes@3.0.0: {}
+
+ cli-truncate@2.1.0:
+ dependencies:
+ slice-ansi: 3.0.0
+ string-width: 4.2.3
+ optional: true
+
+ clipboardy@3.0.0:
+ dependencies:
+ arch: 2.2.0
+ execa: 5.1.1
+ is-wsl: 2.2.0
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ clone-response@1.0.3:
+ dependencies:
+ mimic-response: 1.0.1
+
+ codemirror@6.0.2:
+ dependencies:
+ '@codemirror/autocomplete': 6.20.1
+ '@codemirror/commands': 6.10.3
+ '@codemirror/language': 6.12.3
+ '@codemirror/lint': 6.9.0
+ '@codemirror/search': 6.5.11
+ '@codemirror/state': 6.6.0
+ '@codemirror/view': 6.41.0
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@11.1.0: {}
+
+ commander@2.20.3: {}
+
+ commander@4.1.1: {}
+
+ commander@5.1.0: {}
+
+ commander@9.5.0:
+ optional: true
+
+ common-tags@1.8.2: {}
+
+ compare-version@0.1.2: {}
+
+ compressible@2.0.18:
+ dependencies:
+ mime-db: 1.54.0
+
+ compression@1.8.1:
+ dependencies:
+ bytes: 3.1.2
+ compressible: 2.0.18
+ debug: 2.6.9
+ negotiator: 0.6.4
+ on-headers: 1.1.0
+ safe-buffer: 5.2.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ concat-map@0.0.1: {}
+
+ concurrently@9.2.1:
+ dependencies:
+ chalk: 4.1.2
+ rxjs: 7.8.2
+ shell-quote: 1.8.3
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+
+ content-disposition@0.5.2: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@1.0.2: {}
+
+ core-js-compat@3.49.0:
+ dependencies:
+ browserslist: 4.28.2
+
+ core-util-is@1.0.2:
+ optional: true
+
+ cpr@3.0.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ minimist: 1.2.8
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+
+ crc@3.8.0:
+ dependencies:
+ buffer: 5.7.1
+ optional: true
+
+ crelt@1.0.6: {}
+
+ cross-dirname@0.1.0:
+ optional: true
+
+ cross-fetch@4.0.0:
+ dependencies:
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ crypto-random-string@2.0.0: {}
+
+ css-select@5.2.2:
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.2.2
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ nth-check: 2.1.1
+
+ css-tree@2.2.1:
+ dependencies:
+ mdn-data: 2.0.28
+ source-map-js: 1.2.1
+
+ css-tree@3.1.0:
+ dependencies:
+ mdn-data: 2.12.2
+ source-map-js: 1.2.1
+
+ css-what@6.2.2: {}
+
+ css.escape@1.5.1: {}
+
+ cssesc@3.0.0: {}
+
+ csso@5.0.5:
+ dependencies:
+ css-tree: 2.2.1
+
+ cssstyle@4.6.0:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
+ cssstyle@5.3.6:
+ dependencies:
+ '@asamuzakjp/css-color': 4.1.1
+ '@csstools/css-syntax-patches-for-csstree': 1.0.22
+ css-tree: 3.1.0
+ lru-cache: 11.2.4
+
+ csstype@3.1.3: {}
+
+ dagre@0.8.5:
+ dependencies:
+ graphlib: 2.1.8
+ lodash: 4.17.21
+
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
+ data-urls@6.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 15.1.0
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ debug@2.6.9:
+ dependencies:
+ ms: 2.0.0
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js@10.6.0: {}
+
+ decompress-response@6.0.0:
+ dependencies:
+ mimic-response: 3.1.0
+
+ deep-extend@0.6.0: {}
+
+ deep-is@0.1.4: {}
+
+ deepmerge-ts@7.1.5: {}
+
+ deepmerge@4.3.1: {}
+
+ defer-to-connect@2.0.1: {}
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-lazy-prop@2.0.0: {}
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ delayed-stream@1.0.0: {}
+
+ dequal@2.0.3: {}
+
+ detect-libc@2.1.2: {}
+
+ detect-node@2.1.0:
+ optional: true
+
+ didyoumean@1.2.2: {}
+
+ dir-compare@4.2.0:
+ dependencies:
+ minimatch: 3.1.2
+ p-limit: 3.1.0
+
+ dlv@1.1.3: {}
+
+ dmg-builder@26.8.1(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ fs-extra: 10.1.0
+ iconv-lite: 0.6.3
+ js-yaml: 4.1.0
+ optionalDependencies:
+ dmg-license: 1.0.11
+ transitivePeerDependencies:
+ - electron-builder-squirrel-windows
+ - supports-color
+
+ dmg-license@1.0.11:
+ dependencies:
+ '@types/plist': 3.0.5
+ '@types/verror': 1.10.11
+ ajv: 6.12.6
+ crc: 3.8.0
+ iconv-corefoundation: 1.1.7
+ plist: 3.1.1
+ smart-buffer: 4.2.0
+ verror: 1.10.1
+ optional: true
+
+ dom-accessibility-api@0.5.16: {}
+
+ dom-accessibility-api@0.6.3: {}
+
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
+ dotenv-expand@11.0.7:
+ dependencies:
+ dotenv: 16.6.1
+
+ dotenv@16.6.1: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ eastasianwidth@0.2.0: {}
+
+ ejs@3.1.10:
+ dependencies:
+ jake: 10.9.4
+
+ electron-builder-squirrel-windows@26.8.1(dmg-builder@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ electron-winstaller: 5.4.0
+ transitivePeerDependencies:
+ - dmg-builder
+ - supports-color
+
+ electron-builder@26.8.1(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ ci-info: 4.4.0
+ dmg-builder: 26.8.1(electron-builder-squirrel-windows@26.8.1)
+ fs-extra: 10.1.0
+ lazy-val: 1.0.5
+ simple-update-notifier: 2.0.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - electron-builder-squirrel-windows
+ - supports-color
+
+ electron-is-dev@3.0.1: {}
+
+ electron-publish@26.8.1:
+ dependencies:
+ '@types/fs-extra': 9.0.13
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ form-data: 4.0.5
+ fs-extra: 10.1.0
+ lazy-val: 1.0.5
+ mime: 2.6.0
+ transitivePeerDependencies:
+ - supports-color
+
+ electron-to-chromium@1.5.237: {}
+
+ electron-to-chromium@1.5.353: {}
+
+ electron-winstaller@5.4.0:
+ dependencies:
+ '@electron/asar': 3.4.1
+ debug: 4.4.3
+ fs-extra: 7.0.1
+ lodash: 4.17.21
+ temp: 0.9.4
+ optionalDependencies:
+ '@electron/windows-sign': 1.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ electron@42.0.1:
+ dependencies:
+ '@electron/get': 5.0.0
+ '@types/node': 24.9.0
+ extract-zip: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ end-of-stream@1.4.5:
+ dependencies:
+ once: 1.4.0
+
+ entities@4.5.0: {}
+
+ entities@6.0.1: {}
+
+ env-paths@2.2.1: {}
+
+ env-paths@3.0.0: {}
+
+ err-code@2.0.3: {}
+
+ es-abstract@1.24.2:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.4
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.20
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-module-lexer@1.7.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ es6-error@4.1.1:
+ optional: true
+
+ esbuild@0.25.11:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.11
+ '@esbuild/android-arm': 0.25.11
+ '@esbuild/android-arm64': 0.25.11
+ '@esbuild/android-x64': 0.25.11
+ '@esbuild/darwin-arm64': 0.25.11
+ '@esbuild/darwin-x64': 0.25.11
+ '@esbuild/freebsd-arm64': 0.25.11
+ '@esbuild/freebsd-x64': 0.25.11
+ '@esbuild/linux-arm': 0.25.11
+ '@esbuild/linux-arm64': 0.25.11
+ '@esbuild/linux-ia32': 0.25.11
+ '@esbuild/linux-loong64': 0.25.11
+ '@esbuild/linux-mips64el': 0.25.11
+ '@esbuild/linux-ppc64': 0.25.11
+ '@esbuild/linux-riscv64': 0.25.11
+ '@esbuild/linux-s390x': 0.25.11
+ '@esbuild/linux-x64': 0.25.11
+ '@esbuild/netbsd-arm64': 0.25.11
+ '@esbuild/netbsd-x64': 0.25.11
+ '@esbuild/openbsd-arm64': 0.25.11
+ '@esbuild/openbsd-x64': 0.25.11
+ '@esbuild/openharmony-arm64': 0.25.11
+ '@esbuild/sunos-x64': 0.25.11
+ '@esbuild/win32-arm64': 0.25.11
+ '@esbuild/win32-ia32': 0.25.11
+ '@esbuild/win32-x64': 0.25.11
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-plugin-react-hooks@7.0.0(eslint@9.38.0(jiti@1.21.7)):
+ dependencies:
+ '@babel/core': 7.28.4
+ '@babel/parser': 7.28.4
+ eslint: 9.38.0(jiti@1.21.7)
+ hermes-parser: 0.25.1
+ zod: 4.1.12
+ zod-validation-error: 4.0.2(zod@4.1.12)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react-refresh@0.4.24(eslint@9.38.0(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.38.0(jiti@1.21.7)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint@9.38.0(jiti@1.21.7):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@1.21.7))
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.21.1
+ '@eslint/config-helpers': 0.4.1
+ '@eslint/core': 0.16.0
+ '@eslint/eslintrc': 3.3.1
+ '@eslint/js': 9.38.0
+ '@eslint/plugin-kit': 0.4.0
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 1.21.7
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ esutils@2.0.3: {}
+
+ eta@4.6.0: {}
+
+ eventemitter3@3.1.2: {}
+
+ eventemitter3@5.0.1: {}
+
+ events@3.3.0: {}
+
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ expect-type@1.3.0: {}
+
+ exponential-backoff@3.1.3: {}
+
+ extract-zip@2.0.1:
+ dependencies:
+ debug: 4.4.3
+ get-stream: 5.2.0
+ yauzl: 2.10.0
+ optionalDependencies:
+ '@types/yauzl': 2.10.3
+ transitivePeerDependencies:
+ - supports-color
+
+ extsprintf@1.4.1:
+ optional: true
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.19.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fd-slicer@1.1.0:
+ dependencies:
+ pend: 1.2.0
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fflate@0.8.2: {}
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ filelist@1.0.6:
+ dependencies:
+ minimatch: 5.1.9
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatted@3.3.3: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ foreground-child@3.3.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data@4.0.5:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.18
+
+ fraction.js@4.3.7: {}
+
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs-extra@11.3.4:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs-extra@7.0.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+
+ fs-extra@8.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+
+ fs-extra@9.1.0:
+ dependencies:
+ at-least-node: 1.0.0
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-own-enumerable-property-symbols@3.0.2: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-stream@5.2.0:
+ dependencies:
+ pump: 3.0.4
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ glob@11.1.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 4.2.3
+ minimatch: 10.2.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 2.0.2
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ global-agent@3.0.0:
+ dependencies:
+ boolean: 3.2.0
+ es6-error: 4.1.1
+ matcher: 3.0.0
+ roarr: 2.15.4
+ semver: 7.7.3
+ serialize-error: 7.0.1
+ optional: true
+
+ globals@14.0.0: {}
+
+ globals@16.4.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ gopd@1.2.0: {}
+
+ got@11.8.6:
+ dependencies:
+ '@sindresorhus/is': 4.6.0
+ '@szmarczak/http-timer': 4.0.6
+ '@types/cacheable-request': 6.0.3
+ '@types/responselike': 1.0.3
+ cacheable-lookup: 5.0.4
+ cacheable-request: 7.0.4
+ decompress-response: 6.0.0
+ http2-wrapper: 1.0.3
+ lowercase-keys: 2.0.0
+ p-cancelable: 2.1.1
+ responselike: 2.0.1
+
+ graceful-fs@4.2.11: {}
+
+ graphlib@2.1.8:
+ dependencies:
+ lodash: 4.17.21
+
+ graphology-types@0.24.8: {}
+
+ graphology@0.25.4(graphology-types@0.24.8):
+ dependencies:
+ events: 3.3.0
+ graphology-types: 0.24.8
+ obliterator: 2.0.5
+
+ has-bigints@1.1.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
+ hosted-git-info@4.1.0:
+ dependencies:
+ lru-cache: 6.0.0
+
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ html-encoding-sniffer@6.0.0:
+ dependencies:
+ '@exodus/bytes': 1.8.0
+ transitivePeerDependencies:
+ - '@exodus/crypto'
+
+ html-escaper@2.0.2: {}
+
+ http-cache-semantics@4.2.0: {}
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ http2-wrapper@1.0.3:
+ dependencies:
+ quick-lru: 5.1.1
+ resolve-alpn: 1.2.1
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@2.1.0: {}
+
+ i18next-http-backend@2.7.3:
+ dependencies:
+ cross-fetch: 4.0.0
+ transitivePeerDependencies:
+ - encoding
+
+ i18next@22.5.1:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ iconv-corefoundation@1.1.7:
+ dependencies:
+ cli-truncate: 2.1.0
+ node-addon-api: 1.7.2
+ optional: true
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ idb@7.1.1: {}
+
+ ieee754@1.2.1:
+ optional: true
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@4.0.0: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-docker@2.2.1: {}
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-map@2.0.3: {}
+
+ is-module@1.0.0: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-obj@1.0.1: {}
+
+ is-port-reachable@4.0.0: {}
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-regexp@1.0.0: {}
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@2.0.1: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.20
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-wsl@2.2.0:
+ dependencies:
+ is-docker: 2.2.1
+
+ isarray@2.0.5: {}
+
+ isbinaryfile@4.0.10: {}
+
+ isbinaryfile@5.0.7: {}
+
+ isexe@2.0.0: {}
+
+ isexe@3.1.5: {}
+
+ isexe@4.0.0: {}
+
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-reports@3.2.0:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jackspeak@4.2.3:
+ dependencies:
+ '@isaacs/cliui': 9.0.0
+
+ jake@10.9.4:
+ dependencies:
+ async: 3.2.6
+ filelist: 1.0.6
+ picocolors: 1.1.1
+
+ jiti@1.21.7: {}
+
+ jiti@2.7.0: {}
+
+ js-interpreter@6.0.1:
+ dependencies:
+ minimist: 1.2.8
+
+ js-tokens@10.0.0: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsdom@26.1.0:
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.22
+ parse5: 7.3.0
+ rrweb-cssom: 0.8.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.18.3
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsdom@27.4.0:
+ dependencies:
+ '@acemir/cssom': 0.9.30
+ '@asamuzakjp/dom-selector': 6.7.6
+ '@exodus/bytes': 1.8.0
+ cssstyle: 5.3.6
+ data-urls: 6.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 6.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ parse5: 8.0.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 6.0.0
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 8.0.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 15.1.0
+ ws: 8.18.3
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - '@exodus/crypto'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json-stringify-safe@5.0.1:
+ optional: true
+
+ json5@2.2.3: {}
+
+ jsonfile@4.0.0:
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsonpointer@5.0.1: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ lazy-val@1.0.5: {}
+
+ leven@3.1.0: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lightningcss-darwin-arm64@1.30.1:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.1:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.1:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ optional: true
+
+ lightningcss@1.30.1:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.30.1
+ lightningcss-darwin-x64: 1.30.1
+ lightningcss-freebsd-x64: 1.30.1
+ lightningcss-linux-arm-gnueabihf: 1.30.1
+ lightningcss-linux-arm64-gnu: 1.30.1
+ lightningcss-linux-arm64-musl: 1.30.1
+ lightningcss-linux-x64-gnu: 1.30.1
+ lightningcss-linux-x64-musl: 1.30.1
+ lightningcss-win32-arm64-msvc: 1.30.1
+ lightningcss-win32-x64-msvc: 1.30.1
+ optional: true
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.debounce@4.0.8: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash.sortby@4.7.0: {}
+
+ lodash@4.17.21: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lowercase-keys@2.0.0: {}
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@11.2.4: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+
+ lucide-react@0.546.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
+ lucide@0.546.0: {}
+
+ lz-string@1.5.0: {}
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ magicast@0.5.2:
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ source-map-js: 1.2.1
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.7.3
+
+ matcher@3.0.0:
+ dependencies:
+ escape-string-regexp: 4.0.0
+ optional: true
+
+ math-intrinsics@1.1.0: {}
+
+ mdn-data@2.0.28: {}
+
+ mdn-data@2.12.2: {}
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.33.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@2.1.18:
+ dependencies:
+ mime-db: 1.33.0
+
+ mime@2.6.0: {}
+
+ mimic-fn@2.1.0: {}
+
+ mimic-response@1.0.1: {}
+
+ mimic-response@3.1.0: {}
+
+ min-indent@1.0.1: {}
+
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.6
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist@1.2.8: {}
+
+ minipass@7.1.2: {}
+
+ minizlib@3.1.0:
+ dependencies:
+ minipass: 7.1.2
+
+ mkdirp@0.5.6:
+ dependencies:
+ minimist: 1.2.8
+
+ mrmime@2.0.1: {}
+
+ ms@2.0.0: {}
+
+ ms@2.1.3: {}
+
+ mustache@4.2.0: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.11: {}
+
+ natural-compare@1.4.0: {}
+
+ negotiator@0.6.4: {}
+
+ node-abi@4.31.0:
+ dependencies:
+ semver: 7.7.3
+
+ node-addon-api@1.7.2:
+ optional: true
+
+ node-api-version@0.2.1:
+ dependencies:
+ semver: 7.7.3
+
+ node-fetch@2.7.0:
+ dependencies:
+ whatwg-url: 5.0.0
+
+ node-gyp@12.3.0:
+ dependencies:
+ env-paths: 2.2.1
+ exponential-backoff: 3.1.3
+ graceful-fs: 4.2.11
+ nopt: 9.0.0
+ proc-log: 6.1.0
+ semver: 7.7.3
+ tar: 7.5.15
+ tinyglobby: 0.2.15
+ undici: 6.25.0
+ which: 6.0.1
+
+ node-releases@2.0.25: {}
+
+ node-releases@2.0.44: {}
+
+ nopt@9.0.0:
+ dependencies:
+ abbrev: 4.0.0
+
+ normalize-path@3.0.0: {}
+
+ normalize-range@0.1.2: {}
+
+ normalize-url@6.1.0: {}
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ nth-check@2.1.1:
+ dependencies:
+ boolbase: 1.0.0
+
+ nwsapi@2.2.22: {}
+
+ object-assign@4.1.1: {}
+
+ object-hash@3.0.0: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ obliterator@2.0.5: {}
+
+ obug@2.1.1: {}
+
+ on-headers@1.1.0: {}
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ open@8.4.2:
+ dependencies:
+ define-lazy-prop: 2.0.0
+ is-docker: 2.2.1
+ is-wsl: 2.2.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-cancelable@2.1.1: {}
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ package-json-from-dist@1.0.1: {}
+
+ papaparse@5.5.3: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ parse5@8.0.0:
+ dependencies:
+ entities: 6.0.1
+
+ path-exists@4.0.0: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-is-inside@1.0.2: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ path-scurry@2.0.2:
+ dependencies:
+ lru-cache: 11.2.4
+ minipass: 7.1.2
+
+ path-to-regexp@3.3.0: {}
+
+ pathe@2.0.3: {}
+
+ pe-library@0.4.1: {}
+
+ pend@1.2.0: {}
+
+ phaser3-rex-plugins@1.80.16(graphology-types@0.24.8):
+ dependencies:
+ dagre: 0.8.5
+ eventemitter3: 3.1.2
+ graphology: 0.25.4(graphology-types@0.24.8)
+ i18next: 22.5.1
+ i18next-http-backend: 2.7.3
+ js-yaml: 4.1.0
+ mustache: 4.2.0
+ papaparse: 5.5.3
+ webfontloader: 1.6.28
+ transitivePeerDependencies:
+ - encoding
+ - graphology-types
+
+ phaser@3.90.0:
+ dependencies:
+ eventemitter3: 5.0.1
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ pify@2.3.0: {}
+
+ pirates@4.0.7: {}
+
+ plist@3.1.0:
+ dependencies:
+ '@xmldom/xmldom': 0.8.13
+ base64-js: 1.5.1
+ xmlbuilder: 15.1.1
+
+ plist@3.1.1:
+ dependencies:
+ '@xmldom/xmldom': 0.9.10
+ base64-js: 1.5.1
+ xmlbuilder: 15.1.1
+ optional: true
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-import@15.1.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.10
+
+ postcss-js@4.1.0(postcss@8.5.6):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.6
+
+ postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.0):
+ dependencies:
+ lilconfig: 3.1.3
+ optionalDependencies:
+ jiti: 1.21.7
+ postcss: 8.5.6
+ yaml: 2.8.0
+
+ postcss-nested@6.2.0(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+ postcss-selector-parser: 6.1.2
+
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postject@1.0.0-alpha.6:
+ dependencies:
+ commander: 9.5.0
+ optional: true
+
+ prelude-ls@1.2.1: {}
+
+ prettier@3.8.1: {}
+
+ pretty-bytes@5.6.0: {}
+
+ pretty-bytes@6.1.1: {}
+
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+ proc-log@6.1.0: {}
+
+ progress@2.0.3: {}
+
+ promise-retry@2.0.1:
+ dependencies:
+ err-code: 2.0.3
+ retry: 0.12.0
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ proper-lockfile@4.1.2:
+ dependencies:
+ graceful-fs: 4.2.11
+ retry: 0.12.0
+ signal-exit: 3.0.7
+
+ pump@3.0.4:
+ dependencies:
+ end-of-stream: 1.4.5
+ once: 1.4.0
+
+ punycode@2.3.1: {}
+
+ queue-microtask@1.2.3: {}
+
+ quick-lru@5.1.1: {}
+
+ range-parser@1.2.0: {}
+
+ rc@1.2.8:
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.8
+ strip-json-comments: 2.0.1
+
+ react-confetti@6.4.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ tween-functions: 1.2.0
+
+ react-dom@19.2.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-refresh@0.17.0: {}
+
+ react-resizable-panels@3.0.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
+ react-router-dom@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ react-router: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+
+ react-router@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ dependencies:
+ cookie: 1.0.2
+ react: 19.2.0
+ set-cookie-parser: 2.7.1
+ optionalDependencies:
+ react-dom: 19.2.0(react@19.2.0)
+
+ react-shepherd@6.1.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3):
+ dependencies:
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+ shepherd.js: 14.5.1
+ typescript: 5.9.3
+
+ react@19.2.0: {}
+
+ read-binary-file-arch@1.0.6:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ read-cache@1.0.0:
+ dependencies:
+ pify: 2.3.0
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regenerate-unicode-properties@10.2.2:
+ dependencies:
+ regenerate: 1.4.2
+
+ regenerate@1.4.2: {}
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ regexpu-core@6.4.0:
+ dependencies:
+ regenerate: 1.4.2
+ regenerate-unicode-properties: 10.2.2
+ regjsgen: 0.8.0
+ regjsparser: 0.13.1
+ unicode-match-property-ecmascript: 2.0.0
+ unicode-match-property-value-ecmascript: 2.2.1
+
+ registry-auth-token@3.3.2:
+ dependencies:
+ rc: 1.2.8
+ safe-buffer: 5.2.1
+
+ registry-url@3.1.0:
+ dependencies:
+ rc: 1.2.8
+
+ regjsgen@0.8.0: {}
+
+ regjsparser@0.13.1:
+ dependencies:
+ jsesc: 3.1.0
+
+ require-directory@2.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ resedit@1.7.2:
+ dependencies:
+ pe-library: 0.4.1
+
+ resolve-alpn@1.2.1: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve@1.22.10:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@1.22.12:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ responselike@2.0.1:
+ dependencies:
+ lowercase-keys: 2.0.0
+
+ retry@0.12.0: {}
+
+ reusify@1.1.0: {}
+
+ rimraf@2.6.3:
+ dependencies:
+ glob: 7.2.3
+
+ rimraf@2.7.1:
+ dependencies:
+ glob: 7.2.3
+
+ roarr@2.15.4:
+ dependencies:
+ boolean: 3.2.0
+ detect-node: 2.1.0
+ globalthis: 1.0.4
+ json-stringify-safe: 5.0.1
+ semver-compare: 1.0.0
+ sprintf-js: 1.1.3
+ optional: true
+
+ rollup-plugin-visualizer@6.0.5(rollup@4.60.3):
+ dependencies:
+ open: 8.4.2
+ picomatch: 4.0.3
+ source-map: 0.7.6
+ yargs: 17.7.2
+ optionalDependencies:
+ rollup: 4.60.3
+
+ rollup@4.52.5:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.52.5
+ '@rollup/rollup-android-arm64': 4.52.5
+ '@rollup/rollup-darwin-arm64': 4.52.5
+ '@rollup/rollup-darwin-x64': 4.52.5
+ '@rollup/rollup-freebsd-arm64': 4.52.5
+ '@rollup/rollup-freebsd-x64': 4.52.5
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.5
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.5
+ '@rollup/rollup-linux-arm64-gnu': 4.52.5
+ '@rollup/rollup-linux-arm64-musl': 4.52.5
+ '@rollup/rollup-linux-loong64-gnu': 4.52.5
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.5
+ '@rollup/rollup-linux-riscv64-musl': 4.52.5
+ '@rollup/rollup-linux-s390x-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-gnu': 4.52.5
+ '@rollup/rollup-linux-x64-musl': 4.52.5
+ '@rollup/rollup-openharmony-arm64': 4.52.5
+ '@rollup/rollup-win32-arm64-msvc': 4.52.5
+ '@rollup/rollup-win32-ia32-msvc': 4.52.5
+ '@rollup/rollup-win32-x64-gnu': 4.52.5
+ '@rollup/rollup-win32-x64-msvc': 4.52.5
+ fsevents: 2.3.3
+
+ rollup@4.60.3:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.3
+ '@rollup/rollup-android-arm64': 4.60.3
+ '@rollup/rollup-darwin-arm64': 4.60.3
+ '@rollup/rollup-darwin-x64': 4.60.3
+ '@rollup/rollup-freebsd-arm64': 4.60.3
+ '@rollup/rollup-freebsd-x64': 4.60.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.3
+ '@rollup/rollup-linux-arm64-gnu': 4.60.3
+ '@rollup/rollup-linux-arm64-musl': 4.60.3
+ '@rollup/rollup-linux-loong64-gnu': 4.60.3
+ '@rollup/rollup-linux-loong64-musl': 4.60.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.3
+ '@rollup/rollup-linux-ppc64-musl': 4.60.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.3
+ '@rollup/rollup-linux-riscv64-musl': 4.60.3
+ '@rollup/rollup-linux-s390x-gnu': 4.60.3
+ '@rollup/rollup-linux-x64-gnu': 4.60.3
+ '@rollup/rollup-linux-x64-musl': 4.60.3
+ '@rollup/rollup-openbsd-x64': 4.60.3
+ '@rollup/rollup-openharmony-arm64': 4.60.3
+ '@rollup/rollup-win32-arm64-msvc': 4.60.3
+ '@rollup/rollup-win32-ia32-msvc': 4.60.3
+ '@rollup/rollup-win32-x64-gnu': 4.60.3
+ '@rollup/rollup-win32-x64-msvc': 4.60.3
+ fsevents: 2.3.3
+
+ rrweb-cssom@0.8.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
+ safe-array-concat@1.1.4:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safer-buffer@2.1.2: {}
+
+ sanitize-filename@1.6.4:
+ dependencies:
+ truncate-utf8-bytes: 1.0.2
+
+ sax@1.4.1: {}
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scheduler@0.27.0: {}
+
+ semver-compare@1.0.0:
+ optional: true
+
+ semver@5.7.2: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.3: {}
+
+ sentiment@5.0.2: {}
+
+ serialize-error@7.0.1:
+ dependencies:
+ type-fest: 0.13.1
+ optional: true
+
+ serialize-javascript@7.0.5: {}
+
+ serve-handler@6.1.6:
+ dependencies:
+ bytes: 3.0.0
+ content-disposition: 0.5.2
+ mime-types: 2.1.18
+ minimatch: 3.1.2
+ path-is-inside: 1.0.2
+ path-to-regexp: 3.3.0
+ range-parser: 1.2.0
+
+ serve@14.2.5:
+ dependencies:
+ '@zeit/schemas': 2.36.0
+ ajv: 8.12.0
+ arg: 5.0.2
+ boxen: 7.0.0
+ chalk: 5.0.1
+ chalk-template: 0.4.0
+ clipboardy: 3.0.0
+ compression: 1.8.1
+ is-port-reachable: 4.0.0
+ serve-handler: 6.1.6
+ update-check: 1.5.4
+ transitivePeerDependencies:
+ - supports-color
+
+ set-cookie-parser@2.7.1: {}
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ sharp@0.34.4:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.3
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.4
+ '@img/sharp-darwin-x64': 0.34.4
+ '@img/sharp-libvips-darwin-arm64': 1.2.3
+ '@img/sharp-libvips-darwin-x64': 1.2.3
+ '@img/sharp-libvips-linux-arm': 1.2.3
+ '@img/sharp-libvips-linux-arm64': 1.2.3
+ '@img/sharp-libvips-linux-ppc64': 1.2.3
+ '@img/sharp-libvips-linux-s390x': 1.2.3
+ '@img/sharp-libvips-linux-x64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.3
+ '@img/sharp-linux-arm': 0.34.4
+ '@img/sharp-linux-arm64': 0.34.4
+ '@img/sharp-linux-ppc64': 0.34.4
+ '@img/sharp-linux-s390x': 0.34.4
+ '@img/sharp-linux-x64': 0.34.4
+ '@img/sharp-linuxmusl-arm64': 0.34.4
+ '@img/sharp-linuxmusl-x64': 0.34.4
+ '@img/sharp-wasm32': 0.34.4
+ '@img/sharp-win32-arm64': 0.34.4
+ '@img/sharp-win32-ia32': 0.34.4
+ '@img/sharp-win32-x64': 0.34.4
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ shell-quote@1.8.3: {}
+
+ shepherd.js@14.5.1:
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ '@scarf/scarf': 1.4.0
+ deepmerge-ts: 7.1.5
+
+ side-channel-list@1.0.1:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.1
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ siginfo@2.0.0: {}
+
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.1.0: {}
+
+ simple-update-notifier@2.0.0:
+ dependencies:
+ semver: 7.7.3
+
+ sirv@3.0.2:
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
+
+ slice-ansi@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+ optional: true
+
+ smart-buffer@4.2.0:
+ optional: true
+
+ smob@1.6.1: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
+ source-map@0.7.6: {}
+
+ source-map@0.8.0-beta.0:
+ dependencies:
+ whatwg-url: 7.1.0
+
+ sprintf-js@1.1.3:
+ optional: true
+
+ stackback@0.0.2: {}
+
+ stat-mode@1.0.0: {}
+
+ std-env@3.10.0: {}
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.2
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.0
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.2
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.9
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ stringify-object@3.3.0:
+ dependencies:
+ get-own-enumerable-property-symbols: 3.0.2
+ is-obj: 1.0.1
+ is-regexp: 1.0.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.2:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-comments@2.0.1: {}
+
+ strip-final-newline@2.0.0: {}
+
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
+ strip-json-comments@2.0.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ style-mod@4.1.3: {}
+
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ ts-interface-checker: 0.1.13
+
+ sumchecker@3.0.1:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ svgo@4.0.0:
+ dependencies:
+ commander: 11.1.0
+ css-select: 5.2.2
+ css-tree: 3.1.0
+ css-what: 6.2.2
+ csso: 5.0.5
+ picocolors: 1.1.1
+ sax: 1.4.1
+
+ symbol-tree@3.2.4: {}
+
+ tailwindcss@3.4.18(yaml@2.8.0):
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-import: 15.1.0(postcss@8.5.6)
+ postcss-js: 4.1.0(postcss@8.5.6)
+ postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.0)
+ postcss-nested: 6.2.0(postcss@8.5.6)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - tsx
+ - yaml
+
+ tar@7.5.15:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.1.0
+ yallist: 5.0.0
+
+ temp-dir@2.0.0: {}
+
+ temp-file@3.4.0:
+ dependencies:
+ async-exit-hook: 2.0.1
+ fs-extra: 10.1.0
+
+ temp@0.9.4:
+ dependencies:
+ mkdirp: 0.5.6
+ rimraf: 2.6.3
+
+ tempy@0.6.0:
+ dependencies:
+ is-stream: 2.0.1
+ temp-dir: 2.0.0
+ type-fest: 0.16.0
+ unique-string: 2.0.0
+
+ terser@5.44.0:
+ dependencies:
+ '@jridgewell/source-map': 0.3.11
+ acorn: 8.16.0
+ commander: 2.20.3
+ source-map-support: 0.5.21
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ tiny-async-pool@1.3.0:
+ dependencies:
+ semver: 5.7.2
+
+ tinybench@2.9.0: {}
+
+ tinyexec@1.0.2: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tinyrainbow@3.0.3: {}
+
+ tldts-core@6.1.86: {}
+
+ tldts-core@7.0.19: {}
+
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
+
+ tldts@7.0.19:
+ dependencies:
+ tldts-core: 7.0.19
+
+ tmp-promise@3.0.3:
+ dependencies:
+ tmp: 0.2.5
+
+ tmp@0.2.5: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ totalist@3.0.1: {}
+
+ tough-cookie@5.1.2:
+ dependencies:
+ tldts: 6.1.86
+
+ tough-cookie@6.0.0:
+ dependencies:
+ tldts: 7.0.19
+
+ tr46@0.0.3: {}
+
+ tr46@1.0.1:
+ dependencies:
+ punycode: 2.3.1
+
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
+ tr46@6.0.0:
+ dependencies:
+ punycode: 2.3.1
+
+ tree-kill@1.2.2: {}
+
+ truncate-utf8-bytes@1.0.2:
+ dependencies:
+ utf8-byte-length: 1.0.5
+
+ ts-interface-checker@0.1.13: {}
+
+ tslib@2.8.1: {}
+
+ tween-functions@1.2.0: {}
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-fest@0.13.1:
+ optional: true
+
+ type-fest@0.16.0: {}
+
+ type-fest@2.19.0: {}
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.9
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript@5.9.3: {}
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@7.16.0: {}
+
+ undici@6.25.0: {}
+
+ undici@7.25.0:
+ optional: true
+
+ unicode-canonical-property-names-ecmascript@2.0.1: {}
+
+ unicode-match-property-ecmascript@2.0.0:
+ dependencies:
+ unicode-canonical-property-names-ecmascript: 2.0.1
+ unicode-property-aliases-ecmascript: 2.2.0
+
+ unicode-match-property-value-ecmascript@2.2.1: {}
+
+ unicode-property-aliases-ecmascript@2.2.0: {}
+
+ unique-string@2.0.0:
+ dependencies:
+ crypto-random-string: 2.0.0
+
+ universalify@0.1.2: {}
+
+ universalify@2.0.1: {}
+
+ upath@1.2.0: {}
+
+ update-browserslist-db@1.1.3(browserslist@4.26.3):
+ dependencies:
+ browserslist: 4.26.3
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ update-browserslist-db@1.2.3(browserslist@4.28.2):
+ dependencies:
+ browserslist: 4.28.2
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ update-check@1.5.4:
+ dependencies:
+ registry-auth-token: 3.3.2
+ registry-url: 3.1.0
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ utf8-byte-length@1.0.5: {}
+
+ util-deprecate@1.0.2: {}
+
+ vary@1.1.2: {}
+
+ verror@1.10.1:
+ dependencies:
+ assert-plus: 1.0.0
+ core-util-is: 1.0.2
+ extsprintf: 1.4.1
+ optional: true
+
+ vite-plugin-image-optimizer@2.0.2(sharp@0.34.4)(svgo@4.0.0)(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)):
+ dependencies:
+ ansi-colors: 4.1.3
+ pathe: 2.0.3
+ vite: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+ optionalDependencies:
+ sharp: 0.34.4
+ svgo: 4.0.0
+
+ vite-plugin-pwa@0.20.5(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))(workbox-build@7.4.1(@types/babel__core@7.20.5))(workbox-window@7.4.1):
+ dependencies:
+ debug: 4.4.3
+ pretty-bytes: 6.1.1
+ tinyglobby: 0.2.15
+ vite: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+ workbox-build: 7.4.1(@types/babel__core@7.20.5)
+ workbox-window: 7.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0):
+ dependencies:
+ esbuild: 0.25.11
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.52.5
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.9.0
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ lightningcss: 1.30.1
+ terser: 5.44.0
+ yaml: 2.8.0
+
+ vitest@4.0.16(@types/node@24.9.0)(@vitest/ui@4.0.16)(jiti@1.21.7)(jsdom@27.4.0)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0):
+ dependencies:
+ '@vitest/expect': 4.0.16
+ '@vitest/mocker': 4.0.16(vite@7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0))
+ '@vitest/pretty-format': 4.0.16
+ '@vitest/runner': 4.0.16
+ '@vitest/snapshot': 4.0.16
+ '@vitest/spy': 4.0.16
+ '@vitest/utils': 4.0.16
+ es-module-lexer: 1.7.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.0.3
+ vite: 7.1.10(@types/node@24.9.0)(jiti@1.21.7)(lightningcss@1.30.1)(terser@5.44.0)(yaml@2.8.0)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 24.9.0
+ '@vitest/ui': 4.0.16(vitest@4.0.16)
+ jsdom: 27.4.0
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - terser
+ - tsx
+ - yaml
+
+ w3c-keyname@2.2.8: {}
+
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
+ webfontloader@1.6.28: {}
+
+ webidl-conversions@3.0.1: {}
+
+ webidl-conversions@4.0.2: {}
+
+ webidl-conversions@7.0.0: {}
+
+ webidl-conversions@8.0.1: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
+ whatwg-url@15.1.0:
+ dependencies:
+ tr46: 6.0.0
+ webidl-conversions: 8.0.1
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ whatwg-url@7.1.0:
+ dependencies:
+ lodash.sortby: 4.7.0
+ tr46: 1.0.1
+ webidl-conversions: 4.0.2
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.20
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-typed-array@1.1.20:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.9
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ which@5.0.0:
+ dependencies:
+ isexe: 3.1.5
+
+ which@6.0.1:
+ dependencies:
+ isexe: 4.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ widest-line@4.0.1:
+ dependencies:
+ string-width: 5.1.2
+
+ word-wrap@1.2.5: {}
+
+ workbox-background-sync@7.4.1:
+ dependencies:
+ idb: 7.1.1
+ workbox-core: 7.4.1
+
+ workbox-broadcast-update@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-build@7.4.1(@types/babel__core@7.20.5):
+ dependencies:
+ '@apideck/better-ajv-errors': 0.3.7(ajv@8.12.0)
+ '@babel/core': 7.28.4
+ '@babel/preset-env': 7.29.5(@babel/core@7.28.4)
+ '@babel/runtime': 7.28.4
+ '@rollup/plugin-babel': 6.1.0(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@4.60.3)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.3)
+ '@rollup/plugin-replace': 6.0.3(rollup@4.60.3)
+ '@rollup/plugin-terser': 1.0.0(rollup@4.60.3)
+ '@trickfilm400/rollup-plugin-off-main-thread': 3.0.0-pre1
+ ajv: 8.12.0
+ common-tags: 1.8.2
+ eta: 4.6.0
+ fast-json-stable-stringify: 2.1.0
+ fs-extra: 9.1.0
+ glob: 11.1.0
+ pretty-bytes: 5.6.0
+ rollup: 4.60.3
+ source-map: 0.8.0-beta.0
+ stringify-object: 3.3.0
+ strip-comments: 2.0.1
+ tempy: 0.6.0
+ upath: 1.2.0
+ workbox-background-sync: 7.4.1
+ workbox-broadcast-update: 7.4.1
+ workbox-cacheable-response: 7.4.1
+ workbox-core: 7.4.1
+ workbox-expiration: 7.4.1
+ workbox-google-analytics: 7.4.1
+ workbox-navigation-preload: 7.4.1
+ workbox-precaching: 7.4.1
+ workbox-range-requests: 7.4.1
+ workbox-recipes: 7.4.1
+ workbox-routing: 7.4.1
+ workbox-strategies: 7.4.1
+ workbox-streams: 7.4.1
+ workbox-sw: 7.4.1
+ workbox-window: 7.4.1
+ transitivePeerDependencies:
+ - '@types/babel__core'
+ - supports-color
+
+ workbox-cacheable-response@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-core@7.4.1: {}
+
+ workbox-expiration@7.4.1:
+ dependencies:
+ idb: 7.1.1
+ workbox-core: 7.4.1
+
+ workbox-google-analytics@7.4.1:
+ dependencies:
+ workbox-background-sync: 7.4.1
+ workbox-core: 7.4.1
+ workbox-routing: 7.4.1
+ workbox-strategies: 7.4.1
+
+ workbox-navigation-preload@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-precaching@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+ workbox-routing: 7.4.1
+ workbox-strategies: 7.4.1
+
+ workbox-range-requests@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-recipes@7.4.1:
+ dependencies:
+ workbox-cacheable-response: 7.4.1
+ workbox-core: 7.4.1
+ workbox-expiration: 7.4.1
+ workbox-precaching: 7.4.1
+ workbox-routing: 7.4.1
+ workbox-strategies: 7.4.1
+
+ workbox-routing@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-strategies@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+
+ workbox-streams@7.4.1:
+ dependencies:
+ workbox-core: 7.4.1
+ workbox-routing: 7.4.1
+
+ workbox-sw@7.4.1: {}
+
+ workbox-window@7.4.1:
+ dependencies:
+ '@types/trusted-types': 2.0.7
+ workbox-core: 7.4.1
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 5.1.2
+ strip-ansi: 7.1.2
+
+ wrappy@1.0.2: {}
+
+ ws@8.18.3: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlbuilder@15.1.1: {}
+
+ xmlchars@2.2.0: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yallist@4.0.0: {}
+
+ yallist@5.0.0: {}
+
+ yaml@2.8.0:
+ optional: true
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yauzl@2.10.0:
+ dependencies:
+ buffer-crc32: 0.2.13
+ fd-slicer: 1.1.0
+
+ yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.1.12):
+ dependencies:
+ zod: 4.1.12
+
+ zod@4.1.12: {}
diff --git a/app/pnpm-workspace.yaml b/app/pnpm-workspace.yaml
new file mode 100644
index 0000000..13a6528
--- /dev/null
+++ b/app/pnpm-workspace.yaml
@@ -0,0 +1,5 @@
+packages:
+ - "docs"
+
+ignoredBuiltDependencies:
+ - sharp
diff --git a/app/postcss.config.js b/app/postcss.config.js
new file mode 100644
index 0000000..2aa7205
--- /dev/null
+++ b/app/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/app/public/examples/contador.json b/app/public/examples/contador.json
new file mode 100644
index 0000000..8ba5905
--- /dev/null
+++ b/app/public/examples/contador.json
@@ -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@"
+ }
+ ]
+}
diff --git a/app/public/examples/fatorial.json b/app/public/examples/fatorial.json
new file mode 100644
index 0000000..6c6c951
--- /dev/null
+++ b/app/public/examples/fatorial.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/examples/fibonacci.json b/app/public/examples/fibonacci.json
new file mode 100644
index 0000000..0a7acba
--- /dev/null
+++ b/app/public/examples/fibonacci.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/examples/maior-numero.json b/app/public/examples/maior-numero.json
new file mode 100644
index 0000000..3813f66
--- /dev/null
+++ b/app/public/examples/maior-numero.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/examples/nome.json b/app/public/examples/nome.json
new file mode 100644
index 0000000..d492aa6
--- /dev/null
+++ b/app/public/examples/nome.json
@@ -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"
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/app/public/examples/par-impar.json b/app/public/examples/par-impar.json
new file mode 100644
index 0000000..c2a37f3
--- /dev/null
+++ b/app/public/examples/par-impar.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/examples/soma-lista.json b/app/public/examples/soma-lista.json
new file mode 100644
index 0000000..7cda29e
--- /dev/null
+++ b/app/public/examples/soma-lista.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/examples/temperatura.json b/app/public/examples/temperatura.json
new file mode 100644
index 0000000..add6fd5
--- /dev/null
+++ b/app/public/examples/temperatura.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/health.json b/app/public/health.json
new file mode 100644
index 0000000..0c5646a
--- /dev/null
+++ b/app/public/health.json
@@ -0,0 +1,3 @@
+{
+ "status": "UP"
+}
diff --git a/app/public/images/atividades/programacao/aspirador-thumbnail.png b/app/public/images/atividades/programacao/aspirador-thumbnail.png
new file mode 100644
index 0000000..73c9752
Binary files /dev/null and b/app/public/images/atividades/programacao/aspirador-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/automato-thumbnail.png b/app/public/images/atividades/programacao/automato-thumbnail.png
new file mode 100644
index 0000000..f03c837
Binary files /dev/null and b/app/public/images/atividades/programacao/automato-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/cripto-thumbnail.png b/app/public/images/atividades/programacao/cripto-thumbnail.png
new file mode 100644
index 0000000..f7e78db
Binary files /dev/null and b/app/public/images/atividades/programacao/cripto-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/molemash-thumbnail.png b/app/public/images/atividades/programacao/molemash-thumbnail.png
new file mode 100644
index 0000000..641126d
Binary files /dev/null and b/app/public/images/atividades/programacao/molemash-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/ordenacao-thumbnail.png b/app/public/images/atividades/programacao/ordenacao-thumbnail.png
new file mode 100644
index 0000000..8a8e269
Binary files /dev/null and b/app/public/images/atividades/programacao/ordenacao-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/puzzle-thumbnail.png b/app/public/images/atividades/programacao/puzzle-thumbnail.png
new file mode 100644
index 0000000..fc0a59f
Binary files /dev/null and b/app/public/images/atividades/programacao/puzzle-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/semaforo-thumbnail.png b/app/public/images/atividades/programacao/semaforo-thumbnail.png
new file mode 100644
index 0000000..b28918f
Binary files /dev/null and b/app/public/images/atividades/programacao/semaforo-thumbnail.png differ
diff --git a/app/public/images/atividades/programacao/turtle-thumbnail.png b/app/public/images/atividades/programacao/turtle-thumbnail.png
new file mode 100644
index 0000000..8092436
Binary files /dev/null and b/app/public/images/atividades/programacao/turtle-thumbnail.png differ
diff --git a/app/public/img/logo.png b/app/public/img/logo.png
new file mode 100644
index 0000000..d52ee4f
Binary files /dev/null and b/app/public/img/logo.png differ
diff --git a/app/public/img/logo_192x192.png b/app/public/img/logo_192x192.png
new file mode 100644
index 0000000..d52ee4f
Binary files /dev/null and b/app/public/img/logo_192x192.png differ
diff --git a/app/public/img/logo_512x512.png b/app/public/img/logo_512x512.png
new file mode 100644
index 0000000..8d584d2
Binary files /dev/null and b/app/public/img/logo_512x512.png differ
diff --git a/app/public/img/logo_decoda.png b/app/public/img/logo_decoda.png
new file mode 100644
index 0000000..e45bf27
Binary files /dev/null and b/app/public/img/logo_decoda.png differ
diff --git a/app/public/img/logo_nav.png b/app/public/img/logo_nav.png
new file mode 100644
index 0000000..fa3df1a
Binary files /dev/null and b/app/public/img/logo_nav.png differ
diff --git a/app/public/manifest.json b/app/public/manifest.json
new file mode 100644
index 0000000..47aae3e
--- /dev/null
+++ b/app/public/manifest.json
@@ -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"
+ }
+ ]
+}
diff --git a/app/public/offline.html b/app/public/offline.html
new file mode 100644
index 0000000..c18dfff
--- /dev/null
+++ b/app/public/offline.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+ Decoda - Offline
+
+
+
+
+
+
+
+
Não foi possível carregar a página
+
Não conseguimos carregar esta página neste momento.
+
Conecte-se à internet para continuar ou volte para a tela inicial.
+
+
+
diff --git a/app/public/puzzle.svg b/app/public/puzzle.svg
new file mode 100644
index 0000000..728b2da
--- /dev/null
+++ b/app/public/puzzle.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/scripts/test-cache.mjs b/app/scripts/test-cache.mjs
new file mode 100644
index 0000000..831c56c
--- /dev/null
+++ b/app/scripts/test-cache.mjs
@@ -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);
+});
diff --git a/app/serve.json b/app/serve.json
new file mode 100644
index 0000000..ece6042
--- /dev/null
+++ b/app/serve.json
@@ -0,0 +1,5 @@
+{
+ "public": "dist",
+ "cleanUrls": false,
+ "rewrites": [{ "source": "!docs/**", "destination": "/index.html" }]
+}
diff --git a/app/src/App.css b/app/src/App.css
new file mode 100644
index 0000000..3e6c0ca
--- /dev/null
+++ b/app/src/App.css
@@ -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;
+}
diff --git a/app/src/App.jsx b/app/src/App.jsx
new file mode 100644
index 0000000..d56cb15
--- /dev/null
+++ b/app/src/App.jsx
@@ -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 = () => (
+
+);
+
+// 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 (
+ <>
+
+ {/* Main routes — rendered at the background location when a modal is open */}
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ {/* Fallback: direct URL access without backgroundLocation */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ {/* Modal overlay routes — rendered on top of the background page */}
+ {backgroundLocation && (
+
+ } />
+
+ )}
+ >
+ );
+}
+
+export default function App() {
+ return (
+
+ }>
+
+
+
+ );
+}
+
+// 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.
diff --git a/app/src/assets/baner_quemsomos.png b/app/src/assets/baner_quemsomos.png
new file mode 100644
index 0000000..aa76e4a
Binary files /dev/null and b/app/src/assets/baner_quemsomos.png differ
diff --git a/app/src/assets/banner_quemsomos_mobile.png b/app/src/assets/banner_quemsomos_mobile.png
new file mode 100644
index 0000000..90a5fee
Binary files /dev/null and b/app/src/assets/banner_quemsomos_mobile.png differ
diff --git a/app/src/assets/car.png b/app/src/assets/car.png
new file mode 100644
index 0000000..efd540e
Binary files /dev/null and b/app/src/assets/car.png differ
diff --git a/app/src/assets/fail.mp3 b/app/src/assets/fail.mp3
new file mode 100644
index 0000000..cd8cc21
Binary files /dev/null and b/app/src/assets/fail.mp3 differ
diff --git a/app/src/assets/fonts/SF Slapstick Comic Shaded.ttf b/app/src/assets/fonts/SF Slapstick Comic Shaded.ttf
new file mode 100644
index 0000000..c845abd
Binary files /dev/null and b/app/src/assets/fonts/SF Slapstick Comic Shaded.ttf differ
diff --git a/app/src/assets/game_loop.mp3 b/app/src/assets/game_loop.mp3
new file mode 100644
index 0000000..f59f574
Binary files /dev/null and b/app/src/assets/game_loop.mp3 differ
diff --git a/app/src/assets/icon_cabeca.svg b/app/src/assets/icon_cabeca.svg
new file mode 100644
index 0000000..305876e
--- /dev/null
+++ b/app/src/assets/icon_cabeca.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/icon_circulo.svg b/app/src/assets/icon_circulo.svg
new file mode 100644
index 0000000..03fade3
--- /dev/null
+++ b/app/src/assets/icon_circulo.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/icon_elo.svg b/app/src/assets/icon_elo.svg
new file mode 100644
index 0000000..f6130a6
--- /dev/null
+++ b/app/src/assets/icon_elo.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/icon_livro.svg b/app/src/assets/icon_livro.svg
new file mode 100644
index 0000000..104da88
--- /dev/null
+++ b/app/src/assets/icon_livro.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/icon_mao.svg b/app/src/assets/icon_mao.svg
new file mode 100644
index 0000000..6cb70df
--- /dev/null
+++ b/app/src/assets/icon_mao.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/icon_pasta.svg b/app/src/assets/icon_pasta.svg
new file mode 100644
index 0000000..1f1892a
--- /dev/null
+++ b/app/src/assets/icon_pasta.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/assets/iniciativas-banner-mobile.png b/app/src/assets/iniciativas-banner-mobile.png
new file mode 100644
index 0000000..90a5fee
Binary files /dev/null and b/app/src/assets/iniciativas-banner-mobile.png differ
diff --git a/app/src/assets/logo_decoda.svg b/app/src/assets/logo_decoda.svg
new file mode 100644
index 0000000..819dca9
--- /dev/null
+++ b/app/src/assets/logo_decoda.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/assets/logont.svg b/app/src/assets/logont.svg
new file mode 100644
index 0000000..be65355
--- /dev/null
+++ b/app/src/assets/logont.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/assets/motoca.png b/app/src/assets/motoca.png
new file mode 100644
index 0000000..d6cd95e
Binary files /dev/null and b/app/src/assets/motoca.png differ
diff --git a/app/src/assets/police.png b/app/src/assets/police.png
new file mode 100644
index 0000000..d161159
Binary files /dev/null and b/app/src/assets/police.png differ
diff --git a/app/src/assets/react.svg b/app/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/app/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/assets/truck.png b/app/src/assets/truck.png
new file mode 100644
index 0000000..e9c62e9
Binary files /dev/null and b/app/src/assets/truck.png differ
diff --git a/app/src/assets/win.mp3 b/app/src/assets/win.mp3
new file mode 100644
index 0000000..2a0ff1b
Binary files /dev/null and b/app/src/assets/win.mp3 differ
diff --git a/app/src/atividades/letramento/letramentoRegistry.js b/app/src/atividades/letramento/letramentoRegistry.js
new file mode 100644
index 0000000..e7a866f
--- /dev/null
+++ b/app/src/atividades/letramento/letramentoRegistry.js
@@ -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;
+}
diff --git a/app/src/atividades/letramento/mouse/mouse-arrastar/activity.js b/app/src/atividades/letramento/mouse/mouse-arrastar/activity.js
new file mode 100644
index 0000000..2ab8388
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-arrastar/activity.js
@@ -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 = `
+
+ Meus Arquivos
+`;
+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 = `
+
+ ${file.name}
+ `;
+ 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 });
+ }
+ }
+});
diff --git a/app/src/atividades/letramento/mouse/mouse-arrastar/index.html b/app/src/atividades/letramento/mouse/mouse-arrastar/index.html
new file mode 100644
index 0000000..fcc6c41
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-arrastar/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Mouse - Arrastar
+
+
+
+
+
+
+
+
+
+
Arraste os arquivos para a pasta
+
+
+
+
+
+
+
Muito bem!
+
Você aprendeu a arrastar!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-basico/activity.js b/app/src/atividades/letramento/mouse/mouse-basico/activity.js
new file mode 100644
index 0000000..e21e331
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-basico/activity.js
@@ -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);
diff --git a/app/src/atividades/letramento/mouse/mouse-basico/index.html b/app/src/atividades/letramento/mouse/mouse-basico/index.html
new file mode 100644
index 0000000..cb0ab14
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-basico/index.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+ Mouse - Básico
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Cobertura da área:
+
0%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-botao-direito/activity.js b/app/src/atividades/letramento/mouse/mouse-botao-direito/activity.js
new file mode 100644
index 0000000..abec25f
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-botao-direito/activity.js
@@ -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 = `
+
+
+
Documento.txt
+
Clique com botão direito aqui
+
+`;
+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 = `
+
+
+ Abrir
+
+
+
+ Copiar
+
+
+
+ Excluir
+
+ `;
+ 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');
+});
diff --git a/app/src/atividades/letramento/mouse/mouse-botao-direito/index.html b/app/src/atividades/letramento/mouse/mouse-botao-direito/index.html
new file mode 100644
index 0000000..ff7a3f7
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-botao-direito/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Mouse - Botão Direito
+
+
+
+
+
+
+
+
+
+
Clique com botão direito
+
+
+
+
+
+
+
Muito bem!
+
Você aprendeu o botão direito!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-clique-multiplo/activity.js b/app/src/atividades/letramento/mouse/mouse-clique-multiplo/activity.js
new file mode 100644
index 0000000..1f703fd
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-clique-multiplo/activity.js
@@ -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();
diff --git a/app/src/atividades/letramento/mouse/mouse-clique-multiplo/index.html b/app/src/atividades/letramento/mouse/mouse-clique-multiplo/index.html
new file mode 100644
index 0000000..a899573
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-clique-multiplo/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ Mouse - Múltiplos Cliques
+
+
+
+
+
+
+
+
+
+
Clique em todos os círculos vermelhos
+
+
+
+
+
+
+
Muito bem!
+
Você clicou em todos!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-completo/activity.js b/app/src/atividades/letramento/mouse/mouse-completo/activity.js
new file mode 100644
index 0000000..1a89d37
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-completo/activity.js
@@ -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 = `${targets + 1} `;
+
+ 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 = ' ';
+
+ // 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 = '
';
+ 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 = 'arquivo.txt ';
+ 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 = `
+
+ Abrir
+
+
+ Copiar
+
+
+ Excluir
+
+
+ Propriedades ✓
+
+ `;
+ 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 = `${folder.label} `;
+ 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 = `${file.name} `;
+ 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();
diff --git a/app/src/atividades/letramento/mouse/mouse-completo/index.html b/app/src/atividades/letramento/mouse/mouse-completo/index.html
new file mode 100644
index 0000000..22cf346
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-completo/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Mouse - Desafio Completo
+
+
+
+
+
+
+
+
+
+
Desafio Completo
+
+
+
+
+
+
+
Parabéns!
+
Você dominou todas as habilidades do mouse!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-controle/activity.js b/app/src/atividades/letramento/mouse/mouse-controle/activity.js
new file mode 100644
index 0000000..d90d334
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-controle/activity.js
@@ -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 = ' ';
+};
+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 });
+}
diff --git a/app/src/atividades/letramento/mouse/mouse-controle/index.html b/app/src/atividades/letramento/mouse/mouse-controle/index.html
new file mode 100644
index 0000000..2c0fd54
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-controle/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+ Mouse - Controle
+
+
+
+
+
+
+
+
+
+
Clique no círculo vermelho e siga o caminho
+
+
+
+
+
+
+
+
+
Muito bem!
+
Você tem ótimo controle do mouse!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-desenhar/activity.js b/app/src/atividades/letramento/mouse/mouse-desenhar/activity.js
new file mode 100644
index 0000000..c96cb19
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-desenhar/activity.js
@@ -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 });
+}
diff --git a/app/src/atividades/letramento/mouse/mouse-desenhar/index.html b/app/src/atividades/letramento/mouse/mouse-desenhar/index.html
new file mode 100644
index 0000000..ead236a
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-desenhar/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Mouse - Desenhar
+
+
+
+
+
+
+
+
+
+
Desenhe seguindo a linha pontilhada
+
+
+
+
+
+
+
Muito bem!
+
Você completou o traçado!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-precisao/activity.js b/app/src/atividades/letramento/mouse/mouse-precisao/activity.js
new file mode 100644
index 0000000..b3843e5
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-precisao/activity.js
@@ -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 = '
';
+ 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();
diff --git a/app/src/atividades/letramento/mouse/mouse-precisao/index.html b/app/src/atividades/letramento/mouse/mouse-precisao/index.html
new file mode 100644
index 0000000..847d005
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-precisao/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Mouse - Precisão
+
+
+
+
+
+
+
+
+
+
Clique no centro dos alvos
+
+
+
+
+
+
+
Muito bem!
+
Sua precisão está excelente!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-sequencia/activity.js b/app/src/atividades/letramento/mouse/mouse-sequencia/activity.js
new file mode 100644
index 0000000..45d1a59
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-sequencia/activity.js
@@ -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();
diff --git a/app/src/atividades/letramento/mouse/mouse-sequencia/index.html b/app/src/atividades/letramento/mouse/mouse-sequencia/index.html
new file mode 100644
index 0000000..2ccc61a
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-sequencia/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Mouse - Sequência
+
+
+
+
+
+
+
+
+
+
Clique na ordem: 1 → 2 → 3 → 4 → 5
+
+
+
+
+
+
+
Muito bem!
+
Você acertou a sequência!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouse-velocidade/activity.js b/app/src/atividades/letramento/mouse/mouse-velocidade/activity.js
new file mode 100644
index 0000000..68dadfc
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-velocidade/activity.js
@@ -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 });
+}
diff --git a/app/src/atividades/letramento/mouse/mouse-velocidade/index.html b/app/src/atividades/letramento/mouse/mouse-velocidade/index.html
new file mode 100644
index 0000000..6f7e6de
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouse-velocidade/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Mouse - Velocidade
+
+
+
+
+
+
+
+
+
+
Clique rápido antes que desapareçam!
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/mouse/mouseRegistry.js b/app/src/atividades/letramento/mouse/mouseRegistry.js
new file mode 100644
index 0000000..67a6014
--- /dev/null
+++ b/app/src/atividades/letramento/mouse/mouseRegistry.js
@@ -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' },
+ ],
+ },
+};
diff --git a/app/src/atividades/letramento/shared/letramento.css b/app/src/atividades/letramento/shared/letramento.css
new file mode 100644
index 0000000..85645e6
--- /dev/null
+++ b/app/src/atividades/letramento/shared/letramento.css
@@ -0,0 +1,8109 @@
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+/*
+! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: Lato, system-ui, sans-serif;
+ /* 4 */
+ font-feature-settings: normal;
+ /* 5 */
+ font-variation-settings: normal;
+ /* 6 */
+ -webkit-tap-highlight-color: transparent;
+ /* 7 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: JetBrains Mono, monospace;
+ /* 1 */
+ font-feature-settings: normal;
+ /* 2 */
+ font-variation-settings: normal;
+ /* 3 */
+ font-size: 1em;
+ /* 4 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-feature-settings: inherit;
+ /* 1 */
+ font-variation-settings: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ letter-spacing: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+input:where([type='button']),
+input:where([type='reset']),
+input:where([type='submit']) {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden]:where(:not([hidden="until-found"])) {
+ display: none;
+}
+
+.\!container {
+ width: 100% !important;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .\!container {
+ max-width: 640px !important;
+ }
+
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .\!container {
+ max-width: 768px !important;
+ }
+
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .\!container {
+ max-width: 1024px !important;
+ }
+
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .\!container {
+ max-width: 1280px !important;
+ }
+
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .\!container {
+ max-width: 1536px !important;
+ }
+
+ .container {
+ max-width: 1536px;
+ }
+}
+
+.pointer-events-none {
+ pointer-events: none;
+}
+
+.visible {
+ visibility: visible;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.static {
+ position: static;
+}
+
+.fixed {
+ position: fixed;
+}
+
+.absolute {
+ position: absolute;
+}
+
+.relative {
+ position: relative;
+}
+
+.sticky {
+ position: sticky;
+}
+
+.inset-0 {
+ inset: 0px;
+}
+
+.inset-y-0 {
+ top: 0px;
+ bottom: 0px;
+}
+
+.bottom-0 {
+ bottom: 0px;
+}
+
+.bottom-10 {
+ bottom: 2.5rem;
+}
+
+.left-0 {
+ left: 0px;
+}
+
+.left-1\/2 {
+ left: 50%;
+}
+
+.left-32 {
+ left: 8rem;
+}
+
+.left-6 {
+ left: 1.5rem;
+}
+
+.right-0 {
+ right: 0px;
+}
+
+.right-10 {
+ right: 2.5rem;
+}
+
+.right-32 {
+ right: 8rem;
+}
+
+.right-4 {
+ right: 1rem;
+}
+
+.top-0 {
+ top: 0px;
+}
+
+.top-1\/2 {
+ top: 50%;
+}
+
+.top-4 {
+ top: 1rem;
+}
+
+.top-full {
+ top: 100%;
+}
+
+.isolate {
+ isolation: isolate;
+}
+
+.-z-10 {
+ z-index: -10;
+}
+
+.z-0 {
+ z-index: 0;
+}
+
+.z-10 {
+ z-index: 10;
+}
+
+.z-20 {
+ z-index: 20;
+}
+
+.z-50 {
+ z-index: 50;
+}
+
+.z-\[60\] {
+ z-index: 60;
+}
+
+.z-\[70\] {
+ z-index: 70;
+}
+
+.col-span-3 {
+ grid-column: span 3 / span 3;
+}
+
+.m-0 {
+ margin: 0px;
+}
+
+.mx-1 {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+}
+
+.mx-4 {
+ margin-left: 1rem;
+ margin-right: 1rem;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.my-3 {
+ margin-top: 0.75rem;
+ margin-bottom: 0.75rem;
+}
+
+.my-4 {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+}
+
+.my-6 {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.my-8 {
+ margin-top: 2rem;
+ margin-bottom: 2rem;
+}
+
+.-mt-20 {
+ margin-top: -5rem;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem;
+}
+
+.mb-12 {
+ margin-bottom: 3rem;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem;
+}
+
+.mb-4 {
+ margin-bottom: 1rem;
+}
+
+.mb-5 {
+ margin-bottom: 1.25rem;
+}
+
+.mb-6 {
+ margin-bottom: 1.5rem;
+}
+
+.mb-8 {
+ margin-bottom: 2rem;
+}
+
+.ml-0\.5 {
+ margin-left: 0.125rem;
+}
+
+.ml-2 {
+ margin-left: 0.5rem;
+}
+
+.ml-4 {
+ margin-left: 1rem;
+}
+
+.ml-6 {
+ margin-left: 1.5rem;
+}
+
+.ml-auto {
+ margin-left: auto;
+}
+
+.mr-2 {
+ margin-right: 0.5rem;
+}
+
+.mt-0\.5 {
+ margin-top: 0.125rem;
+}
+
+.mt-1 {
+ margin-top: 0.25rem;
+}
+
+.mt-10 {
+ margin-top: 2.5rem;
+}
+
+.mt-12 {
+ margin-top: 3rem;
+}
+
+.mt-2 {
+ margin-top: 0.5rem;
+}
+
+.mt-20 {
+ margin-top: 5rem;
+}
+
+.mt-24 {
+ margin-top: 6rem;
+}
+
+.mt-3 {
+ margin-top: 0.75rem;
+}
+
+.mt-32 {
+ margin-top: 8rem;
+}
+
+.mt-4 {
+ margin-top: 1rem;
+}
+
+.mt-5 {
+ margin-top: 1.25rem;
+}
+
+.mt-6 {
+ margin-top: 1.5rem;
+}
+
+.mt-8 {
+ margin-top: 2rem;
+}
+
+.mt-px {
+ margin-top: 1px;
+}
+
+.line-clamp-2 {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+}
+
+.\!block {
+ display: block !important;
+}
+
+.block {
+ display: block;
+}
+
+.inline-block {
+ display: inline-block;
+}
+
+.inline {
+ display: inline;
+}
+
+.flex {
+ display: flex;
+}
+
+.inline-flex {
+ display: inline-flex;
+}
+
+.grid {
+ display: grid;
+}
+
+.contents {
+ display: contents;
+}
+
+.hidden {
+ display: none;
+}
+
+.aspect-square {
+ aspect-ratio: 1 / 1;
+}
+
+.aspect-video {
+ aspect-ratio: 16 / 9;
+}
+
+.size-10 {
+ width: 2.5rem;
+ height: 2.5rem;
+}
+
+.size-4 {
+ width: 1rem;
+ height: 1rem;
+}
+
+.size-5 {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.size-6 {
+ width: 1.5rem;
+ height: 1.5rem;
+}
+
+.size-\[45px\] {
+ width: 45px;
+ height: 45px;
+}
+
+.h-0\.5 {
+ height: 0.125rem;
+}
+
+.h-1 {
+ height: 0.25rem;
+}
+
+.h-1\.5 {
+ height: 0.375rem;
+}
+
+.h-10 {
+ height: 2.5rem;
+}
+
+.h-11 {
+ height: 2.75rem;
+}
+
+.h-12 {
+ height: 3rem;
+}
+
+.h-14 {
+ height: 3.5rem;
+}
+
+.h-16 {
+ height: 4rem;
+}
+
+.h-2 {
+ height: 0.5rem;
+}
+
+.h-2\.5 {
+ height: 0.625rem;
+}
+
+.h-20 {
+ height: 5rem;
+}
+
+.h-24 {
+ height: 6rem;
+}
+
+.h-28 {
+ height: 7rem;
+}
+
+.h-3 {
+ height: 0.75rem;
+}
+
+.h-32 {
+ height: 8rem;
+}
+
+.h-4 {
+ height: 1rem;
+}
+
+.h-44 {
+ height: 11rem;
+}
+
+.h-48 {
+ height: 12rem;
+}
+
+.h-5 {
+ height: 1.25rem;
+}
+
+.h-6 {
+ height: 1.5rem;
+}
+
+.h-7 {
+ height: 1.75rem;
+}
+
+.h-8 {
+ height: 2rem;
+}
+
+.h-9 {
+ height: 2.25rem;
+}
+
+.h-96 {
+ height: 24rem;
+}
+
+.h-\[186px\] {
+ height: 186px;
+}
+
+.h-\[30px\] {
+ height: 30px;
+}
+
+.h-\[400px\] {
+ height: 400px;
+}
+
+.h-\[45px\] {
+ height: 45px;
+}
+
+.h-\[6px\] {
+ height: 6px;
+}
+
+.h-\[83px\] {
+ height: 83px;
+}
+
+.h-\[90vh\] {
+ height: 90vh;
+}
+
+.h-auto {
+ height: auto;
+}
+
+.h-full {
+ height: 100%;
+}
+
+.h-screen {
+ height: 100vh;
+}
+
+.max-h-0 {
+ max-height: 0px;
+}
+
+.max-h-\[85vh\] {
+ max-height: 85vh;
+}
+
+.max-h-\[90vh\] {
+ max-height: 90vh;
+}
+
+.min-h-0 {
+ min-height: 0px;
+}
+
+.min-h-\[420px\] {
+ min-height: 420px;
+}
+
+.min-h-\[60px\] {
+ min-height: 60px;
+}
+
+.min-h-screen {
+ min-height: 100vh;
+}
+
+.w-0 {
+ width: 0px;
+}
+
+.w-1 {
+ width: 0.25rem;
+}
+
+.w-10 {
+ width: 2.5rem;
+}
+
+.w-11 {
+ width: 2.75rem;
+}
+
+.w-12 {
+ width: 3rem;
+}
+
+.w-14 {
+ width: 3.5rem;
+}
+
+.w-16 {
+ width: 4rem;
+}
+
+.w-2 {
+ width: 0.5rem;
+}
+
+.w-20 {
+ width: 5rem;
+}
+
+.w-24 {
+ width: 6rem;
+}
+
+.w-28 {
+ width: 7rem;
+}
+
+.w-3 {
+ width: 0.75rem;
+}
+
+.w-32 {
+ width: 8rem;
+}
+
+.w-4 {
+ width: 1rem;
+}
+
+.w-44 {
+ width: 11rem;
+}
+
+.w-48 {
+ width: 12rem;
+}
+
+.w-5 {
+ width: 1.25rem;
+}
+
+.w-6 {
+ width: 1.5rem;
+}
+
+.w-64 {
+ width: 16rem;
+}
+
+.w-8 {
+ width: 2rem;
+}
+
+.w-80 {
+ width: 20rem;
+}
+
+.w-9 {
+ width: 2.25rem;
+}
+
+.w-\[60\%\] {
+ width: 60%;
+}
+
+.w-\[79px\] {
+ width: 79px;
+}
+
+.w-\[90\%\] {
+ width: 90%;
+}
+
+.w-\[90vw\] {
+ width: 90vw;
+}
+
+.w-fit {
+ width: -moz-fit-content;
+ width: fit-content;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.w-max {
+ width: -moz-max-content;
+ width: max-content;
+}
+
+.w-screen {
+ width: 100vw;
+}
+
+.min-w-0 {
+ min-width: 0px;
+}
+
+.min-w-\[180px\] {
+ min-width: 180px;
+}
+
+.min-w-\[2\.2rem\] {
+ min-width: 2.2rem;
+}
+
+.min-w-\[200px\] {
+ min-width: 200px;
+}
+
+.min-w-\[4rem\] {
+ min-width: 4rem;
+}
+
+.min-w-\[80px\] {
+ min-width: 80px;
+}
+
+.min-w-full {
+ min-width: 100%;
+}
+
+.max-w-2xl {
+ max-width: 42rem;
+}
+
+.max-w-3xl {
+ max-width: 48rem;
+}
+
+.max-w-4xl {
+ max-width: 56rem;
+}
+
+.max-w-5xl {
+ max-width: 64rem;
+}
+
+.max-w-6xl {
+ max-width: 72rem;
+}
+
+.max-w-7xl {
+ max-width: 80rem;
+}
+
+.max-w-96 {
+ max-width: 24rem;
+}
+
+.max-w-\[260px\] {
+ max-width: 260px;
+}
+
+.max-w-\[300px\] {
+ max-width: 300px;
+}
+
+.max-w-\[400px\] {
+ max-width: 400px;
+}
+
+.max-w-\[560px\] {
+ max-width: 560px;
+}
+
+.max-w-lg {
+ max-width: 32rem;
+}
+
+.max-w-md {
+ max-width: 28rem;
+}
+
+.max-w-sm {
+ max-width: 24rem;
+}
+
+.max-w-xl {
+ max-width: 36rem;
+}
+
+.max-w-xs {
+ max-width: 20rem;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
+.flex-shrink-0 {
+ flex-shrink: 0;
+}
+
+.shrink {
+ flex-shrink: 1;
+}
+
+.-translate-x-1\/2 {
+ --tw-translate-x: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.-translate-y-1\/2 {
+ --tw-translate-y: -50%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.-translate-y-\[calc\(100\%\+8px\)\] {
+ --tw-translate-y: calc(calc(100% + 8px) * -1);
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.scale-105 {
+ --tw-scale-x: 1.05;
+ --tw-scale-y: 1.05;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.scale-110 {
+ --tw-scale-x: 1.1;
+ --tw-scale-y: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.transform {
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+@keyframes bounce {
+ 0%, 100% {
+ transform: translateY(-25%);
+ animation-timing-function: cubic-bezier(0.8,0,1,1);
+ }
+
+ 50% {
+ transform: none;
+ animation-timing-function: cubic-bezier(0,0,0.2,1);
+ }
+}
+
+.animate-bounce {
+ animation: bounce 1s infinite;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.animate-spin {
+ animation: spin 1s linear infinite;
+}
+
+.cursor-col-resize {
+ cursor: col-resize;
+}
+
+.cursor-default {
+ cursor: default;
+}
+
+.cursor-grab {
+ cursor: grab;
+}
+
+.cursor-not-allowed {
+ cursor: not-allowed;
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.cursor-row-resize {
+ cursor: row-resize;
+}
+
+.select-none {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.resize-none {
+ resize: none;
+}
+
+.resize {
+ resize: both;
+}
+
+.list-inside {
+ list-style-position: inside;
+}
+
+.list-disc {
+ list-style-type: disc;
+}
+
+.grid-cols-1 {
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+}
+
+.grid-cols-2 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.flex-row {
+ flex-direction: row;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.flex-col-reverse {
+ flex-direction: column-reverse;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.flex-nowrap {
+ flex-wrap: nowrap;
+}
+
+.items-start {
+ align-items: flex-start;
+}
+
+.items-end {
+ align-items: flex-end;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-start {
+ justify-content: flex-start;
+}
+
+.justify-end {
+ justify-content: flex-end;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-0 {
+ gap: 0px;
+}
+
+.gap-0\.5 {
+ gap: 0.125rem;
+}
+
+.gap-1 {
+ gap: 0.25rem;
+}
+
+.gap-1\.5 {
+ gap: 0.375rem;
+}
+
+.gap-10 {
+ gap: 2.5rem;
+}
+
+.gap-12 {
+ gap: 3rem;
+}
+
+.gap-16 {
+ gap: 4rem;
+}
+
+.gap-2 {
+ gap: 0.5rem;
+}
+
+.gap-24 {
+ gap: 6rem;
+}
+
+.gap-3 {
+ gap: 0.75rem;
+}
+
+.gap-4 {
+ gap: 1rem;
+}
+
+.gap-5 {
+ gap: 1.25rem;
+}
+
+.gap-6 {
+ gap: 1.5rem;
+}
+
+.gap-8 {
+ gap: 2rem;
+}
+
+.gap-\[24px\] {
+ gap: 24px;
+}
+
+.gap-\[40px\] {
+ gap: 40px;
+}
+
+.gap-x-10 {
+ -moz-column-gap: 2.5rem;
+ column-gap: 2.5rem;
+}
+
+.gap-x-3 {
+ -moz-column-gap: 0.75rem;
+ column-gap: 0.75rem;
+}
+
+.gap-x-8 {
+ -moz-column-gap: 2rem;
+ column-gap: 2rem;
+}
+
+.gap-y-4 {
+ row-gap: 1rem;
+}
+
+.space-x-1 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.25rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-3 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.75rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-y-1 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
+.space-y-4 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
+}
+
+.divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+}
+
+.divide-gray-300 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(209 213 219 / var(--tw-divide-opacity, 1));
+}
+
+.self-center {
+ align-self: center;
+}
+
+.overflow-auto {
+ overflow: auto;
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.overflow-x-auto {
+ overflow-x: auto;
+}
+
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
+.scroll-smooth {
+ scroll-behavior: smooth;
+}
+
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.text-ellipsis {
+ text-overflow: ellipsis;
+}
+
+.whitespace-nowrap {
+ white-space: nowrap;
+}
+
+.whitespace-pre-wrap {
+ white-space: pre-wrap;
+}
+
+.break-all {
+ word-break: break-all;
+}
+
+.rounded {
+ border-radius: 0.25rem;
+}
+
+.rounded-2xl {
+ border-radius: 1rem;
+}
+
+.rounded-3xl {
+ border-radius: 1.5rem;
+}
+
+.rounded-\[12px\] {
+ border-radius: 12px;
+}
+
+.rounded-\[16px\] {
+ border-radius: 16px;
+}
+
+.rounded-\[32px\] {
+ border-radius: 32px;
+}
+
+.rounded-full {
+ border-radius: 9999px;
+}
+
+.rounded-lg {
+ border-radius: 0.5rem;
+}
+
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
+.rounded-xl {
+ border-radius: 0.75rem;
+}
+
+.rounded-l-md {
+ border-top-left-radius: 0.375rem;
+ border-bottom-left-radius: 0.375rem;
+}
+
+.rounded-r-lg {
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+}
+
+.rounded-r-md {
+ border-top-right-radius: 0.375rem;
+ border-bottom-right-radius: 0.375rem;
+}
+
+.border {
+ border-width: 1px;
+}
+
+.border-0 {
+ border-width: 0px;
+}
+
+.border-2 {
+ border-width: 2px;
+}
+
+.border-4 {
+ border-width: 4px;
+}
+
+.border-b {
+ border-bottom-width: 1px;
+}
+
+.border-b-2 {
+ border-bottom-width: 2px;
+}
+
+.border-b-4 {
+ border-bottom-width: 4px;
+}
+
+.border-l-4 {
+ border-left-width: 4px;
+}
+
+.border-r {
+ border-right-width: 1px;
+}
+
+.border-t {
+ border-top-width: 1px;
+}
+
+.border-dashed {
+ border-style: dashed;
+}
+
+.border-none {
+ border-style: none;
+}
+
+.border-\[\#D7DF23\] {
+ --tw-border-opacity: 1;
+ border-color: rgb(215 223 35 / var(--tw-border-opacity, 1));
+}
+
+.border-\[\#F90527\] {
+ --tw-border-opacity: 1;
+ border-color: rgb(249 5 39 / var(--tw-border-opacity, 1));
+}
+
+.border-\[\#F9A01B\] {
+ --tw-border-opacity: 1;
+ border-color: rgb(249 160 27 / var(--tw-border-opacity, 1));
+}
+
+.border-amber-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(253 230 138 / var(--tw-border-opacity, 1));
+}
+
+.border-amber-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(252 211 77 / var(--tw-border-opacity, 1));
+}
+
+.border-blue-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(147 197 253 / var(--tw-border-opacity, 1));
+}
+
+.border-blue-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(96 165 250 / var(--tw-border-opacity, 1));
+}
+
+.border-blue-400\/0 {
+ border-color: rgb(96 165 250 / 0);
+}
+
+.border-blue-400\/10 {
+ border-color: rgb(96 165 250 / 0.1);
+}
+
+.border-blue-400\/100 {
+ border-color: rgb(96 165 250 / 1);
+}
+
+.border-blue-400\/15 {
+ border-color: rgb(96 165 250 / 0.15);
+}
+
+.border-blue-400\/20 {
+ border-color: rgb(96 165 250 / 0.2);
+}
+
+.border-blue-400\/25 {
+ border-color: rgb(96 165 250 / 0.25);
+}
+
+.border-blue-400\/30 {
+ border-color: rgb(96 165 250 / 0.3);
+}
+
+.border-blue-400\/35 {
+ border-color: rgb(96 165 250 / 0.35);
+}
+
+.border-blue-400\/40 {
+ border-color: rgb(96 165 250 / 0.4);
+}
+
+.border-blue-400\/45 {
+ border-color: rgb(96 165 250 / 0.45);
+}
+
+.border-blue-400\/5 {
+ border-color: rgb(96 165 250 / 0.05);
+}
+
+.border-blue-400\/50 {
+ border-color: rgb(96 165 250 / 0.5);
+}
+
+.border-blue-400\/55 {
+ border-color: rgb(96 165 250 / 0.55);
+}
+
+.border-blue-400\/60 {
+ border-color: rgb(96 165 250 / 0.6);
+}
+
+.border-blue-400\/65 {
+ border-color: rgb(96 165 250 / 0.65);
+}
+
+.border-blue-400\/70 {
+ border-color: rgb(96 165 250 / 0.7);
+}
+
+.border-blue-400\/75 {
+ border-color: rgb(96 165 250 / 0.75);
+}
+
+.border-blue-400\/80 {
+ border-color: rgb(96 165 250 / 0.8);
+}
+
+.border-blue-400\/85 {
+ border-color: rgb(96 165 250 / 0.85);
+}
+
+.border-blue-400\/90 {
+ border-color: rgb(96 165 250 / 0.9);
+}
+
+.border-blue-400\/95 {
+ border-color: rgb(96 165 250 / 0.95);
+}
+
+.border-brand-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(251 211 165 / var(--tw-border-opacity, 1));
+}
+
+.border-brand-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 181 80 / var(--tw-border-opacity, 1));
+}
+
+.border-brand-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 139 73 / var(--tw-border-opacity, 1));
+}
+
+.border-brand-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(254 0 2 / var(--tw-border-opacity, 1));
+}
+
+.border-cyan-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(103 232 249 / var(--tw-border-opacity, 1));
+}
+
+.border-emerald-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(167 243 208 / var(--tw-border-opacity, 1));
+}
+
+.border-emerald-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(110 231 183 / var(--tw-border-opacity, 1));
+}
+
+.border-emerald-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(52 211 153 / var(--tw-border-opacity, 1));
+}
+
+.border-fuchsia-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(240 171 252 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-100 {
+ --tw-border-opacity: 1;
+ border-color: rgb(234 238 245 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(107 114 128 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-700 {
+ --tw-border-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-800 {
+ --tw-border-opacity: 1;
+ border-color: rgb(31 41 55 / var(--tw-border-opacity, 1));
+}
+
+.border-green-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
+}
+
+.border-green-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
+}
+
+.border-green-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(74 222 128 / var(--tw-border-opacity, 1));
+}
+
+.border-green-400\/0 {
+ border-color: rgb(74 222 128 / 0);
+}
+
+.border-green-400\/10 {
+ border-color: rgb(74 222 128 / 0.1);
+}
+
+.border-green-400\/100 {
+ border-color: rgb(74 222 128 / 1);
+}
+
+.border-green-400\/15 {
+ border-color: rgb(74 222 128 / 0.15);
+}
+
+.border-green-400\/20 {
+ border-color: rgb(74 222 128 / 0.2);
+}
+
+.border-green-400\/25 {
+ border-color: rgb(74 222 128 / 0.25);
+}
+
+.border-green-400\/30 {
+ border-color: rgb(74 222 128 / 0.3);
+}
+
+.border-green-400\/35 {
+ border-color: rgb(74 222 128 / 0.35);
+}
+
+.border-green-400\/40 {
+ border-color: rgb(74 222 128 / 0.4);
+}
+
+.border-green-400\/45 {
+ border-color: rgb(74 222 128 / 0.45);
+}
+
+.border-green-400\/5 {
+ border-color: rgb(74 222 128 / 0.05);
+}
+
+.border-green-400\/50 {
+ border-color: rgb(74 222 128 / 0.5);
+}
+
+.border-green-400\/55 {
+ border-color: rgb(74 222 128 / 0.55);
+}
+
+.border-green-400\/60 {
+ border-color: rgb(74 222 128 / 0.6);
+}
+
+.border-green-400\/65 {
+ border-color: rgb(74 222 128 / 0.65);
+}
+
+.border-green-400\/70 {
+ border-color: rgb(74 222 128 / 0.7);
+}
+
+.border-green-400\/75 {
+ border-color: rgb(74 222 128 / 0.75);
+}
+
+.border-green-400\/80 {
+ border-color: rgb(74 222 128 / 0.8);
+}
+
+.border-green-400\/85 {
+ border-color: rgb(74 222 128 / 0.85);
+}
+
+.border-green-400\/90 {
+ border-color: rgb(74 222 128 / 0.9);
+}
+
+.border-green-400\/95 {
+ border-color: rgb(74 222 128 / 0.95);
+}
+
+.border-green-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 146 71 / var(--tw-border-opacity, 1));
+}
+
+.border-green-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(22 163 74 / var(--tw-border-opacity, 1));
+}
+
+.border-indigo-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(165 180 252 / var(--tw-border-opacity, 1));
+}
+
+.border-orange-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(253 186 116 / var(--tw-border-opacity, 1));
+}
+
+.border-pink-100 {
+ --tw-border-opacity: 1;
+ border-color: rgb(252 231 243 / var(--tw-border-opacity, 1));
+}
+
+.border-pink-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(249 168 212 / var(--tw-border-opacity, 1));
+}
+
+.border-purple-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(233 213 255 / var(--tw-border-opacity, 1));
+}
+
+.border-purple-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(216 180 254 / var(--tw-border-opacity, 1));
+}
+
+.border-purple-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(192 132 252 / var(--tw-border-opacity, 1));
+}
+
+.border-purple-400\/0 {
+ border-color: rgb(192 132 252 / 0);
+}
+
+.border-purple-400\/10 {
+ border-color: rgb(192 132 252 / 0.1);
+}
+
+.border-purple-400\/100 {
+ border-color: rgb(192 132 252 / 1);
+}
+
+.border-purple-400\/15 {
+ border-color: rgb(192 132 252 / 0.15);
+}
+
+.border-purple-400\/20 {
+ border-color: rgb(192 132 252 / 0.2);
+}
+
+.border-purple-400\/25 {
+ border-color: rgb(192 132 252 / 0.25);
+}
+
+.border-purple-400\/30 {
+ border-color: rgb(192 132 252 / 0.3);
+}
+
+.border-purple-400\/35 {
+ border-color: rgb(192 132 252 / 0.35);
+}
+
+.border-purple-400\/40 {
+ border-color: rgb(192 132 252 / 0.4);
+}
+
+.border-purple-400\/45 {
+ border-color: rgb(192 132 252 / 0.45);
+}
+
+.border-purple-400\/5 {
+ border-color: rgb(192 132 252 / 0.05);
+}
+
+.border-purple-400\/50 {
+ border-color: rgb(192 132 252 / 0.5);
+}
+
+.border-purple-400\/55 {
+ border-color: rgb(192 132 252 / 0.55);
+}
+
+.border-purple-400\/60 {
+ border-color: rgb(192 132 252 / 0.6);
+}
+
+.border-purple-400\/65 {
+ border-color: rgb(192 132 252 / 0.65);
+}
+
+.border-purple-400\/70 {
+ border-color: rgb(192 132 252 / 0.7);
+}
+
+.border-purple-400\/75 {
+ border-color: rgb(192 132 252 / 0.75);
+}
+
+.border-purple-400\/80 {
+ border-color: rgb(192 132 252 / 0.8);
+}
+
+.border-purple-400\/85 {
+ border-color: rgb(192 132 252 / 0.85);
+}
+
+.border-purple-400\/90 {
+ border-color: rgb(192 132 252 / 0.9);
+}
+
+.border-purple-400\/95 {
+ border-color: rgb(192 132 252 / 0.95);
+}
+
+.border-red-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));
+}
+
+.border-red-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));
+}
+
+.border-red-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(248 113 113 / var(--tw-border-opacity, 1));
+}
+
+.border-red-400\/0 {
+ border-color: rgb(248 113 113 / 0);
+}
+
+.border-red-400\/10 {
+ border-color: rgb(248 113 113 / 0.1);
+}
+
+.border-red-400\/100 {
+ border-color: rgb(248 113 113 / 1);
+}
+
+.border-red-400\/15 {
+ border-color: rgb(248 113 113 / 0.15);
+}
+
+.border-red-400\/20 {
+ border-color: rgb(248 113 113 / 0.2);
+}
+
+.border-red-400\/25 {
+ border-color: rgb(248 113 113 / 0.25);
+}
+
+.border-red-400\/30 {
+ border-color: rgb(248 113 113 / 0.3);
+}
+
+.border-red-400\/35 {
+ border-color: rgb(248 113 113 / 0.35);
+}
+
+.border-red-400\/40 {
+ border-color: rgb(248 113 113 / 0.4);
+}
+
+.border-red-400\/45 {
+ border-color: rgb(248 113 113 / 0.45);
+}
+
+.border-red-400\/5 {
+ border-color: rgb(248 113 113 / 0.05);
+}
+
+.border-red-400\/50 {
+ border-color: rgb(248 113 113 / 0.5);
+}
+
+.border-red-400\/55 {
+ border-color: rgb(248 113 113 / 0.55);
+}
+
+.border-red-400\/60 {
+ border-color: rgb(248 113 113 / 0.6);
+}
+
+.border-red-400\/65 {
+ border-color: rgb(248 113 113 / 0.65);
+}
+
+.border-red-400\/70 {
+ border-color: rgb(248 113 113 / 0.7);
+}
+
+.border-red-400\/75 {
+ border-color: rgb(248 113 113 / 0.75);
+}
+
+.border-red-400\/80 {
+ border-color: rgb(248 113 113 / 0.8);
+}
+
+.border-red-400\/85 {
+ border-color: rgb(248 113 113 / 0.85);
+}
+
+.border-red-400\/90 {
+ border-color: rgb(248 113 113 / 0.9);
+}
+
+.border-red-400\/95 {
+ border-color: rgb(248 113 113 / 0.95);
+}
+
+.border-red-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(239 68 68 / var(--tw-border-opacity, 1));
+}
+
+.border-rose-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(253 164 175 / var(--tw-border-opacity, 1));
+}
+
+.border-sky-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(186 230 253 / var(--tw-border-opacity, 1));
+}
+
+.border-slate-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(226 232 240 / var(--tw-border-opacity, 1));
+}
+
+.border-slate-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(203 213 225 / var(--tw-border-opacity, 1));
+}
+
+.border-teal-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(153 246 228 / var(--tw-border-opacity, 1));
+}
+
+.border-teal-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(94 234 212 / var(--tw-border-opacity, 1));
+}
+
+.border-teal-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(45 212 191 / var(--tw-border-opacity, 1));
+}
+
+.border-transparent {
+ border-color: transparent;
+}
+
+.border-violet-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(221 214 254 / var(--tw-border-opacity, 1));
+}
+
+.border-violet-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(196 181 253 / var(--tw-border-opacity, 1));
+}
+
+.border-white {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
+}
+
+.border-white\/20 {
+ border-color: rgb(255 255 255 / 0.2);
+}
+
+.border-white\/25 {
+ border-color: rgb(255 255 255 / 0.25);
+}
+
+.border-white\/30 {
+ border-color: rgb(255 255 255 / 0.3);
+}
+
+.border-white\/35 {
+ border-color: rgb(255 255 255 / 0.35);
+}
+
+.border-white\/50 {
+ border-color: rgb(255 255 255 / 0.5);
+}
+
+.border-yellow-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(253 224 71 / var(--tw-border-opacity, 1));
+}
+
+.border-yellow-400 {
+ --tw-border-opacity: 1;
+ border-color: rgb(250 204 21 / var(--tw-border-opacity, 1));
+}
+
+.border-yellow-400\/0 {
+ border-color: rgb(250 204 21 / 0);
+}
+
+.border-yellow-400\/10 {
+ border-color: rgb(250 204 21 / 0.1);
+}
+
+.border-yellow-400\/100 {
+ border-color: rgb(250 204 21 / 1);
+}
+
+.border-yellow-400\/15 {
+ border-color: rgb(250 204 21 / 0.15);
+}
+
+.border-yellow-400\/20 {
+ border-color: rgb(250 204 21 / 0.2);
+}
+
+.border-yellow-400\/25 {
+ border-color: rgb(250 204 21 / 0.25);
+}
+
+.border-yellow-400\/30 {
+ border-color: rgb(250 204 21 / 0.3);
+}
+
+.border-yellow-400\/35 {
+ border-color: rgb(250 204 21 / 0.35);
+}
+
+.border-yellow-400\/40 {
+ border-color: rgb(250 204 21 / 0.4);
+}
+
+.border-yellow-400\/45 {
+ border-color: rgb(250 204 21 / 0.45);
+}
+
+.border-yellow-400\/5 {
+ border-color: rgb(250 204 21 / 0.05);
+}
+
+.border-yellow-400\/50 {
+ border-color: rgb(250 204 21 / 0.5);
+}
+
+.border-yellow-400\/55 {
+ border-color: rgb(250 204 21 / 0.55);
+}
+
+.border-yellow-400\/60 {
+ border-color: rgb(250 204 21 / 0.6);
+}
+
+.border-yellow-400\/65 {
+ border-color: rgb(250 204 21 / 0.65);
+}
+
+.border-yellow-400\/70 {
+ border-color: rgb(250 204 21 / 0.7);
+}
+
+.border-yellow-400\/75 {
+ border-color: rgb(250 204 21 / 0.75);
+}
+
+.border-yellow-400\/80 {
+ border-color: rgb(250 204 21 / 0.8);
+}
+
+.border-yellow-400\/85 {
+ border-color: rgb(250 204 21 / 0.85);
+}
+
+.border-yellow-400\/90 {
+ border-color: rgb(250 204 21 / 0.9);
+}
+
+.border-yellow-400\/95 {
+ border-color: rgb(250 204 21 / 0.95);
+}
+
+.border-t-white {
+ --tw-border-opacity: 1;
+ border-top-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
+}
+
+.bg-\[\#009247\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 146 71 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#1e1e1e\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#242527\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(36 37 39 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#252526\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(37 37 38 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#AF1F16\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(175 31 22 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#BD2A21\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(189 42 33 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#D5DF50\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(213 223 80 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#D7DF23\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(215 223 35 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#F90527\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 5 39 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#F90527\]\/10 {
+ background-color: rgb(249 5 39 / 0.1);
+}
+
+.bg-\[\#F90527\]\/30 {
+ background-color: rgb(249 5 39 / 0.3);
+}
+
+.bg-\[\#F9A01B\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 160 27 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#FFB550\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 181 80 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[\#FFFFFF\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-\[rgb\(254_0_2\/1\)\] {
+ background-color: rgb(254 0 2/1);
+}
+
+.bg-\[var\(--action-green\)\] {
+ background-color: var(--action-green);
+}
+
+.bg-amber-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
+}
+
+.bg-amber-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 251 235 / var(--tw-bg-opacity, 1));
+}
+
+.bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
+}
+
+.bg-black\/10 {
+ background-color: rgb(0 0 0 / 0.1);
+}
+
+.bg-black\/20 {
+ background-color: rgb(0 0 0 / 0.2);
+}
+
+.bg-black\/50 {
+ background-color: rgb(0 0 0 / 0.5);
+}
+
+.bg-blue-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
+}
+
+.bg-blue-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(96 165 250 / var(--tw-bg-opacity, 1));
+}
+
+.bg-blue-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-blue-50\/0 {
+ background-color: rgb(239 246 255 / 0);
+}
+
+.bg-blue-50\/10 {
+ background-color: rgb(239 246 255 / 0.1);
+}
+
+.bg-blue-50\/100 {
+ background-color: rgb(239 246 255 / 1);
+}
+
+.bg-blue-50\/15 {
+ background-color: rgb(239 246 255 / 0.15);
+}
+
+.bg-blue-50\/20 {
+ background-color: rgb(239 246 255 / 0.2);
+}
+
+.bg-blue-50\/25 {
+ background-color: rgb(239 246 255 / 0.25);
+}
+
+.bg-blue-50\/30 {
+ background-color: rgb(239 246 255 / 0.3);
+}
+
+.bg-blue-50\/35 {
+ background-color: rgb(239 246 255 / 0.35);
+}
+
+.bg-blue-50\/40 {
+ background-color: rgb(239 246 255 / 0.4);
+}
+
+.bg-blue-50\/45 {
+ background-color: rgb(239 246 255 / 0.45);
+}
+
+.bg-blue-50\/5 {
+ background-color: rgb(239 246 255 / 0.05);
+}
+
+.bg-blue-50\/50 {
+ background-color: rgb(239 246 255 / 0.5);
+}
+
+.bg-blue-50\/55 {
+ background-color: rgb(239 246 255 / 0.55);
+}
+
+.bg-blue-50\/60 {
+ background-color: rgb(239 246 255 / 0.6);
+}
+
+.bg-blue-50\/65 {
+ background-color: rgb(239 246 255 / 0.65);
+}
+
+.bg-blue-50\/70 {
+ background-color: rgb(239 246 255 / 0.7);
+}
+
+.bg-blue-50\/75 {
+ background-color: rgb(239 246 255 / 0.75);
+}
+
+.bg-blue-50\/80 {
+ background-color: rgb(239 246 255 / 0.8);
+}
+
+.bg-blue-50\/85 {
+ background-color: rgb(239 246 255 / 0.85);
+}
+
+.bg-blue-50\/90 {
+ background-color: rgb(239 246 255 / 0.9);
+}
+
+.bg-blue-50\/95 {
+ background-color: rgb(239 246 255 / 0.95);
+}
+
+.bg-blue-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
+}
+
+.bg-blue-500\/0 {
+ background-color: rgb(59 130 246 / 0);
+}
+
+.bg-blue-500\/10 {
+ background-color: rgb(59 130 246 / 0.1);
+}
+
+.bg-blue-500\/100 {
+ background-color: rgb(59 130 246 / 1);
+}
+
+.bg-blue-500\/15 {
+ background-color: rgb(59 130 246 / 0.15);
+}
+
+.bg-blue-500\/20 {
+ background-color: rgb(59 130 246 / 0.2);
+}
+
+.bg-blue-500\/25 {
+ background-color: rgb(59 130 246 / 0.25);
+}
+
+.bg-blue-500\/30 {
+ background-color: rgb(59 130 246 / 0.3);
+}
+
+.bg-blue-500\/35 {
+ background-color: rgb(59 130 246 / 0.35);
+}
+
+.bg-blue-500\/40 {
+ background-color: rgb(59 130 246 / 0.4);
+}
+
+.bg-blue-500\/45 {
+ background-color: rgb(59 130 246 / 0.45);
+}
+
+.bg-blue-500\/5 {
+ background-color: rgb(59 130 246 / 0.05);
+}
+
+.bg-blue-500\/50 {
+ background-color: rgb(59 130 246 / 0.5);
+}
+
+.bg-blue-500\/55 {
+ background-color: rgb(59 130 246 / 0.55);
+}
+
+.bg-blue-500\/60 {
+ background-color: rgb(59 130 246 / 0.6);
+}
+
+.bg-blue-500\/65 {
+ background-color: rgb(59 130 246 / 0.65);
+}
+
+.bg-blue-500\/70 {
+ background-color: rgb(59 130 246 / 0.7);
+}
+
+.bg-blue-500\/75 {
+ background-color: rgb(59 130 246 / 0.75);
+}
+
+.bg-blue-500\/80 {
+ background-color: rgb(59 130 246 / 0.8);
+}
+
+.bg-blue-500\/85 {
+ background-color: rgb(59 130 246 / 0.85);
+}
+
+.bg-blue-500\/90 {
+ background-color: rgb(59 130 246 / 0.9);
+}
+
+.bg-blue-500\/95 {
+ background-color: rgb(59 130 246 / 0.95);
+}
+
+.bg-blue-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(253 235 211 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 181 80 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 139 73 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 247 238 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 0 2 / var(--tw-bg-opacity, 1));
+}
+
+.bg-brand-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(222 78 50 / var(--tw-bg-opacity, 1));
+}
+
+.bg-cyan-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(6 182 212 / var(--tw-bg-opacity, 1));
+}
+
+.bg-emerald-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 250 229 / var(--tw-bg-opacity, 1));
+}
+
+.bg-emerald-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(236 253 245 / var(--tw-bg-opacity, 1));
+}
+
+.bg-emerald-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(16 185 129 / var(--tw-bg-opacity, 1));
+}
+
+.bg-emerald-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(5 150 105 / var(--tw-bg-opacity, 1));
+}
+
+.bg-fuchsia-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(253 244 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-fuchsia-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(217 70 239 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(234 238 245 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-200\/50 {
+ background-color: rgb(229 231 235 / 0.5);
+}
+
+.bg-gray-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-50\/0 {
+ background-color: rgb(249 250 251 / 0);
+}
+
+.bg-gray-50\/10 {
+ background-color: rgb(249 250 251 / 0.1);
+}
+
+.bg-gray-50\/100 {
+ background-color: rgb(249 250 251 / 1);
+}
+
+.bg-gray-50\/15 {
+ background-color: rgb(249 250 251 / 0.15);
+}
+
+.bg-gray-50\/20 {
+ background-color: rgb(249 250 251 / 0.2);
+}
+
+.bg-gray-50\/25 {
+ background-color: rgb(249 250 251 / 0.25);
+}
+
+.bg-gray-50\/30 {
+ background-color: rgb(249 250 251 / 0.3);
+}
+
+.bg-gray-50\/35 {
+ background-color: rgb(249 250 251 / 0.35);
+}
+
+.bg-gray-50\/40 {
+ background-color: rgb(249 250 251 / 0.4);
+}
+
+.bg-gray-50\/45 {
+ background-color: rgb(249 250 251 / 0.45);
+}
+
+.bg-gray-50\/5 {
+ background-color: rgb(249 250 251 / 0.05);
+}
+
+.bg-gray-50\/50 {
+ background-color: rgb(249 250 251 / 0.5);
+}
+
+.bg-gray-50\/55 {
+ background-color: rgb(249 250 251 / 0.55);
+}
+
+.bg-gray-50\/60 {
+ background-color: rgb(249 250 251 / 0.6);
+}
+
+.bg-gray-50\/65 {
+ background-color: rgb(249 250 251 / 0.65);
+}
+
+.bg-gray-50\/70 {
+ background-color: rgb(249 250 251 / 0.7);
+}
+
+.bg-gray-50\/75 {
+ background-color: rgb(249 250 251 / 0.75);
+}
+
+.bg-gray-50\/80 {
+ background-color: rgb(249 250 251 / 0.8);
+}
+
+.bg-gray-50\/85 {
+ background-color: rgb(249 250 251 / 0.85);
+}
+
+.bg-gray-50\/90 {
+ background-color: rgb(249 250 251 / 0.9);
+}
+
+.bg-gray-50\/95 {
+ background-color: rgb(249 250 251 / 0.95);
+}
+
+.bg-gray-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-500\/0 {
+ background-color: rgb(107 114 128 / 0);
+}
+
+.bg-gray-500\/10 {
+ background-color: rgb(107 114 128 / 0.1);
+}
+
+.bg-gray-500\/100 {
+ background-color: rgb(107 114 128 / 1);
+}
+
+.bg-gray-500\/15 {
+ background-color: rgb(107 114 128 / 0.15);
+}
+
+.bg-gray-500\/20 {
+ background-color: rgb(107 114 128 / 0.2);
+}
+
+.bg-gray-500\/25 {
+ background-color: rgb(107 114 128 / 0.25);
+}
+
+.bg-gray-500\/30 {
+ background-color: rgb(107 114 128 / 0.3);
+}
+
+.bg-gray-500\/35 {
+ background-color: rgb(107 114 128 / 0.35);
+}
+
+.bg-gray-500\/40 {
+ background-color: rgb(107 114 128 / 0.4);
+}
+
+.bg-gray-500\/45 {
+ background-color: rgb(107 114 128 / 0.45);
+}
+
+.bg-gray-500\/5 {
+ background-color: rgb(107 114 128 / 0.05);
+}
+
+.bg-gray-500\/50 {
+ background-color: rgb(107 114 128 / 0.5);
+}
+
+.bg-gray-500\/55 {
+ background-color: rgb(107 114 128 / 0.55);
+}
+
+.bg-gray-500\/60 {
+ background-color: rgb(107 114 128 / 0.6);
+}
+
+.bg-gray-500\/65 {
+ background-color: rgb(107 114 128 / 0.65);
+}
+
+.bg-gray-500\/70 {
+ background-color: rgb(107 114 128 / 0.7);
+}
+
+.bg-gray-500\/75 {
+ background-color: rgb(107 114 128 / 0.75);
+}
+
+.bg-gray-500\/80 {
+ background-color: rgb(107 114 128 / 0.8);
+}
+
+.bg-gray-500\/85 {
+ background-color: rgb(107 114 128 / 0.85);
+}
+
+.bg-gray-500\/90 {
+ background-color: rgb(107 114 128 / 0.9);
+}
+
+.bg-gray-500\/95 {
+ background-color: rgb(107 114 128 / 0.95);
+}
+
+.bg-gray-700 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-900 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(36 37 39 / var(--tw-bg-opacity, 1));
+}
+
+.bg-green-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(213 223 80 / var(--tw-bg-opacity, 1));
+}
+
+.bg-green-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(74 222 128 / var(--tw-bg-opacity, 1));
+}
+
+.bg-green-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1));
+}
+
+.bg-green-50\/0 {
+ background-color: rgb(240 253 244 / 0);
+}
+
+.bg-green-50\/10 {
+ background-color: rgb(240 253 244 / 0.1);
+}
+
+.bg-green-50\/100 {
+ background-color: rgb(240 253 244 / 1);
+}
+
+.bg-green-50\/15 {
+ background-color: rgb(240 253 244 / 0.15);
+}
+
+.bg-green-50\/20 {
+ background-color: rgb(240 253 244 / 0.2);
+}
+
+.bg-green-50\/25 {
+ background-color: rgb(240 253 244 / 0.25);
+}
+
+.bg-green-50\/30 {
+ background-color: rgb(240 253 244 / 0.3);
+}
+
+.bg-green-50\/35 {
+ background-color: rgb(240 253 244 / 0.35);
+}
+
+.bg-green-50\/40 {
+ background-color: rgb(240 253 244 / 0.4);
+}
+
+.bg-green-50\/45 {
+ background-color: rgb(240 253 244 / 0.45);
+}
+
+.bg-green-50\/5 {
+ background-color: rgb(240 253 244 / 0.05);
+}
+
+.bg-green-50\/50 {
+ background-color: rgb(240 253 244 / 0.5);
+}
+
+.bg-green-50\/55 {
+ background-color: rgb(240 253 244 / 0.55);
+}
+
+.bg-green-50\/60 {
+ background-color: rgb(240 253 244 / 0.6);
+}
+
+.bg-green-50\/65 {
+ background-color: rgb(240 253 244 / 0.65);
+}
+
+.bg-green-50\/70 {
+ background-color: rgb(240 253 244 / 0.7);
+}
+
+.bg-green-50\/75 {
+ background-color: rgb(240 253 244 / 0.75);
+}
+
+.bg-green-50\/80 {
+ background-color: rgb(240 253 244 / 0.8);
+}
+
+.bg-green-50\/85 {
+ background-color: rgb(240 253 244 / 0.85);
+}
+
+.bg-green-50\/90 {
+ background-color: rgb(240 253 244 / 0.9);
+}
+
+.bg-green-50\/95 {
+ background-color: rgb(240 253 244 / 0.95);
+}
+
+.bg-green-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 146 71 / var(--tw-bg-opacity, 1));
+}
+
+.bg-green-500\/0 {
+ background-color: rgb(0 146 71 / 0);
+}
+
+.bg-green-500\/10 {
+ background-color: rgb(0 146 71 / 0.1);
+}
+
+.bg-green-500\/100 {
+ background-color: rgb(0 146 71 / 1);
+}
+
+.bg-green-500\/15 {
+ background-color: rgb(0 146 71 / 0.15);
+}
+
+.bg-green-500\/20 {
+ background-color: rgb(0 146 71 / 0.2);
+}
+
+.bg-green-500\/25 {
+ background-color: rgb(0 146 71 / 0.25);
+}
+
+.bg-green-500\/30 {
+ background-color: rgb(0 146 71 / 0.3);
+}
+
+.bg-green-500\/35 {
+ background-color: rgb(0 146 71 / 0.35);
+}
+
+.bg-green-500\/40 {
+ background-color: rgb(0 146 71 / 0.4);
+}
+
+.bg-green-500\/45 {
+ background-color: rgb(0 146 71 / 0.45);
+}
+
+.bg-green-500\/5 {
+ background-color: rgb(0 146 71 / 0.05);
+}
+
+.bg-green-500\/50 {
+ background-color: rgb(0 146 71 / 0.5);
+}
+
+.bg-green-500\/55 {
+ background-color: rgb(0 146 71 / 0.55);
+}
+
+.bg-green-500\/60 {
+ background-color: rgb(0 146 71 / 0.6);
+}
+
+.bg-green-500\/65 {
+ background-color: rgb(0 146 71 / 0.65);
+}
+
+.bg-green-500\/70 {
+ background-color: rgb(0 146 71 / 0.7);
+}
+
+.bg-green-500\/75 {
+ background-color: rgb(0 146 71 / 0.75);
+}
+
+.bg-green-500\/80 {
+ background-color: rgb(0 146 71 / 0.8);
+}
+
+.bg-green-500\/85 {
+ background-color: rgb(0 146 71 / 0.85);
+}
+
+.bg-green-500\/90 {
+ background-color: rgb(0 146 71 / 0.9);
+}
+
+.bg-green-500\/95 {
+ background-color: rgb(0 146 71 / 0.95);
+}
+
+.bg-green-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
+}
+
+.bg-indigo-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(224 231 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-indigo-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(129 140 248 / var(--tw-bg-opacity, 1));
+}
+
+.bg-indigo-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(238 242 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-orange-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 237 213 / var(--tw-bg-opacity, 1));
+}
+
+.bg-orange-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(251 146 60 / var(--tw-bg-opacity, 1));
+}
+
+.bg-orange-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 247 237 / var(--tw-bg-opacity, 1));
+}
+
+.bg-orange-50\/0 {
+ background-color: rgb(255 247 237 / 0);
+}
+
+.bg-orange-50\/10 {
+ background-color: rgb(255 247 237 / 0.1);
+}
+
+.bg-orange-50\/100 {
+ background-color: rgb(255 247 237 / 1);
+}
+
+.bg-orange-50\/15 {
+ background-color: rgb(255 247 237 / 0.15);
+}
+
+.bg-orange-50\/20 {
+ background-color: rgb(255 247 237 / 0.2);
+}
+
+.bg-orange-50\/25 {
+ background-color: rgb(255 247 237 / 0.25);
+}
+
+.bg-orange-50\/30 {
+ background-color: rgb(255 247 237 / 0.3);
+}
+
+.bg-orange-50\/35 {
+ background-color: rgb(255 247 237 / 0.35);
+}
+
+.bg-orange-50\/40 {
+ background-color: rgb(255 247 237 / 0.4);
+}
+
+.bg-orange-50\/45 {
+ background-color: rgb(255 247 237 / 0.45);
+}
+
+.bg-orange-50\/5 {
+ background-color: rgb(255 247 237 / 0.05);
+}
+
+.bg-orange-50\/50 {
+ background-color: rgb(255 247 237 / 0.5);
+}
+
+.bg-orange-50\/55 {
+ background-color: rgb(255 247 237 / 0.55);
+}
+
+.bg-orange-50\/60 {
+ background-color: rgb(255 247 237 / 0.6);
+}
+
+.bg-orange-50\/65 {
+ background-color: rgb(255 247 237 / 0.65);
+}
+
+.bg-orange-50\/70 {
+ background-color: rgb(255 247 237 / 0.7);
+}
+
+.bg-orange-50\/75 {
+ background-color: rgb(255 247 237 / 0.75);
+}
+
+.bg-orange-50\/80 {
+ background-color: rgb(255 247 237 / 0.8);
+}
+
+.bg-orange-50\/85 {
+ background-color: rgb(255 247 237 / 0.85);
+}
+
+.bg-orange-50\/90 {
+ background-color: rgb(255 247 237 / 0.9);
+}
+
+.bg-orange-50\/95 {
+ background-color: rgb(255 247 237 / 0.95);
+}
+
+.bg-orange-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 115 22 / var(--tw-bg-opacity, 1));
+}
+
+.bg-orange-500\/0 {
+ background-color: rgb(249 115 22 / 0);
+}
+
+.bg-orange-500\/10 {
+ background-color: rgb(249 115 22 / 0.1);
+}
+
+.bg-orange-500\/100 {
+ background-color: rgb(249 115 22 / 1);
+}
+
+.bg-orange-500\/15 {
+ background-color: rgb(249 115 22 / 0.15);
+}
+
+.bg-orange-500\/20 {
+ background-color: rgb(249 115 22 / 0.2);
+}
+
+.bg-orange-500\/25 {
+ background-color: rgb(249 115 22 / 0.25);
+}
+
+.bg-orange-500\/30 {
+ background-color: rgb(249 115 22 / 0.3);
+}
+
+.bg-orange-500\/35 {
+ background-color: rgb(249 115 22 / 0.35);
+}
+
+.bg-orange-500\/40 {
+ background-color: rgb(249 115 22 / 0.4);
+}
+
+.bg-orange-500\/45 {
+ background-color: rgb(249 115 22 / 0.45);
+}
+
+.bg-orange-500\/5 {
+ background-color: rgb(249 115 22 / 0.05);
+}
+
+.bg-orange-500\/50 {
+ background-color: rgb(249 115 22 / 0.5);
+}
+
+.bg-orange-500\/55 {
+ background-color: rgb(249 115 22 / 0.55);
+}
+
+.bg-orange-500\/60 {
+ background-color: rgb(249 115 22 / 0.6);
+}
+
+.bg-orange-500\/65 {
+ background-color: rgb(249 115 22 / 0.65);
+}
+
+.bg-orange-500\/70 {
+ background-color: rgb(249 115 22 / 0.7);
+}
+
+.bg-orange-500\/75 {
+ background-color: rgb(249 115 22 / 0.75);
+}
+
+.bg-orange-500\/80 {
+ background-color: rgb(249 115 22 / 0.8);
+}
+
+.bg-orange-500\/85 {
+ background-color: rgb(249 115 22 / 0.85);
+}
+
+.bg-orange-500\/90 {
+ background-color: rgb(249 115 22 / 0.9);
+}
+
+.bg-orange-500\/95 {
+ background-color: rgb(249 115 22 / 0.95);
+}
+
+.bg-pink-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(236 72 153 / var(--tw-bg-opacity, 1));
+}
+
+.bg-purple-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 232 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-purple-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(192 132 252 / var(--tw-bg-opacity, 1));
+}
+
+.bg-purple-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(250 245 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-purple-50\/0 {
+ background-color: rgb(250 245 255 / 0);
+}
+
+.bg-purple-50\/10 {
+ background-color: rgb(250 245 255 / 0.1);
+}
+
+.bg-purple-50\/100 {
+ background-color: rgb(250 245 255 / 1);
+}
+
+.bg-purple-50\/15 {
+ background-color: rgb(250 245 255 / 0.15);
+}
+
+.bg-purple-50\/20 {
+ background-color: rgb(250 245 255 / 0.2);
+}
+
+.bg-purple-50\/25 {
+ background-color: rgb(250 245 255 / 0.25);
+}
+
+.bg-purple-50\/30 {
+ background-color: rgb(250 245 255 / 0.3);
+}
+
+.bg-purple-50\/35 {
+ background-color: rgb(250 245 255 / 0.35);
+}
+
+.bg-purple-50\/40 {
+ background-color: rgb(250 245 255 / 0.4);
+}
+
+.bg-purple-50\/45 {
+ background-color: rgb(250 245 255 / 0.45);
+}
+
+.bg-purple-50\/5 {
+ background-color: rgb(250 245 255 / 0.05);
+}
+
+.bg-purple-50\/50 {
+ background-color: rgb(250 245 255 / 0.5);
+}
+
+.bg-purple-50\/55 {
+ background-color: rgb(250 245 255 / 0.55);
+}
+
+.bg-purple-50\/60 {
+ background-color: rgb(250 245 255 / 0.6);
+}
+
+.bg-purple-50\/65 {
+ background-color: rgb(250 245 255 / 0.65);
+}
+
+.bg-purple-50\/70 {
+ background-color: rgb(250 245 255 / 0.7);
+}
+
+.bg-purple-50\/75 {
+ background-color: rgb(250 245 255 / 0.75);
+}
+
+.bg-purple-50\/80 {
+ background-color: rgb(250 245 255 / 0.8);
+}
+
+.bg-purple-50\/85 {
+ background-color: rgb(250 245 255 / 0.85);
+}
+
+.bg-purple-50\/90 {
+ background-color: rgb(250 245 255 / 0.9);
+}
+
+.bg-purple-50\/95 {
+ background-color: rgb(250 245 255 / 0.95);
+}
+
+.bg-purple-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(168 85 247 / var(--tw-bg-opacity, 1));
+}
+
+.bg-purple-500\/0 {
+ background-color: rgb(168 85 247 / 0);
+}
+
+.bg-purple-500\/10 {
+ background-color: rgb(168 85 247 / 0.1);
+}
+
+.bg-purple-500\/100 {
+ background-color: rgb(168 85 247 / 1);
+}
+
+.bg-purple-500\/15 {
+ background-color: rgb(168 85 247 / 0.15);
+}
+
+.bg-purple-500\/20 {
+ background-color: rgb(168 85 247 / 0.2);
+}
+
+.bg-purple-500\/25 {
+ background-color: rgb(168 85 247 / 0.25);
+}
+
+.bg-purple-500\/30 {
+ background-color: rgb(168 85 247 / 0.3);
+}
+
+.bg-purple-500\/35 {
+ background-color: rgb(168 85 247 / 0.35);
+}
+
+.bg-purple-500\/40 {
+ background-color: rgb(168 85 247 / 0.4);
+}
+
+.bg-purple-500\/45 {
+ background-color: rgb(168 85 247 / 0.45);
+}
+
+.bg-purple-500\/5 {
+ background-color: rgb(168 85 247 / 0.05);
+}
+
+.bg-purple-500\/50 {
+ background-color: rgb(168 85 247 / 0.5);
+}
+
+.bg-purple-500\/55 {
+ background-color: rgb(168 85 247 / 0.55);
+}
+
+.bg-purple-500\/60 {
+ background-color: rgb(168 85 247 / 0.6);
+}
+
+.bg-purple-500\/65 {
+ background-color: rgb(168 85 247 / 0.65);
+}
+
+.bg-purple-500\/70 {
+ background-color: rgb(168 85 247 / 0.7);
+}
+
+.bg-purple-500\/75 {
+ background-color: rgb(168 85 247 / 0.75);
+}
+
+.bg-purple-500\/80 {
+ background-color: rgb(168 85 247 / 0.8);
+}
+
+.bg-purple-500\/85 {
+ background-color: rgb(168 85 247 / 0.85);
+}
+
+.bg-purple-500\/90 {
+ background-color: rgb(168 85 247 / 0.9);
+}
+
+.bg-purple-500\/95 {
+ background-color: rgb(168 85 247 / 0.95);
+}
+
+.bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
+}
+
+.bg-red-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity, 1));
+}
+
+.bg-red-400\/30 {
+ background-color: rgb(248 113 113 / 0.3);
+}
+
+.bg-red-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
+}
+
+.bg-red-50\/0 {
+ background-color: rgb(254 242 242 / 0);
+}
+
+.bg-red-50\/10 {
+ background-color: rgb(254 242 242 / 0.1);
+}
+
+.bg-red-50\/100 {
+ background-color: rgb(254 242 242 / 1);
+}
+
+.bg-red-50\/15 {
+ background-color: rgb(254 242 242 / 0.15);
+}
+
+.bg-red-50\/20 {
+ background-color: rgb(254 242 242 / 0.2);
+}
+
+.bg-red-50\/25 {
+ background-color: rgb(254 242 242 / 0.25);
+}
+
+.bg-red-50\/30 {
+ background-color: rgb(254 242 242 / 0.3);
+}
+
+.bg-red-50\/35 {
+ background-color: rgb(254 242 242 / 0.35);
+}
+
+.bg-red-50\/40 {
+ background-color: rgb(254 242 242 / 0.4);
+}
+
+.bg-red-50\/45 {
+ background-color: rgb(254 242 242 / 0.45);
+}
+
+.bg-red-50\/5 {
+ background-color: rgb(254 242 242 / 0.05);
+}
+
+.bg-red-50\/50 {
+ background-color: rgb(254 242 242 / 0.5);
+}
+
+.bg-red-50\/55 {
+ background-color: rgb(254 242 242 / 0.55);
+}
+
+.bg-red-50\/60 {
+ background-color: rgb(254 242 242 / 0.6);
+}
+
+.bg-red-50\/65 {
+ background-color: rgb(254 242 242 / 0.65);
+}
+
+.bg-red-50\/70 {
+ background-color: rgb(254 242 242 / 0.7);
+}
+
+.bg-red-50\/75 {
+ background-color: rgb(254 242 242 / 0.75);
+}
+
+.bg-red-50\/80 {
+ background-color: rgb(254 242 242 / 0.8);
+}
+
+.bg-red-50\/85 {
+ background-color: rgb(254 242 242 / 0.85);
+}
+
+.bg-red-50\/90 {
+ background-color: rgb(254 242 242 / 0.9);
+}
+
+.bg-red-50\/95 {
+ background-color: rgb(254 242 242 / 0.95);
+}
+
+.bg-red-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
+}
+
+.bg-red-500\/0 {
+ background-color: rgb(239 68 68 / 0);
+}
+
+.bg-red-500\/10 {
+ background-color: rgb(239 68 68 / 0.1);
+}
+
+.bg-red-500\/100 {
+ background-color: rgb(239 68 68 / 1);
+}
+
+.bg-red-500\/15 {
+ background-color: rgb(239 68 68 / 0.15);
+}
+
+.bg-red-500\/20 {
+ background-color: rgb(239 68 68 / 0.2);
+}
+
+.bg-red-500\/25 {
+ background-color: rgb(239 68 68 / 0.25);
+}
+
+.bg-red-500\/30 {
+ background-color: rgb(239 68 68 / 0.3);
+}
+
+.bg-red-500\/35 {
+ background-color: rgb(239 68 68 / 0.35);
+}
+
+.bg-red-500\/40 {
+ background-color: rgb(239 68 68 / 0.4);
+}
+
+.bg-red-500\/45 {
+ background-color: rgb(239 68 68 / 0.45);
+}
+
+.bg-red-500\/5 {
+ background-color: rgb(239 68 68 / 0.05);
+}
+
+.bg-red-500\/50 {
+ background-color: rgb(239 68 68 / 0.5);
+}
+
+.bg-red-500\/55 {
+ background-color: rgb(239 68 68 / 0.55);
+}
+
+.bg-red-500\/60 {
+ background-color: rgb(239 68 68 / 0.6);
+}
+
+.bg-red-500\/65 {
+ background-color: rgb(239 68 68 / 0.65);
+}
+
+.bg-red-500\/70 {
+ background-color: rgb(239 68 68 / 0.7);
+}
+
+.bg-red-500\/75 {
+ background-color: rgb(239 68 68 / 0.75);
+}
+
+.bg-red-500\/80 {
+ background-color: rgb(239 68 68 / 0.8);
+}
+
+.bg-red-500\/85 {
+ background-color: rgb(239 68 68 / 0.85);
+}
+
+.bg-red-500\/90 {
+ background-color: rgb(239 68 68 / 0.9);
+}
+
+.bg-red-500\/95 {
+ background-color: rgb(239 68 68 / 0.95);
+}
+
+.bg-red-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
+}
+
+.bg-rose-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 228 230 / var(--tw-bg-opacity, 1));
+}
+
+.bg-rose-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 241 242 / var(--tw-bg-opacity, 1));
+}
+
+.bg-sky-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(240 249 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-sky-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(14 165 233 / var(--tw-bg-opacity, 1));
+}
+
+.bg-teal-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(240 253 250 / var(--tw-bg-opacity, 1));
+}
+
+.bg-transparent {
+ background-color: transparent;
+}
+
+.bg-violet-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(245 243 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-violet-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(139 92 246 / var(--tw-bg-opacity, 1));
+}
+
+.bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-white\/10 {
+ background-color: rgb(255 255 255 / 0.1);
+}
+
+.bg-white\/15 {
+ background-color: rgb(255 255 255 / 0.15);
+}
+
+.bg-white\/20 {
+ background-color: rgb(255 255 255 / 0.2);
+}
+
+.bg-white\/50 {
+ background-color: rgb(255 255 255 / 0.5);
+}
+
+.bg-white\/60 {
+ background-color: rgb(255 255 255 / 0.6);
+}
+
+.bg-white\/70 {
+ background-color: rgb(255 255 255 / 0.7);
+}
+
+.bg-white\/80 {
+ background-color: rgb(255 255 255 / 0.8);
+}
+
+.bg-white\/95 {
+ background-color: rgb(255 255 255 / 0.95);
+}
+
+.bg-yellow-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1));
+}
+
+.bg-yellow-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(250 204 21 / var(--tw-bg-opacity, 1));
+}
+
+.bg-yellow-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 252 232 / var(--tw-bg-opacity, 1));
+}
+
+.bg-yellow-50\/0 {
+ background-color: rgb(254 252 232 / 0);
+}
+
+.bg-yellow-50\/10 {
+ background-color: rgb(254 252 232 / 0.1);
+}
+
+.bg-yellow-50\/100 {
+ background-color: rgb(254 252 232 / 1);
+}
+
+.bg-yellow-50\/15 {
+ background-color: rgb(254 252 232 / 0.15);
+}
+
+.bg-yellow-50\/20 {
+ background-color: rgb(254 252 232 / 0.2);
+}
+
+.bg-yellow-50\/25 {
+ background-color: rgb(254 252 232 / 0.25);
+}
+
+.bg-yellow-50\/30 {
+ background-color: rgb(254 252 232 / 0.3);
+}
+
+.bg-yellow-50\/35 {
+ background-color: rgb(254 252 232 / 0.35);
+}
+
+.bg-yellow-50\/40 {
+ background-color: rgb(254 252 232 / 0.4);
+}
+
+.bg-yellow-50\/45 {
+ background-color: rgb(254 252 232 / 0.45);
+}
+
+.bg-yellow-50\/5 {
+ background-color: rgb(254 252 232 / 0.05);
+}
+
+.bg-yellow-50\/50 {
+ background-color: rgb(254 252 232 / 0.5);
+}
+
+.bg-yellow-50\/55 {
+ background-color: rgb(254 252 232 / 0.55);
+}
+
+.bg-yellow-50\/60 {
+ background-color: rgb(254 252 232 / 0.6);
+}
+
+.bg-yellow-50\/65 {
+ background-color: rgb(254 252 232 / 0.65);
+}
+
+.bg-yellow-50\/70 {
+ background-color: rgb(254 252 232 / 0.7);
+}
+
+.bg-yellow-50\/75 {
+ background-color: rgb(254 252 232 / 0.75);
+}
+
+.bg-yellow-50\/80 {
+ background-color: rgb(254 252 232 / 0.8);
+}
+
+.bg-yellow-50\/85 {
+ background-color: rgb(254 252 232 / 0.85);
+}
+
+.bg-yellow-50\/90 {
+ background-color: rgb(254 252 232 / 0.9);
+}
+
+.bg-yellow-50\/95 {
+ background-color: rgb(254 252 232 / 0.95);
+}
+
+.bg-yellow-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1));
+}
+
+.bg-yellow-500\/0 {
+ background-color: rgb(234 179 8 / 0);
+}
+
+.bg-yellow-500\/10 {
+ background-color: rgb(234 179 8 / 0.1);
+}
+
+.bg-yellow-500\/100 {
+ background-color: rgb(234 179 8 / 1);
+}
+
+.bg-yellow-500\/15 {
+ background-color: rgb(234 179 8 / 0.15);
+}
+
+.bg-yellow-500\/20 {
+ background-color: rgb(234 179 8 / 0.2);
+}
+
+.bg-yellow-500\/25 {
+ background-color: rgb(234 179 8 / 0.25);
+}
+
+.bg-yellow-500\/30 {
+ background-color: rgb(234 179 8 / 0.3);
+}
+
+.bg-yellow-500\/35 {
+ background-color: rgb(234 179 8 / 0.35);
+}
+
+.bg-yellow-500\/40 {
+ background-color: rgb(234 179 8 / 0.4);
+}
+
+.bg-yellow-500\/45 {
+ background-color: rgb(234 179 8 / 0.45);
+}
+
+.bg-yellow-500\/5 {
+ background-color: rgb(234 179 8 / 0.05);
+}
+
+.bg-yellow-500\/50 {
+ background-color: rgb(234 179 8 / 0.5);
+}
+
+.bg-yellow-500\/55 {
+ background-color: rgb(234 179 8 / 0.55);
+}
+
+.bg-yellow-500\/60 {
+ background-color: rgb(234 179 8 / 0.6);
+}
+
+.bg-yellow-500\/65 {
+ background-color: rgb(234 179 8 / 0.65);
+}
+
+.bg-yellow-500\/70 {
+ background-color: rgb(234 179 8 / 0.7);
+}
+
+.bg-yellow-500\/75 {
+ background-color: rgb(234 179 8 / 0.75);
+}
+
+.bg-yellow-500\/80 {
+ background-color: rgb(234 179 8 / 0.8);
+}
+
+.bg-yellow-500\/85 {
+ background-color: rgb(234 179 8 / 0.85);
+}
+
+.bg-yellow-500\/90 {
+ background-color: rgb(234 179 8 / 0.9);
+}
+
+.bg-yellow-500\/95 {
+ background-color: rgb(234 179 8 / 0.95);
+}
+
+.bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+}
+
+.bg-opacity-70 {
+ --tw-bg-opacity: 0.7;
+}
+
+.bg-\[radial-gradient\(circle_at_20\%_20\%\2c \#ffffff_0\%\2c transparent_45\%\)\2c radial-gradient\(circle_at_80\%_80\%\2c \#d5df50_0\%\2c transparent_38\%\)\] {
+ background-image: radial-gradient(circle at 20% 20%,#ffffff 0%,transparent 45%),radial-gradient(circle at 80% 80%,#d5df50 0%,transparent 38%);
+}
+
+.bg-gradient-to-br {
+ background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
+}
+
+.bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+}
+
+.from-\[\#252526\] {
+ --tw-gradient-from: #252526 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(37 37 38 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-\[\#E70B2A\] {
+ --tw-gradient-from: #E70B2A var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(231 11 42 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-\[\#F90527\] {
+ --tw-gradient-from: #F90527 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(249 5 39 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-blue-100 {
+ --tw-gradient-from: #dbeafe var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(219 234 254 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-blue-50 {
+ --tw-gradient-from: #eff6ff var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(239 246 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-brand-50 {
+ --tw-gradient-from: #fef7ee var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(254 247 238 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-emerald-50 {
+ --tw-gradient-from: #ecfdf5 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(236 253 245 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-gray-400 {
+ --tw-gradient-from: #9ca3af var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(156 163 175 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-gray-50 {
+ --tw-gradient-from: #f9fafb var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(249 250 251 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-green-400 {
+ --tw-gradient-from: #4ade80 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(74 222 128 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-green-50 {
+ --tw-gradient-from: #f0fdf4 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(240 253 244 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-pink-100 {
+ --tw-gradient-from: #fce7f3 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(252 231 243 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-pink-50 {
+ --tw-gradient-from: #fdf2f8 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(253 242 248 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-purple-100 {
+ --tw-gradient-from: #f3e8ff var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(243 232 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-purple-400 {
+ --tw-gradient-from: #c084fc var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(192 132 252 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-red-100 {
+ --tw-gradient-from: #fee2e2 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(254 226 226 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-red-300 {
+ --tw-gradient-from: #fca5a5 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(252 165 165 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-red-50 {
+ --tw-gradient-from: #fef2f2 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(254 242 242 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-red-500 {
+ --tw-gradient-from: #ef4444 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(239 68 68 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-red-600 {
+ --tw-gradient-from: #dc2626 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(220 38 38 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-rose-50 {
+ --tw-gradient-from: #fff1f2 var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(255 241 242 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.from-sky-50 {
+ --tw-gradient-from: #f0f9ff var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(240 249 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.via-pink-300 {
+ --tw-gradient-to: rgb(249 168 212 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #f9a8d4 var(--tw-gradient-via-position), var(--tw-gradient-to);
+}
+
+.via-pink-600 {
+ --tw-gradient-to: rgb(219 39 119 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #db2777 var(--tw-gradient-via-position), var(--tw-gradient-to);
+}
+
+.via-purple-50 {
+ --tw-gradient-to: rgb(250 245 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #faf5ff var(--tw-gradient-via-position), var(--tw-gradient-to);
+}
+
+.via-white {
+ --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to);
+}
+
+.to-\[\#1e1e1e\] {
+ --tw-gradient-to: #1e1e1e var(--tw-gradient-to-position);
+}
+
+.to-\[\#EA0E50\] {
+ --tw-gradient-to: #EA0E50 var(--tw-gradient-to-position);
+}
+
+.to-\[\#F50C52\] {
+ --tw-gradient-to: #F50C52 var(--tw-gradient-to-position);
+}
+
+.to-blue-50 {
+ --tw-gradient-to: #eff6ff var(--tw-gradient-to-position);
+}
+
+.to-brand-100 {
+ --tw-gradient-to: #fdebd3 var(--tw-gradient-to-position);
+}
+
+.to-emerald-50 {
+ --tw-gradient-to: #ecfdf5 var(--tw-gradient-to-position);
+}
+
+.to-gray-100 {
+ --tw-gradient-to: #EAEEF5 var(--tw-gradient-to-position);
+}
+
+.to-gray-200 {
+ --tw-gradient-to: #e5e7eb var(--tw-gradient-to-position);
+}
+
+.to-gray-500 {
+ --tw-gradient-to: #6b7280 var(--tw-gradient-to-position);
+}
+
+.to-green-50 {
+ --tw-gradient-to: #f0fdf4 var(--tw-gradient-to-position);
+}
+
+.to-green-500 {
+ --tw-gradient-to: #009247 var(--tw-gradient-to-position);
+}
+
+.to-indigo-50 {
+ --tw-gradient-to: #eef2ff var(--tw-gradient-to-position);
+}
+
+.to-orange-500 {
+ --tw-gradient-to: #f97316 var(--tw-gradient-to-position);
+}
+
+.to-pink-100 {
+ --tw-gradient-to: #fce7f3 var(--tw-gradient-to-position);
+}
+
+.to-pink-500 {
+ --tw-gradient-to: #ec4899 var(--tw-gradient-to-position);
+}
+
+.to-purple-100 {
+ --tw-gradient-to: #f3e8ff var(--tw-gradient-to-position);
+}
+
+.to-purple-300 {
+ --tw-gradient-to: #d8b4fe var(--tw-gradient-to-position);
+}
+
+.to-purple-50 {
+ --tw-gradient-to: #faf5ff var(--tw-gradient-to-position);
+}
+
+.to-purple-600 {
+ --tw-gradient-to: #9333ea var(--tw-gradient-to-position);
+}
+
+.to-red-600 {
+ --tw-gradient-to: #dc2626 var(--tw-gradient-to-position);
+}
+
+.to-rose-50 {
+ --tw-gradient-to: #fff1f2 var(--tw-gradient-to-position);
+}
+
+.to-teal-100 {
+ --tw-gradient-to: #ccfbf1 var(--tw-gradient-to-position);
+}
+
+.bg-cover {
+ background-size: cover;
+}
+
+.bg-center {
+ background-position: center;
+}
+
+.fill-red-500 {
+ fill: #ef4444;
+}
+
+.object-contain {
+ -o-object-fit: contain;
+ object-fit: contain;
+}
+
+.object-cover {
+ -o-object-fit: cover;
+ object-fit: cover;
+}
+
+.p-0 {
+ padding: 0px;
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+
+.p-10 {
+ padding: 2.5rem;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-3 {
+ padding: 0.75rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.p-5 {
+ padding: 1.25rem;
+}
+
+.p-6 {
+ padding: 1.5rem;
+}
+
+.p-8 {
+ padding: 2rem;
+}
+
+.px-10 {
+ padding-left: 2.5rem;
+ padding-right: 2.5rem;
+}
+
+.px-2 {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.px-3 {
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+.px-5 {
+ padding-left: 1.25rem;
+ padding-right: 1.25rem;
+}
+
+.px-6 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+}
+
+.px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
+}
+
+.px-\[24px\] {
+ padding-left: 24px;
+ padding-right: 24px;
+}
+
+.px-\[36px\] {
+ padding-left: 36px;
+ padding-right: 36px;
+}
+
+.py-0\.5 {
+ padding-top: 0.125rem;
+ padding-bottom: 0.125rem;
+}
+
+.py-1 {
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+}
+
+.py-10 {
+ padding-top: 2.5rem;
+ padding-bottom: 2.5rem;
+}
+
+.py-12 {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+}
+
+.py-16 {
+ padding-top: 4rem;
+ padding-bottom: 4rem;
+}
+
+.py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.py-2\.5 {
+ padding-top: 0.625rem;
+ padding-bottom: 0.625rem;
+}
+
+.py-20 {
+ padding-top: 5rem;
+ padding-bottom: 5rem;
+}
+
+.py-24 {
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+}
+
+.py-3 {
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
+}
+
+.py-4 {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+}
+
+.py-8 {
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+}
+
+.py-\[12px\] {
+ padding-top: 12px;
+ padding-bottom: 12px;
+}
+
+.py-\[32px\] {
+ padding-top: 32px;
+ padding-bottom: 32px;
+}
+
+.py-\[72px\] {
+ padding-top: 72px;
+ padding-bottom: 72px;
+}
+
+.pb-1 {
+ padding-bottom: 0.25rem;
+}
+
+.pb-12 {
+ padding-bottom: 3rem;
+}
+
+.pb-16 {
+ padding-bottom: 4rem;
+}
+
+.pb-24 {
+ padding-bottom: 6rem;
+}
+
+.pb-6 {
+ padding-bottom: 1.5rem;
+}
+
+.pl-2 {
+ padding-left: 0.5rem;
+}
+
+.pl-8 {
+ padding-left: 2rem;
+}
+
+.pr-12 {
+ padding-right: 3rem;
+}
+
+.pr-4 {
+ padding-right: 1rem;
+}
+
+.pt-3 {
+ padding-top: 0.75rem;
+}
+
+.pt-4 {
+ padding-top: 1rem;
+}
+
+.pt-8 {
+ padding-top: 2rem;
+}
+
+.pt-\[220px\] {
+ padding-top: 220px;
+}
+
+.text-left {
+ text-align: left;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.font-\[\'Barlow_Semi_Condensed\'\] {
+ font-family: 'Barlow Semi Condensed';
+}
+
+.font-\[\'Lato\'\] {
+ font-family: 'Lato';
+}
+
+.font-mono {
+ font-family: JetBrains Mono, monospace;
+}
+
+.font-sans {
+ font-family: Lato, system-ui, sans-serif;
+}
+
+.font-title {
+ font-family: Barlow Semi Condensed, sans-serif;
+}
+
+.text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+}
+
+.text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+}
+
+.text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+}
+
+.text-6xl {
+ font-size: 3.75rem;
+ line-height: 1;
+}
+
+.text-8xl {
+ font-size: 6rem;
+ line-height: 1;
+}
+
+.text-\[11px\] {
+ font-size: 11px;
+}
+
+.text-\[13px\] {
+ font-size: 13px;
+}
+
+.text-\[15px\] {
+ font-size: 15px;
+}
+
+.text-\[18px\] {
+ font-size: 18px;
+}
+
+.text-\[20px\] {
+ font-size: 20px;
+}
+
+.text-\[24px\] {
+ font-size: 24px;
+}
+
+.text-\[28px\] {
+ font-size: 28px;
+}
+
+.text-\[2rem\] {
+ font-size: 2rem;
+}
+
+.text-\[30px\] {
+ font-size: 30px;
+}
+
+.text-\[32px\] {
+ font-size: 32px;
+}
+
+.text-\[34px\] {
+ font-size: 34px;
+}
+
+.text-\[40px\] {
+ font-size: 40px;
+}
+
+.text-\[42px\] {
+ font-size: 42px;
+}
+
+.text-\[60px\] {
+ font-size: 60px;
+}
+
+.text-\[6rem\] {
+ font-size: 6rem;
+}
+
+.text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+}
+
+.text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+}
+
+.text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+}
+
+.text-xs {
+ font-size: 0.75rem;
+ line-height: 1rem;
+}
+
+.font-black {
+ font-weight: 900;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.font-normal {
+ font-weight: 400;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.uppercase {
+ text-transform: uppercase;
+}
+
+.capitalize {
+ text-transform: capitalize;
+}
+
+.italic {
+ font-style: italic;
+}
+
+.leading-\[29px\] {
+ line-height: 29px;
+}
+
+.leading-\[38px\] {
+ line-height: 38px;
+}
+
+.leading-\[48px\] {
+ line-height: 48px;
+}
+
+.leading-none {
+ line-height: 1;
+}
+
+.leading-relaxed {
+ line-height: 1.625;
+}
+
+.leading-tight {
+ line-height: 1.25;
+}
+
+.tracking-\[0\.18em\] {
+ letter-spacing: 0.18em;
+}
+
+.tracking-tight {
+ letter-spacing: -0.025em;
+}
+
+.tracking-wide {
+ letter-spacing: 0.025em;
+}
+
+.text-\[\#201D1E\] {
+ --tw-text-opacity: 1;
+ color: rgb(32 29 30 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#454041\] {
+ --tw-text-opacity: 1;
+ color: rgb(69 64 65 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#AF1F16\] {
+ --tw-text-opacity: 1;
+ color: rgb(175 31 22 / var(--tw-text-opacity, 1));
+}
+
+.text-\[\#FFFFFF\] {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.text-amber-600 {
+ --tw-text-opacity: 1;
+ color: rgb(217 119 6 / var(--tw-text-opacity, 1));
+}
+
+.text-amber-700 {
+ --tw-text-opacity: 1;
+ color: rgb(180 83 9 / var(--tw-text-opacity, 1));
+}
+
+.text-amber-800 {
+ --tw-text-opacity: 1;
+ color: rgb(146 64 14 / var(--tw-text-opacity, 1));
+}
+
+.text-amber-900 {
+ --tw-text-opacity: 1;
+ color: rgb(120 53 15 / var(--tw-text-opacity, 1));
+}
+
+.text-black {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity, 1));
+}
+
+.text-blue-400 {
+ --tw-text-opacity: 1;
+ color: rgb(96 165 250 / var(--tw-text-opacity, 1));
+}
+
+.text-blue-500 {
+ --tw-text-opacity: 1;
+ color: rgb(59 130 246 / var(--tw-text-opacity, 1));
+}
+
+.text-blue-500\/0 {
+ color: rgb(59 130 246 / 0);
+}
+
+.text-blue-500\/10 {
+ color: rgb(59 130 246 / 0.1);
+}
+
+.text-blue-500\/100 {
+ color: rgb(59 130 246 / 1);
+}
+
+.text-blue-500\/15 {
+ color: rgb(59 130 246 / 0.15);
+}
+
+.text-blue-500\/20 {
+ color: rgb(59 130 246 / 0.2);
+}
+
+.text-blue-500\/25 {
+ color: rgb(59 130 246 / 0.25);
+}
+
+.text-blue-500\/30 {
+ color: rgb(59 130 246 / 0.3);
+}
+
+.text-blue-500\/35 {
+ color: rgb(59 130 246 / 0.35);
+}
+
+.text-blue-500\/40 {
+ color: rgb(59 130 246 / 0.4);
+}
+
+.text-blue-500\/45 {
+ color: rgb(59 130 246 / 0.45);
+}
+
+.text-blue-500\/5 {
+ color: rgb(59 130 246 / 0.05);
+}
+
+.text-blue-500\/50 {
+ color: rgb(59 130 246 / 0.5);
+}
+
+.text-blue-500\/55 {
+ color: rgb(59 130 246 / 0.55);
+}
+
+.text-blue-500\/60 {
+ color: rgb(59 130 246 / 0.6);
+}
+
+.text-blue-500\/65 {
+ color: rgb(59 130 246 / 0.65);
+}
+
+.text-blue-500\/70 {
+ color: rgb(59 130 246 / 0.7);
+}
+
+.text-blue-500\/75 {
+ color: rgb(59 130 246 / 0.75);
+}
+
+.text-blue-500\/80 {
+ color: rgb(59 130 246 / 0.8);
+}
+
+.text-blue-500\/85 {
+ color: rgb(59 130 246 / 0.85);
+}
+
+.text-blue-500\/90 {
+ color: rgb(59 130 246 / 0.9);
+}
+
+.text-blue-500\/95 {
+ color: rgb(59 130 246 / 0.95);
+}
+
+.text-blue-600 {
+ --tw-text-opacity: 1;
+ color: rgb(37 99 235 / var(--tw-text-opacity, 1));
+}
+
+.text-blue-600\/0 {
+ color: rgb(37 99 235 / 0);
+}
+
+.text-blue-600\/10 {
+ color: rgb(37 99 235 / 0.1);
+}
+
+.text-blue-600\/100 {
+ color: rgb(37 99 235 / 1);
+}
+
+.text-blue-600\/15 {
+ color: rgb(37 99 235 / 0.15);
+}
+
+.text-blue-600\/20 {
+ color: rgb(37 99 235 / 0.2);
+}
+
+.text-blue-600\/25 {
+ color: rgb(37 99 235 / 0.25);
+}
+
+.text-blue-600\/30 {
+ color: rgb(37 99 235 / 0.3);
+}
+
+.text-blue-600\/35 {
+ color: rgb(37 99 235 / 0.35);
+}
+
+.text-blue-600\/40 {
+ color: rgb(37 99 235 / 0.4);
+}
+
+.text-blue-600\/45 {
+ color: rgb(37 99 235 / 0.45);
+}
+
+.text-blue-600\/5 {
+ color: rgb(37 99 235 / 0.05);
+}
+
+.text-blue-600\/50 {
+ color: rgb(37 99 235 / 0.5);
+}
+
+.text-blue-600\/55 {
+ color: rgb(37 99 235 / 0.55);
+}
+
+.text-blue-600\/60 {
+ color: rgb(37 99 235 / 0.6);
+}
+
+.text-blue-600\/65 {
+ color: rgb(37 99 235 / 0.65);
+}
+
+.text-blue-600\/70 {
+ color: rgb(37 99 235 / 0.7);
+}
+
+.text-blue-600\/75 {
+ color: rgb(37 99 235 / 0.75);
+}
+
+.text-blue-600\/80 {
+ color: rgb(37 99 235 / 0.8);
+}
+
+.text-blue-600\/85 {
+ color: rgb(37 99 235 / 0.85);
+}
+
+.text-blue-600\/90 {
+ color: rgb(37 99 235 / 0.9);
+}
+
+.text-blue-600\/95 {
+ color: rgb(37 99 235 / 0.95);
+}
+
+.text-blue-700 {
+ --tw-text-opacity: 1;
+ color: rgb(29 78 216 / var(--tw-text-opacity, 1));
+}
+
+.text-blue-700\/0 {
+ color: rgb(29 78 216 / 0);
+}
+
+.text-blue-700\/10 {
+ color: rgb(29 78 216 / 0.1);
+}
+
+.text-blue-700\/100 {
+ color: rgb(29 78 216 / 1);
+}
+
+.text-blue-700\/15 {
+ color: rgb(29 78 216 / 0.15);
+}
+
+.text-blue-700\/20 {
+ color: rgb(29 78 216 / 0.2);
+}
+
+.text-blue-700\/25 {
+ color: rgb(29 78 216 / 0.25);
+}
+
+.text-blue-700\/30 {
+ color: rgb(29 78 216 / 0.3);
+}
+
+.text-blue-700\/35 {
+ color: rgb(29 78 216 / 0.35);
+}
+
+.text-blue-700\/40 {
+ color: rgb(29 78 216 / 0.4);
+}
+
+.text-blue-700\/45 {
+ color: rgb(29 78 216 / 0.45);
+}
+
+.text-blue-700\/5 {
+ color: rgb(29 78 216 / 0.05);
+}
+
+.text-blue-700\/50 {
+ color: rgb(29 78 216 / 0.5);
+}
+
+.text-blue-700\/55 {
+ color: rgb(29 78 216 / 0.55);
+}
+
+.text-blue-700\/60 {
+ color: rgb(29 78 216 / 0.6);
+}
+
+.text-blue-700\/65 {
+ color: rgb(29 78 216 / 0.65);
+}
+
+.text-blue-700\/70 {
+ color: rgb(29 78 216 / 0.7);
+}
+
+.text-blue-700\/75 {
+ color: rgb(29 78 216 / 0.75);
+}
+
+.text-blue-700\/80 {
+ color: rgb(29 78 216 / 0.8);
+}
+
+.text-blue-700\/85 {
+ color: rgb(29 78 216 / 0.85);
+}
+
+.text-blue-700\/90 {
+ color: rgb(29 78 216 / 0.9);
+}
+
+.text-blue-700\/95 {
+ color: rgb(29 78 216 / 0.95);
+}
+
+.text-brand-500 {
+ --tw-text-opacity: 1;
+ color: rgb(254 0 2 / var(--tw-text-opacity, 1));
+}
+
+.text-brand-600 {
+ --tw-text-opacity: 1;
+ color: rgb(222 78 50 / var(--tw-text-opacity, 1));
+}
+
+.text-brand-700 {
+ --tw-text-opacity: 1;
+ color: rgb(199 62 43 / var(--tw-text-opacity, 1));
+}
+
+.text-brand-900 {
+ --tw-text-opacity: 1;
+ color: rgb(129 45 37 / var(--tw-text-opacity, 1));
+}
+
+.text-cyan-400 {
+ --tw-text-opacity: 1;
+ color: rgb(34 211 238 / var(--tw-text-opacity, 1));
+}
+
+.text-emerald-700 {
+ --tw-text-opacity: 1;
+ color: rgb(4 120 87 / var(--tw-text-opacity, 1));
+}
+
+.text-emerald-800 {
+ --tw-text-opacity: 1;
+ color: rgb(6 95 70 / var(--tw-text-opacity, 1));
+}
+
+.text-fuchsia-700 {
+ --tw-text-opacity: 1;
+ color: rgb(162 28 175 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-300 {
+ --tw-text-opacity: 1;
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-400 {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-500 {
+ --tw-text-opacity: 1;
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-500\/0 {
+ color: rgb(107 114 128 / 0);
+}
+
+.text-gray-500\/10 {
+ color: rgb(107 114 128 / 0.1);
+}
+
+.text-gray-500\/100 {
+ color: rgb(107 114 128 / 1);
+}
+
+.text-gray-500\/15 {
+ color: rgb(107 114 128 / 0.15);
+}
+
+.text-gray-500\/20 {
+ color: rgb(107 114 128 / 0.2);
+}
+
+.text-gray-500\/25 {
+ color: rgb(107 114 128 / 0.25);
+}
+
+.text-gray-500\/30 {
+ color: rgb(107 114 128 / 0.3);
+}
+
+.text-gray-500\/35 {
+ color: rgb(107 114 128 / 0.35);
+}
+
+.text-gray-500\/40 {
+ color: rgb(107 114 128 / 0.4);
+}
+
+.text-gray-500\/45 {
+ color: rgb(107 114 128 / 0.45);
+}
+
+.text-gray-500\/5 {
+ color: rgb(107 114 128 / 0.05);
+}
+
+.text-gray-500\/50 {
+ color: rgb(107 114 128 / 0.5);
+}
+
+.text-gray-500\/55 {
+ color: rgb(107 114 128 / 0.55);
+}
+
+.text-gray-500\/60 {
+ color: rgb(107 114 128 / 0.6);
+}
+
+.text-gray-500\/65 {
+ color: rgb(107 114 128 / 0.65);
+}
+
+.text-gray-500\/70 {
+ color: rgb(107 114 128 / 0.7);
+}
+
+.text-gray-500\/75 {
+ color: rgb(107 114 128 / 0.75);
+}
+
+.text-gray-500\/80 {
+ color: rgb(107 114 128 / 0.8);
+}
+
+.text-gray-500\/85 {
+ color: rgb(107 114 128 / 0.85);
+}
+
+.text-gray-500\/90 {
+ color: rgb(107 114 128 / 0.9);
+}
+
+.text-gray-500\/95 {
+ color: rgb(107 114 128 / 0.95);
+}
+
+.text-gray-600 {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-600\/0 {
+ color: rgb(75 85 99 / 0);
+}
+
+.text-gray-600\/10 {
+ color: rgb(75 85 99 / 0.1);
+}
+
+.text-gray-600\/100 {
+ color: rgb(75 85 99 / 1);
+}
+
+.text-gray-600\/15 {
+ color: rgb(75 85 99 / 0.15);
+}
+
+.text-gray-600\/20 {
+ color: rgb(75 85 99 / 0.2);
+}
+
+.text-gray-600\/25 {
+ color: rgb(75 85 99 / 0.25);
+}
+
+.text-gray-600\/30 {
+ color: rgb(75 85 99 / 0.3);
+}
+
+.text-gray-600\/35 {
+ color: rgb(75 85 99 / 0.35);
+}
+
+.text-gray-600\/40 {
+ color: rgb(75 85 99 / 0.4);
+}
+
+.text-gray-600\/45 {
+ color: rgb(75 85 99 / 0.45);
+}
+
+.text-gray-600\/5 {
+ color: rgb(75 85 99 / 0.05);
+}
+
+.text-gray-600\/50 {
+ color: rgb(75 85 99 / 0.5);
+}
+
+.text-gray-600\/55 {
+ color: rgb(75 85 99 / 0.55);
+}
+
+.text-gray-600\/60 {
+ color: rgb(75 85 99 / 0.6);
+}
+
+.text-gray-600\/65 {
+ color: rgb(75 85 99 / 0.65);
+}
+
+.text-gray-600\/70 {
+ color: rgb(75 85 99 / 0.7);
+}
+
+.text-gray-600\/75 {
+ color: rgb(75 85 99 / 0.75);
+}
+
+.text-gray-600\/80 {
+ color: rgb(75 85 99 / 0.8);
+}
+
+.text-gray-600\/85 {
+ color: rgb(75 85 99 / 0.85);
+}
+
+.text-gray-600\/90 {
+ color: rgb(75 85 99 / 0.9);
+}
+
+.text-gray-600\/95 {
+ color: rgb(75 85 99 / 0.95);
+}
+
+.text-gray-700 {
+ --tw-text-opacity: 1;
+ color: rgb(55 65 81 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-700\/0 {
+ color: rgb(55 65 81 / 0);
+}
+
+.text-gray-700\/10 {
+ color: rgb(55 65 81 / 0.1);
+}
+
+.text-gray-700\/100 {
+ color: rgb(55 65 81 / 1);
+}
+
+.text-gray-700\/15 {
+ color: rgb(55 65 81 / 0.15);
+}
+
+.text-gray-700\/20 {
+ color: rgb(55 65 81 / 0.2);
+}
+
+.text-gray-700\/25 {
+ color: rgb(55 65 81 / 0.25);
+}
+
+.text-gray-700\/30 {
+ color: rgb(55 65 81 / 0.3);
+}
+
+.text-gray-700\/35 {
+ color: rgb(55 65 81 / 0.35);
+}
+
+.text-gray-700\/40 {
+ color: rgb(55 65 81 / 0.4);
+}
+
+.text-gray-700\/45 {
+ color: rgb(55 65 81 / 0.45);
+}
+
+.text-gray-700\/5 {
+ color: rgb(55 65 81 / 0.05);
+}
+
+.text-gray-700\/50 {
+ color: rgb(55 65 81 / 0.5);
+}
+
+.text-gray-700\/55 {
+ color: rgb(55 65 81 / 0.55);
+}
+
+.text-gray-700\/60 {
+ color: rgb(55 65 81 / 0.6);
+}
+
+.text-gray-700\/65 {
+ color: rgb(55 65 81 / 0.65);
+}
+
+.text-gray-700\/70 {
+ color: rgb(55 65 81 / 0.7);
+}
+
+.text-gray-700\/75 {
+ color: rgb(55 65 81 / 0.75);
+}
+
+.text-gray-700\/80 {
+ color: rgb(55 65 81 / 0.8);
+}
+
+.text-gray-700\/85 {
+ color: rgb(55 65 81 / 0.85);
+}
+
+.text-gray-700\/90 {
+ color: rgb(55 65 81 / 0.9);
+}
+
+.text-gray-700\/95 {
+ color: rgb(55 65 81 / 0.95);
+}
+
+.text-gray-800 {
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-900 {
+ --tw-text-opacity: 1;
+ color: rgb(36 37 39 / var(--tw-text-opacity, 1));
+}
+
+.text-green-400 {
+ --tw-text-opacity: 1;
+ color: rgb(74 222 128 / var(--tw-text-opacity, 1));
+}
+
+.text-green-500 {
+ --tw-text-opacity: 1;
+ color: rgb(0 146 71 / var(--tw-text-opacity, 1));
+}
+
+.text-green-500\/0 {
+ color: rgb(0 146 71 / 0);
+}
+
+.text-green-500\/10 {
+ color: rgb(0 146 71 / 0.1);
+}
+
+.text-green-500\/100 {
+ color: rgb(0 146 71 / 1);
+}
+
+.text-green-500\/15 {
+ color: rgb(0 146 71 / 0.15);
+}
+
+.text-green-500\/20 {
+ color: rgb(0 146 71 / 0.2);
+}
+
+.text-green-500\/25 {
+ color: rgb(0 146 71 / 0.25);
+}
+
+.text-green-500\/30 {
+ color: rgb(0 146 71 / 0.3);
+}
+
+.text-green-500\/35 {
+ color: rgb(0 146 71 / 0.35);
+}
+
+.text-green-500\/40 {
+ color: rgb(0 146 71 / 0.4);
+}
+
+.text-green-500\/45 {
+ color: rgb(0 146 71 / 0.45);
+}
+
+.text-green-500\/5 {
+ color: rgb(0 146 71 / 0.05);
+}
+
+.text-green-500\/50 {
+ color: rgb(0 146 71 / 0.5);
+}
+
+.text-green-500\/55 {
+ color: rgb(0 146 71 / 0.55);
+}
+
+.text-green-500\/60 {
+ color: rgb(0 146 71 / 0.6);
+}
+
+.text-green-500\/65 {
+ color: rgb(0 146 71 / 0.65);
+}
+
+.text-green-500\/70 {
+ color: rgb(0 146 71 / 0.7);
+}
+
+.text-green-500\/75 {
+ color: rgb(0 146 71 / 0.75);
+}
+
+.text-green-500\/80 {
+ color: rgb(0 146 71 / 0.8);
+}
+
+.text-green-500\/85 {
+ color: rgb(0 146 71 / 0.85);
+}
+
+.text-green-500\/90 {
+ color: rgb(0 146 71 / 0.9);
+}
+
+.text-green-500\/95 {
+ color: rgb(0 146 71 / 0.95);
+}
+
+.text-green-600 {
+ --tw-text-opacity: 1;
+ color: rgb(22 163 74 / var(--tw-text-opacity, 1));
+}
+
+.text-green-600\/0 {
+ color: rgb(22 163 74 / 0);
+}
+
+.text-green-600\/10 {
+ color: rgb(22 163 74 / 0.1);
+}
+
+.text-green-600\/100 {
+ color: rgb(22 163 74 / 1);
+}
+
+.text-green-600\/15 {
+ color: rgb(22 163 74 / 0.15);
+}
+
+.text-green-600\/20 {
+ color: rgb(22 163 74 / 0.2);
+}
+
+.text-green-600\/25 {
+ color: rgb(22 163 74 / 0.25);
+}
+
+.text-green-600\/30 {
+ color: rgb(22 163 74 / 0.3);
+}
+
+.text-green-600\/35 {
+ color: rgb(22 163 74 / 0.35);
+}
+
+.text-green-600\/40 {
+ color: rgb(22 163 74 / 0.4);
+}
+
+.text-green-600\/45 {
+ color: rgb(22 163 74 / 0.45);
+}
+
+.text-green-600\/5 {
+ color: rgb(22 163 74 / 0.05);
+}
+
+.text-green-600\/50 {
+ color: rgb(22 163 74 / 0.5);
+}
+
+.text-green-600\/55 {
+ color: rgb(22 163 74 / 0.55);
+}
+
+.text-green-600\/60 {
+ color: rgb(22 163 74 / 0.6);
+}
+
+.text-green-600\/65 {
+ color: rgb(22 163 74 / 0.65);
+}
+
+.text-green-600\/70 {
+ color: rgb(22 163 74 / 0.7);
+}
+
+.text-green-600\/75 {
+ color: rgb(22 163 74 / 0.75);
+}
+
+.text-green-600\/80 {
+ color: rgb(22 163 74 / 0.8);
+}
+
+.text-green-600\/85 {
+ color: rgb(22 163 74 / 0.85);
+}
+
+.text-green-600\/90 {
+ color: rgb(22 163 74 / 0.9);
+}
+
+.text-green-600\/95 {
+ color: rgb(22 163 74 / 0.95);
+}
+
+.text-green-700 {
+ --tw-text-opacity: 1;
+ color: rgb(21 128 61 / var(--tw-text-opacity, 1));
+}
+
+.text-green-700\/0 {
+ color: rgb(21 128 61 / 0);
+}
+
+.text-green-700\/10 {
+ color: rgb(21 128 61 / 0.1);
+}
+
+.text-green-700\/100 {
+ color: rgb(21 128 61 / 1);
+}
+
+.text-green-700\/15 {
+ color: rgb(21 128 61 / 0.15);
+}
+
+.text-green-700\/20 {
+ color: rgb(21 128 61 / 0.2);
+}
+
+.text-green-700\/25 {
+ color: rgb(21 128 61 / 0.25);
+}
+
+.text-green-700\/30 {
+ color: rgb(21 128 61 / 0.3);
+}
+
+.text-green-700\/35 {
+ color: rgb(21 128 61 / 0.35);
+}
+
+.text-green-700\/40 {
+ color: rgb(21 128 61 / 0.4);
+}
+
+.text-green-700\/45 {
+ color: rgb(21 128 61 / 0.45);
+}
+
+.text-green-700\/5 {
+ color: rgb(21 128 61 / 0.05);
+}
+
+.text-green-700\/50 {
+ color: rgb(21 128 61 / 0.5);
+}
+
+.text-green-700\/55 {
+ color: rgb(21 128 61 / 0.55);
+}
+
+.text-green-700\/60 {
+ color: rgb(21 128 61 / 0.6);
+}
+
+.text-green-700\/65 {
+ color: rgb(21 128 61 / 0.65);
+}
+
+.text-green-700\/70 {
+ color: rgb(21 128 61 / 0.7);
+}
+
+.text-green-700\/75 {
+ color: rgb(21 128 61 / 0.75);
+}
+
+.text-green-700\/80 {
+ color: rgb(21 128 61 / 0.8);
+}
+
+.text-green-700\/85 {
+ color: rgb(21 128 61 / 0.85);
+}
+
+.text-green-700\/90 {
+ color: rgb(21 128 61 / 0.9);
+}
+
+.text-green-700\/95 {
+ color: rgb(21 128 61 / 0.95);
+}
+
+.text-green-800 {
+ --tw-text-opacity: 1;
+ color: rgb(22 101 52 / var(--tw-text-opacity, 1));
+}
+
+.text-green-900 {
+ --tw-text-opacity: 1;
+ color: rgb(20 83 45 / var(--tw-text-opacity, 1));
+}
+
+.text-indigo-700 {
+ --tw-text-opacity: 1;
+ color: rgb(67 56 202 / var(--tw-text-opacity, 1));
+}
+
+.text-orange-400 {
+ --tw-text-opacity: 1;
+ color: rgb(251 146 60 / var(--tw-text-opacity, 1));
+}
+
+.text-orange-500 {
+ --tw-text-opacity: 1;
+ color: rgb(249 115 22 / var(--tw-text-opacity, 1));
+}
+
+.text-orange-500\/0 {
+ color: rgb(249 115 22 / 0);
+}
+
+.text-orange-500\/10 {
+ color: rgb(249 115 22 / 0.1);
+}
+
+.text-orange-500\/100 {
+ color: rgb(249 115 22 / 1);
+}
+
+.text-orange-500\/15 {
+ color: rgb(249 115 22 / 0.15);
+}
+
+.text-orange-500\/20 {
+ color: rgb(249 115 22 / 0.2);
+}
+
+.text-orange-500\/25 {
+ color: rgb(249 115 22 / 0.25);
+}
+
+.text-orange-500\/30 {
+ color: rgb(249 115 22 / 0.3);
+}
+
+.text-orange-500\/35 {
+ color: rgb(249 115 22 / 0.35);
+}
+
+.text-orange-500\/40 {
+ color: rgb(249 115 22 / 0.4);
+}
+
+.text-orange-500\/45 {
+ color: rgb(249 115 22 / 0.45);
+}
+
+.text-orange-500\/5 {
+ color: rgb(249 115 22 / 0.05);
+}
+
+.text-orange-500\/50 {
+ color: rgb(249 115 22 / 0.5);
+}
+
+.text-orange-500\/55 {
+ color: rgb(249 115 22 / 0.55);
+}
+
+.text-orange-500\/60 {
+ color: rgb(249 115 22 / 0.6);
+}
+
+.text-orange-500\/65 {
+ color: rgb(249 115 22 / 0.65);
+}
+
+.text-orange-500\/70 {
+ color: rgb(249 115 22 / 0.7);
+}
+
+.text-orange-500\/75 {
+ color: rgb(249 115 22 / 0.75);
+}
+
+.text-orange-500\/80 {
+ color: rgb(249 115 22 / 0.8);
+}
+
+.text-orange-500\/85 {
+ color: rgb(249 115 22 / 0.85);
+}
+
+.text-orange-500\/90 {
+ color: rgb(249 115 22 / 0.9);
+}
+
+.text-orange-500\/95 {
+ color: rgb(249 115 22 / 0.95);
+}
+
+.text-orange-600 {
+ --tw-text-opacity: 1;
+ color: rgb(234 88 12 / var(--tw-text-opacity, 1));
+}
+
+.text-orange-600\/0 {
+ color: rgb(234 88 12 / 0);
+}
+
+.text-orange-600\/10 {
+ color: rgb(234 88 12 / 0.1);
+}
+
+.text-orange-600\/100 {
+ color: rgb(234 88 12 / 1);
+}
+
+.text-orange-600\/15 {
+ color: rgb(234 88 12 / 0.15);
+}
+
+.text-orange-600\/20 {
+ color: rgb(234 88 12 / 0.2);
+}
+
+.text-orange-600\/25 {
+ color: rgb(234 88 12 / 0.25);
+}
+
+.text-orange-600\/30 {
+ color: rgb(234 88 12 / 0.3);
+}
+
+.text-orange-600\/35 {
+ color: rgb(234 88 12 / 0.35);
+}
+
+.text-orange-600\/40 {
+ color: rgb(234 88 12 / 0.4);
+}
+
+.text-orange-600\/45 {
+ color: rgb(234 88 12 / 0.45);
+}
+
+.text-orange-600\/5 {
+ color: rgb(234 88 12 / 0.05);
+}
+
+.text-orange-600\/50 {
+ color: rgb(234 88 12 / 0.5);
+}
+
+.text-orange-600\/55 {
+ color: rgb(234 88 12 / 0.55);
+}
+
+.text-orange-600\/60 {
+ color: rgb(234 88 12 / 0.6);
+}
+
+.text-orange-600\/65 {
+ color: rgb(234 88 12 / 0.65);
+}
+
+.text-orange-600\/70 {
+ color: rgb(234 88 12 / 0.7);
+}
+
+.text-orange-600\/75 {
+ color: rgb(234 88 12 / 0.75);
+}
+
+.text-orange-600\/80 {
+ color: rgb(234 88 12 / 0.8);
+}
+
+.text-orange-600\/85 {
+ color: rgb(234 88 12 / 0.85);
+}
+
+.text-orange-600\/90 {
+ color: rgb(234 88 12 / 0.9);
+}
+
+.text-orange-600\/95 {
+ color: rgb(234 88 12 / 0.95);
+}
+
+.text-orange-700 {
+ --tw-text-opacity: 1;
+ color: rgb(194 65 12 / var(--tw-text-opacity, 1));
+}
+
+.text-orange-700\/0 {
+ color: rgb(194 65 12 / 0);
+}
+
+.text-orange-700\/10 {
+ color: rgb(194 65 12 / 0.1);
+}
+
+.text-orange-700\/100 {
+ color: rgb(194 65 12 / 1);
+}
+
+.text-orange-700\/15 {
+ color: rgb(194 65 12 / 0.15);
+}
+
+.text-orange-700\/20 {
+ color: rgb(194 65 12 / 0.2);
+}
+
+.text-orange-700\/25 {
+ color: rgb(194 65 12 / 0.25);
+}
+
+.text-orange-700\/30 {
+ color: rgb(194 65 12 / 0.3);
+}
+
+.text-orange-700\/35 {
+ color: rgb(194 65 12 / 0.35);
+}
+
+.text-orange-700\/40 {
+ color: rgb(194 65 12 / 0.4);
+}
+
+.text-orange-700\/45 {
+ color: rgb(194 65 12 / 0.45);
+}
+
+.text-orange-700\/5 {
+ color: rgb(194 65 12 / 0.05);
+}
+
+.text-orange-700\/50 {
+ color: rgb(194 65 12 / 0.5);
+}
+
+.text-orange-700\/55 {
+ color: rgb(194 65 12 / 0.55);
+}
+
+.text-orange-700\/60 {
+ color: rgb(194 65 12 / 0.6);
+}
+
+.text-orange-700\/65 {
+ color: rgb(194 65 12 / 0.65);
+}
+
+.text-orange-700\/70 {
+ color: rgb(194 65 12 / 0.7);
+}
+
+.text-orange-700\/75 {
+ color: rgb(194 65 12 / 0.75);
+}
+
+.text-orange-700\/80 {
+ color: rgb(194 65 12 / 0.8);
+}
+
+.text-orange-700\/85 {
+ color: rgb(194 65 12 / 0.85);
+}
+
+.text-orange-700\/90 {
+ color: rgb(194 65 12 / 0.9);
+}
+
+.text-orange-700\/95 {
+ color: rgb(194 65 12 / 0.95);
+}
+
+.text-pink-400 {
+ --tw-text-opacity: 1;
+ color: rgb(244 114 182 / var(--tw-text-opacity, 1));
+}
+
+.text-pink-600 {
+ --tw-text-opacity: 1;
+ color: rgb(219 39 119 / var(--tw-text-opacity, 1));
+}
+
+.text-purple-400 {
+ --tw-text-opacity: 1;
+ color: rgb(192 132 252 / var(--tw-text-opacity, 1));
+}
+
+.text-purple-500 {
+ --tw-text-opacity: 1;
+ color: rgb(168 85 247 / var(--tw-text-opacity, 1));
+}
+
+.text-purple-500\/0 {
+ color: rgb(168 85 247 / 0);
+}
+
+.text-purple-500\/10 {
+ color: rgb(168 85 247 / 0.1);
+}
+
+.text-purple-500\/100 {
+ color: rgb(168 85 247 / 1);
+}
+
+.text-purple-500\/15 {
+ color: rgb(168 85 247 / 0.15);
+}
+
+.text-purple-500\/20 {
+ color: rgb(168 85 247 / 0.2);
+}
+
+.text-purple-500\/25 {
+ color: rgb(168 85 247 / 0.25);
+}
+
+.text-purple-500\/30 {
+ color: rgb(168 85 247 / 0.3);
+}
+
+.text-purple-500\/35 {
+ color: rgb(168 85 247 / 0.35);
+}
+
+.text-purple-500\/40 {
+ color: rgb(168 85 247 / 0.4);
+}
+
+.text-purple-500\/45 {
+ color: rgb(168 85 247 / 0.45);
+}
+
+.text-purple-500\/5 {
+ color: rgb(168 85 247 / 0.05);
+}
+
+.text-purple-500\/50 {
+ color: rgb(168 85 247 / 0.5);
+}
+
+.text-purple-500\/55 {
+ color: rgb(168 85 247 / 0.55);
+}
+
+.text-purple-500\/60 {
+ color: rgb(168 85 247 / 0.6);
+}
+
+.text-purple-500\/65 {
+ color: rgb(168 85 247 / 0.65);
+}
+
+.text-purple-500\/70 {
+ color: rgb(168 85 247 / 0.7);
+}
+
+.text-purple-500\/75 {
+ color: rgb(168 85 247 / 0.75);
+}
+
+.text-purple-500\/80 {
+ color: rgb(168 85 247 / 0.8);
+}
+
+.text-purple-500\/85 {
+ color: rgb(168 85 247 / 0.85);
+}
+
+.text-purple-500\/90 {
+ color: rgb(168 85 247 / 0.9);
+}
+
+.text-purple-500\/95 {
+ color: rgb(168 85 247 / 0.95);
+}
+
+.text-purple-600 {
+ --tw-text-opacity: 1;
+ color: rgb(147 51 234 / var(--tw-text-opacity, 1));
+}
+
+.text-purple-600\/0 {
+ color: rgb(147 51 234 / 0);
+}
+
+.text-purple-600\/10 {
+ color: rgb(147 51 234 / 0.1);
+}
+
+.text-purple-600\/100 {
+ color: rgb(147 51 234 / 1);
+}
+
+.text-purple-600\/15 {
+ color: rgb(147 51 234 / 0.15);
+}
+
+.text-purple-600\/20 {
+ color: rgb(147 51 234 / 0.2);
+}
+
+.text-purple-600\/25 {
+ color: rgb(147 51 234 / 0.25);
+}
+
+.text-purple-600\/30 {
+ color: rgb(147 51 234 / 0.3);
+}
+
+.text-purple-600\/35 {
+ color: rgb(147 51 234 / 0.35);
+}
+
+.text-purple-600\/40 {
+ color: rgb(147 51 234 / 0.4);
+}
+
+.text-purple-600\/45 {
+ color: rgb(147 51 234 / 0.45);
+}
+
+.text-purple-600\/5 {
+ color: rgb(147 51 234 / 0.05);
+}
+
+.text-purple-600\/50 {
+ color: rgb(147 51 234 / 0.5);
+}
+
+.text-purple-600\/55 {
+ color: rgb(147 51 234 / 0.55);
+}
+
+.text-purple-600\/60 {
+ color: rgb(147 51 234 / 0.6);
+}
+
+.text-purple-600\/65 {
+ color: rgb(147 51 234 / 0.65);
+}
+
+.text-purple-600\/70 {
+ color: rgb(147 51 234 / 0.7);
+}
+
+.text-purple-600\/75 {
+ color: rgb(147 51 234 / 0.75);
+}
+
+.text-purple-600\/80 {
+ color: rgb(147 51 234 / 0.8);
+}
+
+.text-purple-600\/85 {
+ color: rgb(147 51 234 / 0.85);
+}
+
+.text-purple-600\/90 {
+ color: rgb(147 51 234 / 0.9);
+}
+
+.text-purple-600\/95 {
+ color: rgb(147 51 234 / 0.95);
+}
+
+.text-purple-700 {
+ --tw-text-opacity: 1;
+ color: rgb(126 34 206 / var(--tw-text-opacity, 1));
+}
+
+.text-purple-700\/0 {
+ color: rgb(126 34 206 / 0);
+}
+
+.text-purple-700\/10 {
+ color: rgb(126 34 206 / 0.1);
+}
+
+.text-purple-700\/100 {
+ color: rgb(126 34 206 / 1);
+}
+
+.text-purple-700\/15 {
+ color: rgb(126 34 206 / 0.15);
+}
+
+.text-purple-700\/20 {
+ color: rgb(126 34 206 / 0.2);
+}
+
+.text-purple-700\/25 {
+ color: rgb(126 34 206 / 0.25);
+}
+
+.text-purple-700\/30 {
+ color: rgb(126 34 206 / 0.3);
+}
+
+.text-purple-700\/35 {
+ color: rgb(126 34 206 / 0.35);
+}
+
+.text-purple-700\/40 {
+ color: rgb(126 34 206 / 0.4);
+}
+
+.text-purple-700\/45 {
+ color: rgb(126 34 206 / 0.45);
+}
+
+.text-purple-700\/5 {
+ color: rgb(126 34 206 / 0.05);
+}
+
+.text-purple-700\/50 {
+ color: rgb(126 34 206 / 0.5);
+}
+
+.text-purple-700\/55 {
+ color: rgb(126 34 206 / 0.55);
+}
+
+.text-purple-700\/60 {
+ color: rgb(126 34 206 / 0.6);
+}
+
+.text-purple-700\/65 {
+ color: rgb(126 34 206 / 0.65);
+}
+
+.text-purple-700\/70 {
+ color: rgb(126 34 206 / 0.7);
+}
+
+.text-purple-700\/75 {
+ color: rgb(126 34 206 / 0.75);
+}
+
+.text-purple-700\/80 {
+ color: rgb(126 34 206 / 0.8);
+}
+
+.text-purple-700\/85 {
+ color: rgb(126 34 206 / 0.85);
+}
+
+.text-purple-700\/90 {
+ color: rgb(126 34 206 / 0.9);
+}
+
+.text-purple-700\/95 {
+ color: rgb(126 34 206 / 0.95);
+}
+
+.text-purple-800 {
+ --tw-text-opacity: 1;
+ color: rgb(107 33 168 / var(--tw-text-opacity, 1));
+}
+
+.text-red-300 {
+ --tw-text-opacity: 1;
+ color: rgb(252 165 165 / var(--tw-text-opacity, 1));
+}
+
+.text-red-400 {
+ --tw-text-opacity: 1;
+ color: rgb(248 113 113 / var(--tw-text-opacity, 1));
+}
+
+.text-red-500 {
+ --tw-text-opacity: 1;
+ color: rgb(239 68 68 / var(--tw-text-opacity, 1));
+}
+
+.text-red-500\/0 {
+ color: rgb(239 68 68 / 0);
+}
+
+.text-red-500\/10 {
+ color: rgb(239 68 68 / 0.1);
+}
+
+.text-red-500\/100 {
+ color: rgb(239 68 68 / 1);
+}
+
+.text-red-500\/15 {
+ color: rgb(239 68 68 / 0.15);
+}
+
+.text-red-500\/20 {
+ color: rgb(239 68 68 / 0.2);
+}
+
+.text-red-500\/25 {
+ color: rgb(239 68 68 / 0.25);
+}
+
+.text-red-500\/30 {
+ color: rgb(239 68 68 / 0.3);
+}
+
+.text-red-500\/35 {
+ color: rgb(239 68 68 / 0.35);
+}
+
+.text-red-500\/40 {
+ color: rgb(239 68 68 / 0.4);
+}
+
+.text-red-500\/45 {
+ color: rgb(239 68 68 / 0.45);
+}
+
+.text-red-500\/5 {
+ color: rgb(239 68 68 / 0.05);
+}
+
+.text-red-500\/50 {
+ color: rgb(239 68 68 / 0.5);
+}
+
+.text-red-500\/55 {
+ color: rgb(239 68 68 / 0.55);
+}
+
+.text-red-500\/60 {
+ color: rgb(239 68 68 / 0.6);
+}
+
+.text-red-500\/65 {
+ color: rgb(239 68 68 / 0.65);
+}
+
+.text-red-500\/70 {
+ color: rgb(239 68 68 / 0.7);
+}
+
+.text-red-500\/75 {
+ color: rgb(239 68 68 / 0.75);
+}
+
+.text-red-500\/80 {
+ color: rgb(239 68 68 / 0.8);
+}
+
+.text-red-500\/85 {
+ color: rgb(239 68 68 / 0.85);
+}
+
+.text-red-500\/90 {
+ color: rgb(239 68 68 / 0.9);
+}
+
+.text-red-500\/95 {
+ color: rgb(239 68 68 / 0.95);
+}
+
+.text-red-600 {
+ --tw-text-opacity: 1;
+ color: rgb(220 38 38 / var(--tw-text-opacity, 1));
+}
+
+.text-red-600\/0 {
+ color: rgb(220 38 38 / 0);
+}
+
+.text-red-600\/10 {
+ color: rgb(220 38 38 / 0.1);
+}
+
+.text-red-600\/100 {
+ color: rgb(220 38 38 / 1);
+}
+
+.text-red-600\/15 {
+ color: rgb(220 38 38 / 0.15);
+}
+
+.text-red-600\/20 {
+ color: rgb(220 38 38 / 0.2);
+}
+
+.text-red-600\/25 {
+ color: rgb(220 38 38 / 0.25);
+}
+
+.text-red-600\/30 {
+ color: rgb(220 38 38 / 0.3);
+}
+
+.text-red-600\/35 {
+ color: rgb(220 38 38 / 0.35);
+}
+
+.text-red-600\/40 {
+ color: rgb(220 38 38 / 0.4);
+}
+
+.text-red-600\/45 {
+ color: rgb(220 38 38 / 0.45);
+}
+
+.text-red-600\/5 {
+ color: rgb(220 38 38 / 0.05);
+}
+
+.text-red-600\/50 {
+ color: rgb(220 38 38 / 0.5);
+}
+
+.text-red-600\/55 {
+ color: rgb(220 38 38 / 0.55);
+}
+
+.text-red-600\/60 {
+ color: rgb(220 38 38 / 0.6);
+}
+
+.text-red-600\/65 {
+ color: rgb(220 38 38 / 0.65);
+}
+
+.text-red-600\/70 {
+ color: rgb(220 38 38 / 0.7);
+}
+
+.text-red-600\/75 {
+ color: rgb(220 38 38 / 0.75);
+}
+
+.text-red-600\/80 {
+ color: rgb(220 38 38 / 0.8);
+}
+
+.text-red-600\/85 {
+ color: rgb(220 38 38 / 0.85);
+}
+
+.text-red-600\/90 {
+ color: rgb(220 38 38 / 0.9);
+}
+
+.text-red-600\/95 {
+ color: rgb(220 38 38 / 0.95);
+}
+
+.text-red-700 {
+ --tw-text-opacity: 1;
+ color: rgb(185 28 28 / var(--tw-text-opacity, 1));
+}
+
+.text-red-700\/0 {
+ color: rgb(185 28 28 / 0);
+}
+
+.text-red-700\/10 {
+ color: rgb(185 28 28 / 0.1);
+}
+
+.text-red-700\/100 {
+ color: rgb(185 28 28 / 1);
+}
+
+.text-red-700\/15 {
+ color: rgb(185 28 28 / 0.15);
+}
+
+.text-red-700\/20 {
+ color: rgb(185 28 28 / 0.2);
+}
+
+.text-red-700\/25 {
+ color: rgb(185 28 28 / 0.25);
+}
+
+.text-red-700\/30 {
+ color: rgb(185 28 28 / 0.3);
+}
+
+.text-red-700\/35 {
+ color: rgb(185 28 28 / 0.35);
+}
+
+.text-red-700\/40 {
+ color: rgb(185 28 28 / 0.4);
+}
+
+.text-red-700\/45 {
+ color: rgb(185 28 28 / 0.45);
+}
+
+.text-red-700\/5 {
+ color: rgb(185 28 28 / 0.05);
+}
+
+.text-red-700\/50 {
+ color: rgb(185 28 28 / 0.5);
+}
+
+.text-red-700\/55 {
+ color: rgb(185 28 28 / 0.55);
+}
+
+.text-red-700\/60 {
+ color: rgb(185 28 28 / 0.6);
+}
+
+.text-red-700\/65 {
+ color: rgb(185 28 28 / 0.65);
+}
+
+.text-red-700\/70 {
+ color: rgb(185 28 28 / 0.7);
+}
+
+.text-red-700\/75 {
+ color: rgb(185 28 28 / 0.75);
+}
+
+.text-red-700\/80 {
+ color: rgb(185 28 28 / 0.8);
+}
+
+.text-red-700\/85 {
+ color: rgb(185 28 28 / 0.85);
+}
+
+.text-red-700\/90 {
+ color: rgb(185 28 28 / 0.9);
+}
+
+.text-red-700\/95 {
+ color: rgb(185 28 28 / 0.95);
+}
+
+.text-red-800 {
+ --tw-text-opacity: 1;
+ color: rgb(153 27 27 / var(--tw-text-opacity, 1));
+}
+
+.text-red-900 {
+ --tw-text-opacity: 1;
+ color: rgb(127 29 29 / var(--tw-text-opacity, 1));
+}
+
+.text-rose-600 {
+ --tw-text-opacity: 1;
+ color: rgb(225 29 72 / var(--tw-text-opacity, 1));
+}
+
+.text-rose-700 {
+ --tw-text-opacity: 1;
+ color: rgb(190 18 60 / var(--tw-text-opacity, 1));
+}
+
+.text-sky-700 {
+ --tw-text-opacity: 1;
+ color: rgb(3 105 161 / var(--tw-text-opacity, 1));
+}
+
+.text-slate-500 {
+ --tw-text-opacity: 1;
+ color: rgb(100 116 139 / var(--tw-text-opacity, 1));
+}
+
+.text-slate-600 {
+ --tw-text-opacity: 1;
+ color: rgb(71 85 105 / var(--tw-text-opacity, 1));
+}
+
+.text-slate-700 {
+ --tw-text-opacity: 1;
+ color: rgb(51 65 85 / var(--tw-text-opacity, 1));
+}
+
+.text-slate-800 {
+ --tw-text-opacity: 1;
+ color: rgb(30 41 59 / var(--tw-text-opacity, 1));
+}
+
+.text-teal-700 {
+ --tw-text-opacity: 1;
+ color: rgb(15 118 110 / var(--tw-text-opacity, 1));
+}
+
+.text-teal-800 {
+ --tw-text-opacity: 1;
+ color: rgb(17 94 89 / var(--tw-text-opacity, 1));
+}
+
+.text-transparent {
+ color: transparent;
+}
+
+.text-violet-700 {
+ --tw-text-opacity: 1;
+ color: rgb(109 40 217 / var(--tw-text-opacity, 1));
+}
+
+.text-white {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.text-white\/95 {
+ color: rgb(255 255 255 / 0.95);
+}
+
+.text-yellow-500 {
+ --tw-text-opacity: 1;
+ color: rgb(234 179 8 / var(--tw-text-opacity, 1));
+}
+
+.text-yellow-500\/0 {
+ color: rgb(234 179 8 / 0);
+}
+
+.text-yellow-500\/10 {
+ color: rgb(234 179 8 / 0.1);
+}
+
+.text-yellow-500\/100 {
+ color: rgb(234 179 8 / 1);
+}
+
+.text-yellow-500\/15 {
+ color: rgb(234 179 8 / 0.15);
+}
+
+.text-yellow-500\/20 {
+ color: rgb(234 179 8 / 0.2);
+}
+
+.text-yellow-500\/25 {
+ color: rgb(234 179 8 / 0.25);
+}
+
+.text-yellow-500\/30 {
+ color: rgb(234 179 8 / 0.3);
+}
+
+.text-yellow-500\/35 {
+ color: rgb(234 179 8 / 0.35);
+}
+
+.text-yellow-500\/40 {
+ color: rgb(234 179 8 / 0.4);
+}
+
+.text-yellow-500\/45 {
+ color: rgb(234 179 8 / 0.45);
+}
+
+.text-yellow-500\/5 {
+ color: rgb(234 179 8 / 0.05);
+}
+
+.text-yellow-500\/50 {
+ color: rgb(234 179 8 / 0.5);
+}
+
+.text-yellow-500\/55 {
+ color: rgb(234 179 8 / 0.55);
+}
+
+.text-yellow-500\/60 {
+ color: rgb(234 179 8 / 0.6);
+}
+
+.text-yellow-500\/65 {
+ color: rgb(234 179 8 / 0.65);
+}
+
+.text-yellow-500\/70 {
+ color: rgb(234 179 8 / 0.7);
+}
+
+.text-yellow-500\/75 {
+ color: rgb(234 179 8 / 0.75);
+}
+
+.text-yellow-500\/80 {
+ color: rgb(234 179 8 / 0.8);
+}
+
+.text-yellow-500\/85 {
+ color: rgb(234 179 8 / 0.85);
+}
+
+.text-yellow-500\/90 {
+ color: rgb(234 179 8 / 0.9);
+}
+
+.text-yellow-500\/95 {
+ color: rgb(234 179 8 / 0.95);
+}
+
+.text-yellow-600 {
+ --tw-text-opacity: 1;
+ color: rgb(202 138 4 / var(--tw-text-opacity, 1));
+}
+
+.text-yellow-600\/0 {
+ color: rgb(202 138 4 / 0);
+}
+
+.text-yellow-600\/10 {
+ color: rgb(202 138 4 / 0.1);
+}
+
+.text-yellow-600\/100 {
+ color: rgb(202 138 4 / 1);
+}
+
+.text-yellow-600\/15 {
+ color: rgb(202 138 4 / 0.15);
+}
+
+.text-yellow-600\/20 {
+ color: rgb(202 138 4 / 0.2);
+}
+
+.text-yellow-600\/25 {
+ color: rgb(202 138 4 / 0.25);
+}
+
+.text-yellow-600\/30 {
+ color: rgb(202 138 4 / 0.3);
+}
+
+.text-yellow-600\/35 {
+ color: rgb(202 138 4 / 0.35);
+}
+
+.text-yellow-600\/40 {
+ color: rgb(202 138 4 / 0.4);
+}
+
+.text-yellow-600\/45 {
+ color: rgb(202 138 4 / 0.45);
+}
+
+.text-yellow-600\/5 {
+ color: rgb(202 138 4 / 0.05);
+}
+
+.text-yellow-600\/50 {
+ color: rgb(202 138 4 / 0.5);
+}
+
+.text-yellow-600\/55 {
+ color: rgb(202 138 4 / 0.55);
+}
+
+.text-yellow-600\/60 {
+ color: rgb(202 138 4 / 0.6);
+}
+
+.text-yellow-600\/65 {
+ color: rgb(202 138 4 / 0.65);
+}
+
+.text-yellow-600\/70 {
+ color: rgb(202 138 4 / 0.7);
+}
+
+.text-yellow-600\/75 {
+ color: rgb(202 138 4 / 0.75);
+}
+
+.text-yellow-600\/80 {
+ color: rgb(202 138 4 / 0.8);
+}
+
+.text-yellow-600\/85 {
+ color: rgb(202 138 4 / 0.85);
+}
+
+.text-yellow-600\/90 {
+ color: rgb(202 138 4 / 0.9);
+}
+
+.text-yellow-600\/95 {
+ color: rgb(202 138 4 / 0.95);
+}
+
+.text-yellow-700 {
+ --tw-text-opacity: 1;
+ color: rgb(161 98 7 / var(--tw-text-opacity, 1));
+}
+
+.text-yellow-700\/0 {
+ color: rgb(161 98 7 / 0);
+}
+
+.text-yellow-700\/10 {
+ color: rgb(161 98 7 / 0.1);
+}
+
+.text-yellow-700\/100 {
+ color: rgb(161 98 7 / 1);
+}
+
+.text-yellow-700\/15 {
+ color: rgb(161 98 7 / 0.15);
+}
+
+.text-yellow-700\/20 {
+ color: rgb(161 98 7 / 0.2);
+}
+
+.text-yellow-700\/25 {
+ color: rgb(161 98 7 / 0.25);
+}
+
+.text-yellow-700\/30 {
+ color: rgb(161 98 7 / 0.3);
+}
+
+.text-yellow-700\/35 {
+ color: rgb(161 98 7 / 0.35);
+}
+
+.text-yellow-700\/40 {
+ color: rgb(161 98 7 / 0.4);
+}
+
+.text-yellow-700\/45 {
+ color: rgb(161 98 7 / 0.45);
+}
+
+.text-yellow-700\/5 {
+ color: rgb(161 98 7 / 0.05);
+}
+
+.text-yellow-700\/50 {
+ color: rgb(161 98 7 / 0.5);
+}
+
+.text-yellow-700\/55 {
+ color: rgb(161 98 7 / 0.55);
+}
+
+.text-yellow-700\/60 {
+ color: rgb(161 98 7 / 0.6);
+}
+
+.text-yellow-700\/65 {
+ color: rgb(161 98 7 / 0.65);
+}
+
+.text-yellow-700\/70 {
+ color: rgb(161 98 7 / 0.7);
+}
+
+.text-yellow-700\/75 {
+ color: rgb(161 98 7 / 0.75);
+}
+
+.text-yellow-700\/80 {
+ color: rgb(161 98 7 / 0.8);
+}
+
+.text-yellow-700\/85 {
+ color: rgb(161 98 7 / 0.85);
+}
+
+.text-yellow-700\/90 {
+ color: rgb(161 98 7 / 0.9);
+}
+
+.text-yellow-700\/95 {
+ color: rgb(161 98 7 / 0.95);
+}
+
+.underline-offset-4 {
+ text-underline-offset: 4px;
+}
+
+.antialiased {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.opacity-0 {
+ opacity: 0;
+}
+
+.opacity-100 {
+ opacity: 1;
+}
+
+.opacity-20 {
+ opacity: 0.2;
+}
+
+.opacity-50 {
+ opacity: 0.5;
+}
+
+.opacity-60 {
+ opacity: 0.6;
+}
+
+.opacity-70 {
+ opacity: 0.7;
+}
+
+.opacity-90 {
+ opacity: 0.9;
+}
+
+.mix-blend-multiply {
+ mix-blend-mode: multiply;
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-2xl {
+ --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
+ --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-inner {
+ --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-lg {
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-md {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-xl {
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.outline-none {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.ring {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-2 {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-4 {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-brand-200 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(251 211 165 / var(--tw-ring-opacity, 1));
+}
+
+.ring-green-400 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(74 222 128 / var(--tw-ring-opacity, 1));
+}
+
+.ring-red-200 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity, 1));
+}
+
+.ring-yellow-400 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(250 204 21 / var(--tw-ring-opacity, 1));
+}
+
+.blur {
+ --tw-blur: blur(8px);
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.drop-shadow-2xl {
+ --tw-drop-shadow: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.filter {
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.backdrop-blur-lg {
+ --tw-backdrop-blur: blur(16px);
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
+}
+
+.backdrop-blur-md {
+ --tw-backdrop-blur: blur(12px);
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
+}
+
+.backdrop-blur-sm {
+ --tw-backdrop-blur: blur(4px);
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
+}
+
+.transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-all {
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-colors {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-opacity {
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-shadow {
+ transition-property: box-shadow;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-transform {
+ transition-property: transform;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.duration-1000 {
+ transition-duration: 1000ms;
+}
+
+.duration-150 {
+ transition-duration: 150ms;
+}
+
+.duration-200 {
+ transition-duration: 200ms;
+}
+
+.duration-300 {
+ transition-duration: 300ms;
+}
+
+.duration-500 {
+ transition-duration: 500ms;
+}
+
+.ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.ease-out {
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
+}
+
+.\[-webkit-text-stroke\:2px_black\] {
+ -webkit-text-stroke: 2px black;
+}
+
+.\[-webkit-text-stroke\:2px_white\] {
+ -webkit-text-stroke: 2px white;
+}
+
+.placeholder\:text-gray-400::-moz-placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
+}
+
+.placeholder\:text-gray-400::placeholder {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
+}
+
+.hover\:-translate-y-0\.5:hover {
+ --tw-translate-y: -0.125rem;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:scale-105:hover {
+ --tw-scale-x: 1.05;
+ --tw-scale-y: 1.05;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:scale-110:hover {
+ --tw-scale-x: 1.1;
+ --tw-scale-y: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:scale-125:hover {
+ --tw-scale-x: 1.25;
+ --tw-scale-y: 1.25;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:scale-\[1\.02\]:hover {
+ --tw-scale-x: 1.02;
+ --tw-scale-y: 1.02;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:scale-\[1\.03\]:hover {
+ --tw-scale-x: 1.03;
+ --tw-scale-y: 1.03;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:border-brand-300:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 181 80 / var(--tw-border-opacity, 1));
+}
+
+.hover\:border-brand-400:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(255 139 73 / var(--tw-border-opacity, 1));
+}
+
+.hover\:border-green-300:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(134 239 172 / var(--tw-border-opacity, 1));
+}
+
+.hover\:border-green-400:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(74 222 128 / var(--tw-border-opacity, 1));
+}
+
+.hover\:border-red-300:hover {
+ --tw-border-opacity: 1;
+ border-color: rgb(252 165 165 / var(--tw-border-opacity, 1));
+}
+
+.hover\:bg-\[\#2a2a2a\]:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(42 42 42 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-\[\#F90527\]:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 5 39 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-\[rgb\(230_0_0\/1\)\]:hover {
+ background-color: rgb(230 0 0/1);
+}
+
+.hover\:bg-\[rgb\(235_119_63\/1\)\]:hover {
+ background-color: rgb(235 119 63/1);
+}
+
+.hover\:bg-black\/10:hover {
+ background-color: rgb(0 0 0 / 0.1);
+}
+
+.hover\:bg-black\/20:hover {
+ background-color: rgb(0 0 0 / 0.2);
+}
+
+.hover\:bg-blue-600\/20:hover {
+ background-color: rgb(37 99 235 / 0.2);
+}
+
+.hover\:bg-brand-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(251 211 165 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-emerald-600:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(5 150 105 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-emerald-700:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(4 120 87 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(234 238 245 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-300:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-50:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-600:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-green-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(213 223 80 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-green-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-green-600:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-red-600:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-red-700:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-white:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-white\/10:hover {
+ background-color: rgb(255 255 255 / 0.1);
+}
+
+.hover\:bg-white\/20:hover {
+ background-color: rgb(255 255 255 / 0.2);
+}
+
+.hover\:bg-opacity-50:hover {
+ --tw-bg-opacity: 0.5;
+}
+
+.hover\:from-red-700:hover {
+ --tw-gradient-from: #b91c1c var(--tw-gradient-from-position);
+ --tw-gradient-to: rgb(185 28 28 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+}
+
+.hover\:via-pink-700:hover {
+ --tw-gradient-to: rgb(190 24 93 / 0) var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #be185d var(--tw-gradient-via-position), var(--tw-gradient-to);
+}
+
+.hover\:to-purple-700:hover {
+ --tw-gradient-to: #7e22ce var(--tw-gradient-to-position);
+}
+
+.hover\:text-black\/80:hover {
+ color: rgb(0 0 0 / 0.8);
+}
+
+.hover\:text-blue-600:hover {
+ --tw-text-opacity: 1;
+ color: rgb(37 99 235 / var(--tw-text-opacity, 1));
+}
+
+.hover\:text-gray-300:hover {
+ --tw-text-opacity: 1;
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
+}
+
+.hover\:text-gray-600:hover {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
+}
+
+.hover\:text-gray-800:hover {
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity, 1));
+}
+
+.hover\:underline:hover {
+ text-decoration-line: underline;
+}
+
+.hover\:shadow:hover {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.hover\:shadow-lg:hover {
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.hover\:shadow-xl:hover {
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.focus\:border-emerald-600:focus {
+ --tw-border-opacity: 1;
+ border-color: rgb(5 150 105 / var(--tw-border-opacity, 1));
+}
+
+.focus\:border-teal-600:focus {
+ --tw-border-opacity: 1;
+ border-color: rgb(13 148 136 / var(--tw-border-opacity, 1));
+}
+
+.focus\:border-transparent:focus {
+ border-color: transparent;
+}
+
+.focus\:outline-none:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.focus\:ring-2:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus\:ring-green-300:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(134 239 172 / var(--tw-ring-opacity, 1));
+}
+
+.focus\:ring-green-500:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(0 146 71 / var(--tw-ring-opacity, 1));
+}
+
+.focus\:ring-opacity-50:focus {
+ --tw-ring-opacity: 0.5;
+}
+
+.active\:translate-y-0:active {
+ --tw-translate-y: 0px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.active\:scale-95:active {
+ --tw-scale-x: .95;
+ --tw-scale-y: .95;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.disabled\:cursor-not-allowed:disabled {
+ cursor: not-allowed;
+}
+
+.disabled\:bg-gray-300:disabled {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
+}
+
+.disabled\:text-gray-500:disabled {
+ --tw-text-opacity: 1;
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
+}
+
+.disabled\:opacity-50:disabled {
+ opacity: 0.5;
+}
+
+.group\/conceitos-extra:focus-within .group-focus-within\/conceitos-extra\:opacity-100 {
+ opacity: 1;
+}
+
+.group:hover .group-hover\:scale-110 {
+ --tw-scale-x: 1.1;
+ --tw-scale-y: 1.1;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.group:hover .group-hover\:bg-gray-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
+}
+
+.group:hover .group-hover\:bg-gray-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
+}
+
+.group:hover .group-hover\:bg-gray-700 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
+}
+
+.group:hover .group-hover\:text-green-600 {
+ --tw-text-opacity: 1;
+ color: rgb(22 163 74 / var(--tw-text-opacity, 1));
+}
+
+.group\/conceitos-extra:hover .group-hover\/conceitos-extra\:opacity-100 {
+ opacity: 1;
+}
+
+.group:hover .group-hover\:opacity-100 {
+ opacity: 1;
+}
+
+.peer:checked ~ .peer-checked\:block {
+ display: block;
+}
+
+.peer:checked ~ .peer-checked\:hidden {
+ display: none;
+}
+
+.peer:checked ~ .peer-checked\:max-h-\[1000px\] {
+ max-height: 1000px;
+}
+
+.peer:checked ~ .peer-checked\:pt-6 {
+ padding-top: 1.5rem;
+}
+
+.data-\[prd-ready\=false\]\:opacity-50[data-prd-ready="false"] {
+ opacity: 0.5;
+}
+
+@media (min-width: 640px) {
+ .sm\:mb-0 {
+ margin-bottom: 0px;
+ }
+
+ .sm\:block {
+ display: block;
+ }
+
+ .sm\:flex {
+ display: flex;
+ }
+
+ .sm\:hidden {
+ display: none;
+ }
+
+ .sm\:max-w-3xl {
+ max-width: 48rem;
+ }
+
+ .sm\:grid-cols-2 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .sm\:flex-row {
+ flex-direction: row;
+ }
+
+ .sm\:items-center {
+ align-items: center;
+ }
+
+ .sm\:justify-between {
+ justify-content: space-between;
+ }
+
+ .sm\:px-6 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+
+ .sm\:py-24 {
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+ }
+
+ .sm\:py-32 {
+ padding-top: 8rem;
+ padding-bottom: 8rem;
+ }
+
+ .sm\:text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ .sm\:text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ }
+
+ .sm\:text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+ }
+
+ .sm\:text-\[50px\] {
+ font-size: 50px;
+ }
+}
+
+@media (min-width: 768px) {
+ .md\:inline {
+ display: inline;
+ }
+
+ .md\:flex {
+ display: flex;
+ }
+
+ .md\:hidden {
+ display: none;
+ }
+
+ .md\:h-20 {
+ height: 5rem;
+ }
+
+ .md\:h-\[500px\] {
+ height: 500px;
+ }
+
+ .md\:w-20 {
+ width: 5rem;
+ }
+
+ .md\:max-w-\[378\.67px\] {
+ max-width: 378.67px;
+ }
+
+ .md\:max-w-\[400px\] {
+ max-width: 400px;
+ }
+
+ .md\:grid-cols-2 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .md\:grid-cols-3 {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .md\:flex-row {
+ flex-direction: row;
+ }
+
+ .md\:flex-row-reverse {
+ flex-direction: row-reverse;
+ }
+
+ .md\:justify-end {
+ justify-content: flex-end;
+ }
+
+ .md\:gap-12 {
+ gap: 3rem;
+ }
+
+ .md\:gap-16 {
+ gap: 4rem;
+ }
+
+ .md\:gap-6 {
+ gap: 1.5rem;
+ }
+
+ .md\:gap-7 {
+ gap: 1.75rem;
+ }
+
+ .md\:px-0 {
+ padding-left: 0px;
+ padding-right: 0px;
+ }
+
+ .md\:px-16 {
+ padding-left: 4rem;
+ padding-right: 4rem;
+ }
+
+ .md\:px-20 {
+ padding-left: 5rem;
+ padding-right: 5rem;
+ }
+
+ .md\:px-\[120px\] {
+ padding-left: 120px;
+ padding-right: 120px;
+ }
+
+ .md\:py-20 {
+ padding-top: 5rem;
+ padding-bottom: 5rem;
+ }
+
+ .md\:py-\[106px\] {
+ padding-top: 106px;
+ padding-bottom: 106px;
+ }
+
+ .md\:pb-16 {
+ padding-bottom: 4rem;
+ }
+
+ .md\:pt-12 {
+ padding-top: 3rem;
+ }
+
+ .md\:text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+ }
+
+ .md\:text-\[32px\] {
+ font-size: 32px;
+ }
+
+ .md\:text-\[40px\] {
+ font-size: 40px;
+ }
+
+ .md\:text-\[46px\] {
+ font-size: 46px;
+ }
+
+ .md\:text-\[56px\] {
+ font-size: 56px;
+ }
+
+ .md\:text-\[76px\] {
+ font-size: 76px;
+ }
+
+ .md\:text-\[86px\] {
+ font-size: 86px;
+ }
+
+ .md\:text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ .md\:text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ }
+
+ .md\:text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ }
+}
+
+@media (min-width: 1024px) {
+ .lg\:col-span-4 {
+ grid-column: span 4 / span 4;
+ }
+
+ .lg\:mt-0 {
+ margin-top: 0px;
+ }
+
+ .lg\:mt-32 {
+ margin-top: 8rem;
+ }
+
+ .lg\:mt-\[158px\] {
+ margin-top: 158px;
+ }
+
+ .lg\:mt-\[195px\] {
+ margin-top: 195px;
+ }
+
+ .lg\:mt-\[86px\] {
+ margin-top: 86px;
+ }
+
+ .lg\:block {
+ display: block;
+ }
+
+ .lg\:grid {
+ display: grid;
+ }
+
+ .lg\:hidden {
+ display: none;
+ }
+
+ .lg\:h-12 {
+ height: 3rem;
+ }
+
+ .lg\:h-8 {
+ height: 2rem;
+ }
+
+ .lg\:w-1\/3 {
+ width: 33.333333%;
+ }
+
+ .lg\:w-12 {
+ width: 3rem;
+ }
+
+ .lg\:w-8 {
+ width: 2rem;
+ }
+
+ .lg\:w-\[40\%\] {
+ width: 40%;
+ }
+
+ .lg\:w-\[45\%\] {
+ width: 45%;
+ }
+
+ .lg\:w-\[55\%\] {
+ width: 55%;
+ }
+
+ .lg\:w-\[60\%\] {
+ width: 60%;
+ }
+
+ .lg\:w-\[90\%\] {
+ width: 90%;
+ }
+
+ .lg\:w-auto {
+ width: auto;
+ }
+
+ .lg\:max-w-none {
+ max-width: none;
+ }
+
+ .lg\:grid-cols-2 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .lg\:grid-cols-3 {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .lg\:grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+
+ .lg\:flex-row {
+ flex-direction: row;
+ }
+
+ .lg\:items-center {
+ align-items: center;
+ }
+
+ .lg\:gap-1 {
+ gap: 0.25rem;
+ }
+
+ .lg\:gap-8 {
+ gap: 2rem;
+ }
+
+ .lg\:px-10 {
+ padding-left: 2.5rem;
+ padding-right: 2.5rem;
+ }
+
+ .lg\:px-12 {
+ padding-left: 3rem;
+ padding-right: 3rem;
+ }
+
+ .lg\:px-3 {
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ }
+
+ .lg\:px-5 {
+ padding-left: 1.25rem;
+ padding-right: 1.25rem;
+ }
+
+ .lg\:px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+
+ .lg\:px-9 {
+ padding-left: 2.25rem;
+ padding-right: 2.25rem;
+ }
+
+ .lg\:px-\[120px\] {
+ padding-left: 120px;
+ padding-right: 120px;
+ }
+
+ .lg\:py-1\.5 {
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+ }
+
+ .lg\:py-12 {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ }
+
+ .lg\:py-16 {
+ padding-top: 4rem;
+ padding-bottom: 4rem;
+ }
+
+ .lg\:py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ }
+
+ .lg\:py-3 {
+ padding-top: 0.75rem;
+ padding-bottom: 0.75rem;
+ }
+
+ .lg\:py-32 {
+ padding-top: 8rem;
+ padding-bottom: 8rem;
+ }
+
+ .lg\:py-40 {
+ padding-top: 10rem;
+ padding-bottom: 10rem;
+ }
+
+ .lg\:py-\[106px\] {
+ padding-top: 106px;
+ padding-bottom: 106px;
+ }
+
+ .lg\:py-\[72px\] {
+ padding-top: 72px;
+ padding-bottom: 72px;
+ }
+
+ .lg\:pr-0 {
+ padding-right: 0px;
+ }
+
+ .lg\:pt-\[72px\] {
+ padding-top: 72px;
+ }
+
+ .lg\:text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ .lg\:text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+ }
+
+ .lg\:text-\[24px\] {
+ font-size: 24px;
+ }
+
+ .lg\:text-\[28px\] {
+ font-size: 28px;
+ }
+
+ .lg\:text-\[70px\] {
+ font-size: 70px;
+ }
+
+ .lg\:text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ .lg\:text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ }
+
+ .lg\:text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+
+ .lg\:text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ }
+}
+
+@media (min-width: 1280px) {
+ .xl\:block {
+ display: block;
+ }
+
+ .xl\:hidden {
+ display: none;
+ }
+
+ .xl\:w-1\/3 {
+ width: 33.333333%;
+ }
+
+ .xl\:w-\[30\%\] {
+ width: 30%;
+ }
+
+ .xl\:w-\[70\%\] {
+ width: 70%;
+ }
+
+ .xl\:grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+
+ .xl\:flex-row {
+ flex-direction: row;
+ }
+
+ .xl\:gap-16 {
+ gap: 4rem;
+ }
+
+ .xl\:self-stretch {
+ align-self: stretch;
+ }
+
+ .xl\:p-\[106px\] {
+ padding: 106px;
+ }
+
+ .xl\:px-\[120px\] {
+ padding-left: 120px;
+ padding-right: 120px;
+ }
+
+ .xl\:py-\[64px\] {
+ padding-top: 64px;
+ padding-bottom: 64px;
+ }
+
+ .xl\:py-\[72px\] {
+ padding-top: 72px;
+ padding-bottom: 72px;
+ }
+
+ .xl\:text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ .xl\:text-\[20px\] {
+ font-size: 20px;
+ }
+
+ .xl\:text-\[32px\] {
+ font-size: 32px;
+ }
+
+ .xl\:text-\[40px\] {
+ font-size: 40px;
+ }
+
+ .xl\:text-\[64px\] {
+ font-size: 64px;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .dark\:border-gray-700 {
+ --tw-border-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
+ }
+
+ .dark\:bg-gray-900 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(36 37 39 / var(--tw-bg-opacity, 1));
+ }
+
+ .dark\:text-brand-500 {
+ --tw-text-opacity: 1;
+ color: rgb(254 0 2 / var(--tw-text-opacity, 1));
+ }
+
+ .dark\:text-gray-500 {
+ --tw-text-opacity: 1;
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
+ }
+
+ .dark\:hover\:text-blue-400:hover {
+ --tw-text-opacity: 1;
+ color: rgb(96 165 250 / var(--tw-text-opacity, 1));
+ }
+
+ .dark\:hover\:text-brand-500:hover {
+ --tw-text-opacity: 1;
+ color: rgb(254 0 2 / var(--tw-text-opacity, 1));
+ }
+}
diff --git a/app/src/atividades/letramento/shared/lucide.js b/app/src/atividades/letramento/shared/lucide.js
new file mode 100644
index 0000000..f209068
--- /dev/null
+++ b/app/src/atividades/letramento/shared/lucide.js
@@ -0,0 +1,18134 @@
+/**
+ * @license lucide v0.546.0 - ISC
+ *
+ * This source code is licensed under the ISC license.
+ * See the LICENSE file in the root directory of this source tree.
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.lucide = {}));
+})(this, (function (exports) { 'use strict';
+
+ const defaultAttributes = {
+ xmlns: "http://www.w3.org/2000/svg",
+ width: 24,
+ height: 24,
+ viewBox: "0 0 24 24",
+ fill: "none",
+ stroke: "currentColor",
+ "stroke-width": 2,
+ "stroke-linecap": "round",
+ "stroke-linejoin": "round"
+ };
+
+ const createSVGElement = ([tag, attrs, children]) => {
+ const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
+ Object.keys(attrs).forEach((name) => {
+ element.setAttribute(name, String(attrs[name]));
+ });
+ if (children?.length) {
+ children.forEach((child) => {
+ const childElement = createSVGElement(child);
+ element.appendChild(childElement);
+ });
+ }
+ return element;
+ };
+ const createElement = (iconNode, customAttrs = {}) => {
+ const tag = "svg";
+ const attrs = {
+ ...defaultAttributes,
+ ...customAttrs
+ };
+ return createSVGElement([tag, attrs, iconNode]);
+ };
+
+ const getAttrs = (element) => Array.from(element.attributes).reduce((attrs, attr) => {
+ attrs[attr.name] = attr.value;
+ return attrs;
+ }, {});
+ const getClassNames = (attrs) => {
+ if (typeof attrs === "string") return attrs;
+ if (!attrs || !attrs.class) return "";
+ if (attrs.class && typeof attrs.class === "string") {
+ return attrs.class.split(" ");
+ }
+ if (attrs.class && Array.isArray(attrs.class)) {
+ return attrs.class;
+ }
+ return "";
+ };
+ const combineClassNames = (arrayOfClassnames) => {
+ const classNameArray = arrayOfClassnames.flatMap(getClassNames);
+ return classNameArray.map((classItem) => classItem.trim()).filter(Boolean).filter((value, index, self) => self.indexOf(value) === index).join(" ");
+ };
+ const toPascalCase = (string) => string.replace(/(\w)(\w*)(_|-|\s*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase());
+ const replaceElement = (element, { nameAttr, icons, attrs }) => {
+ const iconName = element.getAttribute(nameAttr);
+ if (iconName == null) return;
+ const ComponentName = toPascalCase(iconName);
+ const iconNode = icons[ComponentName];
+ if (!iconNode) {
+ return console.warn(
+ `${element.outerHTML} icon name was not found in the provided icons object.`
+ );
+ }
+ const elementAttrs = getAttrs(element);
+ const iconAttrs = {
+ ...defaultAttributes,
+ "data-lucide": iconName,
+ ...attrs,
+ ...elementAttrs
+ };
+ const classNames = combineClassNames(["lucide", `lucide-${iconName}`, elementAttrs, attrs]);
+ if (classNames) {
+ Object.assign(iconAttrs, {
+ class: classNames
+ });
+ }
+ const svgElement = createElement(iconNode, iconAttrs);
+ return element.parentNode?.replaceChild(svgElement, element);
+ };
+
+ const AArrowDown = [
+ ["path", { d: "m14 12 4 4 4-4" }],
+ ["path", { d: "M18 16V7" }],
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" }],
+ ["path", { d: "M3.304 13h6.392" }]
+ ];
+
+ const AArrowUp = [
+ ["path", { d: "m14 11 4-4 4 4" }],
+ ["path", { d: "M18 16V7" }],
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" }],
+ ["path", { d: "M3.304 13h6.392" }]
+ ];
+
+ const ALargeSmall = [
+ ["path", { d: "m15 16 2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16" }],
+ ["path", { d: "M15.697 14h5.606" }],
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" }],
+ ["path", { d: "M3.304 13h6.392" }]
+ ];
+
+ const Accessibility = [
+ ["circle", { cx: "16", cy: "4", r: "1" }],
+ ["path", { d: "m18 19 1-7-6 1" }],
+ ["path", { d: "m5 8 3-3 5.5 3-2.36 3.5" }],
+ ["path", { d: "M4.24 14.5a5 5 0 0 0 6.88 6" }],
+ ["path", { d: "M13.76 17.5a5 5 0 0 0-6.88-6" }]
+ ];
+
+ const Activity = [
+ [
+ "path",
+ {
+ d: "M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"
+ }
+ ]
+ ];
+
+ const AirVent = [
+ ["path", { d: "M18 17.5a2.5 2.5 0 1 1-4 2.03V12" }],
+ ["path", { d: "M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M6 8h12" }],
+ ["path", { d: "M6.6 15.572A2 2 0 1 0 10 17v-5" }]
+ ];
+
+ const Airplay = [
+ ["path", { d: "M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1" }],
+ ["path", { d: "m12 15 5 6H7Z" }]
+ ];
+
+ const AlarmClockCheck = [
+ ["circle", { cx: "12", cy: "13", r: "8" }],
+ ["path", { d: "M5 3 2 6" }],
+ ["path", { d: "m22 6-3-3" }],
+ ["path", { d: "M6.38 18.7 4 21" }],
+ ["path", { d: "M17.64 18.67 20 21" }],
+ ["path", { d: "m9 13 2 2 4-4" }]
+ ];
+
+ const AlarmClockMinus = [
+ ["circle", { cx: "12", cy: "13", r: "8" }],
+ ["path", { d: "M5 3 2 6" }],
+ ["path", { d: "m22 6-3-3" }],
+ ["path", { d: "M6.38 18.7 4 21" }],
+ ["path", { d: "M17.64 18.67 20 21" }],
+ ["path", { d: "M9 13h6" }]
+ ];
+
+ const AlarmClockOff = [
+ ["path", { d: "M6.87 6.87a8 8 0 1 0 11.26 11.26" }],
+ ["path", { d: "M19.9 14.25a8 8 0 0 0-9.15-9.15" }],
+ ["path", { d: "m22 6-3-3" }],
+ ["path", { d: "M6.26 18.67 4 21" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M4 4 2 6" }]
+ ];
+
+ const AlarmClockPlus = [
+ ["circle", { cx: "12", cy: "13", r: "8" }],
+ ["path", { d: "M5 3 2 6" }],
+ ["path", { d: "m22 6-3-3" }],
+ ["path", { d: "M6.38 18.7 4 21" }],
+ ["path", { d: "M17.64 18.67 20 21" }],
+ ["path", { d: "M12 10v6" }],
+ ["path", { d: "M9 13h6" }]
+ ];
+
+ const AlarmClock = [
+ ["circle", { cx: "12", cy: "13", r: "8" }],
+ ["path", { d: "M12 9v4l2 2" }],
+ ["path", { d: "M5 3 2 6" }],
+ ["path", { d: "m22 6-3-3" }],
+ ["path", { d: "M6.38 18.7 4 21" }],
+ ["path", { d: "M17.64 18.67 20 21" }]
+ ];
+
+ const AlarmSmoke = [
+ ["path", { d: "M11 21c0-2.5 2-2.5 2-5" }],
+ ["path", { d: "M16 21c0-2.5 2-2.5 2-5" }],
+ ["path", { d: "m19 8-.8 3a1.25 1.25 0 0 1-1.2 1H7a1.25 1.25 0 0 1-1.2-1L5 8" }],
+ ["path", { d: "M21 3a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4a1 1 0 0 1 1-1z" }],
+ ["path", { d: "M6 21c0-2.5 2-2.5 2-5" }]
+ ];
+
+ const Album = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["polyline", { points: "11 3 11 11 14 8 17 11 17 3" }]
+ ];
+
+ const AlignCenterHorizontal = [
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "M10 16v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-4" }],
+ ["path", { d: "M10 8V4a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M20 16v1a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-1" }],
+ ["path", { d: "M14 8V7c0-1.1.9-2 2-2h2a2 2 0 0 1 2 2v1" }]
+ ];
+
+ const AlignCenterVertical = [
+ ["path", { d: "M12 2v20" }],
+ ["path", { d: "M8 10H4a2 2 0 0 1-2-2V6c0-1.1.9-2 2-2h4" }],
+ ["path", { d: "M16 10h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-4" }],
+ ["path", { d: "M8 20H7a2 2 0 0 1-2-2v-2c0-1.1.9-2 2-2h1" }],
+ ["path", { d: "M16 14h1a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-1" }]
+ ];
+
+ const AlignEndHorizontal = [
+ ["rect", { width: "6", height: "16", x: "4", y: "2", rx: "2" }],
+ ["rect", { width: "6", height: "9", x: "14", y: "9", rx: "2" }],
+ ["path", { d: "M22 22H2" }]
+ ];
+
+ const AlignEndVertical = [
+ ["rect", { width: "16", height: "6", x: "2", y: "4", rx: "2" }],
+ ["rect", { width: "9", height: "6", x: "9", y: "14", rx: "2" }],
+ ["path", { d: "M22 22V2" }]
+ ];
+
+ const AlignHorizontalDistributeCenter = [
+ ["rect", { width: "6", height: "14", x: "4", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "14", y: "7", rx: "2" }],
+ ["path", { d: "M17 22v-5" }],
+ ["path", { d: "M17 7V2" }],
+ ["path", { d: "M7 22v-3" }],
+ ["path", { d: "M7 5V2" }]
+ ];
+
+ const AlignHorizontalDistributeEnd = [
+ ["rect", { width: "6", height: "14", x: "4", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "14", y: "7", rx: "2" }],
+ ["path", { d: "M10 2v20" }],
+ ["path", { d: "M20 2v20" }]
+ ];
+
+ const AlignHorizontalDistributeStart = [
+ ["rect", { width: "6", height: "14", x: "4", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "14", y: "7", rx: "2" }],
+ ["path", { d: "M4 2v20" }],
+ ["path", { d: "M14 2v20" }]
+ ];
+
+ const AlignHorizontalJustifyCenter = [
+ ["rect", { width: "6", height: "14", x: "2", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "16", y: "7", rx: "2" }],
+ ["path", { d: "M12 2v20" }]
+ ];
+
+ const AlignHorizontalJustifyEnd = [
+ ["rect", { width: "6", height: "14", x: "2", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "12", y: "7", rx: "2" }],
+ ["path", { d: "M22 2v20" }]
+ ];
+
+ const AlignHorizontalJustifyStart = [
+ ["rect", { width: "6", height: "14", x: "6", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "16", y: "7", rx: "2" }],
+ ["path", { d: "M2 2v20" }]
+ ];
+
+ const AlignHorizontalSpaceAround = [
+ ["rect", { width: "6", height: "10", x: "9", y: "7", rx: "2" }],
+ ["path", { d: "M4 22V2" }],
+ ["path", { d: "M20 22V2" }]
+ ];
+
+ const AlignHorizontalSpaceBetween = [
+ ["rect", { width: "6", height: "14", x: "3", y: "5", rx: "2" }],
+ ["rect", { width: "6", height: "10", x: "15", y: "7", rx: "2" }],
+ ["path", { d: "M3 2v20" }],
+ ["path", { d: "M21 2v20" }]
+ ];
+
+ const AlignStartHorizontal = [
+ ["rect", { width: "6", height: "16", x: "4", y: "6", rx: "2" }],
+ ["rect", { width: "6", height: "9", x: "14", y: "6", rx: "2" }],
+ ["path", { d: "M22 2H2" }]
+ ];
+
+ const AlignStartVertical = [
+ ["rect", { width: "9", height: "6", x: "6", y: "14", rx: "2" }],
+ ["rect", { width: "16", height: "6", x: "6", y: "4", rx: "2" }],
+ ["path", { d: "M2 2v20" }]
+ ];
+
+ const AlignVerticalDistributeCenter = [
+ ["path", { d: "M22 17h-3" }],
+ ["path", { d: "M22 7h-5" }],
+ ["path", { d: "M5 17H2" }],
+ ["path", { d: "M7 7H2" }],
+ ["rect", { x: "5", y: "14", width: "14", height: "6", rx: "2" }],
+ ["rect", { x: "7", y: "4", width: "10", height: "6", rx: "2" }]
+ ];
+
+ const AlignVerticalDistributeEnd = [
+ ["rect", { width: "14", height: "6", x: "5", y: "14", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "4", rx: "2" }],
+ ["path", { d: "M2 20h20" }],
+ ["path", { d: "M2 10h20" }]
+ ];
+
+ const AlignVerticalDistributeStart = [
+ ["rect", { width: "14", height: "6", x: "5", y: "14", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "4", rx: "2" }],
+ ["path", { d: "M2 14h20" }],
+ ["path", { d: "M2 4h20" }]
+ ];
+
+ const AlignVerticalJustifyCenter = [
+ ["rect", { width: "14", height: "6", x: "5", y: "16", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "2", rx: "2" }],
+ ["path", { d: "M2 12h20" }]
+ ];
+
+ const AlignVerticalJustifyEnd = [
+ ["rect", { width: "14", height: "6", x: "5", y: "12", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "2", rx: "2" }],
+ ["path", { d: "M2 22h20" }]
+ ];
+
+ const AlignVerticalJustifyStart = [
+ ["rect", { width: "14", height: "6", x: "5", y: "16", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "6", rx: "2" }],
+ ["path", { d: "M2 2h20" }]
+ ];
+
+ const AlignVerticalSpaceAround = [
+ ["rect", { width: "10", height: "6", x: "7", y: "9", rx: "2" }],
+ ["path", { d: "M22 20H2" }],
+ ["path", { d: "M22 4H2" }]
+ ];
+
+ const AlignVerticalSpaceBetween = [
+ ["rect", { width: "14", height: "6", x: "5", y: "15", rx: "2" }],
+ ["rect", { width: "10", height: "6", x: "7", y: "3", rx: "2" }],
+ ["path", { d: "M2 21h20" }],
+ ["path", { d: "M2 3h20" }]
+ ];
+
+ const Ambulance = [
+ ["path", { d: "M10 10H6" }],
+ ["path", { d: "M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" }],
+ [
+ "path",
+ {
+ d: "M19 18h2a1 1 0 0 0 1-1v-3.28a1 1 0 0 0-.684-.948l-1.923-.641a1 1 0 0 1-.578-.502l-1.539-3.076A1 1 0 0 0 16.382 8H14"
+ }
+ ],
+ ["path", { d: "M8 8v4" }],
+ ["path", { d: "M9 18h6" }],
+ ["circle", { cx: "17", cy: "18", r: "2" }],
+ ["circle", { cx: "7", cy: "18", r: "2" }]
+ ];
+
+ const Ampersand = [
+ [
+ "path",
+ {
+ d: "M17.5 12c0 4.4-3.6 8-8 8A4.5 4.5 0 0 1 5 15.5c0-6 8-4 8-8.5a3 3 0 1 0-6 0c0 3 2.5 8.5 12 13"
+ }
+ ],
+ ["path", { d: "M16 12h3" }]
+ ];
+
+ const Ampersands = [
+ [
+ "path",
+ { d: "M10 17c-5-3-7-7-7-9a2 2 0 0 1 4 0c0 2.5-5 2.5-5 6 0 1.7 1.3 3 3 3 2.8 0 5-2.2 5-5" }
+ ],
+ [
+ "path",
+ { d: "M22 17c-5-3-7-7-7-9a2 2 0 0 1 4 0c0 2.5-5 2.5-5 6 0 1.7 1.3 3 3 3 2.8 0 5-2.2 5-5" }
+ ]
+ ];
+
+ const Amphora = [
+ ["path", { d: "M10 2v5.632c0 .424-.272.795-.653.982A6 6 0 0 0 6 14c.006 4 3 7 5 8" }],
+ ["path", { d: "M10 5H8a2 2 0 0 0 0 4h.68" }],
+ ["path", { d: "M14 2v5.632c0 .424.272.795.652.982A6 6 0 0 1 18 14c0 4-3 7-5 8" }],
+ ["path", { d: "M14 5h2a2 2 0 0 1 0 4h-.68" }],
+ ["path", { d: "M18 22H6" }],
+ ["path", { d: "M9 2h6" }]
+ ];
+
+ const Anchor = [
+ ["path", { d: "M12 22V8" }],
+ ["path", { d: "M5 12H2a10 10 0 0 0 20 0h-3" }],
+ ["circle", { cx: "12", cy: "5", r: "3" }]
+ ];
+
+ const Angry = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M16 16s-1.5-2-4-2-4 2-4 2" }],
+ ["path", { d: "M7.5 8 10 9" }],
+ ["path", { d: "m14 9 2.5-1" }],
+ ["path", { d: "M9 10h.01" }],
+ ["path", { d: "M15 10h.01" }]
+ ];
+
+ const Annoyed = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M8 15h8" }],
+ ["path", { d: "M8 9h2" }],
+ ["path", { d: "M14 9h2" }]
+ ];
+
+ const Antenna = [
+ ["path", { d: "M2 12 7 2" }],
+ ["path", { d: "m7 12 5-10" }],
+ ["path", { d: "m12 12 5-10" }],
+ ["path", { d: "m17 12 5-10" }],
+ ["path", { d: "M4.5 7h15" }],
+ ["path", { d: "M12 16v6" }]
+ ];
+
+ const Anvil = [
+ ["path", { d: "M7 10H6a4 4 0 0 1-4-4 1 1 0 0 1 1-1h4" }],
+ ["path", { d: "M7 5a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1 7 7 0 0 1-7 7H8a1 1 0 0 1-1-1z" }],
+ ["path", { d: "M9 12v5" }],
+ ["path", { d: "M15 12v5" }],
+ ["path", { d: "M5 20a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3 1 1 0 0 1-1 1H6a1 1 0 0 1-1-1" }]
+ ];
+
+ const Aperture = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m14.31 8 5.74 9.94" }],
+ ["path", { d: "M9.69 8h11.48" }],
+ ["path", { d: "m7.38 12 5.74-9.94" }],
+ ["path", { d: "M9.69 16 3.95 6.06" }],
+ ["path", { d: "M14.31 16H2.83" }],
+ ["path", { d: "m16.62 12-5.74 9.94" }]
+ ];
+
+ const AppWindowMac = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M6 8h.01" }],
+ ["path", { d: "M10 8h.01" }],
+ ["path", { d: "M14 8h.01" }]
+ ];
+
+ const AppWindow = [
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }],
+ ["path", { d: "M10 4v4" }],
+ ["path", { d: "M2 8h20" }],
+ ["path", { d: "M6 4v4" }]
+ ];
+
+ const Apple = [
+ ["path", { d: "M12 6.528V3a1 1 0 0 1 1-1h0" }],
+ [
+ "path",
+ {
+ d: "M18.237 21A15 15 0 0 0 22 11a6 6 0 0 0-10-4.472A6 6 0 0 0 2 11a15.1 15.1 0 0 0 3.763 10 3 3 0 0 0 3.648.648 5.5 5.5 0 0 1 5.178 0A3 3 0 0 0 18.237 21"
+ }
+ ]
+ ];
+
+ const ArchiveRestore = [
+ ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1" }],
+ ["path", { d: "M4 8v11a2 2 0 0 0 2 2h2" }],
+ ["path", { d: "M20 8v11a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "m9 15 3-3 3 3" }],
+ ["path", { d: "M12 12v9" }]
+ ];
+
+ const ArchiveX = [
+ ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1" }],
+ ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" }],
+ ["path", { d: "m9.5 17 5-5" }],
+ ["path", { d: "m9.5 12 5 5" }]
+ ];
+
+ const Archive = [
+ ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1" }],
+ ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" }],
+ ["path", { d: "M10 12h4" }]
+ ];
+
+ const Armchair = [
+ ["path", { d: "M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3" }],
+ [
+ "path",
+ {
+ d: "M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z"
+ }
+ ],
+ ["path", { d: "M5 18v2" }],
+ ["path", { d: "M19 18v2" }]
+ ];
+
+ const ArrowBigDownDash = [
+ [
+ "path",
+ {
+ d: "M15 11a1 1 0 0 0 1 1h2.939a1 1 0 0 1 .75 1.811l-6.835 6.836a1.207 1.207 0 0 1-1.707 0L4.31 13.81a1 1 0 0 1 .75-1.811H8a1 1 0 0 0 1-1V9a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M9 4h6" }]
+ ];
+
+ const ArrowBigDown = [
+ [
+ "path",
+ {
+ d: "M15 11a1 1 0 0 0 1 1h2.939a1 1 0 0 1 .75 1.811l-6.835 6.836a1.207 1.207 0 0 1-1.707 0L4.31 13.81a1 1 0 0 1 .75-1.811H8a1 1 0 0 0 1-1V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1z"
+ }
+ ]
+ ];
+
+ const ArrowBigLeftDash = [
+ [
+ "path",
+ {
+ d: "M13 9a1 1 0 0 1-1-1V5.061a1 1 0 0 0-1.811-.75l-6.835 6.836a1.207 1.207 0 0 0 0 1.707l6.835 6.835a1 1 0 0 0 1.811-.75V16a1 1 0 0 1 1-1h2a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1z"
+ }
+ ],
+ ["path", { d: "M20 9v6" }]
+ ];
+
+ const ArrowBigLeft = [
+ [
+ "path",
+ {
+ d: "M13 9a1 1 0 0 1-1-1V5.061a1 1 0 0 0-1.811-.75l-6.835 6.836a1.207 1.207 0 0 0 0 1.707l6.835 6.835a1 1 0 0 0 1.811-.75V16a1 1 0 0 1 1-1h6a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1z"
+ }
+ ]
+ ];
+
+ const ArrowBigRightDash = [
+ [
+ "path",
+ {
+ d: "M11 9a1 1 0 0 0 1-1V5.061a1 1 0 0 1 1.811-.75l6.836 6.836a1.207 1.207 0 0 1 0 1.707l-6.836 6.835a1 1 0 0 1-1.811-.75V16a1 1 0 0 0-1-1H9a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z"
+ }
+ ],
+ ["path", { d: "M4 9v6" }]
+ ];
+
+ const ArrowBigRight = [
+ [
+ "path",
+ {
+ d: "M11 9a1 1 0 0 0 1-1V5.061a1 1 0 0 1 1.811-.75l6.836 6.836a1.207 1.207 0 0 1 0 1.707l-6.836 6.835a1 1 0 0 1-1.811-.75V16a1 1 0 0 0-1-1H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z"
+ }
+ ]
+ ];
+
+ const ArrowBigUpDash = [
+ [
+ "path",
+ {
+ d: "M9 13a1 1 0 0 0-1-1H5.061a1 1 0 0 1-.75-1.811l6.836-6.835a1.207 1.207 0 0 1 1.707 0l6.835 6.835a1 1 0 0 1-.75 1.811H16a1 1 0 0 0-1 1v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1z"
+ }
+ ],
+ ["path", { d: "M9 20h6" }]
+ ];
+
+ const ArrowBigUp = [
+ [
+ "path",
+ {
+ d: "M9 13a1 1 0 0 0-1-1H5.061a1 1 0 0 1-.75-1.811l6.836-6.835a1.207 1.207 0 0 1 1.707 0l6.835 6.835a1 1 0 0 1-.75 1.811H16a1 1 0 0 0-1 1v6a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1z"
+ }
+ ]
+ ];
+
+ const ArrowDown01 = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["rect", { x: "15", y: "4", width: "4", height: "6", ry: "2" }],
+ ["path", { d: "M17 20v-6h-2" }],
+ ["path", { d: "M15 20h4" }]
+ ];
+
+ const ArrowDown10 = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["path", { d: "M17 10V4h-2" }],
+ ["path", { d: "M15 10h4" }],
+ ["rect", { x: "15", y: "14", width: "4", height: "6", ry: "2" }]
+ ];
+
+ const ArrowDownAZ = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["path", { d: "M20 8h-5" }],
+ ["path", { d: "M15 10V6.5a2.5 2.5 0 0 1 5 0V10" }],
+ ["path", { d: "M15 14h5l-5 6h5" }]
+ ];
+
+ const ArrowDownFromLine = [
+ ["path", { d: "M19 3H5" }],
+ ["path", { d: "M12 21V7" }],
+ ["path", { d: "m6 15 6 6 6-6" }]
+ ];
+
+ const ArrowDownLeft = [
+ ["path", { d: "M17 7 7 17" }],
+ ["path", { d: "M17 17H7V7" }]
+ ];
+
+ const ArrowDownNarrowWide = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["path", { d: "M11 4h4" }],
+ ["path", { d: "M11 8h7" }],
+ ["path", { d: "M11 12h10" }]
+ ];
+
+ const ArrowDownRight = [
+ ["path", { d: "m7 7 10 10" }],
+ ["path", { d: "M17 7v10H7" }]
+ ];
+
+ const ArrowDownToDot = [
+ ["path", { d: "M12 2v14" }],
+ ["path", { d: "m19 9-7 7-7-7" }],
+ ["circle", { cx: "12", cy: "21", r: "1" }]
+ ];
+
+ const ArrowDownToLine = [
+ ["path", { d: "M12 17V3" }],
+ ["path", { d: "m6 11 6 6 6-6" }],
+ ["path", { d: "M19 21H5" }]
+ ];
+
+ const ArrowDownUp = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["path", { d: "m21 8-4-4-4 4" }],
+ ["path", { d: "M17 4v16" }]
+ ];
+
+ const ArrowDownWideNarrow = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 20V4" }],
+ ["path", { d: "M11 4h10" }],
+ ["path", { d: "M11 8h7" }],
+ ["path", { d: "M11 12h4" }]
+ ];
+
+ const ArrowDownZA = [
+ ["path", { d: "m3 16 4 4 4-4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M15 4h5l-5 6h5" }],
+ ["path", { d: "M15 20v-3.5a2.5 2.5 0 0 1 5 0V20" }],
+ ["path", { d: "M20 18h-5" }]
+ ];
+
+ const ArrowDown = [
+ ["path", { d: "M12 5v14" }],
+ ["path", { d: "m19 12-7 7-7-7" }]
+ ];
+
+ const ArrowLeftRight = [
+ ["path", { d: "M8 3 4 7l4 4" }],
+ ["path", { d: "M4 7h16" }],
+ ["path", { d: "m16 21 4-4-4-4" }],
+ ["path", { d: "M20 17H4" }]
+ ];
+
+ const ArrowLeftFromLine = [
+ ["path", { d: "m9 6-6 6 6 6" }],
+ ["path", { d: "M3 12h14" }],
+ ["path", { d: "M21 19V5" }]
+ ];
+
+ const ArrowLeftToLine = [
+ ["path", { d: "M3 19V5" }],
+ ["path", { d: "m13 6-6 6 6 6" }],
+ ["path", { d: "M7 12h14" }]
+ ];
+
+ const ArrowLeft = [
+ ["path", { d: "m12 19-7-7 7-7" }],
+ ["path", { d: "M19 12H5" }]
+ ];
+
+ const ArrowRightFromLine = [
+ ["path", { d: "M3 5v14" }],
+ ["path", { d: "M21 12H7" }],
+ ["path", { d: "m15 18 6-6-6-6" }]
+ ];
+
+ const ArrowRightLeft = [
+ ["path", { d: "m16 3 4 4-4 4" }],
+ ["path", { d: "M20 7H4" }],
+ ["path", { d: "m8 21-4-4 4-4" }],
+ ["path", { d: "M4 17h16" }]
+ ];
+
+ const ArrowRightToLine = [
+ ["path", { d: "M17 12H3" }],
+ ["path", { d: "m11 18 6-6-6-6" }],
+ ["path", { d: "M21 5v14" }]
+ ];
+
+ const ArrowRight = [
+ ["path", { d: "M5 12h14" }],
+ ["path", { d: "m12 5 7 7-7 7" }]
+ ];
+
+ const ArrowUp01 = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["rect", { x: "15", y: "4", width: "4", height: "6", ry: "2" }],
+ ["path", { d: "M17 20v-6h-2" }],
+ ["path", { d: "M15 20h4" }]
+ ];
+
+ const ArrowUp10 = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M17 10V4h-2" }],
+ ["path", { d: "M15 10h4" }],
+ ["rect", { x: "15", y: "14", width: "4", height: "6", ry: "2" }]
+ ];
+
+ const ArrowUpAZ = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M20 8h-5" }],
+ ["path", { d: "M15 10V6.5a2.5 2.5 0 0 1 5 0V10" }],
+ ["path", { d: "M15 14h5l-5 6h5" }]
+ ];
+
+ const ArrowUpDown = [
+ ["path", { d: "m21 16-4 4-4-4" }],
+ ["path", { d: "M17 20V4" }],
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }]
+ ];
+
+ const ArrowUpFromDot = [
+ ["path", { d: "m5 9 7-7 7 7" }],
+ ["path", { d: "M12 16V2" }],
+ ["circle", { cx: "12", cy: "21", r: "1" }]
+ ];
+
+ const ArrowUpFromLine = [
+ ["path", { d: "m18 9-6-6-6 6" }],
+ ["path", { d: "M12 3v14" }],
+ ["path", { d: "M5 21h14" }]
+ ];
+
+ const ArrowUpLeft = [
+ ["path", { d: "M7 17V7h10" }],
+ ["path", { d: "M17 17 7 7" }]
+ ];
+
+ const ArrowUpNarrowWide = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M11 12h4" }],
+ ["path", { d: "M11 16h7" }],
+ ["path", { d: "M11 20h10" }]
+ ];
+
+ const ArrowUpRight = [
+ ["path", { d: "M7 7h10v10" }],
+ ["path", { d: "M7 17 17 7" }]
+ ];
+
+ const ArrowUpToLine = [
+ ["path", { d: "M5 3h14" }],
+ ["path", { d: "m18 13-6-6-6 6" }],
+ ["path", { d: "M12 7v14" }]
+ ];
+
+ const ArrowUpWideNarrow = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M11 12h10" }],
+ ["path", { d: "M11 16h7" }],
+ ["path", { d: "M11 20h4" }]
+ ];
+
+ const ArrowUpZA = [
+ ["path", { d: "m3 8 4-4 4 4" }],
+ ["path", { d: "M7 4v16" }],
+ ["path", { d: "M15 4h5l-5 6h5" }],
+ ["path", { d: "M15 20v-3.5a2.5 2.5 0 0 1 5 0V20" }],
+ ["path", { d: "M20 18h-5" }]
+ ];
+
+ const ArrowUp = [
+ ["path", { d: "m5 12 7-7 7 7" }],
+ ["path", { d: "M12 19V5" }]
+ ];
+
+ const ArrowsUpFromLine = [
+ ["path", { d: "m4 6 3-3 3 3" }],
+ ["path", { d: "M7 17V3" }],
+ ["path", { d: "m14 6 3-3 3 3" }],
+ ["path", { d: "M17 17V3" }],
+ ["path", { d: "M4 21h16" }]
+ ];
+
+ const Asterisk = [
+ ["path", { d: "M12 6v12" }],
+ ["path", { d: "M17.196 9 6.804 15" }],
+ ["path", { d: "m6.804 9 10.392 6" }]
+ ];
+
+ const AtSign = [
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["path", { d: "M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8" }]
+ ];
+
+ const Atom = [
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ [
+ "path",
+ {
+ d: "M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z"
+ }
+ ]
+ ];
+
+ const AudioLines = [
+ ["path", { d: "M2 10v3" }],
+ ["path", { d: "M6 6v11" }],
+ ["path", { d: "M10 3v18" }],
+ ["path", { d: "M14 8v7" }],
+ ["path", { d: "M18 5v13" }],
+ ["path", { d: "M22 10v3" }]
+ ];
+
+ const AudioWaveform = [
+ [
+ "path",
+ {
+ d: "M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2"
+ }
+ ]
+ ];
+
+ const Award = [
+ [
+ "path",
+ {
+ d: "m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526"
+ }
+ ],
+ ["circle", { cx: "12", cy: "8", r: "6" }]
+ ];
+
+ const Axe = [
+ ["path", { d: "m14 12-8.381 8.38a1 1 0 0 1-3.001-3L11 9" }],
+ [
+ "path",
+ {
+ d: "M15 15.5a.5.5 0 0 0 .5.5A6.5 6.5 0 0 0 22 9.5a.5.5 0 0 0-.5-.5h-1.672a2 2 0 0 1-1.414-.586l-5.062-5.062a1.205 1.205 0 0 0-1.704 0L9.352 5.648a1.205 1.205 0 0 0 0 1.704l5.062 5.062A2 2 0 0 1 15 13.828z"
+ }
+ ]
+ ];
+
+ const Axis3d = [
+ ["path", { d: "M13.5 10.5 15 9" }],
+ ["path", { d: "M4 4v15a1 1 0 0 0 1 1h15" }],
+ ["path", { d: "M4.293 19.707 6 18" }],
+ ["path", { d: "m9 15 1.5-1.5" }]
+ ];
+
+ const Baby = [
+ ["path", { d: "M10 16c.5.3 1.2.5 2 .5s1.5-.2 2-.5" }],
+ ["path", { d: "M15 12h.01" }],
+ [
+ "path",
+ {
+ d: "M19.38 6.813A9 9 0 0 1 20.8 10.2a2 2 0 0 1 0 3.6 9 9 0 0 1-17.6 0 2 2 0 0 1 0-3.6A9 9 0 0 1 12 3c2 0 3.5 1.1 3.5 2.5s-.9 2.5-2 2.5c-.8 0-1.5-.4-1.5-1"
+ }
+ ],
+ ["path", { d: "M9 12h.01" }]
+ ];
+
+ const Backpack = [
+ ["path", { d: "M4 10a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" }],
+ ["path", { d: "M8 10h8" }],
+ ["path", { d: "M8 18h8" }],
+ ["path", { d: "M8 22v-6a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v6" }],
+ ["path", { d: "M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" }]
+ ];
+
+ const BadgeAlert = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12" }],
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16" }]
+ ];
+
+ const BadgeCent = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M12 7v10" }],
+ ["path", { d: "M15.4 10a4 4 0 1 0 0 4" }]
+ ];
+
+ const BadgeCheck = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "m9 12 2 2 4-4" }]
+ ];
+
+ const BadgeDollarSign = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8" }],
+ ["path", { d: "M12 18V6" }]
+ ];
+
+ const BadgeEuro = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M7 12h5" }],
+ ["path", { d: "M15 9.4a4 4 0 1 0 0 5.2" }]
+ ];
+
+ const BadgeIndianRupee = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M8 8h8" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "m13 17-5-1h1a4 4 0 0 0 0-8" }]
+ ];
+
+ const BadgeInfo = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["line", { x1: "12", x2: "12", y1: "16", y2: "12" }],
+ ["line", { x1: "12", x2: "12.01", y1: "8", y2: "8" }]
+ ];
+
+ const BadgeJapaneseYen = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "m9 8 3 3v7" }],
+ ["path", { d: "m12 11 3-3" }],
+ ["path", { d: "M9 12h6" }],
+ ["path", { d: "M9 16h6" }]
+ ];
+
+ const BadgeMinus = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12" }]
+ ];
+
+ const BadgePlus = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "16" }],
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12" }]
+ ];
+
+ const BadgePercent = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "M9 9h.01" }],
+ ["path", { d: "M15 15h.01" }]
+ ];
+
+ const BadgePoundSterling = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M8 12h4" }],
+ ["path", { d: "M10 16V9.5a2.5 2.5 0 0 1 5 0" }],
+ ["path", { d: "M8 16h7" }]
+ ];
+
+ const BadgeQuestionMark = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }],
+ ["line", { x1: "12", x2: "12.01", y1: "17", y2: "17" }]
+ ];
+
+ const BadgeRussianRuble = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M9 16h5" }],
+ ["path", { d: "M9 12h5a2 2 0 1 0 0-4h-3v9" }]
+ ];
+
+ const BadgeSwissFranc = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["path", { d: "M11 17V8h4" }],
+ ["path", { d: "M11 12h3" }],
+ ["path", { d: "M9 16h4" }]
+ ];
+
+ const BadgeTurkishLira = [
+ ["path", { d: "M11 7v10a5 5 0 0 0 5-5" }],
+ ["path", { d: "m15 8-6 3" }],
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76"
+ }
+ ]
+ ];
+
+ const BadgeX = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ],
+ ["line", { x1: "15", x2: "9", y1: "9", y2: "15" }],
+ ["line", { x1: "9", x2: "15", y1: "9", y2: "15" }]
+ ];
+
+ const Badge = [
+ [
+ "path",
+ {
+ d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"
+ }
+ ]
+ ];
+
+ const BaggageClaim = [
+ ["path", { d: "M22 18H6a2 2 0 0 1-2-2V7a2 2 0 0 0-2-2" }],
+ ["path", { d: "M17 14V4a2 2 0 0 0-2-2h-1a2 2 0 0 0-2 2v10" }],
+ ["rect", { width: "13", height: "8", x: "8", y: "6", rx: "1" }],
+ ["circle", { cx: "18", cy: "20", r: "2" }],
+ ["circle", { cx: "9", cy: "20", r: "2" }]
+ ];
+
+ const Ban = [
+ ["path", { d: "M4.929 4.929 19.07 19.071" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Banana = [
+ ["path", { d: "M4 13c3.5-2 8-2 10 2a5.5 5.5 0 0 1 8 5" }],
+ [
+ "path",
+ {
+ d: "M5.15 17.89c5.52-1.52 8.65-6.89 7-12C11.55 4 11.5 2 13 2c3.22 0 5 5.5 5 8 0 6.5-4.2 12-10.49 12C5.11 22 2 22 2 20c0-1.5 1.14-1.55 3.15-2.11Z"
+ }
+ ]
+ ];
+
+ const Bandage = [
+ ["path", { d: "M10 10.01h.01" }],
+ ["path", { d: "M10 14.01h.01" }],
+ ["path", { d: "M14 10.01h.01" }],
+ ["path", { d: "M14 14.01h.01" }],
+ ["path", { d: "M18 6v11.5" }],
+ ["path", { d: "M6 6v12" }],
+ ["rect", { x: "2", y: "6", width: "20", height: "12", rx: "2" }]
+ ];
+
+ const BanknoteArrowUp = [
+ ["path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }],
+ ["path", { d: "M18 12h.01" }],
+ ["path", { d: "M19 22v-6" }],
+ ["path", { d: "m22 19-3-3-3 3" }],
+ ["path", { d: "M6 12h.01" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const BanknoteArrowDown = [
+ ["path", { d: "M12 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }],
+ ["path", { d: "m16 19 3 3 3-3" }],
+ ["path", { d: "M18 12h.01" }],
+ ["path", { d: "M19 16v6" }],
+ ["path", { d: "M6 12h.01" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const BanknoteX = [
+ ["path", { d: "M13 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5" }],
+ ["path", { d: "m17 17 5 5" }],
+ ["path", { d: "M18 12h.01" }],
+ ["path", { d: "m22 17-5 5" }],
+ ["path", { d: "M6 12h.01" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const Banknote = [
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["path", { d: "M6 12h.01M18 12h.01" }]
+ ];
+
+ const Barcode = [
+ ["path", { d: "M3 5v14" }],
+ ["path", { d: "M8 5v14" }],
+ ["path", { d: "M12 5v14" }],
+ ["path", { d: "M17 5v14" }],
+ ["path", { d: "M21 5v14" }]
+ ];
+
+ const Barrel = [
+ ["path", { d: "M10 3a41 41 0 0 0 0 18" }],
+ ["path", { d: "M14 3a41 41 0 0 1 0 18" }],
+ [
+ "path",
+ {
+ d: "M17 3a2 2 0 0 1 1.68.92 15.25 15.25 0 0 1 0 16.16A2 2 0 0 1 17 21H7a2 2 0 0 1-1.68-.92 15.25 15.25 0 0 1 0-16.16A2 2 0 0 1 7 3z"
+ }
+ ],
+ ["path", { d: "M3.84 17h16.32" }],
+ ["path", { d: "M3.84 7h16.32" }]
+ ];
+
+ const Baseline = [
+ ["path", { d: "M4 20h16" }],
+ ["path", { d: "m6 16 6-12 6 12" }],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const Bath = [
+ ["path", { d: "M10 4 8 6" }],
+ ["path", { d: "M17 19v2" }],
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "M7 19v2" }],
+ ["path", { d: "M9 5 7.621 3.621A2.121 2.121 0 0 0 4 5v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5" }]
+ ];
+
+ const BatteryCharging = [
+ ["path", { d: "m11 7-3 5h4l-3 5" }],
+ ["path", { d: "M14.856 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.935" }],
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M5.14 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.936" }]
+ ];
+
+ const BatteryFull = [
+ ["path", { d: "M10 10v4" }],
+ ["path", { d: "M14 10v4" }],
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M6 10v4" }],
+ ["rect", { x: "2", y: "6", width: "16", height: "12", rx: "2" }]
+ ];
+
+ const BatteryLow = [
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M6 14v-4" }],
+ ["rect", { x: "2", y: "6", width: "16", height: "12", rx: "2" }]
+ ];
+
+ const BatteryMedium = [
+ ["path", { d: "M10 14v-4" }],
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M6 14v-4" }],
+ ["rect", { x: "2", y: "6", width: "16", height: "12", rx: "2" }]
+ ];
+
+ const BatteryPlus = [
+ ["path", { d: "M10 9v6" }],
+ ["path", { d: "M12.543 6H16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3.605" }],
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M7 12h6" }],
+ ["path", { d: "M7.606 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3.606" }]
+ ];
+
+ const BatteryWarning = [
+ ["path", { d: "M10 17h.01" }],
+ ["path", { d: "M10 7v6" }],
+ ["path", { d: "M14 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M22 14v-4" }],
+ ["path", { d: "M6 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2" }]
+ ];
+
+ const Battery = [
+ ["path", { d: "M 22 14 L 22 10" }],
+ ["rect", { x: "2", y: "6", width: "16", height: "12", rx: "2" }]
+ ];
+
+ const Beaker = [
+ ["path", { d: "M4.5 3h15" }],
+ ["path", { d: "M6 3v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V3" }],
+ ["path", { d: "M6 14h12" }]
+ ];
+
+ const Bean = [
+ [
+ "path",
+ {
+ d: "M10.165 6.598C9.954 7.478 9.64 8.36 9 9c-.64.64-1.521.954-2.402 1.165A6 6 0 0 0 8 22c7.732 0 14-6.268 14-14a6 6 0 0 0-11.835-1.402Z"
+ }
+ ],
+ ["path", { d: "M5.341 10.62a4 4 0 1 0 5.279-5.28" }]
+ ];
+
+ const BedDouble = [
+ ["path", { d: "M2 20v-8a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v8" }],
+ ["path", { d: "M4 10V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4" }],
+ ["path", { d: "M12 4v6" }],
+ ["path", { d: "M2 18h20" }]
+ ];
+
+ const BeanOff = [
+ ["path", { d: "M9 9c-.64.64-1.521.954-2.402 1.165A6 6 0 0 0 8 22a13.96 13.96 0 0 0 9.9-4.1" }],
+ ["path", { d: "M10.75 5.093A6 6 0 0 1 22 8c0 2.411-.61 4.68-1.683 6.66" }],
+ ["path", { d: "M5.341 10.62a4 4 0 0 0 6.487 1.208M10.62 5.341a4.015 4.015 0 0 1 2.039 2.04" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const BedSingle = [
+ ["path", { d: "M3 20v-8a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v8" }],
+ ["path", { d: "M5 10V6a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v4" }],
+ ["path", { d: "M3 18h18" }]
+ ];
+
+ const Bed = [
+ ["path", { d: "M2 4v16" }],
+ ["path", { d: "M2 8h18a2 2 0 0 1 2 2v10" }],
+ ["path", { d: "M2 17h20" }],
+ ["path", { d: "M6 8v9" }]
+ ];
+
+ const Beef = [
+ [
+ "path",
+ {
+ d: "M16.4 13.7A6.5 6.5 0 1 0 6.28 6.6c-1.1 3.13-.78 3.9-3.18 6.08A3 3 0 0 0 5 18c4 0 8.4-1.8 11.4-4.3"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m18.5 6 2.19 4.5a6.48 6.48 0 0 1-2.29 7.2C15.4 20.2 11 22 7 22a3 3 0 0 1-2.68-1.66L2.4 16.5"
+ }
+ ],
+ ["circle", { cx: "12.5", cy: "8.5", r: "2.5" }]
+ ];
+
+ const BeerOff = [
+ ["path", { d: "M13 13v5" }],
+ ["path", { d: "M17 11.47V8" }],
+ ["path", { d: "M17 11h1a3 3 0 0 1 2.745 4.211" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M5 8v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-3" }],
+ ["path", { d: "M7.536 7.535C6.766 7.649 6.154 8 5.5 8a2.5 2.5 0 0 1-1.768-4.268" }],
+ [
+ "path",
+ {
+ d: "M8.727 3.204C9.306 2.767 9.885 2 11 2c1.56 0 2 1.5 3 1.5s1.72-.5 2.5-.5a1 1 0 1 1 0 5c-.78 0-1.5-.5-2.5-.5a3.149 3.149 0 0 0-.842.12"
+ }
+ ],
+ ["path", { d: "M9 14.6V18" }]
+ ];
+
+ const Beer = [
+ ["path", { d: "M17 11h1a3 3 0 0 1 0 6h-1" }],
+ ["path", { d: "M9 12v6" }],
+ ["path", { d: "M13 12v6" }],
+ [
+ "path",
+ {
+ d: "M14 7.5c-1 0-1.44.5-3 .5s-2-.5-3-.5-1.72.5-2.5.5a2.5 2.5 0 0 1 0-5c.78 0 1.57.5 2.5.5S9.44 2 11 2s2 1.5 3 1.5 1.72-.5 2.5-.5a2.5 2.5 0 0 1 0 5c-.78 0-1.5-.5-2.5-.5Z"
+ }
+ ],
+ ["path", { d: "M5 8v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8" }]
+ ];
+
+ const BellDot = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ [
+ "path",
+ {
+ d: "M13.916 2.314A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.74 7.327A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673 9 9 0 0 1-.585-.665"
+ }
+ ],
+ ["circle", { cx: "18", cy: "8", r: "3" }]
+ ];
+
+ const BellElectric = [
+ ["path", { d: "M18.518 17.347A7 7 0 0 1 14 19" }],
+ ["path", { d: "M18.8 4A11 11 0 0 1 20 9" }],
+ ["path", { d: "M9 9h.01" }],
+ ["circle", { cx: "20", cy: "16", r: "2" }],
+ ["circle", { cx: "9", cy: "9", r: "7" }],
+ ["rect", { x: "4", y: "16", width: "10", height: "6", rx: "2" }]
+ ];
+
+ const BellMinus = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ ["path", { d: "M15 8h6" }],
+ [
+ "path",
+ {
+ d: "M16.243 3.757A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673A9.4 9.4 0 0 1 18.667 12"
+ }
+ ]
+ ];
+
+ const BellOff = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ ["path", { d: "M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05" }]
+ ];
+
+ const BellPlus = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ ["path", { d: "M15 8h6" }],
+ ["path", { d: "M18 5v6" }],
+ [
+ "path",
+ {
+ d: "M20.002 14.464a9 9 0 0 0 .738.863A1 1 0 0 1 20 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 8.75-5.332"
+ }
+ ]
+ ];
+
+ const BellRing = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ ["path", { d: "M22 8c0-2.3-.8-4.3-2-6" }],
+ [
+ "path",
+ {
+ d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"
+ }
+ ],
+ ["path", { d: "M4 2C2.8 3.7 2 5.7 2 8" }]
+ ];
+
+ const Bell = [
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0" }],
+ [
+ "path",
+ {
+ d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"
+ }
+ ]
+ ];
+
+ const BetweenHorizontalEnd = [
+ ["rect", { width: "13", height: "7", x: "3", y: "3", rx: "1" }],
+ ["path", { d: "m22 15-3-3 3-3" }],
+ ["rect", { width: "13", height: "7", x: "3", y: "14", rx: "1" }]
+ ];
+
+ const BetweenHorizontalStart = [
+ ["rect", { width: "13", height: "7", x: "8", y: "3", rx: "1" }],
+ ["path", { d: "m2 9 3 3-3 3" }],
+ ["rect", { width: "13", height: "7", x: "8", y: "14", rx: "1" }]
+ ];
+
+ const BetweenVerticalEnd = [
+ ["rect", { width: "7", height: "13", x: "3", y: "3", rx: "1" }],
+ ["path", { d: "m9 22 3-3 3 3" }],
+ ["rect", { width: "7", height: "13", x: "14", y: "3", rx: "1" }]
+ ];
+
+ const BetweenVerticalStart = [
+ ["rect", { width: "7", height: "13", x: "3", y: "8", rx: "1" }],
+ ["path", { d: "m15 2-3 3-3-3" }],
+ ["rect", { width: "7", height: "13", x: "14", y: "8", rx: "1" }]
+ ];
+
+ const BicepsFlexed = [
+ [
+ "path",
+ {
+ d: "M12.409 13.017A5 5 0 0 1 22 15c0 3.866-4 7-9 7-4.077 0-8.153-.82-10.371-2.462-.426-.316-.631-.832-.62-1.362C2.118 12.723 2.627 2 10 2a3 3 0 0 1 3 3 2 2 0 0 1-2 2c-1.105 0-1.64-.444-2-1"
+ }
+ ],
+ ["path", { d: "M15 14a5 5 0 0 0-7.584 2" }],
+ ["path", { d: "M9.964 6.825C8.019 7.977 9.5 13 8 15" }]
+ ];
+
+ const Bike = [
+ ["circle", { cx: "18.5", cy: "17.5", r: "3.5" }],
+ ["circle", { cx: "5.5", cy: "17.5", r: "3.5" }],
+ ["circle", { cx: "15", cy: "5", r: "1" }],
+ ["path", { d: "M12 17.5V14l-3-3 4-3 2 3h2" }]
+ ];
+
+ const Binary = [
+ ["rect", { x: "14", y: "14", width: "4", height: "6", rx: "2" }],
+ ["rect", { x: "6", y: "4", width: "4", height: "6", rx: "2" }],
+ ["path", { d: "M6 20h4" }],
+ ["path", { d: "M14 10h4" }],
+ ["path", { d: "M6 14h2v6" }],
+ ["path", { d: "M14 4h2v6" }]
+ ];
+
+ const Binoculars = [
+ ["path", { d: "M10 10h4" }],
+ ["path", { d: "M19 7V4a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3" }],
+ [
+ "path",
+ {
+ d: "M20 21a2 2 0 0 0 2-2v-3.851c0-1.39-2-2.962-2-4.829V8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v11a2 2 0 0 0 2 2z"
+ }
+ ],
+ ["path", { d: "M 22 16 L 2 16" }],
+ [
+ "path",
+ {
+ d: "M4 21a2 2 0 0 1-2-2v-3.851c0-1.39 2-2.962 2-4.829V8a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v11a2 2 0 0 1-2 2z"
+ }
+ ],
+ ["path", { d: "M9 7V4a1 1 0 0 0-1-1H6a1 1 0 0 0-1 1v3" }]
+ ];
+
+ const Biohazard = [
+ ["circle", { cx: "12", cy: "11.9", r: "2" }],
+ ["path", { d: "M6.7 3.4c-.9 2.5 0 5.2 2.2 6.7C6.5 9 3.7 9.6 2 11.6" }],
+ ["path", { d: "m8.9 10.1 1.4.8" }],
+ ["path", { d: "M17.3 3.4c.9 2.5 0 5.2-2.2 6.7 2.4-1.2 5.2-.6 6.9 1.5" }],
+ ["path", { d: "m15.1 10.1-1.4.8" }],
+ ["path", { d: "M16.7 20.8c-2.6-.4-4.6-2.6-4.7-5.3-.2 2.6-2.1 4.8-4.7 5.2" }],
+ ["path", { d: "M12 13.9v1.6" }],
+ ["path", { d: "M13.5 5.4c-1-.2-2-.2-3 0" }],
+ ["path", { d: "M17 16.4c.7-.7 1.2-1.6 1.5-2.5" }],
+ ["path", { d: "M5.5 13.9c.3.9.8 1.8 1.5 2.5" }]
+ ];
+
+ const Bird = [
+ ["path", { d: "M16 7h.01" }],
+ ["path", { d: "M3.4 18H12a8 8 0 0 0 8-8V7a4 4 0 0 0-7.28-2.3L2 20" }],
+ ["path", { d: "m20 7 2 .5-2 .5" }],
+ ["path", { d: "M10 18v3" }],
+ ["path", { d: "M14 17.75V21" }],
+ ["path", { d: "M7 18a6 6 0 0 0 3.84-10.61" }]
+ ];
+
+ const Bitcoin = [
+ [
+ "path",
+ {
+ d: "M11.767 19.089c4.924.868 6.14-6.025 1.216-6.894m-1.216 6.894L5.86 18.047m5.908 1.042-.347 1.97m1.563-8.864c4.924.869 6.14-6.025 1.215-6.893m-1.215 6.893-3.94-.694m5.155-6.2L8.29 4.26m5.908 1.042.348-1.97M7.48 20.364l3.126-17.727"
+ }
+ ]
+ ];
+
+ const Blend = [
+ ["circle", { cx: "9", cy: "9", r: "7" }],
+ ["circle", { cx: "15", cy: "15", r: "7" }]
+ ];
+
+ const Blinds = [
+ ["path", { d: "M3 3h18" }],
+ ["path", { d: "M20 7H8" }],
+ ["path", { d: "M20 11H8" }],
+ ["path", { d: "M10 19h10" }],
+ ["path", { d: "M8 15h12" }],
+ ["path", { d: "M4 3v14" }],
+ ["circle", { cx: "4", cy: "19", r: "2" }]
+ ];
+
+ const Blocks = [
+ [
+ "path",
+ {
+ d: "M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2"
+ }
+ ],
+ ["rect", { x: "14", y: "2", width: "8", height: "8", rx: "1" }]
+ ];
+
+ const BluetoothConnected = [
+ ["path", { d: "m7 7 10 10-5 5V2l5 5L7 17" }],
+ ["line", { x1: "18", x2: "21", y1: "12", y2: "12" }],
+ ["line", { x1: "3", x2: "6", y1: "12", y2: "12" }]
+ ];
+
+ const BluetoothOff = [
+ ["path", { d: "m17 17-5 5V12l-5 5" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M14.5 9.5 17 7l-5-5v4.5" }]
+ ];
+
+ const BluetoothSearching = [
+ ["path", { d: "m7 7 10 10-5 5V2l5 5L7 17" }],
+ ["path", { d: "M20.83 14.83a4 4 0 0 0 0-5.66" }],
+ ["path", { d: "M18 12h.01" }]
+ ];
+
+ const Bluetooth = [["path", { d: "m7 7 10 10-5 5V2l5 5L7 17" }]];
+
+ const Bold = [
+ ["path", { d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8" }]
+ ];
+
+ const Bolt = [
+ [
+ "path",
+ {
+ d: "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "4" }]
+ ];
+
+ const Bomb = [
+ ["circle", { cx: "11", cy: "13", r: "9" }],
+ [
+ "path",
+ { d: "M14.35 4.65 16.3 2.7a2.41 2.41 0 0 1 3.4 0l1.6 1.6a2.4 2.4 0 0 1 0 3.4l-1.95 1.95" }
+ ],
+ ["path", { d: "m22 2-1.5 1.5" }]
+ ];
+
+ const Bone = [
+ [
+ "path",
+ {
+ d: "M17 10c.7-.7 1.69 0 2.5 0a2.5 2.5 0 1 0 0-5 .5.5 0 0 1-.5-.5 2.5 2.5 0 1 0-5 0c0 .81.7 1.8 0 2.5l-7 7c-.7.7-1.69 0-2.5 0a2.5 2.5 0 0 0 0 5c.28 0 .5.22.5.5a2.5 2.5 0 1 0 5 0c0-.81-.7-1.8 0-2.5Z"
+ }
+ ]
+ ];
+
+ const BookA = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "m8 13 4-7 4 7" }],
+ ["path", { d: "M9.1 11h5.7" }]
+ ];
+
+ const BookAlert = [
+ ["path", { d: "M12 13h.01" }],
+ ["path", { d: "M12 6v3" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ]
+ ];
+
+ const BookAudio = [
+ ["path", { d: "M12 6v7" }],
+ ["path", { d: "M16 8v3" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "M8 8v3" }]
+ ];
+
+ const BookCheck = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "m9 9.5 2 2 4-4" }]
+ ];
+
+ const BookCopy = [
+ ["path", { d: "M5 7a2 2 0 0 0-2 2v11" }],
+ ["path", { d: "M5.803 18H5a2 2 0 0 0 0 4h9.5a.5.5 0 0 0 .5-.5V21" }],
+ [
+ "path",
+ { d: "M9 15V4a2 2 0 0 1 2-2h9.5a.5.5 0 0 1 .5.5v14a.5.5 0 0 1-.5.5H11a2 2 0 0 1 0-4h10" }
+ ]
+ ];
+
+ const BookDashed = [
+ ["path", { d: "M12 17h1.5" }],
+ ["path", { d: "M12 22h1.5" }],
+ ["path", { d: "M12 2h1.5" }],
+ ["path", { d: "M17.5 22H19a1 1 0 0 0 1-1" }],
+ ["path", { d: "M17.5 2H19a1 1 0 0 1 1 1v1.5" }],
+ ["path", { d: "M20 14v3h-2.5" }],
+ ["path", { d: "M20 8.5V10" }],
+ ["path", { d: "M4 10V8.5" }],
+ ["path", { d: "M4 19.5V14" }],
+ ["path", { d: "M4 4.5A2.5 2.5 0 0 1 6.5 2H8" }],
+ ["path", { d: "M8 22H6.5a1 1 0 0 1 0-5H8" }]
+ ];
+
+ const BookDown = [
+ ["path", { d: "M12 13V7" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "m9 10 3 3 3-3" }]
+ ];
+
+ const BookHeadphones = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "M8 12v-2a4 4 0 0 1 8 0v2" }],
+ ["circle", { cx: "15", cy: "12", r: "1" }],
+ ["circle", { cx: "9", cy: "12", r: "1" }]
+ ];
+
+ const BookHeart = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ [
+ "path",
+ {
+ d: "M8.62 9.8A2.25 2.25 0 1 1 12 6.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"
+ }
+ ]
+ ];
+
+ const BookImage = [
+ ["path", { d: "m20 13.7-2.1-2.1a2 2 0 0 0-2.8 0L9.7 17" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["circle", { cx: "10", cy: "8", r: "2" }]
+ ];
+
+ const BookKey = [
+ ["path", { d: "m19 3 1 1" }],
+ ["path", { d: "m20 2-4.5 4.5" }],
+ ["path", { d: "M20 7.898V21a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }],
+ ["path", { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2h7.844" }],
+ ["circle", { cx: "14", cy: "8", r: "2" }]
+ ];
+
+ const BookLock = [
+ ["path", { d: "M18 6V4a2 2 0 1 0-4 0v2" }],
+ ["path", { d: "M20 15v6a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }],
+ ["path", { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H10" }],
+ ["rect", { x: "12", y: "6", width: "8", height: "5", rx: "1" }]
+ ];
+
+ const BookMarked = [
+ ["path", { d: "M10 2v8l3-3 3 3V2" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ]
+ ];
+
+ const BookMinus = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "M9 10h6" }]
+ ];
+
+ const BookOpenCheck = [
+ ["path", { d: "M12 21V7" }],
+ ["path", { d: "m16 12 2 2 4-4" }],
+ [
+ "path",
+ {
+ d: "M22 6V4a1 1 0 0 0-1-1h-5a4 4 0 0 0-4 4 4 4 0 0 0-4-4H3a1 1 0 0 0-1 1v13a1 1 0 0 0 1 1h6a3 3 0 0 1 3 3 3 3 0 0 1 3-3h6a1 1 0 0 0 1-1v-1.3"
+ }
+ ]
+ ];
+
+ const BookOpenText = [
+ ["path", { d: "M12 7v14" }],
+ ["path", { d: "M16 12h2" }],
+ ["path", { d: "M16 8h2" }],
+ [
+ "path",
+ {
+ d: "M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"
+ }
+ ],
+ ["path", { d: "M6 12h2" }],
+ ["path", { d: "M6 8h2" }]
+ ];
+
+ const BookOpen = [
+ ["path", { d: "M12 7v14" }],
+ [
+ "path",
+ {
+ d: "M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"
+ }
+ ]
+ ];
+
+ const BookPlus = [
+ ["path", { d: "M12 7v6" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "M9 10h6" }]
+ ];
+
+ const BookText = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "M8 11h8" }],
+ ["path", { d: "M8 7h6" }]
+ ];
+
+ const BookType = [
+ ["path", { d: "M10 13h4" }],
+ ["path", { d: "M12 6v7" }],
+ ["path", { d: "M16 8V6H8v2" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ]
+ ];
+
+ const BookUp2 = [
+ ["path", { d: "M12 13V7" }],
+ ["path", { d: "M18 2h1a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }],
+ ["path", { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2" }],
+ ["path", { d: "m9 10 3-3 3 3" }],
+ ["path", { d: "m9 5 3-3 3 3" }]
+ ];
+
+ const BookUp = [
+ ["path", { d: "M12 13V7" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "m9 10 3-3 3 3" }]
+ ];
+
+ const BookUser = [
+ ["path", { d: "M15 13a3 3 0 1 0-6 0" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["circle", { cx: "12", cy: "8", r: "2" }]
+ ];
+
+ const BookX = [
+ ["path", { d: "m14.5 7-5 5" }],
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ],
+ ["path", { d: "m9.5 7 5 5" }]
+ ];
+
+ const Book = [
+ [
+ "path",
+ { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }
+ ]
+ ];
+
+ const BookmarkCheck = [
+ ["path", { d: "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2Z" }],
+ ["path", { d: "m9 10 2 2 4-4" }]
+ ];
+
+ const BookmarkMinus = [
+ ["path", { d: "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" }],
+ ["line", { x1: "15", x2: "9", y1: "10", y2: "10" }]
+ ];
+
+ const BookmarkPlus = [
+ ["path", { d: "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" }],
+ ["line", { x1: "12", x2: "12", y1: "7", y2: "13" }],
+ ["line", { x1: "15", x2: "9", y1: "10", y2: "10" }]
+ ];
+
+ const BookmarkX = [
+ ["path", { d: "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2Z" }],
+ ["path", { d: "m14.5 7.5-5 5" }],
+ ["path", { d: "m9.5 7.5 5 5" }]
+ ];
+
+ const Bookmark = [["path", { d: "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" }]];
+
+ const BoomBox = [
+ ["path", { d: "M4 9V5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4" }],
+ ["path", { d: "M8 8v1" }],
+ ["path", { d: "M12 8v1" }],
+ ["path", { d: "M16 8v1" }],
+ ["rect", { width: "20", height: "12", x: "2", y: "9", rx: "2" }],
+ ["circle", { cx: "8", cy: "15", r: "2" }],
+ ["circle", { cx: "16", cy: "15", r: "2" }]
+ ];
+
+ const BotMessageSquare = [
+ ["path", { d: "M12 6V2H8" }],
+ ["path", { d: "M15 11v2" }],
+ ["path", { d: "M2 12h2" }],
+ ["path", { d: "M20 12h2" }],
+ [
+ "path",
+ {
+ d: "M20 16a2 2 0 0 1-2 2H8.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 4 20.286V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M9 11v2" }]
+ ];
+
+ const BotOff = [
+ ["path", { d: "M13.67 8H18a2 2 0 0 1 2 2v4.33" }],
+ ["path", { d: "M2 14h2" }],
+ ["path", { d: "M20 14h2" }],
+ ["path", { d: "M22 22 2 2" }],
+ ["path", { d: "M8 8H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 1.414-.586" }],
+ ["path", { d: "M9 13v2" }],
+ ["path", { d: "M9.67 4H12v2.33" }]
+ ];
+
+ const Bot = [
+ ["path", { d: "M12 8V4H8" }],
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2" }],
+ ["path", { d: "M2 14h2" }],
+ ["path", { d: "M20 14h2" }],
+ ["path", { d: "M15 13v2" }],
+ ["path", { d: "M9 13v2" }]
+ ];
+
+ const BottleWine = [
+ [
+ "path",
+ {
+ d: "M10 3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a6 6 0 0 0 1.2 3.6l.6.8A6 6 0 0 1 17 13v8a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-8a6 6 0 0 1 1.2-3.6l.6-.8A6 6 0 0 0 10 5z"
+ }
+ ],
+ ["path", { d: "M17 13h-4a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h4" }]
+ ];
+
+ const BowArrow = [
+ ["path", { d: "M17 3h4v4" }],
+ ["path", { d: "M18.575 11.082a13 13 0 0 1 1.048 9.027 1.17 1.17 0 0 1-1.914.597L14 17" }],
+ ["path", { d: "M7 10 3.29 6.29a1.17 1.17 0 0 1 .6-1.91 13 13 0 0 1 9.03 1.05" }],
+ [
+ "path",
+ {
+ d: "M7 14a1.7 1.7 0 0 0-1.207.5l-2.646 2.646A.5.5 0 0 0 3.5 18H5a1 1 0 0 1 1 1v1.5a.5.5 0 0 0 .854.354L9.5 18.207A1.7 1.7 0 0 0 10 17v-2a1 1 0 0 0-1-1z"
+ }
+ ],
+ ["path", { d: "M9.707 14.293 21 3" }]
+ ];
+
+ const Box = [
+ [
+ "path",
+ {
+ d: "M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"
+ }
+ ],
+ ["path", { d: "m3.3 7 8.7 5 8.7-5" }],
+ ["path", { d: "M12 22V12" }]
+ ];
+
+ const Boxes = [
+ [
+ "path",
+ {
+ d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z"
+ }
+ ],
+ ["path", { d: "m7 16.5-4.74-2.85" }],
+ ["path", { d: "m7 16.5 5-3" }],
+ ["path", { d: "M7 16.5v5.17" }],
+ [
+ "path",
+ {
+ d: "M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z"
+ }
+ ],
+ ["path", { d: "m17 16.5-5-3" }],
+ ["path", { d: "m17 16.5 4.74-2.85" }],
+ ["path", { d: "M17 16.5v5.17" }],
+ [
+ "path",
+ {
+ d: "M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z"
+ }
+ ],
+ ["path", { d: "M12 8 7.26 5.15" }],
+ ["path", { d: "m12 8 4.74-2.85" }],
+ ["path", { d: "M12 13.5V8" }]
+ ];
+
+ const Braces = [
+ ["path", { d: "M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1" }],
+ ["path", { d: "M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1" }]
+ ];
+
+ const Brackets = [
+ ["path", { d: "M16 3h3a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-3" }],
+ ["path", { d: "M8 21H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h3" }]
+ ];
+
+ const BrainCircuit = [
+ [
+ "path",
+ { d: "M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" }
+ ],
+ ["path", { d: "M9 13a4.5 4.5 0 0 0 3-4" }],
+ ["path", { d: "M6.003 5.125A3 3 0 0 0 6.401 6.5" }],
+ ["path", { d: "M3.477 10.896a4 4 0 0 1 .585-.396" }],
+ ["path", { d: "M6 18a4 4 0 0 1-1.967-.516" }],
+ ["path", { d: "M12 13h4" }],
+ ["path", { d: "M12 18h6a2 2 0 0 1 2 2v1" }],
+ ["path", { d: "M12 8h8" }],
+ ["path", { d: "M16 8V5a2 2 0 0 1 2-2" }],
+ ["circle", { cx: "16", cy: "13", r: ".5" }],
+ ["circle", { cx: "18", cy: "3", r: ".5" }],
+ ["circle", { cx: "20", cy: "21", r: ".5" }],
+ ["circle", { cx: "20", cy: "8", r: ".5" }]
+ ];
+
+ const BrainCog = [
+ ["path", { d: "m10.852 14.772-.383.923" }],
+ ["path", { d: "m10.852 9.228-.383-.923" }],
+ ["path", { d: "m13.148 14.772.382.924" }],
+ ["path", { d: "m13.531 8.305-.383.923" }],
+ ["path", { d: "m14.772 10.852.923-.383" }],
+ ["path", { d: "m14.772 13.148.923.383" }],
+ [
+ "path",
+ {
+ d: "M17.598 6.5A3 3 0 1 0 12 5a3 3 0 0 0-5.63-1.446 3 3 0 0 0-.368 1.571 4 4 0 0 0-2.525 5.771"
+ }
+ ],
+ ["path", { d: "M17.998 5.125a4 4 0 0 1 2.525 5.771" }],
+ ["path", { d: "M19.505 10.294a4 4 0 0 1-1.5 7.706" }],
+ [
+ "path",
+ { d: "M4.032 17.483A4 4 0 0 0 11.464 20c.18-.311.892-.311 1.072 0a4 4 0 0 0 7.432-2.516" }
+ ],
+ ["path", { d: "M4.5 10.291A4 4 0 0 0 6 18" }],
+ ["path", { d: "M6.002 5.125a3 3 0 0 0 .4 1.375" }],
+ ["path", { d: "m9.228 10.852-.923-.383" }],
+ ["path", { d: "m9.228 13.148-.923.383" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }]
+ ];
+
+ const Brain = [
+ ["path", { d: "M12 18V5" }],
+ ["path", { d: "M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4" }],
+ ["path", { d: "M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5" }],
+ ["path", { d: "M17.997 5.125a4 4 0 0 1 2.526 5.77" }],
+ ["path", { d: "M18 18a4 4 0 0 0 2-7.464" }],
+ ["path", { d: "M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517" }],
+ ["path", { d: "M6 18a4 4 0 0 1-2-7.464" }],
+ ["path", { d: "M6.003 5.125a4 4 0 0 0-2.526 5.77" }]
+ ];
+
+ const BrickWallFire = [
+ ["path", { d: "M16 3v2.107" }],
+ [
+ "path",
+ {
+ d: "M17 9c1 3 2.5 3.5 3.5 4.5A5 5 0 0 1 22 17a5 5 0 0 1-10 0c0-.3 0-.6.1-.9a2 2 0 1 0 3.3-2C13 11.5 16 9 17 9"
+ }
+ ],
+ ["path", { d: "M21 8.274V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.938" }],
+ ["path", { d: "M3 15h5.253" }],
+ ["path", { d: "M3 9h8.228" }],
+ ["path", { d: "M8 15v6" }],
+ ["path", { d: "M8 3v6" }]
+ ];
+
+ const BrickWallShield = [
+ ["path", { d: "M12 9v1.258" }],
+ ["path", { d: "M16 3v5.46" }],
+ ["path", { d: "M21 9.118V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5.75" }],
+ [
+ "path",
+ {
+ d: "M22 17.5c0 2.499-1.75 3.749-3.83 4.474a.5.5 0 0 1-.335-.005c-2.085-.72-3.835-1.97-3.835-4.47V14a.5.5 0 0 1 .5-.499c1 0 2.25-.6 3.12-1.36a.6.6 0 0 1 .76-.001c.875.765 2.12 1.36 3.12 1.36a.5.5 0 0 1 .5.5z"
+ }
+ ],
+ ["path", { d: "M3 15h7" }],
+ ["path", { d: "M3 9h12.142" }],
+ ["path", { d: "M8 15v6" }],
+ ["path", { d: "M8 3v6" }]
+ ];
+
+ const BrickWall = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M12 9v6" }],
+ ["path", { d: "M16 15v6" }],
+ ["path", { d: "M16 3v6" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "M8 15v6" }],
+ ["path", { d: "M8 3v6" }]
+ ];
+
+ const BriefcaseConveyorBelt = [
+ ["path", { d: "M10 20v2" }],
+ ["path", { d: "M14 20v2" }],
+ ["path", { d: "M18 20v2" }],
+ ["path", { d: "M21 20H3" }],
+ ["path", { d: "M6 20v2" }],
+ ["path", { d: "M8 16V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v12" }],
+ ["rect", { x: "4", y: "6", width: "16", height: "10", rx: "2" }]
+ ];
+
+ const BriefcaseBusiness = [
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" }],
+ ["path", { d: "M22 13a18.15 18.15 0 0 1-20 0" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const BriefcaseMedical = [
+ ["path", { d: "M12 11v4" }],
+ ["path", { d: "M14 13h-4" }],
+ ["path", { d: "M16 6V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" }],
+ ["path", { d: "M18 6v14" }],
+ ["path", { d: "M6 6v14" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const Briefcase = [
+ ["path", { d: "M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const BringToFront = [
+ ["rect", { x: "8", y: "8", width: "8", height: "8", rx: "2" }],
+ ["path", { d: "M4 10a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2" }],
+ ["path", { d: "M14 20a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2" }]
+ ];
+
+ const BrushCleaning = [
+ ["path", { d: "m16 22-1-4" }],
+ [
+ "path",
+ {
+ d: "M19 13.99a1 1 0 0 0 1-1V12a2 2 0 0 0-2-2h-3a1 1 0 0 1-1-1V4a2 2 0 0 0-4 0v5a1 1 0 0 1-1 1H6a2 2 0 0 0-2 2v.99a1 1 0 0 0 1 1"
+ }
+ ],
+ ["path", { d: "M5 14h14l1.973 6.767A1 1 0 0 1 20 22H4a1 1 0 0 1-.973-1.233z" }],
+ ["path", { d: "m8 22 1-4" }]
+ ];
+
+ const Brush = [
+ ["path", { d: "m11 10 3 3" }],
+ ["path", { d: "M6.5 21A3.5 3.5 0 1 0 3 17.5a2.62 2.62 0 0 1-.708 1.792A1 1 0 0 0 3 21z" }],
+ ["path", { d: "M9.969 17.031 21.378 5.624a1 1 0 0 0-3.002-3.002L6.967 14.031" }]
+ ];
+
+ const Bubbles = [
+ ["path", { d: "M7.2 14.8a2 2 0 0 1 2 2" }],
+ ["circle", { cx: "18.5", cy: "8.5", r: "3.5" }],
+ ["circle", { cx: "7.5", cy: "16.5", r: "5.5" }],
+ ["circle", { cx: "7.5", cy: "4.5", r: "2.5" }]
+ ];
+
+ const BugOff = [
+ ["path", { d: "M12 20v-8" }],
+ ["path", { d: "M14.12 3.88 16 2" }],
+ ["path", { d: "M15 7.13V6a3 3 0 0 0-5.14-2.1L8 2" }],
+ ["path", { d: "M18 12.34V11a4 4 0 0 0-4-4h-1.3" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M21 5a4 4 0 0 1-3.55 3.97" }],
+ ["path", { d: "M22 13h-3.34" }],
+ ["path", { d: "M3 21a4 4 0 0 1 3.81-4" }],
+ ["path", { d: "M6 13H2" }],
+ ["path", { d: "M7.7 7.7A4 4 0 0 0 6 11v3a6 6 0 0 0 11.13 3.13" }]
+ ];
+
+ const BugPlay = [
+ ["path", { d: "M10 19.655A6 6 0 0 1 6 14v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 3.97" }],
+ [
+ "path",
+ {
+ d: "M14 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"
+ }
+ ],
+ ["path", { d: "M14.12 3.88 16 2" }],
+ ["path", { d: "M21 5a4 4 0 0 1-3.55 3.97" }],
+ ["path", { d: "M3 21a4 4 0 0 1 3.81-4" }],
+ ["path", { d: "M3 5a4 4 0 0 0 3.55 3.97" }],
+ ["path", { d: "M6 13H2" }],
+ ["path", { d: "m8 2 1.88 1.88" }],
+ ["path", { d: "M9 7.13V6a3 3 0 1 1 6 0v1.13" }]
+ ];
+
+ const Bug = [
+ ["path", { d: "M12 20v-9" }],
+ ["path", { d: "M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z" }],
+ ["path", { d: "M14.12 3.88 16 2" }],
+ ["path", { d: "M21 21a4 4 0 0 0-3.81-4" }],
+ ["path", { d: "M21 5a4 4 0 0 1-3.55 3.97" }],
+ ["path", { d: "M22 13h-4" }],
+ ["path", { d: "M3 21a4 4 0 0 1 3.81-4" }],
+ ["path", { d: "M3 5a4 4 0 0 0 3.55 3.97" }],
+ ["path", { d: "M6 13H2" }],
+ ["path", { d: "m8 2 1.88 1.88" }],
+ ["path", { d: "M9 7.13V6a3 3 0 1 1 6 0v1.13" }]
+ ];
+
+ const Building2 = [
+ ["path", { d: "M10 12h4" }],
+ ["path", { d: "M10 8h4" }],
+ ["path", { d: "M14 21v-3a2 2 0 0 0-4 0v3" }],
+ ["path", { d: "M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2" }],
+ ["path", { d: "M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16" }]
+ ];
+
+ const Building = [
+ ["path", { d: "M12 10h.01" }],
+ ["path", { d: "M12 14h.01" }],
+ ["path", { d: "M12 6h.01" }],
+ ["path", { d: "M16 10h.01" }],
+ ["path", { d: "M16 14h.01" }],
+ ["path", { d: "M16 6h.01" }],
+ ["path", { d: "M8 10h.01" }],
+ ["path", { d: "M8 14h.01" }],
+ ["path", { d: "M8 6h.01" }],
+ ["path", { d: "M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3" }],
+ ["rect", { x: "4", y: "2", width: "16", height: "20", rx: "2" }]
+ ];
+
+ const BusFront = [
+ ["path", { d: "M4 6 2 7" }],
+ ["path", { d: "M10 6h4" }],
+ ["path", { d: "m22 7-2-1" }],
+ ["rect", { width: "16", height: "16", x: "4", y: "3", rx: "2" }],
+ ["path", { d: "M4 11h16" }],
+ ["path", { d: "M8 15h.01" }],
+ ["path", { d: "M16 15h.01" }],
+ ["path", { d: "M6 19v2" }],
+ ["path", { d: "M18 21v-2" }]
+ ];
+
+ const Bus = [
+ ["path", { d: "M8 6v6" }],
+ ["path", { d: "M15 6v6" }],
+ ["path", { d: "M2 12h19.6" }],
+ [
+ "path",
+ {
+ d: "M18 18h3s.5-1.7.8-2.8c.1-.4.2-.8.2-1.2 0-.4-.1-.8-.2-1.2l-1.4-5C20.1 6.8 19.1 6 18 6H4a2 2 0 0 0-2 2v10h3"
+ }
+ ],
+ ["circle", { cx: "7", cy: "18", r: "2" }],
+ ["path", { d: "M9 18h5" }],
+ ["circle", { cx: "16", cy: "18", r: "2" }]
+ ];
+
+ const CableCar = [
+ ["path", { d: "M10 3h.01" }],
+ ["path", { d: "M14 2h.01" }],
+ ["path", { d: "m2 9 20-5" }],
+ ["path", { d: "M12 12V6.5" }],
+ ["rect", { width: "16", height: "10", x: "4", y: "12", rx: "3" }],
+ ["path", { d: "M9 12v5" }],
+ ["path", { d: "M15 12v5" }],
+ ["path", { d: "M4 17h16" }]
+ ];
+
+ const Cable = [
+ ["path", { d: "M17 19a1 1 0 0 1-1-1v-2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a1 1 0 0 1-1 1z" }],
+ ["path", { d: "M17 21v-2" }],
+ ["path", { d: "M19 14V6.5a1 1 0 0 0-7 0v11a1 1 0 0 1-7 0V10" }],
+ ["path", { d: "M21 21v-2" }],
+ ["path", { d: "M3 5V3" }],
+ ["path", { d: "M4 10a2 2 0 0 1-2-2V6a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2z" }],
+ ["path", { d: "M7 5V3" }]
+ ];
+
+ const CakeSlice = [
+ ["path", { d: "M16 13H3" }],
+ ["path", { d: "M16 17H3" }],
+ [
+ "path",
+ {
+ d: "m7.2 7.9-3.388 2.5A2 2 0 0 0 3 12.01V20a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1v-8.654c0-2-2.44-6.026-6.44-8.026a1 1 0 0 0-1.082.057L10.4 5.6"
+ }
+ ],
+ ["circle", { cx: "9", cy: "7", r: "2" }]
+ ];
+
+ const Cake = [
+ ["path", { d: "M20 21v-8a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8" }],
+ ["path", { d: "M4 16s.5-1 2-1 2.5 2 4 2 2.5-2 4-2 2.5 2 4 2 2-1 2-1" }],
+ ["path", { d: "M2 21h20" }],
+ ["path", { d: "M7 8v3" }],
+ ["path", { d: "M12 8v3" }],
+ ["path", { d: "M17 8v3" }],
+ ["path", { d: "M7 4h.01" }],
+ ["path", { d: "M12 4h.01" }],
+ ["path", { d: "M17 4h.01" }]
+ ];
+
+ const Calculator = [
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }],
+ ["line", { x1: "8", x2: "16", y1: "6", y2: "6" }],
+ ["line", { x1: "16", x2: "16", y1: "14", y2: "18" }],
+ ["path", { d: "M16 10h.01" }],
+ ["path", { d: "M12 10h.01" }],
+ ["path", { d: "M8 10h.01" }],
+ ["path", { d: "M12 14h.01" }],
+ ["path", { d: "M8 14h.01" }],
+ ["path", { d: "M12 18h.01" }],
+ ["path", { d: "M8 18h.01" }]
+ ];
+
+ const Calendar1 = [
+ ["path", { d: "M11 14h1v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }],
+ ["rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const CalendarArrowDown = [
+ ["path", { d: "m14 18 4 4 4-4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M18 14v8" }],
+ ["path", { d: "M21 11.354V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7.343" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarArrowUp = [
+ ["path", { d: "m14 18 4-4 4 4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M18 22v-8" }],
+ ["path", { d: "M21 11.343V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h9" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarCheck2 = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 14V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "m16 20 2 2 4-4" }]
+ ];
+
+ const CalendarCheck = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "m9 16 2 2 4-4" }]
+ ];
+
+ const CalendarClock = [
+ ["path", { d: "M16 14v2.2l1.6 1" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5" }],
+ ["path", { d: "M3 10h5" }],
+ ["path", { d: "M8 2v4" }],
+ ["circle", { cx: "16", cy: "16", r: "6" }]
+ ];
+
+ const CalendarCog = [
+ ["path", { d: "m15.228 16.852-.923-.383" }],
+ ["path", { d: "m15.228 19.148-.923.383" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "m16.47 14.305.382.923" }],
+ ["path", { d: "m16.852 20.772-.383.924" }],
+ ["path", { d: "m19.148 15.228.383-.923" }],
+ ["path", { d: "m19.53 21.696-.382-.924" }],
+ ["path", { d: "m20.772 16.852.924-.383" }],
+ ["path", { d: "m20.772 19.148.924.383" }],
+ ["path", { d: "M21 10.592V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const CalendarDays = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 14h.01" }],
+ ["path", { d: "M12 14h.01" }],
+ ["path", { d: "M16 14h.01" }],
+ ["path", { d: "M8 18h.01" }],
+ ["path", { d: "M12 18h.01" }],
+ ["path", { d: "M16 18h.01" }]
+ ];
+
+ const CalendarFold = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" }]
+ ];
+
+ const CalendarHeart = [
+ ["path", { d: "M12.127 22H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.125" }],
+ [
+ "path",
+ {
+ d: "M14.62 18.8A2.25 2.25 0 1 1 18 15.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"
+ }
+ ],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarMinus2 = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M10 16h4" }]
+ ];
+
+ const CalendarMinus = [
+ ["path", { d: "M16 19h6" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 15V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8.5" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarOff = [
+ ["path", { d: "M4.2 4.2A2 2 0 0 0 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 1.82-1.18" }],
+ ["path", { d: "M21 15.5V6a2 2 0 0 0-2-2H9.5" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M3 10h7" }],
+ ["path", { d: "M21 10h-5.5" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const CalendarPlus2 = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M10 16h4" }],
+ ["path", { d: "M12 14v4" }]
+ ];
+
+ const CalendarPlus = [
+ ["path", { d: "M16 19h6" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M19 16v6" }],
+ ["path", { d: "M21 12.598V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8.5" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarRange = [
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M17 14h-6" }],
+ ["path", { d: "M13 18H7" }],
+ ["path", { d: "M7 14h.01" }],
+ ["path", { d: "M17 18h.01" }]
+ ];
+
+ const CalendarSearch = [
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 11.75V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7.25" }],
+ ["path", { d: "m22 22-1.875-1.875" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "M8 2v4" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const CalendarSync = [
+ ["path", { d: "M11 10v4h4" }],
+ ["path", { d: "m11 14 1.535-1.605a5 5 0 0 1 8 1.5" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "m21 18-1.535 1.605a5 5 0 0 1-8-1.5" }],
+ ["path", { d: "M21 22v-4h-4" }],
+ ["path", { d: "M21 8.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4.3" }],
+ ["path", { d: "M3 10h4" }],
+ ["path", { d: "M8 2v4" }]
+ ];
+
+ const CalendarX2 = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M21 13V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "m17 22 5-5" }],
+ ["path", { d: "m17 17 5 5" }]
+ ];
+
+ const CalendarX = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }],
+ ["path", { d: "m14 14-4 4" }],
+ ["path", { d: "m10 14 4 4" }]
+ ];
+
+ const Calendar = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2" }],
+ ["path", { d: "M3 10h18" }]
+ ];
+
+ const CameraOff = [
+ ["path", { d: "M14.564 14.558a3 3 0 1 1-4.122-4.121" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20 20H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 .819-.175" }],
+ [
+ "path",
+ {
+ d: "M9.695 4.024A2 2 0 0 1 10.004 4h3.993a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v7.344"
+ }
+ ]
+ ];
+
+ const Camera = [
+ [
+ "path",
+ {
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "13", r: "3" }]
+ ];
+
+ const CandyCane = [
+ [
+ "path",
+ { d: "M5.7 21a2 2 0 0 1-3.5-2l8.6-14a6 6 0 0 1 10.4 6 2 2 0 1 1-3.464-2 2 2 0 1 0-3.464-2Z" }
+ ],
+ ["path", { d: "M17.75 7 15 2.1" }],
+ ["path", { d: "M10.9 4.8 13 9" }],
+ ["path", { d: "m7.9 9.7 2 4.4" }],
+ ["path", { d: "M4.9 14.7 7 18.9" }]
+ ];
+
+ const CandyOff = [
+ ["path", { d: "M10 10v7.9" }],
+ ["path", { d: "M11.802 6.145a5 5 0 0 1 6.053 6.053" }],
+ ["path", { d: "M14 6.1v2.243" }],
+ ["path", { d: "m15.5 15.571-.964.964a5 5 0 0 1-7.071 0 5 5 0 0 1 0-7.07l.964-.965" }],
+ [
+ "path",
+ {
+ d: "M16 7V3a1 1 0 0 1 1.707-.707 2.5 2.5 0 0 0 2.152.717 1 1 0 0 1 1.131 1.131 2.5 2.5 0 0 0 .717 2.152A1 1 0 0 1 21 8h-4"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }],
+ [
+ "path",
+ {
+ d: "M8 17v4a1 1 0 0 1-1.707.707 2.5 2.5 0 0 0-2.152-.717 1 1 0 0 1-1.131-1.131 2.5 2.5 0 0 0-.717-2.152A1 1 0 0 1 3 16h4"
+ }
+ ]
+ ];
+
+ const Candy = [
+ ["path", { d: "M10 7v10.9" }],
+ ["path", { d: "M14 6.1V17" }],
+ [
+ "path",
+ {
+ d: "M16 7V3a1 1 0 0 1 1.707-.707 2.5 2.5 0 0 0 2.152.717 1 1 0 0 1 1.131 1.131 2.5 2.5 0 0 0 .717 2.152A1 1 0 0 1 21 8h-4"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M16.536 7.465a5 5 0 0 0-7.072 0l-2 2a5 5 0 0 0 0 7.07 5 5 0 0 0 7.072 0l2-2a5 5 0 0 0 0-7.07"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M8 17v4a1 1 0 0 1-1.707.707 2.5 2.5 0 0 0-2.152-.717 1 1 0 0 1-1.131-1.131 2.5 2.5 0 0 0-.717-2.152A1 1 0 0 1 3 16h4"
+ }
+ ]
+ ];
+
+ const Cannabis = [
+ ["path", { d: "M12 22v-4" }],
+ [
+ "path",
+ {
+ d: "M7 12c-1.5 0-4.5 1.5-5 3 3.5 1.5 6 1 6 1-1.5 1.5-2 3.5-2 5 2.5 0 4.5-1.5 6-3 1.5 1.5 3.5 3 6 3 0-1.5-.5-3.5-2-5 0 0 2.5.5 6-1-.5-1.5-3.5-3-5-3 1.5-1 4-4 4-6-2.5 0-5.5 1.5-7 3 0-2.5-.5-5-2-7-1.5 2-2 4.5-2 7-1.5-1.5-4.5-3-7-3 0 2 2.5 5 4 6"
+ }
+ ]
+ ];
+
+ const CaptionsOff = [
+ ["path", { d: "M10.5 5H19a2 2 0 0 1 2 2v8.5" }],
+ ["path", { d: "M17 11h-.5" }],
+ ["path", { d: "M19 19H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M7 11h4" }],
+ ["path", { d: "M7 15h2.5" }]
+ ];
+
+ const Captions = [
+ ["rect", { width: "18", height: "14", x: "3", y: "5", rx: "2", ry: "2" }],
+ ["path", { d: "M7 15h4M15 15h2M7 11h2M13 11h4" }]
+ ];
+
+ const CarFront = [
+ ["path", { d: "m21 8-2 2-1.5-3.7A2 2 0 0 0 15.646 5H8.4a2 2 0 0 0-1.903 1.257L5 10 3 8" }],
+ ["path", { d: "M7 14h.01" }],
+ ["path", { d: "M17 14h.01" }],
+ ["rect", { width: "18", height: "8", x: "3", y: "10", rx: "2" }],
+ ["path", { d: "M5 18v2" }],
+ ["path", { d: "M19 18v2" }]
+ ];
+
+ const CarTaxiFront = [
+ ["path", { d: "M10 2h4" }],
+ ["path", { d: "m21 8-2 2-1.5-3.7A2 2 0 0 0 15.646 5H8.4a2 2 0 0 0-1.903 1.257L5 10 3 8" }],
+ ["path", { d: "M7 14h.01" }],
+ ["path", { d: "M17 14h.01" }],
+ ["rect", { width: "18", height: "8", x: "3", y: "10", rx: "2" }],
+ ["path", { d: "M5 18v2" }],
+ ["path", { d: "M19 18v2" }]
+ ];
+
+ const Car = [
+ [
+ "path",
+ {
+ d: "M19 17h2c.6 0 1-.4 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4c0 .6.4 1 1 1h2"
+ }
+ ],
+ ["circle", { cx: "7", cy: "17", r: "2" }],
+ ["path", { d: "M9 17h6" }],
+ ["circle", { cx: "17", cy: "17", r: "2" }]
+ ];
+
+ const Caravan = [
+ ["path", { d: "M18 19V9a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v8a2 2 0 0 0 2 2h2" }],
+ ["path", { d: "M2 9h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2" }],
+ ["path", { d: "M22 17v1a1 1 0 0 1-1 1H10v-9a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v9" }],
+ ["circle", { cx: "8", cy: "19", r: "2" }]
+ ];
+
+ const CardSim = [
+ ["path", { d: "M12 14v4" }],
+ [
+ "path",
+ {
+ d: "M14.172 2a2 2 0 0 1 1.414.586l3.828 3.828A2 2 0 0 1 20 7.828V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z"
+ }
+ ],
+ ["path", { d: "M8 14h8" }],
+ ["rect", { x: "8", y: "10", width: "8", height: "8", rx: "1" }]
+ ];
+
+ const Carrot = [
+ [
+ "path",
+ {
+ d: "M2.27 21.7s9.87-3.5 12.73-6.36a4.5 4.5 0 0 0-6.36-6.37C5.77 11.84 2.27 21.7 2.27 21.7zM8.64 14l-2.05-2.04M15.34 15l-2.46-2.46"
+ }
+ ],
+ ["path", { d: "M22 9s-1.33-2-3.5-2C16.86 7 15 9 15 9s1.33 2 3.5 2S22 9 22 9z" }],
+ ["path", { d: "M15 2s-2 1.33-2 3.5S15 9 15 9s2-1.84 2-3.5C17 3.33 15 2 15 2z" }]
+ ];
+
+ const CaseLower = [
+ ["path", { d: "M10 9v7" }],
+ ["path", { d: "M14 6v10" }],
+ ["circle", { cx: "17.5", cy: "12.5", r: "3.5" }],
+ ["circle", { cx: "6.5", cy: "12.5", r: "3.5" }]
+ ];
+
+ const CaseSensitive = [
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" }],
+ ["path", { d: "M22 9v7" }],
+ ["path", { d: "M3.304 13h6.392" }],
+ ["circle", { cx: "18.5", cy: "12.5", r: "3.5" }]
+ ];
+
+ const CaseUpper = [
+ [
+ "path",
+ { d: "M15 11h4.5a1 1 0 0 1 0 5h-4a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h3a1 1 0 0 1 0 5" }
+ ],
+ ["path", { d: "m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" }],
+ ["path", { d: "M3.304 13h6.392" }]
+ ];
+
+ const CassetteTape = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["circle", { cx: "8", cy: "10", r: "2" }],
+ ["path", { d: "M8 12h8" }],
+ ["circle", { cx: "16", cy: "10", r: "2" }],
+ ["path", { d: "m6 20 .7-2.9A1.4 1.4 0 0 1 8.1 16h7.8a1.4 1.4 0 0 1 1.4 1l.7 3" }]
+ ];
+
+ const Cast = [
+ ["path", { d: "M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6" }],
+ ["path", { d: "M2 12a9 9 0 0 1 8 8" }],
+ ["path", { d: "M2 16a5 5 0 0 1 4 4" }],
+ ["line", { x1: "2", x2: "2.01", y1: "20", y2: "20" }]
+ ];
+
+ const Castle = [
+ ["path", { d: "M10 5V3" }],
+ ["path", { d: "M14 5V3" }],
+ ["path", { d: "M15 21v-3a3 3 0 0 0-6 0v3" }],
+ ["path", { d: "M18 3v8" }],
+ ["path", { d: "M18 5H6" }],
+ ["path", { d: "M22 11H2" }],
+ ["path", { d: "M22 9v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9" }],
+ ["path", { d: "M6 3v8" }]
+ ];
+
+ const Cat = [
+ [
+ "path",
+ {
+ d: "M12 5c.67 0 1.35.09 2 .26 1.78-2 5.03-2.84 6.42-2.26 1.4.58-.42 7-.42 7 .57 1.07 1 2.24 1 3.44C21 17.9 16.97 21 12 21s-9-3-9-7.56c0-1.25.5-2.4 1-3.44 0 0-1.89-6.42-.5-7 1.39-.58 4.72.23 6.5 2.23A9.04 9.04 0 0 1 12 5Z"
+ }
+ ],
+ ["path", { d: "M8 14v.5" }],
+ ["path", { d: "M16 14v.5" }],
+ ["path", { d: "M11.25 16.25h1.5L12 17l-.75-.75Z" }]
+ ];
+
+ const Cctv = [
+ [
+ "path",
+ { d: "M16.75 12h3.632a1 1 0 0 1 .894 1.447l-2.034 4.069a1 1 0 0 1-1.708.134l-2.124-2.97" }
+ ],
+ [
+ "path",
+ {
+ d: "M17.106 9.053a1 1 0 0 1 .447 1.341l-3.106 6.211a1 1 0 0 1-1.342.447L3.61 12.3a2.92 2.92 0 0 1-1.3-3.91L3.69 5.6a2.92 2.92 0 0 1 3.92-1.3z"
+ }
+ ],
+ ["path", { d: "M2 19h3.76a2 2 0 0 0 1.8-1.1L9 15" }],
+ ["path", { d: "M2 21v-4" }],
+ ["path", { d: "M7 9h.01" }]
+ ];
+
+ const ChartArea = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ [
+ "path",
+ {
+ d: "M7 11.207a.5.5 0 0 1 .146-.353l2-2a.5.5 0 0 1 .708 0l3.292 3.292a.5.5 0 0 0 .708 0l4.292-4.292a.5.5 0 0 1 .854.353V16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1z"
+ }
+ ]
+ ];
+
+ const ChartBarBig = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["rect", { x: "7", y: "13", width: "9", height: "4", rx: "1" }],
+ ["rect", { x: "7", y: "5", width: "12", height: "4", rx: "1" }]
+ ];
+
+ const ChartBarDecreasing = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M7 11h8" }],
+ ["path", { d: "M7 16h3" }],
+ ["path", { d: "M7 6h12" }]
+ ];
+
+ const ChartBarIncreasing = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M7 11h8" }],
+ ["path", { d: "M7 16h12" }],
+ ["path", { d: "M7 6h3" }]
+ ];
+
+ const ChartBarStacked = [
+ ["path", { d: "M11 13v4" }],
+ ["path", { d: "M15 5v4" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["rect", { x: "7", y: "13", width: "9", height: "4", rx: "1" }],
+ ["rect", { x: "7", y: "5", width: "12", height: "4", rx: "1" }]
+ ];
+
+ const ChartBar = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M7 16h8" }],
+ ["path", { d: "M7 11h12" }],
+ ["path", { d: "M7 6h3" }]
+ ];
+
+ const ChartCandlestick = [
+ ["path", { d: "M9 5v4" }],
+ ["rect", { width: "4", height: "6", x: "7", y: "9", rx: "1" }],
+ ["path", { d: "M9 15v2" }],
+ ["path", { d: "M17 3v2" }],
+ ["rect", { width: "4", height: "8", x: "15", y: "5", rx: "1" }],
+ ["path", { d: "M17 13v3" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }]
+ ];
+
+ const ChartColumnBig = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["rect", { x: "15", y: "5", width: "4", height: "12", rx: "1" }],
+ ["rect", { x: "7", y: "8", width: "4", height: "9", rx: "1" }]
+ ];
+
+ const ChartColumnDecreasing = [
+ ["path", { d: "M13 17V9" }],
+ ["path", { d: "M18 17v-3" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M8 17V5" }]
+ ];
+
+ const ChartColumnIncreasing = [
+ ["path", { d: "M13 17V9" }],
+ ["path", { d: "M18 17V5" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M8 17v-3" }]
+ ];
+
+ const ChartColumnStacked = [
+ ["path", { d: "M11 13H7" }],
+ ["path", { d: "M19 9h-4" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["rect", { x: "15", y: "5", width: "4", height: "12", rx: "1" }],
+ ["rect", { x: "7", y: "8", width: "4", height: "9", rx: "1" }]
+ ];
+
+ const ChartColumn = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M18 17V9" }],
+ ["path", { d: "M13 17V5" }],
+ ["path", { d: "M8 17v-3" }]
+ ];
+
+ const ChartGantt = [
+ ["path", { d: "M10 6h8" }],
+ ["path", { d: "M12 16h6" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M8 11h7" }]
+ ];
+
+ const ChartLine = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "m19 9-5 5-4-4-3 3" }]
+ ];
+
+ const ChartNetwork = [
+ ["path", { d: "m13.11 7.664 1.78 2.672" }],
+ ["path", { d: "m14.162 12.788-3.324 1.424" }],
+ ["path", { d: "m20 4-6.06 1.515" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["circle", { cx: "12", cy: "6", r: "2" }],
+ ["circle", { cx: "16", cy: "12", r: "2" }],
+ ["circle", { cx: "9", cy: "15", r: "2" }]
+ ];
+
+ const ChartNoAxesColumnDecreasing = [
+ ["path", { d: "M5 21V3" }],
+ ["path", { d: "M12 21V9" }],
+ ["path", { d: "M19 21v-6" }]
+ ];
+
+ const ChartNoAxesColumnIncreasing = [
+ ["path", { d: "M5 21v-6" }],
+ ["path", { d: "M12 21V9" }],
+ ["path", { d: "M19 21V3" }]
+ ];
+
+ const ChartNoAxesColumn = [
+ ["path", { d: "M5 21v-6" }],
+ ["path", { d: "M12 21V3" }],
+ ["path", { d: "M19 21V9" }]
+ ];
+
+ const ChartNoAxesCombined = [
+ ["path", { d: "M12 16v5" }],
+ ["path", { d: "M16 14v7" }],
+ ["path", { d: "M20 10v11" }],
+ ["path", { d: "m22 3-8.646 8.646a.5.5 0 0 1-.708 0L9.354 8.354a.5.5 0 0 0-.707 0L2 15" }],
+ ["path", { d: "M4 18v3" }],
+ ["path", { d: "M8 14v7" }]
+ ];
+
+ const ChartNoAxesGantt = [
+ ["path", { d: "M6 5h12" }],
+ ["path", { d: "M4 12h10" }],
+ ["path", { d: "M12 19h8" }]
+ ];
+
+ const ChartPie = [
+ [
+ "path",
+ {
+ d: "M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z"
+ }
+ ],
+ ["path", { d: "M21.21 15.89A10 10 0 1 1 8 2.83" }]
+ ];
+
+ const ChartScatter = [
+ ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "18.5", cy: "5.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "11.5", cy: "11.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "7.5", cy: "16.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "17.5", cy: "14.5", r: ".5", fill: "currentColor" }],
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }]
+ ];
+
+ const ChartSpline = [
+ ["path", { d: "M3 3v16a2 2 0 0 0 2 2h16" }],
+ ["path", { d: "M7 16c.5-2 1.5-7 4-7 2 0 2 3 4 3 2.5 0 4.5-5 5-7" }]
+ ];
+
+ const CheckCheck = [
+ ["path", { d: "M18 6 7 17l-5-5" }],
+ ["path", { d: "m22 10-7.5 7.5L13 16" }]
+ ];
+
+ const CheckLine = [
+ ["path", { d: "M20 4L9 15" }],
+ ["path", { d: "M21 19L3 19" }],
+ ["path", { d: "M9 15L4 10" }]
+ ];
+
+ const Check = [["path", { d: "M20 6 9 17l-5-5" }]];
+
+ const ChefHat = [
+ [
+ "path",
+ {
+ d: "M17 21a1 1 0 0 0 1-1v-5.35c0-.457.316-.844.727-1.041a4 4 0 0 0-2.134-7.589 5 5 0 0 0-9.186 0 4 4 0 0 0-2.134 7.588c.411.198.727.585.727 1.041V20a1 1 0 0 0 1 1Z"
+ }
+ ],
+ ["path", { d: "M6 17h12" }]
+ ];
+
+ const Cherry = [
+ ["path", { d: "M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z" }],
+ ["path", { d: "M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z" }],
+ ["path", { d: "M7 14c3.22-2.91 4.29-8.75 5-12 1.66 2.38 4.94 9 5 12" }],
+ ["path", { d: "M22 9c-4.29 0-7.14-2.33-10-7 5.71 0 10 4.67 10 7Z" }]
+ ];
+
+ const ChevronDown = [["path", { d: "m6 9 6 6 6-6" }]];
+
+ const ChevronFirst = [
+ ["path", { d: "m17 18-6-6 6-6" }],
+ ["path", { d: "M7 6v12" }]
+ ];
+
+ const ChevronLast = [
+ ["path", { d: "m7 18 6-6-6-6" }],
+ ["path", { d: "M17 6v12" }]
+ ];
+
+ const ChevronLeft = [["path", { d: "m15 18-6-6 6-6" }]];
+
+ const ChevronRight = [["path", { d: "m9 18 6-6-6-6" }]];
+
+ const ChevronUp = [["path", { d: "m18 15-6-6-6 6" }]];
+
+ const ChevronsDownUp = [
+ ["path", { d: "m7 20 5-5 5 5" }],
+ ["path", { d: "m7 4 5 5 5-5" }]
+ ];
+
+ const ChevronsDown = [
+ ["path", { d: "m7 6 5 5 5-5" }],
+ ["path", { d: "m7 13 5 5 5-5" }]
+ ];
+
+ const ChevronsLeftRightEllipsis = [
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M16 12h.01" }],
+ ["path", { d: "m17 7 5 5-5 5" }],
+ ["path", { d: "m7 7-5 5 5 5" }],
+ ["path", { d: "M8 12h.01" }]
+ ];
+
+ const ChevronsLeftRight = [
+ ["path", { d: "m9 7-5 5 5 5" }],
+ ["path", { d: "m15 7 5 5-5 5" }]
+ ];
+
+ const ChevronsLeft = [
+ ["path", { d: "m11 17-5-5 5-5" }],
+ ["path", { d: "m18 17-5-5 5-5" }]
+ ];
+
+ const ChevronsRightLeft = [
+ ["path", { d: "m20 17-5-5 5-5" }],
+ ["path", { d: "m4 17 5-5-5-5" }]
+ ];
+
+ const ChevronsRight = [
+ ["path", { d: "m6 17 5-5-5-5" }],
+ ["path", { d: "m13 17 5-5-5-5" }]
+ ];
+
+ const ChevronsUpDown = [
+ ["path", { d: "m7 15 5 5 5-5" }],
+ ["path", { d: "m7 9 5-5 5 5" }]
+ ];
+
+ const ChevronsUp = [
+ ["path", { d: "m17 11-5-5-5 5" }],
+ ["path", { d: "m17 18-5-5-5 5" }]
+ ];
+
+ const Chromium = [
+ ["path", { d: "M10.88 21.94 15.46 14" }],
+ ["path", { d: "M21.17 8H12" }],
+ ["path", { d: "M3.95 6.06 8.54 14" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "12", r: "4" }]
+ ];
+
+ const Church = [
+ ["path", { d: "M10 9h4" }],
+ ["path", { d: "M12 7v5" }],
+ ["path", { d: "M14 21v-3a2 2 0 0 0-4 0v3" }],
+ [
+ "path",
+ {
+ d: "m18 9 3.52 2.147a1 1 0 0 1 .48.854V19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6.999a1 1 0 0 1 .48-.854L6 9"
+ }
+ ],
+ ["path", { d: "M6 21V7a1 1 0 0 1 .376-.782l5-3.999a1 1 0 0 1 1.249.001l5 4A1 1 0 0 1 18 7v14" }]
+ ];
+
+ const CigaretteOff = [
+ ["path", { d: "M12 12H3a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h13" }],
+ ["path", { d: "M18 8c0-2.5-2-2.5-2-5" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M21 12a1 1 0 0 1 1 1v2a1 1 0 0 1-.5.866" }],
+ ["path", { d: "M22 8c0-2.5-2-2.5-2-5" }],
+ ["path", { d: "M7 12v4" }]
+ ];
+
+ const Cigarette = [
+ ["path", { d: "M17 12H3a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h14" }],
+ ["path", { d: "M18 8c0-2.5-2-2.5-2-5" }],
+ ["path", { d: "M21 16a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1" }],
+ ["path", { d: "M22 8c0-2.5-2-2.5-2-5" }],
+ ["path", { d: "M7 12v4" }]
+ ];
+
+ const CircleAlert = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12" }],
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16" }]
+ ];
+
+ const CircleArrowDown = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M12 8v8" }],
+ ["path", { d: "m8 12 4 4 4-4" }]
+ ];
+
+ const CircleArrowLeft = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m12 8-4 4 4 4" }],
+ ["path", { d: "M16 12H8" }]
+ ];
+
+ const CircleArrowOutDownLeft = [
+ ["path", { d: "M2 12a10 10 0 1 1 10 10" }],
+ ["path", { d: "m2 22 10-10" }],
+ ["path", { d: "M8 22H2v-6" }]
+ ];
+
+ const CircleArrowOutDownRight = [
+ ["path", { d: "M12 22a10 10 0 1 1 10-10" }],
+ ["path", { d: "M22 22 12 12" }],
+ ["path", { d: "M22 16v6h-6" }]
+ ];
+
+ const CircleArrowOutUpLeft = [
+ ["path", { d: "M2 8V2h6" }],
+ ["path", { d: "m2 2 10 10" }],
+ ["path", { d: "M12 2A10 10 0 1 1 2 12" }]
+ ];
+
+ const CircleArrowOutUpRight = [
+ ["path", { d: "M22 12A10 10 0 1 1 12 2" }],
+ ["path", { d: "M22 2 12 12" }],
+ ["path", { d: "M16 2h6v6" }]
+ ];
+
+ const CircleArrowRight = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m12 16 4-4-4-4" }],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const CircleArrowUp = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m16 12-4-4-4 4" }],
+ ["path", { d: "M12 16V8" }]
+ ];
+
+ const CircleCheckBig = [
+ ["path", { d: "M21.801 10A10 10 0 1 1 17 3.335" }],
+ ["path", { d: "m9 11 3 3L22 4" }]
+ ];
+
+ const CircleCheck = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m9 12 2 2 4-4" }]
+ ];
+
+ const CircleChevronDown = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m16 10-4 4-4-4" }]
+ ];
+
+ const CircleChevronLeft = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m14 16-4-4 4-4" }]
+ ];
+
+ const CircleChevronRight = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m10 8 4 4-4 4" }]
+ ];
+
+ const CircleChevronUp = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m8 14 4-4 4 4" }]
+ ];
+
+ const CircleDashed = [
+ ["path", { d: "M10.1 2.182a10 10 0 0 1 3.8 0" }],
+ ["path", { d: "M13.9 21.818a10 10 0 0 1-3.8 0" }],
+ ["path", { d: "M17.609 3.721a10 10 0 0 1 2.69 2.7" }],
+ ["path", { d: "M2.182 13.9a10 10 0 0 1 0-3.8" }],
+ ["path", { d: "M20.279 17.609a10 10 0 0 1-2.7 2.69" }],
+ ["path", { d: "M21.818 10.1a10 10 0 0 1 0 3.8" }],
+ ["path", { d: "M3.721 6.391a10 10 0 0 1 2.7-2.69" }],
+ ["path", { d: "M6.391 20.279a10 10 0 0 1-2.69-2.7" }]
+ ];
+
+ const CircleDivide = [
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12" }],
+ ["line", { x1: "12", x2: "12", y1: "16", y2: "16" }],
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "8" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleDollarSign = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8" }],
+ ["path", { d: "M12 18V6" }]
+ ];
+
+ const CircleDotDashed = [
+ ["path", { d: "M10.1 2.18a9.93 9.93 0 0 1 3.8 0" }],
+ ["path", { d: "M17.6 3.71a9.95 9.95 0 0 1 2.69 2.7" }],
+ ["path", { d: "M21.82 10.1a9.93 9.93 0 0 1 0 3.8" }],
+ ["path", { d: "M20.29 17.6a9.95 9.95 0 0 1-2.7 2.69" }],
+ ["path", { d: "M13.9 21.82a9.94 9.94 0 0 1-3.8 0" }],
+ ["path", { d: "M6.4 20.29a9.95 9.95 0 0 1-2.69-2.7" }],
+ ["path", { d: "M2.18 13.9a9.93 9.93 0 0 1 0-3.8" }],
+ ["path", { d: "M3.71 6.4a9.95 9.95 0 0 1 2.7-2.69" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }]
+ ];
+
+ const CircleDot = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }]
+ ];
+
+ const CircleEllipsis = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M17 12h.01" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M7 12h.01" }]
+ ];
+
+ const CircleFadingArrowUp = [
+ ["path", { d: "M12 2a10 10 0 0 1 7.38 16.75" }],
+ ["path", { d: "m16 12-4-4-4 4" }],
+ ["path", { d: "M12 16V8" }],
+ ["path", { d: "M2.5 8.875a10 10 0 0 0-.5 3" }],
+ ["path", { d: "M2.83 16a10 10 0 0 0 2.43 3.4" }],
+ ["path", { d: "M4.636 5.235a10 10 0 0 1 .891-.857" }],
+ ["path", { d: "M8.644 21.42a10 10 0 0 0 7.631-.38" }]
+ ];
+
+ const CircleEqual = [
+ ["path", { d: "M7 10h10" }],
+ ["path", { d: "M7 14h10" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleFadingPlus = [
+ ["path", { d: "M12 2a10 10 0 0 1 7.38 16.75" }],
+ ["path", { d: "M12 8v8" }],
+ ["path", { d: "M16 12H8" }],
+ ["path", { d: "M2.5 8.875a10 10 0 0 0-.5 3" }],
+ ["path", { d: "M2.83 16a10 10 0 0 0 2.43 3.4" }],
+ ["path", { d: "M4.636 5.235a10 10 0 0 1 .891-.857" }],
+ ["path", { d: "M8.644 21.42a10 10 0 0 0 7.631-.38" }]
+ ];
+
+ const CircleGauge = [
+ ["path", { d: "M15.6 2.7a10 10 0 1 0 5.7 5.7" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["path", { d: "M13.4 10.6 19 5" }]
+ ];
+
+ const CircleMinus = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const CircleOff = [
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.35 2.69A10 10 0 0 1 21.3 15.65" }],
+ ["path", { d: "M19.08 19.08A10 10 0 1 1 4.92 4.92" }]
+ ];
+
+ const CircleParkingOff = [
+ ["path", { d: "M12.656 7H13a3 3 0 0 1 2.984 3.307" }],
+ ["path", { d: "M13 13H9" }],
+ ["path", { d: "M19.071 19.071A1 1 0 0 1 4.93 4.93" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.357 2.687a10 10 0 0 1 12.956 12.956" }],
+ ["path", { d: "M9 17V9" }]
+ ];
+
+ const CircleParking = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M9 17V7h4a3 3 0 0 1 0 6H9" }]
+ ];
+
+ const CirclePause = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["line", { x1: "10", x2: "10", y1: "15", y2: "9" }],
+ ["line", { x1: "14", x2: "14", y1: "15", y2: "9" }]
+ ];
+
+ const CirclePercent = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "M9 9h.01" }],
+ ["path", { d: "M15 15h.01" }]
+ ];
+
+ const CirclePlay = [
+ [
+ "path",
+ {
+ d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CirclePlus = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "M12 8v8" }]
+ ];
+
+ const CirclePoundSterling = [
+ ["path", { d: "M10 16V9.5a1 1 0 0 1 5 0" }],
+ ["path", { d: "M8 12h4" }],
+ ["path", { d: "M8 16h7" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CirclePower = [
+ ["path", { d: "M12 7v4" }],
+ ["path", { d: "M7.998 9.003a5 5 0 1 0 8-.005" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleQuestionMark = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }],
+ ["path", { d: "M12 17h.01" }]
+ ];
+
+ const CircleSlash2 = [
+ ["path", { d: "M22 2 2 22" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleSlash = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["line", { x1: "9", x2: "15", y1: "15", y2: "9" }]
+ ];
+
+ const CircleSmall = [["circle", { cx: "12", cy: "12", r: "6" }]];
+
+ const CircleStar = [
+ [
+ "path",
+ {
+ d: "M11.051 7.616a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.867l-1.156-1.152a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleStop = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" }]
+ ];
+
+ const CircleUserRound = [
+ ["path", { d: "M18 20a6 6 0 0 0-12 0" }],
+ ["circle", { cx: "12", cy: "10", r: "4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const CircleUser = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662" }]
+ ];
+
+ const CircleX = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "m9 9 6 6" }]
+ ];
+
+ const Circle = [["circle", { cx: "12", cy: "12", r: "10" }]];
+
+ const CircuitBoard = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M11 9h4a2 2 0 0 0 2-2V3" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }],
+ ["path", { d: "M7 21v-4a2 2 0 0 1 2-2h4" }],
+ ["circle", { cx: "15", cy: "15", r: "2" }]
+ ];
+
+ const Citrus = [
+ [
+ "path",
+ { d: "M21.66 17.67a1.08 1.08 0 0 1-.04 1.6A12 12 0 0 1 4.73 2.38a1.1 1.1 0 0 1 1.61-.04z" }
+ ],
+ ["path", { d: "M19.65 15.66A8 8 0 0 1 8.35 4.34" }],
+ ["path", { d: "m14 10-5.5 5.5" }],
+ ["path", { d: "M14 17.85V10H6.15" }]
+ ];
+
+ const Clapperboard = [
+ ["path", { d: "M20.2 6 3 11l-.9-2.4c-.3-1.1.3-2.2 1.3-2.5l13.5-4c1.1-.3 2.2.3 2.5 1.3Z" }],
+ ["path", { d: "m6.2 5.3 3.1 3.9" }],
+ ["path", { d: "m12.4 3.4 3.1 4" }],
+ ["path", { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" }]
+ ];
+
+ const ClipboardCheck = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "m9 14 2 2 4-4" }]
+ ];
+
+ const ClipboardClock = [
+ ["path", { d: "M16 14v2.2l1.6 1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v.832" }],
+ ["path", { d: "M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2" }],
+ ["circle", { cx: "16", cy: "16", r: "6" }],
+ ["rect", { x: "8", y: "2", width: "8", height: "4", rx: "1" }]
+ ];
+
+ const ClipboardCopy = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v4" }],
+ ["path", { d: "M21 14H11" }],
+ ["path", { d: "m15 10-4 4 4 4" }]
+ ];
+
+ const ClipboardList = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M12 11h4" }],
+ ["path", { d: "M12 16h4" }],
+ ["path", { d: "M8 11h.01" }],
+ ["path", { d: "M8 16h.01" }]
+ ];
+
+ const ClipboardMinus = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M9 14h6" }]
+ ];
+
+ const ClipboardPaste = [
+ ["path", { d: "M11 14h10" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v1.344" }],
+ ["path", { d: "m17 18 4-4-4-4" }],
+ ["path", { d: "M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" }],
+ ["rect", { x: "8", y: "2", width: "8", height: "4", rx: "1" }]
+ ];
+
+ const ClipboardPenLine = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1" }],
+ ["path", { d: "M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-.5" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 1.73 1" }],
+ ["path", { d: "M8 18h1" }],
+ [
+ "path",
+ {
+ d: "M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ]
+ ];
+
+ const ClipboardPen = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5.5" }],
+ ["path", { d: "M4 13.5V6a2 2 0 0 1 2-2h2" }],
+ [
+ "path",
+ {
+ d: "M13.378 15.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ]
+ ];
+
+ const ClipboardPlus = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M9 14h6" }],
+ ["path", { d: "M12 17v-6" }]
+ ];
+
+ const ClipboardType = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M9 12v-1h6v1" }],
+ ["path", { d: "M11 17h2" }],
+ ["path", { d: "M12 11v6" }]
+ ];
+
+ const ClipboardX = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "m15 11-6 6" }],
+ ["path", { d: "m9 11 6 6" }]
+ ];
+
+ const Clipboard = [
+ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }]
+ ];
+
+ const Clock1 = [
+ ["path", { d: "M12 6v6l2-4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock10 = [
+ ["path", { d: "M12 6v6l-4-2" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock11 = [
+ ["path", { d: "M12 6v6l-2-4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock12 = [
+ ["path", { d: "M12 6v6" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock2 = [
+ ["path", { d: "M12 6v6l4-2" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock3 = [
+ ["path", { d: "M12 6v6h4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock5 = [
+ ["path", { d: "M12 6v6l2 4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock4 = [
+ ["path", { d: "M12 6v6l4 2" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock6 = [
+ ["path", { d: "M12 6v10" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock7 = [
+ ["path", { d: "M12 6v6l-2 4" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock8 = [
+ ["path", { d: "M12 6v6l-4 2" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Clock9 = [
+ ["path", { d: "M12 6v6H8" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const ClockAlert = [
+ ["path", { d: "M12 6v6l4 2" }],
+ ["path", { d: "M20 12v5" }],
+ ["path", { d: "M20 21h.01" }],
+ ["path", { d: "M21.25 8.2A10 10 0 1 0 16 21.16" }]
+ ];
+
+ const ClockArrowDown = [
+ ["path", { d: "M12 6v6l2 1" }],
+ ["path", { d: "M12.337 21.994a10 10 0 1 1 9.588-8.767" }],
+ ["path", { d: "m14 18 4 4 4-4" }],
+ ["path", { d: "M18 14v8" }]
+ ];
+
+ const ClockArrowUp = [
+ ["path", { d: "M12 6v6l1.56.78" }],
+ ["path", { d: "M13.227 21.925a10 10 0 1 1 8.767-9.588" }],
+ ["path", { d: "m14 18 4-4 4 4" }],
+ ["path", { d: "M18 22v-8" }]
+ ];
+
+ const ClockFading = [
+ ["path", { d: "M12 2a10 10 0 0 1 7.38 16.75" }],
+ ["path", { d: "M12 6v6l4 2" }],
+ ["path", { d: "M2.5 8.875a10 10 0 0 0-.5 3" }],
+ ["path", { d: "M2.83 16a10 10 0 0 0 2.43 3.4" }],
+ ["path", { d: "M4.636 5.235a10 10 0 0 1 .891-.857" }],
+ ["path", { d: "M8.644 21.42a10 10 0 0 0 7.631-.38" }]
+ ];
+
+ const ClockPlus = [
+ ["path", { d: "M12 6v6l3.644 1.822" }],
+ ["path", { d: "M16 19h6" }],
+ ["path", { d: "M19 16v6" }],
+ ["path", { d: "M21.92 13.267a10 10 0 1 0-8.653 8.653" }]
+ ];
+
+ const Clock = [
+ ["path", { d: "M12 6v6l4 2" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const ClosedCaption = [
+ ["path", { d: "M10 9.17a3 3 0 1 0 0 5.66" }],
+ ["path", { d: "M17 9.17a3 3 0 1 0 0 5.66" }],
+ ["rect", { x: "2", y: "5", width: "20", height: "14", rx: "2" }]
+ ];
+
+ const CloudAlert = [
+ ["path", { d: "M12 12v4" }],
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M17 18h.5a1 1 0 0 0 0-9h-1.79A7 7 0 1 0 7 17.708" }]
+ ];
+
+ const CloudCheck = [
+ ["path", { d: "m17 15-5.5 5.5L9 18" }],
+ ["path", { d: "M5 17.743A7 7 0 1 1 15.71 10h1.79a4.5 4.5 0 0 1 1.5 8.742" }]
+ ];
+
+ const CloudCog = [
+ ["path", { d: "m10.852 19.772-.383.924" }],
+ ["path", { d: "m13.148 14.228.383-.923" }],
+ ["path", { d: "M13.148 19.772a3 3 0 1 0-2.296-5.544l-.383-.923" }],
+ ["path", { d: "m13.53 20.696-.382-.924a3 3 0 1 1-2.296-5.544" }],
+ ["path", { d: "m14.772 15.852.923-.383" }],
+ ["path", { d: "m14.772 18.148.923.383" }],
+ ["path", { d: "M4.2 15.1a7 7 0 1 1 9.93-9.858A7 7 0 0 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.2" }],
+ ["path", { d: "m9.228 15.852-.923-.383" }],
+ ["path", { d: "m9.228 18.148-.923.383" }]
+ ];
+
+ const CloudDownload = [
+ ["path", { d: "M12 13v8l-4-4" }],
+ ["path", { d: "m12 21 4-4" }],
+ ["path", { d: "M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284" }]
+ ];
+
+ const CloudDrizzle = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "M8 19v1" }],
+ ["path", { d: "M8 14v1" }],
+ ["path", { d: "M16 19v1" }],
+ ["path", { d: "M16 14v1" }],
+ ["path", { d: "M12 21v1" }],
+ ["path", { d: "M12 16v1" }]
+ ];
+
+ const CloudFog = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "M16 17H7" }],
+ ["path", { d: "M17 21H9" }]
+ ];
+
+ const CloudHail = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "M16 14v2" }],
+ ["path", { d: "M8 14v2" }],
+ ["path", { d: "M16 20h.01" }],
+ ["path", { d: "M8 20h.01" }],
+ ["path", { d: "M12 16v2" }],
+ ["path", { d: "M12 22h.01" }]
+ ];
+
+ const CloudLightning = [
+ ["path", { d: "M6 16.326A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 .5 8.973" }],
+ ["path", { d: "m13 12-3 5h4l-3 5" }]
+ ];
+
+ const CloudMoonRain = [
+ ["path", { d: "M11 20v2" }],
+ [
+ "path",
+ {
+ d: "M18.376 14.512a6 6 0 0 0 3.461-4.127c.148-.625-.659-.97-1.248-.714a4 4 0 0 1-5.259-5.26c.255-.589-.09-1.395-.716-1.248a6 6 0 0 0-4.594 5.36"
+ }
+ ],
+ ["path", { d: "M3 20a5 5 0 1 1 8.9-4H13a3 3 0 0 1 2 5.24" }],
+ ["path", { d: "M7 19v2" }]
+ ];
+
+ const CloudMoon = [
+ ["path", { d: "M13 16a3 3 0 0 1 0 6H7a5 5 0 1 1 4.9-6z" }],
+ [
+ "path",
+ {
+ d: "M18.376 14.512a6 6 0 0 0 3.461-4.127c.148-.625-.659-.97-1.248-.714a4 4 0 0 1-5.259-5.26c.255-.589-.09-1.395-.716-1.248a6 6 0 0 0-4.594 5.36"
+ }
+ ]
+ ];
+
+ const CloudOff = [
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M5.782 5.782A7 7 0 0 0 9 19h8.5a4.5 4.5 0 0 0 1.307-.193" }],
+ ["path", { d: "M21.532 16.5A4.5 4.5 0 0 0 17.5 10h-1.79A7.008 7.008 0 0 0 10 5.07" }]
+ ];
+
+ const CloudRainWind = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "m9.2 22 3-7" }],
+ ["path", { d: "m9 13-3 7" }],
+ ["path", { d: "m17 13-3 7" }]
+ ];
+
+ const CloudRain = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "M16 14v6" }],
+ ["path", { d: "M8 14v6" }],
+ ["path", { d: "M12 16v6" }]
+ ];
+
+ const CloudSnow = [
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "M8 15h.01" }],
+ ["path", { d: "M8 19h.01" }],
+ ["path", { d: "M12 17h.01" }],
+ ["path", { d: "M12 21h.01" }],
+ ["path", { d: "M16 15h.01" }],
+ ["path", { d: "M16 19h.01" }]
+ ];
+
+ const CloudSunRain = [
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "m4.93 4.93 1.41 1.41" }],
+ ["path", { d: "M20 12h2" }],
+ ["path", { d: "m19.07 4.93-1.41 1.41" }],
+ ["path", { d: "M15.947 12.65a4 4 0 0 0-5.925-4.128" }],
+ ["path", { d: "M3 20a5 5 0 1 1 8.9-4H13a3 3 0 0 1 2 5.24" }],
+ ["path", { d: "M11 20v2" }],
+ ["path", { d: "M7 19v2" }]
+ ];
+
+ const CloudSun = [
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "m4.93 4.93 1.41 1.41" }],
+ ["path", { d: "M20 12h2" }],
+ ["path", { d: "m19.07 4.93-1.41 1.41" }],
+ ["path", { d: "M15.947 12.65a4 4 0 0 0-5.925-4.128" }],
+ ["path", { d: "M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z" }]
+ ];
+
+ const CloudUpload = [
+ ["path", { d: "M12 13v8" }],
+ ["path", { d: "M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" }],
+ ["path", { d: "m8 17 4-4 4 4" }]
+ ];
+
+ const Cloud = [["path", { d: "M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" }]];
+
+ const Cloudy = [
+ ["path", { d: "M17.5 21H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" }],
+ ["path", { d: "M22 10a3 3 0 0 0-3-3h-2.207a5.502 5.502 0 0 0-10.702.5" }]
+ ];
+
+ const Clover = [
+ ["path", { d: "M16.17 7.83 2 22" }],
+ [
+ "path",
+ {
+ d: "M4.02 12a2.827 2.827 0 1 1 3.81-4.17A2.827 2.827 0 1 1 12 4.02a2.827 2.827 0 1 1 4.17 3.81A2.827 2.827 0 1 1 19.98 12a2.827 2.827 0 1 1-3.81 4.17A2.827 2.827 0 1 1 12 19.98a2.827 2.827 0 1 1-4.17-3.81A1 1 0 1 1 4 12"
+ }
+ ],
+ ["path", { d: "m7.83 7.83 8.34 8.34" }]
+ ];
+
+ const Club = [
+ [
+ "path",
+ { d: "M17.28 9.05a5.5 5.5 0 1 0-10.56 0A5.5 5.5 0 1 0 12 17.66a5.5 5.5 0 1 0 5.28-8.6Z" }
+ ],
+ ["path", { d: "M12 17.66L12 22" }]
+ ];
+
+ const CodeXml = [
+ ["path", { d: "m18 16 4-4-4-4" }],
+ ["path", { d: "m6 8-4 4 4 4" }],
+ ["path", { d: "m14.5 4-5 16" }]
+ ];
+
+ const Code = [
+ ["path", { d: "m16 18 6-6-6-6" }],
+ ["path", { d: "m8 6-6 6 6 6" }]
+ ];
+
+ const Codepen = [
+ ["polygon", { points: "12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "15.5" }],
+ ["polyline", { points: "22 8.5 12 15.5 2 8.5" }],
+ ["polyline", { points: "2 15.5 12 8.5 22 15.5" }],
+ ["line", { x1: "12", x2: "12", y1: "2", y2: "8.5" }]
+ ];
+
+ const Codesandbox = [
+ [
+ "path",
+ {
+ d: "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
+ }
+ ],
+ ["polyline", { points: "7.5 4.21 12 6.81 16.5 4.21" }],
+ ["polyline", { points: "7.5 19.79 7.5 14.6 3 12" }],
+ ["polyline", { points: "21 12 16.5 14.6 16.5 19.79" }],
+ ["polyline", { points: "3.27 6.96 12 12.01 20.73 6.96" }],
+ ["line", { x1: "12", x2: "12", y1: "22.08", y2: "12" }]
+ ];
+
+ const Coffee = [
+ ["path", { d: "M10 2v2" }],
+ ["path", { d: "M14 2v2" }],
+ [
+ "path",
+ {
+ d: "M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1"
+ }
+ ],
+ ["path", { d: "M6 2v2" }]
+ ];
+
+ const Coins = [
+ ["circle", { cx: "8", cy: "8", r: "6" }],
+ ["path", { d: "M18.09 10.37A6 6 0 1 1 10.34 18" }],
+ ["path", { d: "M7 6h1v4" }],
+ ["path", { d: "m16.71 13.88.7.71-2.82 2.82" }]
+ ];
+
+ const Cog = [
+ ["path", { d: "M11 10.27 7 3.34" }],
+ ["path", { d: "m11 13.73-4 6.93" }],
+ ["path", { d: "M12 22v-2" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M14 12h8" }],
+ ["path", { d: "m17 20.66-1-1.73" }],
+ ["path", { d: "m17 3.34-1 1.73" }],
+ ["path", { d: "M2 12h2" }],
+ ["path", { d: "m20.66 17-1.73-1" }],
+ ["path", { d: "m20.66 7-1.73 1" }],
+ ["path", { d: "m3.34 17 1.73-1" }],
+ ["path", { d: "m3.34 7 1.73 1" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["circle", { cx: "12", cy: "12", r: "8" }]
+ ];
+
+ const Columns2 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M12 3v18" }]
+ ];
+
+ const Columns3Cog = [
+ ["path", { d: "M10.5 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.5" }],
+ ["path", { d: "m14.3 19.6 1-.4" }],
+ ["path", { d: "M15 3v7.5" }],
+ ["path", { d: "m15.2 16.9-.9-.3" }],
+ ["path", { d: "m16.6 21.7.3-.9" }],
+ ["path", { d: "m16.8 15.3-.4-1" }],
+ ["path", { d: "m19.1 15.2.3-.9" }],
+ ["path", { d: "m19.6 21.7-.4-1" }],
+ ["path", { d: "m20.7 16.8 1-.4" }],
+ ["path", { d: "m21.7 19.4-.9-.3" }],
+ ["path", { d: "M9 3v18" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const Columns3 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 3v18" }],
+ ["path", { d: "M15 3v18" }]
+ ];
+
+ const Columns4 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7.5 3v18" }],
+ ["path", { d: "M12 3v18" }],
+ ["path", { d: "M16.5 3v18" }]
+ ];
+
+ const Combine = [
+ ["path", { d: "M14 3a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1" }],
+ ["path", { d: "M19 3a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1" }],
+ ["path", { d: "m7 15 3 3" }],
+ ["path", { d: "m7 21 3-3H5a2 2 0 0 1-2-2v-2" }],
+ ["rect", { x: "14", y: "14", width: "7", height: "7", rx: "1" }],
+ ["rect", { x: "3", y: "3", width: "7", height: "7", rx: "1" }]
+ ];
+
+ const Compass = [
+ [
+ "path",
+ {
+ d: "m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Command = [
+ ["path", { d: "M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" }]
+ ];
+
+ const Component = [
+ [
+ "path",
+ {
+ d: "M15.536 11.293a1 1 0 0 0 0 1.414l2.376 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M2.297 11.293a1 1 0 0 0 0 1.414l2.377 2.377a1 1 0 0 0 1.414 0l2.377-2.377a1 1 0 0 0 0-1.414L6.088 8.916a1 1 0 0 0-1.414 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M8.916 17.912a1 1 0 0 0 0 1.415l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.415l-2.377-2.376a1 1 0 0 0-1.414 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M8.916 4.674a1 1 0 0 0 0 1.414l2.377 2.376a1 1 0 0 0 1.414 0l2.377-2.376a1 1 0 0 0 0-1.414l-2.377-2.377a1 1 0 0 0-1.414 0z"
+ }
+ ]
+ ];
+
+ const Computer = [
+ ["rect", { width: "14", height: "8", x: "5", y: "2", rx: "2" }],
+ ["rect", { width: "20", height: "8", x: "2", y: "14", rx: "2" }],
+ ["path", { d: "M6 18h2" }],
+ ["path", { d: "M12 18h6" }]
+ ];
+
+ const ConciergeBell = [
+ ["path", { d: "M3 20a1 1 0 0 1-1-1v-1a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1Z" }],
+ ["path", { d: "M20 16a8 8 0 1 0-16 0" }],
+ ["path", { d: "M12 4v4" }],
+ ["path", { d: "M10 4h4" }]
+ ];
+
+ const Cone = [
+ ["path", { d: "m20.9 18.55-8-15.98a1 1 0 0 0-1.8 0l-8 15.98" }],
+ ["ellipse", { cx: "12", cy: "19", rx: "9", ry: "3" }]
+ ];
+
+ const Construction = [
+ ["rect", { x: "2", y: "6", width: "20", height: "8", rx: "1" }],
+ ["path", { d: "M17 14v7" }],
+ ["path", { d: "M7 14v7" }],
+ ["path", { d: "M17 3v3" }],
+ ["path", { d: "M7 3v3" }],
+ ["path", { d: "M10 14 2.3 6.3" }],
+ ["path", { d: "m14 6 7.7 7.7" }],
+ ["path", { d: "m8 6 8 8" }]
+ ];
+
+ const ContactRound = [
+ ["path", { d: "M16 2v2" }],
+ ["path", { d: "M17.915 22a6 6 0 0 0-12 0" }],
+ ["path", { d: "M8 2v2" }],
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const Contact = [
+ ["path", { d: "M16 2v2" }],
+ ["path", { d: "M7 22v-2a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M8 2v2" }],
+ ["circle", { cx: "12", cy: "11", r: "3" }],
+ ["rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const Container = [
+ [
+ "path",
+ {
+ d: "M22 7.7c0-.6-.4-1.2-.8-1.5l-6.3-3.9a1.72 1.72 0 0 0-1.7 0l-10.3 6c-.5.2-.9.8-.9 1.4v6.6c0 .5.4 1.2.8 1.5l6.3 3.9a1.72 1.72 0 0 0 1.7 0l10.3-6c.5-.3.9-1 .9-1.5Z"
+ }
+ ],
+ ["path", { d: "M10 21.9V14L2.1 9.1" }],
+ ["path", { d: "m10 14 11.9-6.9" }],
+ ["path", { d: "M14 19.8v-8.1" }],
+ ["path", { d: "M18 17.5V9.4" }]
+ ];
+
+ const Contrast = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M12 18a6 6 0 0 0 0-12v12z" }]
+ ];
+
+ const Cookie = [
+ ["path", { d: "M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5" }],
+ ["path", { d: "M8.5 8.5v.01" }],
+ ["path", { d: "M16 15.5v.01" }],
+ ["path", { d: "M12 12v.01" }],
+ ["path", { d: "M11 17v.01" }],
+ ["path", { d: "M7 14v.01" }]
+ ];
+
+ const CookingPot = [
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8" }],
+ ["path", { d: "m4 8 16-4" }],
+ ["path", { d: "m8.86 6.78-.45-1.81a2 2 0 0 1 1.45-2.43l1.94-.48a2 2 0 0 1 2.43 1.46l.45 1.8" }]
+ ];
+
+ const CopyCheck = [
+ ["path", { d: "m12 15 2 2 4-4" }],
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const CopyMinus = [
+ ["line", { x1: "12", x2: "18", y1: "15", y2: "15" }],
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const CopyPlus = [
+ ["line", { x1: "15", x2: "15", y1: "12", y2: "18" }],
+ ["line", { x1: "12", x2: "18", y1: "15", y2: "15" }],
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const CopySlash = [
+ ["line", { x1: "12", x2: "18", y1: "18", y2: "12" }],
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const CopyX = [
+ ["line", { x1: "12", x2: "18", y1: "12", y2: "18" }],
+ ["line", { x1: "12", x2: "18", y1: "18", y2: "12" }],
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const Copy = [
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }],
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]
+ ];
+
+ const Copyright = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M14.83 14.83a4 4 0 1 1 0-5.66" }]
+ ];
+
+ const Copyleft = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M9.17 14.83a4 4 0 1 0 0-5.66" }]
+ ];
+
+ const CornerDownLeft = [
+ ["path", { d: "M20 4v7a4 4 0 0 1-4 4H4" }],
+ ["path", { d: "m9 10-5 5 5 5" }]
+ ];
+
+ const CornerDownRight = [
+ ["path", { d: "m15 10 5 5-5 5" }],
+ ["path", { d: "M4 4v7a4 4 0 0 0 4 4h12" }]
+ ];
+
+ const CornerLeftDown = [
+ ["path", { d: "m14 15-5 5-5-5" }],
+ ["path", { d: "M20 4h-7a4 4 0 0 0-4 4v12" }]
+ ];
+
+ const CornerLeftUp = [
+ ["path", { d: "M14 9 9 4 4 9" }],
+ ["path", { d: "M20 20h-7a4 4 0 0 1-4-4V4" }]
+ ];
+
+ const CornerRightDown = [
+ ["path", { d: "m10 15 5 5 5-5" }],
+ ["path", { d: "M4 4h7a4 4 0 0 1 4 4v12" }]
+ ];
+
+ const CornerUpLeft = [
+ ["path", { d: "M20 20v-7a4 4 0 0 0-4-4H4" }],
+ ["path", { d: "M9 14 4 9l5-5" }]
+ ];
+
+ const CornerRightUp = [
+ ["path", { d: "m10 9 5-5 5 5" }],
+ ["path", { d: "M4 20h7a4 4 0 0 0 4-4V4" }]
+ ];
+
+ const CornerUpRight = [
+ ["path", { d: "m15 14 5-5-5-5" }],
+ ["path", { d: "M4 20v-7a4 4 0 0 1 4-4h12" }]
+ ];
+
+ const Cpu = [
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M17 20v2" }],
+ ["path", { d: "M17 2v2" }],
+ ["path", { d: "M2 12h2" }],
+ ["path", { d: "M2 17h2" }],
+ ["path", { d: "M2 7h2" }],
+ ["path", { d: "M20 12h2" }],
+ ["path", { d: "M20 17h2" }],
+ ["path", { d: "M20 7h2" }],
+ ["path", { d: "M7 20v2" }],
+ ["path", { d: "M7 2v2" }],
+ ["rect", { x: "4", y: "4", width: "16", height: "16", rx: "2" }],
+ ["rect", { x: "8", y: "8", width: "8", height: "8", rx: "1" }]
+ ];
+
+ const CreativeCommons = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M10 9.3a2.8 2.8 0 0 0-3.5 1 3.1 3.1 0 0 0 0 3.4 2.7 2.7 0 0 0 3.5 1" }],
+ ["path", { d: "M17 9.3a2.8 2.8 0 0 0-3.5 1 3.1 3.1 0 0 0 0 3.4 2.7 2.7 0 0 0 3.5 1" }]
+ ];
+
+ const CreditCard = [
+ ["rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }],
+ ["line", { x1: "2", x2: "22", y1: "10", y2: "10" }]
+ ];
+
+ const Croissant = [
+ ["path", { d: "M10.2 18H4.774a1.5 1.5 0 0 1-1.352-.97 11 11 0 0 1 .132-6.487" }],
+ ["path", { d: "M18 10.2V4.774a1.5 1.5 0 0 0-.97-1.352 11 11 0 0 0-6.486.132" }],
+ ["path", { d: "M18 5a4 3 0 0 1 4 3 2 2 0 0 1-2 2 10 10 0 0 0-5.139 1.42" }],
+ ["path", { d: "M5 18a3 4 0 0 0 3 4 2 2 0 0 0 2-2 10 10 0 0 1 1.42-5.14" }],
+ [
+ "path",
+ {
+ d: "M8.709 2.554a10 10 0 0 0-6.155 6.155 1.5 1.5 0 0 0 .676 1.626l9.807 5.42a2 2 0 0 0 2.718-2.718l-5.42-9.807a1.5 1.5 0 0 0-1.626-.676"
+ }
+ ]
+ ];
+
+ const Crop = [
+ ["path", { d: "M6 2v14a2 2 0 0 0 2 2h14" }],
+ ["path", { d: "M18 22V8a2 2 0 0 0-2-2H2" }]
+ ];
+
+ const Cross = [
+ [
+ "path",
+ {
+ d: "M4 9a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h4a1 1 0 0 1 1 1v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4a1 1 0 0 1 1-1h4a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-4a1 1 0 0 1-1-1V4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4a1 1 0 0 1-1 1z"
+ }
+ ]
+ ];
+
+ const Crosshair = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["line", { x1: "22", x2: "18", y1: "12", y2: "12" }],
+ ["line", { x1: "6", x2: "2", y1: "12", y2: "12" }],
+ ["line", { x1: "12", x2: "12", y1: "6", y2: "2" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "18" }]
+ ];
+
+ const Crown = [
+ [
+ "path",
+ {
+ d: "M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z"
+ }
+ ],
+ ["path", { d: "M5 21h14" }]
+ ];
+
+ const Cuboid = [
+ [
+ "path",
+ {
+ d: "m21.12 6.4-6.05-4.06a2 2 0 0 0-2.17-.05L2.95 8.41a2 2 0 0 0-.95 1.7v5.82a2 2 0 0 0 .88 1.66l6.05 4.07a2 2 0 0 0 2.17.05l9.95-6.12a2 2 0 0 0 .95-1.7V8.06a2 2 0 0 0-.88-1.66Z"
+ }
+ ],
+ ["path", { d: "M10 22v-8L2.25 9.15" }],
+ ["path", { d: "m10 14 11.77-6.87" }]
+ ];
+
+ const CupSoda = [
+ ["path", { d: "m6 8 1.75 12.28a2 2 0 0 0 2 1.72h4.54a2 2 0 0 0 2-1.72L18 8" }],
+ ["path", { d: "M5 8h14" }],
+ ["path", { d: "M7 15a6.47 6.47 0 0 1 5 0 6.47 6.47 0 0 0 5 0" }],
+ ["path", { d: "m12 8 1-6h2" }]
+ ];
+
+ const Currency = [
+ ["circle", { cx: "12", cy: "12", r: "8" }],
+ ["line", { x1: "3", x2: "6", y1: "3", y2: "6" }],
+ ["line", { x1: "21", x2: "18", y1: "3", y2: "6" }],
+ ["line", { x1: "3", x2: "6", y1: "21", y2: "18" }],
+ ["line", { x1: "21", x2: "18", y1: "21", y2: "18" }]
+ ];
+
+ const Cylinder = [
+ ["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3" }],
+ ["path", { d: "M3 5v14a9 3 0 0 0 18 0V5" }]
+ ];
+
+ const Dam = [
+ ["path", { d: "M11 11.31c1.17.56 1.54 1.69 3.5 1.69 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1" }],
+ ["path", { d: "M11.75 18c.35.5 1.45 1 2.75 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1" }],
+ ["path", { d: "M2 10h4" }],
+ ["path", { d: "M2 14h4" }],
+ ["path", { d: "M2 18h4" }],
+ ["path", { d: "M2 6h4" }],
+ ["path", { d: "M7 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1L10 4a1 1 0 0 0-1-1z" }]
+ ];
+
+ const DatabaseBackup = [
+ ["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3" }],
+ ["path", { d: "M3 12a9 3 0 0 0 5 2.69" }],
+ ["path", { d: "M21 9.3V5" }],
+ ["path", { d: "M3 5v14a9 3 0 0 0 6.47 2.88" }],
+ ["path", { d: "M12 12v4h4" }],
+ ["path", { d: "M13 20a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L12 16" }]
+ ];
+
+ const DatabaseZap = [
+ ["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3" }],
+ ["path", { d: "M3 5V19A9 3 0 0 0 15 21.84" }],
+ ["path", { d: "M21 5V8" }],
+ ["path", { d: "M21 12L18 17H22L19 22" }],
+ ["path", { d: "M3 12A9 3 0 0 0 14.59 14.87" }]
+ ];
+
+ const Database = [
+ ["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3" }],
+ ["path", { d: "M3 5V19A9 3 0 0 0 21 19V5" }],
+ ["path", { d: "M3 12A9 3 0 0 0 21 12" }]
+ ];
+
+ const DecimalsArrowLeft = [
+ ["path", { d: "m13 21-3-3 3-3" }],
+ ["path", { d: "M20 18H10" }],
+ ["path", { d: "M3 11h.01" }],
+ ["rect", { x: "6", y: "3", width: "5", height: "8", rx: "2.5" }]
+ ];
+
+ const DecimalsArrowRight = [
+ ["path", { d: "M10 18h10" }],
+ ["path", { d: "m17 21 3-3-3-3" }],
+ ["path", { d: "M3 11h.01" }],
+ ["rect", { x: "15", y: "3", width: "5", height: "8", rx: "2.5" }],
+ ["rect", { x: "6", y: "3", width: "5", height: "8", rx: "2.5" }]
+ ];
+
+ const Delete = [
+ [
+ "path",
+ {
+ d: "M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z"
+ }
+ ],
+ ["path", { d: "m12 9 6 6" }],
+ ["path", { d: "m18 9-6 6" }]
+ ];
+
+ const Diameter = [
+ ["circle", { cx: "19", cy: "19", r: "2" }],
+ ["circle", { cx: "5", cy: "5", r: "2" }],
+ ["path", { d: "M6.48 3.66a10 10 0 0 1 13.86 13.86" }],
+ ["path", { d: "m6.41 6.41 11.18 11.18" }],
+ ["path", { d: "M3.66 6.48a10 10 0 0 0 13.86 13.86" }]
+ ];
+
+ const Dessert = [
+ [
+ "path",
+ {
+ d: "M10.162 3.167A10 10 0 0 0 2 13a2 2 0 0 0 4 0v-1a2 2 0 0 1 4 0v4a2 2 0 0 0 4 0v-4a2 2 0 0 1 4 0v1a2 2 0 0 0 4-.006 10 10 0 0 0-8.161-9.826"
+ }
+ ],
+ ["path", { d: "M20.804 14.869a9 9 0 0 1-17.608 0" }],
+ ["circle", { cx: "12", cy: "4", r: "2" }]
+ ];
+
+ const DiamondMinus = [
+ [
+ "path",
+ {
+ d: "M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0z"
+ }
+ ],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const DiamondPercent = [
+ [
+ "path",
+ {
+ d: "M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0Z"
+ }
+ ],
+ ["path", { d: "M9.2 9.2h.01" }],
+ ["path", { d: "m14.5 9.5-5 5" }],
+ ["path", { d: "M14.7 14.8h.01" }]
+ ];
+
+ const DiamondPlus = [
+ ["path", { d: "M12 8v8" }],
+ [
+ "path",
+ {
+ d: "M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41L13.7 2.71a2.41 2.41 0 0 0-3.41 0z"
+ }
+ ],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const Diamond = [
+ [
+ "path",
+ {
+ d: "M2.7 10.3a2.41 2.41 0 0 0 0 3.41l7.59 7.59a2.41 2.41 0 0 0 3.41 0l7.59-7.59a2.41 2.41 0 0 0 0-3.41l-7.59-7.59a2.41 2.41 0 0 0-3.41 0Z"
+ }
+ ]
+ ];
+
+ const Dice1 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M12 12h.01" }]
+ ];
+
+ const Dice2 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M15 9h.01" }],
+ ["path", { d: "M9 15h.01" }]
+ ];
+
+ const Dice3 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M16 8h.01" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M8 16h.01" }]
+ ];
+
+ const Dice4 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M16 8h.01" }],
+ ["path", { d: "M8 8h.01" }],
+ ["path", { d: "M8 16h.01" }],
+ ["path", { d: "M16 16h.01" }]
+ ];
+
+ const Dice5 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M16 8h.01" }],
+ ["path", { d: "M8 8h.01" }],
+ ["path", { d: "M8 16h.01" }],
+ ["path", { d: "M16 16h.01" }],
+ ["path", { d: "M12 12h.01" }]
+ ];
+
+ const Dice6 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M16 8h.01" }],
+ ["path", { d: "M16 12h.01" }],
+ ["path", { d: "M16 16h.01" }],
+ ["path", { d: "M8 8h.01" }],
+ ["path", { d: "M8 12h.01" }],
+ ["path", { d: "M8 16h.01" }]
+ ];
+
+ const Dices = [
+ ["rect", { width: "12", height: "12", x: "2", y: "10", rx: "2", ry: "2" }],
+ ["path", { d: "m17.92 14 3.5-3.5a2.24 2.24 0 0 0 0-3l-5-4.92a2.24 2.24 0 0 0-3 0L10 6" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "M10 14h.01" }],
+ ["path", { d: "M15 6h.01" }],
+ ["path", { d: "M18 9h.01" }]
+ ];
+
+ const Diff = [
+ ["path", { d: "M12 3v14" }],
+ ["path", { d: "M5 10h14" }],
+ ["path", { d: "M5 21h14" }]
+ ];
+
+ const Disc2 = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["path", { d: "M12 12h.01" }]
+ ];
+
+ const Disc3 = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M6 12c0-1.7.7-3.2 1.8-4.2" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["path", { d: "M18 12c0 1.7-.7 3.2-1.8 4.2" }]
+ ];
+
+ const DiscAlbum = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["circle", { cx: "12", cy: "12", r: "5" }],
+ ["path", { d: "M12 12h.01" }]
+ ];
+
+ const Disc = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const Divide = [
+ ["circle", { cx: "12", cy: "6", r: "1" }],
+ ["line", { x1: "5", x2: "19", y1: "12", y2: "12" }],
+ ["circle", { cx: "12", cy: "18", r: "1" }]
+ ];
+
+ const DnaOff = [
+ ["path", { d: "M15 2c-1.35 1.5-2.092 3-2.5 4.5L14 8" }],
+ ["path", { d: "m17 6-2.891-2.891" }],
+ ["path", { d: "M2 15c3.333-3 6.667-3 10-3" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "m20 9 .891.891" }],
+ ["path", { d: "M22 9c-1.5 1.35-3 2.092-4.5 2.5l-1-1" }],
+ ["path", { d: "M3.109 14.109 4 15" }],
+ ["path", { d: "m6.5 12.5 1 1" }],
+ ["path", { d: "m7 18 2.891 2.891" }],
+ ["path", { d: "M9 22c1.35-1.5 2.092-3 2.5-4.5L10 16" }]
+ ];
+
+ const Dna = [
+ ["path", { d: "m10 16 1.5 1.5" }],
+ ["path", { d: "m14 8-1.5-1.5" }],
+ ["path", { d: "M15 2c-1.798 1.998-2.518 3.995-2.807 5.993" }],
+ ["path", { d: "m16.5 10.5 1 1" }],
+ ["path", { d: "m17 6-2.891-2.891" }],
+ ["path", { d: "M2 15c6.667-6 13.333 0 20-6" }],
+ ["path", { d: "m20 9 .891.891" }],
+ ["path", { d: "M3.109 14.109 4 15" }],
+ ["path", { d: "m6.5 12.5 1 1" }],
+ ["path", { d: "m7 18 2.891 2.891" }],
+ ["path", { d: "M9 22c1.798-1.998 2.518-3.995 2.807-5.993" }]
+ ];
+
+ const Dog = [
+ ["path", { d: "M11.25 16.25h1.5L12 17z" }],
+ ["path", { d: "M16 14v.5" }],
+ [
+ "path",
+ {
+ d: "M4.42 11.247A13.152 13.152 0 0 0 4 14.556C4 18.728 7.582 21 12 21s8-2.272 8-6.444a11.702 11.702 0 0 0-.493-3.309"
+ }
+ ],
+ ["path", { d: "M8 14v.5" }],
+ [
+ "path",
+ {
+ d: "M8.5 8.5c-.384 1.05-1.083 2.028-2.344 2.5-1.931.722-3.576-.297-3.656-1-.113-.994 1.177-6.53 4-7 1.923-.321 3.651.845 3.651 2.235A7.497 7.497 0 0 1 14 5.277c0-1.39 1.844-2.598 3.767-2.277 2.823.47 4.113 6.006 4 7-.08.703-1.725 1.722-3.656 1-1.261-.472-1.855-1.45-2.239-2.5"
+ }
+ ]
+ ];
+
+ const Dock = [
+ ["path", { d: "M2 8h20" }],
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M6 16h12" }]
+ ];
+
+ const DollarSign = [
+ ["line", { x1: "12", x2: "12", y1: "2", y2: "22" }],
+ ["path", { d: "M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" }]
+ ];
+
+ const Donut = [
+ [
+ "path",
+ {
+ d: "M20.5 10a2.5 2.5 0 0 1-2.4-3H18a2.95 2.95 0 0 1-2.6-4.4 10 10 0 1 0 6.3 7.1c-.3.2-.8.3-1.2.3"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "3" }]
+ ];
+
+ const DoorClosedLocked = [
+ ["path", { d: "M10 12h.01" }],
+ ["path", { d: "M18 9V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14" }],
+ ["path", { d: "M2 20h8" }],
+ ["path", { d: "M20 17v-2a2 2 0 1 0-4 0v2" }],
+ ["rect", { x: "14", y: "17", width: "8", height: "5", rx: "1" }]
+ ];
+
+ const DoorClosed = [
+ ["path", { d: "M10 12h.01" }],
+ ["path", { d: "M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14" }],
+ ["path", { d: "M2 20h20" }]
+ ];
+
+ const DoorOpen = [
+ ["path", { d: "M11 20H2" }],
+ [
+ "path",
+ {
+ d: "M11 4.562v16.157a1 1 0 0 0 1.242.97L19 20V5.562a2 2 0 0 0-1.515-1.94l-4-1A2 2 0 0 0 11 4.561z"
+ }
+ ],
+ ["path", { d: "M11 4H8a2 2 0 0 0-2 2v14" }],
+ ["path", { d: "M14 12h.01" }],
+ ["path", { d: "M22 20h-3" }]
+ ];
+
+ const Dot = [["circle", { cx: "12.1", cy: "12.1", r: "1" }]];
+
+ const Download = [
+ ["path", { d: "M12 15V3" }],
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }],
+ ["path", { d: "m7 10 5 5 5-5" }]
+ ];
+
+ const DraftingCompass = [
+ ["path", { d: "m12.99 6.74 1.93 3.44" }],
+ ["path", { d: "M19.136 12a10 10 0 0 1-14.271 0" }],
+ ["path", { d: "m21 21-2.16-3.84" }],
+ ["path", { d: "m3 21 8.02-14.26" }],
+ ["circle", { cx: "12", cy: "5", r: "2" }]
+ ];
+
+ const Drama = [
+ ["path", { d: "M10 11h.01" }],
+ ["path", { d: "M14 6h.01" }],
+ ["path", { d: "M18 6h.01" }],
+ ["path", { d: "M6.5 13.1h.01" }],
+ ["path", { d: "M22 5c0 9-4 12-6 12s-6-3-6-12c0-2 2-3 6-3s6 1 6 3" }],
+ ["path", { d: "M17.4 9.9c-.8.8-2 .8-2.8 0" }],
+ [
+ "path",
+ {
+ d: "M10.1 7.1C9 7.2 7.7 7.7 6 8.6c-3.5 2-4.7 3.9-3.7 5.6 4.5 7.8 9.5 8.4 11.2 7.4.9-.5 1.9-2.1 1.9-4.7"
+ }
+ ],
+ ["path", { d: "M9.1 16.5c.3-1.1 1.4-1.7 2.4-1.4" }]
+ ];
+
+ const Dribbble = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M19.13 5.09C15.22 9.14 10 10.44 2.25 10.94" }],
+ ["path", { d: "M21.75 12.84c-6.62-1.41-12.14 1-16.38 6.32" }],
+ ["path", { d: "M8.56 2.75c4.37 6 6 9.42 8 17.72" }]
+ ];
+
+ const Drill = [
+ ["path", { d: "M10 18a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a3 3 0 0 1-3-3 1 1 0 0 1 1-1z" }],
+ [
+ "path",
+ {
+ d: "M13 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1l-.81 3.242a1 1 0 0 1-.97.758H8"
+ }
+ ],
+ ["path", { d: "M14 4h3a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-3" }],
+ ["path", { d: "M18 6h4" }],
+ ["path", { d: "m5 10-2 8" }],
+ ["path", { d: "m7 18 2-8" }]
+ ];
+
+ const Drone = [
+ ["path", { d: "M10 10 7 7" }],
+ ["path", { d: "m10 14-3 3" }],
+ ["path", { d: "m14 10 3-3" }],
+ ["path", { d: "m14 14 3 3" }],
+ ["path", { d: "M14.205 4.139a4 4 0 1 1 5.439 5.863" }],
+ ["path", { d: "M19.637 14a4 4 0 1 1-5.432 5.868" }],
+ ["path", { d: "M4.367 10a4 4 0 1 1 5.438-5.862" }],
+ ["path", { d: "M9.795 19.862a4 4 0 1 1-5.429-5.873" }],
+ ["rect", { x: "10", y: "8", width: "4", height: "8", rx: "1" }]
+ ];
+
+ const DropletOff = [
+ [
+ "path",
+ {
+ d: "M18.715 13.186C18.29 11.858 17.384 10.607 16 9.5c-2-1.6-3.5-4-4-6.5a10.7 10.7 0 0 1-.884 2.586"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.795 8.797A11 11 0 0 1 8 9.5C6 11.1 5 13 5 15a7 7 0 0 0 13.222 3.208" }]
+ ];
+
+ const Droplet = [
+ [
+ "path",
+ {
+ d: "M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z"
+ }
+ ]
+ ];
+
+ const Droplets = [
+ [
+ "path",
+ {
+ d: "M7 16.3c2.2 0 4-1.83 4-4.05 0-1.16-.57-2.26-1.71-3.19S7.29 6.75 7 5.3c-.29 1.45-1.14 2.84-2.29 3.76S3 11.1 3 12.25c0 2.22 1.8 4.05 4 4.05z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M12.56 6.6A10.97 10.97 0 0 0 14 3.02c.5 2.5 2 4.9 4 6.5s3 3.5 3 5.5a6.98 6.98 0 0 1-11.91 4.97"
+ }
+ ]
+ ];
+
+ const Drum = [
+ ["path", { d: "m2 2 8 8" }],
+ ["path", { d: "m22 2-8 8" }],
+ ["ellipse", { cx: "12", cy: "9", rx: "10", ry: "5" }],
+ ["path", { d: "M7 13.4v7.9" }],
+ ["path", { d: "M12 14v8" }],
+ ["path", { d: "M17 13.4v7.9" }],
+ ["path", { d: "M2 9v8a10 5 0 0 0 20 0V9" }]
+ ];
+
+ const Drumstick = [
+ ["path", { d: "M15.4 15.63a7.875 6 135 1 1 6.23-6.23 4.5 3.43 135 0 0-6.23 6.23" }],
+ ["path", { d: "m8.29 12.71-2.6 2.6a2.5 2.5 0 1 0-1.65 4.65A2.5 2.5 0 1 0 8.7 18.3l2.59-2.59" }]
+ ];
+
+ const Dumbbell = [
+ [
+ "path",
+ {
+ d: "M17.596 12.768a2 2 0 1 0 2.829-2.829l-1.768-1.767a2 2 0 0 0 2.828-2.829l-2.828-2.828a2 2 0 0 0-2.829 2.828l-1.767-1.768a2 2 0 1 0-2.829 2.829z"
+ }
+ ],
+ ["path", { d: "m2.5 21.5 1.4-1.4" }],
+ ["path", { d: "m20.1 3.9 1.4-1.4" }],
+ [
+ "path",
+ {
+ d: "M5.343 21.485a2 2 0 1 0 2.829-2.828l1.767 1.768a2 2 0 1 0 2.829-2.829l-6.364-6.364a2 2 0 1 0-2.829 2.829l1.768 1.767a2 2 0 0 0-2.828 2.829z"
+ }
+ ],
+ ["path", { d: "m9.6 14.4 4.8-4.8" }]
+ ];
+
+ const EarOff = [
+ ["path", { d: "M6 18.5a3.5 3.5 0 1 0 7 0c0-1.57.92-2.52 2.04-3.46" }],
+ ["path", { d: "M6 8.5c0-.75.13-1.47.36-2.14" }],
+ ["path", { d: "M8.8 3.15A6.5 6.5 0 0 1 19 8.5c0 1.63-.44 2.81-1.09 3.76" }],
+ ["path", { d: "M12.5 6A2.5 2.5 0 0 1 15 8.5M10 13a2 2 0 0 0 1.82-1.18" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Ear = [
+ ["path", { d: "M6 8.5a6.5 6.5 0 1 1 13 0c0 6-6 6-6 10a3.5 3.5 0 1 1-7 0" }],
+ ["path", { d: "M15 8.5a2.5 2.5 0 0 0-5 0v1a2 2 0 1 1 0 4" }]
+ ];
+
+ const EarthLock = [
+ ["path", { d: "M7 3.34V5a3 3 0 0 0 3 3" }],
+ ["path", { d: "M11 21.95V18a2 2 0 0 0-2-2 2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05" }],
+ ["path", { d: "M21.54 15H17a2 2 0 0 0-2 2v4.54" }],
+ ["path", { d: "M12 2a10 10 0 1 0 9.54 13" }],
+ ["path", { d: "M20 6V4a2 2 0 1 0-4 0v2" }],
+ ["rect", { width: "8", height: "5", x: "14", y: "6", rx: "1" }]
+ ];
+
+ const Earth = [
+ ["path", { d: "M21.54 15H17a2 2 0 0 0-2 2v4.54" }],
+ [
+ "path",
+ { d: "M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17" }
+ ],
+ ["path", { d: "M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Eclipse = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M12 2a7 7 0 1 0 10 10" }]
+ ];
+
+ const EggFried = [
+ ["circle", { cx: "11.5", cy: "12.5", r: "3.5" }],
+ [
+ "path",
+ {
+ d: "M3 8c0-3.5 2.5-6 6.5-6 5 0 4.83 3 7.5 5s5 2 5 6c0 4.5-2.5 6.5-7 6.5-2.5 0-2.5 2.5-6 2.5s-7-2-7-5.5c0-3 1.5-3 1.5-5C3.5 10 3 9 3 8Z"
+ }
+ ]
+ ];
+
+ const EggOff = [
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20 14.347V14c0-6-4-12-8-12-1.078 0-2.157.436-3.157 1.19" }],
+ ["path", { d: "M6.206 6.21C4.871 8.4 4 11.2 4 14a8 8 0 0 0 14.568 4.568" }]
+ ];
+
+ const Egg = [["path", { d: "M12 2C8 2 4 8 4 14a8 8 0 0 0 16 0c0-6-4-12-8-12" }]];
+
+ const EllipsisVertical = [
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ ["circle", { cx: "12", cy: "5", r: "1" }],
+ ["circle", { cx: "12", cy: "19", r: "1" }]
+ ];
+
+ const Ellipsis = [
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ ["circle", { cx: "19", cy: "12", r: "1" }],
+ ["circle", { cx: "5", cy: "12", r: "1" }]
+ ];
+
+ const EqualApproximately = [
+ ["path", { d: "M5 15a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0" }],
+ ["path", { d: "M5 9a6.5 6.5 0 0 1 7 0 6.5 6.5 0 0 0 7 0" }]
+ ];
+
+ const EqualNot = [
+ ["line", { x1: "5", x2: "19", y1: "9", y2: "9" }],
+ ["line", { x1: "5", x2: "19", y1: "15", y2: "15" }],
+ ["line", { x1: "19", x2: "5", y1: "5", y2: "19" }]
+ ];
+
+ const Equal = [
+ ["line", { x1: "5", x2: "19", y1: "9", y2: "9" }],
+ ["line", { x1: "5", x2: "19", y1: "15", y2: "15" }]
+ ];
+
+ const Eraser = [
+ [
+ "path",
+ {
+ d: "M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21"
+ }
+ ],
+ ["path", { d: "m5.082 11.09 8.828 8.828" }]
+ ];
+
+ const EthernetPort = [
+ [
+ "path",
+ { d: "m15 20 3-3h2a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h2l3 3z" }
+ ],
+ ["path", { d: "M6 8v1" }],
+ ["path", { d: "M10 8v1" }],
+ ["path", { d: "M14 8v1" }],
+ ["path", { d: "M18 8v1" }]
+ ];
+
+ const Euro = [
+ ["path", { d: "M4 10h12" }],
+ ["path", { d: "M4 14h9" }],
+ [
+ "path",
+ { d: "M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12c0 4.4 3.5 8 7.8 8 2 0 3.8-.8 5.2-2" }
+ ]
+ ];
+
+ const EvCharger = [
+ ["path", { d: "M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 4 0v-6.998a2 2 0 0 0-.59-1.42L18 5" }],
+ ["path", { d: "M14 21V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v16" }],
+ ["path", { d: "M2 21h13" }],
+ ["path", { d: "M3 7h11" }],
+ ["path", { d: "m9 11-2 3h3l-2 3" }]
+ ];
+
+ const Expand = [
+ ["path", { d: "m15 15 6 6" }],
+ ["path", { d: "m15 9 6-6" }],
+ ["path", { d: "M21 16v5h-5" }],
+ ["path", { d: "M21 8V3h-5" }],
+ ["path", { d: "M3 16v5h5" }],
+ ["path", { d: "m3 21 6-6" }],
+ ["path", { d: "M3 8V3h5" }],
+ ["path", { d: "M9 9 3 3" }]
+ ];
+
+ const ExternalLink = [
+ ["path", { d: "M15 3h6v6" }],
+ ["path", { d: "M10 14 21 3" }],
+ ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }]
+ ];
+
+ const EyeClosed = [
+ ["path", { d: "m15 18-.722-3.25" }],
+ ["path", { d: "M2 8a10.645 10.645 0 0 0 20 0" }],
+ ["path", { d: "m20 15-1.726-2.05" }],
+ ["path", { d: "m4 15 1.726-2.05" }],
+ ["path", { d: "m9 18 .722-3.25" }]
+ ];
+
+ const EyeOff = [
+ [
+ "path",
+ {
+ d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"
+ }
+ ],
+ ["path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }],
+ [
+ "path",
+ {
+ d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Eye = [
+ [
+ "path",
+ {
+ d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "3" }]
+ ];
+
+ const Factory = [
+ ["path", { d: "M12 16h.01" }],
+ ["path", { d: "M16 16h.01" }],
+ [
+ "path",
+ {
+ d: "M3 19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8.5a.5.5 0 0 0-.769-.422l-4.462 2.844A.5.5 0 0 1 15 10.5v-2a.5.5 0 0 0-.769-.422L9.77 10.922A.5.5 0 0 1 9 10.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2z"
+ }
+ ],
+ ["path", { d: "M8 16h.01" }]
+ ];
+
+ const Facebook = [
+ ["path", { d: "M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" }]
+ ];
+
+ const Fan = [
+ [
+ "path",
+ {
+ d: "M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"
+ }
+ ],
+ ["path", { d: "M12 12v.01" }]
+ ];
+
+ const FastForward = [
+ ["path", { d: "M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z" }],
+ ["path", { d: "M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z" }]
+ ];
+
+ const Feather = [
+ [
+ "path",
+ {
+ d: "M12.67 19a2 2 0 0 0 1.416-.588l6.154-6.172a6 6 0 0 0-8.49-8.49L5.586 9.914A2 2 0 0 0 5 11.328V18a1 1 0 0 0 1 1z"
+ }
+ ],
+ ["path", { d: "M16 8 2 22" }],
+ ["path", { d: "M17.5 15H9" }]
+ ];
+
+ const Fence = [
+ ["path", { d: "M4 3 2 5v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z" }],
+ ["path", { d: "M6 8h4" }],
+ ["path", { d: "M6 18h4" }],
+ ["path", { d: "m12 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z" }],
+ ["path", { d: "M14 8h4" }],
+ ["path", { d: "M14 18h4" }],
+ ["path", { d: "m20 3-2 2v15c0 .6.4 1 1 1h2c.6 0 1-.4 1-1V5Z" }]
+ ];
+
+ const FerrisWheel = [
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["path", { d: "M12 2v4" }],
+ ["path", { d: "m6.8 15-3.5 2" }],
+ ["path", { d: "m20.7 7-3.5 2" }],
+ ["path", { d: "M6.8 9 3.3 7" }],
+ ["path", { d: "m20.7 17-3.5-2" }],
+ ["path", { d: "m9 22 3-8 3 8" }],
+ ["path", { d: "M8 22h8" }],
+ ["path", { d: "M18 18.7a9 9 0 1 0-12 0" }]
+ ];
+
+ const Figma = [
+ ["path", { d: "M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z" }],
+ ["path", { d: "M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z" }],
+ ["path", { d: "M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z" }],
+ ["path", { d: "M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z" }],
+ ["path", { d: "M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z" }]
+ ];
+
+ const FileArchive = [
+ ["path", { d: "M10 12v-1" }],
+ ["path", { d: "M10 18v-2" }],
+ ["path", { d: "M10 7V6" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M15.5 22H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v16a2 2 0 0 0 .274 1.01" }],
+ ["circle", { cx: "10", cy: "20", r: "2" }]
+ ];
+
+ const FileAudio2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v2" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["circle", { cx: "3", cy: "17", r: "1" }],
+ ["path", { d: "M2 17v-3a4 4 0 0 1 8 0v3" }],
+ ["circle", { cx: "9", cy: "17", r: "1" }]
+ ];
+
+ const FileAudio = [
+ ["path", { d: "M17.5 22h.5a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ { d: "M2 19a2 2 0 1 1 4 0v1a2 2 0 1 1-4 0v-4a6 6 0 0 1 12 0v4a2 2 0 1 1-4 0v-1a2 2 0 1 1 4 0" }
+ ]
+ ];
+
+ const FileAxis3d = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m8 18 4-4" }],
+ ["path", { d: "M8 10v8h8" }]
+ ];
+
+ const FileBadge2 = [
+ [
+ "path",
+ {
+ d: "m13.69 12.479 1.29 4.88a.5.5 0 0 1-.697.591l-1.844-.849a1 1 0 0 0-.88.001l-1.846.85a.5.5 0 0 1-.693-.593l1.29-4.88"
+ }
+ ],
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }],
+ ["circle", { cx: "12", cy: "10", r: "3" }]
+ ];
+
+ const FileBadge = [
+ ["path", { d: "M12 22h6a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3.072" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ {
+ d: "m6.69 16.479 1.29 4.88a.5.5 0 0 1-.698.591l-1.843-.849a1 1 0 0 0-.88.001l-1.846.85a.5.5 0 0 1-.693-.593l1.29-4.88"
+ }
+ ],
+ ["circle", { cx: "5", cy: "14", r: "3" }]
+ ];
+
+ const FileBox = [
+ ["path", { d: "M14.5 22H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ {
+ d: "M3 13.1a2 2 0 0 0-1 1.76v3.24a2 2 0 0 0 .97 1.78L6 21.7a2 2 0 0 0 2.03.01L11 19.9a2 2 0 0 0 1-1.76V14.9a2 2 0 0 0-.97-1.78L8 11.3a2 2 0 0 0-2.03-.01Z"
+ }
+ ],
+ ["path", { d: "M7 17v5" }],
+ ["path", { d: "M11.7 14.2 7 17l-4.7-2.8" }]
+ ];
+
+ const FileChartColumnIncreasing = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 18v-2" }],
+ ["path", { d: "M12 18v-4" }],
+ ["path", { d: "M16 18v-6" }]
+ ];
+
+ const FileChartColumn = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 18v-1" }],
+ ["path", { d: "M12 18v-6" }],
+ ["path", { d: "M16 18v-3" }]
+ ];
+
+ const FileChartLine = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m16 13-3.5 3.5-2-2L8 17" }]
+ ];
+
+ const FileChartPie = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M16 22h2a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3.5" }],
+ ["path", { d: "M4.017 11.512a6 6 0 1 0 8.466 8.475" }],
+ [
+ "path",
+ {
+ d: "M9 16a1 1 0 0 1-1-1v-4c0-.552.45-1.008.995-.917a6 6 0 0 1 4.922 4.922c.091.544-.365.995-.917.995z"
+ }
+ ]
+ ];
+
+ const FileCheck2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m3 15 2 2 4-4" }]
+ ];
+
+ const FileClock = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M16 22h2a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "M8 14v2.2l1.6 1" }],
+ ["circle", { cx: "8", cy: "16", r: "6" }]
+ ];
+
+ const FileCheck = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m9 15 2 2 4-4" }]
+ ];
+
+ const FileCode2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m5 12-3 3 3 3" }],
+ ["path", { d: "m9 18 3-3-3-3" }]
+ ];
+
+ const FileCode = [
+ ["path", { d: "M10 12.5 8 15l2 2.5" }],
+ ["path", { d: "m14 12.5 2 2.5-2 2.5" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }]
+ ];
+
+ const FileDiff = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M9 10h6" }],
+ ["path", { d: "M12 13V7" }],
+ ["path", { d: "M9 17h6" }]
+ ];
+
+ const FileCog = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m2.305 15.53.923-.382" }],
+ ["path", { d: "m3.228 12.852-.924-.383" }],
+ ["path", { d: "M4.677 21.5a2 2 0 0 0 1.313.5H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v2.5" }],
+ ["path", { d: "m4.852 11.228-.383-.923" }],
+ ["path", { d: "m4.852 16.772-.383.924" }],
+ ["path", { d: "m7.148 11.228.383-.923" }],
+ ["path", { d: "m7.53 17.696-.382-.924" }],
+ ["path", { d: "m8.772 12.852.923-.383" }],
+ ["path", { d: "m8.772 15.148.923.383" }],
+ ["circle", { cx: "6", cy: "14", r: "3" }]
+ ];
+
+ const FileDigit = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["rect", { width: "4", height: "6", x: "2", y: "12", rx: "2" }],
+ ["path", { d: "M10 12h2v6" }],
+ ["path", { d: "M10 18h4" }]
+ ];
+
+ const FileDown = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M12 18v-6" }],
+ ["path", { d: "m9 15 3 3 3-3" }]
+ ];
+
+ const FileHeart = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ {
+ d: "M2.62 13.8A2.25 2.25 0 1 1 6 10.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"
+ }
+ ],
+ ["path", { d: "M4 6.005V4a2 2 0 0 1 2-2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-1.9-1.376" }]
+ ];
+
+ const FileImage = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["circle", { cx: "10", cy: "12", r: "2" }],
+ ["path", { d: "m20 17-1.296-1.296a2.41 2.41 0 0 0-3.408 0L9 22" }]
+ ];
+
+ const FileInput = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M2 15h10" }],
+ ["path", { d: "m9 18 3-3-3-3" }]
+ ];
+
+ const FileJson2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M4 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1" }],
+ ["path", { d: "M8 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1" }]
+ ];
+
+ const FileJson = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1" }],
+ ["path", { d: "M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1" }]
+ ];
+
+ const FileKey2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v6" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["circle", { cx: "4", cy: "16", r: "2" }],
+ ["path", { d: "m10 10-4.5 4.5" }],
+ ["path", { d: "m9 11 1 1" }]
+ ];
+
+ const FileKey = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["circle", { cx: "10", cy: "16", r: "2" }],
+ ["path", { d: "m16 10-4.5 4.5" }],
+ ["path", { d: "m15 11 1 1" }]
+ ];
+
+ const FileLock2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v1" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["rect", { width: "8", height: "5", x: "2", y: "13", rx: "1" }],
+ ["path", { d: "M8 13v-2a2 2 0 1 0-4 0v2" }]
+ ];
+
+ const FileLock = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["rect", { width: "8", height: "6", x: "8", y: "12", rx: "1" }],
+ ["path", { d: "M10 12v-2a2 2 0 1 1 4 0v2" }]
+ ];
+
+ const FileMinus2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M3 15h6" }]
+ ];
+
+ const FileMinus = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M9 15h6" }]
+ ];
+
+ const FileMusic = [
+ ["path", { d: "M10.5 22H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v8.4" }],
+ ["path", { d: "M8 18v-7.7L16 9v7" }],
+ ["circle", { cx: "14", cy: "16", r: "2" }],
+ ["circle", { cx: "6", cy: "18", r: "2" }]
+ ];
+
+ const FileOutput = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M4 7V4a2 2 0 0 1 2-2 2 2 0 0 0-2 2" }],
+ ["path", { d: "M4.063 20.999a2 2 0 0 0 2 1L18 22a2 2 0 0 0 2-2V7l-5-5H6" }],
+ ["path", { d: "m5 11-3 3" }],
+ ["path", { d: "m5 17-3-3h10" }]
+ ];
+
+ const FilePenLine = [
+ [
+ "path",
+ { d: "m18 5-2.414-2.414A2 2 0 0 0 14.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2" }
+ ],
+ [
+ "path",
+ {
+ d: "M21.378 12.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ],
+ ["path", { d: "M8 18h1" }]
+ ];
+
+ const FilePen = [
+ ["path", { d: "M12.5 22H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v9.5" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ {
+ d: "M13.378 15.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ]
+ ];
+
+ const FilePlay = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }],
+ [
+ "path",
+ {
+ d: "M15.033 13.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56v-4.704a.645.645 0 0 1 .967-.56z"
+ }
+ ]
+ ];
+
+ const FilePlus2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M3 15h6" }],
+ ["path", { d: "M6 12v6" }]
+ ];
+
+ const FilePlus = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M9 15h6" }],
+ ["path", { d: "M12 18v-6" }]
+ ];
+
+ const FileQuestionMark = [
+ ["path", { d: "M12 17h.01" }],
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }],
+ ["path", { d: "M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3" }]
+ ];
+
+ const FileScan = [
+ ["path", { d: "M20 10V7l-5-5H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M16 14a2 2 0 0 0-2 2" }],
+ ["path", { d: "M20 14a2 2 0 0 1 2 2" }],
+ ["path", { d: "M20 22a2 2 0 0 0 2-2" }],
+ ["path", { d: "M16 22a2 2 0 0 1-2-2" }]
+ ];
+
+ const FileSearch2 = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["circle", { cx: "11.5", cy: "14.5", r: "2.5" }],
+ ["path", { d: "M13.3 16.3 15 18" }]
+ ];
+
+ const FileSearch = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "m9 18-1.5-1.5" }],
+ ["circle", { cx: "5", cy: "14", r: "3" }]
+ ];
+
+ const FileSliders = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "M10 11v2" }],
+ ["path", { d: "M8 17h8" }],
+ ["path", { d: "M14 16v2" }]
+ ];
+
+ const FileSpreadsheet = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 13h2" }],
+ ["path", { d: "M14 13h2" }],
+ ["path", { d: "M8 17h2" }],
+ ["path", { d: "M14 17h2" }]
+ ];
+
+ const FileStack = [
+ ["path", { d: "M11 21a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1" }],
+ ["path", { d: "M16 16a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1" }],
+ [
+ "path",
+ {
+ d: "M21 6a2 2 0 0 0-.586-1.414l-2-2A2 2 0 0 0 17 2h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1z"
+ }
+ ]
+ ];
+
+ const FileSymlink = [
+ ["path", { d: "m10 18 3-3-3-3" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ [
+ "path",
+ { d: "M4 11V4a2 2 0 0 1 2-2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7" }
+ ]
+ ];
+
+ const FileTerminal = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m8 16 2-2-2-2" }],
+ ["path", { d: "M12 18h4" }]
+ ];
+
+ const FileText = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M10 9H8" }],
+ ["path", { d: "M16 13H8" }],
+ ["path", { d: "M16 17H8" }]
+ ];
+
+ const FileType2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M2 13v-1h6v1" }],
+ ["path", { d: "M5 12v6" }],
+ ["path", { d: "M4 18h2" }]
+ ];
+
+ const FileType = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M9 13v-1h6v1" }],
+ ["path", { d: "M12 12v6" }],
+ ["path", { d: "M11 18h2" }]
+ ];
+
+ const FileUp = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M12 12v6" }],
+ ["path", { d: "m15 15-3-3-3 3" }]
+ ];
+
+ const FileUser = [
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M15 18a3 3 0 1 0-6 0" }],
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" }],
+ ["circle", { cx: "12", cy: "13", r: "2" }]
+ ];
+
+ const FileVideoCamera = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["rect", { width: "8", height: "6", x: "2", y: "12", rx: "1" }],
+ [
+ "path",
+ { d: "m10 13.843 3.033-1.755a.645.645 0 0 1 .967.56v4.704a.645.645 0 0 1-.967.56L10 16.157" }
+ ]
+ ];
+
+ const FileVolume2 = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 15h.01" }],
+ ["path", { d: "M11.5 13.5a2.5 2.5 0 0 1 0 3" }],
+ ["path", { d: "M15 12a5 5 0 0 1 0 6" }]
+ ];
+
+ const FileVolume = [
+ ["path", { d: "M11 11a5 5 0 0 1 0 6" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M4 6.765V4a2 2 0 0 1 2-2h9l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-.93-.23" }],
+ [
+ "path",
+ {
+ d: "M7 10.51a.5.5 0 0 0-.826-.38l-1.893 1.628A1 1 0 0 1 3.63 12H2.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h1.129a1 1 0 0 1 .652.242l1.893 1.63a.5.5 0 0 0 .826-.38z"
+ }
+ ]
+ ];
+
+ const FileWarning = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M12 9v4" }],
+ ["path", { d: "M12 17h.01" }]
+ ];
+
+ const FileX2 = [
+ ["path", { d: "M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m8 12.5-5 5" }],
+ ["path", { d: "m3 12.5 5 5" }]
+ ];
+
+ const FileX = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "m14.5 12.5-5 5" }],
+ ["path", { d: "m9.5 12.5 5 5" }]
+ ];
+
+ const File = [
+ ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }]
+ ];
+
+ const Files = [
+ [
+ "path",
+ {
+ d: "M15 2a2 2 0 0 1 1.414.586l4 4A2 2 0 0 1 21 8v7a2 2 0 0 1-2 2h-8a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2z"
+ }
+ ],
+ ["path", { d: "M15 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M5 7a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h8a2 2 0 0 0 1.732-1" }]
+ ];
+
+ const Film = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 3v18" }],
+ ["path", { d: "M3 7.5h4" }],
+ ["path", { d: "M3 12h18" }],
+ ["path", { d: "M3 16.5h4" }],
+ ["path", { d: "M17 3v18" }],
+ ["path", { d: "M17 7.5h4" }],
+ ["path", { d: "M17 16.5h4" }]
+ ];
+
+ const Fingerprint = [
+ ["path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }],
+ ["path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }],
+ ["path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }],
+ ["path", { d: "M2 12a10 10 0 0 1 18-6" }],
+ ["path", { d: "M2 16h.01" }],
+ ["path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }],
+ ["path", { d: "M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2" }],
+ ["path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }],
+ ["path", { d: "M9 6.8a6 6 0 0 1 9 5.2v2" }]
+ ];
+
+ const FireExtinguisher = [
+ ["path", { d: "M15 6.5V3a1 1 0 0 0-1-1h-2a1 1 0 0 0-1 1v3.5" }],
+ ["path", { d: "M9 18h8" }],
+ ["path", { d: "M18 3h-3" }],
+ ["path", { d: "M11 3a6 6 0 0 0-6 6v11" }],
+ ["path", { d: "M5 13h4" }],
+ ["path", { d: "M17 10a4 4 0 0 0-8 0v10a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2Z" }]
+ ];
+
+ const FishOff = [
+ [
+ "path",
+ {
+ d: "M18 12.47v.03m0-.5v.47m-.475 5.056A6.744 6.744 0 0 1 15 18c-3.56 0-7.56-2.53-8.5-6 .348-1.28 1.114-2.433 2.121-3.38m3.444-2.088A8.802 8.802 0 0 1 15 6c3.56 0 6.06 2.54 7 6-.309 1.14-.786 2.177-1.413 3.058"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M7 10.67C7 8 5.58 5.97 2.73 5.5c-1 1.5-1 5 .23 6.5-1.24 1.5-1.24 5-.23 6.5C5.58 18.03 7 16 7 13.33m7.48-4.372A9.77 9.77 0 0 1 16 6.07m0 11.86a9.77 9.77 0 0 1-1.728-3.618"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m16.01 17.93-.23 1.4A2 2 0 0 1 13.8 21H9.5a5.96 5.96 0 0 0 1.49-3.98M8.53 3h5.27a2 2 0 0 1 1.98 1.67l.23 1.4M2 2l20 20"
+ }
+ ]
+ ];
+
+ const FishSymbol = [["path", { d: "M2 16s9-15 20-4C11 23 2 8 2 8" }]];
+
+ const FlagOff = [
+ ["path", { d: "M16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M4 22V4" }],
+ ["path", { d: "M7.656 2H8c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10.347" }]
+ ];
+
+ const Fish = [
+ [
+ "path",
+ {
+ d: "M6.5 12c.94-3.46 4.94-6 8.5-6 3.56 0 6.06 2.54 7 6-.94 3.47-3.44 6-7 6s-7.56-2.53-8.5-6Z"
+ }
+ ],
+ ["path", { d: "M18 12v.5" }],
+ ["path", { d: "M16 17.93a9.77 9.77 0 0 1 0-11.86" }],
+ [
+ "path",
+ {
+ d: "M7 10.67C7 8 5.58 5.97 2.73 5.5c-1 1.5-1 5 .23 6.5-1.24 1.5-1.24 5-.23 6.5C5.58 18.03 7 16 7 13.33"
+ }
+ ],
+ ["path", { d: "M10.46 7.26C10.2 5.88 9.17 4.24 8 3h5.8a2 2 0 0 1 1.98 1.67l.23 1.4" }],
+ ["path", { d: "m16.01 17.93-.23 1.4A2 2 0 0 1 13.8 21H9.5a5.96 5.96 0 0 0 1.49-3.98" }]
+ ];
+
+ const FlagTriangleLeft = [
+ ["path", { d: "M18 22V2.8a.8.8 0 0 0-1.17-.71L5.45 7.78a.8.8 0 0 0 0 1.44L18 15.5" }]
+ ];
+
+ const FlagTriangleRight = [
+ ["path", { d: "M6 22V2.8a.8.8 0 0 1 1.17-.71l11.38 5.69a.8.8 0 0 1 0 1.44L6 15.5" }]
+ ];
+
+ const Flag = [
+ [
+ "path",
+ {
+ d: "M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"
+ }
+ ]
+ ];
+
+ const FlameKindling = [
+ [
+ "path",
+ {
+ d: "M12 2c1 3 2.5 3.5 3.5 4.5A5 5 0 0 1 17 10a5 5 0 1 1-10 0c0-.3 0-.6.1-.9a2 2 0 1 0 3.3-2C8 4.5 11 2 12 2Z"
+ }
+ ],
+ ["path", { d: "m5 22 14-4" }],
+ ["path", { d: "m5 18 14 4" }]
+ ];
+
+ const Flame = [
+ [
+ "path",
+ {
+ d: "M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0 5 5 0 0 1 1-3 1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4"
+ }
+ ]
+ ];
+
+ const FlashlightOff = [
+ ["path", { d: "M16 16v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V10c0-2-2-2-2-4" }],
+ ["path", { d: "M7 2h11v4c0 2-2 2-2 4v1" }],
+ ["line", { x1: "11", x2: "18", y1: "6", y2: "6" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Flashlight = [
+ ["path", { d: "M18 6c0 2-2 2-2 4v10a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V10c0-2-2-2-2-4V2h12z" }],
+ ["line", { x1: "6", x2: "18", y1: "6", y2: "6" }],
+ ["line", { x1: "12", x2: "12", y1: "12", y2: "12" }]
+ ];
+
+ const FlaskConicalOff = [
+ ["path", { d: "M10 2v2.343" }],
+ ["path", { d: "M14 2v6.343" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20 20a2 2 0 0 1-2 2H6a2 2 0 0 1-1.755-2.96l5.227-9.563" }],
+ ["path", { d: "M6.453 15H15" }],
+ ["path", { d: "M8.5 2h7" }]
+ ];
+
+ const FlaskConical = [
+ [
+ "path",
+ {
+ d: "M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"
+ }
+ ],
+ ["path", { d: "M6.453 15h11.094" }],
+ ["path", { d: "M8.5 2h7" }]
+ ];
+
+ const FlaskRound = [
+ ["path", { d: "M10 2v6.292a7 7 0 1 0 4 0V2" }],
+ ["path", { d: "M5 15h14" }],
+ ["path", { d: "M8.5 2h7" }]
+ ];
+
+ const FlipHorizontal2 = [
+ ["path", { d: "m3 7 5 5-5 5V7" }],
+ ["path", { d: "m21 7-5 5 5 5V7" }],
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "M12 14v2" }],
+ ["path", { d: "M12 8v2" }],
+ ["path", { d: "M12 2v2" }]
+ ];
+
+ const FlipHorizontal = [
+ ["path", { d: "M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3" }],
+ ["path", { d: "M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3" }],
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "M12 14v2" }],
+ ["path", { d: "M12 8v2" }],
+ ["path", { d: "M12 2v2" }]
+ ];
+
+ const FlipVertical2 = [
+ ["path", { d: "m17 3-5 5-5-5h10" }],
+ ["path", { d: "m17 21-5-5-5 5h10" }],
+ ["path", { d: "M4 12H2" }],
+ ["path", { d: "M10 12H8" }],
+ ["path", { d: "M16 12h-2" }],
+ ["path", { d: "M22 12h-2" }]
+ ];
+
+ const FlipVertical = [
+ ["path", { d: "M21 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "M21 16v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3" }],
+ ["path", { d: "M4 12H2" }],
+ ["path", { d: "M10 12H8" }],
+ ["path", { d: "M16 12h-2" }],
+ ["path", { d: "M22 12h-2" }]
+ ];
+
+ const Flower2 = [
+ [
+ "path",
+ {
+ d: "M12 5a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3v1M9 8a3 3 0 1 0 3 3M9 8h1m5 0a3 3 0 1 1-3 3m3-3h-1m-2 3v-1"
+ }
+ ],
+ ["circle", { cx: "12", cy: "8", r: "2" }],
+ ["path", { d: "M12 10v12" }],
+ ["path", { d: "M12 22c4.2 0 7-1.667 7-5-4.2 0-7 1.667-7 5Z" }],
+ ["path", { d: "M12 22c-4.2 0-7-1.667-7-5 4.2 0 7 1.667 7 5Z" }]
+ ];
+
+ const Flower = [
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ [
+ "path",
+ {
+ d: "M12 16.5A4.5 4.5 0 1 1 7.5 12 4.5 4.5 0 1 1 12 7.5a4.5 4.5 0 1 1 4.5 4.5 4.5 4.5 0 1 1-4.5 4.5"
+ }
+ ],
+ ["path", { d: "M12 7.5V9" }],
+ ["path", { d: "M7.5 12H9" }],
+ ["path", { d: "M16.5 12H15" }],
+ ["path", { d: "M12 16.5V15" }],
+ ["path", { d: "m8 8 1.88 1.88" }],
+ ["path", { d: "M14.12 9.88 16 8" }],
+ ["path", { d: "m8 16 1.88-1.88" }],
+ ["path", { d: "M14.12 14.12 16 16" }]
+ ];
+
+ const Focus = [
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }]
+ ];
+
+ const FoldHorizontal = [
+ ["path", { d: "M2 12h6" }],
+ ["path", { d: "M22 12h-6" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M12 8v2" }],
+ ["path", { d: "M12 14v2" }],
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "m19 9-3 3 3 3" }],
+ ["path", { d: "m5 15 3-3-3-3" }]
+ ];
+
+ const FoldVertical = [
+ ["path", { d: "M12 22v-6" }],
+ ["path", { d: "M12 8V2" }],
+ ["path", { d: "M4 12H2" }],
+ ["path", { d: "M10 12H8" }],
+ ["path", { d: "M16 12h-2" }],
+ ["path", { d: "M22 12h-2" }],
+ ["path", { d: "m15 19-3-3-3 3" }],
+ ["path", { d: "m15 5-3 3-3-3" }]
+ ];
+
+ const FolderArchive = [
+ ["circle", { cx: "15", cy: "19", r: "2" }],
+ [
+ "path",
+ {
+ d: "M20.9 19.8A2 2 0 0 0 22 18V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h5.1"
+ }
+ ],
+ ["path", { d: "M15 11v-1" }],
+ ["path", { d: "M15 17v-2" }]
+ ];
+
+ const FolderCheck = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "m9 13 2 2 4-4" }]
+ ];
+
+ const FolderClock = [
+ ["path", { d: "M16 14v2.2l1.6 1" }],
+ [
+ "path",
+ {
+ d: "M7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2"
+ }
+ ],
+ ["circle", { cx: "16", cy: "16", r: "6" }]
+ ];
+
+ const FolderClosed = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "M2 10h20" }]
+ ];
+
+ const FolderCode = [
+ ["path", { d: "M10 10.5 8 13l2 2.5" }],
+ ["path", { d: "m14 10.5 2 2.5-2 2.5" }],
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2z"
+ }
+ ]
+ ];
+
+ const FolderCog = [
+ [
+ "path",
+ {
+ d: "M10.3 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.98a2 2 0 0 1 1.69.9l.66 1.2A2 2 0 0 0 12 6h8a2 2 0 0 1 2 2v3.3"
+ }
+ ],
+ ["path", { d: "m14.305 19.53.923-.382" }],
+ ["path", { d: "m15.228 16.852-.923-.383" }],
+ ["path", { d: "m16.852 15.228-.383-.923" }],
+ ["path", { d: "m16.852 20.772-.383.924" }],
+ ["path", { d: "m19.148 15.228.383-.923" }],
+ ["path", { d: "m19.53 21.696-.382-.924" }],
+ ["path", { d: "m20.772 16.852.924-.383" }],
+ ["path", { d: "m20.772 19.148.924.383" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const FolderDot = [
+ [
+ "path",
+ {
+ d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "13", r: "1" }]
+ ];
+
+ const FolderDown = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "M12 10v6" }],
+ ["path", { d: "m15 13-3 3-3-3" }]
+ ];
+
+ const FolderGit2 = [
+ [
+ "path",
+ {
+ d: "M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5"
+ }
+ ],
+ ["circle", { cx: "13", cy: "12", r: "2" }],
+ ["path", { d: "M18 19c-2.8 0-5-2.2-5-5v8" }],
+ ["circle", { cx: "20", cy: "19", r: "2" }]
+ ];
+
+ const FolderGit = [
+ ["circle", { cx: "12", cy: "13", r: "2" }],
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "M14 13h3" }],
+ ["path", { d: "M7 13h3" }]
+ ];
+
+ const FolderHeart = [
+ [
+ "path",
+ {
+ d: "M10.638 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v3.417"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M14.62 18.8A2.25 2.25 0 1 1 18 15.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"
+ }
+ ]
+ ];
+
+ const FolderInput = [
+ [
+ "path",
+ {
+ d: "M2 9V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-1"
+ }
+ ],
+ ["path", { d: "M2 13h10" }],
+ ["path", { d: "m9 16 3-3-3-3" }]
+ ];
+
+ const FolderKanban = [
+ [
+ "path",
+ {
+ d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"
+ }
+ ],
+ ["path", { d: "M8 10v4" }],
+ ["path", { d: "M12 10v2" }],
+ ["path", { d: "M16 10v6" }]
+ ];
+
+ const FolderKey = [
+ ["circle", { cx: "16", cy: "20", r: "2" }],
+ [
+ "path",
+ {
+ d: "M10 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v2"
+ }
+ ],
+ ["path", { d: "m22 14-4.5 4.5" }],
+ ["path", { d: "m21 15 1 1" }]
+ ];
+
+ const FolderLock = [
+ ["rect", { width: "8", height: "5", x: "14", y: "17", rx: "1" }],
+ [
+ "path",
+ {
+ d: "M10 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v2.5"
+ }
+ ],
+ ["path", { d: "M20 17v-2a2 2 0 1 0-4 0v2" }]
+ ];
+
+ const FolderMinus = [
+ ["path", { d: "M9 13h6" }],
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ]
+ ];
+
+ const FolderOpenDot = [
+ [
+ "path",
+ {
+ d: "m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"
+ }
+ ],
+ ["circle", { cx: "14", cy: "15", r: "1" }]
+ ];
+
+ const FolderOutput = [
+ [
+ "path",
+ {
+ d: "M2 7.5V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-1.5"
+ }
+ ],
+ ["path", { d: "M2 13h10" }],
+ ["path", { d: "m5 10-3 3 3 3" }]
+ ];
+
+ const FolderOpen = [
+ [
+ "path",
+ {
+ d: "m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"
+ }
+ ]
+ ];
+
+ const FolderPen = [
+ [
+ "path",
+ {
+ d: "M2 11.5V5a2 2 0 0 1 2-2h3.9c.7 0 1.3.3 1.7.9l.8 1.2c.4.6 1 .9 1.7.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-9.5"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M11.378 13.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ]
+ ];
+
+ const FolderPlus = [
+ ["path", { d: "M12 10v6" }],
+ ["path", { d: "M9 13h6" }],
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ]
+ ];
+
+ const FolderRoot = [
+ [
+ "path",
+ {
+ d: "M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"
+ }
+ ],
+ ["circle", { cx: "12", cy: "13", r: "2" }],
+ ["path", { d: "M12 15v5" }]
+ ];
+
+ const FolderSearch2 = [
+ ["circle", { cx: "11.5", cy: "12.5", r: "2.5" }],
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "M13.3 14.3 15 16" }]
+ ];
+
+ const FolderSearch = [
+ [
+ "path",
+ {
+ d: "M10.7 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4.1"
+ }
+ ],
+ ["path", { d: "m21 21-1.9-1.9" }],
+ ["circle", { cx: "17", cy: "17", r: "3" }]
+ ];
+
+ const FolderSync = [
+ [
+ "path",
+ {
+ d: "M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v.5"
+ }
+ ],
+ ["path", { d: "M12 10v4h4" }],
+ ["path", { d: "m12 14 1.535-1.605a5 5 0 0 1 8 1.5" }],
+ ["path", { d: "M22 22v-4h-4" }],
+ ["path", { d: "m22 18-1.535 1.605a5 5 0 0 1-8-1.5" }]
+ ];
+
+ const FolderSymlink = [
+ [
+ "path",
+ {
+ d: "M2 9.35V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h7"
+ }
+ ],
+ ["path", { d: "m8 16 3-3-3-3" }]
+ ];
+
+ const FolderTree = [
+ [
+ "path",
+ {
+ d: "M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z"
+ }
+ ],
+ ["path", { d: "M3 5a2 2 0 0 0 2 2h3" }],
+ ["path", { d: "M3 3v13a2 2 0 0 0 2 2h3" }]
+ ];
+
+ const FolderUp = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "M12 10v6" }],
+ ["path", { d: "m9 13 3-3 3 3" }]
+ ];
+
+ const FolderX = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ],
+ ["path", { d: "m9.5 10.5 5 5" }],
+ ["path", { d: "m14.5 10.5-5 5" }]
+ ];
+
+ const Folder = [
+ [
+ "path",
+ {
+ d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
+ }
+ ]
+ ];
+
+ const Footprints = [
+ [
+ "path",
+ {
+ d: "M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z"
+ }
+ ],
+ ["path", { d: "M16 17h4" }],
+ ["path", { d: "M4 13h4" }]
+ ];
+
+ const Folders = [
+ [
+ "path",
+ {
+ d: "M20 5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h2.5a1.5 1.5 0 0 1 1.2.6l.6.8a1.5 1.5 0 0 0 1.2.6z"
+ }
+ ],
+ ["path", { d: "M3 8.268a2 2 0 0 0-1 1.738V19a2 2 0 0 0 2 2h11a2 2 0 0 0 1.732-1" }]
+ ];
+
+ const Forklift = [
+ ["path", { d: "M12 12H5a2 2 0 0 0-2 2v5" }],
+ ["circle", { cx: "13", cy: "19", r: "2" }],
+ ["circle", { cx: "5", cy: "19", r: "2" }],
+ ["path", { d: "M8 19h3m5-17v17h6M6 12V7c0-1.1.9-2 2-2h3l5 5" }]
+ ];
+
+ const Forward = [
+ ["path", { d: "m15 17 5-5-5-5" }],
+ ["path", { d: "M4 18v-2a4 4 0 0 1 4-4h12" }]
+ ];
+
+ const Frame = [
+ ["line", { x1: "22", x2: "2", y1: "6", y2: "6" }],
+ ["line", { x1: "22", x2: "2", y1: "18", y2: "18" }],
+ ["line", { x1: "6", x2: "6", y1: "2", y2: "22" }],
+ ["line", { x1: "18", x2: "18", y1: "2", y2: "22" }]
+ ];
+
+ const Framer = [["path", { d: "M5 16V9h14V2H5l14 14h-7m-7 0 7 7v-7m-7 0h7" }]];
+
+ const Frown = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M16 16s-1.5-2-4-2-4 2-4 2" }],
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
+ ];
+
+ const Fullscreen = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["rect", { width: "10", height: "8", x: "7", y: "8", rx: "1" }]
+ ];
+
+ const Fuel = [
+ ["path", { d: "M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 4 0v-6.998a2 2 0 0 0-.59-1.42L18 5" }],
+ ["path", { d: "M14 21V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v16" }],
+ ["path", { d: "M2 21h13" }],
+ ["path", { d: "M3 9h11" }]
+ ];
+
+ const FunnelPlus = [
+ [
+ "path",
+ {
+ d: "M13.354 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14v6a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341l1.218-1.348"
+ }
+ ],
+ ["path", { d: "M16 6h6" }],
+ ["path", { d: "M19 3v6" }]
+ ];
+
+ const FunnelX = [
+ [
+ "path",
+ {
+ d: "M12.531 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14v6a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341l.427-.473"
+ }
+ ],
+ ["path", { d: "m16.5 3.5 5 5" }],
+ ["path", { d: "m21.5 3.5-5 5" }]
+ ];
+
+ const Funnel = [
+ [
+ "path",
+ {
+ d: "M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"
+ }
+ ]
+ ];
+
+ const GalleryHorizontalEnd = [
+ ["path", { d: "M2 7v10" }],
+ ["path", { d: "M6 5v14" }],
+ ["rect", { width: "12", height: "18", x: "10", y: "3", rx: "2" }]
+ ];
+
+ const GalleryHorizontal = [
+ ["path", { d: "M2 3v18" }],
+ ["rect", { width: "12", height: "18", x: "6", y: "3", rx: "2" }],
+ ["path", { d: "M22 3v18" }]
+ ];
+
+ const GalleryThumbnails = [
+ ["rect", { width: "18", height: "14", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M4 21h1" }],
+ ["path", { d: "M9 21h1" }],
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "M19 21h1" }]
+ ];
+
+ const GalleryVerticalEnd = [
+ ["path", { d: "M7 2h10" }],
+ ["path", { d: "M5 6h14" }],
+ ["rect", { width: "18", height: "12", x: "3", y: "10", rx: "2" }]
+ ];
+
+ const GalleryVertical = [
+ ["path", { d: "M3 2h18" }],
+ ["rect", { width: "18", height: "12", x: "3", y: "6", rx: "2" }],
+ ["path", { d: "M3 22h18" }]
+ ];
+
+ const Gamepad2 = [
+ ["line", { x1: "6", x2: "10", y1: "11", y2: "11" }],
+ ["line", { x1: "8", x2: "8", y1: "9", y2: "13" }],
+ ["line", { x1: "15", x2: "15.01", y1: "12", y2: "12" }],
+ ["line", { x1: "18", x2: "18.01", y1: "10", y2: "10" }],
+ [
+ "path",
+ {
+ d: "M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"
+ }
+ ]
+ ];
+
+ const Gamepad = [
+ ["line", { x1: "6", x2: "10", y1: "12", y2: "12" }],
+ ["line", { x1: "8", x2: "8", y1: "10", y2: "14" }],
+ ["line", { x1: "15", x2: "15.01", y1: "13", y2: "13" }],
+ ["line", { x1: "18", x2: "18.01", y1: "11", y2: "11" }],
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const Gauge = [
+ ["path", { d: "m12 14 4-4" }],
+ ["path", { d: "M3.34 19a10 10 0 1 1 17.32 0" }]
+ ];
+
+ const Gavel = [
+ ["path", { d: "m14 13-8.381 8.38a1 1 0 0 1-3.001-3l8.384-8.381" }],
+ ["path", { d: "m16 16 6-6" }],
+ ["path", { d: "m21.5 10.5-8-8" }],
+ ["path", { d: "m8 8 6-6" }],
+ ["path", { d: "m8.5 7.5 8 8" }]
+ ];
+
+ const Gem = [
+ ["path", { d: "M10.5 3 8 9l4 13 4-13-2.5-6" }],
+ [
+ "path",
+ {
+ d: "M17 3a2 2 0 0 1 1.6.8l3 4a2 2 0 0 1 .013 2.382l-7.99 10.986a2 2 0 0 1-3.247 0l-7.99-10.986A2 2 0 0 1 2.4 7.8l2.998-3.997A2 2 0 0 1 7 3z"
+ }
+ ],
+ ["path", { d: "M2 9h20" }]
+ ];
+
+ const GeorgianLari = [
+ ["path", { d: "M11.5 21a7.5 7.5 0 1 1 7.35-9" }],
+ ["path", { d: "M13 12V3" }],
+ ["path", { d: "M4 21h16" }],
+ ["path", { d: "M9 12V3" }]
+ ];
+
+ const Ghost = [
+ ["path", { d: "M9 10h.01" }],
+ ["path", { d: "M15 10h.01" }],
+ ["path", { d: "M12 2a8 8 0 0 0-8 8v12l3-3 2.5 2.5L12 19l2.5 2.5L17 19l3 3V10a8 8 0 0 0-8-8z" }]
+ ];
+
+ const Gift = [
+ ["rect", { x: "3", y: "8", width: "18", height: "4", rx: "1" }],
+ ["path", { d: "M12 8v13" }],
+ ["path", { d: "M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7" }],
+ ["path", { d: "M7.5 8a2.5 2.5 0 0 1 0-5A4.8 8 0 0 1 12 8a4.8 8 0 0 1 4.5-5 2.5 2.5 0 0 1 0 5" }]
+ ];
+
+ const GitBranchPlus = [
+ ["path", { d: "M6 3v12" }],
+ ["path", { d: "M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }],
+ ["path", { d: "M6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }],
+ ["path", { d: "M15 6a9 9 0 0 0-9 9" }],
+ ["path", { d: "M18 15v6" }],
+ ["path", { d: "M21 18h-6" }]
+ ];
+
+ const GitBranch = [
+ ["line", { x1: "6", x2: "6", y1: "3", y2: "15" }],
+ ["circle", { cx: "18", cy: "6", r: "3" }],
+ ["circle", { cx: "6", cy: "18", r: "3" }],
+ ["path", { d: "M18 9a9 9 0 0 1-9 9" }]
+ ];
+
+ const GitCommitHorizontal = [
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["line", { x1: "3", x2: "9", y1: "12", y2: "12" }],
+ ["line", { x1: "15", x2: "21", y1: "12", y2: "12" }]
+ ];
+
+ const GitCommitVertical = [
+ ["path", { d: "M12 3v6" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["path", { d: "M12 15v6" }]
+ ];
+
+ const GitCompareArrows = [
+ ["circle", { cx: "5", cy: "6", r: "3" }],
+ ["path", { d: "M12 6h5a2 2 0 0 1 2 2v7" }],
+ ["path", { d: "m15 9-3-3 3-3" }],
+ ["circle", { cx: "19", cy: "18", r: "3" }],
+ ["path", { d: "M12 18H7a2 2 0 0 1-2-2V9" }],
+ ["path", { d: "m9 15 3 3-3 3" }]
+ ];
+
+ const GitCompare = [
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M13 6h3a2 2 0 0 1 2 2v7" }],
+ ["path", { d: "M11 18H8a2 2 0 0 1-2-2V9" }]
+ ];
+
+ const GitFork = [
+ ["circle", { cx: "12", cy: "18", r: "3" }],
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["circle", { cx: "18", cy: "6", r: "3" }],
+ ["path", { d: "M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" }],
+ ["path", { d: "M12 12v3" }]
+ ];
+
+ const GitGraph = [
+ ["circle", { cx: "5", cy: "6", r: "3" }],
+ ["path", { d: "M5 9v6" }],
+ ["circle", { cx: "5", cy: "18", r: "3" }],
+ ["path", { d: "M12 3v18" }],
+ ["circle", { cx: "19", cy: "6", r: "3" }],
+ ["path", { d: "M16 15.7A9 9 0 0 0 19 9" }]
+ ];
+
+ const GitMerge = [
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M6 21V9a9 9 0 0 0 9 9" }]
+ ];
+
+ const GitPullRequestArrow = [
+ ["circle", { cx: "5", cy: "6", r: "3" }],
+ ["path", { d: "M5 9v12" }],
+ ["circle", { cx: "19", cy: "18", r: "3" }],
+ ["path", { d: "m15 9-3-3 3-3" }],
+ ["path", { d: "M12 6h5a2 2 0 0 1 2 2v7" }]
+ ];
+
+ const GitPullRequestClosed = [
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M6 9v12" }],
+ ["path", { d: "m21 3-6 6" }],
+ ["path", { d: "m21 9-6-6" }],
+ ["path", { d: "M18 11.5V15" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const GitPullRequestCreateArrow = [
+ ["circle", { cx: "5", cy: "6", r: "3" }],
+ ["path", { d: "M5 9v12" }],
+ ["path", { d: "m15 9-3-3 3-3" }],
+ ["path", { d: "M12 6h5a2 2 0 0 1 2 2v3" }],
+ ["path", { d: "M19 15v6" }],
+ ["path", { d: "M22 18h-6" }]
+ ];
+
+ const GitPullRequestCreate = [
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M6 9v12" }],
+ ["path", { d: "M13 6h3a2 2 0 0 1 2 2v3" }],
+ ["path", { d: "M18 15v6" }],
+ ["path", { d: "M21 18h-6" }]
+ ];
+
+ const GitPullRequestDraft = [
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M18 6V5" }],
+ ["path", { d: "M18 11v-1" }],
+ ["line", { x1: "6", x2: "6", y1: "9", y2: "21" }]
+ ];
+
+ const GitPullRequest = [
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M13 6h3a2 2 0 0 1 2 2v7" }],
+ ["line", { x1: "6", x2: "6", y1: "9", y2: "21" }]
+ ];
+
+ const Github = [
+ [
+ "path",
+ {
+ d: "M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
+ }
+ ],
+ ["path", { d: "M9 18c-4.51 2-5-2-7-2" }]
+ ];
+
+ const Gitlab = [
+ [
+ "path",
+ {
+ d: "m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z"
+ }
+ ]
+ ];
+
+ const GlassWater = [
+ [
+ "path",
+ {
+ d: "M5.116 4.104A1 1 0 0 1 6.11 3h11.78a1 1 0 0 1 .994 1.105L17.19 20.21A2 2 0 0 1 15.2 22H8.8a2 2 0 0 1-2-1.79z"
+ }
+ ],
+ ["path", { d: "M6 12a5 5 0 0 1 6 0 5 5 0 0 0 6 0" }]
+ ];
+
+ const Glasses = [
+ ["circle", { cx: "6", cy: "15", r: "4" }],
+ ["circle", { cx: "18", cy: "15", r: "4" }],
+ ["path", { d: "M14 15a2 2 0 0 0-2-2 2 2 0 0 0-2 2" }],
+ ["path", { d: "M2.5 13 5 7c.7-1.3 1.4-2 3-2" }],
+ ["path", { d: "M21.5 13 19 7c-.7-1.3-1.5-2-3-2" }]
+ ];
+
+ const GlobeLock = [
+ ["path", { d: "M15.686 15A14.5 14.5 0 0 1 12 22a14.5 14.5 0 0 1 0-20 10 10 0 1 0 9.542 13" }],
+ ["path", { d: "M2 12h8.5" }],
+ ["path", { d: "M20 6V4a2 2 0 1 0-4 0v2" }],
+ ["rect", { width: "8", height: "5", x: "14", y: "6", rx: "1" }]
+ ];
+
+ const Globe = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }],
+ ["path", { d: "M2 12h20" }]
+ ];
+
+ const Goal = [
+ ["path", { d: "M12 13V2l8 4-8 4" }],
+ ["path", { d: "M20.561 10.222a9 9 0 1 1-12.55-5.29" }],
+ ["path", { d: "M8.002 9.997a5 5 0 1 0 8.9 2.02" }]
+ ];
+
+ const Gpu = [
+ ["path", { d: "M2 21V3" }],
+ ["path", { d: "M2 5h18a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2.26" }],
+ ["path", { d: "M7 17v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-3" }],
+ ["circle", { cx: "16", cy: "11", r: "2" }],
+ ["circle", { cx: "8", cy: "11", r: "2" }]
+ ];
+
+ const GraduationCap = [
+ [
+ "path",
+ {
+ d: "M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z"
+ }
+ ],
+ ["path", { d: "M22 10v6" }],
+ ["path", { d: "M6 12.5V16a6 3 0 0 0 12 0v-3.5" }]
+ ];
+
+ const Grape = [
+ ["path", { d: "M22 5V2l-5.89 5.89" }],
+ ["circle", { cx: "16.6", cy: "15.89", r: "3" }],
+ ["circle", { cx: "8.11", cy: "7.4", r: "3" }],
+ ["circle", { cx: "12.35", cy: "11.65", r: "3" }],
+ ["circle", { cx: "13.91", cy: "5.85", r: "3" }],
+ ["circle", { cx: "18.15", cy: "10.09", r: "3" }],
+ ["circle", { cx: "6.56", cy: "13.2", r: "3" }],
+ ["circle", { cx: "10.8", cy: "17.44", r: "3" }],
+ ["circle", { cx: "5", cy: "19", r: "3" }]
+ ];
+
+ const Grid2x2Check = [
+ [
+ "path",
+ {
+ d: "M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"
+ }
+ ],
+ ["path", { d: "m16 19 2 2 4-4" }]
+ ];
+
+ const Grid2x2Plus = [
+ [
+ "path",
+ {
+ d: "M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"
+ }
+ ],
+ ["path", { d: "M16 19h6" }],
+ ["path", { d: "M19 22v-6" }]
+ ];
+
+ const Grid2x2X = [
+ [
+ "path",
+ {
+ d: "M12 3v17a1 1 0 0 1-1 1H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v6a1 1 0 0 1-1 1H3"
+ }
+ ],
+ ["path", { d: "m16 16 5 5" }],
+ ["path", { d: "m16 21 5-5" }]
+ ];
+
+ const Grid2x2 = [
+ ["path", { d: "M12 3v18" }],
+ ["path", { d: "M3 12h18" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const Grid3x2 = [
+ ["path", { d: "M15 3v18" }],
+ ["path", { d: "M3 12h18" }],
+ ["path", { d: "M9 3v18" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const Grid3x3 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "M9 3v18" }],
+ ["path", { d: "M15 3v18" }]
+ ];
+
+ const GripHorizontal = [
+ ["circle", { cx: "12", cy: "9", r: "1" }],
+ ["circle", { cx: "19", cy: "9", r: "1" }],
+ ["circle", { cx: "5", cy: "9", r: "1" }],
+ ["circle", { cx: "12", cy: "15", r: "1" }],
+ ["circle", { cx: "19", cy: "15", r: "1" }],
+ ["circle", { cx: "5", cy: "15", r: "1" }]
+ ];
+
+ const Grip = [
+ ["circle", { cx: "12", cy: "5", r: "1" }],
+ ["circle", { cx: "19", cy: "5", r: "1" }],
+ ["circle", { cx: "5", cy: "5", r: "1" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ ["circle", { cx: "19", cy: "12", r: "1" }],
+ ["circle", { cx: "5", cy: "12", r: "1" }],
+ ["circle", { cx: "12", cy: "19", r: "1" }],
+ ["circle", { cx: "19", cy: "19", r: "1" }],
+ ["circle", { cx: "5", cy: "19", r: "1" }]
+ ];
+
+ const GripVertical = [
+ ["circle", { cx: "9", cy: "12", r: "1" }],
+ ["circle", { cx: "9", cy: "5", r: "1" }],
+ ["circle", { cx: "9", cy: "19", r: "1" }],
+ ["circle", { cx: "15", cy: "12", r: "1" }],
+ ["circle", { cx: "15", cy: "5", r: "1" }],
+ ["circle", { cx: "15", cy: "19", r: "1" }]
+ ];
+
+ const Group = [
+ ["path", { d: "M3 7V5c0-1.1.9-2 2-2h2" }],
+ ["path", { d: "M17 3h2c1.1 0 2 .9 2 2v2" }],
+ ["path", { d: "M21 17v2c0 1.1-.9 2-2 2h-2" }],
+ ["path", { d: "M7 21H5c-1.1 0-2-.9-2-2v-2" }],
+ ["rect", { width: "7", height: "5", x: "7", y: "7", rx: "1" }],
+ ["rect", { width: "7", height: "5", x: "10", y: "12", rx: "1" }]
+ ];
+
+ const Guitar = [
+ ["path", { d: "m11.9 12.1 4.514-4.514" }],
+ [
+ "path",
+ {
+ d: "M20.1 2.3a1 1 0 0 0-1.4 0l-1.114 1.114A2 2 0 0 0 17 4.828v1.344a2 2 0 0 1-.586 1.414A2 2 0 0 1 17.828 7h1.344a2 2 0 0 0 1.414-.586L21.7 5.3a1 1 0 0 0 0-1.4z"
+ }
+ ],
+ ["path", { d: "m6 16 2 2" }],
+ [
+ "path",
+ {
+ d: "M8.23 9.85A3 3 0 0 1 11 8a5 5 0 0 1 5 5 3 3 0 0 1-1.85 2.77l-.92.38A2 2 0 0 0 12 18a4 4 0 0 1-4 4 6 6 0 0 1-6-6 4 4 0 0 1 4-4 2 2 0 0 0 1.85-1.23z"
+ }
+ ]
+ ];
+
+ const Ham = [
+ ["path", { d: "M13.144 21.144A7.274 10.445 45 1 0 2.856 10.856" }],
+ [
+ "path",
+ { d: "M13.144 21.144A7.274 4.365 45 0 0 2.856 10.856a7.274 4.365 45 0 0 10.288 10.288" }
+ ],
+ [
+ "path",
+ {
+ d: "M16.565 10.435 18.6 8.4a2.501 2.501 0 1 0 1.65-4.65 2.5 2.5 0 1 0-4.66 1.66l-2.024 2.025"
+ }
+ ],
+ ["path", { d: "m8.5 16.5-1-1" }]
+ ];
+
+ const Hamburger = [
+ ["path", { d: "M12 16H4a2 2 0 1 1 0-4h16a2 2 0 1 1 0 4h-4.25" }],
+ ["path", { d: "M5 12a2 2 0 0 1-2-2 9 7 0 0 1 18 0 2 2 0 0 1-2 2" }],
+ ["path", { d: "M5 16a2 2 0 0 0-2 2 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 2 2 0 0 0-2-2q0 0 0 0" }],
+ ["path", { d: "m6.67 12 6.13 4.6a2 2 0 0 0 2.8-.4l3.15-4.2" }]
+ ];
+
+ const Hammer = [
+ ["path", { d: "m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" }],
+ ["path", { d: "m18 15 4-4" }],
+ [
+ "path",
+ {
+ d: "m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5"
+ }
+ ]
+ ];
+
+ const HandCoins = [
+ ["path", { d: "M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17" }],
+ [
+ "path",
+ {
+ d: "m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"
+ }
+ ],
+ ["path", { d: "m2 16 6 6" }],
+ ["circle", { cx: "16", cy: "9", r: "2.9" }],
+ ["circle", { cx: "6", cy: "5", r: "3" }]
+ ];
+
+ const HandFist = [
+ [
+ "path",
+ {
+ d: "M12.035 17.012a3 3 0 0 0-3-3l-.311-.002a.72.72 0 0 1-.505-1.229l1.195-1.195A2 2 0 0 1 10.828 11H12a2 2 0 0 0 0-4H9.243a3 3 0 0 0-2.122.879l-2.707 2.707A4.83 4.83 0 0 0 3 14a8 8 0 0 0 8 8h2a8 8 0 0 0 8-8V7a2 2 0 1 0-4 0v2a2 2 0 1 0 4 0"
+ }
+ ],
+ ["path", { d: "M13.888 9.662A2 2 0 0 0 17 8V5A2 2 0 1 0 13 5" }],
+ ["path", { d: "M9 5A2 2 0 1 0 5 5V10" }],
+ ["path", { d: "M9 7V4A2 2 0 1 1 13 4V7.268" }]
+ ];
+
+ const HandGrab = [
+ ["path", { d: "M18 11.5V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4" }],
+ ["path", { d: "M14 10V8a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
+ ["path", { d: "M10 9.9V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v5" }],
+ ["path", { d: "M6 14a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
+ ["path", { d: "M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-4a8 8 0 0 1-8-8 2 2 0 1 1 4 0" }]
+ ];
+
+ const HandHeart = [
+ ["path", { d: "M11 14h2a2 2 0 0 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 16" }],
+ [
+ "path",
+ {
+ d: "m14.45 13.39 5.05-4.694C20.196 8 21 6.85 21 5.75a2.75 2.75 0 0 0-4.797-1.837.276.276 0 0 1-.406 0A2.75 2.75 0 0 0 11 5.75c0 1.2.802 2.248 1.5 2.946L16 11.95"
+ }
+ ],
+ ["path", { d: "m2 15 6 6" }],
+ [
+ "path",
+ { d: "m7 20 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a1 1 0 0 0-2.75-2.91" }
+ ]
+ ];
+
+ const HandHelping = [
+ ["path", { d: "M11 12h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 14" }],
+ [
+ "path",
+ {
+ d: "m7 18 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"
+ }
+ ],
+ ["path", { d: "m2 13 6 6" }]
+ ];
+
+ const HandMetal = [
+ ["path", { d: "M18 12.5V10a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1.4" }],
+ ["path", { d: "M14 11V9a2 2 0 1 0-4 0v2" }],
+ ["path", { d: "M10 10.5V5a2 2 0 1 0-4 0v9" }],
+ [
+ "path",
+ {
+ d: "m7 15-1.76-1.76a2 2 0 0 0-2.83 2.82l3.6 3.6C7.5 21.14 9.2 22 12 22h2a8 8 0 0 0 8-8V7a2 2 0 1 0-4 0v5"
+ }
+ ]
+ ];
+
+ const HandPlatter = [
+ ["path", { d: "M12 3V2" }],
+ [
+ "path",
+ {
+ d: "m15.4 17.4 3.2-2.8a2 2 0 1 1 2.8 2.9l-3.6 3.3c-.7.8-1.7 1.2-2.8 1.2h-4c-1.1 0-2.1-.4-2.8-1.2l-1.302-1.464A1 1 0 0 0 6.151 19H5"
+ }
+ ],
+ ["path", { d: "M2 14h12a2 2 0 0 1 0 4h-2" }],
+ ["path", { d: "M4 10h16" }],
+ ["path", { d: "M5 10a7 7 0 0 1 14 0" }],
+ ["path", { d: "M5 14v6a1 1 0 0 1-1 1H2" }]
+ ];
+
+ const Hand = [
+ ["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
+ ["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2" }],
+ ["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8" }],
+ [
+ "path",
+ {
+ d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"
+ }
+ ]
+ ];
+
+ const Handbag = [
+ [
+ "path",
+ {
+ d: "M2.048 18.566A2 2 0 0 0 4 21h16a2 2 0 0 0 1.952-2.434l-2-9A2 2 0 0 0 18 8H6a2 2 0 0 0-1.952 1.566z"
+ }
+ ],
+ ["path", { d: "M8 11V6a4 4 0 0 1 8 0v5" }]
+ ];
+
+ const Handshake = [
+ ["path", { d: "m11 17 2 2a1 1 0 1 0 3-3" }],
+ [
+ "path",
+ {
+ d: "m14 14 2.5 2.5a1 1 0 1 0 3-3l-3.88-3.88a3 3 0 0 0-4.24 0l-.88.88a1 1 0 1 1-3-3l2.81-2.81a5.79 5.79 0 0 1 7.06-.87l.47.28a2 2 0 0 0 1.42.25L21 4"
+ }
+ ],
+ ["path", { d: "m21 3 1 11h-2" }],
+ ["path", { d: "M3 3 2 14l6.5 6.5a1 1 0 1 0 3-3" }],
+ ["path", { d: "M3 4h8" }]
+ ];
+
+ const HardDriveDownload = [
+ ["path", { d: "M12 2v8" }],
+ ["path", { d: "m16 6-4 4-4-4" }],
+ ["rect", { width: "20", height: "8", x: "2", y: "14", rx: "2" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "M10 18h.01" }]
+ ];
+
+ const HardDriveUpload = [
+ ["path", { d: "m16 6-4-4-4 4" }],
+ ["path", { d: "M12 2v8" }],
+ ["rect", { width: "20", height: "8", x: "2", y: "14", rx: "2" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "M10 18h.01" }]
+ ];
+
+ const HardHat = [
+ ["path", { d: "M10 10V5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5" }],
+ ["path", { d: "M14 6a6 6 0 0 1 6 6v3" }],
+ ["path", { d: "M4 15v-3a6 6 0 0 1 6-6" }],
+ ["rect", { x: "2", y: "15", width: "20", height: "4", rx: "1" }]
+ ];
+
+ const HardDrive = [
+ ["line", { x1: "22", x2: "2", y1: "12", y2: "12" }],
+ [
+ "path",
+ {
+ d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
+ }
+ ],
+ ["line", { x1: "6", x2: "6.01", y1: "16", y2: "16" }],
+ ["line", { x1: "10", x2: "10.01", y1: "16", y2: "16" }]
+ ];
+
+ const Hash = [
+ ["line", { x1: "4", x2: "20", y1: "9", y2: "9" }],
+ ["line", { x1: "4", x2: "20", y1: "15", y2: "15" }],
+ ["line", { x1: "10", x2: "8", y1: "3", y2: "21" }],
+ ["line", { x1: "16", x2: "14", y1: "3", y2: "21" }]
+ ];
+
+ const HatGlasses = [
+ ["path", { d: "M14 18a2 2 0 0 0-4 0" }],
+ [
+ "path",
+ {
+ d: "m19 11-2.11-6.657a2 2 0 0 0-2.752-1.148l-1.276.61A2 2 0 0 1 12 4H8.5a2 2 0 0 0-1.925 1.456L5 11"
+ }
+ ],
+ ["path", { d: "M2 11h20" }],
+ ["circle", { cx: "17", cy: "18", r: "3" }],
+ ["circle", { cx: "7", cy: "18", r: "3" }]
+ ];
+
+ const Haze = [
+ ["path", { d: "m5.2 6.2 1.4 1.4" }],
+ ["path", { d: "M2 13h2" }],
+ ["path", { d: "M20 13h2" }],
+ ["path", { d: "m17.4 7.6 1.4-1.4" }],
+ ["path", { d: "M22 17H2" }],
+ ["path", { d: "M22 21H2" }],
+ ["path", { d: "M16 13a4 4 0 0 0-8 0" }],
+ ["path", { d: "M12 5V2.5" }]
+ ];
+
+ const HdmiPort = [
+ [
+ "path",
+ { d: "M22 9a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h1l2 2h12l2-2h1a1 1 0 0 0 1-1Z" }
+ ],
+ ["path", { d: "M7.5 12h9" }]
+ ];
+
+ const Heading2 = [
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }],
+ ["path", { d: "M12 18V6" }],
+ ["path", { d: "M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1" }]
+ ];
+
+ const Heading1 = [
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }],
+ ["path", { d: "M12 18V6" }],
+ ["path", { d: "m17 12 3-2v8" }]
+ ];
+
+ const Heading3 = [
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }],
+ ["path", { d: "M12 18V6" }],
+ ["path", { d: "M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2" }],
+ ["path", { d: "M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2" }]
+ ];
+
+ const Heading4 = [
+ ["path", { d: "M12 18V6" }],
+ ["path", { d: "M17 10v3a1 1 0 0 0 1 1h3" }],
+ ["path", { d: "M21 10v8" }],
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }]
+ ];
+
+ const Heading5 = [
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }],
+ ["path", { d: "M12 18V6" }],
+ ["path", { d: "M17 13v-3h4" }],
+ ["path", { d: "M17 17.7c.4.2.8.3 1.3.3 1.5 0 2.7-1.1 2.7-2.5S19.8 13 18.3 13H17" }]
+ ];
+
+ const Heading = [
+ ["path", { d: "M6 12h12" }],
+ ["path", { d: "M6 20V4" }],
+ ["path", { d: "M18 20V4" }]
+ ];
+
+ const Heading6 = [
+ ["path", { d: "M4 12h8" }],
+ ["path", { d: "M4 18V6" }],
+ ["path", { d: "M12 18V6" }],
+ ["circle", { cx: "19", cy: "16", r: "2" }],
+ ["path", { d: "M20 10c-2 2-3 3.5-3 6" }]
+ ];
+
+ const HeadphoneOff = [
+ ["path", { d: "M21 14h-1.343" }],
+ ["path", { d: "M9.128 3.47A9 9 0 0 1 21 12v3.343" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20.414 20.414A2 2 0 0 1 19 21h-1a2 2 0 0 1-2-2v-3" }],
+ ["path", { d: "M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 2.636-6.364" }]
+ ];
+
+ const Headphones = [
+ [
+ "path",
+ {
+ d: "M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 18 0v7a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3"
+ }
+ ]
+ ];
+
+ const Headset = [
+ [
+ "path",
+ {
+ d: "M3 11h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5Zm0 0a9 9 0 1 1 18 0m0 0v5a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3Z"
+ }
+ ],
+ ["path", { d: "M21 16v2a4 4 0 0 1-4 4h-5" }]
+ ];
+
+ const HeartCrack = [
+ [
+ "path",
+ {
+ d: "M12.409 5.824c-.702.792-1.15 1.496-1.415 2.166l2.153 2.156a.5.5 0 0 1 0 .707l-2.293 2.293a.5.5 0 0 0 0 .707L12 15"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M13.508 20.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.677.6.6 0 0 0 .818.001A5.5 5.5 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5z"
+ }
+ ]
+ ];
+
+ const HeartHandshake = [
+ [
+ "path",
+ {
+ d: "M19.414 14.414C21 12.828 22 11.5 22 9.5a5.5 5.5 0 0 0-9.591-3.676.6.6 0 0 1-.818.001A5.5 5.5 0 0 0 2 9.5c0 2.3 1.5 4 3 5.5l5.535 5.362a2 2 0 0 0 2.879.052 2.12 2.12 0 0 0-.004-3 2.124 2.124 0 1 0 3-3 2.124 2.124 0 0 0 3.004 0 2 2 0 0 0 0-2.828l-1.881-1.882a2.41 2.41 0 0 0-3.409 0l-1.71 1.71a2 2 0 0 1-2.828 0 2 2 0 0 1 0-2.828l2.823-2.762"
+ }
+ ]
+ ];
+
+ const HeartMinus = [
+ [
+ "path",
+ {
+ d: "m14.876 18.99-1.368 1.323a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5a5.2 5.2 0 0 1-.244 1.572"
+ }
+ ],
+ ["path", { d: "M15 15h6" }]
+ ];
+
+ const HeartOff = [
+ [
+ "path",
+ {
+ d: "M10.5 4.893a5.5 5.5 0 0 1 1.091.931.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 1.872-1.002 3.356-2.187 4.655"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m16.967 16.967-3.459 3.346a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 2.747-4.761"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const HeartPlus = [
+ [
+ "path",
+ {
+ d: "m14.479 19.374-.971.939a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5a5.2 5.2 0 0 1-.219 1.49"
+ }
+ ],
+ ["path", { d: "M15 15h6" }],
+ ["path", { d: "M18 12v6" }]
+ ];
+
+ const HeartPulse = [
+ [
+ "path",
+ {
+ d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"
+ }
+ ],
+ ["path", { d: "M3.22 13H9.5l.5-1 2 4.5 2-7 1.5 3.5h5.27" }]
+ ];
+
+ const Heart = [
+ [
+ "path",
+ {
+ d: "M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5"
+ }
+ ]
+ ];
+
+ const Heater = [
+ ["path", { d: "M11 8c2-3-2-3 0-6" }],
+ ["path", { d: "M15.5 8c2-3-2-3 0-6" }],
+ ["path", { d: "M6 10h.01" }],
+ ["path", { d: "M6 14h.01" }],
+ ["path", { d: "M10 16v-4" }],
+ ["path", { d: "M14 16v-4" }],
+ ["path", { d: "M18 16v-4" }],
+ ["path", { d: "M20 6a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3" }],
+ ["path", { d: "M5 20v2" }],
+ ["path", { d: "M19 20v2" }]
+ ];
+
+ const Hexagon = [
+ [
+ "path",
+ {
+ d: "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
+ }
+ ]
+ ];
+
+ const Highlighter = [
+ ["path", { d: "m9 11-6 6v3h9l3-3" }],
+ ["path", { d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" }]
+ ];
+
+ const History = [
+ ["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
+ ["path", { d: "M3 3v5h5" }],
+ ["path", { d: "M12 7v5l4 2" }]
+ ];
+
+ const Hop = [
+ [
+ "path",
+ { d: "M10.82 16.12c1.69.6 3.91.79 5.18.85.55.03 1-.42.97-.97-.06-1.27-.26-3.5-.85-5.18" }
+ ],
+ [
+ "path",
+ {
+ d: "M11.5 6.5c1.64 0 5-.38 6.71-1.07.52-.2.55-.82.12-1.17A10 10 0 0 0 4.26 18.33c.35.43.96.4 1.17-.12.69-1.71 1.07-5.07 1.07-6.71 1.34.45 3.1.9 4.88.62a.88.88 0 0 0 .73-.74c.3-2.14-.15-3.5-.61-4.88"
+ }
+ ],
+ [
+ "path",
+ { d: "M15.62 16.95c.2.85.62 2.76.5 4.28a.77.77 0 0 1-.9.7 16.64 16.64 0 0 1-4.08-1.36" }
+ ],
+ [
+ "path",
+ { d: "M16.13 21.05c1.65.63 3.68.84 4.87.91a.9.9 0 0 0 .96-.96 17.68 17.68 0 0 0-.9-4.87" }
+ ],
+ [
+ "path",
+ { d: "M16.94 15.62c.86.2 2.77.62 4.29.5a.77.77 0 0 0 .7-.9 16.64 16.64 0 0 0-1.36-4.08" }
+ ],
+ [
+ "path",
+ { d: "M17.99 5.52a20.82 20.82 0 0 1 3.15 4.5.8.8 0 0 1-.68 1.13c-2.33.2-5.3-.32-8.27-1.57" }
+ ],
+ ["path", { d: "M4.93 4.93 3 3a.7.7 0 0 1 0-1" }],
+ [
+ "path",
+ {
+ d: "M9.58 12.18c1.24 2.98 1.77 5.95 1.57 8.28a.8.8 0 0 1-1.13.68 20.82 20.82 0 0 1-4.5-3.15"
+ }
+ ]
+ ];
+
+ const HopOff = [
+ ["path", { d: "M10.82 16.12c1.69.6 3.91.79 5.18.85.28.01.53-.09.7-.27" }],
+ [
+ "path",
+ { d: "M11.14 20.57c.52.24 2.44 1.12 4.08 1.37.46.06.86-.25.9-.71.12-1.52-.3-3.43-.5-4.28" }
+ ],
+ ["path", { d: "M16.13 21.05c1.65.63 3.68.84 4.87.91a.9.9 0 0 0 .7-.26" }],
+ [
+ "path",
+ { d: "M17.99 5.52a20.83 20.83 0 0 1 3.15 4.5.8.8 0 0 1-.68 1.13c-1.17.1-2.5.02-3.9-.25" }
+ ],
+ ["path", { d: "M20.57 11.14c.24.52 1.12 2.44 1.37 4.08.04.3-.08.59-.31.75" }],
+ [
+ "path",
+ {
+ d: "M4.93 4.93a10 10 0 0 0-.67 13.4c.35.43.96.4 1.17-.12.69-1.71 1.07-5.07 1.07-6.71 1.34.45 3.1.9 4.88.62a.85.85 0 0 0 .48-.24"
+ }
+ ],
+ [
+ "path",
+ { d: "M5.52 17.99c1.05.95 2.91 2.42 4.5 3.15a.8.8 0 0 0 1.13-.68c.2-2.34-.33-5.3-1.57-8.28" }
+ ],
+ ["path", { d: "M8.35 2.68a10 10 0 0 1 9.98 1.58c.43.35.4.96-.12 1.17-1.5.6-4.3.98-6.07 1.05" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Hospital = [
+ ["path", { d: "M12 7v4" }],
+ ["path", { d: "M14 21v-3a2 2 0 0 0-4 0v3" }],
+ ["path", { d: "M14 9h-4" }],
+ ["path", { d: "M18 11h2a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-9a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M18 21V5a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16" }]
+ ];
+
+ const Hotel = [
+ ["path", { d: "M10 22v-6.57" }],
+ ["path", { d: "M12 11h.01" }],
+ ["path", { d: "M12 7h.01" }],
+ ["path", { d: "M14 15.43V22" }],
+ ["path", { d: "M15 16a5 5 0 0 0-6 0" }],
+ ["path", { d: "M16 11h.01" }],
+ ["path", { d: "M16 7h.01" }],
+ ["path", { d: "M8 11h.01" }],
+ ["path", { d: "M8 7h.01" }],
+ ["rect", { x: "4", y: "2", width: "16", height: "20", rx: "2" }]
+ ];
+
+ const Hourglass = [
+ ["path", { d: "M5 22h14" }],
+ ["path", { d: "M5 2h14" }],
+ ["path", { d: "M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22" }],
+ ["path", { d: "M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2" }]
+ ];
+
+ const HouseHeart = [
+ [
+ "path",
+ {
+ d: "M8.62 13.8A2.25 2.25 0 1 1 12 10.836a2.25 2.25 0 1 1 3.38 2.966l-2.626 2.856a.998.998 0 0 1-1.507 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+ }
+ ]
+ ];
+
+ const HousePlug = [
+ ["path", { d: "M10 12V8.964" }],
+ ["path", { d: "M14 12V8.964" }],
+ ["path", { d: "M15 12a1 1 0 0 1 1 1v2a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-2a1 1 0 0 1 1-1z" }],
+ [
+ "path",
+ {
+ d: "M8.5 21H5a2 2 0 0 1-2-2v-9a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-2"
+ }
+ ]
+ ];
+
+ const HousePlus = [
+ [
+ "path",
+ {
+ d: "M12.35 21H5a2 2 0 0 1-2-2v-9a2 2 0 0 1 .71-1.53l7-6a2 2 0 0 1 2.58 0l7 6A2 2 0 0 1 21 10v2.35"
+ }
+ ],
+ ["path", { d: "M14.8 12.4A1 1 0 0 0 14 12h-4a1 1 0 0 0-1 1v8" }],
+ ["path", { d: "M15 18h6" }],
+ ["path", { d: "M18 15v6" }]
+ ];
+
+ const HouseWifi = [
+ ["path", { d: "M9.5 13.866a4 4 0 0 1 5 .01" }],
+ ["path", { d: "M12 17h.01" }],
+ [
+ "path",
+ {
+ d: "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+ }
+ ],
+ ["path", { d: "M7 10.754a8 8 0 0 1 10 0" }]
+ ];
+
+ const House = [
+ ["path", { d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" }],
+ [
+ "path",
+ {
+ d: "M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
+ }
+ ]
+ ];
+
+ const IceCreamBowl = [
+ [
+ "path",
+ { d: "M12 17c5 0 8-2.69 8-6H4c0 3.31 3 6 8 6m-4 4h8m-4-3v3M5.14 11a3.5 3.5 0 1 1 6.71 0" }
+ ],
+ ["path", { d: "M12.14 11a3.5 3.5 0 1 1 6.71 0" }],
+ ["path", { d: "M15.5 6.5a3.5 3.5 0 1 0-7 0" }]
+ ];
+
+ const IceCreamCone = [
+ ["path", { d: "m7 11 4.08 10.35a1 1 0 0 0 1.84 0L17 11" }],
+ ["path", { d: "M17 7A5 5 0 0 0 7 7" }],
+ ["path", { d: "M17 7a2 2 0 0 1 0 4H7a2 2 0 0 1 0-4" }]
+ ];
+
+ const IdCardLanyard = [
+ ["path", { d: "M13.5 8h-3" }],
+ ["path", { d: "m15 2-1 2h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h3" }],
+ ["path", { d: "M16.899 22A5 5 0 0 0 7.1 22" }],
+ ["path", { d: "m9 2 3 6" }],
+ ["circle", { cx: "12", cy: "15", r: "3" }]
+ ];
+
+ const IdCard = [
+ ["path", { d: "M16 10h2" }],
+ ["path", { d: "M16 14h2" }],
+ ["path", { d: "M6.17 15a3 3 0 0 1 5.66 0" }],
+ ["circle", { cx: "9", cy: "11", r: "2" }],
+ ["rect", { x: "2", y: "5", width: "20", height: "14", rx: "2" }]
+ ];
+
+ const ImageDown = [
+ [
+ "path",
+ {
+ d: "M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"
+ }
+ ],
+ ["path", { d: "m14 19 3 3v-5.5" }],
+ ["path", { d: "m17 22 3-3" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }]
+ ];
+
+ const ImageMinus = [
+ ["path", { d: "M21 9v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" }],
+ ["line", { x1: "16", x2: "22", y1: "5", y2: "5" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }],
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }]
+ ];
+
+ const ImageOff = [
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }],
+ ["path", { d: "M10.41 10.41a2 2 0 1 1-2.83-2.83" }],
+ ["line", { x1: "13.5", x2: "6", y1: "13.5", y2: "21" }],
+ ["line", { x1: "18", x2: "21", y1: "12", y2: "15" }],
+ ["path", { d: "M3.59 3.59A1.99 1.99 0 0 0 3 5v14a2 2 0 0 0 2 2h14c.55 0 1.052-.22 1.41-.59" }],
+ ["path", { d: "M21 15V5a2 2 0 0 0-2-2H9" }]
+ ];
+
+ const ImagePlay = [
+ [
+ "path",
+ {
+ d: "M15 15.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"
+ }
+ ],
+ ["path", { d: "M21 12.17V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6" }],
+ ["path", { d: "m6 21 5-5" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }]
+ ];
+
+ const ImagePlus = [
+ ["path", { d: "M16 5h6" }],
+ ["path", { d: "M19 2v6" }],
+ ["path", { d: "M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5" }],
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }]
+ ];
+
+ const ImageUp = [
+ [
+ "path",
+ {
+ d: "M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"
+ }
+ ],
+ ["path", { d: "m14 19.5 3-3 3 3" }],
+ ["path", { d: "M17 22v-5.5" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }]
+ ];
+
+ const ImageUpscale = [
+ ["path", { d: "M16 3h5v5" }],
+ ["path", { d: "M17 21h2a2 2 0 0 0 2-2" }],
+ ["path", { d: "M21 12v3" }],
+ ["path", { d: "m21 3-5 5" }],
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2" }],
+ ["path", { d: "m5 21 4.144-4.144a1.21 1.21 0 0 1 1.712 0L13 19" }],
+ ["path", { d: "M9 3h3" }],
+ ["rect", { x: "3", y: "11", width: "10", height: "10", rx: "1" }]
+ ];
+
+ const Image = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["circle", { cx: "9", cy: "9", r: "2" }],
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }]
+ ];
+
+ const Images = [
+ ["path", { d: "m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16" }],
+ ["path", { d: "M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2" }],
+ ["circle", { cx: "13", cy: "7", r: "1", fill: "currentColor" }],
+ ["rect", { x: "8", y: "2", width: "14", height: "14", rx: "2" }]
+ ];
+
+ const Import = [
+ ["path", { d: "M12 3v12" }],
+ ["path", { d: "m8 11 4 4 4-4" }],
+ ["path", { d: "M8 5H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4" }]
+ ];
+
+ const IndianRupee = [
+ ["path", { d: "M6 3h12" }],
+ ["path", { d: "M6 8h12" }],
+ ["path", { d: "m6 13 8.5 8" }],
+ ["path", { d: "M6 13h3" }],
+ ["path", { d: "M9 13c6.667 0 6.667-10 0-10" }]
+ ];
+
+ const Inbox = [
+ ["polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }],
+ [
+ "path",
+ {
+ d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
+ }
+ ]
+ ];
+
+ const Infinity = [
+ ["path", { d: "M6 16c5 0 7-8 12-8a4 4 0 0 1 0 8c-5 0-7-8-12-8a4 4 0 1 0 0 8" }]
+ ];
+
+ const Info = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M12 16v-4" }],
+ ["path", { d: "M12 8h.01" }]
+ ];
+
+ const InspectionPanel = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 7h.01" }],
+ ["path", { d: "M17 7h.01" }],
+ ["path", { d: "M7 17h.01" }],
+ ["path", { d: "M17 17h.01" }]
+ ];
+
+ const Instagram = [
+ ["rect", { width: "20", height: "20", x: "2", y: "2", rx: "5", ry: "5" }],
+ ["path", { d: "M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" }],
+ ["line", { x1: "17.5", x2: "17.51", y1: "6.5", y2: "6.5" }]
+ ];
+
+ const Italic = [
+ ["line", { x1: "19", x2: "10", y1: "4", y2: "4" }],
+ ["line", { x1: "14", x2: "5", y1: "20", y2: "20" }],
+ ["line", { x1: "15", x2: "9", y1: "4", y2: "20" }]
+ ];
+
+ const IterationCcw = [
+ ["path", { d: "m16 14 4 4-4 4" }],
+ ["path", { d: "M20 10a8 8 0 1 0-8 8h8" }]
+ ];
+
+ const IterationCw = [
+ ["path", { d: "M4 10a8 8 0 1 1 8 8H4" }],
+ ["path", { d: "m8 22-4-4 4-4" }]
+ ];
+
+ const JapaneseYen = [
+ ["path", { d: "M12 9.5V21m0-11.5L6 3m6 6.5L18 3" }],
+ ["path", { d: "M6 15h12" }],
+ ["path", { d: "M6 11h12" }]
+ ];
+
+ const Joystick = [
+ ["path", { d: "M21 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-2Z" }],
+ ["path", { d: "M6 15v-2" }],
+ ["path", { d: "M12 15V9" }],
+ ["circle", { cx: "12", cy: "6", r: "3" }]
+ ];
+
+ const Kanban = [
+ ["path", { d: "M5 3v14" }],
+ ["path", { d: "M12 3v8" }],
+ ["path", { d: "M19 3v18" }]
+ ];
+
+ const Kayak = [
+ ["path", { d: "M18 17a1 1 0 0 0-1 1v1a2 2 0 1 0 2-2z" }],
+ [
+ "path",
+ {
+ d: "M20.97 3.61a.45.45 0 0 0-.58-.58C10.2 6.6 6.6 10.2 3.03 20.39a.45.45 0 0 0 .58.58C13.8 17.4 17.4 13.8 20.97 3.61"
+ }
+ ],
+ ["path", { d: "m6.707 6.707 10.586 10.586" }],
+ ["path", { d: "M7 5a2 2 0 1 0-2 2h1a1 1 0 0 0 1-1z" }]
+ ];
+
+ const KeyRound = [
+ [
+ "path",
+ {
+ d: "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"
+ }
+ ],
+ ["circle", { cx: "16.5", cy: "7.5", r: ".5", fill: "currentColor" }]
+ ];
+
+ const KeySquare = [
+ [
+ "path",
+ {
+ d: "M12.4 2.7a2.5 2.5 0 0 1 3.4 0l5.5 5.5a2.5 2.5 0 0 1 0 3.4l-3.7 3.7a2.5 2.5 0 0 1-3.4 0L8.7 9.8a2.5 2.5 0 0 1 0-3.4z"
+ }
+ ],
+ ["path", { d: "m14 7 3 3" }],
+ [
+ "path",
+ {
+ d: "m9.4 10.6-6.814 6.814A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814"
+ }
+ ]
+ ];
+
+ const Key = [
+ ["path", { d: "m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" }],
+ ["path", { d: "m21 2-9.6 9.6" }],
+ ["circle", { cx: "7.5", cy: "15.5", r: "5.5" }]
+ ];
+
+ const KeyboardMusic = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M6 8h4" }],
+ ["path", { d: "M14 8h.01" }],
+ ["path", { d: "M18 8h.01" }],
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "M6 12v4" }],
+ ["path", { d: "M10 12v4" }],
+ ["path", { d: "M14 12v4" }],
+ ["path", { d: "M18 12v4" }]
+ ];
+
+ const KeyboardOff = [
+ ["path", { d: "M 20 4 A2 2 0 0 1 22 6" }],
+ ["path", { d: "M 22 6 L 22 16.41" }],
+ ["path", { d: "M 7 16 L 16 16" }],
+ ["path", { d: "M 9.69 4 L 20 4" }],
+ ["path", { d: "M14 8h.01" }],
+ ["path", { d: "M18 8h.01" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20 20H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" }],
+ ["path", { d: "M6 8h.01" }],
+ ["path", { d: "M8 12h.01" }]
+ ];
+
+ const Keyboard = [
+ ["path", { d: "M10 8h.01" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M14 8h.01" }],
+ ["path", { d: "M16 12h.01" }],
+ ["path", { d: "M18 8h.01" }],
+ ["path", { d: "M6 8h.01" }],
+ ["path", { d: "M7 16h10" }],
+ ["path", { d: "M8 12h.01" }],
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }]
+ ];
+
+ const LampCeiling = [
+ ["path", { d: "M12 2v5" }],
+ ["path", { d: "M14.829 15.998a3 3 0 1 1-5.658 0" }],
+ [
+ "path",
+ {
+ d: "M20.92 14.606A1 1 0 0 1 20 16H4a1 1 0 0 1-.92-1.394l3-7A1 1 0 0 1 7 7h10a1 1 0 0 1 .92.606z"
+ }
+ ]
+ ];
+
+ const LampDesk = [
+ [
+ "path",
+ {
+ d: "M10.293 2.293a1 1 0 0 1 1.414 0l2.5 2.5 5.994 1.227a1 1 0 0 1 .506 1.687l-7 7a1 1 0 0 1-1.687-.506l-1.227-5.994-2.5-2.5a1 1 0 0 1 0-1.414z"
+ }
+ ],
+ ["path", { d: "m14.207 4.793-3.414 3.414" }],
+ ["path", { d: "M3 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z" }],
+ ["path", { d: "m9.086 6.5-4.793 4.793a1 1 0 0 0-.18 1.17L7 18" }]
+ ];
+
+ const LampFloor = [
+ ["path", { d: "M12 10v12" }],
+ [
+ "path",
+ {
+ d: "M17.929 7.629A1 1 0 0 1 17 9H7a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 9 2h6a1 1 0 0 1 .928.629z"
+ }
+ ],
+ ["path", { d: "M9 22h6" }]
+ ];
+
+ const LampWallDown = [
+ [
+ "path",
+ {
+ d: "M19.929 18.629A1 1 0 0 1 19 20H9a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 11 13h6a1 1 0 0 1 .928.629z"
+ }
+ ],
+ ["path", { d: "M6 3a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }],
+ ["path", { d: "M8 6h4a2 2 0 0 1 2 2v5" }]
+ ];
+
+ const LampWallUp = [
+ [
+ "path",
+ {
+ d: "M19.929 9.629A1 1 0 0 1 19 11H9a1 1 0 0 1-.928-1.371l2-5A1 1 0 0 1 11 4h6a1 1 0 0 1 .928.629z"
+ }
+ ],
+ ["path", { d: "M6 15a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H5a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1z" }],
+ ["path", { d: "M8 18h4a2 2 0 0 0 2-2v-5" }]
+ ];
+
+ const Lamp = [
+ ["path", { d: "M12 12v6" }],
+ [
+ "path",
+ {
+ d: "M4.077 10.615A1 1 0 0 0 5 12h14a1 1 0 0 0 .923-1.385l-3.077-7.384A2 2 0 0 0 15 2H9a2 2 0 0 0-1.846 1.23Z"
+ }
+ ],
+ ["path", { d: "M8 20a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1z" }]
+ ];
+
+ const LandPlot = [
+ ["path", { d: "m12 8 6-3-6-3v10" }],
+ [
+ "path",
+ {
+ d: "m8 11.99-5.5 3.14a1 1 0 0 0 0 1.74l8.5 4.86a2 2 0 0 0 2 0l8.5-4.86a1 1 0 0 0 0-1.74L16 12"
+ }
+ ],
+ ["path", { d: "m6.49 12.85 11.02 6.3" }],
+ ["path", { d: "M17.51 12.85 6.5 19.15" }]
+ ];
+
+ const Landmark = [
+ ["path", { d: "M10 18v-7" }],
+ [
+ "path",
+ {
+ d: "M11.12 2.198a2 2 0 0 1 1.76.006l7.866 3.847c.476.233.31.949-.22.949H3.474c-.53 0-.695-.716-.22-.949z"
+ }
+ ],
+ ["path", { d: "M14 18v-7" }],
+ ["path", { d: "M18 18v-7" }],
+ ["path", { d: "M3 22h18" }],
+ ["path", { d: "M6 18v-7" }]
+ ];
+
+ const Languages = [
+ ["path", { d: "m5 8 6 6" }],
+ ["path", { d: "m4 14 6-6 2-3" }],
+ ["path", { d: "M2 5h12" }],
+ ["path", { d: "M7 2h1" }],
+ ["path", { d: "m22 22-5-10-5 10" }],
+ ["path", { d: "M14 18h6" }]
+ ];
+
+ const LaptopMinimalCheck = [
+ ["path", { d: "M2 20h20" }],
+ ["path", { d: "m9 10 2 2 4-4" }],
+ ["rect", { x: "3", y: "4", width: "18", height: "12", rx: "2" }]
+ ];
+
+ const LaptopMinimal = [
+ ["rect", { width: "18", height: "12", x: "3", y: "4", rx: "2", ry: "2" }],
+ ["line", { x1: "2", x2: "22", y1: "20", y2: "20" }]
+ ];
+
+ const Laptop = [
+ [
+ "path",
+ {
+ d: "M18 5a2 2 0 0 1 2 2v8.526a2 2 0 0 0 .212.897l1.068 2.127a1 1 0 0 1-.9 1.45H3.62a1 1 0 0 1-.9-1.45l1.068-2.127A2 2 0 0 0 4 15.526V7a2 2 0 0 1 2-2z"
+ }
+ ],
+ ["path", { d: "M20.054 15.987H3.946" }]
+ ];
+
+ const LassoSelect = [
+ ["path", { d: "M7 22a5 5 0 0 1-2-4" }],
+ ["path", { d: "M7 16.93c.96.43 1.96.74 2.99.91" }],
+ [
+ "path",
+ { d: "M3.34 14A6.8 6.8 0 0 1 2 10c0-4.42 4.48-8 10-8s10 3.58 10 8a7.19 7.19 0 0 1-.33 2" }
+ ],
+ ["path", { d: "M5 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" }],
+ [
+ "path",
+ {
+ d: "M14.33 22h-.09a.35.35 0 0 1-.24-.32v-10a.34.34 0 0 1 .33-.34c.08 0 .15.03.21.08l7.34 6a.33.33 0 0 1-.21.59h-4.49l-2.57 3.85a.35.35 0 0 1-.28.14z"
+ }
+ ]
+ ];
+
+ const Lasso = [
+ [
+ "path",
+ { d: "M3.704 14.467A10 8 0 0 1 2 10a10 8 0 0 1 20 0 10 8 0 0 1-10 8 10 8 0 0 1-5.181-1.158" }
+ ],
+ ["path", { d: "M7 22a5 5 0 0 1-2-3.994" }],
+ ["circle", { cx: "5", cy: "16", r: "2" }]
+ ];
+
+ const Laugh = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M18 13a6 6 0 0 1-6 5 6 6 0 0 1-6-5h12Z" }],
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
+ ];
+
+ const Layers = [
+ [
+ "path",
+ {
+ d: "M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"
+ }
+ ],
+ ["path", { d: "M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12" }],
+ ["path", { d: "M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17" }]
+ ];
+
+ const Layers2 = [
+ [
+ "path",
+ {
+ d: "M13 13.74a2 2 0 0 1-2 0L2.5 8.87a1 1 0 0 1 0-1.74L11 2.26a2 2 0 0 1 2 0l8.5 4.87a1 1 0 0 1 0 1.74z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m20 14.285 1.5.845a1 1 0 0 1 0 1.74L13 21.74a2 2 0 0 1-2 0l-8.5-4.87a1 1 0 0 1 0-1.74l1.5-.845"
+ }
+ ]
+ ];
+
+ const LayoutDashboard = [
+ ["rect", { width: "7", height: "9", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "5", x: "14", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "9", x: "14", y: "12", rx: "1" }],
+ ["rect", { width: "7", height: "5", x: "3", y: "16", rx: "1" }]
+ ];
+
+ const LayoutGrid = [
+ ["rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" }]
+ ];
+
+ const LayoutList = [
+ ["rect", { width: "7", height: "7", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" }],
+ ["path", { d: "M14 4h7" }],
+ ["path", { d: "M14 9h7" }],
+ ["path", { d: "M14 15h7" }],
+ ["path", { d: "M14 20h7" }]
+ ];
+
+ const LayoutPanelLeft = [
+ ["rect", { width: "7", height: "18", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "14", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }]
+ ];
+
+ const LayoutPanelTop = [
+ ["rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "3", y: "14", rx: "1" }],
+ ["rect", { width: "7", height: "7", x: "14", y: "14", rx: "1" }]
+ ];
+
+ const LayoutTemplate = [
+ ["rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }],
+ ["rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" }]
+ ];
+
+ const Leaf = [
+ [
+ "path",
+ { d: "M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z" }
+ ],
+ ["path", { d: "M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" }]
+ ];
+
+ const LeafyGreen = [
+ [
+ "path",
+ {
+ d: "M2 22c1.25-.987 2.27-1.975 3.9-2.2a5.56 5.56 0 0 1 3.8 1.5 4 4 0 0 0 6.187-2.353 3.5 3.5 0 0 0 3.69-5.116A3.5 3.5 0 0 0 20.95 8 3.5 3.5 0 1 0 16 3.05a3.5 3.5 0 0 0-5.831 1.373 3.5 3.5 0 0 0-5.116 3.69 4 4 0 0 0-2.348 6.155C3.499 15.42 4.409 16.712 4.2 18.1 3.926 19.743 3.014 20.732 2 22"
+ }
+ ],
+ ["path", { d: "M2 22 17 7" }]
+ ];
+
+ const Lectern = [
+ [
+ "path",
+ {
+ d: "M16 12h3a2 2 0 0 0 1.902-1.38l1.056-3.333A1 1 0 0 0 21 6H3a1 1 0 0 0-.958 1.287l1.056 3.334A2 2 0 0 0 5 12h3"
+ }
+ ],
+ ["path", { d: "M18 6V3a1 1 0 0 0-1-1h-3" }],
+ ["rect", { width: "8", height: "12", x: "8", y: "10", rx: "1" }]
+ ];
+
+ const LibraryBig = [
+ ["rect", { width: "8", height: "18", x: "3", y: "3", rx: "1" }],
+ ["path", { d: "M7 3v18" }],
+ [
+ "path",
+ {
+ d: "M20.4 18.9c.2.5-.1 1.1-.6 1.3l-1.9.7c-.5.2-1.1-.1-1.3-.6L11.1 5.1c-.2-.5.1-1.1.6-1.3l1.9-.7c.5-.2 1.1.1 1.3.6Z"
+ }
+ ]
+ ];
+
+ const Library = [
+ ["path", { d: "m16 6 4 14" }],
+ ["path", { d: "M12 6v14" }],
+ ["path", { d: "M8 8v12" }],
+ ["path", { d: "M4 4v16" }]
+ ];
+
+ const LifeBuoy = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "m4.93 4.93 4.24 4.24" }],
+ ["path", { d: "m14.83 9.17 4.24-4.24" }],
+ ["path", { d: "m14.83 14.83 4.24 4.24" }],
+ ["path", { d: "m9.17 14.83-4.24 4.24" }],
+ ["circle", { cx: "12", cy: "12", r: "4" }]
+ ];
+
+ const Ligature = [
+ ["path", { d: "M14 12h2v8" }],
+ ["path", { d: "M14 20h4" }],
+ ["path", { d: "M6 12h4" }],
+ ["path", { d: "M6 20h4" }],
+ ["path", { d: "M8 20V8a4 4 0 0 1 7.464-2" }]
+ ];
+
+ const LightbulbOff = [
+ ["path", { d: "M16.8 11.2c.8-.9 1.2-2 1.2-3.2a6 6 0 0 0-9.3-5" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M6.3 6.3a4.67 4.67 0 0 0 1.2 5.2c.7.7 1.3 1.5 1.5 2.5" }],
+ ["path", { d: "M9 18h6" }],
+ ["path", { d: "M10 22h4" }]
+ ];
+
+ const Lightbulb = [
+ [
+ "path",
+ {
+ d: "M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"
+ }
+ ],
+ ["path", { d: "M9 18h6" }],
+ ["path", { d: "M10 22h4" }]
+ ];
+
+ const LineSquiggle = [
+ [
+ "path",
+ { d: "M7 3.5c5-2 7 2.5 3 4C1.5 10 2 15 5 16c5 2 9-10 14-7s.5 13.5-4 12c-5-2.5.5-11 6-2" }
+ ]
+ ];
+
+ const Link2Off = [
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7" }],
+ ["path", { d: "M15 7h2a5 5 0 0 1 4 8" }],
+ ["line", { x1: "8", x2: "12", y1: "12", y2: "12" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Link2 = [
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }],
+ ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }],
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12" }]
+ ];
+
+ const Link = [
+ ["path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }],
+ ["path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" }]
+ ];
+
+ const Linkedin = [
+ ["path", { d: "M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" }],
+ ["rect", { width: "4", height: "12", x: "2", y: "9" }],
+ ["circle", { cx: "4", cy: "4", r: "2" }]
+ ];
+
+ const ListCheck = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M16 12H3" }],
+ ["path", { d: "M11 19H3" }],
+ ["path", { d: "m15 18 2 2 4-4" }]
+ ];
+
+ const ListChecks = [
+ ["path", { d: "M13 5h8" }],
+ ["path", { d: "M13 12h8" }],
+ ["path", { d: "M13 19h8" }],
+ ["path", { d: "m3 17 2 2 4-4" }],
+ ["path", { d: "m3 7 2 2 4-4" }]
+ ];
+
+ const ListChevronsDownUp = [
+ ["path", { d: "M3 5h8" }],
+ ["path", { d: "M3 12h8" }],
+ ["path", { d: "M3 19h8" }],
+ ["path", { d: "m15 5 3 3 3-3" }],
+ ["path", { d: "m15 19 3-3 3 3" }]
+ ];
+
+ const ListChevronsUpDown = [
+ ["path", { d: "M3 5h8" }],
+ ["path", { d: "M3 12h8" }],
+ ["path", { d: "M3 19h8" }],
+ ["path", { d: "m15 8 3-3 3 3" }],
+ ["path", { d: "m15 16 3 3 3-3" }]
+ ];
+
+ const ListCollapse = [
+ ["path", { d: "M10 5h11" }],
+ ["path", { d: "M10 12h11" }],
+ ["path", { d: "M10 19h11" }],
+ ["path", { d: "m3 10 3-3-3-3" }],
+ ["path", { d: "m3 20 3-3-3-3" }]
+ ];
+
+ const ListEnd = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M16 12H3" }],
+ ["path", { d: "M9 19H3" }],
+ ["path", { d: "m16 16-3 3 3 3" }],
+ ["path", { d: "M21 5v12a2 2 0 0 1-2 2h-6" }]
+ ];
+
+ const ListFilterPlus = [
+ ["path", { d: "M12 5H2" }],
+ ["path", { d: "M6 12h12" }],
+ ["path", { d: "M9 19h6" }],
+ ["path", { d: "M16 5h6" }],
+ ["path", { d: "M19 8V2" }]
+ ];
+
+ const ListFilter = [
+ ["path", { d: "M2 5h20" }],
+ ["path", { d: "M6 12h12" }],
+ ["path", { d: "M9 19h6" }]
+ ];
+
+ const ListIndentDecrease = [
+ ["path", { d: "M21 5H11" }],
+ ["path", { d: "M21 12H11" }],
+ ["path", { d: "M21 19H11" }],
+ ["path", { d: "m7 8-4 4 4 4" }]
+ ];
+
+ const ListIndentIncrease = [
+ ["path", { d: "M21 5H11" }],
+ ["path", { d: "M21 12H11" }],
+ ["path", { d: "M21 19H11" }],
+ ["path", { d: "m3 8 4 4-4 4" }]
+ ];
+
+ const ListMinus = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M11 12H3" }],
+ ["path", { d: "M16 19H3" }],
+ ["path", { d: "M21 12h-6" }]
+ ];
+
+ const ListMusic = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M11 12H3" }],
+ ["path", { d: "M11 19H3" }],
+ ["path", { d: "M21 16V5" }],
+ ["circle", { cx: "18", cy: "16", r: "3" }]
+ ];
+
+ const ListOrdered = [
+ ["path", { d: "M11 5h10" }],
+ ["path", { d: "M11 12h10" }],
+ ["path", { d: "M11 19h10" }],
+ ["path", { d: "M4 4h1v5" }],
+ ["path", { d: "M4 9h2" }],
+ ["path", { d: "M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02" }]
+ ];
+
+ const ListPlus = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M11 12H3" }],
+ ["path", { d: "M16 19H3" }],
+ ["path", { d: "M18 9v6" }],
+ ["path", { d: "M21 12h-6" }]
+ ];
+
+ const ListRestart = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M7 12H3" }],
+ ["path", { d: "M7 19H3" }],
+ ["path", { d: "M12 18a5 5 0 0 0 9-3 4.5 4.5 0 0 0-4.5-4.5c-1.33 0-2.54.54-3.41 1.41L11 14" }],
+ ["path", { d: "M11 10v4h4" }]
+ ];
+
+ const ListStart = [
+ ["path", { d: "M3 5h6" }],
+ ["path", { d: "M3 12h13" }],
+ ["path", { d: "M3 19h13" }],
+ ["path", { d: "m16 8-3-3 3-3" }],
+ ["path", { d: "M21 19V7a2 2 0 0 0-2-2h-6" }]
+ ];
+
+ const ListTodo = [
+ ["path", { d: "M13 5h8" }],
+ ["path", { d: "M13 12h8" }],
+ ["path", { d: "M13 19h8" }],
+ ["path", { d: "m3 17 2 2 4-4" }],
+ ["rect", { x: "3", y: "4", width: "6", height: "6", rx: "1" }]
+ ];
+
+ const ListTree = [
+ ["path", { d: "M8 5h13" }],
+ ["path", { d: "M13 12h8" }],
+ ["path", { d: "M13 19h8" }],
+ ["path", { d: "M3 10a2 2 0 0 0 2 2h3" }],
+ ["path", { d: "M3 5v12a2 2 0 0 0 2 2h3" }]
+ ];
+
+ const ListVideo = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M10 12H3" }],
+ ["path", { d: "M10 19H3" }],
+ [
+ "path",
+ {
+ d: "M15 12.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z"
+ }
+ ]
+ ];
+
+ const ListX = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M11 12H3" }],
+ ["path", { d: "M16 19H3" }],
+ ["path", { d: "m15.5 9.5 5 5" }],
+ ["path", { d: "m20.5 9.5-5 5" }]
+ ];
+
+ const List = [
+ ["path", { d: "M3 5h.01" }],
+ ["path", { d: "M3 12h.01" }],
+ ["path", { d: "M3 19h.01" }],
+ ["path", { d: "M8 5h13" }],
+ ["path", { d: "M8 12h13" }],
+ ["path", { d: "M8 19h13" }]
+ ];
+
+ const LoaderCircle = [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }]];
+
+ const LoaderPinwheel = [
+ ["path", { d: "M22 12a1 1 0 0 1-10 0 1 1 0 0 0-10 0" }],
+ ["path", { d: "M7 20.7a1 1 0 1 1 5-8.7 1 1 0 1 0 5-8.6" }],
+ ["path", { d: "M7 3.3a1 1 0 1 1 5 8.6 1 1 0 1 0 5 8.6" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Loader = [
+ ["path", { d: "M12 2v4" }],
+ ["path", { d: "m16.2 7.8 2.9-2.9" }],
+ ["path", { d: "M18 12h4" }],
+ ["path", { d: "m16.2 16.2 2.9 2.9" }],
+ ["path", { d: "M12 18v4" }],
+ ["path", { d: "m4.9 19.1 2.9-2.9" }],
+ ["path", { d: "M2 12h4" }],
+ ["path", { d: "m4.9 4.9 2.9 2.9" }]
+ ];
+
+ const LocateFixed = [
+ ["line", { x1: "2", x2: "5", y1: "12", y2: "12" }],
+ ["line", { x1: "19", x2: "22", y1: "12", y2: "12" }],
+ ["line", { x1: "12", x2: "12", y1: "2", y2: "5" }],
+ ["line", { x1: "12", x2: "12", y1: "19", y2: "22" }],
+ ["circle", { cx: "12", cy: "12", r: "7" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }]
+ ];
+
+ const LocateOff = [
+ ["path", { d: "M12 19v3" }],
+ ["path", { d: "M12 2v3" }],
+ ["path", { d: "M18.89 13.24a7 7 0 0 0-8.13-8.13" }],
+ ["path", { d: "M19 12h3" }],
+ ["path", { d: "M2 12h3" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M7.05 7.05a7 7 0 0 0 9.9 9.9" }]
+ ];
+
+ const Locate = [
+ ["line", { x1: "2", x2: "5", y1: "12", y2: "12" }],
+ ["line", { x1: "19", x2: "22", y1: "12", y2: "12" }],
+ ["line", { x1: "12", x2: "12", y1: "2", y2: "5" }],
+ ["line", { x1: "12", x2: "12", y1: "19", y2: "22" }],
+ ["circle", { cx: "12", cy: "12", r: "7" }]
+ ];
+
+ const LockKeyholeOpen = [
+ ["circle", { cx: "12", cy: "16", r: "1" }],
+ ["rect", { width: "18", height: "12", x: "3", y: "10", rx: "2" }],
+ ["path", { d: "M7 10V7a5 5 0 0 1 9.33-2.5" }]
+ ];
+
+ const LockKeyhole = [
+ ["circle", { cx: "12", cy: "16", r: "1" }],
+ ["rect", { x: "3", y: "10", width: "18", height: "12", rx: "2" }],
+ ["path", { d: "M7 10V7a5 5 0 0 1 10 0v3" }]
+ ];
+
+ const LockOpen = [
+ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }],
+ ["path", { d: "M7 11V7a5 5 0 0 1 9.9-1" }]
+ ];
+
+ const Lock = [
+ ["rect", { width: "18", height: "11", x: "3", y: "11", rx: "2", ry: "2" }],
+ ["path", { d: "M7 11V7a5 5 0 0 1 10 0v4" }]
+ ];
+
+ const LogIn = [
+ ["path", { d: "m10 17 5-5-5-5" }],
+ ["path", { d: "M15 12H3" }],
+ ["path", { d: "M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" }]
+ ];
+
+ const LogOut = [
+ ["path", { d: "m16 17 5-5-5-5" }],
+ ["path", { d: "M21 12H9" }],
+ ["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }]
+ ];
+
+ const Logs = [
+ ["path", { d: "M3 5h1" }],
+ ["path", { d: "M3 12h1" }],
+ ["path", { d: "M3 19h1" }],
+ ["path", { d: "M8 5h1" }],
+ ["path", { d: "M8 12h1" }],
+ ["path", { d: "M8 19h1" }],
+ ["path", { d: "M13 5h8" }],
+ ["path", { d: "M13 12h8" }],
+ ["path", { d: "M13 19h8" }]
+ ];
+
+ const Lollipop = [
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["path", { d: "m21 21-4.3-4.3" }],
+ ["path", { d: "M11 11a2 2 0 0 0 4 0 4 4 0 0 0-8 0 6 6 0 0 0 12 0" }]
+ ];
+
+ const Luggage = [
+ ["path", { d: "M6 20a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2" }],
+ ["path", { d: "M8 18V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v14" }],
+ ["path", { d: "M10 20h4" }],
+ ["circle", { cx: "16", cy: "20", r: "2" }],
+ ["circle", { cx: "8", cy: "20", r: "2" }]
+ ];
+
+ const Magnet = [
+ ["path", { d: "m12 15 4 4" }],
+ [
+ "path",
+ {
+ d: "M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z"
+ }
+ ],
+ ["path", { d: "m5 8 4 4" }]
+ ];
+
+ const MailCheck = [
+ ["path", { d: "M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "m16 19 2 2 4-4" }]
+ ];
+
+ const MailMinus = [
+ ["path", { d: "M22 15V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "M16 19h6" }]
+ ];
+
+ const MailOpen = [
+ [
+ "path",
+ {
+ d: "M21.2 8.4c.5.38.8.97.8 1.6v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V10a2 2 0 0 1 .8-1.6l8-6a2 2 0 0 1 2.4 0l8 6Z"
+ }
+ ],
+ ["path", { d: "m22 10-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 10" }]
+ ];
+
+ const MailPlus = [
+ ["path", { d: "M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h8" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "M19 16v6" }],
+ ["path", { d: "M16 19h6" }]
+ ];
+
+ const MailQuestionMark = [
+ ["path", { d: "M22 10.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12.5" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "M18 15.28c.2-.4.5-.8.9-1a2.1 2.1 0 0 1 2.6.4c.3.4.5.8.5 1.3 0 1.3-2 2-2 2" }],
+ ["path", { d: "M20 22v.01" }]
+ ];
+
+ const MailSearch = [
+ ["path", { d: "M22 12.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h7.5" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "M18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["path", { d: "m22 22-1.5-1.5" }]
+ ];
+
+ const MailWarning = [
+ ["path", { d: "M22 10.5V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h12.5" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "M20 14v4" }],
+ ["path", { d: "M20 22v.01" }]
+ ];
+
+ const MailX = [
+ ["path", { d: "M22 13V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v12c0 1.1.9 2 2 2h9" }],
+ ["path", { d: "m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" }],
+ ["path", { d: "m17 17 4 4" }],
+ ["path", { d: "m21 17-4 4" }]
+ ];
+
+ const Mail = [
+ ["path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" }],
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }]
+ ];
+
+ const Mailbox = [
+ ["path", { d: "M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z" }],
+ ["polyline", { points: "15,9 18,9 18,11" }],
+ ["path", { d: "M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2" }],
+ ["line", { x1: "6", x2: "7", y1: "10", y2: "10" }]
+ ];
+
+ const Mails = [
+ ["path", { d: "M17 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 1-1.732" }],
+ ["path", { d: "m22 5.5-6.419 4.179a2 2 0 0 1-2.162 0L7 5.5" }],
+ ["rect", { x: "7", y: "3", width: "15", height: "12", rx: "2" }]
+ ];
+
+ const MapMinus = [
+ [
+ "path",
+ {
+ d: "m11 19-1.106-.552a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0l4.212 2.106a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619V14"
+ }
+ ],
+ ["path", { d: "M15 5.764V14" }],
+ ["path", { d: "M21 18h-6" }],
+ ["path", { d: "M9 3.236v15" }]
+ ];
+
+ const MapPinCheckInside = [
+ [
+ "path",
+ {
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ }
+ ],
+ ["path", { d: "m9 10 2 2 4-4" }]
+ ];
+
+ const MapPinCheck = [
+ [
+ "path",
+ {
+ d: "M19.43 12.935c.357-.967.57-1.955.57-2.935a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32.197 32.197 0 0 0 .813-.728"
+ }
+ ],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "m16 18 2 2 4-4" }]
+ ];
+
+ const MapPinHouse = [
+ [
+ "path",
+ {
+ d: "M15 22a1 1 0 0 1-1-1v-4a1 1 0 0 1 .445-.832l3-2a1 1 0 0 1 1.11 0l3 2A1 1 0 0 1 22 17v4a1 1 0 0 1-1 1z"
+ }
+ ],
+ ["path", { d: "M18 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 .601.2" }],
+ ["path", { d: "M18 22v-3" }],
+ ["circle", { cx: "10", cy: "10", r: "3" }]
+ ];
+
+ const MapPinMinusInside = [
+ [
+ "path",
+ {
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ }
+ ],
+ ["path", { d: "M9 10h6" }]
+ ];
+
+ const MapPinMinus = [
+ [
+ "path",
+ {
+ d: "M18.977 14C19.6 12.701 20 11.343 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32 32 0 0 0 .824-.738"
+ }
+ ],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "M16 18h6" }]
+ ];
+
+ const MapPinOff = [
+ ["path", { d: "M12.75 7.09a3 3 0 0 1 2.16 2.16" }],
+ [
+ "path",
+ {
+ d: "M17.072 17.072c-1.634 2.17-3.527 3.912-4.471 4.727a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 1.432-4.568"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.475 2.818A8 8 0 0 1 20 10c0 1.183-.31 2.377-.81 3.533" }],
+ ["path", { d: "M9.13 9.13a3 3 0 0 0 3.74 3.74" }]
+ ];
+
+ const MapPinPen = [
+ ["path", { d: "M17.97 9.304A8 8 0 0 0 2 10c0 4.69 4.887 9.562 7.022 11.468" }],
+ [
+ "path",
+ {
+ d: "M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ],
+ ["circle", { cx: "10", cy: "10", r: "3" }]
+ ];
+
+ const MapPinPlusInside = [
+ [
+ "path",
+ {
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ }
+ ],
+ ["path", { d: "M12 7v6" }],
+ ["path", { d: "M9 10h6" }]
+ ];
+
+ const MapPinPlus = [
+ [
+ "path",
+ {
+ d: "M19.914 11.105A7.298 7.298 0 0 0 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 32 32 0 0 0 .824-.738"
+ }
+ ],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "M16 18h6" }],
+ ["path", { d: "M19 15v6" }]
+ ];
+
+ const MapPinXInside = [
+ [
+ "path",
+ {
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ }
+ ],
+ ["path", { d: "m14.5 7.5-5 5" }],
+ ["path", { d: "m9.5 7.5 5 5" }]
+ ];
+
+ const MapPinX = [
+ [
+ "path",
+ {
+ d: "M19.752 11.901A7.78 7.78 0 0 0 20 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 1.202 0 19 19 0 0 0 .09-.077"
+ }
+ ],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "m21.5 15.5-5 5" }],
+ ["path", { d: "m21.5 20.5-5-5" }]
+ ];
+
+ const MapPin = [
+ [
+ "path",
+ {
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
+ }
+ ],
+ ["circle", { cx: "12", cy: "10", r: "3" }]
+ ];
+
+ const MapPinned = [
+ [
+ "path",
+ {
+ d: "M18 8c0 3.613-3.869 7.429-5.393 8.795a1 1 0 0 1-1.214 0C9.87 15.429 6 11.613 6 8a6 6 0 0 1 12 0"
+ }
+ ],
+ ["circle", { cx: "12", cy: "8", r: "2" }],
+ [
+ "path",
+ {
+ d: "M8.714 14h-3.71a1 1 0 0 0-.948.683l-2.004 6A1 1 0 0 0 3 22h18a1 1 0 0 0 .948-1.316l-2-6a1 1 0 0 0-.949-.684h-3.712"
+ }
+ ]
+ ];
+
+ const MapPlus = [
+ [
+ "path",
+ {
+ d: "m11 19-1.106-.552a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0l4.212 2.106a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619V12"
+ }
+ ],
+ ["path", { d: "M15 5.764V12" }],
+ ["path", { d: "M18 15v6" }],
+ ["path", { d: "M21 18h-6" }],
+ ["path", { d: "M9 3.236v15" }]
+ ];
+
+ const Map = [
+ [
+ "path",
+ {
+ d: "M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"
+ }
+ ],
+ ["path", { d: "M15 5.764v15" }],
+ ["path", { d: "M9 3.236v15" }]
+ ];
+
+ const MarsStroke = [
+ ["path", { d: "m14 6 4 4" }],
+ ["path", { d: "M17 3h4v4" }],
+ ["path", { d: "m21 3-7.75 7.75" }],
+ ["circle", { cx: "9", cy: "15", r: "6" }]
+ ];
+
+ const Mars = [
+ ["path", { d: "M16 3h5v5" }],
+ ["path", { d: "m21 3-6.75 6.75" }],
+ ["circle", { cx: "10", cy: "14", r: "6" }]
+ ];
+
+ const Martini = [
+ ["path", { d: "M8 22h8" }],
+ ["path", { d: "M12 11v11" }],
+ ["path", { d: "m19 3-7 8-7-8Z" }]
+ ];
+
+ const Maximize2 = [
+ ["path", { d: "M15 3h6v6" }],
+ ["path", { d: "m21 3-7 7" }],
+ ["path", { d: "m3 21 7-7" }],
+ ["path", { d: "M9 21H3v-6" }]
+ ];
+
+ const Medal = [
+ [
+ "path",
+ {
+ d: "M7.21 15 2.66 7.14a2 2 0 0 1 .13-2.2L4.4 2.8A2 2 0 0 1 6 2h12a2 2 0 0 1 1.6.8l1.6 2.14a2 2 0 0 1 .14 2.2L16.79 15"
+ }
+ ],
+ ["path", { d: "M11 12 5.12 2.2" }],
+ ["path", { d: "m13 12 5.88-9.8" }],
+ ["path", { d: "M8 7h8" }],
+ ["circle", { cx: "12", cy: "17", r: "5" }],
+ ["path", { d: "M12 18v-2h-.5" }]
+ ];
+
+ const Maximize = [
+ ["path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }],
+ ["path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }],
+ ["path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" }]
+ ];
+
+ const Megaphone = [
+ [
+ "path",
+ {
+ d: "M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"
+ }
+ ],
+ ["path", { d: "M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14" }],
+ ["path", { d: "M8 6v8" }]
+ ];
+
+ const MegaphoneOff = [
+ ["path", { d: "M11.636 6A13 13 0 0 0 19.4 3.2 1 1 0 0 1 21 4v11.344" }],
+ ["path", { d: "M14.378 14.357A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h1" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14" }],
+ ["path", { d: "M8 8v6" }]
+ ];
+
+ const Meh = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["line", { x1: "8", x2: "16", y1: "15", y2: "15" }],
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
+ ];
+
+ const MemoryStick = [
+ ["path", { d: "M6 19v-3" }],
+ ["path", { d: "M10 19v-3" }],
+ ["path", { d: "M14 19v-3" }],
+ ["path", { d: "M18 19v-3" }],
+ ["path", { d: "M8 11V9" }],
+ ["path", { d: "M16 11V9" }],
+ ["path", { d: "M12 11V9" }],
+ ["path", { d: "M2 15h20" }],
+ [
+ "path",
+ {
+ d: "M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z"
+ }
+ ]
+ ];
+
+ const Menu = [
+ ["path", { d: "M4 5h16" }],
+ ["path", { d: "M4 12h16" }],
+ ["path", { d: "M4 19h16" }]
+ ];
+
+ const Merge = [
+ ["path", { d: "m8 6 4-4 4 4" }],
+ ["path", { d: "M12 2v10.3a4 4 0 0 1-1.172 2.872L4 22" }],
+ ["path", { d: "m20 22-5-5" }]
+ ];
+
+ const MessageCircleCode = [
+ ["path", { d: "m10 9-3 3 3 3" }],
+ ["path", { d: "m14 15 3-3-3-3" }],
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ]
+ ];
+
+ const MessageCircleDashed = [
+ ["path", { d: "M10.1 2.182a10 10 0 0 1 3.8 0" }],
+ ["path", { d: "M13.9 21.818a10 10 0 0 1-3.8 0" }],
+ ["path", { d: "M17.609 3.72a10 10 0 0 1 2.69 2.7" }],
+ ["path", { d: "M2.182 13.9a10 10 0 0 1 0-3.8" }],
+ ["path", { d: "M20.28 17.61a10 10 0 0 1-2.7 2.69" }],
+ ["path", { d: "M21.818 10.1a10 10 0 0 1 0 3.8" }],
+ ["path", { d: "M3.721 6.391a10 10 0 0 1 2.7-2.69" }],
+ ["path", { d: "m6.163 21.117-2.906.85a1 1 0 0 1-1.236-1.169l.965-2.98" }]
+ ];
+
+ const MessageCircleHeart = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M7.828 13.07A3 3 0 0 1 12 8.764a3 3 0 0 1 5.004 2.224 3 3 0 0 1-.832 2.083l-3.447 3.62a1 1 0 0 1-1.45-.001z"
+ }
+ ]
+ ];
+
+ const MessageCircleMore = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "M8 12h.01" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M16 12h.01" }]
+ ];
+
+ const MessageCircleOff = [
+ ["path", { d: "m2 2 20 20" }],
+ [
+ "path",
+ {
+ d: "M4.93 4.929a10 10 0 0 0-1.938 11.412 2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 0 0 11.302-1.989"
+ }
+ ],
+ ["path", { d: "M8.35 2.69A10 10 0 0 1 21.3 15.65" }]
+ ];
+
+ const MessageCirclePlus = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "M12 8v8" }]
+ ];
+
+ const MessageCircleQuestionMark = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }],
+ ["path", { d: "M12 17h.01" }]
+ ];
+
+ const MessageCircleWarning = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "M12 8v4" }],
+ ["path", { d: "M12 16h.01" }]
+ ];
+
+ const MessageCircleReply = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "m10 15-3-3 3-3" }],
+ ["path", { d: "M7 12h8a2 2 0 0 1 2 2v1" }]
+ ];
+
+ const MessageCircleX = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "m9 9 6 6" }]
+ ];
+
+ const MessageCircle = [
+ [
+ "path",
+ {
+ d: "M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"
+ }
+ ]
+ ];
+
+ const MessageSquareCode = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "m10 8-3 3 3 3" }],
+ ["path", { d: "m14 14 3-3-3-3" }]
+ ];
+
+ const MessageSquareDashed = [
+ ["path", { d: "M12 19h.01" }],
+ ["path", { d: "M12 3h.01" }],
+ ["path", { d: "M16 19h.01" }],
+ ["path", { d: "M16 3h.01" }],
+ ["path", { d: "M2 13h.01" }],
+ ["path", { d: "M2 17v4.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H8" }],
+ ["path", { d: "M2 5a2 2 0 0 1 2-2" }],
+ ["path", { d: "M2 9h.01" }],
+ ["path", { d: "M20 3a2 2 0 0 1 2 2" }],
+ ["path", { d: "M22 13h.01" }],
+ ["path", { d: "M22 17a2 2 0 0 1-2 2" }],
+ ["path", { d: "M22 9h.01" }],
+ ["path", { d: "M8 3h.01" }]
+ ];
+
+ const MessageSquareDiff = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M10 15h4" }],
+ ["path", { d: "M10 9h4" }],
+ ["path", { d: "M12 7v4" }]
+ ];
+
+ const MessageSquareDot = [
+ [
+ "path",
+ {
+ d: "M12.7 3H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H20a2 2 0 0 0 2-2v-4.7"
+ }
+ ],
+ ["circle", { cx: "19", cy: "6", r: "3" }]
+ ];
+
+ const MessageSquareHeart = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M7.5 9.5c0 .687.265 1.383.697 1.844l3.009 3.264a1.14 1.14 0 0 0 .407.314 1 1 0 0 0 .783-.004 1.14 1.14 0 0 0 .398-.31l3.008-3.264A2.77 2.77 0 0 0 16.5 9.5 2.5 2.5 0 0 0 12 8a2.5 2.5 0 0 0-4.5 1.5"
+ }
+ ]
+ ];
+
+ const MessageSquareLock = [
+ [
+ "path",
+ {
+ d: "M22 8.5V5a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H10"
+ }
+ ],
+ ["path", { d: "M20 15v-2a2 2 0 0 0-4 0v2" }],
+ ["rect", { x: "14", y: "15", width: "8", height: "5", rx: "1" }]
+ ];
+
+ const MessageSquareMore = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M12 11h.01" }],
+ ["path", { d: "M16 11h.01" }],
+ ["path", { d: "M8 11h.01" }]
+ ];
+
+ const MessageSquareOff = [
+ [
+ "path",
+ {
+ d: "M19 19H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.7.7 0 0 1 2 21.286V5a2 2 0 0 1 1.184-1.826"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M8.656 3H20a2 2 0 0 1 2 2v11.344" }]
+ ];
+
+ const MessageSquarePlus = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M12 8v6" }],
+ ["path", { d: "M9 11h6" }]
+ ];
+
+ const MessageSquareQuote = [
+ ["path", { d: "M14 14a2 2 0 0 0 2-2V8h-2" }],
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M8 14a2 2 0 0 0 2-2V8H8" }]
+ ];
+
+ const MessageSquareReply = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "m10 8-3 3 3 3" }],
+ ["path", { d: "M17 14v-1a2 2 0 0 0-2-2H7" }]
+ ];
+
+ const MessageSquareShare = [
+ [
+ "path",
+ {
+ d: "M12 3H4a2 2 0 0 0-2 2v16.286a.71.71 0 0 0 1.212.502l2.202-2.202A2 2 0 0 1 6.828 19H20a2 2 0 0 0 2-2v-4"
+ }
+ ],
+ ["path", { d: "M16 3h6v6" }],
+ ["path", { d: "m16 9 6-6" }]
+ ];
+
+ const MessageSquareText = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M7 11h10" }],
+ ["path", { d: "M7 15h6" }],
+ ["path", { d: "M7 7h8" }]
+ ];
+
+ const MessageSquareWarning = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "M12 15h.01" }],
+ ["path", { d: "M12 7v4" }]
+ ];
+
+ const MessageSquareX = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ],
+ ["path", { d: "m14.5 8.5-5 5" }],
+ ["path", { d: "m9.5 8.5 5 5" }]
+ ];
+
+ const MessageSquare = [
+ [
+ "path",
+ {
+ d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"
+ }
+ ]
+ ];
+
+ const MessagesSquare = [
+ [
+ "path",
+ {
+ d: "M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1"
+ }
+ ]
+ ];
+
+ const MicOff = [
+ ["path", { d: "M12 19v3" }],
+ ["path", { d: "M15 9.34V5a3 3 0 0 0-5.68-1.33" }],
+ ["path", { d: "M16.95 16.95A7 7 0 0 1 5 12v-2" }],
+ ["path", { d: "M18.89 13.23A7 7 0 0 0 19 12v-2" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M9 9v3a3 3 0 0 0 5.12 2.12" }]
+ ];
+
+ const MicVocal = [
+ ["path", { d: "m11 7.601-5.994 8.19a1 1 0 0 0 .1 1.298l.817.818a1 1 0 0 0 1.314.087L15.09 12" }],
+ [
+ "path",
+ {
+ d: "M16.5 21.174C15.5 20.5 14.372 20 13 20c-2.058 0-3.928 2.356-6 2-2.072-.356-2.775-3.369-1.5-4.5"
+ }
+ ],
+ ["circle", { cx: "16", cy: "7", r: "5" }]
+ ];
+
+ const Mic = [
+ ["path", { d: "M12 19v3" }],
+ ["path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }],
+ ["rect", { x: "9", y: "2", width: "6", height: "13", rx: "3" }]
+ ];
+
+ const Microchip = [
+ ["path", { d: "M18 12h2" }],
+ ["path", { d: "M18 16h2" }],
+ ["path", { d: "M18 20h2" }],
+ ["path", { d: "M18 4h2" }],
+ ["path", { d: "M18 8h2" }],
+ ["path", { d: "M4 12h2" }],
+ ["path", { d: "M4 16h2" }],
+ ["path", { d: "M4 20h2" }],
+ ["path", { d: "M4 4h2" }],
+ ["path", { d: "M4 8h2" }],
+ [
+ "path",
+ {
+ d: "M8 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-1.5c-.276 0-.494.227-.562.495a2 2 0 0 1-3.876 0C9.994 2.227 9.776 2 9.5 2z"
+ }
+ ]
+ ];
+
+ const Microscope = [
+ ["path", { d: "M6 18h8" }],
+ ["path", { d: "M3 22h18" }],
+ ["path", { d: "M14 22a7 7 0 1 0 0-14h-1" }],
+ ["path", { d: "M9 14h2" }],
+ ["path", { d: "M9 12a2 2 0 0 1-2-2V6h6v4a2 2 0 0 1-2 2Z" }],
+ ["path", { d: "M12 6V3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3" }]
+ ];
+
+ const Microwave = [
+ ["rect", { width: "20", height: "15", x: "2", y: "4", rx: "2" }],
+ ["rect", { width: "8", height: "7", x: "6", y: "8", rx: "1" }],
+ ["path", { d: "M18 8v7" }],
+ ["path", { d: "M6 19v2" }],
+ ["path", { d: "M18 19v2" }]
+ ];
+
+ const Milestone = [
+ ["path", { d: "M12 13v8" }],
+ ["path", { d: "M12 3v3" }],
+ [
+ "path",
+ {
+ d: "M4 6a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h13a2 2 0 0 0 1.152-.365l3.424-2.317a1 1 0 0 0 0-1.635l-3.424-2.318A2 2 0 0 0 17 6z"
+ }
+ ]
+ ];
+
+ const MilkOff = [
+ ["path", { d: "M8 2h8" }],
+ [
+ "path",
+ {
+ d: "M9 2v1.343M15 2v2.789a4 4 0 0 0 .672 2.219l.656.984a4 4 0 0 1 .672 2.22v1.131M7.8 7.8l-.128.192A4 4 0 0 0 7 10.212V20a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-3"
+ }
+ ],
+ ["path", { d: "M7 15a6.47 6.47 0 0 1 5 0 6.472 6.472 0 0 0 3.435.435" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Milk = [
+ ["path", { d: "M8 2h8" }],
+ [
+ "path",
+ {
+ d: "M9 2v2.789a4 4 0 0 1-.672 2.219l-.656.984A4 4 0 0 0 7 10.212V20a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2v-9.789a4 4 0 0 0-.672-2.219l-.656-.984A4 4 0 0 1 15 4.788V2"
+ }
+ ],
+ ["path", { d: "M7 15a6.472 6.472 0 0 1 5 0 6.47 6.47 0 0 0 5 0" }]
+ ];
+
+ const Minimize2 = [
+ ["path", { d: "m14 10 7-7" }],
+ ["path", { d: "M20 10h-6V4" }],
+ ["path", { d: "m3 21 7-7" }],
+ ["path", { d: "M4 14h6v6" }]
+ ];
+
+ const Minimize = [
+ ["path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }],
+ ["path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }],
+ ["path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }],
+ ["path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" }]
+ ];
+
+ const Minus = [["path", { d: "M5 12h14" }]];
+
+ const MonitorCheck = [
+ ["path", { d: "m9 10 2 2 4-4" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }]
+ ];
+
+ const MonitorCloud = [
+ ["path", { d: "M11 13a3 3 0 1 1 2.83-4H14a2 2 0 0 1 0 4z" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }],
+ ["rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }]
+ ];
+
+ const MonitorCog = [
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "m14.305 7.53.923-.382" }],
+ ["path", { d: "m15.228 4.852-.923-.383" }],
+ ["path", { d: "m16.852 3.228-.383-.924" }],
+ ["path", { d: "m16.852 8.772-.383.923" }],
+ ["path", { d: "m19.148 3.228.383-.924" }],
+ ["path", { d: "m19.53 9.696-.382-.924" }],
+ ["path", { d: "m20.772 4.852.924-.383" }],
+ ["path", { d: "m20.772 7.148.924.383" }],
+ ["path", { d: "M22 13v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7" }],
+ ["path", { d: "M8 21h8" }],
+ ["circle", { cx: "18", cy: "6", r: "3" }]
+ ];
+
+ const MonitorDown = [
+ ["path", { d: "M12 13V7" }],
+ ["path", { d: "m15 10-3 3-3-3" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }]
+ ];
+
+ const MonitorDot = [
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M22 12.307V15a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8.693" }],
+ ["path", { d: "M8 21h8" }],
+ ["circle", { cx: "19", cy: "6", r: "3" }]
+ ];
+
+ const MonitorOff = [
+ ["path", { d: "M17 17H4a2 2 0 0 1-2-2V5c0-1.5 1-2 1-2" }],
+ ["path", { d: "M22 15V5a2 2 0 0 0-2-2H9" }],
+ ["path", { d: "M8 21h8" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const MonitorPause = [
+ ["path", { d: "M10 13V7" }],
+ ["path", { d: "M14 13V7" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }]
+ ];
+
+ const MonitorPlay = [
+ [
+ "path",
+ {
+ d: "M15.033 9.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56V7.648a.645.645 0 0 1 .967-.56z"
+ }
+ ],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }],
+ ["rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }]
+ ];
+
+ const MonitorSmartphone = [
+ ["path", { d: "M18 8V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h8" }],
+ ["path", { d: "M10 19v-3.96 3.15" }],
+ ["path", { d: "M7 19h5" }],
+ ["rect", { width: "6", height: "10", x: "16", y: "12", rx: "2" }]
+ ];
+
+ const MonitorSpeaker = [
+ ["path", { d: "M5.5 20H8" }],
+ ["path", { d: "M17 9h.01" }],
+ ["rect", { width: "10", height: "16", x: "12", y: "4", rx: "2" }],
+ ["path", { d: "M8 6H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h4" }],
+ ["circle", { cx: "17", cy: "15", r: "1" }]
+ ];
+
+ const MonitorStop = [
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }],
+ ["rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }],
+ ["rect", { x: "9", y: "7", width: "6", height: "6", rx: "1" }]
+ ];
+
+ const MonitorUp = [
+ ["path", { d: "m9 10 3-3 3 3" }],
+ ["path", { d: "M12 13V7" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }]
+ ];
+
+ const MonitorX = [
+ ["path", { d: "m14.5 12.5-5-5" }],
+ ["path", { d: "m9.5 12.5 5-5" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }]
+ ];
+
+ const Monitor = [
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }],
+ ["line", { x1: "8", x2: "16", y1: "21", y2: "21" }],
+ ["line", { x1: "12", x2: "12", y1: "17", y2: "21" }]
+ ];
+
+ const MoonStar = [
+ ["path", { d: "M18 5h4" }],
+ ["path", { d: "M20 3v4" }],
+ [
+ "path",
+ {
+ d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"
+ }
+ ]
+ ];
+
+ const Moon = [
+ [
+ "path",
+ {
+ d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"
+ }
+ ]
+ ];
+
+ const Motorbike = [
+ ["path", { d: "m18 14-1-3" }],
+ ["path", { d: "m3 9 6 2a2 2 0 0 1 2-2h2a2 2 0 0 1 1.99 1.81" }],
+ ["path", { d: "M8 17h3a1 1 0 0 0 1-1 6 6 0 0 1 6-6 1 1 0 0 0 1-1v-.75A5 5 0 0 0 17 5" }],
+ ["circle", { cx: "19", cy: "17", r: "3" }],
+ ["circle", { cx: "5", cy: "17", r: "3" }]
+ ];
+
+ const MountainSnow = [
+ ["path", { d: "m8 3 4 8 5-5 5 15H2L8 3z" }],
+ ["path", { d: "M4.14 15.08c2.62-1.57 5.24-1.43 7.86.42 2.74 1.94 5.49 2 8.23.19" }]
+ ];
+
+ const Mountain = [["path", { d: "m8 3 4 8 5-5 5 15H2L8 3z" }]];
+
+ const MouseOff = [
+ ["path", { d: "M12 6v.343" }],
+ ["path", { d: "M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218" }],
+ ["path", { d: "M19 13.343V9A7 7 0 0 0 8.56 2.902" }],
+ ["path", { d: "M22 22 2 2" }]
+ ];
+
+ const MousePointer2 = [
+ [
+ "path",
+ {
+ d: "M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z"
+ }
+ ]
+ ];
+
+ const MousePointerBan = [
+ [
+ "path",
+ {
+ d: "M2.034 2.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.944L8.204 7.545a1 1 0 0 0-.66.66l-1.066 3.443a.5.5 0 0 1-.944.033z"
+ }
+ ],
+ ["circle", { cx: "16", cy: "16", r: "6" }],
+ ["path", { d: "m11.8 11.8 8.4 8.4" }]
+ ];
+
+ const MousePointerClick = [
+ ["path", { d: "M14 4.1 12 6" }],
+ ["path", { d: "m5.1 8-2.9-.8" }],
+ ["path", { d: "m6 12-1.9 2" }],
+ ["path", { d: "M7.2 2.2 8 5.1" }],
+ [
+ "path",
+ {
+ d: "M9.037 9.69a.498.498 0 0 1 .653-.653l11 4.5a.5.5 0 0 1-.074.949l-4.349 1.041a1 1 0 0 0-.74.739l-1.04 4.35a.5.5 0 0 1-.95.074z"
+ }
+ ]
+ ];
+
+ const MousePointer = [
+ ["path", { d: "M12.586 12.586 19 19" }],
+ [
+ "path",
+ {
+ d: "M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z"
+ }
+ ]
+ ];
+
+ const Mouse = [
+ ["rect", { x: "5", y: "2", width: "14", height: "20", rx: "7" }],
+ ["path", { d: "M12 6v4" }]
+ ];
+
+ const Move3d = [
+ ["path", { d: "M5 3v16h16" }],
+ ["path", { d: "m5 19 6-6" }],
+ ["path", { d: "m2 6 3-3 3 3" }],
+ ["path", { d: "m18 16 3 3-3 3" }]
+ ];
+
+ const MoveDiagonal2 = [
+ ["path", { d: "M19 13v6h-6" }],
+ ["path", { d: "M5 11V5h6" }],
+ ["path", { d: "m5 5 14 14" }]
+ ];
+
+ const MoveDiagonal = [
+ ["path", { d: "M11 19H5v-6" }],
+ ["path", { d: "M13 5h6v6" }],
+ ["path", { d: "M19 5 5 19" }]
+ ];
+
+ const MoveDownLeft = [
+ ["path", { d: "M11 19H5V13" }],
+ ["path", { d: "M19 5L5 19" }]
+ ];
+
+ const MoveDownRight = [
+ ["path", { d: "M19 13V19H13" }],
+ ["path", { d: "M5 5L19 19" }]
+ ];
+
+ const MoveHorizontal = [
+ ["path", { d: "m18 8 4 4-4 4" }],
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "m6 8-4 4 4 4" }]
+ ];
+
+ const MoveLeft = [
+ ["path", { d: "M6 8L2 12L6 16" }],
+ ["path", { d: "M2 12H22" }]
+ ];
+
+ const MoveDown = [
+ ["path", { d: "M8 18L12 22L16 18" }],
+ ["path", { d: "M12 2V22" }]
+ ];
+
+ const MoveRight = [
+ ["path", { d: "M18 8L22 12L18 16" }],
+ ["path", { d: "M2 12H22" }]
+ ];
+
+ const MoveUpLeft = [
+ ["path", { d: "M5 11V5H11" }],
+ ["path", { d: "M5 5L19 19" }]
+ ];
+
+ const MoveUpRight = [
+ ["path", { d: "M13 5H19V11" }],
+ ["path", { d: "M19 5L5 19" }]
+ ];
+
+ const MoveUp = [
+ ["path", { d: "M8 6L12 2L16 6" }],
+ ["path", { d: "M12 2V22" }]
+ ];
+
+ const MoveVertical = [
+ ["path", { d: "M12 2v20" }],
+ ["path", { d: "m8 18 4 4 4-4" }],
+ ["path", { d: "m8 6 4-4 4 4" }]
+ ];
+
+ const Move = [
+ ["path", { d: "M12 2v20" }],
+ ["path", { d: "m15 19-3 3-3-3" }],
+ ["path", { d: "m19 9 3 3-3 3" }],
+ ["path", { d: "M2 12h20" }],
+ ["path", { d: "m5 9-3 3 3 3" }],
+ ["path", { d: "m9 5 3-3 3 3" }]
+ ];
+
+ const Music2 = [
+ ["circle", { cx: "8", cy: "18", r: "4" }],
+ ["path", { d: "M12 18V2l7 4" }]
+ ];
+
+ const Music3 = [
+ ["circle", { cx: "12", cy: "18", r: "4" }],
+ ["path", { d: "M16 18V2" }]
+ ];
+
+ const Music4 = [
+ ["path", { d: "M9 18V5l12-2v13" }],
+ ["path", { d: "m9 9 12-2" }],
+ ["circle", { cx: "6", cy: "18", r: "3" }],
+ ["circle", { cx: "18", cy: "16", r: "3" }]
+ ];
+
+ const Music = [
+ ["path", { d: "M9 18V5l12-2v13" }],
+ ["circle", { cx: "6", cy: "18", r: "3" }],
+ ["circle", { cx: "18", cy: "16", r: "3" }]
+ ];
+
+ const Navigation2Off = [
+ ["path", { d: "M9.31 9.31 5 21l7-4 7 4-1.17-3.17" }],
+ ["path", { d: "M14.53 8.88 12 2l-1.17 3.17" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Navigation2 = [["polygon", { points: "12 2 19 21 12 17 5 21 12 2" }]];
+
+ const NavigationOff = [
+ ["path", { d: "M8.43 8.43 3 11l8 2 2 8 2.57-5.43" }],
+ ["path", { d: "M17.39 11.73 22 2l-9.73 4.61" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Navigation = [["polygon", { points: "3 11 22 2 13 21 11 13 3 11" }]];
+
+ const Network = [
+ ["rect", { x: "16", y: "16", width: "6", height: "6", rx: "1" }],
+ ["rect", { x: "2", y: "16", width: "6", height: "6", rx: "1" }],
+ ["rect", { x: "9", y: "2", width: "6", height: "6", rx: "1" }],
+ ["path", { d: "M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3" }],
+ ["path", { d: "M12 12V8" }]
+ ];
+
+ const Newspaper = [
+ ["path", { d: "M15 18h-5" }],
+ ["path", { d: "M18 14h-8" }],
+ [
+ "path",
+ {
+ d: "M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2"
+ }
+ ],
+ ["rect", { width: "8", height: "4", x: "10", y: "6", rx: "1" }]
+ ];
+
+ const Nfc = [
+ ["path", { d: "M6 8.32a7.43 7.43 0 0 1 0 7.36" }],
+ ["path", { d: "M9.46 6.21a11.76 11.76 0 0 1 0 11.58" }],
+ ["path", { d: "M12.91 4.1a15.91 15.91 0 0 1 .01 15.8" }],
+ ["path", { d: "M16.37 2a20.16 20.16 0 0 1 0 20" }]
+ ];
+
+ const NonBinary = [
+ ["path", { d: "M12 2v10" }],
+ ["path", { d: "m8.5 4 7 4" }],
+ ["path", { d: "m8.5 8 7-4" }],
+ ["circle", { cx: "12", cy: "17", r: "5" }]
+ ];
+
+ const NotebookPen = [
+ ["path", { d: "M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4" }],
+ ["path", { d: "M2 6h4" }],
+ ["path", { d: "M2 10h4" }],
+ ["path", { d: "M2 14h4" }],
+ ["path", { d: "M2 18h4" }],
+ [
+ "path",
+ {
+ d: "M21.378 5.626a1 1 0 1 0-3.004-3.004l-5.01 5.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ]
+ ];
+
+ const NotebookTabs = [
+ ["path", { d: "M2 6h4" }],
+ ["path", { d: "M2 10h4" }],
+ ["path", { d: "M2 14h4" }],
+ ["path", { d: "M2 18h4" }],
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }],
+ ["path", { d: "M15 2v20" }],
+ ["path", { d: "M15 7h5" }],
+ ["path", { d: "M15 12h5" }],
+ ["path", { d: "M15 17h5" }]
+ ];
+
+ const Notebook = [
+ ["path", { d: "M2 6h4" }],
+ ["path", { d: "M2 10h4" }],
+ ["path", { d: "M2 14h4" }],
+ ["path", { d: "M2 18h4" }],
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }],
+ ["path", { d: "M16 2v20" }]
+ ];
+
+ const NotebookText = [
+ ["path", { d: "M2 6h4" }],
+ ["path", { d: "M2 10h4" }],
+ ["path", { d: "M2 14h4" }],
+ ["path", { d: "M2 18h4" }],
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }],
+ ["path", { d: "M9.5 8h5" }],
+ ["path", { d: "M9.5 12H16" }],
+ ["path", { d: "M9.5 16H14" }]
+ ];
+
+ const NotepadTextDashed = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M12 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["path", { d: "M16 4h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M20 12v2" }],
+ ["path", { d: "M20 18v2a2 2 0 0 1-2 2h-1" }],
+ ["path", { d: "M13 22h-2" }],
+ ["path", { d: "M7 22H6a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M4 14v-2" }],
+ ["path", { d: "M4 8V6a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M8 10h6" }],
+ ["path", { d: "M8 14h8" }],
+ ["path", { d: "M8 18h5" }]
+ ];
+
+ const NotepadText = [
+ ["path", { d: "M8 2v4" }],
+ ["path", { d: "M12 2v4" }],
+ ["path", { d: "M16 2v4" }],
+ ["rect", { width: "16", height: "18", x: "4", y: "4", rx: "2" }],
+ ["path", { d: "M8 10h6" }],
+ ["path", { d: "M8 14h8" }],
+ ["path", { d: "M8 18h5" }]
+ ];
+
+ const NutOff = [
+ ["path", { d: "M12 4V2" }],
+ [
+ "path",
+ {
+ d: "M5 10v4a7.004 7.004 0 0 0 5.277 6.787c.412.104.802.292 1.102.592L12 22l.621-.621c.3-.3.69-.488 1.102-.592a7.01 7.01 0 0 0 4.125-2.939"
+ }
+ ],
+ ["path", { d: "M19 10v3.343" }],
+ [
+ "path",
+ {
+ d: "M12 12c-1.349-.573-1.905-1.005-2.5-2-.546.902-1.048 1.353-2.5 2-1.018-.644-1.46-1.08-2-2-1.028.71-1.69.918-3 1 1.081-1.048 1.757-2.03 2-3 .194-.776.84-1.551 1.79-2.21m11.654 5.997c.887-.457 1.28-.891 1.556-1.787 1.032.916 1.683 1.157 3 1-1.297-1.036-1.758-2.03-2-3-.5-2-4-4-8-4-.74 0-1.461.068-2.15.192"
+ }
+ ],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Nut = [
+ ["path", { d: "M12 4V2" }],
+ [
+ "path",
+ {
+ d: "M5 10v4a7.004 7.004 0 0 0 5.277 6.787c.412.104.802.292 1.102.592L12 22l.621-.621c.3-.3.69-.488 1.102-.592A7.003 7.003 0 0 0 19 14v-4"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M12 4C8 4 4.5 6 4 8c-.243.97-.919 1.952-2 3 1.31-.082 1.972-.29 3-1 .54.92.982 1.356 2 2 1.452-.647 1.954-1.098 2.5-2 .595.995 1.151 1.427 2.5 2 1.31-.621 1.862-1.058 2.5-2 .629.977 1.162 1.423 2.5 2 1.209-.548 1.68-.967 2-2 1.032.916 1.683 1.157 3 1-1.297-1.036-1.758-2.03-2-3-.5-2-4-4-8-4Z"
+ }
+ ]
+ ];
+
+ const OctagonAlert = [
+ ["path", { d: "M12 16h.01" }],
+ ["path", { d: "M12 8v4" }],
+ [
+ "path",
+ {
+ d: "M15.312 2a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586l-4.688-4.688A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2z"
+ }
+ ]
+ ];
+
+ const OctagonMinus = [
+ [
+ "path",
+ {
+ d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"
+ }
+ ],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const OctagonPause = [
+ ["path", { d: "M10 15V9" }],
+ ["path", { d: "M14 15V9" }],
+ [
+ "path",
+ {
+ d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"
+ }
+ ]
+ ];
+
+ const OctagonX = [
+ ["path", { d: "m15 9-6 6" }],
+ [
+ "path",
+ {
+ d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"
+ }
+ ],
+ ["path", { d: "m9 9 6 6" }]
+ ];
+
+ const Octagon = [
+ [
+ "path",
+ {
+ d: "M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z"
+ }
+ ]
+ ];
+
+ const Omega = [
+ [
+ "path",
+ {
+ d: "M3 20h4.5a.5.5 0 0 0 .5-.5v-.282a.52.52 0 0 0-.247-.437 8 8 0 1 1 8.494-.001.52.52 0 0 0-.247.438v.282a.5.5 0 0 0 .5.5H21"
+ }
+ ]
+ ];
+
+ const Option = [
+ ["path", { d: "M3 3h6l6 18h6" }],
+ ["path", { d: "M14 3h7" }]
+ ];
+
+ const Orbit = [
+ ["path", { d: "M20.341 6.484A10 10 0 0 1 10.266 21.85" }],
+ ["path", { d: "M3.659 17.516A10 10 0 0 1 13.74 2.152" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["circle", { cx: "19", cy: "5", r: "2" }],
+ ["circle", { cx: "5", cy: "19", r: "2" }]
+ ];
+
+ const Origami = [
+ ["path", { d: "M12 12V4a1 1 0 0 1 1-1h6.297a1 1 0 0 1 .651 1.759l-4.696 4.025" }],
+ [
+ "path",
+ { d: "m12 21-7.414-7.414A2 2 0 0 1 4 12.172V6.415a1.002 1.002 0 0 1 1.707-.707L20 20.009" }
+ ],
+ [
+ "path",
+ {
+ d: "m12.214 3.381 8.414 14.966a1 1 0 0 1-.167 1.199l-1.168 1.163a1 1 0 0 1-.706.291H6.351a1 1 0 0 1-.625-.219L3.25 18.8a1 1 0 0 1 .631-1.781l4.165.027"
+ }
+ ]
+ ];
+
+ const Package2 = [
+ ["path", { d: "M12 3v6" }],
+ [
+ "path",
+ {
+ d: "M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z"
+ }
+ ],
+ ["path", { d: "M3.054 9.013h17.893" }]
+ ];
+
+ const PackageCheck = [
+ ["path", { d: "m16 16 2 2 4-4" }],
+ [
+ "path",
+ {
+ d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
+ }
+ ],
+ ["path", { d: "m7.5 4.27 9 5.15" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }]
+ ];
+
+ const PackageMinus = [
+ ["path", { d: "M16 16h6" }],
+ [
+ "path",
+ {
+ d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
+ }
+ ],
+ ["path", { d: "m7.5 4.27 9 5.15" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }]
+ ];
+
+ const PackageOpen = [
+ ["path", { d: "M12 22v-9" }],
+ [
+ "path",
+ {
+ d: "M15.17 2.21a1.67 1.67 0 0 1 1.63 0L21 4.57a1.93 1.93 0 0 1 0 3.36L8.82 14.79a1.655 1.655 0 0 1-1.64 0L3 12.43a1.93 1.93 0 0 1 0-3.36z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M20 13v3.87a2.06 2.06 0 0 1-1.11 1.83l-6 3.08a1.93 1.93 0 0 1-1.78 0l-6-3.08A2.06 2.06 0 0 1 4 16.87V13"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M21 12.43a1.93 1.93 0 0 0 0-3.36L8.83 2.2a1.64 1.64 0 0 0-1.63 0L3 4.57a1.93 1.93 0 0 0 0 3.36l12.18 6.86a1.636 1.636 0 0 0 1.63 0z"
+ }
+ ]
+ ];
+
+ const PackagePlus = [
+ ["path", { d: "M16 16h6" }],
+ ["path", { d: "M19 13v6" }],
+ [
+ "path",
+ {
+ d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
+ }
+ ],
+ ["path", { d: "m7.5 4.27 9 5.15" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }]
+ ];
+
+ const PackageX = [
+ [
+ "path",
+ {
+ d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
+ }
+ ],
+ ["path", { d: "m7.5 4.27 9 5.15" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }],
+ ["path", { d: "m17 13 5 5m-5 0 5-5" }]
+ ];
+
+ const PackageSearch = [
+ [
+ "path",
+ {
+ d: "M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14"
+ }
+ ],
+ ["path", { d: "m7.5 4.27 9 5.15" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["line", { x1: "12", x2: "12", y1: "22", y2: "12" }],
+ ["circle", { cx: "18.5", cy: "15.5", r: "2.5" }],
+ ["path", { d: "M20.27 17.27 22 19" }]
+ ];
+
+ const Package = [
+ [
+ "path",
+ {
+ d: "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"
+ }
+ ],
+ ["path", { d: "M12 22V12" }],
+ ["polyline", { points: "3.29 7 12 12 20.71 7" }],
+ ["path", { d: "m7.5 4.27 9 5.15" }]
+ ];
+
+ const PaintBucket = [
+ ["path", { d: "m19 11-8-8-8.6 8.6a2 2 0 0 0 0 2.8l5.2 5.2c.8.8 2 .8 2.8 0L19 11Z" }],
+ ["path", { d: "m5 2 5 5" }],
+ ["path", { d: "M2 13h15" }],
+ ["path", { d: "M22 20a2 2 0 1 1-4 0c0-1.6 1.7-2.4 2-4 .3 1.6 2 2.4 2 4Z" }]
+ ];
+
+ const PaintRoller = [
+ ["rect", { width: "16", height: "6", x: "2", y: "2", rx: "2" }],
+ ["path", { d: "M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" }],
+ ["rect", { width: "4", height: "6", x: "8", y: "16", rx: "1" }]
+ ];
+
+ const PaintbrushVertical = [
+ ["path", { d: "M10 2v2" }],
+ ["path", { d: "M14 2v4" }],
+ ["path", { d: "M17 2a1 1 0 0 1 1 1v9H6V3a1 1 0 0 1 1-1z" }],
+ [
+ "path",
+ {
+ d: "M6 12a1 1 0 0 0-1 1v1a2 2 0 0 0 2 2h2a1 1 0 0 1 1 1v2.9a2 2 0 1 0 4 0V17a1 1 0 0 1 1-1h2a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1"
+ }
+ ]
+ ];
+
+ const Paintbrush = [
+ ["path", { d: "m14.622 17.897-10.68-2.913" }],
+ [
+ "path",
+ {
+ d: "M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15"
+ }
+ ]
+ ];
+
+ const Palette = [
+ [
+ "path",
+ {
+ d: "M12 22a1 1 0 0 1 0-20 10 9 0 0 1 10 9 5 5 0 0 1-5 5h-2.25a1.75 1.75 0 0 0-1.4 2.8l.3.4a1.75 1.75 0 0 1-1.4 2.8z"
+ }
+ ],
+ ["circle", { cx: "13.5", cy: "6.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "17.5", cy: "10.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "6.5", cy: "12.5", r: ".5", fill: "currentColor" }],
+ ["circle", { cx: "8.5", cy: "7.5", r: ".5", fill: "currentColor" }]
+ ];
+
+ const Panda = [
+ ["path", { d: "M11.25 17.25h1.5L12 18z" }],
+ ["path", { d: "m15 12 2 2" }],
+ ["path", { d: "M18 6.5a.5.5 0 0 0-.5-.5" }],
+ [
+ "path",
+ {
+ d: "M20.69 9.67a4.5 4.5 0 1 0-7.04-5.5 8.35 8.35 0 0 0-3.3 0 4.5 4.5 0 1 0-7.04 5.5C2.49 11.2 2 12.88 2 14.5 2 19.47 6.48 22 12 22s10-2.53 10-7.5c0-1.62-.48-3.3-1.3-4.83"
+ }
+ ],
+ ["path", { d: "M6 6.5a.495.495 0 0 1 .5-.5" }],
+ ["path", { d: "m9 12-2 2" }]
+ ];
+
+ const PanelBottomClose = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "m15 8-3 3-3-3" }]
+ ];
+
+ const PanelBottomDashed = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M14 15h1" }],
+ ["path", { d: "M19 15h2" }],
+ ["path", { d: "M3 15h2" }],
+ ["path", { d: "M9 15h1" }]
+ ];
+
+ const PanelBottomOpen = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "m9 10 3-3 3 3" }]
+ ];
+
+ const PanelBottom = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 15h18" }]
+ ];
+
+ const PanelLeftClose = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 3v18" }],
+ ["path", { d: "m16 15-3-3 3-3" }]
+ ];
+
+ const PanelLeftDashed = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 14v1" }],
+ ["path", { d: "M9 19v2" }],
+ ["path", { d: "M9 3v2" }],
+ ["path", { d: "M9 9v1" }]
+ ];
+
+ const PanelLeftRightDashed = [
+ ["path", { d: "M15 10V9" }],
+ ["path", { d: "M15 15v-1" }],
+ ["path", { d: "M15 21v-2" }],
+ ["path", { d: "M15 5V3" }],
+ ["path", { d: "M9 10V9" }],
+ ["path", { d: "M9 15v-1" }],
+ ["path", { d: "M9 21v-2" }],
+ ["path", { d: "M9 5V3" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const PanelLeftOpen = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 3v18" }],
+ ["path", { d: "m14 9 3 3-3 3" }]
+ ];
+
+ const PanelLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 3v18" }]
+ ];
+
+ const PanelRightClose = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M15 3v18" }],
+ ["path", { d: "m8 9 3 3-3 3" }]
+ ];
+
+ const PanelRightDashed = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M15 14v1" }],
+ ["path", { d: "M15 19v2" }],
+ ["path", { d: "M15 3v2" }],
+ ["path", { d: "M15 9v1" }]
+ ];
+
+ const PanelRightOpen = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M15 3v18" }],
+ ["path", { d: "m10 15-3-3 3-3" }]
+ ];
+
+ const PanelRight = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M15 3v18" }]
+ ];
+
+ const PanelTopBottomDashed = [
+ ["path", { d: "M14 15h1" }],
+ ["path", { d: "M14 9h1" }],
+ ["path", { d: "M19 15h2" }],
+ ["path", { d: "M19 9h2" }],
+ ["path", { d: "M3 15h2" }],
+ ["path", { d: "M3 9h2" }],
+ ["path", { d: "M9 15h1" }],
+ ["path", { d: "M9 9h1" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const PanelTopClose = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "m9 16 3-3 3 3" }]
+ ];
+
+ const PanelTopDashed = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M14 9h1" }],
+ ["path", { d: "M19 9h2" }],
+ ["path", { d: "M3 9h2" }],
+ ["path", { d: "M9 9h1" }]
+ ];
+
+ const PanelTopOpen = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "m15 14-3 3-3-3" }]
+ ];
+
+ const PanelTop = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }]
+ ];
+
+ const PanelsLeftBottom = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 3v18" }],
+ ["path", { d: "M9 15h12" }]
+ ];
+
+ const PanelsRightBottom = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 15h12" }],
+ ["path", { d: "M15 3v18" }]
+ ];
+
+ const PanelsTopLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "M9 21V9" }]
+ ];
+
+ const Paperclip = [
+ [
+ "path",
+ {
+ d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551"
+ }
+ ]
+ ];
+
+ const Parentheses = [
+ ["path", { d: "M8 21s-4-3-4-9 4-9 4-9" }],
+ ["path", { d: "M16 3s4 3 4 9-4 9-4 9" }]
+ ];
+
+ const ParkingMeter = [
+ ["path", { d: "M11 15h2" }],
+ ["path", { d: "M12 12v3" }],
+ ["path", { d: "M12 19v3" }],
+ [
+ "path",
+ {
+ d: "M15.282 19a1 1 0 0 0 .948-.68l2.37-6.988a7 7 0 1 0-13.2 0l2.37 6.988a1 1 0 0 0 .948.68z"
+ }
+ ],
+ ["path", { d: "M9 9a3 3 0 1 1 6 0" }]
+ ];
+
+ const PartyPopper = [
+ ["path", { d: "M5.8 11.3 2 22l10.7-3.79" }],
+ ["path", { d: "M4 3h.01" }],
+ ["path", { d: "M22 8h.01" }],
+ ["path", { d: "M15 2h.01" }],
+ ["path", { d: "M22 20h.01" }],
+ [
+ "path",
+ {
+ d: "m22 2-2.24.75a2.9 2.9 0 0 0-1.96 3.12c.1.86-.57 1.63-1.45 1.63h-.38c-.86 0-1.6.6-1.76 1.44L14 10"
+ }
+ ],
+ ["path", { d: "m22 13-.82-.33c-.86-.34-1.82.2-1.98 1.11c-.11.7-.72 1.22-1.43 1.22H17" }],
+ ["path", { d: "m11 2 .33.82c.34.86-.2 1.82-1.11 1.98C9.52 4.9 9 5.52 9 6.23V7" }],
+ [
+ "path",
+ {
+ d: "M11 13c1.93 1.93 2.83 4.17 2 5-.83.83-3.07-.07-5-2-1.93-1.93-2.83-4.17-2-5 .83-.83 3.07.07 5 2Z"
+ }
+ ]
+ ];
+
+ const Pause = [
+ ["rect", { x: "14", y: "3", width: "5", height: "18", rx: "1" }],
+ ["rect", { x: "5", y: "3", width: "5", height: "18", rx: "1" }]
+ ];
+
+ const PcCase = [
+ ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2" }],
+ ["path", { d: "M15 14h.01" }],
+ ["path", { d: "M9 6h6" }],
+ ["path", { d: "M9 10h6" }]
+ ];
+
+ const PenLine = [
+ ["path", { d: "M13 21h8" }],
+ [
+ "path",
+ {
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
+ }
+ ]
+ ];
+
+ const PawPrint = [
+ ["circle", { cx: "11", cy: "4", r: "2" }],
+ ["circle", { cx: "18", cy: "8", r: "2" }],
+ ["circle", { cx: "20", cy: "16", r: "2" }],
+ [
+ "path",
+ {
+ d: "M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"
+ }
+ ]
+ ];
+
+ const PenOff = [
+ [
+ "path",
+ {
+ d: "m10 10-6.157 6.162a2 2 0 0 0-.5.833l-1.322 4.36a.5.5 0 0 0 .622.624l4.358-1.323a2 2 0 0 0 .83-.5L14 13.982"
+ }
+ ],
+ ["path", { d: "m12.829 7.172 4.359-4.346a1 1 0 1 1 3.986 3.986l-4.353 4.353" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const PenTool = [
+ [
+ "path",
+ {
+ d: "M15.707 21.293a1 1 0 0 1-1.414 0l-1.586-1.586a1 1 0 0 1 0-1.414l5.586-5.586a1 1 0 0 1 1.414 0l1.586 1.586a1 1 0 0 1 0 1.414z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m18 13-1.375-6.874a1 1 0 0 0-.746-.776L3.235 2.028a1 1 0 0 0-1.207 1.207L5.35 15.879a1 1 0 0 0 .776.746L13 18"
+ }
+ ],
+ ["path", { d: "m2.3 2.3 7.286 7.286" }],
+ ["circle", { cx: "11", cy: "11", r: "2" }]
+ ];
+
+ const Pen = [
+ [
+ "path",
+ {
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
+ }
+ ]
+ ];
+
+ const PencilLine = [
+ ["path", { d: "M13 21h8" }],
+ ["path", { d: "m15 5 4 4" }],
+ [
+ "path",
+ {
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
+ }
+ ]
+ ];
+
+ const PencilOff = [
+ [
+ "path",
+ {
+ d: "m10 10-6.157 6.162a2 2 0 0 0-.5.833l-1.322 4.36a.5.5 0 0 0 .622.624l4.358-1.323a2 2 0 0 0 .83-.5L14 13.982"
+ }
+ ],
+ ["path", { d: "m12.829 7.172 4.359-4.346a1 1 0 1 1 3.986 3.986l-4.353 4.353" }],
+ ["path", { d: "m15 5 4 4" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const PencilRuler = [
+ ["path", { d: "M13 7 8.7 2.7a2.41 2.41 0 0 0-3.4 0L2.7 5.3a2.41 2.41 0 0 0 0 3.4L7 13" }],
+ ["path", { d: "m8 6 2-2" }],
+ ["path", { d: "m18 16 2-2" }],
+ ["path", { d: "m17 11 4.3 4.3c.94.94.94 2.46 0 3.4l-2.6 2.6c-.94.94-2.46.94-3.4 0L11 17" }],
+ [
+ "path",
+ {
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
+ }
+ ],
+ ["path", { d: "m15 5 4 4" }]
+ ];
+
+ const Pencil = [
+ [
+ "path",
+ {
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
+ }
+ ],
+ ["path", { d: "m15 5 4 4" }]
+ ];
+
+ const Pentagon = [
+ [
+ "path",
+ {
+ d: "M10.83 2.38a2 2 0 0 1 2.34 0l8 5.74a2 2 0 0 1 .73 2.25l-3.04 9.26a2 2 0 0 1-1.9 1.37H7.04a2 2 0 0 1-1.9-1.37L2.1 10.37a2 2 0 0 1 .73-2.25z"
+ }
+ ]
+ ];
+
+ const Percent = [
+ ["line", { x1: "19", x2: "5", y1: "5", y2: "19" }],
+ ["circle", { cx: "6.5", cy: "6.5", r: "2.5" }],
+ ["circle", { cx: "17.5", cy: "17.5", r: "2.5" }]
+ ];
+
+ const PersonStanding = [
+ ["circle", { cx: "12", cy: "5", r: "1" }],
+ ["path", { d: "m9 20 3-6 3 6" }],
+ ["path", { d: "m6 8 6 2 6-2" }],
+ ["path", { d: "M12 10v4" }]
+ ];
+
+ const PhilippinePeso = [
+ ["path", { d: "M20 11H4" }],
+ ["path", { d: "M20 7H4" }],
+ ["path", { d: "M7 21V4a1 1 0 0 1 1-1h4a1 1 0 0 1 0 12H7" }]
+ ];
+
+ const PhoneCall = [
+ ["path", { d: "M13 2a9 9 0 0 1 9 9" }],
+ ["path", { d: "M13 6a5 5 0 0 1 5 5" }],
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const PhoneForwarded = [
+ ["path", { d: "M14 6h8" }],
+ ["path", { d: "m18 2 4 4-4 4" }],
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const PhoneIncoming = [
+ ["path", { d: "M16 2v6h6" }],
+ ["path", { d: "m22 2-6 6" }],
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const PhoneMissed = [
+ ["path", { d: "m16 2 6 6" }],
+ ["path", { d: "m22 2-6 6" }],
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const PhoneOff = [
+ [
+ "path",
+ {
+ d: "M10.1 13.9a14 14 0 0 0 3.732 2.668 1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2 18 18 0 0 1-12.728-5.272"
+ }
+ ],
+ ["path", { d: "M22 2 2 22" }],
+ [
+ "path",
+ {
+ d: "M4.76 13.582A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 .244.473"
+ }
+ ]
+ ];
+
+ const PhoneOutgoing = [
+ ["path", { d: "m16 8 6-6" }],
+ ["path", { d: "M22 8V2h-6" }],
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const Phone = [
+ [
+ "path",
+ {
+ d: "M13.832 16.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233 14 14 0 0 0 6.392 6.384"
+ }
+ ]
+ ];
+
+ const Pi = [
+ ["line", { x1: "9", x2: "9", y1: "4", y2: "20" }],
+ ["path", { d: "M4 7c0-1.7 1.3-3 3-3h13" }],
+ ["path", { d: "M18 20c-1.7 0-3-1.3-3-3V4" }]
+ ];
+
+ const Piano = [
+ [
+ "path",
+ {
+ d: "M18.5 8c-1.4 0-2.6-.8-3.2-2A6.87 6.87 0 0 0 2 9v11a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-8.5C22 9.6 20.4 8 18.5 8"
+ }
+ ],
+ ["path", { d: "M2 14h20" }],
+ ["path", { d: "M6 14v4" }],
+ ["path", { d: "M10 14v4" }],
+ ["path", { d: "M14 14v4" }],
+ ["path", { d: "M18 14v4" }]
+ ];
+
+ const Pickaxe = [
+ ["path", { d: "m14 13-8.381 8.38a1 1 0 0 1-3.001-3L11 9.999" }],
+ [
+ "path",
+ {
+ d: "M15.973 4.027A13 13 0 0 0 5.902 2.373c-1.398.342-1.092 2.158.277 2.601a19.9 19.9 0 0 1 5.822 3.024"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M16.001 11.999a19.9 19.9 0 0 1 3.024 5.824c.444 1.369 2.26 1.676 2.603.278A13 13 0 0 0 20 8.069"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M18.352 3.352a1.205 1.205 0 0 0-1.704 0l-5.296 5.296a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l5.296-5.296a1.205 1.205 0 0 0 0-1.704z"
+ }
+ ]
+ ];
+
+ const PictureInPicture2 = [
+ ["path", { d: "M21 9V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h4" }],
+ ["rect", { width: "10", height: "7", x: "12", y: "13", rx: "2" }]
+ ];
+
+ const PictureInPicture = [
+ ["path", { d: "M2 10h6V4" }],
+ ["path", { d: "m2 4 6 6" }],
+ ["path", { d: "M21 10V7a2 2 0 0 0-2-2h-7" }],
+ ["path", { d: "M3 14v2a2 2 0 0 0 2 2h3" }],
+ ["rect", { x: "12", y: "14", width: "10", height: "7", rx: "1" }]
+ ];
+
+ const PiggyBank = [
+ [
+ "path",
+ {
+ d: "M11 17h3v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3a3.16 3.16 0 0 0 2-2h1a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-1a5 5 0 0 0-2-4V3a4 4 0 0 0-3.2 1.6l-.3.4H11a6 6 0 0 0-6 6v1a5 5 0 0 0 2 4v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1z"
+ }
+ ],
+ ["path", { d: "M16 10h.01" }],
+ ["path", { d: "M2 8v1a2 2 0 0 0 2 2h1" }]
+ ];
+
+ const PilcrowLeft = [
+ ["path", { d: "M14 3v11" }],
+ ["path", { d: "M14 9h-3a3 3 0 0 1 0-6h9" }],
+ ["path", { d: "M18 3v11" }],
+ ["path", { d: "M22 18H2l4-4" }],
+ ["path", { d: "m6 22-4-4" }]
+ ];
+
+ const PilcrowRight = [
+ ["path", { d: "M10 3v11" }],
+ ["path", { d: "M10 9H7a1 1 0 0 1 0-6h8" }],
+ ["path", { d: "M14 3v11" }],
+ ["path", { d: "m18 14 4 4H2" }],
+ ["path", { d: "m22 18-4 4" }]
+ ];
+
+ const Pilcrow = [
+ ["path", { d: "M13 4v16" }],
+ ["path", { d: "M17 4v16" }],
+ ["path", { d: "M19 4H9.5a4.5 4.5 0 0 0 0 9H13" }]
+ ];
+
+ const PillBottle = [
+ ["path", { d: "M18 11h-4a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h4" }],
+ ["path", { d: "M6 7v13a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7" }],
+ ["rect", { width: "16", height: "5", x: "4", y: "2", rx: "1" }]
+ ];
+
+ const Pill = [
+ ["path", { d: "m10.5 20.5 10-10a4.95 4.95 0 1 0-7-7l-10 10a4.95 4.95 0 1 0 7 7Z" }],
+ ["path", { d: "m8.5 8.5 7 7" }]
+ ];
+
+ const PinOff = [
+ ["path", { d: "M12 17v5" }],
+ ["path", { d: "M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11" }]
+ ];
+
+ const Pin = [
+ ["path", { d: "M12 17v5" }],
+ [
+ "path",
+ {
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"
+ }
+ ]
+ ];
+
+ const Pipette = [
+ [
+ "path",
+ {
+ d: "m12 9-8.414 8.414A2 2 0 0 0 3 18.828v1.344a2 2 0 0 1-.586 1.414A2 2 0 0 1 3.828 21h1.344a2 2 0 0 0 1.414-.586L15 12"
+ }
+ ],
+ ["path", { d: "m18 9 .4.4a1 1 0 1 1-3 3l-3.8-3.8a1 1 0 1 1 3-3l.4.4 3.4-3.4a1 1 0 1 1 3 3z" }],
+ ["path", { d: "m2 22 .414-.414" }]
+ ];
+
+ const Pizza = [
+ ["path", { d: "m12 14-1 1" }],
+ ["path", { d: "m13.75 18.25-1.25 1.42" }],
+ ["path", { d: "M17.775 5.654a15.68 15.68 0 0 0-12.121 12.12" }],
+ ["path", { d: "M18.8 9.3a1 1 0 0 0 2.1 7.7" }],
+ [
+ "path",
+ {
+ d: "M21.964 20.732a1 1 0 0 1-1.232 1.232l-18-5a1 1 0 0 1-.695-1.232A19.68 19.68 0 0 1 15.732 2.037a1 1 0 0 1 1.232.695z"
+ }
+ ]
+ ];
+
+ const PlaneLanding = [
+ ["path", { d: "M2 22h20" }],
+ [
+ "path",
+ {
+ d: "M3.77 10.77 2 9l2-4.5 1.1.55c.55.28.9.84.9 1.45s.35 1.17.9 1.45L8 8.5l3-6 1.05.53a2 2 0 0 1 1.09 1.52l.72 5.4a2 2 0 0 0 1.09 1.52l4.4 2.2c.42.22.78.55 1.01.96l.6 1.03c.49.88-.06 1.98-1.06 2.1l-1.18.15c-.47.06-.95-.02-1.37-.24L4.29 11.15a2 2 0 0 1-.52-.38Z"
+ }
+ ]
+ ];
+
+ const PlaneTakeoff = [
+ ["path", { d: "M2 22h20" }],
+ [
+ "path",
+ {
+ d: "M6.36 17.4 4 17l-2-4 1.1-.55a2 2 0 0 1 1.8 0l.17.1a2 2 0 0 0 1.8 0L8 12 5 6l.9-.45a2 2 0 0 1 2.09.2l4.02 3a2 2 0 0 0 2.1.2l4.19-2.06a2.41 2.41 0 0 1 1.73-.17L21 7a1.4 1.4 0 0 1 .87 1.99l-.38.76c-.23.46-.6.84-1.07 1.08L7.58 17.2a2 2 0 0 1-1.22.18Z"
+ }
+ ]
+ ];
+
+ const Plane = [
+ [
+ "path",
+ {
+ d: "M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-.9.1-1.1.5l-.3.5c-.2.5-.1 1 .3 1.3L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.5 5.3c.3.4.8.5 1.3.3l.5-.2c.4-.3.6-.7.5-1.2z"
+ }
+ ]
+ ];
+
+ const Play = [
+ [
+ "path",
+ { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" }
+ ]
+ ];
+
+ const Plug2 = [
+ ["path", { d: "M9 2v6" }],
+ ["path", { d: "M15 2v6" }],
+ ["path", { d: "M12 17v5" }],
+ ["path", { d: "M5 8h14" }],
+ ["path", { d: "M6 11V8h12v3a6 6 0 1 1-12 0Z" }]
+ ];
+
+ const PlugZap = [
+ ["path", { d: "M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z" }],
+ ["path", { d: "m2 22 3-3" }],
+ ["path", { d: "M7.5 13.5 10 11" }],
+ ["path", { d: "M10.5 16.5 13 14" }],
+ ["path", { d: "m18 3-4 4h6l-4 4" }]
+ ];
+
+ const Plug = [
+ ["path", { d: "M12 22v-5" }],
+ ["path", { d: "M9 8V2" }],
+ ["path", { d: "M15 8V2" }],
+ ["path", { d: "M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z" }]
+ ];
+
+ const Plus = [
+ ["path", { d: "M5 12h14" }],
+ ["path", { d: "M12 5v14" }]
+ ];
+
+ const PocketKnife = [
+ ["path", { d: "M3 2v1c0 1 2 1 2 2S3 6 3 7s2 1 2 2-2 1-2 2 2 1 2 2" }],
+ ["path", { d: "M18 6h.01" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "M20.83 8.83a4 4 0 0 0-5.66-5.66l-12 12a4 4 0 1 0 5.66 5.66Z" }],
+ ["path", { d: "M18 11.66V22a4 4 0 0 0 4-4V6" }]
+ ];
+
+ const Pocket = [
+ ["path", { d: "M20 3a2 2 0 0 1 2 2v6a1 1 0 0 1-20 0V5a2 2 0 0 1 2-2z" }],
+ ["path", { d: "m8 10 4 4 4-4" }]
+ ];
+
+ const Podcast = [
+ ["path", { d: "M13 17a1 1 0 1 0-2 0l.5 4.5a0.5 0.5 0 0 0 1 0z", fill: "currentColor" }],
+ ["path", { d: "M16.85 18.58a9 9 0 1 0-9.7 0" }],
+ ["path", { d: "M8 14a5 5 0 1 1 8 0" }],
+ ["circle", { cx: "12", cy: "11", r: "1", fill: "currentColor" }]
+ ];
+
+ const PointerOff = [
+ ["path", { d: "M10 4.5V4a2 2 0 0 0-2.41-1.957" }],
+ ["path", { d: "M13.9 8.4a2 2 0 0 0-1.26-1.295" }],
+ ["path", { d: "M21.7 16.2A8 8 0 0 0 22 14v-3a2 2 0 1 0-4 0v-1a2 2 0 0 0-3.63-1.158" }],
+ [
+ "path",
+ { d: "m7 15-1.8-1.8a2 2 0 0 0-2.79 2.86L6 19.7a7.74 7.74 0 0 0 6 2.3h2a8 8 0 0 0 5.657-2.343" }
+ ],
+ ["path", { d: "M6 6v8" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Pointer = [
+ ["path", { d: "M22 14a8 8 0 0 1-8 8" }],
+ ["path", { d: "M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }],
+ ["path", { d: "M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1" }],
+ ["path", { d: "M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10" }],
+ [
+ "path",
+ {
+ d: "M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"
+ }
+ ]
+ ];
+
+ const Popcorn = [
+ ["path", { d: "M18 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4" }],
+ ["path", { d: "M10 22 9 8" }],
+ ["path", { d: "m14 22 1-14" }],
+ [
+ "path",
+ {
+ d: "M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z"
+ }
+ ]
+ ];
+
+ const Popsicle = [
+ [
+ "path",
+ { d: "M18.6 14.4c.8-.8.8-2 0-2.8l-8.1-8.1a4.95 4.95 0 1 0-7.1 7.1l8.1 8.1c.9.7 2.1.7 2.9-.1Z" }
+ ],
+ ["path", { d: "m22 22-5.5-5.5" }]
+ ];
+
+ const PoundSterling = [
+ ["path", { d: "M18 7c0-5.333-8-5.333-8 0" }],
+ ["path", { d: "M10 7v14" }],
+ ["path", { d: "M6 21h12" }],
+ ["path", { d: "M6 13h10" }]
+ ];
+
+ const PowerOff = [
+ ["path", { d: "M18.36 6.64A9 9 0 0 1 20.77 15" }],
+ ["path", { d: "M6.16 6.16a9 9 0 1 0 12.68 12.68" }],
+ ["path", { d: "M12 2v4" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Power = [
+ ["path", { d: "M12 2v10" }],
+ ["path", { d: "M18.4 6.6a9 9 0 1 1-12.77.04" }]
+ ];
+
+ const Presentation = [
+ ["path", { d: "M2 3h20" }],
+ ["path", { d: "M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3" }],
+ ["path", { d: "m7 21 5-5 5 5" }]
+ ];
+
+ const PrinterCheck = [
+ ["path", { d: "M13.5 22H7a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v.5" }],
+ ["path", { d: "m16 19 2 2 4-4" }],
+ ["path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" }]
+ ];
+
+ const Projector = [
+ ["path", { d: "M5 7 3 5" }],
+ ["path", { d: "M9 6V3" }],
+ ["path", { d: "m13 7 2-2" }],
+ ["circle", { cx: "9", cy: "13", r: "3" }],
+ [
+ "path",
+ { d: "M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17" }
+ ],
+ ["path", { d: "M16 16h2" }]
+ ];
+
+ const Printer = [
+ ["path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M6 9V3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6" }],
+ ["rect", { x: "6", y: "14", width: "12", height: "8", rx: "1" }]
+ ];
+
+ const Proportions = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M12 9v11" }],
+ ["path", { d: "M2 9h13a2 2 0 0 1 2 2v9" }]
+ ];
+
+ const Puzzle = [
+ [
+ "path",
+ {
+ d: "M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z"
+ }
+ ]
+ ];
+
+ const Pyramid = [
+ [
+ "path",
+ {
+ d: "M2.5 16.88a1 1 0 0 1-.32-1.43l9-13.02a1 1 0 0 1 1.64 0l9 13.01a1 1 0 0 1-.32 1.44l-8.51 4.86a2 2 0 0 1-1.98 0Z"
+ }
+ ],
+ ["path", { d: "M12 2v20" }]
+ ];
+
+ const QrCode = [
+ ["rect", { width: "5", height: "5", x: "3", y: "3", rx: "1" }],
+ ["rect", { width: "5", height: "5", x: "16", y: "3", rx: "1" }],
+ ["rect", { width: "5", height: "5", x: "3", y: "16", rx: "1" }],
+ ["path", { d: "M21 16h-3a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "M21 21v.01" }],
+ ["path", { d: "M12 7v3a2 2 0 0 1-2 2H7" }],
+ ["path", { d: "M3 12h.01" }],
+ ["path", { d: "M12 3h.01" }],
+ ["path", { d: "M12 16v.01" }],
+ ["path", { d: "M16 12h1" }],
+ ["path", { d: "M21 12v.01" }],
+ ["path", { d: "M12 21v-1" }]
+ ];
+
+ const Quote = [
+ [
+ "path",
+ {
+ d: "M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"
+ }
+ ]
+ ];
+
+ const Rabbit = [
+ ["path", { d: "M13 16a3 3 0 0 1 2.24 5" }],
+ ["path", { d: "M18 12h.01" }],
+ [
+ "path",
+ {
+ d: "M18 21h-8a4 4 0 0 1-4-4 7 7 0 0 1 7-7h.2L9.6 6.4a1 1 0 1 1 2.8-2.8L15.8 7h.2c3.3 0 6 2.7 6 6v1a2 2 0 0 1-2 2h-1a3 3 0 0 0-3 3"
+ }
+ ],
+ ["path", { d: "M20 8.54V4a2 2 0 1 0-4 0v3" }],
+ ["path", { d: "M7.612 12.524a3 3 0 1 0-1.6 4.3" }]
+ ];
+
+ const Radiation = [
+ ["path", { d: "M12 12h.01" }],
+ [
+ "path",
+ {
+ d: "M14 15.4641a4 4 0 0 1-4 0L7.52786 19.74597 A 1 1 0 0 0 7.99303 21.16211 10 10 0 0 0 16.00697 21.16211 1 1 0 0 0 16.47214 19.74597z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M16 12a4 4 0 0 0-2-3.464l2.472-4.282a1 1 0 0 1 1.46-.305 10 10 0 0 1 4.006 6.94A1 1 0 0 1 21 12z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M8 12a4 4 0 0 1 2-3.464L7.528 4.254a1 1 0 0 0-1.46-.305 10 10 0 0 0-4.006 6.94A1 1 0 0 0 3 12z"
+ }
+ ]
+ ];
+
+ const Radar = [
+ ["path", { d: "M19.07 4.93A10 10 0 0 0 6.99 3.34" }],
+ ["path", { d: "M4 6h.01" }],
+ ["path", { d: "M2.29 9.62A10 10 0 1 0 21.31 8.35" }],
+ ["path", { d: "M16.24 7.76A6 6 0 1 0 8.23 16.67" }],
+ ["path", { d: "M12 18h.01" }],
+ ["path", { d: "M17.99 11.66A6 6 0 0 1 15.77 16.67" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }],
+ ["path", { d: "m13.41 10.59 5.66-5.66" }]
+ ];
+
+ const Radical = [
+ [
+ "path",
+ {
+ d: "M3 12h3.28a1 1 0 0 1 .948.684l2.298 7.934a.5.5 0 0 0 .96-.044L13.82 4.771A1 1 0 0 1 14.792 4H21"
+ }
+ ]
+ ];
+
+ const RadioReceiver = [
+ ["path", { d: "M5 16v2" }],
+ ["path", { d: "M19 16v2" }],
+ ["rect", { width: "20", height: "8", x: "2", y: "8", rx: "2" }],
+ ["path", { d: "M18 12h.01" }]
+ ];
+
+ const Radio = [
+ ["path", { d: "M16.247 7.761a6 6 0 0 1 0 8.478" }],
+ ["path", { d: "M19.075 4.933a10 10 0 0 1 0 14.134" }],
+ ["path", { d: "M4.925 19.067a10 10 0 0 1 0-14.134" }],
+ ["path", { d: "M7.753 16.239a6 6 0 0 1 0-8.478" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const RadioTower = [
+ ["path", { d: "M4.9 16.1C1 12.2 1 5.8 4.9 1.9" }],
+ ["path", { d: "M7.8 4.7a6.14 6.14 0 0 0-.8 7.5" }],
+ ["circle", { cx: "12", cy: "9", r: "2" }],
+ ["path", { d: "M16.2 4.8c2 2 2.26 5.11.8 7.47" }],
+ ["path", { d: "M19.1 1.9a9.96 9.96 0 0 1 0 14.1" }],
+ ["path", { d: "M9.5 18h5" }],
+ ["path", { d: "m8 22 4-11 4 11" }]
+ ];
+
+ const Radius = [
+ ["path", { d: "M20.34 17.52a10 10 0 1 0-2.82 2.82" }],
+ ["circle", { cx: "19", cy: "19", r: "2" }],
+ ["path", { d: "m13.41 13.41 4.18 4.18" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const RailSymbol = [
+ ["path", { d: "M5 15h14" }],
+ ["path", { d: "M5 9h14" }],
+ ["path", { d: "m14 20-5-5 6-6-5-5" }]
+ ];
+
+ const Rainbow = [
+ ["path", { d: "M22 17a10 10 0 0 0-20 0" }],
+ ["path", { d: "M6 17a6 6 0 0 1 12 0" }],
+ ["path", { d: "M10 17a2 2 0 0 1 4 0" }]
+ ];
+
+ const Rat = [
+ ["path", { d: "M13 22H4a2 2 0 0 1 0-4h12" }],
+ ["path", { d: "M13.236 18a3 3 0 0 0-2.2-5" }],
+ ["path", { d: "M16 9h.01" }],
+ [
+ "path",
+ {
+ d: "M16.82 3.94a3 3 0 1 1 3.237 4.868l1.815 2.587a1.5 1.5 0 0 1-1.5 2.1l-2.872-.453a3 3 0 0 0-3.5 3"
+ }
+ ],
+ ["path", { d: "M17 4.988a3 3 0 1 0-5.2 2.052A7 7 0 0 0 4 14.015 4 4 0 0 0 8 18" }]
+ ];
+
+ const Ratio = [
+ ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2" }],
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const ReceiptCent = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M12 6.5v11" }],
+ ["path", { d: "M15 9.4a4 4 0 1 0 0 5.2" }]
+ ];
+
+ const ReceiptEuro = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M8 12h5" }],
+ ["path", { d: "M16 9.5a4 4 0 1 0 0 5.2" }]
+ ];
+
+ const ReceiptIndianRupee = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M8 7h8" }],
+ ["path", { d: "M12 17.5 8 15h1a4 4 0 0 0 0-8" }],
+ ["path", { d: "M8 11h8" }]
+ ];
+
+ const ReceiptJapaneseYen = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "m12 10 3-3" }],
+ ["path", { d: "m9 7 3 3v7.5" }],
+ ["path", { d: "M9 11h6" }],
+ ["path", { d: "M9 15h6" }]
+ ];
+
+ const ReceiptRussianRuble = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M8 15h5" }],
+ ["path", { d: "M8 11h5a2 2 0 1 0 0-4h-3v10" }]
+ ];
+
+ const ReceiptPoundSterling = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M8 13h5" }],
+ ["path", { d: "M10 17V9.5a2.5 2.5 0 0 1 5 0" }],
+ ["path", { d: "M8 17h7" }]
+ ];
+
+ const ReceiptSwissFranc = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M10 17V7h5" }],
+ ["path", { d: "M10 11h4" }],
+ ["path", { d: "M8 15h5" }]
+ ];
+
+ const ReceiptText = [
+ ["path", { d: "M13 16H8" }],
+ ["path", { d: "M14 8H8" }],
+ ["path", { d: "M16 12H8" }],
+ [
+ "path",
+ {
+ d: "M4 3a1 1 0 0 1 1-1 1.3 1.3 0 0 1 .7.2l.933.6a1.3 1.3 0 0 0 1.4 0l.934-.6a1.3 1.3 0 0 1 1.4 0l.933.6a1.3 1.3 0 0 0 1.4 0l.933-.6a1.3 1.3 0 0 1 1.4 0l.934.6a1.3 1.3 0 0 0 1.4 0l.933-.6A1.3 1.3 0 0 1 19 2a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1 1.3 1.3 0 0 1-.7-.2l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.934.6a1.3 1.3 0 0 1-1.4 0l-.933-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-1.4 0l-.934-.6a1.3 1.3 0 0 0-1.4 0l-.933.6a1.3 1.3 0 0 1-.7.2 1 1 0 0 1-1-1z"
+ }
+ ]
+ ];
+
+ const ReceiptTurkishLira = [
+ ["path", { d: "M10 6.5v11a5.5 5.5 0 0 0 5.5-5.5" }],
+ ["path", { d: "m14 8-6 3" }],
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1z" }]
+ ];
+
+ const Receipt = [
+ ["path", { d: "M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" }],
+ ["path", { d: "M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8" }],
+ ["path", { d: "M12 17.5v-11" }]
+ ];
+
+ const RectangleCircle = [
+ ["path", { d: "M14 4v16H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z" }],
+ ["circle", { cx: "14", cy: "12", r: "8" }]
+ ];
+
+ const RectangleEllipsis = [
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M17 12h.01" }],
+ ["path", { d: "M7 12h.01" }]
+ ];
+
+ const RectangleGoggles = [
+ [
+ "path",
+ {
+ d: "M20 6a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4a2 2 0 0 1-1.6-.8l-1.6-2.13a1 1 0 0 0-1.6 0L9.6 17.2A2 2 0 0 1 8 18H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z"
+ }
+ ]
+ ];
+
+ const RectangleHorizontal = [
+ ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2" }]
+ ];
+
+ const RectangleVertical = [
+ ["rect", { width: "12", height: "20", x: "6", y: "2", rx: "2" }]
+ ];
+
+ const Recycle = [
+ ["path", { d: "M7 19H4.815a1.83 1.83 0 0 1-1.57-.881 1.785 1.785 0 0 1-.004-1.784L7.196 9.5" }],
+ ["path", { d: "M11 19h8.203a1.83 1.83 0 0 0 1.556-.89 1.784 1.784 0 0 0 0-1.775l-1.226-2.12" }],
+ ["path", { d: "m14 16-3 3 3 3" }],
+ ["path", { d: "M8.293 13.596 7.196 9.5 3.1 10.598" }],
+ [
+ "path",
+ {
+ d: "m9.344 5.811 1.093-1.892A1.83 1.83 0 0 1 11.985 3a1.784 1.784 0 0 1 1.546.888l3.943 6.843"
+ }
+ ],
+ ["path", { d: "m13.378 9.633 4.096 1.098 1.097-4.096" }]
+ ];
+
+ const Redo2 = [
+ ["path", { d: "m15 14 5-5-5-5" }],
+ ["path", { d: "M20 9H9.5A5.5 5.5 0 0 0 4 14.5A5.5 5.5 0 0 0 9.5 20H13" }]
+ ];
+
+ const RedoDot = [
+ ["circle", { cx: "12", cy: "17", r: "1" }],
+ ["path", { d: "M21 7v6h-6" }],
+ ["path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7" }]
+ ];
+
+ const Redo = [
+ ["path", { d: "M21 7v6h-6" }],
+ ["path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7" }]
+ ];
+
+ const RefreshCcwDot = [
+ ["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
+ ["path", { d: "M3 3v5h5" }],
+ ["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" }],
+ ["path", { d: "M16 16h5v5" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }]
+ ];
+
+ const RefreshCwOff = [
+ ["path", { d: "M21 8L18.74 5.74A9.75 9.75 0 0 0 12 3C11 3 10.03 3.16 9.13 3.47" }],
+ ["path", { d: "M8 16H3v5" }],
+ ["path", { d: "M3 12C3 9.51 4 7.26 5.64 5.64" }],
+ ["path", { d: "m3 16 2.26 2.26A9.75 9.75 0 0 0 12 21c2.49 0 4.74-1 6.36-2.64" }],
+ ["path", { d: "M21 12c0 1-.16 1.97-.47 2.87" }],
+ ["path", { d: "M21 3v5h-5" }],
+ ["path", { d: "M22 22 2 2" }]
+ ];
+
+ const RefreshCcw = [
+ ["path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
+ ["path", { d: "M3 3v5h5" }],
+ ["path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" }],
+ ["path", { d: "M16 16h5v5" }]
+ ];
+
+ const RefreshCw = [
+ ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }],
+ ["path", { d: "M21 3v5h-5" }],
+ ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }],
+ ["path", { d: "M8 16H3v5" }]
+ ];
+
+ const Refrigerator = [
+ ["path", { d: "M5 6a4 4 0 0 1 4-4h6a4 4 0 0 1 4 4v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6Z" }],
+ ["path", { d: "M5 10h14" }],
+ ["path", { d: "M15 7v6" }]
+ ];
+
+ const Regex = [
+ ["path", { d: "M17 3v10" }],
+ ["path", { d: "m12.67 5.5 8.66 5" }],
+ ["path", { d: "m12.67 10.5 8.66-5" }],
+ ["path", { d: "M9 17a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-2z" }]
+ ];
+
+ const RemoveFormatting = [
+ ["path", { d: "M4 7V4h16v3" }],
+ ["path", { d: "M5 20h6" }],
+ ["path", { d: "M13 4 8 20" }],
+ ["path", { d: "m15 15 5 5" }],
+ ["path", { d: "m20 15-5 5" }]
+ ];
+
+ const Repeat1 = [
+ ["path", { d: "m17 2 4 4-4 4" }],
+ ["path", { d: "M3 11v-1a4 4 0 0 1 4-4h14" }],
+ ["path", { d: "m7 22-4-4 4-4" }],
+ ["path", { d: "M21 13v1a4 4 0 0 1-4 4H3" }],
+ ["path", { d: "M11 10h1v4" }]
+ ];
+
+ const Repeat = [
+ ["path", { d: "m17 2 4 4-4 4" }],
+ ["path", { d: "M3 11v-1a4 4 0 0 1 4-4h14" }],
+ ["path", { d: "m7 22-4-4 4-4" }],
+ ["path", { d: "M21 13v1a4 4 0 0 1-4 4H3" }]
+ ];
+
+ const Repeat2 = [
+ ["path", { d: "m2 9 3-3 3 3" }],
+ ["path", { d: "M13 18H7a2 2 0 0 1-2-2V6" }],
+ ["path", { d: "m22 15-3 3-3-3" }],
+ ["path", { d: "M11 6h6a2 2 0 0 1 2 2v10" }]
+ ];
+
+ const ReplaceAll = [
+ ["path", { d: "M14 14a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1" }],
+ ["path", { d: "M14 4a1 1 0 0 1 1-1" }],
+ ["path", { d: "M15 10a1 1 0 0 1-1-1" }],
+ ["path", { d: "M19 14a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1" }],
+ ["path", { d: "M21 4a1 1 0 0 0-1-1" }],
+ ["path", { d: "M21 9a1 1 0 0 1-1 1" }],
+ ["path", { d: "m3 7 3 3 3-3" }],
+ ["path", { d: "M6 10V5a2 2 0 0 1 2-2h2" }],
+ ["rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }]
+ ];
+
+ const Replace = [
+ ["path", { d: "M14 4a1 1 0 0 1 1-1" }],
+ ["path", { d: "M15 10a1 1 0 0 1-1-1" }],
+ ["path", { d: "M21 4a1 1 0 0 0-1-1" }],
+ ["path", { d: "M21 9a1 1 0 0 1-1 1" }],
+ ["path", { d: "m3 7 3 3 3-3" }],
+ ["path", { d: "M6 10V5a2 2 0 0 1 2-2h2" }],
+ ["rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }]
+ ];
+
+ const ReplyAll = [
+ ["path", { d: "m12 17-5-5 5-5" }],
+ ["path", { d: "M22 18v-2a4 4 0 0 0-4-4H7" }],
+ ["path", { d: "m7 17-5-5 5-5" }]
+ ];
+
+ const Reply = [
+ ["path", { d: "M20 18v-2a4 4 0 0 0-4-4H4" }],
+ ["path", { d: "m9 17-5-5 5-5" }]
+ ];
+
+ const Rewind = [
+ ["path", { d: "M12 6a2 2 0 0 0-3.414-1.414l-6 6a2 2 0 0 0 0 2.828l6 6A2 2 0 0 0 12 18z" }],
+ ["path", { d: "M22 6a2 2 0 0 0-3.414-1.414l-6 6a2 2 0 0 0 0 2.828l6 6A2 2 0 0 0 22 18z" }]
+ ];
+
+ const Ribbon = [
+ ["path", { d: "M12 11.22C11 9.997 10 9 10 8a2 2 0 0 1 4 0c0 1-.998 2.002-2.01 3.22" }],
+ ["path", { d: "m12 18 2.57-3.5" }],
+ ["path", { d: "M6.243 9.016a7 7 0 0 1 11.507-.009" }],
+ ["path", { d: "M9.35 14.53 12 11.22" }],
+ [
+ "path",
+ {
+ d: "M9.35 14.53C7.728 12.246 6 10.221 6 7a6 5 0 0 1 12 0c-.005 3.22-1.778 5.235-3.43 7.5l3.557 4.527a1 1 0 0 1-.203 1.43l-1.894 1.36a1 1 0 0 1-1.384-.215L12 18l-2.679 3.593a1 1 0 0 1-1.39.213l-1.865-1.353a1 1 0 0 1-.203-1.422z"
+ }
+ ]
+ ];
+
+ const Rocket = [
+ [
+ "path",
+ {
+ d: "M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"
+ }
+ ],
+ ["path", { d: "M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" }],
+ ["path", { d: "M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" }]
+ ];
+
+ const RockingChair = [
+ ["polyline", { points: "3.5 2 6.5 12.5 18 12.5" }],
+ ["line", { x1: "9.5", x2: "5.5", y1: "12.5", y2: "20" }],
+ ["line", { x1: "15", x2: "18.5", y1: "12.5", y2: "20" }],
+ ["path", { d: "M2.75 18a13 13 0 0 0 18.5 0" }]
+ ];
+
+ const RollerCoaster = [
+ ["path", { d: "M6 19V5" }],
+ ["path", { d: "M10 19V6.8" }],
+ ["path", { d: "M14 19v-7.8" }],
+ ["path", { d: "M18 5v4" }],
+ ["path", { d: "M18 19v-6" }],
+ ["path", { d: "M22 19V9" }],
+ ["path", { d: "M2 19V9a4 4 0 0 1 4-4c2 0 4 1.33 6 4s4 4 6 4a4 4 0 1 0-3-6.65" }]
+ ];
+
+ const Rose = [
+ ["path", { d: "M17 10h-1a4 4 0 1 1 4-4v.534" }],
+ ["path", { d: "M17 6h1a4 4 0 0 1 1.42 7.74l-2.29.87a6 6 0 0 1-5.339-10.68l2.069-1.31" }],
+ [
+ "path",
+ { d: "M4.5 17c2.8-.5 4.4 0 5.5.8s1.8 2.2 2.3 3.7c-2 .4-3.5.4-4.8-.3-1.2-.6-2.3-1.9-3-4.2" }
+ ],
+ ["path", { d: "M9.77 12C4 15 2 22 2 22" }],
+ ["circle", { cx: "17", cy: "8", r: "2" }]
+ ];
+
+ const Rotate3d = [
+ [
+ "path",
+ {
+ d: "M16.466 7.5C15.643 4.237 13.952 2 12 2 9.239 2 7 6.477 7 12s2.239 10 5 10c.342 0 .677-.069 1-.2"
+ }
+ ],
+ ["path", { d: "m15.194 13.707 3.814 1.86-1.86 3.814" }],
+ [
+ "path",
+ {
+ d: "M19 15.57c-1.804.885-4.274 1.43-7 1.43-5.523 0-10-2.239-10-5s4.477-5 10-5c4.838 0 8.873 1.718 9.8 4"
+ }
+ ]
+ ];
+
+ const RotateCcwKey = [
+ ["path", { d: "m14.5 9.5 1 1" }],
+ ["path", { d: "m15.5 8.5-4 4" }],
+ ["path", { d: "M3 12a9 9 0 1 0 9-9 9.74 9.74 0 0 0-6.74 2.74L3 8" }],
+ ["path", { d: "M3 3v5h5" }],
+ ["circle", { cx: "10", cy: "14", r: "2" }]
+ ];
+
+ const RotateCcwSquare = [
+ ["path", { d: "M20 9V7a2 2 0 0 0-2-2h-6" }],
+ ["path", { d: "m15 2-3 3 3 3" }],
+ ["path", { d: "M20 13v5a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2" }]
+ ];
+
+ const RotateCcw = [
+ ["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
+ ["path", { d: "M3 3v5h5" }]
+ ];
+
+ const RotateCwSquare = [
+ ["path", { d: "M12 5H6a2 2 0 0 0-2 2v3" }],
+ ["path", { d: "m9 8 3-3-3-3" }],
+ ["path", { d: "M4 14v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" }]
+ ];
+
+ const RotateCw = [
+ ["path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }],
+ ["path", { d: "M21 3v5h-5" }]
+ ];
+
+ const Route = [
+ ["circle", { cx: "6", cy: "19", r: "3" }],
+ ["path", { d: "M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15" }],
+ ["circle", { cx: "18", cy: "5", r: "3" }]
+ ];
+
+ const RouteOff = [
+ ["circle", { cx: "6", cy: "19", r: "3" }],
+ ["path", { d: "M9 19h8.5c.4 0 .9-.1 1.3-.2" }],
+ ["path", { d: "M5.2 5.2A3.5 3.53 0 0 0 6.5 12H12" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M21 15.3a3.5 3.5 0 0 0-3.3-3.3" }],
+ ["path", { d: "M15 5h-4.3" }],
+ ["circle", { cx: "18", cy: "5", r: "3" }]
+ ];
+
+ const Rows2 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 12h18" }]
+ ];
+
+ const Router = [
+ ["rect", { width: "20", height: "8", x: "2", y: "14", rx: "2" }],
+ ["path", { d: "M6.01 18H6" }],
+ ["path", { d: "M10.01 18H10" }],
+ ["path", { d: "M15 10v4" }],
+ ["path", { d: "M17.84 7.17a4 4 0 0 0-5.66 0" }],
+ ["path", { d: "M20.66 4.34a8 8 0 0 0-11.31 0" }]
+ ];
+
+ const Rows3 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M21 9H3" }],
+ ["path", { d: "M21 15H3" }]
+ ];
+
+ const Rows4 = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M21 7.5H3" }],
+ ["path", { d: "M21 12H3" }],
+ ["path", { d: "M21 16.5H3" }]
+ ];
+
+ const RulerDimensionLine = [
+ ["path", { d: "M12 15v-3.014" }],
+ ["path", { d: "M16 15v-3.014" }],
+ ["path", { d: "M20 6H4" }],
+ ["path", { d: "M20 8V4" }],
+ ["path", { d: "M4 8V4" }],
+ ["path", { d: "M8 15v-3.014" }],
+ ["rect", { x: "3", y: "12", width: "18", height: "7", rx: "1" }]
+ ];
+
+ const Rss = [
+ ["path", { d: "M4 11a9 9 0 0 1 9 9" }],
+ ["path", { d: "M4 4a16 16 0 0 1 16 16" }],
+ ["circle", { cx: "5", cy: "19", r: "1" }]
+ ];
+
+ const Ruler = [
+ [
+ "path",
+ {
+ d: "M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"
+ }
+ ],
+ ["path", { d: "m14.5 12.5 2-2" }],
+ ["path", { d: "m11.5 9.5 2-2" }],
+ ["path", { d: "m8.5 6.5 2-2" }],
+ ["path", { d: "m17.5 15.5 2-2" }]
+ ];
+
+ const RussianRuble = [
+ ["path", { d: "M6 11h8a4 4 0 0 0 0-8H9v18" }],
+ ["path", { d: "M6 15h8" }]
+ ];
+
+ const Sailboat = [
+ ["path", { d: "M10 2v15" }],
+ ["path", { d: "M7 22a4 4 0 0 1-4-4 1 1 0 0 1 1-1h16a1 1 0 0 1 1 1 4 4 0 0 1-4 4z" }],
+ [
+ "path",
+ { d: "M9.159 2.46a1 1 0 0 1 1.521-.193l9.977 8.98A1 1 0 0 1 20 13H4a1 1 0 0 1-.824-1.567z" }
+ ]
+ ];
+
+ const Salad = [
+ ["path", { d: "M7 21h10" }],
+ ["path", { d: "M12 21a9 9 0 0 0 9-9H3a9 9 0 0 0 9 9Z" }],
+ [
+ "path",
+ {
+ d: "M11.38 12a2.4 2.4 0 0 1-.4-4.77 2.4 2.4 0 0 1 3.2-2.77 2.4 2.4 0 0 1 3.47-.63 2.4 2.4 0 0 1 3.37 3.37 2.4 2.4 0 0 1-1.1 3.7 2.51 2.51 0 0 1 .03 1.1"
+ }
+ ],
+ ["path", { d: "m13 12 4-4" }],
+ ["path", { d: "M10.9 7.25A3.99 3.99 0 0 0 4 10c0 .73.2 1.41.54 2" }]
+ ];
+
+ const Sandwich = [
+ ["path", { d: "m2.37 11.223 8.372-6.777a2 2 0 0 1 2.516 0l8.371 6.777" }],
+ ["path", { d: "M21 15a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-5.25" }],
+ ["path", { d: "M3 15a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h9" }],
+ ["path", { d: "m6.67 15 6.13 4.6a2 2 0 0 0 2.8-.4l3.15-4.2" }],
+ ["rect", { width: "20", height: "4", x: "2", y: "11", rx: "1" }]
+ ];
+
+ const SatelliteDish = [
+ ["path", { d: "M4 10a7.31 7.31 0 0 0 10 10Z" }],
+ ["path", { d: "m9 15 3-3" }],
+ ["path", { d: "M17 13a6 6 0 0 0-6-6" }],
+ ["path", { d: "M21 13A10 10 0 0 0 11 3" }]
+ ];
+
+ const Satellite = [
+ [
+ "path",
+ {
+ d: "m13.5 6.5-3.148-3.148a1.205 1.205 0 0 0-1.704 0L6.352 5.648a1.205 1.205 0 0 0 0 1.704L9.5 10.5"
+ }
+ ],
+ ["path", { d: "M16.5 7.5 19 5" }],
+ [
+ "path",
+ {
+ d: "m17.5 10.5 3.148 3.148a1.205 1.205 0 0 1 0 1.704l-2.296 2.296a1.205 1.205 0 0 1-1.704 0L13.5 14.5"
+ }
+ ],
+ ["path", { d: "M9 21a6 6 0 0 0-6-6" }],
+ [
+ "path",
+ {
+ d: "M9.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l4.296-4.296a1.205 1.205 0 0 0 0-1.704l-2.296-2.296a1.205 1.205 0 0 0-1.704 0z"
+ }
+ ]
+ ];
+
+ const SaudiRiyal = [
+ ["path", { d: "m20 19.5-5.5 1.2" }],
+ ["path", { d: "M14.5 4v11.22a1 1 0 0 0 1.242.97L20 15.2" }],
+ ["path", { d: "m2.978 19.351 5.549-1.363A2 2 0 0 0 10 16V2" }],
+ ["path", { d: "M20 10 4 13.5" }]
+ ];
+
+ const SaveAll = [
+ ["path", { d: "M10 2v3a1 1 0 0 0 1 1h5" }],
+ ["path", { d: "M18 18v-6a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6" }],
+ ["path", { d: "M18 22H4a2 2 0 0 1-2-2V6" }],
+ [
+ "path",
+ {
+ d: "M8 18a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9.172a2 2 0 0 1 1.414.586l2.828 2.828A2 2 0 0 1 22 6.828V16a2 2 0 0 1-2.01 2z"
+ }
+ ]
+ ];
+
+ const SaveOff = [
+ ["path", { d: "M13 13H8a1 1 0 0 0-1 1v7" }],
+ ["path", { d: "M14 8h1" }],
+ ["path", { d: "M17 21v-4" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20.41 20.41A2 2 0 0 1 19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 .59-1.41" }],
+ ["path", { d: "M29.5 11.5s5 5 4 5" }],
+ ["path", { d: "M9 3h6.2a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V15" }]
+ ];
+
+ const Save = [
+ [
+ "path",
+ {
+ d: "M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"
+ }
+ ],
+ ["path", { d: "M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7" }],
+ ["path", { d: "M7 3v4a1 1 0 0 0 1 1h7" }]
+ ];
+
+ const Scale3d = [
+ ["path", { d: "M5 7v11a1 1 0 0 0 1 1h11" }],
+ ["path", { d: "M5.293 18.707 11 13" }],
+ ["circle", { cx: "19", cy: "19", r: "2" }],
+ ["circle", { cx: "5", cy: "5", r: "2" }]
+ ];
+
+ const Scale = [
+ ["path", { d: "m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }],
+ ["path", { d: "m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z" }],
+ ["path", { d: "M7 21h10" }],
+ ["path", { d: "M12 3v18" }],
+ ["path", { d: "M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" }]
+ ];
+
+ const Scaling = [
+ ["path", { d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }],
+ ["path", { d: "M14 15H9v-5" }],
+ ["path", { d: "M16 3h5v5" }],
+ ["path", { d: "M21 3 9 15" }]
+ ];
+
+ const ScanBarcode = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M8 7v10" }],
+ ["path", { d: "M12 7v10" }],
+ ["path", { d: "M17 7v10" }]
+ ];
+
+ const ScanEye = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ [
+ "path",
+ {
+ d: "M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0"
+ }
+ ]
+ ];
+
+ const ScanFace = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }],
+ ["path", { d: "M9 9h.01" }],
+ ["path", { d: "M15 9h.01" }]
+ ];
+
+ const ScanHeart = [
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ [
+ "path",
+ { d: "M7.828 13.07A3 3 0 0 1 12 8.764a3 3 0 0 1 4.172 4.306l-3.447 3.62a1 1 0 0 1-1.449 0z" }
+ ]
+ ];
+
+ const ScanLine = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M7 12h10" }]
+ ];
+
+ const ScanQrCode = [
+ ["path", { d: "M17 12v4a1 1 0 0 1-1 1h-4" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M17 8V7" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M7 17h.01" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["rect", { x: "7", y: "7", width: "5", height: "5", rx: "1" }]
+ ];
+
+ const ScanSearch = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["path", { d: "m16 16-1.9-1.9" }]
+ ];
+
+ const ScanText = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M7 8h8" }],
+ ["path", { d: "M7 12h10" }],
+ ["path", { d: "M7 16h6" }]
+ ];
+
+ const Scan = [
+ ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2" }],
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2" }]
+ ];
+
+ const School = [
+ ["path", { d: "M14 21v-3a2 2 0 0 0-4 0v3" }],
+ ["path", { d: "M18 5v16" }],
+ ["path", { d: "m4 6 7.106-3.79a2 2 0 0 1 1.788 0L20 6" }],
+ [
+ "path",
+ {
+ d: "m6 11-3.52 2.147a1 1 0 0 0-.48.854V19a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a1 1 0 0 0-.48-.853L18 11"
+ }
+ ],
+ ["path", { d: "M6 5v16" }],
+ ["circle", { cx: "12", cy: "9", r: "2" }]
+ ];
+
+ const ScissorsLineDashed = [
+ ["path", { d: "M5.42 9.42 8 12" }],
+ ["circle", { cx: "4", cy: "8", r: "2" }],
+ ["path", { d: "m14 6-8.58 8.58" }],
+ ["circle", { cx: "4", cy: "16", r: "2" }],
+ ["path", { d: "M10.8 14.8 14 18" }],
+ ["path", { d: "M16 12h-2" }],
+ ["path", { d: "M22 12h-2" }]
+ ];
+
+ const ScreenShareOff = [
+ ["path", { d: "M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3" }],
+ ["path", { d: "M8 21h8" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "m22 3-5 5" }],
+ ["path", { d: "m17 3 5 5" }]
+ ];
+
+ const Scissors = [
+ ["circle", { cx: "6", cy: "6", r: "3" }],
+ ["path", { d: "M8.12 8.12 12 12" }],
+ ["path", { d: "M20 4 8.12 15.88" }],
+ ["circle", { cx: "6", cy: "18", r: "3" }],
+ ["path", { d: "M14.8 14.8 20 20" }]
+ ];
+
+ const ScreenShare = [
+ ["path", { d: "M13 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-3" }],
+ ["path", { d: "M8 21h8" }],
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "m17 8 5-5" }],
+ ["path", { d: "M17 3h5v5" }]
+ ];
+
+ const ScrollText = [
+ ["path", { d: "M15 12h-5" }],
+ ["path", { d: "M15 8h-5" }],
+ ["path", { d: "M19 17V5a2 2 0 0 0-2-2H4" }],
+ [
+ "path",
+ {
+ d: "M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"
+ }
+ ]
+ ];
+
+ const Scroll = [
+ ["path", { d: "M19 17V5a2 2 0 0 0-2-2H4" }],
+ [
+ "path",
+ {
+ d: "M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"
+ }
+ ]
+ ];
+
+ const SearchCheck = [
+ ["path", { d: "m8 11 2 2 4-4" }],
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["path", { d: "m21 21-4.3-4.3" }]
+ ];
+
+ const SearchCode = [
+ ["path", { d: "m13 13.5 2-2.5-2-2.5" }],
+ ["path", { d: "m21 21-4.3-4.3" }],
+ ["path", { d: "M9 8.5 7 11l2 2.5" }],
+ ["circle", { cx: "11", cy: "11", r: "8" }]
+ ];
+
+ const SearchSlash = [
+ ["path", { d: "m13.5 8.5-5 5" }],
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["path", { d: "m21 21-4.3-4.3" }]
+ ];
+
+ const SearchX = [
+ ["path", { d: "m13.5 8.5-5 5" }],
+ ["path", { d: "m8.5 8.5 5 5" }],
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["path", { d: "m21 21-4.3-4.3" }]
+ ];
+
+ const Search = [
+ ["path", { d: "m21 21-4.34-4.34" }],
+ ["circle", { cx: "11", cy: "11", r: "8" }]
+ ];
+
+ const Section = [
+ ["path", { d: "M16 5a4 3 0 0 0-8 0c0 4 8 3 8 7a4 3 0 0 1-8 0" }],
+ ["path", { d: "M8 19a4 3 0 0 0 8 0c0-4-8-3-8-7a4 3 0 0 1 8 0" }]
+ ];
+
+ const SendHorizontal = [
+ [
+ "path",
+ {
+ d: "M3.714 3.048a.498.498 0 0 0-.683.627l2.843 7.627a2 2 0 0 1 0 1.396l-2.842 7.627a.498.498 0 0 0 .682.627l18-8.5a.5.5 0 0 0 0-.904z"
+ }
+ ],
+ ["path", { d: "M6 12h16" }]
+ ];
+
+ const SendToBack = [
+ ["rect", { x: "14", y: "14", width: "8", height: "8", rx: "2" }],
+ ["rect", { x: "2", y: "2", width: "8", height: "8", rx: "2" }],
+ ["path", { d: "M7 14v1a2 2 0 0 0 2 2h1" }],
+ ["path", { d: "M14 7h1a2 2 0 0 1 2 2v1" }]
+ ];
+
+ const Send = [
+ [
+ "path",
+ {
+ d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"
+ }
+ ],
+ ["path", { d: "m21.854 2.147-10.94 10.939" }]
+ ];
+
+ const SeparatorHorizontal = [
+ ["path", { d: "m16 16-4 4-4-4" }],
+ ["path", { d: "M3 12h18" }],
+ ["path", { d: "m8 8 4-4 4 4" }]
+ ];
+
+ const SeparatorVertical = [
+ ["path", { d: "M12 3v18" }],
+ ["path", { d: "m16 16 4-4-4-4" }],
+ ["path", { d: "m8 8-4 4 4 4" }]
+ ];
+
+ const ServerCog = [
+ ["path", { d: "m10.852 14.772-.383.923" }],
+ ["path", { d: "M13.148 14.772a3 3 0 1 0-2.296-5.544l-.383-.923" }],
+ ["path", { d: "m13.148 9.228.383-.923" }],
+ ["path", { d: "m13.53 15.696-.382-.924a3 3 0 1 1-2.296-5.544" }],
+ ["path", { d: "m14.772 10.852.923-.383" }],
+ ["path", { d: "m14.772 13.148.923.383" }],
+ ["path", { d: "M4.5 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-.5" }],
+ ["path", { d: "M4.5 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-.5" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "M6 6h.01" }],
+ ["path", { d: "m9.228 10.852-.923-.383" }],
+ ["path", { d: "m9.228 13.148-.923.383" }]
+ ];
+
+ const ServerCrash = [
+ ["path", { d: "M6 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2" }],
+ ["path", { d: "M6 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2" }],
+ ["path", { d: "M6 6h.01" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "m13 6-4 6h6l-4 6" }]
+ ];
+
+ const ServerOff = [
+ ["path", { d: "M7 2h13a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-5" }],
+ ["path", { d: "M10 10 2.5 2.5C2 2 2 2.5 2 5v3a2 2 0 0 0 2 2h6z" }],
+ ["path", { d: "M22 17v-1a2 2 0 0 0-2-2h-1" }],
+ ["path", { d: "M4 14a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16.5l1-.5.5.5-8-8H4z" }],
+ ["path", { d: "M6 18h.01" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Server = [
+ ["rect", { width: "20", height: "8", x: "2", y: "2", rx: "2", ry: "2" }],
+ ["rect", { width: "20", height: "8", x: "2", y: "14", rx: "2", ry: "2" }],
+ ["line", { x1: "6", x2: "6.01", y1: "6", y2: "6" }],
+ ["line", { x1: "6", x2: "6.01", y1: "18", y2: "18" }]
+ ];
+
+ const Settings2 = [
+ ["path", { d: "M14 17H5" }],
+ ["path", { d: "M19 7h-9" }],
+ ["circle", { cx: "17", cy: "17", r: "3" }],
+ ["circle", { cx: "7", cy: "7", r: "3" }]
+ ];
+
+ const Settings = [
+ [
+ "path",
+ {
+ d: "M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"
+ }
+ ],
+ ["circle", { cx: "12", cy: "12", r: "3" }]
+ ];
+
+ const Shapes = [
+ [
+ "path",
+ {
+ d: "M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z"
+ }
+ ],
+ ["rect", { x: "3", y: "14", width: "7", height: "7", rx: "1" }],
+ ["circle", { cx: "17.5", cy: "17.5", r: "3.5" }]
+ ];
+
+ const Share2 = [
+ ["circle", { cx: "18", cy: "5", r: "3" }],
+ ["circle", { cx: "6", cy: "12", r: "3" }],
+ ["circle", { cx: "18", cy: "19", r: "3" }],
+ ["line", { x1: "8.59", x2: "15.42", y1: "13.51", y2: "17.49" }],
+ ["line", { x1: "15.41", x2: "8.59", y1: "6.51", y2: "10.49" }]
+ ];
+
+ const Share = [
+ ["path", { d: "M12 2v13" }],
+ ["path", { d: "m16 6-4-4-4 4" }],
+ ["path", { d: "M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" }]
+ ];
+
+ const Sheet = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["line", { x1: "3", x2: "21", y1: "9", y2: "9" }],
+ ["line", { x1: "3", x2: "21", y1: "15", y2: "15" }],
+ ["line", { x1: "9", x2: "9", y1: "9", y2: "21" }],
+ ["line", { x1: "15", x2: "15", y1: "9", y2: "21" }]
+ ];
+
+ const Shell = [
+ [
+ "path",
+ {
+ d: "M14 11a2 2 0 1 1-4 0 4 4 0 0 1 8 0 6 6 0 0 1-12 0 8 8 0 0 1 16 0 10 10 0 1 1-20 0 11.93 11.93 0 0 1 2.42-7.22 2 2 0 1 1 3.16 2.44"
+ }
+ ]
+ ];
+
+ const ShieldAlert = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M12 8v4" }],
+ ["path", { d: "M12 16h.01" }]
+ ];
+
+ const ShieldBan = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "m4.243 5.21 14.39 12.472" }]
+ ];
+
+ const ShieldCheck = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "m9 12 2 2 4-4" }]
+ ];
+
+ const ShieldEllipsis = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M8 12h.01" }],
+ ["path", { d: "M12 12h.01" }],
+ ["path", { d: "M16 12h.01" }]
+ ];
+
+ const ShieldHalf = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M12 22V2" }]
+ ];
+
+ const ShieldMinus = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M9 12h6" }]
+ ];
+
+ const ShieldOff = [
+ ["path", { d: "m2 2 20 20" }],
+ [
+ "path",
+ {
+ d: "M5 5a1 1 0 0 0-1 1v7c0 5 3.5 7.5 7.67 8.94a1 1 0 0 0 .67.01c2.35-.82 4.48-1.97 5.9-3.71"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M9.309 3.652A12.252 12.252 0 0 0 11.24 2.28a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1v7a9.784 9.784 0 0 1-.08 1.264"
+ }
+ ]
+ ];
+
+ const ShieldPlus = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M9 12h6" }],
+ ["path", { d: "M12 9v6" }]
+ ];
+
+ const ShieldQuestionMark = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M9.1 9a3 3 0 0 1 5.82 1c0 2-3 3-3 3" }],
+ ["path", { d: "M12 17h.01" }]
+ ];
+
+ const ShieldX = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "m14.5 9.5-5 5" }],
+ ["path", { d: "m9.5 9.5 5 5" }]
+ ];
+
+ const ShieldUser = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ],
+ ["path", { d: "M6.376 18.91a6 6 0 0 1 11.249.003" }],
+ ["circle", { cx: "12", cy: "11", r: "4" }]
+ ];
+
+ const Shield = [
+ [
+ "path",
+ {
+ d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"
+ }
+ ]
+ ];
+
+ const ShipWheel = [
+ ["circle", { cx: "12", cy: "12", r: "8" }],
+ ["path", { d: "M12 2v7.5" }],
+ ["path", { d: "m19 5-5.23 5.23" }],
+ ["path", { d: "M22 12h-7.5" }],
+ ["path", { d: "m19 19-5.23-5.23" }],
+ ["path", { d: "M12 14.5V22" }],
+ ["path", { d: "M10.23 13.77 5 19" }],
+ ["path", { d: "M9.5 12H2" }],
+ ["path", { d: "M10.23 10.23 5 5" }],
+ ["circle", { cx: "12", cy: "12", r: "2.5" }]
+ ];
+
+ const Ship = [
+ ["path", { d: "M12 10.189V14" }],
+ ["path", { d: "M12 2v3" }],
+ ["path", { d: "M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6" }],
+ [
+ "path",
+ {
+ d: "M19.38 20A11.6 11.6 0 0 0 21 14l-8.188-3.639a2 2 0 0 0-1.624 0L3 14a11.6 11.6 0 0 0 2.81 7.76"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"
+ }
+ ]
+ ];
+
+ const Shirt = [
+ [
+ "path",
+ {
+ d: "M20.38 3.46 16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z"
+ }
+ ]
+ ];
+
+ const ShoppingBag = [
+ ["path", { d: "M16 10a4 4 0 0 1-8 0" }],
+ ["path", { d: "M3.103 6.034h17.794" }],
+ [
+ "path",
+ {
+ d: "M3.4 5.467a2 2 0 0 0-.4 1.2V20a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.667a2 2 0 0 0-.4-1.2l-2-2.667A2 2 0 0 0 17 2H7a2 2 0 0 0-1.6.8z"
+ }
+ ]
+ ];
+
+ const ShoppingBasket = [
+ ["path", { d: "m15 11-1 9" }],
+ ["path", { d: "m19 11-4-7" }],
+ ["path", { d: "M2 11h20" }],
+ ["path", { d: "m3.5 11 1.6 7.4a2 2 0 0 0 2 1.6h9.8a2 2 0 0 0 2-1.6l1.7-7.4" }],
+ ["path", { d: "M4.5 15.5h15" }],
+ ["path", { d: "m5 11 4-7" }],
+ ["path", { d: "m9 11 1 9" }]
+ ];
+
+ const ShoppingCart = [
+ ["circle", { cx: "8", cy: "21", r: "1" }],
+ ["circle", { cx: "19", cy: "21", r: "1" }],
+ [
+ "path",
+ { d: "M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12" }
+ ]
+ ];
+
+ const Shovel = [
+ [
+ "path",
+ {
+ d: "M21.56 4.56a1.5 1.5 0 0 1 0 2.122l-.47.47a3 3 0 0 1-4.212-.03 3 3 0 0 1 0-4.243l.44-.44a1.5 1.5 0 0 1 2.121 0z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M3 22a1 1 0 0 1-1-1v-3.586a1 1 0 0 1 .293-.707l3.355-3.355a1.205 1.205 0 0 1 1.704 0l3.296 3.296a1.205 1.205 0 0 1 0 1.704l-3.355 3.355a1 1 0 0 1-.707.293z"
+ }
+ ],
+ ["path", { d: "m9 15 7.879-7.878" }]
+ ];
+
+ const ShowerHead = [
+ ["path", { d: "m4 4 2.5 2.5" }],
+ ["path", { d: "M13.5 6.5a4.95 4.95 0 0 0-7 7" }],
+ ["path", { d: "M15 5 5 15" }],
+ ["path", { d: "M14 17v.01" }],
+ ["path", { d: "M10 16v.01" }],
+ ["path", { d: "M13 13v.01" }],
+ ["path", { d: "M16 10v.01" }],
+ ["path", { d: "M11 20v.01" }],
+ ["path", { d: "M17 14v.01" }],
+ ["path", { d: "M20 11v.01" }]
+ ];
+
+ const Shredder = [
+ ["path", { d: "M10 22v-5" }],
+ ["path", { d: "M14 19v-2" }],
+ ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M18 20v-3" }],
+ ["path", { d: "M2 13h20" }],
+ ["path", { d: "M20 13V7l-5-5H6a2 2 0 0 0-2 2v9" }],
+ ["path", { d: "M6 20v-3" }]
+ ];
+
+ const Shrimp = [
+ ["path", { d: "M11 12h.01" }],
+ ["path", { d: "M13 22c.5-.5 1.12-1 2.5-1-1.38 0-2-.5-2.5-1" }],
+ [
+ "path",
+ {
+ d: "M14 2a3.28 3.28 0 0 1-3.227 1.798l-6.17-.561A2.387 2.387 0 1 0 4.387 8H15.5a1 1 0 0 1 0 13 1 1 0 0 0 0-5H12a7 7 0 0 1-7-7V8"
+ }
+ ],
+ ["path", { d: "M14 8a8.5 8.5 0 0 1 0 8" }],
+ ["path", { d: "M16 16c2 0 4.5-4 4-6" }]
+ ];
+
+ const Shrink = [
+ ["path", { d: "m15 15 6 6m-6-6v4.8m0-4.8h4.8" }],
+ ["path", { d: "M9 19.8V15m0 0H4.2M9 15l-6 6" }],
+ ["path", { d: "M15 4.2V9m0 0h4.8M15 9l6-6" }],
+ ["path", { d: "M9 4.2V9m0 0H4.2M9 9 3 3" }]
+ ];
+
+ const Shrub = [
+ ["path", { d: "M12 22v-5.172a2 2 0 0 0-.586-1.414L9.5 13.5" }],
+ ["path", { d: "M14.5 14.5 12 17" }],
+ ["path", { d: "M17 8.8A6 6 0 0 1 13.8 20H10A6.5 6.5 0 0 1 7 8a5 5 0 0 1 10 0z" }]
+ ];
+
+ const Shuffle = [
+ ["path", { d: "m18 14 4 4-4 4" }],
+ ["path", { d: "m18 2 4 4-4 4" }],
+ ["path", { d: "M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22" }],
+ ["path", { d: "M2 6h1.972a4 4 0 0 1 3.6 2.2" }],
+ ["path", { d: "M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45" }]
+ ];
+
+ const Sigma = [
+ [
+ "path",
+ {
+ d: "M18 7V5a1 1 0 0 0-1-1H6.5a.5.5 0 0 0-.4.8l4.5 6a2 2 0 0 1 0 2.4l-4.5 6a.5.5 0 0 0 .4.8H17a1 1 0 0 0 1-1v-2"
+ }
+ ]
+ ];
+
+ const SignalHigh = [
+ ["path", { d: "M2 20h.01" }],
+ ["path", { d: "M7 20v-4" }],
+ ["path", { d: "M12 20v-8" }],
+ ["path", { d: "M17 20V8" }]
+ ];
+
+ const SignalLow = [
+ ["path", { d: "M2 20h.01" }],
+ ["path", { d: "M7 20v-4" }]
+ ];
+
+ const SignalMedium = [
+ ["path", { d: "M2 20h.01" }],
+ ["path", { d: "M7 20v-4" }],
+ ["path", { d: "M12 20v-8" }]
+ ];
+
+ const Signal = [
+ ["path", { d: "M2 20h.01" }],
+ ["path", { d: "M7 20v-4" }],
+ ["path", { d: "M12 20v-8" }],
+ ["path", { d: "M17 20V8" }],
+ ["path", { d: "M22 4v16" }]
+ ];
+
+ const SignalZero = [["path", { d: "M2 20h.01" }]];
+
+ const Signature = [
+ [
+ "path",
+ {
+ d: "m21 17-2.156-1.868A.5.5 0 0 0 18 15.5v.5a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1c0-2.545-3.991-3.97-8.5-4a1 1 0 0 0 0 5c4.153 0 4.745-11.295 5.708-13.5a2.5 2.5 0 1 1 3.31 3.284"
+ }
+ ],
+ ["path", { d: "M3 21h18" }]
+ ];
+
+ const SignpostBig = [
+ ["path", { d: "M10 9H4L2 7l2-2h6" }],
+ ["path", { d: "M14 5h6l2 2-2 2h-6" }],
+ ["path", { d: "M10 22V4a2 2 0 1 1 4 0v18" }],
+ ["path", { d: "M8 22h8" }]
+ ];
+
+ const Signpost = [
+ ["path", { d: "M12 13v8" }],
+ ["path", { d: "M12 3v3" }],
+ [
+ "path",
+ {
+ d: "M18 6a2 2 0 0 1 1.387.56l2.307 2.22a1 1 0 0 1 0 1.44l-2.307 2.22A2 2 0 0 1 18 13H6a2 2 0 0 1-1.387-.56l-2.306-2.22a1 1 0 0 1 0-1.44l2.306-2.22A2 2 0 0 1 6 6z"
+ }
+ ]
+ ];
+
+ const Siren = [
+ ["path", { d: "M7 18v-6a5 5 0 1 1 10 0v6" }],
+ ["path", { d: "M5 21a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2z" }],
+ ["path", { d: "M21 12h1" }],
+ ["path", { d: "M18.5 4.5 18 5" }],
+ ["path", { d: "M2 12h1" }],
+ ["path", { d: "M12 2v1" }],
+ ["path", { d: "m4.929 4.929.707.707" }],
+ ["path", { d: "M12 12v6" }]
+ ];
+
+ const SkipBack = [
+ [
+ "path",
+ {
+ d: "M17.971 4.285A2 2 0 0 1 21 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z"
+ }
+ ],
+ ["path", { d: "M3 20V4" }]
+ ];
+
+ const Skull = [
+ ["path", { d: "m12.5 17-.5-1-.5 1h1z" }],
+ [
+ "path",
+ {
+ d: "M15 22a1 1 0 0 0 1-1v-1a2 2 0 0 0 1.56-3.25 8 8 0 1 0-11.12 0A2 2 0 0 0 8 20v1a1 1 0 0 0 1 1z"
+ }
+ ],
+ ["circle", { cx: "15", cy: "12", r: "1" }],
+ ["circle", { cx: "9", cy: "12", r: "1" }]
+ ];
+
+ const SkipForward = [
+ ["path", { d: "M21 4v16" }],
+ [
+ "path",
+ { d: "M6.029 4.285A2 2 0 0 0 3 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z" }
+ ]
+ ];
+
+ const Slash = [["path", { d: "M22 2 2 22" }]];
+
+ const Slack = [
+ ["rect", { width: "3", height: "8", x: "13", y: "2", rx: "1.5" }],
+ ["path", { d: "M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5" }],
+ ["rect", { width: "3", height: "8", x: "8", y: "14", rx: "1.5" }],
+ ["path", { d: "M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5" }],
+ ["rect", { width: "8", height: "3", x: "14", y: "13", rx: "1.5" }],
+ ["path", { d: "M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5" }],
+ ["rect", { width: "8", height: "3", x: "2", y: "8", rx: "1.5" }],
+ ["path", { d: "M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5" }]
+ ];
+
+ const Slice = [
+ [
+ "path",
+ {
+ d: "M11 16.586V19a1 1 0 0 1-1 1H2L18.37 3.63a1 1 0 1 1 3 3l-9.663 9.663a1 1 0 0 1-1.414 0L8 14"
+ }
+ ]
+ ];
+
+ const SlidersHorizontal = [
+ ["path", { d: "M10 5H3" }],
+ ["path", { d: "M12 19H3" }],
+ ["path", { d: "M14 3v4" }],
+ ["path", { d: "M16 17v4" }],
+ ["path", { d: "M21 12h-9" }],
+ ["path", { d: "M21 19h-5" }],
+ ["path", { d: "M21 5h-7" }],
+ ["path", { d: "M8 10v4" }],
+ ["path", { d: "M8 12H3" }]
+ ];
+
+ const SlidersVertical = [
+ ["path", { d: "M10 8h4" }],
+ ["path", { d: "M12 21v-9" }],
+ ["path", { d: "M12 8V3" }],
+ ["path", { d: "M17 16h4" }],
+ ["path", { d: "M19 12V3" }],
+ ["path", { d: "M19 21v-5" }],
+ ["path", { d: "M3 14h4" }],
+ ["path", { d: "M5 10V3" }],
+ ["path", { d: "M5 21v-7" }]
+ ];
+
+ const SmartphoneCharging = [
+ ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2" }],
+ ["path", { d: "M12.667 8 10 12h4l-2.667 4" }]
+ ];
+
+ const SmartphoneNfc = [
+ ["rect", { width: "7", height: "12", x: "2", y: "6", rx: "1" }],
+ ["path", { d: "M13 8.32a7.43 7.43 0 0 1 0 7.36" }],
+ ["path", { d: "M16.46 6.21a11.76 11.76 0 0 1 0 11.58" }],
+ ["path", { d: "M19.91 4.1a15.91 15.91 0 0 1 .01 15.8" }]
+ ];
+
+ const Smartphone = [
+ ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2" }],
+ ["path", { d: "M12 18h.01" }]
+ ];
+
+ const SmilePlus = [
+ ["path", { d: "M22 11v1a10 10 0 1 1-9-10" }],
+ ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }],
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }],
+ ["path", { d: "M16 5h6" }],
+ ["path", { d: "M19 2v6" }]
+ ];
+
+ const Smile = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }],
+ ["line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }],
+ ["line", { x1: "15", x2: "15.01", y1: "9", y2: "9" }]
+ ];
+
+ const Snail = [
+ ["path", { d: "M2 13a6 6 0 1 0 12 0 4 4 0 1 0-8 0 2 2 0 0 0 4 0" }],
+ ["circle", { cx: "10", cy: "13", r: "8" }],
+ ["path", { d: "M2 21h12c4.4 0 8-3.6 8-8V7a2 2 0 1 0-4 0v6" }],
+ ["path", { d: "M18 3 19.1 5.2" }],
+ ["path", { d: "M22 3 20.9 5.2" }]
+ ];
+
+ const Snowflake = [
+ ["path", { d: "m10 20-1.25-2.5L6 18" }],
+ ["path", { d: "M10 4 8.75 6.5 6 6" }],
+ ["path", { d: "m14 20 1.25-2.5L18 18" }],
+ ["path", { d: "m14 4 1.25 2.5L18 6" }],
+ ["path", { d: "m17 21-3-6h-4" }],
+ ["path", { d: "m17 3-3 6 1.5 3" }],
+ ["path", { d: "M2 12h6.5L10 9" }],
+ ["path", { d: "m20 10-1.5 2 1.5 2" }],
+ ["path", { d: "M22 12h-6.5L14 15" }],
+ ["path", { d: "m4 10 1.5 2L4 14" }],
+ ["path", { d: "m7 21 3-6-1.5-3" }],
+ ["path", { d: "m7 3 3 6h4" }]
+ ];
+
+ const SoapDispenserDroplet = [
+ ["path", { d: "M10.5 2v4" }],
+ ["path", { d: "M14 2H7a2 2 0 0 0-2 2" }],
+ [
+ "path",
+ {
+ d: "M19.29 14.76A6.67 6.67 0 0 1 17 11a6.6 6.6 0 0 1-2.29 3.76c-1.15.92-1.71 2.04-1.71 3.19 0 2.22 1.8 4.05 4 4.05s4-1.83 4-4.05c0-1.16-.57-2.26-1.71-3.19"
+ }
+ ],
+ ["path", { d: "M9.607 21H6a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h7V7a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3" }]
+ ];
+
+ const Sofa = [
+ ["path", { d: "M20 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v3" }],
+ [
+ "path",
+ {
+ d: "M2 16a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z"
+ }
+ ],
+ ["path", { d: "M4 18v2" }],
+ ["path", { d: "M20 18v2" }],
+ ["path", { d: "M12 4v9" }]
+ ];
+
+ const Soup = [
+ ["path", { d: "M12 21a9 9 0 0 0 9-9H3a9 9 0 0 0 9 9Z" }],
+ ["path", { d: "M7 21h10" }],
+ ["path", { d: "M19.5 12 22 6" }],
+ ["path", { d: "M16.25 3c.27.1.8.53.75 1.36-.06.83-.93 1.2-1 2.02-.05.78.34 1.24.73 1.62" }],
+ ["path", { d: "M11.25 3c.27.1.8.53.74 1.36-.05.83-.93 1.2-.98 2.02-.06.78.33 1.24.72 1.62" }],
+ ["path", { d: "M6.25 3c.27.1.8.53.75 1.36-.06.83-.93 1.2-1 2.02-.05.78.34 1.24.74 1.62" }]
+ ];
+
+ const Space = [["path", { d: "M22 17v1c0 .5-.5 1-1 1H3c-.5 0-1-.5-1-1v-1" }]];
+
+ const Spade = [
+ ["path", { d: "M12 18v4" }],
+ [
+ "path",
+ {
+ d: "M2 14.499a5.5 5.5 0 0 0 9.591 3.675.6.6 0 0 1 .818.001A5.5 5.5 0 0 0 22 14.5c0-2.29-1.5-4-3-5.5l-5.492-5.312a2 2 0 0 0-3-.02L5 8.999c-1.5 1.5-3 3.2-3 5.5"
+ }
+ ]
+ ];
+
+ const Sparkle = [
+ [
+ "path",
+ {
+ d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"
+ }
+ ]
+ ];
+
+ const Sparkles = [
+ [
+ "path",
+ {
+ d: "M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"
+ }
+ ],
+ ["path", { d: "M20 2v4" }],
+ ["path", { d: "M22 4h-4" }],
+ ["circle", { cx: "4", cy: "20", r: "2" }]
+ ];
+
+ const Speaker = [
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2" }],
+ ["path", { d: "M12 6h.01" }],
+ ["circle", { cx: "12", cy: "14", r: "4" }],
+ ["path", { d: "M12 14h.01" }]
+ ];
+
+ const Speech = [
+ [
+ "path",
+ {
+ d: "M8.8 20v-4.1l1.9.2a2.3 2.3 0 0 0 2.164-2.1V8.3A5.37 5.37 0 0 0 2 8.25c0 2.8.656 3.054 1 4.55a5.77 5.77 0 0 1 .029 2.758L2 20"
+ }
+ ],
+ ["path", { d: "M19.8 17.8a7.5 7.5 0 0 0 .003-10.603" }],
+ ["path", { d: "M17 15a3.5 3.5 0 0 0-.025-4.975" }]
+ ];
+
+ const SpellCheck = [
+ ["path", { d: "m6 16 6-12 6 12" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "m16 20 2 2 4-4" }]
+ ];
+
+ const SpellCheck2 = [
+ ["path", { d: "m6 16 6-12 6 12" }],
+ ["path", { d: "M8 12h8" }],
+ [
+ "path",
+ {
+ d: "M4 21c1.1 0 1.1-1 2.3-1s1.1 1 2.3 1c1.1 0 1.1-1 2.3-1 1.1 0 1.1 1 2.3 1 1.1 0 1.1-1 2.3-1 1.1 0 1.1 1 2.3 1 1.1 0 1.1-1 2.3-1"
+ }
+ ]
+ ];
+
+ const SplinePointer = [
+ [
+ "path",
+ {
+ d: "M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"
+ }
+ ],
+ ["path", { d: "M5 17A12 12 0 0 1 17 5" }],
+ ["circle", { cx: "19", cy: "5", r: "2" }],
+ ["circle", { cx: "5", cy: "19", r: "2" }]
+ ];
+
+ const Spline = [
+ ["circle", { cx: "19", cy: "5", r: "2" }],
+ ["circle", { cx: "5", cy: "19", r: "2" }],
+ ["path", { d: "M5 17A12 12 0 0 1 17 5" }]
+ ];
+
+ const Split = [
+ ["path", { d: "M16 3h5v5" }],
+ ["path", { d: "M8 3H3v5" }],
+ ["path", { d: "M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3" }],
+ ["path", { d: "m15 9 6-6" }]
+ ];
+
+ const Spool = [
+ [
+ "path",
+ {
+ d: "M17 13.44 4.442 17.082A2 2 0 0 0 4.982 21H19a2 2 0 0 0 .558-3.921l-1.115-.32A2 2 0 0 1 17 14.837V7.66"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m7 10.56 12.558-3.642A2 2 0 0 0 19.018 3H5a2 2 0 0 0-.558 3.921l1.115.32A2 2 0 0 1 7 9.163v7.178"
+ }
+ ]
+ ];
+
+ const Spotlight = [
+ ["path", { d: "M15.295 19.562 16 22" }],
+ ["path", { d: "m17 16 3.758 2.098" }],
+ ["path", { d: "m19 12.5 3.026-.598" }],
+ [
+ "path",
+ {
+ d: "M7.61 6.3a3 3 0 0 0-3.92 1.3l-1.38 2.79a3 3 0 0 0 1.3 3.91l6.89 3.597a1 1 0 0 0 1.342-.447l3.106-6.211a1 1 0 0 0-.447-1.341z"
+ }
+ ],
+ ["path", { d: "M8 9V2" }]
+ ];
+
+ const SprayCan = [
+ ["path", { d: "M3 3h.01" }],
+ ["path", { d: "M7 5h.01" }],
+ ["path", { d: "M11 7h.01" }],
+ ["path", { d: "M3 7h.01" }],
+ ["path", { d: "M7 9h.01" }],
+ ["path", { d: "M3 11h.01" }],
+ ["rect", { width: "4", height: "4", x: "15", y: "5" }],
+ ["path", { d: "m19 9 2 2v10c0 .6-.4 1-1 1h-6c-.6 0-1-.4-1-1V11l2-2" }],
+ ["path", { d: "m13 14 8-2" }],
+ ["path", { d: "m13 19 8-2" }]
+ ];
+
+ const SquareActivity = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M17 12h-2l-2 5-2-10-2 5H7" }]
+ ];
+
+ const Sprout = [
+ [
+ "path",
+ {
+ d: "M14 9.536V7a4 4 0 0 1 4-4h1.5a.5.5 0 0 1 .5.5V5a4 4 0 0 1-4 4 4 4 0 0 0-4 4c0 2 1 3 1 5a5 5 0 0 1-1 3"
+ }
+ ],
+ ["path", { d: "M4 9a5 5 0 0 1 8 4 5 5 0 0 1-8-4" }],
+ ["path", { d: "M5 21h14" }]
+ ];
+
+ const SquareArrowDownLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m16 8-8 8" }],
+ ["path", { d: "M16 16H8V8" }]
+ ];
+
+ const SquareArrowDownRight = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m8 8 8 8" }],
+ ["path", { d: "M16 8v8H8" }]
+ ];
+
+ const SquareArrowDown = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M12 8v8" }],
+ ["path", { d: "m8 12 4 4 4-4" }]
+ ];
+
+ const SquareArrowLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m12 8-4 4 4 4" }],
+ ["path", { d: "M16 12H8" }]
+ ];
+
+ const SquareArrowOutDownLeft = [
+ ["path", { d: "M13 21h6a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6" }],
+ ["path", { d: "m3 21 9-9" }],
+ ["path", { d: "M9 21H3v-6" }]
+ ];
+
+ const SquareArrowOutDownRight = [
+ ["path", { d: "M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6" }],
+ ["path", { d: "m21 21-9-9" }],
+ ["path", { d: "M21 15v6h-6" }]
+ ];
+
+ const SquareArrowOutUpLeft = [
+ ["path", { d: "M13 3h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-6" }],
+ ["path", { d: "m3 3 9 9" }],
+ ["path", { d: "M3 9V3h6" }]
+ ];
+
+ const SquareArrowOutUpRight = [
+ ["path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }],
+ ["path", { d: "m21 3-9 9" }],
+ ["path", { d: "M15 3h6v6" }]
+ ];
+
+ const SquareArrowRight = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "m12 16 4-4-4-4" }]
+ ];
+
+ const SquareArrowUpLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 16V8h8" }],
+ ["path", { d: "M16 16 8 8" }]
+ ];
+
+ const SquareArrowUpRight = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 8h8v8" }],
+ ["path", { d: "m8 16 8-8" }]
+ ];
+
+ const SquareArrowUp = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m16 12-4-4-4 4" }],
+ ["path", { d: "M12 16V8" }]
+ ];
+
+ const SquareAsterisk = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M12 8v8" }],
+ ["path", { d: "m8.5 14 7-4" }],
+ ["path", { d: "m8.5 10 7 4" }]
+ ];
+
+ const SquareBottomDashedScissors = [
+ ["path", { d: "M4 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2" }],
+ ["path", { d: "M10 22H8" }],
+ ["path", { d: "M16 22h-2" }],
+ ["circle", { cx: "8", cy: "8", r: "2" }],
+ ["path", { d: "M9.414 9.414 12 12" }],
+ ["path", { d: "M14.8 14.8 18 18" }],
+ ["circle", { cx: "8", cy: "16", r: "2" }],
+ ["path", { d: "m18 6-8.586 8.586" }]
+ ];
+
+ const SquareChartGantt = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 8h7" }],
+ ["path", { d: "M8 12h6" }],
+ ["path", { d: "M11 16h5" }]
+ ];
+
+ const SquareCheckBig = [
+ ["path", { d: "M21 10.656V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h12.344" }],
+ ["path", { d: "m9 11 3 3L22 4" }]
+ ];
+
+ const SquareCheck = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m9 12 2 2 4-4" }]
+ ];
+
+ const SquareChevronDown = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m16 10-4 4-4-4" }]
+ ];
+
+ const SquareChevronLeft = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m14 16-4-4 4-4" }]
+ ];
+
+ const SquareChevronRight = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m10 8 4 4-4 4" }]
+ ];
+
+ const SquareChevronUp = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m8 14 4-4 4 4" }]
+ ];
+
+ const SquareCode = [
+ ["path", { d: "m10 9-3 3 3 3" }],
+ ["path", { d: "m14 15 3-3-3-3" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const SquareDashedBottomCode = [
+ ["path", { d: "M10 9.5 8 12l2 2.5" }],
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "m14 9.5 2 2.5-2 2.5" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2" }],
+ ["path", { d: "M9 21h1" }]
+ ];
+
+ const SquareDashedBottom = [
+ ["path", { d: "M5 21a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2" }],
+ ["path", { d: "M9 21h1" }],
+ ["path", { d: "M14 21h1" }]
+ ];
+
+ const SquareDashedKanban = [
+ ["path", { d: "M8 7v7" }],
+ ["path", { d: "M12 7v4" }],
+ ["path", { d: "M16 7v9" }],
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
+ ["path", { d: "M9 3h1" }],
+ ["path", { d: "M14 3h1" }],
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
+ ["path", { d: "M21 9v1" }],
+ ["path", { d: "M21 14v1" }],
+ ["path", { d: "M21 19a2 2 0 0 1-2 2" }],
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "M9 21h1" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
+ ["path", { d: "M3 14v1" }],
+ ["path", { d: "M3 9v1" }]
+ ];
+
+ const SquareDashedMousePointer = [
+ [
+ "path",
+ {
+ d: "M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"
+ }
+ ],
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
+ ["path", { d: "M9 3h1" }],
+ ["path", { d: "M9 21h2" }],
+ ["path", { d: "M14 3h1" }],
+ ["path", { d: "M3 9v1" }],
+ ["path", { d: "M21 9v2" }],
+ ["path", { d: "M3 14v1" }]
+ ];
+
+ const SquareDashedTopSolid = [
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "M21 14v1" }],
+ ["path", { d: "M21 19a2 2 0 0 1-2 2" }],
+ ["path", { d: "M21 9v1" }],
+ ["path", { d: "M3 14v1" }],
+ ["path", { d: "M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2" }],
+ ["path", { d: "M3 9v1" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
+ ["path", { d: "M9 21h1" }]
+ ];
+
+ const SquareDashed = [
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
+ ["path", { d: "M21 19a2 2 0 0 1-2 2" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
+ ["path", { d: "M9 3h1" }],
+ ["path", { d: "M9 21h1" }],
+ ["path", { d: "M14 3h1" }],
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "M3 9v1" }],
+ ["path", { d: "M21 9v1" }],
+ ["path", { d: "M3 14v1" }],
+ ["path", { d: "M21 14v1" }]
+ ];
+
+ const SquareDivide = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12" }],
+ ["line", { x1: "12", x2: "12", y1: "16", y2: "16" }],
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "8" }]
+ ];
+
+ const SquareDot = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }]
+ ];
+
+ const SquareFunction = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3" }],
+ ["path", { d: "M9 11.2h5.7" }]
+ ];
+
+ const SquareEqual = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 10h10" }],
+ ["path", { d: "M7 14h10" }]
+ ];
+
+ const SquareKanban = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 7v7" }],
+ ["path", { d: "M12 7v4" }],
+ ["path", { d: "M16 7v9" }]
+ ];
+
+ const SquareLibrary = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 7v10" }],
+ ["path", { d: "M11 7v10" }],
+ ["path", { d: "m15 7 2 10" }]
+ ];
+
+ const SquareM = [
+ [
+ "path",
+ { d: "M8 16V8.5a.5.5 0 0 1 .9-.3l2.7 3.599a.5.5 0 0 0 .8 0l2.7-3.6a.5.5 0 0 1 .9.3V16" }
+ ],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const SquareMenu = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 8h10" }],
+ ["path", { d: "M7 12h10" }],
+ ["path", { d: "M7 16h10" }]
+ ];
+
+ const SquareMinus = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 12h8" }]
+ ];
+
+ const SquareMousePointer = [
+ [
+ "path",
+ {
+ d: "M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033z"
+ }
+ ],
+ ["path", { d: "M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6" }]
+ ];
+
+ const SquareParkingOff = [
+ ["path", { d: "M3.6 3.6A2 2 0 0 1 5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-.59 1.41" }],
+ ["path", { d: "M3 8.7V19a2 2 0 0 0 2 2h10.3" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M13 13a3 3 0 1 0 0-6H9v2" }],
+ ["path", { d: "M9 17v-2.3" }]
+ ];
+
+ const SquareParking = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M9 17V7h4a3 3 0 0 1 0 6H9" }]
+ ];
+
+ const SquarePause = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["line", { x1: "10", x2: "10", y1: "15", y2: "9" }],
+ ["line", { x1: "14", x2: "14", y1: "15", y2: "9" }]
+ ];
+
+ const SquarePen = [
+ ["path", { d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }],
+ [
+ "path",
+ {
+ d: "M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"
+ }
+ ]
+ ];
+
+ const SquarePercent = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "M9 9h.01" }],
+ ["path", { d: "M15 15h.01" }]
+ ];
+
+ const SquarePi = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 7h10" }],
+ ["path", { d: "M10 7v10" }],
+ ["path", { d: "M16 17a2 2 0 0 1-2-2V7" }]
+ ];
+
+ const SquarePilcrow = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M12 12H9.5a2.5 2.5 0 0 1 0-5H17" }],
+ ["path", { d: "M12 7v10" }],
+ ["path", { d: "M16 7v10" }]
+ ];
+
+ const SquarePlay = [
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }],
+ [
+ "path",
+ {
+ d: "M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z"
+ }
+ ]
+ ];
+
+ const SquarePlus = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M8 12h8" }],
+ ["path", { d: "M12 8v8" }]
+ ];
+
+ const SquarePower = [
+ ["path", { d: "M12 7v4" }],
+ ["path", { d: "M7.998 9.003a5 5 0 1 0 8-.005" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const SquareRadical = [
+ ["path", { d: "M7 12h2l2 5 2-10h4" }],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const SquareRoundCorner = [
+ ["path", { d: "M21 11a8 8 0 0 0-8-8" }],
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }]
+ ];
+
+ const SquareScissors = [
+ ["rect", { width: "20", height: "20", x: "2", y: "2", rx: "2" }],
+ ["circle", { cx: "8", cy: "8", r: "2" }],
+ ["path", { d: "M9.414 9.414 12 12" }],
+ ["path", { d: "M14.8 14.8 18 18" }],
+ ["circle", { cx: "8", cy: "16", r: "2" }],
+ ["path", { d: "m18 6-8.586 8.586" }]
+ ];
+
+ const SquareSigma = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M16 8.9V7H8l4 5-4 5h8v-1.9" }]
+ ];
+
+ const SquareSlash = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["line", { x1: "9", x2: "15", y1: "15", y2: "9" }]
+ ];
+
+ const SquareSplitHorizontal = [
+ ["path", { d: "M8 19H5c-1 0-2-1-2-2V7c0-1 1-2 2-2h3" }],
+ ["path", { d: "M16 5h3c1 0 2 1 2 2v10c0 1-1 2-2 2h-3" }],
+ ["line", { x1: "12", x2: "12", y1: "4", y2: "20" }]
+ ];
+
+ const SquareSplitVertical = [
+ ["path", { d: "M5 8V5c0-1 1-2 2-2h10c1 0 2 1 2 2v3" }],
+ ["path", { d: "M19 16v3c0 1-1 2-2 2H7c-1 0-2-1-2-2v-3" }],
+ ["line", { x1: "4", x2: "20", y1: "12", y2: "12" }]
+ ];
+
+ const SquareSquare = [
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }],
+ ["rect", { x: "8", y: "8", width: "8", height: "8", rx: "1" }]
+ ];
+
+ const SquareStack = [
+ ["path", { d: "M4 10c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2" }],
+ ["path", { d: "M10 16c-1.1 0-2-.9-2-2v-4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2" }],
+ ["rect", { width: "8", height: "8", x: "14", y: "14", rx: "2" }]
+ ];
+
+ const SquareStar = [
+ [
+ "path",
+ {
+ d: "M11.035 7.69a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.866l-1.156-1.153a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"
+ }
+ ],
+ ["rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }]
+ ];
+
+ const SquareStop = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["rect", { x: "9", y: "9", width: "6", height: "6", rx: "1" }]
+ ];
+
+ const SquareTerminal = [
+ ["path", { d: "m7 11 2-2-2-2" }],
+ ["path", { d: "M11 13h4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }]
+ ];
+
+ const SquareUserRound = [
+ ["path", { d: "M18 21a6 6 0 0 0-12 0" }],
+ ["circle", { cx: "12", cy: "11", r: "4" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]
+ ];
+
+ const SquareUser = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "M7 21v-2a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v2" }]
+ ];
+
+ const SquareX = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "m9 9 6 6" }]
+ ];
+
+ const Square = [["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]];
+
+ const SquaresExclude = [
+ [
+ "path",
+ {
+ d: "M16 12v2a2 2 0 0 1-2 2H9a1 1 0 0 0-1 1v3a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2h0"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v3a1 1 0 0 1-1 1h-5a2 2 0 0 0-2 2v2"
+ }
+ ]
+ ];
+
+ const SquaresIntersect = [
+ ["path", { d: "M10 22a2 2 0 0 1-2-2" }],
+ ["path", { d: "M14 2a2 2 0 0 1 2 2" }],
+ ["path", { d: "M16 22h-2" }],
+ ["path", { d: "M2 10V8" }],
+ ["path", { d: "M2 4a2 2 0 0 1 2-2" }],
+ ["path", { d: "M20 8a2 2 0 0 1 2 2" }],
+ ["path", { d: "M22 14v2" }],
+ ["path", { d: "M22 20a2 2 0 0 1-2 2" }],
+ ["path", { d: "M4 16a2 2 0 0 1-2-2" }],
+ ["path", { d: "M8 10a2 2 0 0 1 2-2h5a1 1 0 0 1 1 1v5a2 2 0 0 1-2 2H9a1 1 0 0 1-1-1z" }],
+ ["path", { d: "M8 2h2" }]
+ ];
+
+ const SquaresUnite = [
+ [
+ "path",
+ {
+ d: "M4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v3a1 1 0 0 0 1 1h3a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-3a1 1 0 0 0-1-1z"
+ }
+ ]
+ ];
+
+ const SquaresSubtract = [
+ ["path", { d: "M10 22a2 2 0 0 1-2-2" }],
+ ["path", { d: "M16 22h-2" }],
+ [
+ "path",
+ {
+ d: "M16 4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-5a2 2 0 0 1 2-2h5a1 1 0 0 0 1-1z"
+ }
+ ],
+ ["path", { d: "M20 8a2 2 0 0 1 2 2" }],
+ ["path", { d: "M22 14v2" }],
+ ["path", { d: "M22 20a2 2 0 0 1-2 2" }]
+ ];
+
+ const SquircleDashed = [
+ ["path", { d: "M13.77 3.043a34 34 0 0 0-3.54 0" }],
+ ["path", { d: "M13.771 20.956a33 33 0 0 1-3.541.001" }],
+ ["path", { d: "M20.18 17.74c-.51 1.15-1.29 1.93-2.439 2.44" }],
+ ["path", { d: "M20.18 6.259c-.51-1.148-1.291-1.929-2.44-2.438" }],
+ ["path", { d: "M20.957 10.23a33 33 0 0 1 0 3.54" }],
+ ["path", { d: "M3.043 10.23a34 34 0 0 0 .001 3.541" }],
+ ["path", { d: "M6.26 20.179c-1.15-.508-1.93-1.29-2.44-2.438" }],
+ ["path", { d: "M6.26 3.82c-1.149.51-1.93 1.291-2.44 2.44" }]
+ ];
+
+ const Squircle = [
+ ["path", { d: "M12 3c7.2 0 9 1.8 9 9s-1.8 9-9 9-9-1.8-9-9 1.8-9 9-9" }]
+ ];
+
+ const Squirrel = [
+ ["path", { d: "M15.236 22a3 3 0 0 0-2.2-5" }],
+ ["path", { d: "M16 20a3 3 0 0 1 3-3h1a2 2 0 0 0 2-2v-2a4 4 0 0 0-4-4V4" }],
+ ["path", { d: "M18 13h.01" }],
+ [
+ "path",
+ {
+ d: "M18 6a4 4 0 0 0-4 4 7 7 0 0 0-7 7c0-5 4-5 4-10.5a4.5 4.5 0 1 0-9 0 2.5 2.5 0 0 0 5 0C7 10 3 11 3 17c0 2.8 2.2 5 5 5h10"
+ }
+ ]
+ ];
+
+ const Stamp = [
+ ["path", { d: "M14 13V8.5C14 7 15 7 15 5a3 3 0 0 0-6 0c0 2 1 2 1 3.5V13" }],
+ [
+ "path",
+ {
+ d: "M20 15.5a2.5 2.5 0 0 0-2.5-2.5h-11A2.5 2.5 0 0 0 4 15.5V17a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1z"
+ }
+ ],
+ ["path", { d: "M5 22h14" }]
+ ];
+
+ const StarHalf = [
+ [
+ "path",
+ {
+ d: "M12 18.338a2.1 2.1 0 0 0-.987.244L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16l2.309-4.679A.53.53 0 0 1 12 2"
+ }
+ ]
+ ];
+
+ const StarOff = [
+ ["path", { d: "M8.34 8.34 2 9.27l5 4.87L5.82 21 12 17.77 18.18 21l-.59-3.43" }],
+ ["path", { d: "M18.42 12.76 22 9.27l-6.91-1L12 2l-1.44 2.91" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Star = [
+ [
+ "path",
+ {
+ d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"
+ }
+ ]
+ ];
+
+ const StepBack = [
+ [
+ "path",
+ {
+ d: "M13.971 4.285A2 2 0 0 1 17 6v12a2 2 0 0 1-3.029 1.715l-9.997-5.998a2 2 0 0 1-.003-3.432z"
+ }
+ ],
+ ["path", { d: "M21 20V4" }]
+ ];
+
+ const StepForward = [
+ [
+ "path",
+ { d: "M10.029 4.285A2 2 0 0 0 7 6v12a2 2 0 0 0 3.029 1.715l9.997-5.998a2 2 0 0 0 .003-3.432z" }
+ ],
+ ["path", { d: "M3 4v16" }]
+ ];
+
+ const Stethoscope = [
+ ["path", { d: "M11 2v2" }],
+ ["path", { d: "M5 2v2" }],
+ ["path", { d: "M5 3H4a2 2 0 0 0-2 2v4a6 6 0 0 0 12 0V5a2 2 0 0 0-2-2h-1" }],
+ ["path", { d: "M8 15a6 6 0 0 0 12 0v-3" }],
+ ["circle", { cx: "20", cy: "10", r: "2" }]
+ ];
+
+ const Sticker = [
+ ["path", { d: "M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z" }],
+ ["path", { d: "M14 3v4a2 2 0 0 0 2 2h4" }],
+ ["path", { d: "M8 13h.01" }],
+ ["path", { d: "M16 13h.01" }],
+ ["path", { d: "M10 16s.8 1 2 1c1.3 0 2-1 2-1" }]
+ ];
+
+ const StickyNote = [
+ ["path", { d: "M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z" }],
+ ["path", { d: "M15 3v4a2 2 0 0 0 2 2h4" }]
+ ];
+
+ const Store = [
+ ["path", { d: "M15 21v-5a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v5" }],
+ [
+ "path",
+ {
+ d: "M17.774 10.31a1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.451 0 1.12 1.12 0 0 0-1.548 0 2.5 2.5 0 0 1-3.452 0 1.12 1.12 0 0 0-1.549 0 2.5 2.5 0 0 1-3.77-3.248l2.889-4.184A2 2 0 0 1 7 2h10a2 2 0 0 1 1.653.873l2.895 4.192a2.5 2.5 0 0 1-3.774 3.244"
+ }
+ ],
+ ["path", { d: "M4 10.95V19a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8.05" }]
+ ];
+
+ const StretchHorizontal = [
+ ["rect", { width: "20", height: "6", x: "2", y: "4", rx: "2" }],
+ ["rect", { width: "20", height: "6", x: "2", y: "14", rx: "2" }]
+ ];
+
+ const StretchVertical = [
+ ["rect", { width: "6", height: "20", x: "4", y: "2", rx: "2" }],
+ ["rect", { width: "6", height: "20", x: "14", y: "2", rx: "2" }]
+ ];
+
+ const Strikethrough = [
+ ["path", { d: "M16 4H9a3 3 0 0 0-2.83 4" }],
+ ["path", { d: "M14 12a4 4 0 0 1 0 8H6" }],
+ ["line", { x1: "4", x2: "20", y1: "12", y2: "12" }]
+ ];
+
+ const Subscript = [
+ ["path", { d: "m4 5 8 8" }],
+ ["path", { d: "m12 5-8 8" }],
+ [
+ "path",
+ {
+ d: "M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"
+ }
+ ]
+ ];
+
+ const SunDim = [
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["path", { d: "M12 4h.01" }],
+ ["path", { d: "M20 12h.01" }],
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M4 12h.01" }],
+ ["path", { d: "M17.657 6.343h.01" }],
+ ["path", { d: "M17.657 17.657h.01" }],
+ ["path", { d: "M6.343 17.657h.01" }],
+ ["path", { d: "M6.343 6.343h.01" }]
+ ];
+
+ const SunMoon = [
+ ["path", { d: "M12 2v2" }],
+ [
+ "path",
+ {
+ d: "M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"
+ }
+ ],
+ ["path", { d: "M16 12a4 4 0 0 0-4-4" }],
+ ["path", { d: "m19 5-1.256 1.256" }],
+ ["path", { d: "M20 12h2" }]
+ ];
+
+ const SunMedium = [
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["path", { d: "M12 3v1" }],
+ ["path", { d: "M12 20v1" }],
+ ["path", { d: "M3 12h1" }],
+ ["path", { d: "M20 12h1" }],
+ ["path", { d: "m18.364 5.636-.707.707" }],
+ ["path", { d: "m6.343 17.657-.707.707" }],
+ ["path", { d: "m5.636 5.636.707.707" }],
+ ["path", { d: "m17.657 17.657.707.707" }]
+ ];
+
+ const SunSnow = [
+ ["path", { d: "M10 21v-1" }],
+ ["path", { d: "M10 4V3" }],
+ ["path", { d: "M10 9a3 3 0 0 0 0 6" }],
+ ["path", { d: "m14 20 1.25-2.5L18 18" }],
+ ["path", { d: "m14 4 1.25 2.5L18 6" }],
+ ["path", { d: "m17 21-3-6 1.5-3H22" }],
+ ["path", { d: "m17 3-3 6 1.5 3" }],
+ ["path", { d: "M2 12h1" }],
+ ["path", { d: "m20 10-1.5 2 1.5 2" }],
+ ["path", { d: "m3.64 18.36.7-.7" }],
+ ["path", { d: "m4.34 6.34-.7-.7" }]
+ ];
+
+ const Sun = [
+ ["circle", { cx: "12", cy: "12", r: "4" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "m4.93 4.93 1.41 1.41" }],
+ ["path", { d: "m17.66 17.66 1.41 1.41" }],
+ ["path", { d: "M2 12h2" }],
+ ["path", { d: "M20 12h2" }],
+ ["path", { d: "m6.34 17.66-1.41 1.41" }],
+ ["path", { d: "m19.07 4.93-1.41 1.41" }]
+ ];
+
+ const Sunrise = [
+ ["path", { d: "M12 2v8" }],
+ ["path", { d: "m4.93 10.93 1.41 1.41" }],
+ ["path", { d: "M2 18h2" }],
+ ["path", { d: "M20 18h2" }],
+ ["path", { d: "m19.07 10.93-1.41 1.41" }],
+ ["path", { d: "M22 22H2" }],
+ ["path", { d: "m8 6 4-4 4 4" }],
+ ["path", { d: "M16 18a4 4 0 0 0-8 0" }]
+ ];
+
+ const Sunset = [
+ ["path", { d: "M12 10V2" }],
+ ["path", { d: "m4.93 10.93 1.41 1.41" }],
+ ["path", { d: "M2 18h2" }],
+ ["path", { d: "M20 18h2" }],
+ ["path", { d: "m19.07 10.93-1.41 1.41" }],
+ ["path", { d: "M22 22H2" }],
+ ["path", { d: "m16 6-4 4-4-4" }],
+ ["path", { d: "M16 18a4 4 0 0 0-8 0" }]
+ ];
+
+ const Superscript = [
+ ["path", { d: "m4 19 8-8" }],
+ ["path", { d: "m12 19-8-8" }],
+ [
+ "path",
+ {
+ d: "M20 12h-4c0-1.5.442-2 1.5-2.5S20 8.334 20 7.002c0-.472-.17-.93-.484-1.29a2.105 2.105 0 0 0-2.617-.436c-.42.239-.738.614-.899 1.06"
+ }
+ ]
+ ];
+
+ const SwatchBook = [
+ ["path", { d: "M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z" }],
+ ["path", { d: "M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7" }],
+ ["path", { d: "M 7 17h.01" }],
+ [
+ "path",
+ { d: "m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8" }
+ ]
+ ];
+
+ const SwissFranc = [
+ ["path", { d: "M10 21V3h8" }],
+ ["path", { d: "M6 16h9" }],
+ ["path", { d: "M10 9.5h7" }]
+ ];
+
+ const SwitchCamera = [
+ ["path", { d: "M11 19H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5" }],
+ ["path", { d: "M13 5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5" }],
+ ["circle", { cx: "12", cy: "12", r: "3" }],
+ ["path", { d: "m18 22-3-3 3-3" }],
+ ["path", { d: "m6 2 3 3-3 3" }]
+ ];
+
+ const Sword = [
+ ["path", { d: "m11 19-6-6" }],
+ ["path", { d: "m5 21-2-2" }],
+ ["path", { d: "m8 16-4 4" }],
+ ["path", { d: "M9.5 17.5 21 6V3h-3L6.5 14.5" }]
+ ];
+
+ const Swords = [
+ ["polyline", { points: "14.5 17.5 3 6 3 3 6 3 17.5 14.5" }],
+ ["line", { x1: "13", x2: "19", y1: "19", y2: "13" }],
+ ["line", { x1: "16", x2: "20", y1: "16", y2: "20" }],
+ ["line", { x1: "19", x2: "21", y1: "21", y2: "19" }],
+ ["polyline", { points: "14.5 6.5 18 3 21 3 21 6 17.5 9.5" }],
+ ["line", { x1: "5", x2: "9", y1: "14", y2: "18" }],
+ ["line", { x1: "7", x2: "4", y1: "17", y2: "20" }],
+ ["line", { x1: "3", x2: "5", y1: "19", y2: "21" }]
+ ];
+
+ const Syringe = [
+ ["path", { d: "m18 2 4 4" }],
+ ["path", { d: "m17 7 3-3" }],
+ ["path", { d: "M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5" }],
+ ["path", { d: "m9 11 4 4" }],
+ ["path", { d: "m5 19-3 3" }],
+ ["path", { d: "m14 4 6 6" }]
+ ];
+
+ const Table2 = [
+ [
+ "path",
+ {
+ d: "M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"
+ }
+ ]
+ ];
+
+ const TableCellsMerge = [
+ ["path", { d: "M12 21v-6" }],
+ ["path", { d: "M12 9V3" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "M3 9h18" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]
+ ];
+
+ const TableCellsSplit = [
+ ["path", { d: "M12 15V9" }],
+ ["path", { d: "M3 15h18" }],
+ ["path", { d: "M3 9h18" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }]
+ ];
+
+ const TableColumnsSplit = [
+ ["path", { d: "M14 14v2" }],
+ ["path", { d: "M14 20v2" }],
+ ["path", { d: "M14 2v2" }],
+ ["path", { d: "M14 8v2" }],
+ ["path", { d: "M2 15h8" }],
+ ["path", { d: "M2 3h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2" }],
+ ["path", { d: "M2 9h8" }],
+ ["path", { d: "M22 15h-4" }],
+ ["path", { d: "M22 3h-2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2" }],
+ ["path", { d: "M22 9h-4" }],
+ ["path", { d: "M5 3v18" }]
+ ];
+
+ const TableOfContents = [
+ ["path", { d: "M16 5H3" }],
+ ["path", { d: "M16 12H3" }],
+ ["path", { d: "M16 19H3" }],
+ ["path", { d: "M21 5h.01" }],
+ ["path", { d: "M21 12h.01" }],
+ ["path", { d: "M21 19h.01" }]
+ ];
+
+ const TableProperties = [
+ ["path", { d: "M15 3v18" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M21 9H3" }],
+ ["path", { d: "M21 15H3" }]
+ ];
+
+ const TableRowsSplit = [
+ ["path", { d: "M14 10h2" }],
+ ["path", { d: "M15 22v-8" }],
+ ["path", { d: "M15 2v4" }],
+ ["path", { d: "M2 10h2" }],
+ ["path", { d: "M20 10h2" }],
+ ["path", { d: "M3 19h18" }],
+ ["path", { d: "M3 22v-6a2 2 135 0 1 2-2h14a2 2 45 0 1 2 2v6" }],
+ ["path", { d: "M3 2v2a2 2 45 0 0 2 2h14a2 2 135 0 0 2-2V2" }],
+ ["path", { d: "M8 10h2" }],
+ ["path", { d: "M9 22v-8" }],
+ ["path", { d: "M9 2v4" }]
+ ];
+
+ const Table = [
+ ["path", { d: "M12 3v18" }],
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9h18" }],
+ ["path", { d: "M3 15h18" }]
+ ];
+
+ const TabletSmartphone = [
+ ["rect", { width: "10", height: "14", x: "3", y: "8", rx: "2" }],
+ ["path", { d: "M5 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2h-2.4" }],
+ ["path", { d: "M8 18h.01" }]
+ ];
+
+ const Tablet = [
+ ["rect", { width: "16", height: "20", x: "4", y: "2", rx: "2", ry: "2" }],
+ ["line", { x1: "12", x2: "12.01", y1: "18", y2: "18" }]
+ ];
+
+ const Tablets = [
+ ["circle", { cx: "7", cy: "7", r: "5" }],
+ ["circle", { cx: "17", cy: "17", r: "5" }],
+ ["path", { d: "M12 17h10" }],
+ ["path", { d: "m3.46 10.54 7.08-7.08" }]
+ ];
+
+ const Tag = [
+ [
+ "path",
+ {
+ d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"
+ }
+ ],
+ ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }]
+ ];
+
+ const Tags = [
+ [
+ "path",
+ {
+ d: "M13.172 2a2 2 0 0 1 1.414.586l6.71 6.71a2.4 2.4 0 0 1 0 3.408l-4.592 4.592a2.4 2.4 0 0 1-3.408 0l-6.71-6.71A2 2 0 0 1 6 9.172V3a1 1 0 0 1 1-1z"
+ }
+ ],
+ ["path", { d: "M2 7v6.172a2 2 0 0 0 .586 1.414l6.71 6.71a2.4 2.4 0 0 0 3.191.193" }],
+ ["circle", { cx: "10.5", cy: "6.5", r: ".5", fill: "currentColor" }]
+ ];
+
+ const Tally1 = [["path", { d: "M4 4v16" }]];
+
+ const Tally2 = [
+ ["path", { d: "M4 4v16" }],
+ ["path", { d: "M9 4v16" }]
+ ];
+
+ const Tally3 = [
+ ["path", { d: "M4 4v16" }],
+ ["path", { d: "M9 4v16" }],
+ ["path", { d: "M14 4v16" }]
+ ];
+
+ const Tally4 = [
+ ["path", { d: "M4 4v16" }],
+ ["path", { d: "M9 4v16" }],
+ ["path", { d: "M14 4v16" }],
+ ["path", { d: "M19 4v16" }]
+ ];
+
+ const Tally5 = [
+ ["path", { d: "M4 4v16" }],
+ ["path", { d: "M9 4v16" }],
+ ["path", { d: "M14 4v16" }],
+ ["path", { d: "M19 4v16" }],
+ ["path", { d: "M22 6 2 18" }]
+ ];
+
+ const Tangent = [
+ ["circle", { cx: "17", cy: "4", r: "2" }],
+ ["path", { d: "M15.59 5.41 5.41 15.59" }],
+ ["circle", { cx: "4", cy: "17", r: "2" }],
+ ["path", { d: "M12 22s-4-9-1.5-11.5S22 12 22 12" }]
+ ];
+
+ const Target = [
+ ["circle", { cx: "12", cy: "12", r: "10" }],
+ ["circle", { cx: "12", cy: "12", r: "6" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const Telescope = [
+ [
+ "path",
+ {
+ d: "m10.065 12.493-6.18 1.318a.934.934 0 0 1-1.108-.702l-.537-2.15a1.07 1.07 0 0 1 .691-1.265l13.504-4.44"
+ }
+ ],
+ ["path", { d: "m13.56 11.747 4.332-.924" }],
+ ["path", { d: "m16 21-3.105-6.21" }],
+ [
+ "path",
+ {
+ d: "M16.485 5.94a2 2 0 0 1 1.455-2.425l1.09-.272a1 1 0 0 1 1.212.727l1.515 6.06a1 1 0 0 1-.727 1.213l-1.09.272a2 2 0 0 1-2.425-1.455z"
+ }
+ ],
+ ["path", { d: "m6.158 8.633 1.114 4.456" }],
+ ["path", { d: "m8 21 3.105-6.21" }],
+ ["circle", { cx: "12", cy: "13", r: "2" }]
+ ];
+
+ const TentTree = [
+ ["circle", { cx: "4", cy: "4", r: "2" }],
+ ["path", { d: "m14 5 3-3 3 3" }],
+ ["path", { d: "m14 10 3-3 3 3" }],
+ ["path", { d: "M17 14V2" }],
+ ["path", { d: "M17 14H7l-5 8h20Z" }],
+ ["path", { d: "M8 14v8" }],
+ ["path", { d: "m9 14 5 8" }]
+ ];
+
+ const Tent = [
+ ["path", { d: "M3.5 21 14 3" }],
+ ["path", { d: "M20.5 21 10 3" }],
+ ["path", { d: "M15.5 21 12 15l-3.5 6" }],
+ ["path", { d: "M2 21h20" }]
+ ];
+
+ const Terminal = [
+ ["path", { d: "M12 19h8" }],
+ ["path", { d: "m4 17 6-6-6-6" }]
+ ];
+
+ const TestTubeDiagonal = [
+ ["path", { d: "M21 7 6.82 21.18a2.83 2.83 0 0 1-3.99-.01a2.83 2.83 0 0 1 0-4L17 3" }],
+ ["path", { d: "m16 2 6 6" }],
+ ["path", { d: "M12 16H4" }]
+ ];
+
+ const TestTube = [
+ ["path", { d: "M14.5 2v17.5c0 1.4-1.1 2.5-2.5 2.5c-1.4 0-2.5-1.1-2.5-2.5V2" }],
+ ["path", { d: "M8.5 2h7" }],
+ ["path", { d: "M14.5 16h-5" }]
+ ];
+
+ const TestTubes = [
+ ["path", { d: "M9 2v17.5A2.5 2.5 0 0 1 6.5 22A2.5 2.5 0 0 1 4 19.5V2" }],
+ ["path", { d: "M20 2v17.5a2.5 2.5 0 0 1-2.5 2.5a2.5 2.5 0 0 1-2.5-2.5V2" }],
+ ["path", { d: "M3 2h7" }],
+ ["path", { d: "M14 2h7" }],
+ ["path", { d: "M9 16H4" }],
+ ["path", { d: "M20 16h-5" }]
+ ];
+
+ const TextAlignEnd = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M21 12H9" }],
+ ["path", { d: "M21 19H7" }]
+ ];
+
+ const TextAlignJustify = [
+ ["path", { d: "M3 5h18" }],
+ ["path", { d: "M3 12h18" }],
+ ["path", { d: "M3 19h18" }]
+ ];
+
+ const TextAlignCenter = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M17 12H7" }],
+ ["path", { d: "M19 19H5" }]
+ ];
+
+ const TextAlignStart = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M15 12H3" }],
+ ["path", { d: "M17 19H3" }]
+ ];
+
+ const TextCursorInput = [
+ ["path", { d: "M12 20h-1a2 2 0 0 1-2-2 2 2 0 0 1-2 2H6" }],
+ ["path", { d: "M13 8h7a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-7" }],
+ ["path", { d: "M5 16H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1" }],
+ ["path", { d: "M6 4h1a2 2 0 0 1 2 2 2 2 0 0 1 2-2h1" }],
+ ["path", { d: "M9 6v12" }]
+ ];
+
+ const TextCursor = [
+ ["path", { d: "M17 22h-1a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1" }],
+ ["path", { d: "M7 22h1a4 4 0 0 0 4-4v-1" }],
+ ["path", { d: "M7 2h1a4 4 0 0 1 4 4v1" }]
+ ];
+
+ const TextInitial = [
+ ["path", { d: "M15 5h6" }],
+ ["path", { d: "M15 12h6" }],
+ ["path", { d: "M3 19h18" }],
+ ["path", { d: "m3 12 3.553-7.724a.5.5 0 0 1 .894 0L11 12" }],
+ ["path", { d: "M3.92 10h6.16" }]
+ ];
+
+ const TextQuote = [
+ ["path", { d: "M17 5H3" }],
+ ["path", { d: "M21 12H8" }],
+ ["path", { d: "M21 19H8" }],
+ ["path", { d: "M3 12v7" }]
+ ];
+
+ const TextSearch = [
+ ["path", { d: "M21 5H3" }],
+ ["path", { d: "M10 12H3" }],
+ ["path", { d: "M10 19H3" }],
+ ["circle", { cx: "17", cy: "15", r: "3" }],
+ ["path", { d: "m21 19-1.9-1.9" }]
+ ];
+
+ const TextSelect = [
+ ["path", { d: "M14 21h1" }],
+ ["path", { d: "M14 3h1" }],
+ ["path", { d: "M19 3a2 2 0 0 1 2 2" }],
+ ["path", { d: "M21 14v1" }],
+ ["path", { d: "M21 19a2 2 0 0 1-2 2" }],
+ ["path", { d: "M21 9v1" }],
+ ["path", { d: "M3 14v1" }],
+ ["path", { d: "M3 9v1" }],
+ ["path", { d: "M5 21a2 2 0 0 1-2-2" }],
+ ["path", { d: "M5 3a2 2 0 0 0-2 2" }],
+ ["path", { d: "M7 12h10" }],
+ ["path", { d: "M7 16h6" }],
+ ["path", { d: "M7 8h8" }],
+ ["path", { d: "M9 21h1" }],
+ ["path", { d: "M9 3h1" }]
+ ];
+
+ const TextWrap = [
+ ["path", { d: "m16 16-3 3 3 3" }],
+ ["path", { d: "M3 12h14.5a1 1 0 0 1 0 7H13" }],
+ ["path", { d: "M3 19h6" }],
+ ["path", { d: "M3 5h18" }]
+ ];
+
+ const Theater = [
+ ["path", { d: "M2 10s3-3 3-8" }],
+ ["path", { d: "M22 10s-3-3-3-8" }],
+ ["path", { d: "M10 2c0 4.4-3.6 8-8 8" }],
+ ["path", { d: "M14 2c0 4.4 3.6 8 8 8" }],
+ ["path", { d: "M2 10s2 2 2 5" }],
+ ["path", { d: "M22 10s-2 2-2 5" }],
+ ["path", { d: "M8 15h8" }],
+ ["path", { d: "M2 22v-1a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1" }],
+ ["path", { d: "M14 22v-1a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1" }]
+ ];
+
+ const ThermometerSnowflake = [
+ ["path", { d: "m10 20-1.25-2.5L6 18" }],
+ ["path", { d: "M10 4 8.75 6.5 6 6" }],
+ ["path", { d: "M10.585 15H10" }],
+ ["path", { d: "M2 12h6.5L10 9" }],
+ ["path", { d: "M20 14.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0z" }],
+ ["path", { d: "m4 10 1.5 2L4 14" }],
+ ["path", { d: "m7 21 3-6-1.5-3" }],
+ ["path", { d: "m7 3 3 6h2" }]
+ ];
+
+ const ThermometerSun = [
+ ["path", { d: "M12 9a4 4 0 0 0-2 7.5" }],
+ ["path", { d: "M12 3v2" }],
+ ["path", { d: "m6.6 18.4-1.4 1.4" }],
+ ["path", { d: "M20 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" }],
+ ["path", { d: "M4 13H2" }],
+ ["path", { d: "M6.34 7.34 4.93 5.93" }]
+ ];
+
+ const Thermometer = [["path", { d: "M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" }]];
+
+ const ThumbsDown = [
+ ["path", { d: "M17 14V2" }],
+ [
+ "path",
+ {
+ d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z"
+ }
+ ]
+ ];
+
+ const ThumbsUp = [
+ ["path", { d: "M7 10v12" }],
+ [
+ "path",
+ {
+ d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z"
+ }
+ ]
+ ];
+
+ const TicketCheck = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "m9 12 2 2 4-4" }]
+ ];
+
+ const TicketMinus = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "M9 12h6" }]
+ ];
+
+ const TicketPercent = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 1 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 1 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "M9 9h.01" }],
+ ["path", { d: "m15 9-6 6" }],
+ ["path", { d: "M15 15h.01" }]
+ ];
+
+ const TicketPlus = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "M9 12h6" }],
+ ["path", { d: "M12 9v6" }]
+ ];
+
+ const TicketSlash = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "m9.5 14.5 5-5" }]
+ ];
+
+ const TicketX = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "m9.5 14.5 5-5" }],
+ ["path", { d: "m9.5 9.5 5 5" }]
+ ];
+
+ const Ticket = [
+ [
+ "path",
+ {
+ d: "M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"
+ }
+ ],
+ ["path", { d: "M13 5v2" }],
+ ["path", { d: "M13 17v2" }],
+ ["path", { d: "M13 11v2" }]
+ ];
+
+ const TicketsPlane = [
+ ["path", { d: "M10.5 17h1.227a2 2 0 0 0 1.345-.52L18 12" }],
+ ["path", { d: "m12 13.5 3.75.5" }],
+ ["path", { d: "m4.5 8 10.58-5.06a1 1 0 0 1 1.342.488L18.5 8" }],
+ ["path", { d: "M6 10V8" }],
+ ["path", { d: "M6 14v1" }],
+ ["path", { d: "M6 19v2" }],
+ ["rect", { x: "2", y: "8", width: "20", height: "13", rx: "2" }]
+ ];
+
+ const Tickets = [
+ ["path", { d: "m4.5 8 10.58-5.06a1 1 0 0 1 1.342.488L18.5 8" }],
+ ["path", { d: "M6 10V8" }],
+ ["path", { d: "M6 14v1" }],
+ ["path", { d: "M6 19v2" }],
+ ["rect", { x: "2", y: "8", width: "20", height: "13", rx: "2" }]
+ ];
+
+ const TimerOff = [
+ ["path", { d: "M10 2h4" }],
+ ["path", { d: "M4.6 11a8 8 0 0 0 1.7 8.7 8 8 0 0 0 8.7 1.7" }],
+ ["path", { d: "M7.4 7.4a8 8 0 0 1 10.3 1 8 8 0 0 1 .9 10.2" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M12 12v-2" }]
+ ];
+
+ const TimerReset = [
+ ["path", { d: "M10 2h4" }],
+ ["path", { d: "M12 14v-4" }],
+ ["path", { d: "M4 13a8 8 0 0 1 8-7 8 8 0 1 1-5.3 14L4 17.6" }],
+ ["path", { d: "M9 17H4v5" }]
+ ];
+
+ const ToggleLeft = [
+ ["circle", { cx: "9", cy: "12", r: "3" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "5", rx: "7" }]
+ ];
+
+ const Timer = [
+ ["line", { x1: "10", x2: "14", y1: "2", y2: "2" }],
+ ["line", { x1: "12", x2: "15", y1: "14", y2: "11" }],
+ ["circle", { cx: "12", cy: "14", r: "8" }]
+ ];
+
+ const ToggleRight = [
+ ["circle", { cx: "15", cy: "12", r: "3" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "5", rx: "7" }]
+ ];
+
+ const Toilet = [
+ [
+ "path",
+ {
+ d: "M7 12h13a1 1 0 0 1 1 1 5 5 0 0 1-5 5h-.598a.5.5 0 0 0-.424.765l1.544 2.47a.5.5 0 0 1-.424.765H5.402a.5.5 0 0 1-.424-.765L7 18"
+ }
+ ],
+ ["path", { d: "M8 18a5 5 0 0 1-5-5V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8" }]
+ ];
+
+ const ToolCase = [
+ ["path", { d: "M10 15h4" }],
+ [
+ "path",
+ {
+ d: "m14.817 10.995-.971-1.45 1.034-1.232a2 2 0 0 0-2.025-3.238l-1.82.364L9.91 3.885a2 2 0 0 0-3.625.748L6.141 6.55l-1.725.426a2 2 0 0 0-.19 3.756l.657.27"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "m18.822 10.995 2.26-5.38a1 1 0 0 0-.557-1.318L16.954 2.9a1 1 0 0 0-1.281.533l-.924 2.122"
+ }
+ ],
+ ["path", { d: "M4 12.006A1 1 0 0 1 4.994 11H19a1 1 0 0 1 1 1v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z" }]
+ ];
+
+ const Tornado = [
+ ["path", { d: "M21 4H3" }],
+ ["path", { d: "M18 8H6" }],
+ ["path", { d: "M19 12H9" }],
+ ["path", { d: "M16 16h-6" }],
+ ["path", { d: "M11 20H9" }]
+ ];
+
+ const Torus = [
+ ["ellipse", { cx: "12", cy: "11", rx: "3", ry: "2" }],
+ ["ellipse", { cx: "12", cy: "12.5", rx: "10", ry: "8.5" }]
+ ];
+
+ const TouchpadOff = [
+ ["path", { d: "M12 20v-6" }],
+ ["path", { d: "M19.656 14H22" }],
+ ["path", { d: "M2 14h12" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M20 20H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2" }],
+ ["path", { d: "M9.656 4H20a2 2 0 0 1 2 2v10.344" }]
+ ];
+
+ const Touchpad = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M2 14h20" }],
+ ["path", { d: "M12 20v-6" }]
+ ];
+
+ const TowerControl = [
+ ["path", { d: "M18.2 12.27 20 6H4l1.8 6.27a1 1 0 0 0 .95.73h10.5a1 1 0 0 0 .96-.73Z" }],
+ ["path", { d: "M8 13v9" }],
+ ["path", { d: "M16 22v-9" }],
+ ["path", { d: "m9 6 1 7" }],
+ ["path", { d: "m15 6-1 7" }],
+ ["path", { d: "M12 6V2" }],
+ ["path", { d: "M13 2h-2" }]
+ ];
+
+ const ToyBrick = [
+ ["rect", { width: "18", height: "12", x: "3", y: "8", rx: "1" }],
+ ["path", { d: "M10 8V5c0-.6-.4-1-1-1H6a1 1 0 0 0-1 1v3" }],
+ ["path", { d: "M19 8V5c0-.6-.4-1-1-1h-3a1 1 0 0 0-1 1v3" }]
+ ];
+
+ const Tractor = [
+ ["path", { d: "m10 11 11 .9a1 1 0 0 1 .8 1.1l-.665 4.158a1 1 0 0 1-.988.842H20" }],
+ ["path", { d: "M16 18h-5" }],
+ ["path", { d: "M18 5a1 1 0 0 0-1 1v5.573" }],
+ ["path", { d: "M3 4h8.129a1 1 0 0 1 .99.863L13 11.246" }],
+ ["path", { d: "M4 11V4" }],
+ ["path", { d: "M7 15h.01" }],
+ ["path", { d: "M8 10.1V4" }],
+ ["circle", { cx: "18", cy: "18", r: "2" }],
+ ["circle", { cx: "7", cy: "15", r: "5" }]
+ ];
+
+ const TrafficCone = [
+ ["path", { d: "M16.05 10.966a5 2.5 0 0 1-8.1 0" }],
+ [
+ "path",
+ {
+ d: "m16.923 14.049 4.48 2.04a1 1 0 0 1 .001 1.831l-8.574 3.9a2 2 0 0 1-1.66 0l-8.574-3.91a1 1 0 0 1 0-1.83l4.484-2.04"
+ }
+ ],
+ ["path", { d: "M16.949 14.14a5 2.5 0 1 1-9.9 0L10.063 3.5a2 2 0 0 1 3.874 0z" }],
+ ["path", { d: "M9.194 6.57a5 2.5 0 0 0 5.61 0" }]
+ ];
+
+ const TrainFrontTunnel = [
+ ["path", { d: "M2 22V12a10 10 0 1 1 20 0v10" }],
+ ["path", { d: "M15 6.8v1.4a3 2.8 0 1 1-6 0V6.8" }],
+ ["path", { d: "M10 15h.01" }],
+ ["path", { d: "M14 15h.01" }],
+ ["path", { d: "M10 19a4 4 0 0 1-4-4v-3a6 6 0 1 1 12 0v3a4 4 0 0 1-4 4Z" }],
+ ["path", { d: "m9 19-2 3" }],
+ ["path", { d: "m15 19 2 3" }]
+ ];
+
+ const TrainFront = [
+ ["path", { d: "M8 3.1V7a4 4 0 0 0 8 0V3.1" }],
+ ["path", { d: "m9 15-1-1" }],
+ ["path", { d: "m15 15 1-1" }],
+ ["path", { d: "M9 19c-2.8 0-5-2.2-5-5v-4a8 8 0 0 1 16 0v4c0 2.8-2.2 5-5 5Z" }],
+ ["path", { d: "m8 19-2 3" }],
+ ["path", { d: "m16 19 2 3" }]
+ ];
+
+ const TrainTrack = [
+ ["path", { d: "M2 17 17 2" }],
+ ["path", { d: "m2 14 8 8" }],
+ ["path", { d: "m5 11 8 8" }],
+ ["path", { d: "m8 8 8 8" }],
+ ["path", { d: "m11 5 8 8" }],
+ ["path", { d: "m14 2 8 8" }],
+ ["path", { d: "M7 22 22 7" }]
+ ];
+
+ const TramFront = [
+ ["rect", { width: "16", height: "16", x: "4", y: "3", rx: "2" }],
+ ["path", { d: "M4 11h16" }],
+ ["path", { d: "M12 3v8" }],
+ ["path", { d: "m8 19-2 3" }],
+ ["path", { d: "m18 22-2-3" }],
+ ["path", { d: "M8 15h.01" }],
+ ["path", { d: "M16 15h.01" }]
+ ];
+
+ const Trash2 = [
+ ["path", { d: "M10 11v6" }],
+ ["path", { d: "M14 11v6" }],
+ ["path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }],
+ ["path", { d: "M3 6h18" }],
+ ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }]
+ ];
+
+ const Transgender = [
+ ["path", { d: "M12 16v6" }],
+ ["path", { d: "M14 20h-4" }],
+ ["path", { d: "M18 2h4v4" }],
+ ["path", { d: "m2 2 7.17 7.17" }],
+ ["path", { d: "M2 5.355V2h3.357" }],
+ ["path", { d: "m22 2-7.17 7.17" }],
+ ["path", { d: "M8 5 5 8" }],
+ ["circle", { cx: "12", cy: "12", r: "4" }]
+ ];
+
+ const Trash = [
+ ["path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }],
+ ["path", { d: "M3 6h18" }],
+ ["path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" }]
+ ];
+
+ const TreeDeciduous = [
+ [
+ "path",
+ {
+ d: "M8 19a4 4 0 0 1-2.24-7.32A3.5 3.5 0 0 1 9 6.03V6a3 3 0 1 1 6 0v.04a3.5 3.5 0 0 1 3.24 5.65A4 4 0 0 1 16 19Z"
+ }
+ ],
+ ["path", { d: "M12 19v3" }]
+ ];
+
+ const TreePalm = [
+ ["path", { d: "M13 8c0-2.76-2.46-5-5.5-5S2 5.24 2 8h2l1-1 1 1h4" }],
+ ["path", { d: "M13 7.14A5.82 5.82 0 0 1 16.5 6c3.04 0 5.5 2.24 5.5 5h-3l-1-1-1 1h-3" }],
+ [
+ "path",
+ {
+ d: "M5.89 9.71c-2.15 2.15-2.3 5.47-.35 7.43l4.24-4.25.7-.7.71-.71 2.12-2.12c-1.95-1.96-5.27-1.8-7.42.35"
+ }
+ ],
+ ["path", { d: "M11 15.5c.5 2.5-.17 4.5-1 6.5h4c2-5.5-.5-12-1-14" }]
+ ];
+
+ const Trees = [
+ ["path", { d: "M10 10v.2A3 3 0 0 1 8.9 16H5a3 3 0 0 1-1-5.8V10a3 3 0 0 1 6 0Z" }],
+ ["path", { d: "M7 16v6" }],
+ ["path", { d: "M13 19v3" }],
+ [
+ "path",
+ {
+ d: "M12 19h8.3a1 1 0 0 0 .7-1.7L18 14h.3a1 1 0 0 0 .7-1.7L16 9h.2a1 1 0 0 0 .8-1.7L13 3l-1.4 1.5"
+ }
+ ]
+ ];
+
+ const TreePine = [
+ [
+ "path",
+ {
+ d: "m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z"
+ }
+ ],
+ ["path", { d: "M12 22v-3" }]
+ ];
+
+ const TrendingDown = [
+ ["path", { d: "M16 17h6v-6" }],
+ ["path", { d: "m22 17-8.5-8.5-5 5L2 7" }]
+ ];
+
+ const Trello = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }],
+ ["rect", { width: "3", height: "9", x: "7", y: "7" }],
+ ["rect", { width: "3", height: "5", x: "14", y: "7" }]
+ ];
+
+ const TrendingUpDown = [
+ ["path", { d: "M14.828 14.828 21 21" }],
+ ["path", { d: "M21 16v5h-5" }],
+ ["path", { d: "m21 3-9 9-4-4-6 6" }],
+ ["path", { d: "M21 8V3h-5" }]
+ ];
+
+ const TrendingUp = [
+ ["path", { d: "M16 7h6v6" }],
+ ["path", { d: "m22 7-8.5 8.5-5-5L2 17" }]
+ ];
+
+ const TriangleAlert = [
+ ["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }],
+ ["path", { d: "M12 9v4" }],
+ ["path", { d: "M12 17h.01" }]
+ ];
+
+ const TriangleDashed = [
+ ["path", { d: "M10.17 4.193a2 2 0 0 1 3.666.013" }],
+ ["path", { d: "M14 21h2" }],
+ ["path", { d: "m15.874 7.743 1 1.732" }],
+ ["path", { d: "m18.849 12.952 1 1.732" }],
+ ["path", { d: "M21.824 18.18a2 2 0 0 1-1.835 2.824" }],
+ ["path", { d: "M4.024 21a2 2 0 0 1-1.839-2.839" }],
+ ["path", { d: "m5.136 12.952-1 1.732" }],
+ ["path", { d: "M8 21h2" }],
+ ["path", { d: "m8.102 7.743-1 1.732" }]
+ ];
+
+ const TriangleRight = [
+ ["path", { d: "M22 18a2 2 0 0 1-2 2H3c-1.1 0-1.3-.6-.4-1.3L20.4 4.3c.9-.7 1.6-.4 1.6.7Z" }]
+ ];
+
+ const Triangle = [
+ ["path", { d: "M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }]
+ ];
+
+ const Trophy = [
+ ["path", { d: "M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978" }],
+ ["path", { d: "M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978" }],
+ ["path", { d: "M18 9h1.5a1 1 0 0 0 0-5H18" }],
+ ["path", { d: "M4 22h16" }],
+ ["path", { d: "M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z" }],
+ ["path", { d: "M6 9H4.5a1 1 0 0 1 0-5H6" }]
+ ];
+
+ const TruckElectric = [
+ ["path", { d: "M14 19V7a2 2 0 0 0-2-2H9" }],
+ ["path", { d: "M15 19H9" }],
+ ["path", { d: "M19 19h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.62L18.3 9.38a1 1 0 0 0-.78-.38H14" }],
+ ["path", { d: "M2 13v5a1 1 0 0 0 1 1h2" }],
+ ["path", { d: "M4 3 2.15 5.15a.495.495 0 0 0 .35.86h2.15a.47.47 0 0 1 .35.86L3 9.02" }],
+ ["circle", { cx: "17", cy: "19", r: "2" }],
+ ["circle", { cx: "7", cy: "19", r: "2" }]
+ ];
+
+ const Truck = [
+ ["path", { d: "M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" }],
+ ["path", { d: "M15 18H9" }],
+ [
+ "path",
+ { d: "M19 18h2a1 1 0 0 0 1-1v-3.65a1 1 0 0 0-.22-.624l-3.48-4.35A1 1 0 0 0 17.52 8H14" }
+ ],
+ ["circle", { cx: "17", cy: "18", r: "2" }],
+ ["circle", { cx: "7", cy: "18", r: "2" }]
+ ];
+
+ const TurkishLira = [
+ ["path", { d: "M15 4 5 9" }],
+ ["path", { d: "m15 8.5-10 5" }],
+ ["path", { d: "M18 12a9 9 0 0 1-9 9V3" }]
+ ];
+
+ const Turntable = [
+ ["path", { d: "M10 12.01h.01" }],
+ ["path", { d: "M18 8v4a8 8 0 0 1-1.07 4" }],
+ ["circle", { cx: "10", cy: "12", r: "4" }],
+ ["rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }]
+ ];
+
+ const TvMinimalPlay = [
+ [
+ "path",
+ {
+ d: "M15.033 9.44a.647.647 0 0 1 0 1.12l-4.065 2.352a.645.645 0 0 1-.968-.56V7.648a.645.645 0 0 1 .967-.56z"
+ }
+ ],
+ ["path", { d: "M7 21h10" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }]
+ ];
+
+ const Turtle = [
+ [
+ "path",
+ {
+ d: "m12 10 2 4v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3a8 8 0 1 0-16 0v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-3l2-4h4Z"
+ }
+ ],
+ ["path", { d: "M4.82 7.9 8 10" }],
+ ["path", { d: "M15.18 7.9 12 10" }],
+ ["path", { d: "M16.93 10H20a2 2 0 0 1 0 4H2" }]
+ ];
+
+ const TvMinimal = [
+ ["path", { d: "M7 21h10" }],
+ ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2" }]
+ ];
+
+ const Tv = [
+ ["path", { d: "m17 2-5 5-5-5" }],
+ ["rect", { width: "20", height: "15", x: "2", y: "7", rx: "2" }]
+ ];
+
+ const Twitch = [["path", { d: "M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7" }]];
+
+ const Twitter = [
+ [
+ "path",
+ {
+ d: "M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"
+ }
+ ]
+ ];
+
+ const TypeOutline = [
+ [
+ "path",
+ {
+ d: "M14 16.5a.5.5 0 0 0 .5.5h.5a2 2 0 0 1 0 4H9a2 2 0 0 1 0-4h.5a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5V8a2 2 0 0 1-4 0V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v3a2 2 0 0 1-4 0v-.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5Z"
+ }
+ ]
+ ];
+
+ const Type = [
+ ["path", { d: "M12 4v16" }],
+ ["path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }],
+ ["path", { d: "M9 20h6" }]
+ ];
+
+ const UmbrellaOff = [
+ ["path", { d: "M12 13v7a2 2 0 0 0 4 0" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M18.656 13h2.336a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-12.07-7.51" }],
+ ["path", { d: "m2 2 20 20" }],
+ ["path", { d: "M5.961 5.957a10.28 10.28 0 0 0-3.922 5.769A1 1 0 0 0 3 13h10" }]
+ ];
+
+ const Umbrella = [
+ ["path", { d: "M12 13v7a2 2 0 0 0 4 0" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M20.992 13a1 1 0 0 0 .97-1.274 10.284 10.284 0 0 0-19.923 0A1 1 0 0 0 3 13z" }]
+ ];
+
+ const Underline = [
+ ["path", { d: "M6 4v6a6 6 0 0 0 12 0V4" }],
+ ["line", { x1: "4", x2: "20", y1: "20", y2: "20" }]
+ ];
+
+ const Undo2 = [
+ ["path", { d: "M9 14 4 9l5-5" }],
+ ["path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" }]
+ ];
+
+ const UndoDot = [
+ ["path", { d: "M21 17a9 9 0 0 0-15-6.7L3 13" }],
+ ["path", { d: "M3 7v6h6" }],
+ ["circle", { cx: "12", cy: "17", r: "1" }]
+ ];
+
+ const Undo = [
+ ["path", { d: "M3 7v6h6" }],
+ ["path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }]
+ ];
+
+ const UnfoldHorizontal = [
+ ["path", { d: "M16 12h6" }],
+ ["path", { d: "M8 12H2" }],
+ ["path", { d: "M12 2v2" }],
+ ["path", { d: "M12 8v2" }],
+ ["path", { d: "M12 14v2" }],
+ ["path", { d: "M12 20v2" }],
+ ["path", { d: "m19 15 3-3-3-3" }],
+ ["path", { d: "m5 9-3 3 3 3" }]
+ ];
+
+ const UnfoldVertical = [
+ ["path", { d: "M12 22v-6" }],
+ ["path", { d: "M12 8V2" }],
+ ["path", { d: "M4 12H2" }],
+ ["path", { d: "M10 12H8" }],
+ ["path", { d: "M16 12h-2" }],
+ ["path", { d: "M22 12h-2" }],
+ ["path", { d: "m15 19-3 3-3-3" }],
+ ["path", { d: "m15 5-3-3-3 3" }]
+ ];
+
+ const Ungroup = [
+ ["rect", { width: "8", height: "6", x: "5", y: "4", rx: "1" }],
+ ["rect", { width: "8", height: "6", x: "11", y: "14", rx: "1" }]
+ ];
+
+ const University = [
+ ["path", { d: "M14 21v-3a2 2 0 0 0-4 0v3" }],
+ ["path", { d: "M18 12h.01" }],
+ ["path", { d: "M18 16h.01" }],
+ [
+ "path",
+ {
+ d: "M22 7a1 1 0 0 0-1-1h-2a2 2 0 0 1-1.143-.359L13.143 2.36a2 2 0 0 0-2.286-.001L6.143 5.64A2 2 0 0 1 5 6H3a1 1 0 0 0-1 1v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2z"
+ }
+ ],
+ ["path", { d: "M6 12h.01" }],
+ ["path", { d: "M6 16h.01" }],
+ ["circle", { cx: "12", cy: "10", r: "2" }]
+ ];
+
+ const Unlink2 = [["path", { d: "M15 7h2a5 5 0 0 1 0 10h-2m-6 0H7A5 5 0 0 1 7 7h2" }]];
+
+ const Unlink = [
+ [
+ "path",
+ {
+ d: "m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71"
+ }
+ ],
+ [
+ "path",
+ { d: "m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71" }
+ ],
+ ["line", { x1: "8", x2: "8", y1: "2", y2: "5" }],
+ ["line", { x1: "2", x2: "5", y1: "8", y2: "8" }],
+ ["line", { x1: "16", x2: "16", y1: "19", y2: "22" }],
+ ["line", { x1: "19", x2: "22", y1: "16", y2: "16" }]
+ ];
+
+ const Unplug = [
+ ["path", { d: "m19 5 3-3" }],
+ ["path", { d: "m2 22 3-3" }],
+ ["path", { d: "M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z" }],
+ ["path", { d: "M7.5 13.5 10 11" }],
+ ["path", { d: "M10.5 16.5 13 14" }],
+ ["path", { d: "m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z" }]
+ ];
+
+ const Upload = [
+ ["path", { d: "M12 3v12" }],
+ ["path", { d: "m17 8-5-5-5 5" }],
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }]
+ ];
+
+ const Usb = [
+ ["circle", { cx: "10", cy: "7", r: "1" }],
+ ["circle", { cx: "4", cy: "20", r: "1" }],
+ ["path", { d: "M4.7 19.3 19 5" }],
+ ["path", { d: "m21 3-3 1 2 2Z" }],
+ ["path", { d: "M9.26 7.68 5 12l2 5" }],
+ ["path", { d: "m10 14 5 2 3.5-3.5" }],
+ ["path", { d: "m18 12 1-1 1 1-1 1Z" }]
+ ];
+
+ const UserCheck = [
+ ["path", { d: "m16 11 2 2 4-4" }],
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }]
+ ];
+
+ const UserCog = [
+ ["path", { d: "M10 15H6a4 4 0 0 0-4 4v2" }],
+ ["path", { d: "m14.305 16.53.923-.382" }],
+ ["path", { d: "m15.228 13.852-.923-.383" }],
+ ["path", { d: "m16.852 12.228-.383-.923" }],
+ ["path", { d: "m16.852 17.772-.383.924" }],
+ ["path", { d: "m19.148 12.228.383-.923" }],
+ ["path", { d: "m19.53 18.696-.382-.924" }],
+ ["path", { d: "m20.772 13.852.924-.383" }],
+ ["path", { d: "m20.772 16.148.924.383" }],
+ ["circle", { cx: "18", cy: "15", r: "3" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }]
+ ];
+
+ const UserLock = [
+ ["circle", { cx: "10", cy: "7", r: "4" }],
+ ["path", { d: "M10.3 15H7a4 4 0 0 0-4 4v2" }],
+ ["path", { d: "M15 15.5V14a2 2 0 0 1 4 0v1.5" }],
+ ["rect", { width: "8", height: "5", x: "13", y: "16", rx: ".899" }]
+ ];
+
+ const UserMinus = [
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }],
+ ["line", { x1: "22", x2: "16", y1: "11", y2: "11" }]
+ ];
+
+ const UserPen = [
+ ["path", { d: "M11.5 15H7a4 4 0 0 0-4 4v2" }],
+ [
+ "path",
+ {
+ d: "M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ],
+ ["circle", { cx: "10", cy: "7", r: "4" }]
+ ];
+
+ const UserPlus = [
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }],
+ ["line", { x1: "19", x2: "19", y1: "8", y2: "14" }],
+ ["line", { x1: "22", x2: "16", y1: "11", y2: "11" }]
+ ];
+
+ const UserRoundCheck = [
+ ["path", { d: "M2 21a8 8 0 0 1 13.292-6" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "m16 19 2 2 4-4" }]
+ ];
+
+ const UserRoundCog = [
+ ["path", { d: "m14.305 19.53.923-.382" }],
+ ["path", { d: "m15.228 16.852-.923-.383" }],
+ ["path", { d: "m16.852 15.228-.383-.923" }],
+ ["path", { d: "m16.852 20.772-.383.924" }],
+ ["path", { d: "m19.148 15.228.383-.923" }],
+ ["path", { d: "m19.53 21.696-.382-.924" }],
+ ["path", { d: "M2 21a8 8 0 0 1 10.434-7.62" }],
+ ["path", { d: "m20.772 16.852.924-.383" }],
+ ["path", { d: "m20.772 19.148.924.383" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const UserRoundMinus = [
+ ["path", { d: "M2 21a8 8 0 0 1 13.292-6" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "M22 19h-6" }]
+ ];
+
+ const UserRoundPen = [
+ ["path", { d: "M2 21a8 8 0 0 1 10.821-7.487" }],
+ [
+ "path",
+ {
+ d: "M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ],
+ ["circle", { cx: "10", cy: "8", r: "5" }]
+ ];
+
+ const UserRoundPlus = [
+ ["path", { d: "M2 21a8 8 0 0 1 13.292-6" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "M19 16v6" }],
+ ["path", { d: "M22 19h-6" }]
+ ];
+
+ const UserRoundSearch = [
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "M2 21a8 8 0 0 1 10.434-7.62" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }],
+ ["path", { d: "m22 22-1.9-1.9" }]
+ ];
+
+ const UserRoundX = [
+ ["path", { d: "M2 21a8 8 0 0 1 11.873-7" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "m17 17 5 5" }],
+ ["path", { d: "m22 17-5 5" }]
+ ];
+
+ const UserRound = [
+ ["circle", { cx: "12", cy: "8", r: "5" }],
+ ["path", { d: "M20 21a8 8 0 0 0-16 0" }]
+ ];
+
+ const UserSearch = [
+ ["circle", { cx: "10", cy: "7", r: "4" }],
+ ["path", { d: "M10.3 15H7a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "17", cy: "17", r: "3" }],
+ ["path", { d: "m21 21-1.9-1.9" }]
+ ];
+
+ const UserStar = [
+ [
+ "path",
+ {
+ d: "M16.051 12.616a1 1 0 0 1 1.909.024l.737 1.452a1 1 0 0 0 .737.535l1.634.256a1 1 0 0 1 .588 1.806l-1.172 1.168a1 1 0 0 0-.282.866l.259 1.613a1 1 0 0 1-1.541 1.134l-1.465-.75a1 1 0 0 0-.912 0l-1.465.75a1 1 0 0 1-1.539-1.133l.258-1.613a1 1 0 0 0-.282-.866l-1.156-1.153a1 1 0 0 1 .572-1.822l1.633-.256a1 1 0 0 0 .737-.535z"
+ }
+ ],
+ ["path", { d: "M8 15H7a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "10", cy: "7", r: "4" }]
+ ];
+
+ const UserX = [
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }],
+ ["line", { x1: "17", x2: "22", y1: "8", y2: "13" }],
+ ["line", { x1: "22", x2: "17", y1: "8", y2: "13" }]
+ ];
+
+ const User = [
+ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }],
+ ["circle", { cx: "12", cy: "7", r: "4" }]
+ ];
+
+ const UsersRound = [
+ ["path", { d: "M18 21a8 8 0 0 0-16 0" }],
+ ["circle", { cx: "10", cy: "8", r: "5" }],
+ ["path", { d: "M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3" }]
+ ];
+
+ const Users = [
+ ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }],
+ ["path", { d: "M16 3.128a4 4 0 0 1 0 7.744" }],
+ ["path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }],
+ ["circle", { cx: "9", cy: "7", r: "4" }]
+ ];
+
+ const UtensilsCrossed = [
+ ["path", { d: "m16 2-2.3 2.3a3 3 0 0 0 0 4.2l1.8 1.8a3 3 0 0 0 4.2 0L22 8" }],
+ ["path", { d: "M15 15 3.3 3.3a4.2 4.2 0 0 0 0 6l7.3 7.3c.7.7 2 .7 2.8 0L15 15Zm0 0 7 7" }],
+ ["path", { d: "m2.1 21.8 6.4-6.3" }],
+ ["path", { d: "m19 5-7 7" }]
+ ];
+
+ const Utensils = [
+ ["path", { d: "M3 2v7c0 1.1.9 2 2 2h4a2 2 0 0 0 2-2V2" }],
+ ["path", { d: "M7 2v20" }],
+ ["path", { d: "M21 15V2a5 5 0 0 0-5 5v6c0 1.1.9 2 2 2h3Zm0 0v7" }]
+ ];
+
+ const UtilityPole = [
+ ["path", { d: "M12 2v20" }],
+ ["path", { d: "M2 5h20" }],
+ ["path", { d: "M3 3v2" }],
+ ["path", { d: "M7 3v2" }],
+ ["path", { d: "M17 3v2" }],
+ ["path", { d: "M21 3v2" }],
+ ["path", { d: "m19 5-7 7-7-7" }]
+ ];
+
+ const Variable = [
+ ["path", { d: "M8 21s-4-3-4-9 4-9 4-9" }],
+ ["path", { d: "M16 3s4 3 4 9-4 9-4 9" }],
+ ["line", { x1: "15", x2: "9", y1: "9", y2: "15" }],
+ ["line", { x1: "9", x2: "15", y1: "9", y2: "15" }]
+ ];
+
+ const Vault = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }],
+ ["path", { d: "m7.9 7.9 2.7 2.7" }],
+ ["circle", { cx: "16.5", cy: "7.5", r: ".5", fill: "currentColor" }],
+ ["path", { d: "m13.4 10.6 2.7-2.7" }],
+ ["circle", { cx: "7.5", cy: "16.5", r: ".5", fill: "currentColor" }],
+ ["path", { d: "m7.9 16.1 2.7-2.7" }],
+ ["circle", { cx: "16.5", cy: "16.5", r: ".5", fill: "currentColor" }],
+ ["path", { d: "m13.4 13.4 2.7 2.7" }],
+ ["circle", { cx: "12", cy: "12", r: "2" }]
+ ];
+
+ const Vegan = [
+ ["path", { d: "M16 8q6 0 6-6-6 0-6 6" }],
+ ["path", { d: "M17.41 3.59a10 10 0 1 0 3 3" }],
+ ["path", { d: "M2 2a26.6 26.6 0 0 1 10 20c.9-6.82 1.5-9.5 4-14" }]
+ ];
+
+ const VectorSquare = [
+ ["path", { d: "M19.5 7a24 24 0 0 1 0 10" }],
+ ["path", { d: "M4.5 7a24 24 0 0 0 0 10" }],
+ ["path", { d: "M7 19.5a24 24 0 0 0 10 0" }],
+ ["path", { d: "M7 4.5a24 24 0 0 1 10 0" }],
+ ["rect", { x: "17", y: "17", width: "5", height: "5", rx: "1" }],
+ ["rect", { x: "17", y: "2", width: "5", height: "5", rx: "1" }],
+ ["rect", { x: "2", y: "17", width: "5", height: "5", rx: "1" }],
+ ["rect", { x: "2", y: "2", width: "5", height: "5", rx: "1" }]
+ ];
+
+ const VenetianMask = [
+ ["path", { d: "M18 11c-1.5 0-2.5.5-3 2" }],
+ [
+ "path",
+ {
+ d: "M4 6a2 2 0 0 0-2 2v4a5 5 0 0 0 5 5 8 8 0 0 1 5 2 8 8 0 0 1 5-2 5 5 0 0 0 5-5V8a2 2 0 0 0-2-2h-3a8 8 0 0 0-5 2 8 8 0 0 0-5-2z"
+ }
+ ],
+ ["path", { d: "M6 11c1.5 0 2.5.5 3 2" }]
+ ];
+
+ const VenusAndMars = [
+ ["path", { d: "M10 20h4" }],
+ ["path", { d: "M12 16v6" }],
+ ["path", { d: "M17 2h4v4" }],
+ ["path", { d: "m21 2-5.46 5.46" }],
+ ["circle", { cx: "12", cy: "11", r: "5" }]
+ ];
+
+ const Venus = [
+ ["path", { d: "M12 15v7" }],
+ ["path", { d: "M9 19h6" }],
+ ["circle", { cx: "12", cy: "9", r: "6" }]
+ ];
+
+ const VibrateOff = [
+ ["path", { d: "m2 8 2 2-2 2 2 2-2 2" }],
+ ["path", { d: "m22 8-2 2 2 2-2 2 2 2" }],
+ ["path", { d: "M8 8v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2" }],
+ ["path", { d: "M16 10.34V6c0-.55-.45-1-1-1h-4.34" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const VideoOff = [
+ ["path", { d: "M10.66 6H14a2 2 0 0 1 2 2v2.5l5.248-3.062A.5.5 0 0 1 22 7.87v8.196" }],
+ ["path", { d: "M16 16a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Vibrate = [
+ ["path", { d: "m2 8 2 2-2 2 2 2-2 2" }],
+ ["path", { d: "m22 8-2 2 2 2-2 2 2 2" }],
+ ["rect", { width: "8", height: "14", x: "8", y: "5", rx: "1" }]
+ ];
+
+ const Video = [
+ ["path", { d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5" }],
+ ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }]
+ ];
+
+ const Videotape = [
+ ["rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" }],
+ ["path", { d: "M2 8h20" }],
+ ["circle", { cx: "8", cy: "14", r: "2" }],
+ ["path", { d: "M8 12h8" }],
+ ["circle", { cx: "16", cy: "14", r: "2" }]
+ ];
+
+ const View = [
+ ["path", { d: "M21 17v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }],
+ ["path", { d: "M21 7V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v2" }],
+ ["circle", { cx: "12", cy: "12", r: "1" }],
+ [
+ "path",
+ {
+ d: "M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0"
+ }
+ ]
+ ];
+
+ const Voicemail = [
+ ["circle", { cx: "6", cy: "12", r: "4" }],
+ ["circle", { cx: "18", cy: "12", r: "4" }],
+ ["line", { x1: "6", x2: "18", y1: "16", y2: "16" }]
+ ];
+
+ const Volleyball = [
+ ["path", { d: "M11.1 7.1a16.55 16.55 0 0 1 10.9 4" }],
+ ["path", { d: "M12 12a12.6 12.6 0 0 1-8.7 5" }],
+ ["path", { d: "M16.8 13.6a16.55 16.55 0 0 1-9 7.5" }],
+ ["path", { d: "M20.7 17a12.8 12.8 0 0 0-8.7-5 13.3 13.3 0 0 1 0-10" }],
+ ["path", { d: "M6.3 3.8a16.55 16.55 0 0 0 1.9 11.5" }],
+ ["circle", { cx: "12", cy: "12", r: "10" }]
+ ];
+
+ const Volume1 = [
+ [
+ "path",
+ {
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
+ }
+ ],
+ ["path", { d: "M16 9a5 5 0 0 1 0 6" }]
+ ];
+
+ const Volume2 = [
+ [
+ "path",
+ {
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
+ }
+ ],
+ ["path", { d: "M16 9a5 5 0 0 1 0 6" }],
+ ["path", { d: "M19.364 18.364a9 9 0 0 0 0-12.728" }]
+ ];
+
+ const VolumeOff = [
+ ["path", { d: "M16 9a5 5 0 0 1 .95 2.293" }],
+ ["path", { d: "M19.364 5.636a9 9 0 0 1 1.889 9.96" }],
+ ["path", { d: "m2 2 20 20" }],
+ [
+ "path",
+ {
+ d: "m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"
+ }
+ ],
+ ["path", { d: "M9.828 4.172A.686.686 0 0 1 11 4.657v.686" }]
+ ];
+
+ const VolumeX = [
+ [
+ "path",
+ {
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
+ }
+ ],
+ ["line", { x1: "22", x2: "16", y1: "9", y2: "15" }],
+ ["line", { x1: "16", x2: "22", y1: "9", y2: "15" }]
+ ];
+
+ const Vote = [
+ ["path", { d: "m9 12 2 2 4-4" }],
+ ["path", { d: "M5 7c0-1.1.9-2 2-2h10a2 2 0 0 1 2 2v12H5V7Z" }],
+ ["path", { d: "M22 19H2" }]
+ ];
+
+ const Volume = [
+ [
+ "path",
+ {
+ d: "M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"
+ }
+ ]
+ ];
+
+ const WalletCards = [
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2" }],
+ [
+ "path",
+ { d: "M3 11h3c.8 0 1.6.3 2.1.9l1.1.9c1.6 1.6 4.1 1.6 5.7 0l1.1-.9c.5-.5 1.3-.9 2.1-.9H21" }
+ ]
+ ];
+
+ const WalletMinimal = [
+ ["path", { d: "M17 14h.01" }],
+ ["path", { d: "M7 7h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14" }]
+ ];
+
+ const Wallet = [
+ [
+ "path",
+ {
+ d: "M19 7V4a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1"
+ }
+ ],
+ ["path", { d: "M3 5v14a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-4" }]
+ ];
+
+ const Wallpaper = [
+ ["path", { d: "M12 17v4" }],
+ ["path", { d: "M8 21h8" }],
+ ["path", { d: "m9 17 6.1-6.1a2 2 0 0 1 2.81.01L22 15" }],
+ ["circle", { cx: "8", cy: "9", r: "2" }],
+ ["rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }]
+ ];
+
+ const WandSparkles = [
+ [
+ "path",
+ {
+ d: "m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72"
+ }
+ ],
+ ["path", { d: "m14 7 3 3" }],
+ ["path", { d: "M5 6v4" }],
+ ["path", { d: "M19 14v4" }],
+ ["path", { d: "M10 2v2" }],
+ ["path", { d: "M7 8H3" }],
+ ["path", { d: "M21 16h-4" }],
+ ["path", { d: "M11 3H9" }]
+ ];
+
+ const Wand = [
+ ["path", { d: "M15 4V2" }],
+ ["path", { d: "M15 16v-2" }],
+ ["path", { d: "M8 9h2" }],
+ ["path", { d: "M20 9h2" }],
+ ["path", { d: "M17.8 11.8 19 13" }],
+ ["path", { d: "M15 9h.01" }],
+ ["path", { d: "M17.8 6.2 19 5" }],
+ ["path", { d: "m3 21 9-9" }],
+ ["path", { d: "M12.2 6.2 11 5" }]
+ ];
+
+ const Warehouse = [
+ ["path", { d: "M18 21V10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v11" }],
+ [
+ "path",
+ {
+ d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 1.132-1.803l7.95-3.974a2 2 0 0 1 1.837 0l7.948 3.974A2 2 0 0 1 22 8z"
+ }
+ ],
+ ["path", { d: "M6 13h12" }],
+ ["path", { d: "M6 17h12" }]
+ ];
+
+ const WashingMachine = [
+ ["path", { d: "M3 6h3" }],
+ ["path", { d: "M17 6h.01" }],
+ ["rect", { width: "18", height: "20", x: "3", y: "2", rx: "2" }],
+ ["circle", { cx: "12", cy: "13", r: "5" }],
+ ["path", { d: "M12 18a2.5 2.5 0 0 0 0-5 2.5 2.5 0 0 1 0-5" }]
+ ];
+
+ const Watch = [
+ ["path", { d: "M12 10v2.2l1.6 1" }],
+ ["path", { d: "m16.13 7.66-.81-4.05a2 2 0 0 0-2-1.61h-2.68a2 2 0 0 0-2 1.61l-.78 4.05" }],
+ ["path", { d: "m7.88 16.36.8 4a2 2 0 0 0 2 1.61h2.72a2 2 0 0 0 2-1.61l.81-4.05" }],
+ ["circle", { cx: "12", cy: "12", r: "6" }]
+ ];
+
+ const WavesLadder = [
+ ["path", { d: "M19 5a2 2 0 0 0-2 2v11" }],
+ [
+ "path",
+ {
+ d: "M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"
+ }
+ ],
+ ["path", { d: "M7 13h10" }],
+ ["path", { d: "M7 9h10" }],
+ ["path", { d: "M9 5a2 2 0 0 0-2 2v11" }]
+ ];
+
+ const Waves = [
+ [
+ "path",
+ { d: "M2 6c.6.5 1.2 1 2.5 1C7 7 7 5 9.5 5c2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1" }
+ ],
+ [
+ "path",
+ {
+ d: "M2 12c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M2 18c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 2.6 0 2.4 2 5 2 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"
+ }
+ ]
+ ];
+
+ const Waypoints = [
+ ["circle", { cx: "12", cy: "4.5", r: "2.5" }],
+ ["path", { d: "m10.2 6.3-3.9 3.9" }],
+ ["circle", { cx: "4.5", cy: "12", r: "2.5" }],
+ ["path", { d: "M7 12h10" }],
+ ["circle", { cx: "19.5", cy: "12", r: "2.5" }],
+ ["path", { d: "m13.8 17.7 3.9-3.9" }],
+ ["circle", { cx: "12", cy: "19.5", r: "2.5" }]
+ ];
+
+ const Webcam = [
+ ["circle", { cx: "12", cy: "10", r: "8" }],
+ ["circle", { cx: "12", cy: "10", r: "3" }],
+ ["path", { d: "M7 22h10" }],
+ ["path", { d: "M12 22v-4" }]
+ ];
+
+ const WebhookOff = [
+ ["path", { d: "M17 17h-5c-1.09-.02-1.94.92-2.5 1.9A3 3 0 1 1 2.57 15" }],
+ ["path", { d: "M9 3.4a4 4 0 0 1 6.52.66" }],
+ ["path", { d: "m6 17 3.1-5.8a2.5 2.5 0 0 0 .057-2.05" }],
+ ["path", { d: "M20.3 20.3a4 4 0 0 1-2.3.7" }],
+ ["path", { d: "M18.6 13a4 4 0 0 1 3.357 3.414" }],
+ ["path", { d: "m12 6 .6 1" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Webhook = [
+ ["path", { d: "M18 16.98h-5.99c-1.1 0-1.95.94-2.48 1.9A4 4 0 0 1 2 17c.01-.7.2-1.4.57-2" }],
+ ["path", { d: "m6 17 3.13-5.78c.53-.97.1-2.18-.5-3.1a4 4 0 1 1 6.89-4.06" }],
+ ["path", { d: "m12 6 3.13 5.73C15.66 12.7 16.9 13 18 13a4 4 0 0 1 0 8" }]
+ ];
+
+ const WheatOff = [
+ ["path", { d: "m2 22 10-10" }],
+ ["path", { d: "m16 8-1.17 1.17" }],
+ [
+ "path",
+ { d: "M3.47 12.53 5 11l1.53 1.53a3.5 3.5 0 0 1 0 4.94L5 19l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z" }
+ ],
+ ["path", { d: "m8 8-.53.53a3.5 3.5 0 0 0 0 4.94L9 15l1.53-1.53c.55-.55.88-1.25.98-1.97" }],
+ ["path", { d: "M10.91 5.26c.15-.26.34-.51.56-.73L13 3l1.53 1.53a3.5 3.5 0 0 1 .28 4.62" }],
+ ["path", { d: "M20 2h2v2a4 4 0 0 1-4 4h-2V6a4 4 0 0 1 4-4Z" }],
+ [
+ "path",
+ {
+ d: "M11.47 17.47 13 19l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L5 19l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"
+ }
+ ],
+ ["path", { d: "m16 16-.53.53a3.5 3.5 0 0 1-4.94 0L9 15l1.53-1.53a3.49 3.49 0 0 1 1.97-.98" }],
+ ["path", { d: "M18.74 13.09c.26-.15.51-.34.73-.56L21 11l-1.53-1.53a3.5 3.5 0 0 0-4.62-.28" }],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Weight = [
+ ["circle", { cx: "12", cy: "5", r: "3" }],
+ [
+ "path",
+ {
+ d: "M6.5 8a2 2 0 0 0-1.905 1.46L2.1 18.5A2 2 0 0 0 4 21h16a2 2 0 0 0 1.925-2.54L19.4 9.5A2 2 0 0 0 17.48 8Z"
+ }
+ ]
+ ];
+
+ const Wheat = [
+ ["path", { d: "M2 22 16 8" }],
+ [
+ "path",
+ { d: "M3.47 12.53 5 11l1.53 1.53a3.5 3.5 0 0 1 0 4.94L5 19l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z" }
+ ],
+ [
+ "path",
+ { d: "M7.47 8.53 9 7l1.53 1.53a3.5 3.5 0 0 1 0 4.94L9 15l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z" }
+ ],
+ [
+ "path",
+ { d: "M11.47 4.53 13 3l1.53 1.53a3.5 3.5 0 0 1 0 4.94L13 11l-1.53-1.53a3.5 3.5 0 0 1 0-4.94Z" }
+ ],
+ ["path", { d: "M20 2h2v2a4 4 0 0 1-4 4h-2V6a4 4 0 0 1 4-4Z" }],
+ [
+ "path",
+ {
+ d: "M11.47 17.47 13 19l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L5 19l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M15.47 13.47 17 15l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L9 15l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"
+ }
+ ],
+ [
+ "path",
+ {
+ d: "M19.47 9.47 21 11l-1.53 1.53a3.5 3.5 0 0 1-4.94 0L13 11l1.53-1.53a3.5 3.5 0 0 1 4.94 0Z"
+ }
+ ]
+ ];
+
+ const WholeWord = [
+ ["circle", { cx: "7", cy: "12", r: "3" }],
+ ["path", { d: "M10 9v6" }],
+ ["circle", { cx: "17", cy: "12", r: "3" }],
+ ["path", { d: "M14 7v8" }],
+ ["path", { d: "M22 17v1c0 .5-.5 1-1 1H3c-.5 0-1-.5-1-1v-1" }]
+ ];
+
+ const WifiHigh = [
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M5 12.859a10 10 0 0 1 14 0" }],
+ ["path", { d: "M8.5 16.429a5 5 0 0 1 7 0" }]
+ ];
+
+ const WifiCog = [
+ ["path", { d: "m14.305 19.53.923-.382" }],
+ ["path", { d: "m15.228 16.852-.923-.383" }],
+ ["path", { d: "m16.852 15.228-.383-.923" }],
+ ["path", { d: "m16.852 20.772-.383.924" }],
+ ["path", { d: "m19.148 15.228.383-.923" }],
+ ["path", { d: "m19.53 21.696-.382-.924" }],
+ ["path", { d: "M2 7.82a15 15 0 0 1 20 0" }],
+ ["path", { d: "m20.772 16.852.924-.383" }],
+ ["path", { d: "m20.772 19.148.924.383" }],
+ ["path", { d: "M5 11.858a10 10 0 0 1 11.5-1.785" }],
+ ["path", { d: "M8.5 15.429a5 5 0 0 1 2.413-1.31" }],
+ ["circle", { cx: "18", cy: "18", r: "3" }]
+ ];
+
+ const WifiLow = [
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M8.5 16.429a5 5 0 0 1 7 0" }]
+ ];
+
+ const WifiOff = [
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M8.5 16.429a5 5 0 0 1 7 0" }],
+ ["path", { d: "M5 12.859a10 10 0 0 1 5.17-2.69" }],
+ ["path", { d: "M19 12.859a10 10 0 0 0-2.007-1.523" }],
+ ["path", { d: "M2 8.82a15 15 0 0 1 4.177-2.643" }],
+ ["path", { d: "M22 8.82a15 15 0 0 0-11.288-3.764" }],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const WifiPen = [
+ ["path", { d: "M2 8.82a15 15 0 0 1 20 0" }],
+ [
+ "path",
+ {
+ d: "M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"
+ }
+ ],
+ ["path", { d: "M5 12.859a10 10 0 0 1 10.5-2.222" }],
+ ["path", { d: "M8.5 16.429a5 5 0 0 1 3-1.406" }]
+ ];
+
+ const WifiSync = [
+ ["path", { d: "M11.965 10.105v4L13.5 12.5a5 5 0 0 1 8 1.5" }],
+ ["path", { d: "M11.965 14.105h4" }],
+ ["path", { d: "M17.965 18.105h4L20.43 19.71a5 5 0 0 1-8-1.5" }],
+ ["path", { d: "M2 8.82a15 15 0 0 1 20 0" }],
+ ["path", { d: "M21.965 22.105v-4" }],
+ ["path", { d: "M5 12.86a10 10 0 0 1 3-2.032" }],
+ ["path", { d: "M8.5 16.429h.01" }]
+ ];
+
+ const WifiZero = [["path", { d: "M12 20h.01" }]];
+
+ const Wifi = [
+ ["path", { d: "M12 20h.01" }],
+ ["path", { d: "M2 8.82a15 15 0 0 1 20 0" }],
+ ["path", { d: "M5 12.859a10 10 0 0 1 14 0" }],
+ ["path", { d: "M8.5 16.429a5 5 0 0 1 7 0" }]
+ ];
+
+ const WindArrowDown = [
+ ["path", { d: "M10 2v8" }],
+ ["path", { d: "M12.8 21.6A2 2 0 1 0 14 18H2" }],
+ ["path", { d: "M17.5 10a2.5 2.5 0 1 1 2 4H2" }],
+ ["path", { d: "m6 6 4 4 4-4" }]
+ ];
+
+ const Wind = [
+ ["path", { d: "M12.8 19.6A2 2 0 1 0 14 16H2" }],
+ ["path", { d: "M17.5 8a2.5 2.5 0 1 1 2 4H2" }],
+ ["path", { d: "M9.8 4.4A2 2 0 1 1 11 8H2" }]
+ ];
+
+ const WineOff = [
+ ["path", { d: "M8 22h8" }],
+ ["path", { d: "M7 10h3m7 0h-1.343" }],
+ ["path", { d: "M12 15v7" }],
+ [
+ "path",
+ {
+ d: "M7.307 7.307A12.33 12.33 0 0 0 7 10a5 5 0 0 0 7.391 4.391M8.638 2.981C8.75 2.668 8.872 2.34 9 2h6c1.5 4 2 6 2 8 0 .407-.05.809-.145 1.198"
+ }
+ ],
+ ["line", { x1: "2", x2: "22", y1: "2", y2: "22" }]
+ ];
+
+ const Wine = [
+ ["path", { d: "M8 22h8" }],
+ ["path", { d: "M7 10h10" }],
+ ["path", { d: "M12 15v7" }],
+ ["path", { d: "M12 15a5 5 0 0 0 5-5c0-2-.5-4-2-8H9c-1.5 4-2 6-2 8a5 5 0 0 0 5 5Z" }]
+ ];
+
+ const Workflow = [
+ ["rect", { width: "8", height: "8", x: "3", y: "3", rx: "2" }],
+ ["path", { d: "M7 11v4a2 2 0 0 0 2 2h4" }],
+ ["rect", { width: "8", height: "8", x: "13", y: "13", rx: "2" }]
+ ];
+
+ const Worm = [
+ ["path", { d: "m19 12-1.5 3" }],
+ ["path", { d: "M19.63 18.81 22 20" }],
+ [
+ "path",
+ {
+ d: "M6.47 8.23a1.68 1.68 0 0 1 2.44 1.93l-.64 2.08a6.76 6.76 0 0 0 10.16 7.67l.42-.27a1 1 0 1 0-2.73-4.21l-.42.27a1.76 1.76 0 0 1-2.63-1.99l.64-2.08A6.66 6.66 0 0 0 3.94 3.9l-.7.4a1 1 0 1 0 2.55 4.34z"
+ }
+ ]
+ ];
+
+ const Wrench = [
+ [
+ "path",
+ {
+ d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"
+ }
+ ]
+ ];
+
+ const X = [
+ ["path", { d: "M18 6 6 18" }],
+ ["path", { d: "m6 6 12 12" }]
+ ];
+
+ const ZapOff = [
+ ["path", { d: "M10.513 4.856 13.12 2.17a.5.5 0 0 1 .86.46l-1.377 4.317" }],
+ ["path", { d: "M15.656 10H20a1 1 0 0 1 .78 1.63l-1.72 1.773" }],
+ [
+ "path",
+ {
+ d: "M16.273 16.273 10.88 21.83a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14H4a1 1 0 0 1-.78-1.63l4.507-4.643"
+ }
+ ],
+ ["path", { d: "m2 2 20 20" }]
+ ];
+
+ const Youtube = [
+ [
+ "path",
+ {
+ d: "M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17"
+ }
+ ],
+ ["path", { d: "m10 15 5-3-5-3z" }]
+ ];
+
+ const Zap = [
+ [
+ "path",
+ {
+ d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"
+ }
+ ]
+ ];
+
+ const ZoomIn = [
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65" }],
+ ["line", { x1: "11", x2: "11", y1: "8", y2: "14" }],
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11" }]
+ ];
+
+ const ZoomOut = [
+ ["circle", { cx: "11", cy: "11", r: "8" }],
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65" }],
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11" }]
+ ];
+
+ var iconAndAliases = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ AArrowDown: AArrowDown,
+ AArrowUp: AArrowUp,
+ ALargeSmall: ALargeSmall,
+ Accessibility: Accessibility,
+ Activity: Activity,
+ ActivitySquare: SquareActivity,
+ AirVent: AirVent,
+ Airplay: Airplay,
+ AlarmCheck: AlarmClockCheck,
+ AlarmClock: AlarmClock,
+ AlarmClockCheck: AlarmClockCheck,
+ AlarmClockMinus: AlarmClockMinus,
+ AlarmClockOff: AlarmClockOff,
+ AlarmClockPlus: AlarmClockPlus,
+ AlarmMinus: AlarmClockMinus,
+ AlarmPlus: AlarmClockPlus,
+ AlarmSmoke: AlarmSmoke,
+ Album: Album,
+ AlertCircle: CircleAlert,
+ AlertOctagon: OctagonAlert,
+ AlertTriangle: TriangleAlert,
+ AlignCenter: TextAlignCenter,
+ AlignCenterHorizontal: AlignCenterHorizontal,
+ AlignCenterVertical: AlignCenterVertical,
+ AlignEndHorizontal: AlignEndHorizontal,
+ AlignEndVertical: AlignEndVertical,
+ AlignHorizontalDistributeCenter: AlignHorizontalDistributeCenter,
+ AlignHorizontalDistributeEnd: AlignHorizontalDistributeEnd,
+ AlignHorizontalDistributeStart: AlignHorizontalDistributeStart,
+ AlignHorizontalJustifyCenter: AlignHorizontalJustifyCenter,
+ AlignHorizontalJustifyEnd: AlignHorizontalJustifyEnd,
+ AlignHorizontalJustifyStart: AlignHorizontalJustifyStart,
+ AlignHorizontalSpaceAround: AlignHorizontalSpaceAround,
+ AlignHorizontalSpaceBetween: AlignHorizontalSpaceBetween,
+ AlignJustify: TextAlignJustify,
+ AlignLeft: TextAlignStart,
+ AlignRight: TextAlignEnd,
+ AlignStartHorizontal: AlignStartHorizontal,
+ AlignStartVertical: AlignStartVertical,
+ AlignVerticalDistributeCenter: AlignVerticalDistributeCenter,
+ AlignVerticalDistributeEnd: AlignVerticalDistributeEnd,
+ AlignVerticalDistributeStart: AlignVerticalDistributeStart,
+ AlignVerticalJustifyCenter: AlignVerticalJustifyCenter,
+ AlignVerticalJustifyEnd: AlignVerticalJustifyEnd,
+ AlignVerticalJustifyStart: AlignVerticalJustifyStart,
+ AlignVerticalSpaceAround: AlignVerticalSpaceAround,
+ AlignVerticalSpaceBetween: AlignVerticalSpaceBetween,
+ Ambulance: Ambulance,
+ Ampersand: Ampersand,
+ Ampersands: Ampersands,
+ Amphora: Amphora,
+ Anchor: Anchor,
+ Angry: Angry,
+ Annoyed: Annoyed,
+ Antenna: Antenna,
+ Anvil: Anvil,
+ Aperture: Aperture,
+ AppWindow: AppWindow,
+ AppWindowMac: AppWindowMac,
+ Apple: Apple,
+ Archive: Archive,
+ ArchiveRestore: ArchiveRestore,
+ ArchiveX: ArchiveX,
+ AreaChart: ChartArea,
+ Armchair: Armchair,
+ ArrowBigDown: ArrowBigDown,
+ ArrowBigDownDash: ArrowBigDownDash,
+ ArrowBigLeft: ArrowBigLeft,
+ ArrowBigLeftDash: ArrowBigLeftDash,
+ ArrowBigRight: ArrowBigRight,
+ ArrowBigRightDash: ArrowBigRightDash,
+ ArrowBigUp: ArrowBigUp,
+ ArrowBigUpDash: ArrowBigUpDash,
+ ArrowDown: ArrowDown,
+ ArrowDown01: ArrowDown01,
+ ArrowDown10: ArrowDown10,
+ ArrowDownAZ: ArrowDownAZ,
+ ArrowDownAz: ArrowDownAZ,
+ ArrowDownCircle: CircleArrowDown,
+ ArrowDownFromLine: ArrowDownFromLine,
+ ArrowDownLeft: ArrowDownLeft,
+ ArrowDownLeftFromCircle: CircleArrowOutDownLeft,
+ ArrowDownLeftFromSquare: SquareArrowOutDownLeft,
+ ArrowDownLeftSquare: SquareArrowDownLeft,
+ ArrowDownNarrowWide: ArrowDownNarrowWide,
+ ArrowDownRight: ArrowDownRight,
+ ArrowDownRightFromCircle: CircleArrowOutDownRight,
+ ArrowDownRightFromSquare: SquareArrowOutDownRight,
+ ArrowDownRightSquare: SquareArrowDownRight,
+ ArrowDownSquare: SquareArrowDown,
+ ArrowDownToDot: ArrowDownToDot,
+ ArrowDownToLine: ArrowDownToLine,
+ ArrowDownUp: ArrowDownUp,
+ ArrowDownWideNarrow: ArrowDownWideNarrow,
+ ArrowDownZA: ArrowDownZA,
+ ArrowDownZa: ArrowDownZA,
+ ArrowLeft: ArrowLeft,
+ ArrowLeftCircle: CircleArrowLeft,
+ ArrowLeftFromLine: ArrowLeftFromLine,
+ ArrowLeftRight: ArrowLeftRight,
+ ArrowLeftSquare: SquareArrowLeft,
+ ArrowLeftToLine: ArrowLeftToLine,
+ ArrowRight: ArrowRight,
+ ArrowRightCircle: CircleArrowRight,
+ ArrowRightFromLine: ArrowRightFromLine,
+ ArrowRightLeft: ArrowRightLeft,
+ ArrowRightSquare: SquareArrowRight,
+ ArrowRightToLine: ArrowRightToLine,
+ ArrowUp: ArrowUp,
+ ArrowUp01: ArrowUp01,
+ ArrowUp10: ArrowUp10,
+ ArrowUpAZ: ArrowUpAZ,
+ ArrowUpAz: ArrowUpAZ,
+ ArrowUpCircle: CircleArrowUp,
+ ArrowUpDown: ArrowUpDown,
+ ArrowUpFromDot: ArrowUpFromDot,
+ ArrowUpFromLine: ArrowUpFromLine,
+ ArrowUpLeft: ArrowUpLeft,
+ ArrowUpLeftFromCircle: CircleArrowOutUpLeft,
+ ArrowUpLeftFromSquare: SquareArrowOutUpLeft,
+ ArrowUpLeftSquare: SquareArrowUpLeft,
+ ArrowUpNarrowWide: ArrowUpNarrowWide,
+ ArrowUpRight: ArrowUpRight,
+ ArrowUpRightFromCircle: CircleArrowOutUpRight,
+ ArrowUpRightFromSquare: SquareArrowOutUpRight,
+ ArrowUpRightSquare: SquareArrowUpRight,
+ ArrowUpSquare: SquareArrowUp,
+ ArrowUpToLine: ArrowUpToLine,
+ ArrowUpWideNarrow: ArrowUpWideNarrow,
+ ArrowUpZA: ArrowUpZA,
+ ArrowUpZa: ArrowUpZA,
+ ArrowsUpFromLine: ArrowsUpFromLine,
+ Asterisk: Asterisk,
+ AsteriskSquare: SquareAsterisk,
+ AtSign: AtSign,
+ Atom: Atom,
+ AudioLines: AudioLines,
+ AudioWaveform: AudioWaveform,
+ Award: Award,
+ Axe: Axe,
+ Axis3D: Axis3d,
+ Axis3d: Axis3d,
+ Baby: Baby,
+ Backpack: Backpack,
+ Badge: Badge,
+ BadgeAlert: BadgeAlert,
+ BadgeCent: BadgeCent,
+ BadgeCheck: BadgeCheck,
+ BadgeDollarSign: BadgeDollarSign,
+ BadgeEuro: BadgeEuro,
+ BadgeHelp: BadgeQuestionMark,
+ BadgeIndianRupee: BadgeIndianRupee,
+ BadgeInfo: BadgeInfo,
+ BadgeJapaneseYen: BadgeJapaneseYen,
+ BadgeMinus: BadgeMinus,
+ BadgePercent: BadgePercent,
+ BadgePlus: BadgePlus,
+ BadgePoundSterling: BadgePoundSterling,
+ BadgeQuestionMark: BadgeQuestionMark,
+ BadgeRussianRuble: BadgeRussianRuble,
+ BadgeSwissFranc: BadgeSwissFranc,
+ BadgeTurkishLira: BadgeTurkishLira,
+ BadgeX: BadgeX,
+ BaggageClaim: BaggageClaim,
+ Ban: Ban,
+ Banana: Banana,
+ Bandage: Bandage,
+ Banknote: Banknote,
+ BanknoteArrowDown: BanknoteArrowDown,
+ BanknoteArrowUp: BanknoteArrowUp,
+ BanknoteX: BanknoteX,
+ BarChart: ChartNoAxesColumnIncreasing,
+ BarChart2: ChartNoAxesColumn,
+ BarChart3: ChartColumn,
+ BarChart4: ChartColumnIncreasing,
+ BarChartBig: ChartColumnBig,
+ BarChartHorizontal: ChartBar,
+ BarChartHorizontalBig: ChartBarBig,
+ Barcode: Barcode,
+ Barrel: Barrel,
+ Baseline: Baseline,
+ Bath: Bath,
+ Battery: Battery,
+ BatteryCharging: BatteryCharging,
+ BatteryFull: BatteryFull,
+ BatteryLow: BatteryLow,
+ BatteryMedium: BatteryMedium,
+ BatteryPlus: BatteryPlus,
+ BatteryWarning: BatteryWarning,
+ Beaker: Beaker,
+ Bean: Bean,
+ BeanOff: BeanOff,
+ Bed: Bed,
+ BedDouble: BedDouble,
+ BedSingle: BedSingle,
+ Beef: Beef,
+ Beer: Beer,
+ BeerOff: BeerOff,
+ Bell: Bell,
+ BellDot: BellDot,
+ BellElectric: BellElectric,
+ BellMinus: BellMinus,
+ BellOff: BellOff,
+ BellPlus: BellPlus,
+ BellRing: BellRing,
+ BetweenHorizonalEnd: BetweenHorizontalEnd,
+ BetweenHorizonalStart: BetweenHorizontalStart,
+ BetweenHorizontalEnd: BetweenHorizontalEnd,
+ BetweenHorizontalStart: BetweenHorizontalStart,
+ BetweenVerticalEnd: BetweenVerticalEnd,
+ BetweenVerticalStart: BetweenVerticalStart,
+ BicepsFlexed: BicepsFlexed,
+ Bike: Bike,
+ Binary: Binary,
+ Binoculars: Binoculars,
+ Biohazard: Biohazard,
+ Bird: Bird,
+ Bitcoin: Bitcoin,
+ Blend: Blend,
+ Blinds: Blinds,
+ Blocks: Blocks,
+ Bluetooth: Bluetooth,
+ BluetoothConnected: BluetoothConnected,
+ BluetoothOff: BluetoothOff,
+ BluetoothSearching: BluetoothSearching,
+ Bold: Bold,
+ Bolt: Bolt,
+ Bomb: Bomb,
+ Bone: Bone,
+ Book: Book,
+ BookA: BookA,
+ BookAlert: BookAlert,
+ BookAudio: BookAudio,
+ BookCheck: BookCheck,
+ BookCopy: BookCopy,
+ BookDashed: BookDashed,
+ BookDown: BookDown,
+ BookHeadphones: BookHeadphones,
+ BookHeart: BookHeart,
+ BookImage: BookImage,
+ BookKey: BookKey,
+ BookLock: BookLock,
+ BookMarked: BookMarked,
+ BookMinus: BookMinus,
+ BookOpen: BookOpen,
+ BookOpenCheck: BookOpenCheck,
+ BookOpenText: BookOpenText,
+ BookPlus: BookPlus,
+ BookTemplate: BookDashed,
+ BookText: BookText,
+ BookType: BookType,
+ BookUp: BookUp,
+ BookUp2: BookUp2,
+ BookUser: BookUser,
+ BookX: BookX,
+ Bookmark: Bookmark,
+ BookmarkCheck: BookmarkCheck,
+ BookmarkMinus: BookmarkMinus,
+ BookmarkPlus: BookmarkPlus,
+ BookmarkX: BookmarkX,
+ BoomBox: BoomBox,
+ Bot: Bot,
+ BotMessageSquare: BotMessageSquare,
+ BotOff: BotOff,
+ BottleWine: BottleWine,
+ BowArrow: BowArrow,
+ Box: Box,
+ BoxSelect: SquareDashed,
+ Boxes: Boxes,
+ Braces: Braces,
+ Brackets: Brackets,
+ Brain: Brain,
+ BrainCircuit: BrainCircuit,
+ BrainCog: BrainCog,
+ BrickWall: BrickWall,
+ BrickWallFire: BrickWallFire,
+ BrickWallShield: BrickWallShield,
+ Briefcase: Briefcase,
+ BriefcaseBusiness: BriefcaseBusiness,
+ BriefcaseConveyorBelt: BriefcaseConveyorBelt,
+ BriefcaseMedical: BriefcaseMedical,
+ BringToFront: BringToFront,
+ Brush: Brush,
+ BrushCleaning: BrushCleaning,
+ Bubbles: Bubbles,
+ Bug: Bug,
+ BugOff: BugOff,
+ BugPlay: BugPlay,
+ Building: Building,
+ Building2: Building2,
+ Bus: Bus,
+ BusFront: BusFront,
+ Cable: Cable,
+ CableCar: CableCar,
+ Cake: Cake,
+ CakeSlice: CakeSlice,
+ Calculator: Calculator,
+ Calendar: Calendar,
+ Calendar1: Calendar1,
+ CalendarArrowDown: CalendarArrowDown,
+ CalendarArrowUp: CalendarArrowUp,
+ CalendarCheck: CalendarCheck,
+ CalendarCheck2: CalendarCheck2,
+ CalendarClock: CalendarClock,
+ CalendarCog: CalendarCog,
+ CalendarDays: CalendarDays,
+ CalendarFold: CalendarFold,
+ CalendarHeart: CalendarHeart,
+ CalendarMinus: CalendarMinus,
+ CalendarMinus2: CalendarMinus2,
+ CalendarOff: CalendarOff,
+ CalendarPlus: CalendarPlus,
+ CalendarPlus2: CalendarPlus2,
+ CalendarRange: CalendarRange,
+ CalendarSearch: CalendarSearch,
+ CalendarSync: CalendarSync,
+ CalendarX: CalendarX,
+ CalendarX2: CalendarX2,
+ Camera: Camera,
+ CameraOff: CameraOff,
+ CandlestickChart: ChartCandlestick,
+ Candy: Candy,
+ CandyCane: CandyCane,
+ CandyOff: CandyOff,
+ Cannabis: Cannabis,
+ Captions: Captions,
+ CaptionsOff: CaptionsOff,
+ Car: Car,
+ CarFront: CarFront,
+ CarTaxiFront: CarTaxiFront,
+ Caravan: Caravan,
+ CardSim: CardSim,
+ Carrot: Carrot,
+ CaseLower: CaseLower,
+ CaseSensitive: CaseSensitive,
+ CaseUpper: CaseUpper,
+ CassetteTape: CassetteTape,
+ Cast: Cast,
+ Castle: Castle,
+ Cat: Cat,
+ Cctv: Cctv,
+ ChartArea: ChartArea,
+ ChartBar: ChartBar,
+ ChartBarBig: ChartBarBig,
+ ChartBarDecreasing: ChartBarDecreasing,
+ ChartBarIncreasing: ChartBarIncreasing,
+ ChartBarStacked: ChartBarStacked,
+ ChartCandlestick: ChartCandlestick,
+ ChartColumn: ChartColumn,
+ ChartColumnBig: ChartColumnBig,
+ ChartColumnDecreasing: ChartColumnDecreasing,
+ ChartColumnIncreasing: ChartColumnIncreasing,
+ ChartColumnStacked: ChartColumnStacked,
+ ChartGantt: ChartGantt,
+ ChartLine: ChartLine,
+ ChartNetwork: ChartNetwork,
+ ChartNoAxesColumn: ChartNoAxesColumn,
+ ChartNoAxesColumnDecreasing: ChartNoAxesColumnDecreasing,
+ ChartNoAxesColumnIncreasing: ChartNoAxesColumnIncreasing,
+ ChartNoAxesCombined: ChartNoAxesCombined,
+ ChartNoAxesGantt: ChartNoAxesGantt,
+ ChartPie: ChartPie,
+ ChartScatter: ChartScatter,
+ ChartSpline: ChartSpline,
+ Check: Check,
+ CheckCheck: CheckCheck,
+ CheckCircle: CircleCheckBig,
+ CheckCircle2: CircleCheck,
+ CheckLine: CheckLine,
+ CheckSquare: SquareCheckBig,
+ CheckSquare2: SquareCheck,
+ ChefHat: ChefHat,
+ Cherry: Cherry,
+ ChevronDown: ChevronDown,
+ ChevronDownCircle: CircleChevronDown,
+ ChevronDownSquare: SquareChevronDown,
+ ChevronFirst: ChevronFirst,
+ ChevronLast: ChevronLast,
+ ChevronLeft: ChevronLeft,
+ ChevronLeftCircle: CircleChevronLeft,
+ ChevronLeftSquare: SquareChevronLeft,
+ ChevronRight: ChevronRight,
+ ChevronRightCircle: CircleChevronRight,
+ ChevronRightSquare: SquareChevronRight,
+ ChevronUp: ChevronUp,
+ ChevronUpCircle: CircleChevronUp,
+ ChevronUpSquare: SquareChevronUp,
+ ChevronsDown: ChevronsDown,
+ ChevronsDownUp: ChevronsDownUp,
+ ChevronsLeft: ChevronsLeft,
+ ChevronsLeftRight: ChevronsLeftRight,
+ ChevronsLeftRightEllipsis: ChevronsLeftRightEllipsis,
+ ChevronsRight: ChevronsRight,
+ ChevronsRightLeft: ChevronsRightLeft,
+ ChevronsUp: ChevronsUp,
+ ChevronsUpDown: ChevronsUpDown,
+ Chrome: Chromium,
+ Chromium: Chromium,
+ Church: Church,
+ Cigarette: Cigarette,
+ CigaretteOff: CigaretteOff,
+ Circle: Circle,
+ CircleAlert: CircleAlert,
+ CircleArrowDown: CircleArrowDown,
+ CircleArrowLeft: CircleArrowLeft,
+ CircleArrowOutDownLeft: CircleArrowOutDownLeft,
+ CircleArrowOutDownRight: CircleArrowOutDownRight,
+ CircleArrowOutUpLeft: CircleArrowOutUpLeft,
+ CircleArrowOutUpRight: CircleArrowOutUpRight,
+ CircleArrowRight: CircleArrowRight,
+ CircleArrowUp: CircleArrowUp,
+ CircleCheck: CircleCheck,
+ CircleCheckBig: CircleCheckBig,
+ CircleChevronDown: CircleChevronDown,
+ CircleChevronLeft: CircleChevronLeft,
+ CircleChevronRight: CircleChevronRight,
+ CircleChevronUp: CircleChevronUp,
+ CircleDashed: CircleDashed,
+ CircleDivide: CircleDivide,
+ CircleDollarSign: CircleDollarSign,
+ CircleDot: CircleDot,
+ CircleDotDashed: CircleDotDashed,
+ CircleEllipsis: CircleEllipsis,
+ CircleEqual: CircleEqual,
+ CircleFadingArrowUp: CircleFadingArrowUp,
+ CircleFadingPlus: CircleFadingPlus,
+ CircleGauge: CircleGauge,
+ CircleHelp: CircleQuestionMark,
+ CircleMinus: CircleMinus,
+ CircleOff: CircleOff,
+ CircleParking: CircleParking,
+ CircleParkingOff: CircleParkingOff,
+ CirclePause: CirclePause,
+ CirclePercent: CirclePercent,
+ CirclePlay: CirclePlay,
+ CirclePlus: CirclePlus,
+ CirclePoundSterling: CirclePoundSterling,
+ CirclePower: CirclePower,
+ CircleQuestionMark: CircleQuestionMark,
+ CircleSlash: CircleSlash,
+ CircleSlash2: CircleSlash2,
+ CircleSlashed: CircleSlash2,
+ CircleSmall: CircleSmall,
+ CircleStar: CircleStar,
+ CircleStop: CircleStop,
+ CircleUser: CircleUser,
+ CircleUserRound: CircleUserRound,
+ CircleX: CircleX,
+ CircuitBoard: CircuitBoard,
+ Citrus: Citrus,
+ Clapperboard: Clapperboard,
+ Clipboard: Clipboard,
+ ClipboardCheck: ClipboardCheck,
+ ClipboardClock: ClipboardClock,
+ ClipboardCopy: ClipboardCopy,
+ ClipboardEdit: ClipboardPen,
+ ClipboardList: ClipboardList,
+ ClipboardMinus: ClipboardMinus,
+ ClipboardPaste: ClipboardPaste,
+ ClipboardPen: ClipboardPen,
+ ClipboardPenLine: ClipboardPenLine,
+ ClipboardPlus: ClipboardPlus,
+ ClipboardSignature: ClipboardPenLine,
+ ClipboardType: ClipboardType,
+ ClipboardX: ClipboardX,
+ Clock: Clock,
+ Clock1: Clock1,
+ Clock10: Clock10,
+ Clock11: Clock11,
+ Clock12: Clock12,
+ Clock2: Clock2,
+ Clock3: Clock3,
+ Clock4: Clock4,
+ Clock5: Clock5,
+ Clock6: Clock6,
+ Clock7: Clock7,
+ Clock8: Clock8,
+ Clock9: Clock9,
+ ClockAlert: ClockAlert,
+ ClockArrowDown: ClockArrowDown,
+ ClockArrowUp: ClockArrowUp,
+ ClockFading: ClockFading,
+ ClockPlus: ClockPlus,
+ ClosedCaption: ClosedCaption,
+ Cloud: Cloud,
+ CloudAlert: CloudAlert,
+ CloudCheck: CloudCheck,
+ CloudCog: CloudCog,
+ CloudDownload: CloudDownload,
+ CloudDrizzle: CloudDrizzle,
+ CloudFog: CloudFog,
+ CloudHail: CloudHail,
+ CloudLightning: CloudLightning,
+ CloudMoon: CloudMoon,
+ CloudMoonRain: CloudMoonRain,
+ CloudOff: CloudOff,
+ CloudRain: CloudRain,
+ CloudRainWind: CloudRainWind,
+ CloudSnow: CloudSnow,
+ CloudSun: CloudSun,
+ CloudSunRain: CloudSunRain,
+ CloudUpload: CloudUpload,
+ Cloudy: Cloudy,
+ Clover: Clover,
+ Club: Club,
+ Code: Code,
+ Code2: CodeXml,
+ CodeSquare: SquareCode,
+ CodeXml: CodeXml,
+ Codepen: Codepen,
+ Codesandbox: Codesandbox,
+ Coffee: Coffee,
+ Cog: Cog,
+ Coins: Coins,
+ Columns: Columns2,
+ Columns2: Columns2,
+ Columns3: Columns3,
+ Columns3Cog: Columns3Cog,
+ Columns4: Columns4,
+ ColumnsSettings: Columns3Cog,
+ Combine: Combine,
+ Command: Command,
+ Compass: Compass,
+ Component: Component,
+ Computer: Computer,
+ ConciergeBell: ConciergeBell,
+ Cone: Cone,
+ Construction: Construction,
+ Contact: Contact,
+ Contact2: ContactRound,
+ ContactRound: ContactRound,
+ Container: Container,
+ Contrast: Contrast,
+ Cookie: Cookie,
+ CookingPot: CookingPot,
+ Copy: Copy,
+ CopyCheck: CopyCheck,
+ CopyMinus: CopyMinus,
+ CopyPlus: CopyPlus,
+ CopySlash: CopySlash,
+ CopyX: CopyX,
+ Copyleft: Copyleft,
+ Copyright: Copyright,
+ CornerDownLeft: CornerDownLeft,
+ CornerDownRight: CornerDownRight,
+ CornerLeftDown: CornerLeftDown,
+ CornerLeftUp: CornerLeftUp,
+ CornerRightDown: CornerRightDown,
+ CornerRightUp: CornerRightUp,
+ CornerUpLeft: CornerUpLeft,
+ CornerUpRight: CornerUpRight,
+ Cpu: Cpu,
+ CreativeCommons: CreativeCommons,
+ CreditCard: CreditCard,
+ Croissant: Croissant,
+ Crop: Crop,
+ Cross: Cross,
+ Crosshair: Crosshair,
+ Crown: Crown,
+ Cuboid: Cuboid,
+ CupSoda: CupSoda,
+ CurlyBraces: Braces,
+ Currency: Currency,
+ Cylinder: Cylinder,
+ Dam: Dam,
+ Database: Database,
+ DatabaseBackup: DatabaseBackup,
+ DatabaseZap: DatabaseZap,
+ DecimalsArrowLeft: DecimalsArrowLeft,
+ DecimalsArrowRight: DecimalsArrowRight,
+ Delete: Delete,
+ Dessert: Dessert,
+ Diameter: Diameter,
+ Diamond: Diamond,
+ DiamondMinus: DiamondMinus,
+ DiamondPercent: DiamondPercent,
+ DiamondPlus: DiamondPlus,
+ Dice1: Dice1,
+ Dice2: Dice2,
+ Dice3: Dice3,
+ Dice4: Dice4,
+ Dice5: Dice5,
+ Dice6: Dice6,
+ Dices: Dices,
+ Diff: Diff,
+ Disc: Disc,
+ Disc2: Disc2,
+ Disc3: Disc3,
+ DiscAlbum: DiscAlbum,
+ Divide: Divide,
+ DivideCircle: CircleDivide,
+ DivideSquare: SquareDivide,
+ Dna: Dna,
+ DnaOff: DnaOff,
+ Dock: Dock,
+ Dog: Dog,
+ DollarSign: DollarSign,
+ Donut: Donut,
+ DoorClosed: DoorClosed,
+ DoorClosedLocked: DoorClosedLocked,
+ DoorOpen: DoorOpen,
+ Dot: Dot,
+ DotSquare: SquareDot,
+ Download: Download,
+ DownloadCloud: CloudDownload,
+ DraftingCompass: DraftingCompass,
+ Drama: Drama,
+ Dribbble: Dribbble,
+ Drill: Drill,
+ Drone: Drone,
+ Droplet: Droplet,
+ DropletOff: DropletOff,
+ Droplets: Droplets,
+ Drum: Drum,
+ Drumstick: Drumstick,
+ Dumbbell: Dumbbell,
+ Ear: Ear,
+ EarOff: EarOff,
+ Earth: Earth,
+ EarthLock: EarthLock,
+ Eclipse: Eclipse,
+ Edit: SquarePen,
+ Edit2: Pen,
+ Edit3: PenLine,
+ Egg: Egg,
+ EggFried: EggFried,
+ EggOff: EggOff,
+ Ellipsis: Ellipsis,
+ EllipsisVertical: EllipsisVertical,
+ Equal: Equal,
+ EqualApproximately: EqualApproximately,
+ EqualNot: EqualNot,
+ EqualSquare: SquareEqual,
+ Eraser: Eraser,
+ EthernetPort: EthernetPort,
+ Euro: Euro,
+ EvCharger: EvCharger,
+ Expand: Expand,
+ ExternalLink: ExternalLink,
+ Eye: Eye,
+ EyeClosed: EyeClosed,
+ EyeOff: EyeOff,
+ Facebook: Facebook,
+ Factory: Factory,
+ Fan: Fan,
+ FastForward: FastForward,
+ Feather: Feather,
+ Fence: Fence,
+ FerrisWheel: FerrisWheel,
+ Figma: Figma,
+ File: File,
+ FileArchive: FileArchive,
+ FileAudio: FileAudio,
+ FileAudio2: FileAudio2,
+ FileAxis3D: FileAxis3d,
+ FileAxis3d: FileAxis3d,
+ FileBadge: FileBadge,
+ FileBadge2: FileBadge2,
+ FileBarChart: FileChartColumnIncreasing,
+ FileBarChart2: FileChartColumn,
+ FileBox: FileBox,
+ FileChartColumn: FileChartColumn,
+ FileChartColumnIncreasing: FileChartColumnIncreasing,
+ FileChartLine: FileChartLine,
+ FileChartPie: FileChartPie,
+ FileCheck: FileCheck,
+ FileCheck2: FileCheck2,
+ FileClock: FileClock,
+ FileCode: FileCode,
+ FileCode2: FileCode2,
+ FileCog: FileCog,
+ FileCog2: FileCog,
+ FileDiff: FileDiff,
+ FileDigit: FileDigit,
+ FileDown: FileDown,
+ FileEdit: FilePen,
+ FileHeart: FileHeart,
+ FileImage: FileImage,
+ FileInput: FileInput,
+ FileJson: FileJson,
+ FileJson2: FileJson2,
+ FileKey: FileKey,
+ FileKey2: FileKey2,
+ FileLineChart: FileChartLine,
+ FileLock: FileLock,
+ FileLock2: FileLock2,
+ FileMinus: FileMinus,
+ FileMinus2: FileMinus2,
+ FileMusic: FileMusic,
+ FileOutput: FileOutput,
+ FilePen: FilePen,
+ FilePenLine: FilePenLine,
+ FilePieChart: FileChartPie,
+ FilePlay: FilePlay,
+ FilePlus: FilePlus,
+ FilePlus2: FilePlus2,
+ FileQuestion: FileQuestionMark,
+ FileQuestionMark: FileQuestionMark,
+ FileScan: FileScan,
+ FileSearch: FileSearch,
+ FileSearch2: FileSearch2,
+ FileSignature: FilePenLine,
+ FileSliders: FileSliders,
+ FileSpreadsheet: FileSpreadsheet,
+ FileStack: FileStack,
+ FileSymlink: FileSymlink,
+ FileTerminal: FileTerminal,
+ FileText: FileText,
+ FileType: FileType,
+ FileType2: FileType2,
+ FileUp: FileUp,
+ FileUser: FileUser,
+ FileVideo: FilePlay,
+ FileVideo2: FileVideoCamera,
+ FileVideoCamera: FileVideoCamera,
+ FileVolume: FileVolume,
+ FileVolume2: FileVolume2,
+ FileWarning: FileWarning,
+ FileX: FileX,
+ FileX2: FileX2,
+ Files: Files,
+ Film: Film,
+ Filter: Funnel,
+ FilterX: FunnelX,
+ Fingerprint: Fingerprint,
+ FireExtinguisher: FireExtinguisher,
+ Fish: Fish,
+ FishOff: FishOff,
+ FishSymbol: FishSymbol,
+ Flag: Flag,
+ FlagOff: FlagOff,
+ FlagTriangleLeft: FlagTriangleLeft,
+ FlagTriangleRight: FlagTriangleRight,
+ Flame: Flame,
+ FlameKindling: FlameKindling,
+ Flashlight: Flashlight,
+ FlashlightOff: FlashlightOff,
+ FlaskConical: FlaskConical,
+ FlaskConicalOff: FlaskConicalOff,
+ FlaskRound: FlaskRound,
+ FlipHorizontal: FlipHorizontal,
+ FlipHorizontal2: FlipHorizontal2,
+ FlipVertical: FlipVertical,
+ FlipVertical2: FlipVertical2,
+ Flower: Flower,
+ Flower2: Flower2,
+ Focus: Focus,
+ FoldHorizontal: FoldHorizontal,
+ FoldVertical: FoldVertical,
+ Folder: Folder,
+ FolderArchive: FolderArchive,
+ FolderCheck: FolderCheck,
+ FolderClock: FolderClock,
+ FolderClosed: FolderClosed,
+ FolderCode: FolderCode,
+ FolderCog: FolderCog,
+ FolderCog2: FolderCog,
+ FolderDot: FolderDot,
+ FolderDown: FolderDown,
+ FolderEdit: FolderPen,
+ FolderGit: FolderGit,
+ FolderGit2: FolderGit2,
+ FolderHeart: FolderHeart,
+ FolderInput: FolderInput,
+ FolderKanban: FolderKanban,
+ FolderKey: FolderKey,
+ FolderLock: FolderLock,
+ FolderMinus: FolderMinus,
+ FolderOpen: FolderOpen,
+ FolderOpenDot: FolderOpenDot,
+ FolderOutput: FolderOutput,
+ FolderPen: FolderPen,
+ FolderPlus: FolderPlus,
+ FolderRoot: FolderRoot,
+ FolderSearch: FolderSearch,
+ FolderSearch2: FolderSearch2,
+ FolderSymlink: FolderSymlink,
+ FolderSync: FolderSync,
+ FolderTree: FolderTree,
+ FolderUp: FolderUp,
+ FolderX: FolderX,
+ Folders: Folders,
+ Footprints: Footprints,
+ ForkKnife: Utensils,
+ ForkKnifeCrossed: UtensilsCrossed,
+ Forklift: Forklift,
+ FormInput: RectangleEllipsis,
+ Forward: Forward,
+ Frame: Frame,
+ Framer: Framer,
+ Frown: Frown,
+ Fuel: Fuel,
+ Fullscreen: Fullscreen,
+ FunctionSquare: SquareFunction,
+ Funnel: Funnel,
+ FunnelPlus: FunnelPlus,
+ FunnelX: FunnelX,
+ GalleryHorizontal: GalleryHorizontal,
+ GalleryHorizontalEnd: GalleryHorizontalEnd,
+ GalleryThumbnails: GalleryThumbnails,
+ GalleryVertical: GalleryVertical,
+ GalleryVerticalEnd: GalleryVerticalEnd,
+ Gamepad: Gamepad,
+ Gamepad2: Gamepad2,
+ GanttChart: ChartNoAxesGantt,
+ GanttChartSquare: SquareChartGantt,
+ Gauge: Gauge,
+ GaugeCircle: CircleGauge,
+ Gavel: Gavel,
+ Gem: Gem,
+ GeorgianLari: GeorgianLari,
+ Ghost: Ghost,
+ Gift: Gift,
+ GitBranch: GitBranch,
+ GitBranchPlus: GitBranchPlus,
+ GitCommit: GitCommitHorizontal,
+ GitCommitHorizontal: GitCommitHorizontal,
+ GitCommitVertical: GitCommitVertical,
+ GitCompare: GitCompare,
+ GitCompareArrows: GitCompareArrows,
+ GitFork: GitFork,
+ GitGraph: GitGraph,
+ GitMerge: GitMerge,
+ GitPullRequest: GitPullRequest,
+ GitPullRequestArrow: GitPullRequestArrow,
+ GitPullRequestClosed: GitPullRequestClosed,
+ GitPullRequestCreate: GitPullRequestCreate,
+ GitPullRequestCreateArrow: GitPullRequestCreateArrow,
+ GitPullRequestDraft: GitPullRequestDraft,
+ Github: Github,
+ Gitlab: Gitlab,
+ GlassWater: GlassWater,
+ Glasses: Glasses,
+ Globe: Globe,
+ Globe2: Earth,
+ GlobeLock: GlobeLock,
+ Goal: Goal,
+ Gpu: Gpu,
+ Grab: HandGrab,
+ GraduationCap: GraduationCap,
+ Grape: Grape,
+ Grid: Grid3x3,
+ Grid2X2: Grid2x2,
+ Grid2X2Check: Grid2x2Check,
+ Grid2X2Plus: Grid2x2Plus,
+ Grid2X2X: Grid2x2X,
+ Grid2x2: Grid2x2,
+ Grid2x2Check: Grid2x2Check,
+ Grid2x2Plus: Grid2x2Plus,
+ Grid2x2X: Grid2x2X,
+ Grid3X3: Grid3x3,
+ Grid3x2: Grid3x2,
+ Grid3x3: Grid3x3,
+ Grip: Grip,
+ GripHorizontal: GripHorizontal,
+ GripVertical: GripVertical,
+ Group: Group,
+ Guitar: Guitar,
+ Ham: Ham,
+ Hamburger: Hamburger,
+ Hammer: Hammer,
+ Hand: Hand,
+ HandCoins: HandCoins,
+ HandFist: HandFist,
+ HandGrab: HandGrab,
+ HandHeart: HandHeart,
+ HandHelping: HandHelping,
+ HandMetal: HandMetal,
+ HandPlatter: HandPlatter,
+ Handbag: Handbag,
+ Handshake: Handshake,
+ HardDrive: HardDrive,
+ HardDriveDownload: HardDriveDownload,
+ HardDriveUpload: HardDriveUpload,
+ HardHat: HardHat,
+ Hash: Hash,
+ HatGlasses: HatGlasses,
+ Haze: Haze,
+ HdmiPort: HdmiPort,
+ Heading: Heading,
+ Heading1: Heading1,
+ Heading2: Heading2,
+ Heading3: Heading3,
+ Heading4: Heading4,
+ Heading5: Heading5,
+ Heading6: Heading6,
+ HeadphoneOff: HeadphoneOff,
+ Headphones: Headphones,
+ Headset: Headset,
+ Heart: Heart,
+ HeartCrack: HeartCrack,
+ HeartHandshake: HeartHandshake,
+ HeartMinus: HeartMinus,
+ HeartOff: HeartOff,
+ HeartPlus: HeartPlus,
+ HeartPulse: HeartPulse,
+ Heater: Heater,
+ HelpCircle: CircleQuestionMark,
+ HelpingHand: HandHelping,
+ Hexagon: Hexagon,
+ Highlighter: Highlighter,
+ History: History,
+ Home: House,
+ Hop: Hop,
+ HopOff: HopOff,
+ Hospital: Hospital,
+ Hotel: Hotel,
+ Hourglass: Hourglass,
+ House: House,
+ HouseHeart: HouseHeart,
+ HousePlug: HousePlug,
+ HousePlus: HousePlus,
+ HouseWifi: HouseWifi,
+ IceCream: IceCreamCone,
+ IceCream2: IceCreamBowl,
+ IceCreamBowl: IceCreamBowl,
+ IceCreamCone: IceCreamCone,
+ IdCard: IdCard,
+ IdCardLanyard: IdCardLanyard,
+ Image: Image,
+ ImageDown: ImageDown,
+ ImageMinus: ImageMinus,
+ ImageOff: ImageOff,
+ ImagePlay: ImagePlay,
+ ImagePlus: ImagePlus,
+ ImageUp: ImageUp,
+ ImageUpscale: ImageUpscale,
+ Images: Images,
+ Import: Import,
+ Inbox: Inbox,
+ Indent: ListIndentIncrease,
+ IndentDecrease: ListIndentDecrease,
+ IndentIncrease: ListIndentIncrease,
+ IndianRupee: IndianRupee,
+ Infinity: Infinity,
+ Info: Info,
+ Inspect: SquareMousePointer,
+ InspectionPanel: InspectionPanel,
+ Instagram: Instagram,
+ Italic: Italic,
+ IterationCcw: IterationCcw,
+ IterationCw: IterationCw,
+ JapaneseYen: JapaneseYen,
+ Joystick: Joystick,
+ Kanban: Kanban,
+ KanbanSquare: SquareKanban,
+ KanbanSquareDashed: SquareDashedKanban,
+ Kayak: Kayak,
+ Key: Key,
+ KeyRound: KeyRound,
+ KeySquare: KeySquare,
+ Keyboard: Keyboard,
+ KeyboardMusic: KeyboardMusic,
+ KeyboardOff: KeyboardOff,
+ Lamp: Lamp,
+ LampCeiling: LampCeiling,
+ LampDesk: LampDesk,
+ LampFloor: LampFloor,
+ LampWallDown: LampWallDown,
+ LampWallUp: LampWallUp,
+ LandPlot: LandPlot,
+ Landmark: Landmark,
+ Languages: Languages,
+ Laptop: Laptop,
+ Laptop2: LaptopMinimal,
+ LaptopMinimal: LaptopMinimal,
+ LaptopMinimalCheck: LaptopMinimalCheck,
+ Lasso: Lasso,
+ LassoSelect: LassoSelect,
+ Laugh: Laugh,
+ Layers: Layers,
+ Layers2: Layers2,
+ Layers3: Layers,
+ Layout: PanelsTopLeft,
+ LayoutDashboard: LayoutDashboard,
+ LayoutGrid: LayoutGrid,
+ LayoutList: LayoutList,
+ LayoutPanelLeft: LayoutPanelLeft,
+ LayoutPanelTop: LayoutPanelTop,
+ LayoutTemplate: LayoutTemplate,
+ Leaf: Leaf,
+ LeafyGreen: LeafyGreen,
+ Lectern: Lectern,
+ LetterText: TextInitial,
+ Library: Library,
+ LibraryBig: LibraryBig,
+ LibrarySquare: SquareLibrary,
+ LifeBuoy: LifeBuoy,
+ Ligature: Ligature,
+ Lightbulb: Lightbulb,
+ LightbulbOff: LightbulbOff,
+ LineChart: ChartLine,
+ LineSquiggle: LineSquiggle,
+ Link: Link,
+ Link2: Link2,
+ Link2Off: Link2Off,
+ Linkedin: Linkedin,
+ List: List,
+ ListCheck: ListCheck,
+ ListChecks: ListChecks,
+ ListChevronsDownUp: ListChevronsDownUp,
+ ListChevronsUpDown: ListChevronsUpDown,
+ ListCollapse: ListCollapse,
+ ListEnd: ListEnd,
+ ListFilter: ListFilter,
+ ListFilterPlus: ListFilterPlus,
+ ListIndentDecrease: ListIndentDecrease,
+ ListIndentIncrease: ListIndentIncrease,
+ ListMinus: ListMinus,
+ ListMusic: ListMusic,
+ ListOrdered: ListOrdered,
+ ListPlus: ListPlus,
+ ListRestart: ListRestart,
+ ListStart: ListStart,
+ ListTodo: ListTodo,
+ ListTree: ListTree,
+ ListVideo: ListVideo,
+ ListX: ListX,
+ Loader: Loader,
+ Loader2: LoaderCircle,
+ LoaderCircle: LoaderCircle,
+ LoaderPinwheel: LoaderPinwheel,
+ Locate: Locate,
+ LocateFixed: LocateFixed,
+ LocateOff: LocateOff,
+ LocationEdit: MapPinPen,
+ Lock: Lock,
+ LockKeyhole: LockKeyhole,
+ LockKeyholeOpen: LockKeyholeOpen,
+ LockOpen: LockOpen,
+ LogIn: LogIn,
+ LogOut: LogOut,
+ Logs: Logs,
+ Lollipop: Lollipop,
+ Luggage: Luggage,
+ MSquare: SquareM,
+ Magnet: Magnet,
+ Mail: Mail,
+ MailCheck: MailCheck,
+ MailMinus: MailMinus,
+ MailOpen: MailOpen,
+ MailPlus: MailPlus,
+ MailQuestion: MailQuestionMark,
+ MailQuestionMark: MailQuestionMark,
+ MailSearch: MailSearch,
+ MailWarning: MailWarning,
+ MailX: MailX,
+ Mailbox: Mailbox,
+ Mails: Mails,
+ Map: Map,
+ MapMinus: MapMinus,
+ MapPin: MapPin,
+ MapPinCheck: MapPinCheck,
+ MapPinCheckInside: MapPinCheckInside,
+ MapPinHouse: MapPinHouse,
+ MapPinMinus: MapPinMinus,
+ MapPinMinusInside: MapPinMinusInside,
+ MapPinOff: MapPinOff,
+ MapPinPen: MapPinPen,
+ MapPinPlus: MapPinPlus,
+ MapPinPlusInside: MapPinPlusInside,
+ MapPinX: MapPinX,
+ MapPinXInside: MapPinXInside,
+ MapPinned: MapPinned,
+ MapPlus: MapPlus,
+ Mars: Mars,
+ MarsStroke: MarsStroke,
+ Martini: Martini,
+ Maximize: Maximize,
+ Maximize2: Maximize2,
+ Medal: Medal,
+ Megaphone: Megaphone,
+ MegaphoneOff: MegaphoneOff,
+ Meh: Meh,
+ MemoryStick: MemoryStick,
+ Menu: Menu,
+ MenuSquare: SquareMenu,
+ Merge: Merge,
+ MessageCircle: MessageCircle,
+ MessageCircleCode: MessageCircleCode,
+ MessageCircleDashed: MessageCircleDashed,
+ MessageCircleHeart: MessageCircleHeart,
+ MessageCircleMore: MessageCircleMore,
+ MessageCircleOff: MessageCircleOff,
+ MessageCirclePlus: MessageCirclePlus,
+ MessageCircleQuestion: MessageCircleQuestionMark,
+ MessageCircleQuestionMark: MessageCircleQuestionMark,
+ MessageCircleReply: MessageCircleReply,
+ MessageCircleWarning: MessageCircleWarning,
+ MessageCircleX: MessageCircleX,
+ MessageSquare: MessageSquare,
+ MessageSquareCode: MessageSquareCode,
+ MessageSquareDashed: MessageSquareDashed,
+ MessageSquareDiff: MessageSquareDiff,
+ MessageSquareDot: MessageSquareDot,
+ MessageSquareHeart: MessageSquareHeart,
+ MessageSquareLock: MessageSquareLock,
+ MessageSquareMore: MessageSquareMore,
+ MessageSquareOff: MessageSquareOff,
+ MessageSquarePlus: MessageSquarePlus,
+ MessageSquareQuote: MessageSquareQuote,
+ MessageSquareReply: MessageSquareReply,
+ MessageSquareShare: MessageSquareShare,
+ MessageSquareText: MessageSquareText,
+ MessageSquareWarning: MessageSquareWarning,
+ MessageSquareX: MessageSquareX,
+ MessagesSquare: MessagesSquare,
+ Mic: Mic,
+ Mic2: MicVocal,
+ MicOff: MicOff,
+ MicVocal: MicVocal,
+ Microchip: Microchip,
+ Microscope: Microscope,
+ Microwave: Microwave,
+ Milestone: Milestone,
+ Milk: Milk,
+ MilkOff: MilkOff,
+ Minimize: Minimize,
+ Minimize2: Minimize2,
+ Minus: Minus,
+ MinusCircle: CircleMinus,
+ MinusSquare: SquareMinus,
+ Monitor: Monitor,
+ MonitorCheck: MonitorCheck,
+ MonitorCloud: MonitorCloud,
+ MonitorCog: MonitorCog,
+ MonitorDot: MonitorDot,
+ MonitorDown: MonitorDown,
+ MonitorOff: MonitorOff,
+ MonitorPause: MonitorPause,
+ MonitorPlay: MonitorPlay,
+ MonitorSmartphone: MonitorSmartphone,
+ MonitorSpeaker: MonitorSpeaker,
+ MonitorStop: MonitorStop,
+ MonitorUp: MonitorUp,
+ MonitorX: MonitorX,
+ Moon: Moon,
+ MoonStar: MoonStar,
+ MoreHorizontal: Ellipsis,
+ MoreVertical: EllipsisVertical,
+ Motorbike: Motorbike,
+ Mountain: Mountain,
+ MountainSnow: MountainSnow,
+ Mouse: Mouse,
+ MouseOff: MouseOff,
+ MousePointer: MousePointer,
+ MousePointer2: MousePointer2,
+ MousePointerBan: MousePointerBan,
+ MousePointerClick: MousePointerClick,
+ MousePointerSquareDashed: SquareDashedMousePointer,
+ Move: Move,
+ Move3D: Move3d,
+ Move3d: Move3d,
+ MoveDiagonal: MoveDiagonal,
+ MoveDiagonal2: MoveDiagonal2,
+ MoveDown: MoveDown,
+ MoveDownLeft: MoveDownLeft,
+ MoveDownRight: MoveDownRight,
+ MoveHorizontal: MoveHorizontal,
+ MoveLeft: MoveLeft,
+ MoveRight: MoveRight,
+ MoveUp: MoveUp,
+ MoveUpLeft: MoveUpLeft,
+ MoveUpRight: MoveUpRight,
+ MoveVertical: MoveVertical,
+ Music: Music,
+ Music2: Music2,
+ Music3: Music3,
+ Music4: Music4,
+ Navigation: Navigation,
+ Navigation2: Navigation2,
+ Navigation2Off: Navigation2Off,
+ NavigationOff: NavigationOff,
+ Network: Network,
+ Newspaper: Newspaper,
+ Nfc: Nfc,
+ NonBinary: NonBinary,
+ Notebook: Notebook,
+ NotebookPen: NotebookPen,
+ NotebookTabs: NotebookTabs,
+ NotebookText: NotebookText,
+ NotepadText: NotepadText,
+ NotepadTextDashed: NotepadTextDashed,
+ Nut: Nut,
+ NutOff: NutOff,
+ Octagon: Octagon,
+ OctagonAlert: OctagonAlert,
+ OctagonMinus: OctagonMinus,
+ OctagonPause: OctagonPause,
+ OctagonX: OctagonX,
+ Omega: Omega,
+ Option: Option,
+ Orbit: Orbit,
+ Origami: Origami,
+ Outdent: ListIndentDecrease,
+ Package: Package,
+ Package2: Package2,
+ PackageCheck: PackageCheck,
+ PackageMinus: PackageMinus,
+ PackageOpen: PackageOpen,
+ PackagePlus: PackagePlus,
+ PackageSearch: PackageSearch,
+ PackageX: PackageX,
+ PaintBucket: PaintBucket,
+ PaintRoller: PaintRoller,
+ Paintbrush: Paintbrush,
+ Paintbrush2: PaintbrushVertical,
+ PaintbrushVertical: PaintbrushVertical,
+ Palette: Palette,
+ Palmtree: TreePalm,
+ Panda: Panda,
+ PanelBottom: PanelBottom,
+ PanelBottomClose: PanelBottomClose,
+ PanelBottomDashed: PanelBottomDashed,
+ PanelBottomInactive: PanelBottomDashed,
+ PanelBottomOpen: PanelBottomOpen,
+ PanelLeft: PanelLeft,
+ PanelLeftClose: PanelLeftClose,
+ PanelLeftDashed: PanelLeftDashed,
+ PanelLeftInactive: PanelLeftDashed,
+ PanelLeftOpen: PanelLeftOpen,
+ PanelLeftRightDashed: PanelLeftRightDashed,
+ PanelRight: PanelRight,
+ PanelRightClose: PanelRightClose,
+ PanelRightDashed: PanelRightDashed,
+ PanelRightInactive: PanelRightDashed,
+ PanelRightOpen: PanelRightOpen,
+ PanelTop: PanelTop,
+ PanelTopBottomDashed: PanelTopBottomDashed,
+ PanelTopClose: PanelTopClose,
+ PanelTopDashed: PanelTopDashed,
+ PanelTopInactive: PanelTopDashed,
+ PanelTopOpen: PanelTopOpen,
+ PanelsLeftBottom: PanelsLeftBottom,
+ PanelsLeftRight: Columns3,
+ PanelsRightBottom: PanelsRightBottom,
+ PanelsTopBottom: Rows3,
+ PanelsTopLeft: PanelsTopLeft,
+ Paperclip: Paperclip,
+ Parentheses: Parentheses,
+ ParkingCircle: CircleParking,
+ ParkingCircleOff: CircleParkingOff,
+ ParkingMeter: ParkingMeter,
+ ParkingSquare: SquareParking,
+ ParkingSquareOff: SquareParkingOff,
+ PartyPopper: PartyPopper,
+ Pause: Pause,
+ PauseCircle: CirclePause,
+ PauseOctagon: OctagonPause,
+ PawPrint: PawPrint,
+ PcCase: PcCase,
+ Pen: Pen,
+ PenBox: SquarePen,
+ PenLine: PenLine,
+ PenOff: PenOff,
+ PenSquare: SquarePen,
+ PenTool: PenTool,
+ Pencil: Pencil,
+ PencilLine: PencilLine,
+ PencilOff: PencilOff,
+ PencilRuler: PencilRuler,
+ Pentagon: Pentagon,
+ Percent: Percent,
+ PercentCircle: CirclePercent,
+ PercentDiamond: DiamondPercent,
+ PercentSquare: SquarePercent,
+ PersonStanding: PersonStanding,
+ PhilippinePeso: PhilippinePeso,
+ Phone: Phone,
+ PhoneCall: PhoneCall,
+ PhoneForwarded: PhoneForwarded,
+ PhoneIncoming: PhoneIncoming,
+ PhoneMissed: PhoneMissed,
+ PhoneOff: PhoneOff,
+ PhoneOutgoing: PhoneOutgoing,
+ Pi: Pi,
+ PiSquare: SquarePi,
+ Piano: Piano,
+ Pickaxe: Pickaxe,
+ PictureInPicture: PictureInPicture,
+ PictureInPicture2: PictureInPicture2,
+ PieChart: ChartPie,
+ PiggyBank: PiggyBank,
+ Pilcrow: Pilcrow,
+ PilcrowLeft: PilcrowLeft,
+ PilcrowRight: PilcrowRight,
+ PilcrowSquare: SquarePilcrow,
+ Pill: Pill,
+ PillBottle: PillBottle,
+ Pin: Pin,
+ PinOff: PinOff,
+ Pipette: Pipette,
+ Pizza: Pizza,
+ Plane: Plane,
+ PlaneLanding: PlaneLanding,
+ PlaneTakeoff: PlaneTakeoff,
+ Play: Play,
+ PlayCircle: CirclePlay,
+ PlaySquare: SquarePlay,
+ Plug: Plug,
+ Plug2: Plug2,
+ PlugZap: PlugZap,
+ PlugZap2: PlugZap,
+ Plus: Plus,
+ PlusCircle: CirclePlus,
+ PlusSquare: SquarePlus,
+ Pocket: Pocket,
+ PocketKnife: PocketKnife,
+ Podcast: Podcast,
+ Pointer: Pointer,
+ PointerOff: PointerOff,
+ Popcorn: Popcorn,
+ Popsicle: Popsicle,
+ PoundSterling: PoundSterling,
+ Power: Power,
+ PowerCircle: CirclePower,
+ PowerOff: PowerOff,
+ PowerSquare: SquarePower,
+ Presentation: Presentation,
+ Printer: Printer,
+ PrinterCheck: PrinterCheck,
+ Projector: Projector,
+ Proportions: Proportions,
+ Puzzle: Puzzle,
+ Pyramid: Pyramid,
+ QrCode: QrCode,
+ Quote: Quote,
+ Rabbit: Rabbit,
+ Radar: Radar,
+ Radiation: Radiation,
+ Radical: Radical,
+ Radio: Radio,
+ RadioReceiver: RadioReceiver,
+ RadioTower: RadioTower,
+ Radius: Radius,
+ RailSymbol: RailSymbol,
+ Rainbow: Rainbow,
+ Rat: Rat,
+ Ratio: Ratio,
+ Receipt: Receipt,
+ ReceiptCent: ReceiptCent,
+ ReceiptEuro: ReceiptEuro,
+ ReceiptIndianRupee: ReceiptIndianRupee,
+ ReceiptJapaneseYen: ReceiptJapaneseYen,
+ ReceiptPoundSterling: ReceiptPoundSterling,
+ ReceiptRussianRuble: ReceiptRussianRuble,
+ ReceiptSwissFranc: ReceiptSwissFranc,
+ ReceiptText: ReceiptText,
+ ReceiptTurkishLira: ReceiptTurkishLira,
+ RectangleCircle: RectangleCircle,
+ RectangleEllipsis: RectangleEllipsis,
+ RectangleGoggles: RectangleGoggles,
+ RectangleHorizontal: RectangleHorizontal,
+ RectangleVertical: RectangleVertical,
+ Recycle: Recycle,
+ Redo: Redo,
+ Redo2: Redo2,
+ RedoDot: RedoDot,
+ RefreshCcw: RefreshCcw,
+ RefreshCcwDot: RefreshCcwDot,
+ RefreshCw: RefreshCw,
+ RefreshCwOff: RefreshCwOff,
+ Refrigerator: Refrigerator,
+ Regex: Regex,
+ RemoveFormatting: RemoveFormatting,
+ Repeat: Repeat,
+ Repeat1: Repeat1,
+ Repeat2: Repeat2,
+ Replace: Replace,
+ ReplaceAll: ReplaceAll,
+ Reply: Reply,
+ ReplyAll: ReplyAll,
+ Rewind: Rewind,
+ Ribbon: Ribbon,
+ Rocket: Rocket,
+ RockingChair: RockingChair,
+ RollerCoaster: RollerCoaster,
+ Rose: Rose,
+ Rotate3D: Rotate3d,
+ Rotate3d: Rotate3d,
+ RotateCcw: RotateCcw,
+ RotateCcwKey: RotateCcwKey,
+ RotateCcwSquare: RotateCcwSquare,
+ RotateCw: RotateCw,
+ RotateCwSquare: RotateCwSquare,
+ Route: Route,
+ RouteOff: RouteOff,
+ Router: Router,
+ Rows: Rows2,
+ Rows2: Rows2,
+ Rows3: Rows3,
+ Rows4: Rows4,
+ Rss: Rss,
+ Ruler: Ruler,
+ RulerDimensionLine: RulerDimensionLine,
+ RussianRuble: RussianRuble,
+ Sailboat: Sailboat,
+ Salad: Salad,
+ Sandwich: Sandwich,
+ Satellite: Satellite,
+ SatelliteDish: SatelliteDish,
+ SaudiRiyal: SaudiRiyal,
+ Save: Save,
+ SaveAll: SaveAll,
+ SaveOff: SaveOff,
+ Scale: Scale,
+ Scale3D: Scale3d,
+ Scale3d: Scale3d,
+ Scaling: Scaling,
+ Scan: Scan,
+ ScanBarcode: ScanBarcode,
+ ScanEye: ScanEye,
+ ScanFace: ScanFace,
+ ScanHeart: ScanHeart,
+ ScanLine: ScanLine,
+ ScanQrCode: ScanQrCode,
+ ScanSearch: ScanSearch,
+ ScanText: ScanText,
+ ScatterChart: ChartScatter,
+ School: School,
+ School2: University,
+ Scissors: Scissors,
+ ScissorsLineDashed: ScissorsLineDashed,
+ ScissorsSquare: SquareScissors,
+ ScissorsSquareDashedBottom: SquareBottomDashedScissors,
+ ScreenShare: ScreenShare,
+ ScreenShareOff: ScreenShareOff,
+ Scroll: Scroll,
+ ScrollText: ScrollText,
+ Search: Search,
+ SearchCheck: SearchCheck,
+ SearchCode: SearchCode,
+ SearchSlash: SearchSlash,
+ SearchX: SearchX,
+ Section: Section,
+ Send: Send,
+ SendHorizonal: SendHorizontal,
+ SendHorizontal: SendHorizontal,
+ SendToBack: SendToBack,
+ SeparatorHorizontal: SeparatorHorizontal,
+ SeparatorVertical: SeparatorVertical,
+ Server: Server,
+ ServerCog: ServerCog,
+ ServerCrash: ServerCrash,
+ ServerOff: ServerOff,
+ Settings: Settings,
+ Settings2: Settings2,
+ Shapes: Shapes,
+ Share: Share,
+ Share2: Share2,
+ Sheet: Sheet,
+ Shell: Shell,
+ Shield: Shield,
+ ShieldAlert: ShieldAlert,
+ ShieldBan: ShieldBan,
+ ShieldCheck: ShieldCheck,
+ ShieldClose: ShieldX,
+ ShieldEllipsis: ShieldEllipsis,
+ ShieldHalf: ShieldHalf,
+ ShieldMinus: ShieldMinus,
+ ShieldOff: ShieldOff,
+ ShieldPlus: ShieldPlus,
+ ShieldQuestion: ShieldQuestionMark,
+ ShieldQuestionMark: ShieldQuestionMark,
+ ShieldUser: ShieldUser,
+ ShieldX: ShieldX,
+ Ship: Ship,
+ ShipWheel: ShipWheel,
+ Shirt: Shirt,
+ ShoppingBag: ShoppingBag,
+ ShoppingBasket: ShoppingBasket,
+ ShoppingCart: ShoppingCart,
+ Shovel: Shovel,
+ ShowerHead: ShowerHead,
+ Shredder: Shredder,
+ Shrimp: Shrimp,
+ Shrink: Shrink,
+ Shrub: Shrub,
+ Shuffle: Shuffle,
+ Sidebar: PanelLeft,
+ SidebarClose: PanelLeftClose,
+ SidebarOpen: PanelLeftOpen,
+ Sigma: Sigma,
+ SigmaSquare: SquareSigma,
+ Signal: Signal,
+ SignalHigh: SignalHigh,
+ SignalLow: SignalLow,
+ SignalMedium: SignalMedium,
+ SignalZero: SignalZero,
+ Signature: Signature,
+ Signpost: Signpost,
+ SignpostBig: SignpostBig,
+ Siren: Siren,
+ SkipBack: SkipBack,
+ SkipForward: SkipForward,
+ Skull: Skull,
+ Slack: Slack,
+ Slash: Slash,
+ SlashSquare: SquareSlash,
+ Slice: Slice,
+ Sliders: SlidersVertical,
+ SlidersHorizontal: SlidersHorizontal,
+ SlidersVertical: SlidersVertical,
+ Smartphone: Smartphone,
+ SmartphoneCharging: SmartphoneCharging,
+ SmartphoneNfc: SmartphoneNfc,
+ Smile: Smile,
+ SmilePlus: SmilePlus,
+ Snail: Snail,
+ Snowflake: Snowflake,
+ SoapDispenserDroplet: SoapDispenserDroplet,
+ Sofa: Sofa,
+ SortAsc: ArrowUpNarrowWide,
+ SortDesc: ArrowDownWideNarrow,
+ Soup: Soup,
+ Space: Space,
+ Spade: Spade,
+ Sparkle: Sparkle,
+ Sparkles: Sparkles,
+ Speaker: Speaker,
+ Speech: Speech,
+ SpellCheck: SpellCheck,
+ SpellCheck2: SpellCheck2,
+ Spline: Spline,
+ SplinePointer: SplinePointer,
+ Split: Split,
+ SplitSquareHorizontal: SquareSplitHorizontal,
+ SplitSquareVertical: SquareSplitVertical,
+ Spool: Spool,
+ Spotlight: Spotlight,
+ SprayCan: SprayCan,
+ Sprout: Sprout,
+ Square: Square,
+ SquareActivity: SquareActivity,
+ SquareArrowDown: SquareArrowDown,
+ SquareArrowDownLeft: SquareArrowDownLeft,
+ SquareArrowDownRight: SquareArrowDownRight,
+ SquareArrowLeft: SquareArrowLeft,
+ SquareArrowOutDownLeft: SquareArrowOutDownLeft,
+ SquareArrowOutDownRight: SquareArrowOutDownRight,
+ SquareArrowOutUpLeft: SquareArrowOutUpLeft,
+ SquareArrowOutUpRight: SquareArrowOutUpRight,
+ SquareArrowRight: SquareArrowRight,
+ SquareArrowUp: SquareArrowUp,
+ SquareArrowUpLeft: SquareArrowUpLeft,
+ SquareArrowUpRight: SquareArrowUpRight,
+ SquareAsterisk: SquareAsterisk,
+ SquareBottomDashedScissors: SquareBottomDashedScissors,
+ SquareChartGantt: SquareChartGantt,
+ SquareCheck: SquareCheck,
+ SquareCheckBig: SquareCheckBig,
+ SquareChevronDown: SquareChevronDown,
+ SquareChevronLeft: SquareChevronLeft,
+ SquareChevronRight: SquareChevronRight,
+ SquareChevronUp: SquareChevronUp,
+ SquareCode: SquareCode,
+ SquareDashed: SquareDashed,
+ SquareDashedBottom: SquareDashedBottom,
+ SquareDashedBottomCode: SquareDashedBottomCode,
+ SquareDashedKanban: SquareDashedKanban,
+ SquareDashedMousePointer: SquareDashedMousePointer,
+ SquareDashedTopSolid: SquareDashedTopSolid,
+ SquareDivide: SquareDivide,
+ SquareDot: SquareDot,
+ SquareEqual: SquareEqual,
+ SquareFunction: SquareFunction,
+ SquareGanttChart: SquareChartGantt,
+ SquareKanban: SquareKanban,
+ SquareLibrary: SquareLibrary,
+ SquareM: SquareM,
+ SquareMenu: SquareMenu,
+ SquareMinus: SquareMinus,
+ SquareMousePointer: SquareMousePointer,
+ SquareParking: SquareParking,
+ SquareParkingOff: SquareParkingOff,
+ SquarePause: SquarePause,
+ SquarePen: SquarePen,
+ SquarePercent: SquarePercent,
+ SquarePi: SquarePi,
+ SquarePilcrow: SquarePilcrow,
+ SquarePlay: SquarePlay,
+ SquarePlus: SquarePlus,
+ SquarePower: SquarePower,
+ SquareRadical: SquareRadical,
+ SquareRoundCorner: SquareRoundCorner,
+ SquareScissors: SquareScissors,
+ SquareSigma: SquareSigma,
+ SquareSlash: SquareSlash,
+ SquareSplitHorizontal: SquareSplitHorizontal,
+ SquareSplitVertical: SquareSplitVertical,
+ SquareSquare: SquareSquare,
+ SquareStack: SquareStack,
+ SquareStar: SquareStar,
+ SquareStop: SquareStop,
+ SquareTerminal: SquareTerminal,
+ SquareUser: SquareUser,
+ SquareUserRound: SquareUserRound,
+ SquareX: SquareX,
+ SquaresExclude: SquaresExclude,
+ SquaresIntersect: SquaresIntersect,
+ SquaresSubtract: SquaresSubtract,
+ SquaresUnite: SquaresUnite,
+ Squircle: Squircle,
+ SquircleDashed: SquircleDashed,
+ Squirrel: Squirrel,
+ Stamp: Stamp,
+ Star: Star,
+ StarHalf: StarHalf,
+ StarOff: StarOff,
+ Stars: Sparkles,
+ StepBack: StepBack,
+ StepForward: StepForward,
+ Stethoscope: Stethoscope,
+ Sticker: Sticker,
+ StickyNote: StickyNote,
+ StopCircle: CircleStop,
+ Store: Store,
+ StretchHorizontal: StretchHorizontal,
+ StretchVertical: StretchVertical,
+ Strikethrough: Strikethrough,
+ Subscript: Subscript,
+ Subtitles: Captions,
+ Sun: Sun,
+ SunDim: SunDim,
+ SunMedium: SunMedium,
+ SunMoon: SunMoon,
+ SunSnow: SunSnow,
+ Sunrise: Sunrise,
+ Sunset: Sunset,
+ Superscript: Superscript,
+ SwatchBook: SwatchBook,
+ SwissFranc: SwissFranc,
+ SwitchCamera: SwitchCamera,
+ Sword: Sword,
+ Swords: Swords,
+ Syringe: Syringe,
+ Table: Table,
+ Table2: Table2,
+ TableCellsMerge: TableCellsMerge,
+ TableCellsSplit: TableCellsSplit,
+ TableColumnsSplit: TableColumnsSplit,
+ TableConfig: Columns3Cog,
+ TableOfContents: TableOfContents,
+ TableProperties: TableProperties,
+ TableRowsSplit: TableRowsSplit,
+ Tablet: Tablet,
+ TabletSmartphone: TabletSmartphone,
+ Tablets: Tablets,
+ Tag: Tag,
+ Tags: Tags,
+ Tally1: Tally1,
+ Tally2: Tally2,
+ Tally3: Tally3,
+ Tally4: Tally4,
+ Tally5: Tally5,
+ Tangent: Tangent,
+ Target: Target,
+ Telescope: Telescope,
+ Tent: Tent,
+ TentTree: TentTree,
+ Terminal: Terminal,
+ TerminalSquare: SquareTerminal,
+ TestTube: TestTube,
+ TestTube2: TestTubeDiagonal,
+ TestTubeDiagonal: TestTubeDiagonal,
+ TestTubes: TestTubes,
+ Text: TextAlignStart,
+ TextAlignCenter: TextAlignCenter,
+ TextAlignEnd: TextAlignEnd,
+ TextAlignJustify: TextAlignJustify,
+ TextAlignStart: TextAlignStart,
+ TextCursor: TextCursor,
+ TextCursorInput: TextCursorInput,
+ TextInitial: TextInitial,
+ TextQuote: TextQuote,
+ TextSearch: TextSearch,
+ TextSelect: TextSelect,
+ TextSelection: TextSelect,
+ TextWrap: TextWrap,
+ Theater: Theater,
+ Thermometer: Thermometer,
+ ThermometerSnowflake: ThermometerSnowflake,
+ ThermometerSun: ThermometerSun,
+ ThumbsDown: ThumbsDown,
+ ThumbsUp: ThumbsUp,
+ Ticket: Ticket,
+ TicketCheck: TicketCheck,
+ TicketMinus: TicketMinus,
+ TicketPercent: TicketPercent,
+ TicketPlus: TicketPlus,
+ TicketSlash: TicketSlash,
+ TicketX: TicketX,
+ Tickets: Tickets,
+ TicketsPlane: TicketsPlane,
+ Timer: Timer,
+ TimerOff: TimerOff,
+ TimerReset: TimerReset,
+ ToggleLeft: ToggleLeft,
+ ToggleRight: ToggleRight,
+ Toilet: Toilet,
+ ToolCase: ToolCase,
+ Tornado: Tornado,
+ Torus: Torus,
+ Touchpad: Touchpad,
+ TouchpadOff: TouchpadOff,
+ TowerControl: TowerControl,
+ ToyBrick: ToyBrick,
+ Tractor: Tractor,
+ TrafficCone: TrafficCone,
+ Train: TramFront,
+ TrainFront: TrainFront,
+ TrainFrontTunnel: TrainFrontTunnel,
+ TrainTrack: TrainTrack,
+ TramFront: TramFront,
+ Transgender: Transgender,
+ Trash: Trash,
+ Trash2: Trash2,
+ TreeDeciduous: TreeDeciduous,
+ TreePalm: TreePalm,
+ TreePine: TreePine,
+ Trees: Trees,
+ Trello: Trello,
+ TrendingDown: TrendingDown,
+ TrendingUp: TrendingUp,
+ TrendingUpDown: TrendingUpDown,
+ Triangle: Triangle,
+ TriangleAlert: TriangleAlert,
+ TriangleDashed: TriangleDashed,
+ TriangleRight: TriangleRight,
+ Trophy: Trophy,
+ Truck: Truck,
+ TruckElectric: TruckElectric,
+ TurkishLira: TurkishLira,
+ Turntable: Turntable,
+ Turtle: Turtle,
+ Tv: Tv,
+ Tv2: TvMinimal,
+ TvMinimal: TvMinimal,
+ TvMinimalPlay: TvMinimalPlay,
+ Twitch: Twitch,
+ Twitter: Twitter,
+ Type: Type,
+ TypeOutline: TypeOutline,
+ Umbrella: Umbrella,
+ UmbrellaOff: UmbrellaOff,
+ Underline: Underline,
+ Undo: Undo,
+ Undo2: Undo2,
+ UndoDot: UndoDot,
+ UnfoldHorizontal: UnfoldHorizontal,
+ UnfoldVertical: UnfoldVertical,
+ Ungroup: Ungroup,
+ University: University,
+ Unlink: Unlink,
+ Unlink2: Unlink2,
+ Unlock: LockOpen,
+ UnlockKeyhole: LockKeyholeOpen,
+ Unplug: Unplug,
+ Upload: Upload,
+ UploadCloud: CloudUpload,
+ Usb: Usb,
+ User: User,
+ User2: UserRound,
+ UserCheck: UserCheck,
+ UserCheck2: UserRoundCheck,
+ UserCircle: CircleUser,
+ UserCircle2: CircleUserRound,
+ UserCog: UserCog,
+ UserCog2: UserRoundCog,
+ UserLock: UserLock,
+ UserMinus: UserMinus,
+ UserMinus2: UserRoundMinus,
+ UserPen: UserPen,
+ UserPlus: UserPlus,
+ UserPlus2: UserRoundPlus,
+ UserRound: UserRound,
+ UserRoundCheck: UserRoundCheck,
+ UserRoundCog: UserRoundCog,
+ UserRoundMinus: UserRoundMinus,
+ UserRoundPen: UserRoundPen,
+ UserRoundPlus: UserRoundPlus,
+ UserRoundSearch: UserRoundSearch,
+ UserRoundX: UserRoundX,
+ UserSearch: UserSearch,
+ UserSquare: SquareUser,
+ UserSquare2: SquareUserRound,
+ UserStar: UserStar,
+ UserX: UserX,
+ UserX2: UserRoundX,
+ Users: Users,
+ Users2: UsersRound,
+ UsersRound: UsersRound,
+ Utensils: Utensils,
+ UtensilsCrossed: UtensilsCrossed,
+ UtilityPole: UtilityPole,
+ Variable: Variable,
+ Vault: Vault,
+ VectorSquare: VectorSquare,
+ Vegan: Vegan,
+ VenetianMask: VenetianMask,
+ Venus: Venus,
+ VenusAndMars: VenusAndMars,
+ Verified: BadgeCheck,
+ Vibrate: Vibrate,
+ VibrateOff: VibrateOff,
+ Video: Video,
+ VideoOff: VideoOff,
+ Videotape: Videotape,
+ View: View,
+ Voicemail: Voicemail,
+ Volleyball: Volleyball,
+ Volume: Volume,
+ Volume1: Volume1,
+ Volume2: Volume2,
+ VolumeOff: VolumeOff,
+ VolumeX: VolumeX,
+ Vote: Vote,
+ Wallet: Wallet,
+ Wallet2: WalletMinimal,
+ WalletCards: WalletCards,
+ WalletMinimal: WalletMinimal,
+ Wallpaper: Wallpaper,
+ Wand: Wand,
+ Wand2: WandSparkles,
+ WandSparkles: WandSparkles,
+ Warehouse: Warehouse,
+ WashingMachine: WashingMachine,
+ Watch: Watch,
+ Waves: Waves,
+ WavesLadder: WavesLadder,
+ Waypoints: Waypoints,
+ Webcam: Webcam,
+ Webhook: Webhook,
+ WebhookOff: WebhookOff,
+ Weight: Weight,
+ Wheat: Wheat,
+ WheatOff: WheatOff,
+ WholeWord: WholeWord,
+ Wifi: Wifi,
+ WifiCog: WifiCog,
+ WifiHigh: WifiHigh,
+ WifiLow: WifiLow,
+ WifiOff: WifiOff,
+ WifiPen: WifiPen,
+ WifiSync: WifiSync,
+ WifiZero: WifiZero,
+ Wind: Wind,
+ WindArrowDown: WindArrowDown,
+ Wine: Wine,
+ WineOff: WineOff,
+ Workflow: Workflow,
+ Worm: Worm,
+ WrapText: TextWrap,
+ Wrench: Wrench,
+ X: X,
+ XCircle: CircleX,
+ XOctagon: OctagonX,
+ XSquare: SquareX,
+ Youtube: Youtube,
+ Zap: Zap,
+ ZapOff: ZapOff,
+ ZoomIn: ZoomIn,
+ ZoomOut: ZoomOut
+ });
+
+ const createIcons = ({
+ icons = iconAndAliases,
+ nameAttr = "data-lucide",
+ attrs = {},
+ root = document
+ } = {}) => {
+ if (!Object.values(icons).length) {
+ throw new Error(
+ "Please provide an icons object.\nIf you want to use all the icons you can import it like:\n `import { createIcons, icons } from 'lucide';\nlucide.createIcons({icons});`"
+ );
+ }
+ if (typeof root === "undefined") {
+ throw new Error("`createIcons()` only works in a browser environment.");
+ }
+ const elementsToReplace = root.querySelectorAll(`[${nameAttr}]`);
+ Array.from(elementsToReplace).forEach(
+ (element) => replaceElement(element, { nameAttr, icons, attrs })
+ );
+ if (nameAttr === "data-lucide") {
+ const deprecatedElements = root.querySelectorAll("[icon-name]");
+ if (deprecatedElements.length > 0) {
+ console.warn(
+ "[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide"
+ );
+ Array.from(deprecatedElements).forEach(
+ (element) => replaceElement(element, { nameAttr: "icon-name", icons, attrs })
+ );
+ }
+ }
+ };
+
+ exports.AArrowDown = AArrowDown;
+ exports.AArrowUp = AArrowUp;
+ exports.ALargeSmall = ALargeSmall;
+ exports.Accessibility = Accessibility;
+ exports.Activity = Activity;
+ exports.ActivitySquare = SquareActivity;
+ exports.AirVent = AirVent;
+ exports.Airplay = Airplay;
+ exports.AlarmCheck = AlarmClockCheck;
+ exports.AlarmClock = AlarmClock;
+ exports.AlarmClockCheck = AlarmClockCheck;
+ exports.AlarmClockMinus = AlarmClockMinus;
+ exports.AlarmClockOff = AlarmClockOff;
+ exports.AlarmClockPlus = AlarmClockPlus;
+ exports.AlarmMinus = AlarmClockMinus;
+ exports.AlarmPlus = AlarmClockPlus;
+ exports.AlarmSmoke = AlarmSmoke;
+ exports.Album = Album;
+ exports.AlertCircle = CircleAlert;
+ exports.AlertOctagon = OctagonAlert;
+ exports.AlertTriangle = TriangleAlert;
+ exports.AlignCenter = TextAlignCenter;
+ exports.AlignCenterHorizontal = AlignCenterHorizontal;
+ exports.AlignCenterVertical = AlignCenterVertical;
+ exports.AlignEndHorizontal = AlignEndHorizontal;
+ exports.AlignEndVertical = AlignEndVertical;
+ exports.AlignHorizontalDistributeCenter = AlignHorizontalDistributeCenter;
+ exports.AlignHorizontalDistributeEnd = AlignHorizontalDistributeEnd;
+ exports.AlignHorizontalDistributeStart = AlignHorizontalDistributeStart;
+ exports.AlignHorizontalJustifyCenter = AlignHorizontalJustifyCenter;
+ exports.AlignHorizontalJustifyEnd = AlignHorizontalJustifyEnd;
+ exports.AlignHorizontalJustifyStart = AlignHorizontalJustifyStart;
+ exports.AlignHorizontalSpaceAround = AlignHorizontalSpaceAround;
+ exports.AlignHorizontalSpaceBetween = AlignHorizontalSpaceBetween;
+ exports.AlignJustify = TextAlignJustify;
+ exports.AlignLeft = TextAlignStart;
+ exports.AlignRight = TextAlignEnd;
+ exports.AlignStartHorizontal = AlignStartHorizontal;
+ exports.AlignStartVertical = AlignStartVertical;
+ exports.AlignVerticalDistributeCenter = AlignVerticalDistributeCenter;
+ exports.AlignVerticalDistributeEnd = AlignVerticalDistributeEnd;
+ exports.AlignVerticalDistributeStart = AlignVerticalDistributeStart;
+ exports.AlignVerticalJustifyCenter = AlignVerticalJustifyCenter;
+ exports.AlignVerticalJustifyEnd = AlignVerticalJustifyEnd;
+ exports.AlignVerticalJustifyStart = AlignVerticalJustifyStart;
+ exports.AlignVerticalSpaceAround = AlignVerticalSpaceAround;
+ exports.AlignVerticalSpaceBetween = AlignVerticalSpaceBetween;
+ exports.Ambulance = Ambulance;
+ exports.Ampersand = Ampersand;
+ exports.Ampersands = Ampersands;
+ exports.Amphora = Amphora;
+ exports.Anchor = Anchor;
+ exports.Angry = Angry;
+ exports.Annoyed = Annoyed;
+ exports.Antenna = Antenna;
+ exports.Anvil = Anvil;
+ exports.Aperture = Aperture;
+ exports.AppWindow = AppWindow;
+ exports.AppWindowMac = AppWindowMac;
+ exports.Apple = Apple;
+ exports.Archive = Archive;
+ exports.ArchiveRestore = ArchiveRestore;
+ exports.ArchiveX = ArchiveX;
+ exports.AreaChart = ChartArea;
+ exports.Armchair = Armchair;
+ exports.ArrowBigDown = ArrowBigDown;
+ exports.ArrowBigDownDash = ArrowBigDownDash;
+ exports.ArrowBigLeft = ArrowBigLeft;
+ exports.ArrowBigLeftDash = ArrowBigLeftDash;
+ exports.ArrowBigRight = ArrowBigRight;
+ exports.ArrowBigRightDash = ArrowBigRightDash;
+ exports.ArrowBigUp = ArrowBigUp;
+ exports.ArrowBigUpDash = ArrowBigUpDash;
+ exports.ArrowDown = ArrowDown;
+ exports.ArrowDown01 = ArrowDown01;
+ exports.ArrowDown10 = ArrowDown10;
+ exports.ArrowDownAZ = ArrowDownAZ;
+ exports.ArrowDownAz = ArrowDownAZ;
+ exports.ArrowDownCircle = CircleArrowDown;
+ exports.ArrowDownFromLine = ArrowDownFromLine;
+ exports.ArrowDownLeft = ArrowDownLeft;
+ exports.ArrowDownLeftFromCircle = CircleArrowOutDownLeft;
+ exports.ArrowDownLeftFromSquare = SquareArrowOutDownLeft;
+ exports.ArrowDownLeftSquare = SquareArrowDownLeft;
+ exports.ArrowDownNarrowWide = ArrowDownNarrowWide;
+ exports.ArrowDownRight = ArrowDownRight;
+ exports.ArrowDownRightFromCircle = CircleArrowOutDownRight;
+ exports.ArrowDownRightFromSquare = SquareArrowOutDownRight;
+ exports.ArrowDownRightSquare = SquareArrowDownRight;
+ exports.ArrowDownSquare = SquareArrowDown;
+ exports.ArrowDownToDot = ArrowDownToDot;
+ exports.ArrowDownToLine = ArrowDownToLine;
+ exports.ArrowDownUp = ArrowDownUp;
+ exports.ArrowDownWideNarrow = ArrowDownWideNarrow;
+ exports.ArrowDownZA = ArrowDownZA;
+ exports.ArrowDownZa = ArrowDownZA;
+ exports.ArrowLeft = ArrowLeft;
+ exports.ArrowLeftCircle = CircleArrowLeft;
+ exports.ArrowLeftFromLine = ArrowLeftFromLine;
+ exports.ArrowLeftRight = ArrowLeftRight;
+ exports.ArrowLeftSquare = SquareArrowLeft;
+ exports.ArrowLeftToLine = ArrowLeftToLine;
+ exports.ArrowRight = ArrowRight;
+ exports.ArrowRightCircle = CircleArrowRight;
+ exports.ArrowRightFromLine = ArrowRightFromLine;
+ exports.ArrowRightLeft = ArrowRightLeft;
+ exports.ArrowRightSquare = SquareArrowRight;
+ exports.ArrowRightToLine = ArrowRightToLine;
+ exports.ArrowUp = ArrowUp;
+ exports.ArrowUp01 = ArrowUp01;
+ exports.ArrowUp10 = ArrowUp10;
+ exports.ArrowUpAZ = ArrowUpAZ;
+ exports.ArrowUpAz = ArrowUpAZ;
+ exports.ArrowUpCircle = CircleArrowUp;
+ exports.ArrowUpDown = ArrowUpDown;
+ exports.ArrowUpFromDot = ArrowUpFromDot;
+ exports.ArrowUpFromLine = ArrowUpFromLine;
+ exports.ArrowUpLeft = ArrowUpLeft;
+ exports.ArrowUpLeftFromCircle = CircleArrowOutUpLeft;
+ exports.ArrowUpLeftFromSquare = SquareArrowOutUpLeft;
+ exports.ArrowUpLeftSquare = SquareArrowUpLeft;
+ exports.ArrowUpNarrowWide = ArrowUpNarrowWide;
+ exports.ArrowUpRight = ArrowUpRight;
+ exports.ArrowUpRightFromCircle = CircleArrowOutUpRight;
+ exports.ArrowUpRightFromSquare = SquareArrowOutUpRight;
+ exports.ArrowUpRightSquare = SquareArrowUpRight;
+ exports.ArrowUpSquare = SquareArrowUp;
+ exports.ArrowUpToLine = ArrowUpToLine;
+ exports.ArrowUpWideNarrow = ArrowUpWideNarrow;
+ exports.ArrowUpZA = ArrowUpZA;
+ exports.ArrowUpZa = ArrowUpZA;
+ exports.ArrowsUpFromLine = ArrowsUpFromLine;
+ exports.Asterisk = Asterisk;
+ exports.AsteriskSquare = SquareAsterisk;
+ exports.AtSign = AtSign;
+ exports.Atom = Atom;
+ exports.AudioLines = AudioLines;
+ exports.AudioWaveform = AudioWaveform;
+ exports.Award = Award;
+ exports.Axe = Axe;
+ exports.Axis3D = Axis3d;
+ exports.Axis3d = Axis3d;
+ exports.Baby = Baby;
+ exports.Backpack = Backpack;
+ exports.Badge = Badge;
+ exports.BadgeAlert = BadgeAlert;
+ exports.BadgeCent = BadgeCent;
+ exports.BadgeCheck = BadgeCheck;
+ exports.BadgeDollarSign = BadgeDollarSign;
+ exports.BadgeEuro = BadgeEuro;
+ exports.BadgeHelp = BadgeQuestionMark;
+ exports.BadgeIndianRupee = BadgeIndianRupee;
+ exports.BadgeInfo = BadgeInfo;
+ exports.BadgeJapaneseYen = BadgeJapaneseYen;
+ exports.BadgeMinus = BadgeMinus;
+ exports.BadgePercent = BadgePercent;
+ exports.BadgePlus = BadgePlus;
+ exports.BadgePoundSterling = BadgePoundSterling;
+ exports.BadgeQuestionMark = BadgeQuestionMark;
+ exports.BadgeRussianRuble = BadgeRussianRuble;
+ exports.BadgeSwissFranc = BadgeSwissFranc;
+ exports.BadgeTurkishLira = BadgeTurkishLira;
+ exports.BadgeX = BadgeX;
+ exports.BaggageClaim = BaggageClaim;
+ exports.Ban = Ban;
+ exports.Banana = Banana;
+ exports.Bandage = Bandage;
+ exports.Banknote = Banknote;
+ exports.BanknoteArrowDown = BanknoteArrowDown;
+ exports.BanknoteArrowUp = BanknoteArrowUp;
+ exports.BanknoteX = BanknoteX;
+ exports.BarChart = ChartNoAxesColumnIncreasing;
+ exports.BarChart2 = ChartNoAxesColumn;
+ exports.BarChart3 = ChartColumn;
+ exports.BarChart4 = ChartColumnIncreasing;
+ exports.BarChartBig = ChartColumnBig;
+ exports.BarChartHorizontal = ChartBar;
+ exports.BarChartHorizontalBig = ChartBarBig;
+ exports.Barcode = Barcode;
+ exports.Barrel = Barrel;
+ exports.Baseline = Baseline;
+ exports.Bath = Bath;
+ exports.Battery = Battery;
+ exports.BatteryCharging = BatteryCharging;
+ exports.BatteryFull = BatteryFull;
+ exports.BatteryLow = BatteryLow;
+ exports.BatteryMedium = BatteryMedium;
+ exports.BatteryPlus = BatteryPlus;
+ exports.BatteryWarning = BatteryWarning;
+ exports.Beaker = Beaker;
+ exports.Bean = Bean;
+ exports.BeanOff = BeanOff;
+ exports.Bed = Bed;
+ exports.BedDouble = BedDouble;
+ exports.BedSingle = BedSingle;
+ exports.Beef = Beef;
+ exports.Beer = Beer;
+ exports.BeerOff = BeerOff;
+ exports.Bell = Bell;
+ exports.BellDot = BellDot;
+ exports.BellElectric = BellElectric;
+ exports.BellMinus = BellMinus;
+ exports.BellOff = BellOff;
+ exports.BellPlus = BellPlus;
+ exports.BellRing = BellRing;
+ exports.BetweenHorizonalEnd = BetweenHorizontalEnd;
+ exports.BetweenHorizonalStart = BetweenHorizontalStart;
+ exports.BetweenHorizontalEnd = BetweenHorizontalEnd;
+ exports.BetweenHorizontalStart = BetweenHorizontalStart;
+ exports.BetweenVerticalEnd = BetweenVerticalEnd;
+ exports.BetweenVerticalStart = BetweenVerticalStart;
+ exports.BicepsFlexed = BicepsFlexed;
+ exports.Bike = Bike;
+ exports.Binary = Binary;
+ exports.Binoculars = Binoculars;
+ exports.Biohazard = Biohazard;
+ exports.Bird = Bird;
+ exports.Bitcoin = Bitcoin;
+ exports.Blend = Blend;
+ exports.Blinds = Blinds;
+ exports.Blocks = Blocks;
+ exports.Bluetooth = Bluetooth;
+ exports.BluetoothConnected = BluetoothConnected;
+ exports.BluetoothOff = BluetoothOff;
+ exports.BluetoothSearching = BluetoothSearching;
+ exports.Bold = Bold;
+ exports.Bolt = Bolt;
+ exports.Bomb = Bomb;
+ exports.Bone = Bone;
+ exports.Book = Book;
+ exports.BookA = BookA;
+ exports.BookAlert = BookAlert;
+ exports.BookAudio = BookAudio;
+ exports.BookCheck = BookCheck;
+ exports.BookCopy = BookCopy;
+ exports.BookDashed = BookDashed;
+ exports.BookDown = BookDown;
+ exports.BookHeadphones = BookHeadphones;
+ exports.BookHeart = BookHeart;
+ exports.BookImage = BookImage;
+ exports.BookKey = BookKey;
+ exports.BookLock = BookLock;
+ exports.BookMarked = BookMarked;
+ exports.BookMinus = BookMinus;
+ exports.BookOpen = BookOpen;
+ exports.BookOpenCheck = BookOpenCheck;
+ exports.BookOpenText = BookOpenText;
+ exports.BookPlus = BookPlus;
+ exports.BookTemplate = BookDashed;
+ exports.BookText = BookText;
+ exports.BookType = BookType;
+ exports.BookUp = BookUp;
+ exports.BookUp2 = BookUp2;
+ exports.BookUser = BookUser;
+ exports.BookX = BookX;
+ exports.Bookmark = Bookmark;
+ exports.BookmarkCheck = BookmarkCheck;
+ exports.BookmarkMinus = BookmarkMinus;
+ exports.BookmarkPlus = BookmarkPlus;
+ exports.BookmarkX = BookmarkX;
+ exports.BoomBox = BoomBox;
+ exports.Bot = Bot;
+ exports.BotMessageSquare = BotMessageSquare;
+ exports.BotOff = BotOff;
+ exports.BottleWine = BottleWine;
+ exports.BowArrow = BowArrow;
+ exports.Box = Box;
+ exports.BoxSelect = SquareDashed;
+ exports.Boxes = Boxes;
+ exports.Braces = Braces;
+ exports.Brackets = Brackets;
+ exports.Brain = Brain;
+ exports.BrainCircuit = BrainCircuit;
+ exports.BrainCog = BrainCog;
+ exports.BrickWall = BrickWall;
+ exports.BrickWallFire = BrickWallFire;
+ exports.BrickWallShield = BrickWallShield;
+ exports.Briefcase = Briefcase;
+ exports.BriefcaseBusiness = BriefcaseBusiness;
+ exports.BriefcaseConveyorBelt = BriefcaseConveyorBelt;
+ exports.BriefcaseMedical = BriefcaseMedical;
+ exports.BringToFront = BringToFront;
+ exports.Brush = Brush;
+ exports.BrushCleaning = BrushCleaning;
+ exports.Bubbles = Bubbles;
+ exports.Bug = Bug;
+ exports.BugOff = BugOff;
+ exports.BugPlay = BugPlay;
+ exports.Building = Building;
+ exports.Building2 = Building2;
+ exports.Bus = Bus;
+ exports.BusFront = BusFront;
+ exports.Cable = Cable;
+ exports.CableCar = CableCar;
+ exports.Cake = Cake;
+ exports.CakeSlice = CakeSlice;
+ exports.Calculator = Calculator;
+ exports.Calendar = Calendar;
+ exports.Calendar1 = Calendar1;
+ exports.CalendarArrowDown = CalendarArrowDown;
+ exports.CalendarArrowUp = CalendarArrowUp;
+ exports.CalendarCheck = CalendarCheck;
+ exports.CalendarCheck2 = CalendarCheck2;
+ exports.CalendarClock = CalendarClock;
+ exports.CalendarCog = CalendarCog;
+ exports.CalendarDays = CalendarDays;
+ exports.CalendarFold = CalendarFold;
+ exports.CalendarHeart = CalendarHeart;
+ exports.CalendarMinus = CalendarMinus;
+ exports.CalendarMinus2 = CalendarMinus2;
+ exports.CalendarOff = CalendarOff;
+ exports.CalendarPlus = CalendarPlus;
+ exports.CalendarPlus2 = CalendarPlus2;
+ exports.CalendarRange = CalendarRange;
+ exports.CalendarSearch = CalendarSearch;
+ exports.CalendarSync = CalendarSync;
+ exports.CalendarX = CalendarX;
+ exports.CalendarX2 = CalendarX2;
+ exports.Camera = Camera;
+ exports.CameraOff = CameraOff;
+ exports.CandlestickChart = ChartCandlestick;
+ exports.Candy = Candy;
+ exports.CandyCane = CandyCane;
+ exports.CandyOff = CandyOff;
+ exports.Cannabis = Cannabis;
+ exports.Captions = Captions;
+ exports.CaptionsOff = CaptionsOff;
+ exports.Car = Car;
+ exports.CarFront = CarFront;
+ exports.CarTaxiFront = CarTaxiFront;
+ exports.Caravan = Caravan;
+ exports.CardSim = CardSim;
+ exports.Carrot = Carrot;
+ exports.CaseLower = CaseLower;
+ exports.CaseSensitive = CaseSensitive;
+ exports.CaseUpper = CaseUpper;
+ exports.CassetteTape = CassetteTape;
+ exports.Cast = Cast;
+ exports.Castle = Castle;
+ exports.Cat = Cat;
+ exports.Cctv = Cctv;
+ exports.ChartArea = ChartArea;
+ exports.ChartBar = ChartBar;
+ exports.ChartBarBig = ChartBarBig;
+ exports.ChartBarDecreasing = ChartBarDecreasing;
+ exports.ChartBarIncreasing = ChartBarIncreasing;
+ exports.ChartBarStacked = ChartBarStacked;
+ exports.ChartCandlestick = ChartCandlestick;
+ exports.ChartColumn = ChartColumn;
+ exports.ChartColumnBig = ChartColumnBig;
+ exports.ChartColumnDecreasing = ChartColumnDecreasing;
+ exports.ChartColumnIncreasing = ChartColumnIncreasing;
+ exports.ChartColumnStacked = ChartColumnStacked;
+ exports.ChartGantt = ChartGantt;
+ exports.ChartLine = ChartLine;
+ exports.ChartNetwork = ChartNetwork;
+ exports.ChartNoAxesColumn = ChartNoAxesColumn;
+ exports.ChartNoAxesColumnDecreasing = ChartNoAxesColumnDecreasing;
+ exports.ChartNoAxesColumnIncreasing = ChartNoAxesColumnIncreasing;
+ exports.ChartNoAxesCombined = ChartNoAxesCombined;
+ exports.ChartNoAxesGantt = ChartNoAxesGantt;
+ exports.ChartPie = ChartPie;
+ exports.ChartScatter = ChartScatter;
+ exports.ChartSpline = ChartSpline;
+ exports.Check = Check;
+ exports.CheckCheck = CheckCheck;
+ exports.CheckCircle = CircleCheckBig;
+ exports.CheckCircle2 = CircleCheck;
+ exports.CheckLine = CheckLine;
+ exports.CheckSquare = SquareCheckBig;
+ exports.CheckSquare2 = SquareCheck;
+ exports.ChefHat = ChefHat;
+ exports.Cherry = Cherry;
+ exports.ChevronDown = ChevronDown;
+ exports.ChevronDownCircle = CircleChevronDown;
+ exports.ChevronDownSquare = SquareChevronDown;
+ exports.ChevronFirst = ChevronFirst;
+ exports.ChevronLast = ChevronLast;
+ exports.ChevronLeft = ChevronLeft;
+ exports.ChevronLeftCircle = CircleChevronLeft;
+ exports.ChevronLeftSquare = SquareChevronLeft;
+ exports.ChevronRight = ChevronRight;
+ exports.ChevronRightCircle = CircleChevronRight;
+ exports.ChevronRightSquare = SquareChevronRight;
+ exports.ChevronUp = ChevronUp;
+ exports.ChevronUpCircle = CircleChevronUp;
+ exports.ChevronUpSquare = SquareChevronUp;
+ exports.ChevronsDown = ChevronsDown;
+ exports.ChevronsDownUp = ChevronsDownUp;
+ exports.ChevronsLeft = ChevronsLeft;
+ exports.ChevronsLeftRight = ChevronsLeftRight;
+ exports.ChevronsLeftRightEllipsis = ChevronsLeftRightEllipsis;
+ exports.ChevronsRight = ChevronsRight;
+ exports.ChevronsRightLeft = ChevronsRightLeft;
+ exports.ChevronsUp = ChevronsUp;
+ exports.ChevronsUpDown = ChevronsUpDown;
+ exports.Chrome = Chromium;
+ exports.Chromium = Chromium;
+ exports.Church = Church;
+ exports.Cigarette = Cigarette;
+ exports.CigaretteOff = CigaretteOff;
+ exports.Circle = Circle;
+ exports.CircleAlert = CircleAlert;
+ exports.CircleArrowDown = CircleArrowDown;
+ exports.CircleArrowLeft = CircleArrowLeft;
+ exports.CircleArrowOutDownLeft = CircleArrowOutDownLeft;
+ exports.CircleArrowOutDownRight = CircleArrowOutDownRight;
+ exports.CircleArrowOutUpLeft = CircleArrowOutUpLeft;
+ exports.CircleArrowOutUpRight = CircleArrowOutUpRight;
+ exports.CircleArrowRight = CircleArrowRight;
+ exports.CircleArrowUp = CircleArrowUp;
+ exports.CircleCheck = CircleCheck;
+ exports.CircleCheckBig = CircleCheckBig;
+ exports.CircleChevronDown = CircleChevronDown;
+ exports.CircleChevronLeft = CircleChevronLeft;
+ exports.CircleChevronRight = CircleChevronRight;
+ exports.CircleChevronUp = CircleChevronUp;
+ exports.CircleDashed = CircleDashed;
+ exports.CircleDivide = CircleDivide;
+ exports.CircleDollarSign = CircleDollarSign;
+ exports.CircleDot = CircleDot;
+ exports.CircleDotDashed = CircleDotDashed;
+ exports.CircleEllipsis = CircleEllipsis;
+ exports.CircleEqual = CircleEqual;
+ exports.CircleFadingArrowUp = CircleFadingArrowUp;
+ exports.CircleFadingPlus = CircleFadingPlus;
+ exports.CircleGauge = CircleGauge;
+ exports.CircleHelp = CircleQuestionMark;
+ exports.CircleMinus = CircleMinus;
+ exports.CircleOff = CircleOff;
+ exports.CircleParking = CircleParking;
+ exports.CircleParkingOff = CircleParkingOff;
+ exports.CirclePause = CirclePause;
+ exports.CirclePercent = CirclePercent;
+ exports.CirclePlay = CirclePlay;
+ exports.CirclePlus = CirclePlus;
+ exports.CirclePoundSterling = CirclePoundSterling;
+ exports.CirclePower = CirclePower;
+ exports.CircleQuestionMark = CircleQuestionMark;
+ exports.CircleSlash = CircleSlash;
+ exports.CircleSlash2 = CircleSlash2;
+ exports.CircleSlashed = CircleSlash2;
+ exports.CircleSmall = CircleSmall;
+ exports.CircleStar = CircleStar;
+ exports.CircleStop = CircleStop;
+ exports.CircleUser = CircleUser;
+ exports.CircleUserRound = CircleUserRound;
+ exports.CircleX = CircleX;
+ exports.CircuitBoard = CircuitBoard;
+ exports.Citrus = Citrus;
+ exports.Clapperboard = Clapperboard;
+ exports.Clipboard = Clipboard;
+ exports.ClipboardCheck = ClipboardCheck;
+ exports.ClipboardClock = ClipboardClock;
+ exports.ClipboardCopy = ClipboardCopy;
+ exports.ClipboardEdit = ClipboardPen;
+ exports.ClipboardList = ClipboardList;
+ exports.ClipboardMinus = ClipboardMinus;
+ exports.ClipboardPaste = ClipboardPaste;
+ exports.ClipboardPen = ClipboardPen;
+ exports.ClipboardPenLine = ClipboardPenLine;
+ exports.ClipboardPlus = ClipboardPlus;
+ exports.ClipboardSignature = ClipboardPenLine;
+ exports.ClipboardType = ClipboardType;
+ exports.ClipboardX = ClipboardX;
+ exports.Clock = Clock;
+ exports.Clock1 = Clock1;
+ exports.Clock10 = Clock10;
+ exports.Clock11 = Clock11;
+ exports.Clock12 = Clock12;
+ exports.Clock2 = Clock2;
+ exports.Clock3 = Clock3;
+ exports.Clock4 = Clock4;
+ exports.Clock5 = Clock5;
+ exports.Clock6 = Clock6;
+ exports.Clock7 = Clock7;
+ exports.Clock8 = Clock8;
+ exports.Clock9 = Clock9;
+ exports.ClockAlert = ClockAlert;
+ exports.ClockArrowDown = ClockArrowDown;
+ exports.ClockArrowUp = ClockArrowUp;
+ exports.ClockFading = ClockFading;
+ exports.ClockPlus = ClockPlus;
+ exports.ClosedCaption = ClosedCaption;
+ exports.Cloud = Cloud;
+ exports.CloudAlert = CloudAlert;
+ exports.CloudCheck = CloudCheck;
+ exports.CloudCog = CloudCog;
+ exports.CloudDownload = CloudDownload;
+ exports.CloudDrizzle = CloudDrizzle;
+ exports.CloudFog = CloudFog;
+ exports.CloudHail = CloudHail;
+ exports.CloudLightning = CloudLightning;
+ exports.CloudMoon = CloudMoon;
+ exports.CloudMoonRain = CloudMoonRain;
+ exports.CloudOff = CloudOff;
+ exports.CloudRain = CloudRain;
+ exports.CloudRainWind = CloudRainWind;
+ exports.CloudSnow = CloudSnow;
+ exports.CloudSun = CloudSun;
+ exports.CloudSunRain = CloudSunRain;
+ exports.CloudUpload = CloudUpload;
+ exports.Cloudy = Cloudy;
+ exports.Clover = Clover;
+ exports.Club = Club;
+ exports.Code = Code;
+ exports.Code2 = CodeXml;
+ exports.CodeSquare = SquareCode;
+ exports.CodeXml = CodeXml;
+ exports.Codepen = Codepen;
+ exports.Codesandbox = Codesandbox;
+ exports.Coffee = Coffee;
+ exports.Cog = Cog;
+ exports.Coins = Coins;
+ exports.Columns = Columns2;
+ exports.Columns2 = Columns2;
+ exports.Columns3 = Columns3;
+ exports.Columns3Cog = Columns3Cog;
+ exports.Columns4 = Columns4;
+ exports.ColumnsSettings = Columns3Cog;
+ exports.Combine = Combine;
+ exports.Command = Command;
+ exports.Compass = Compass;
+ exports.Component = Component;
+ exports.Computer = Computer;
+ exports.ConciergeBell = ConciergeBell;
+ exports.Cone = Cone;
+ exports.Construction = Construction;
+ exports.Contact = Contact;
+ exports.Contact2 = ContactRound;
+ exports.ContactRound = ContactRound;
+ exports.Container = Container;
+ exports.Contrast = Contrast;
+ exports.Cookie = Cookie;
+ exports.CookingPot = CookingPot;
+ exports.Copy = Copy;
+ exports.CopyCheck = CopyCheck;
+ exports.CopyMinus = CopyMinus;
+ exports.CopyPlus = CopyPlus;
+ exports.CopySlash = CopySlash;
+ exports.CopyX = CopyX;
+ exports.Copyleft = Copyleft;
+ exports.Copyright = Copyright;
+ exports.CornerDownLeft = CornerDownLeft;
+ exports.CornerDownRight = CornerDownRight;
+ exports.CornerLeftDown = CornerLeftDown;
+ exports.CornerLeftUp = CornerLeftUp;
+ exports.CornerRightDown = CornerRightDown;
+ exports.CornerRightUp = CornerRightUp;
+ exports.CornerUpLeft = CornerUpLeft;
+ exports.CornerUpRight = CornerUpRight;
+ exports.Cpu = Cpu;
+ exports.CreativeCommons = CreativeCommons;
+ exports.CreditCard = CreditCard;
+ exports.Croissant = Croissant;
+ exports.Crop = Crop;
+ exports.Cross = Cross;
+ exports.Crosshair = Crosshair;
+ exports.Crown = Crown;
+ exports.Cuboid = Cuboid;
+ exports.CupSoda = CupSoda;
+ exports.CurlyBraces = Braces;
+ exports.Currency = Currency;
+ exports.Cylinder = Cylinder;
+ exports.Dam = Dam;
+ exports.Database = Database;
+ exports.DatabaseBackup = DatabaseBackup;
+ exports.DatabaseZap = DatabaseZap;
+ exports.DecimalsArrowLeft = DecimalsArrowLeft;
+ exports.DecimalsArrowRight = DecimalsArrowRight;
+ exports.Delete = Delete;
+ exports.Dessert = Dessert;
+ exports.Diameter = Diameter;
+ exports.Diamond = Diamond;
+ exports.DiamondMinus = DiamondMinus;
+ exports.DiamondPercent = DiamondPercent;
+ exports.DiamondPlus = DiamondPlus;
+ exports.Dice1 = Dice1;
+ exports.Dice2 = Dice2;
+ exports.Dice3 = Dice3;
+ exports.Dice4 = Dice4;
+ exports.Dice5 = Dice5;
+ exports.Dice6 = Dice6;
+ exports.Dices = Dices;
+ exports.Diff = Diff;
+ exports.Disc = Disc;
+ exports.Disc2 = Disc2;
+ exports.Disc3 = Disc3;
+ exports.DiscAlbum = DiscAlbum;
+ exports.Divide = Divide;
+ exports.DivideCircle = CircleDivide;
+ exports.DivideSquare = SquareDivide;
+ exports.Dna = Dna;
+ exports.DnaOff = DnaOff;
+ exports.Dock = Dock;
+ exports.Dog = Dog;
+ exports.DollarSign = DollarSign;
+ exports.Donut = Donut;
+ exports.DoorClosed = DoorClosed;
+ exports.DoorClosedLocked = DoorClosedLocked;
+ exports.DoorOpen = DoorOpen;
+ exports.Dot = Dot;
+ exports.DotSquare = SquareDot;
+ exports.Download = Download;
+ exports.DownloadCloud = CloudDownload;
+ exports.DraftingCompass = DraftingCompass;
+ exports.Drama = Drama;
+ exports.Dribbble = Dribbble;
+ exports.Drill = Drill;
+ exports.Drone = Drone;
+ exports.Droplet = Droplet;
+ exports.DropletOff = DropletOff;
+ exports.Droplets = Droplets;
+ exports.Drum = Drum;
+ exports.Drumstick = Drumstick;
+ exports.Dumbbell = Dumbbell;
+ exports.Ear = Ear;
+ exports.EarOff = EarOff;
+ exports.Earth = Earth;
+ exports.EarthLock = EarthLock;
+ exports.Eclipse = Eclipse;
+ exports.Edit = SquarePen;
+ exports.Edit2 = Pen;
+ exports.Edit3 = PenLine;
+ exports.Egg = Egg;
+ exports.EggFried = EggFried;
+ exports.EggOff = EggOff;
+ exports.Ellipsis = Ellipsis;
+ exports.EllipsisVertical = EllipsisVertical;
+ exports.Equal = Equal;
+ exports.EqualApproximately = EqualApproximately;
+ exports.EqualNot = EqualNot;
+ exports.EqualSquare = SquareEqual;
+ exports.Eraser = Eraser;
+ exports.EthernetPort = EthernetPort;
+ exports.Euro = Euro;
+ exports.EvCharger = EvCharger;
+ exports.Expand = Expand;
+ exports.ExternalLink = ExternalLink;
+ exports.Eye = Eye;
+ exports.EyeClosed = EyeClosed;
+ exports.EyeOff = EyeOff;
+ exports.Facebook = Facebook;
+ exports.Factory = Factory;
+ exports.Fan = Fan;
+ exports.FastForward = FastForward;
+ exports.Feather = Feather;
+ exports.Fence = Fence;
+ exports.FerrisWheel = FerrisWheel;
+ exports.Figma = Figma;
+ exports.File = File;
+ exports.FileArchive = FileArchive;
+ exports.FileAudio = FileAudio;
+ exports.FileAudio2 = FileAudio2;
+ exports.FileAxis3D = FileAxis3d;
+ exports.FileAxis3d = FileAxis3d;
+ exports.FileBadge = FileBadge;
+ exports.FileBadge2 = FileBadge2;
+ exports.FileBarChart = FileChartColumnIncreasing;
+ exports.FileBarChart2 = FileChartColumn;
+ exports.FileBox = FileBox;
+ exports.FileChartColumn = FileChartColumn;
+ exports.FileChartColumnIncreasing = FileChartColumnIncreasing;
+ exports.FileChartLine = FileChartLine;
+ exports.FileChartPie = FileChartPie;
+ exports.FileCheck = FileCheck;
+ exports.FileCheck2 = FileCheck2;
+ exports.FileClock = FileClock;
+ exports.FileCode = FileCode;
+ exports.FileCode2 = FileCode2;
+ exports.FileCog = FileCog;
+ exports.FileCog2 = FileCog;
+ exports.FileDiff = FileDiff;
+ exports.FileDigit = FileDigit;
+ exports.FileDown = FileDown;
+ exports.FileEdit = FilePen;
+ exports.FileHeart = FileHeart;
+ exports.FileImage = FileImage;
+ exports.FileInput = FileInput;
+ exports.FileJson = FileJson;
+ exports.FileJson2 = FileJson2;
+ exports.FileKey = FileKey;
+ exports.FileKey2 = FileKey2;
+ exports.FileLineChart = FileChartLine;
+ exports.FileLock = FileLock;
+ exports.FileLock2 = FileLock2;
+ exports.FileMinus = FileMinus;
+ exports.FileMinus2 = FileMinus2;
+ exports.FileMusic = FileMusic;
+ exports.FileOutput = FileOutput;
+ exports.FilePen = FilePen;
+ exports.FilePenLine = FilePenLine;
+ exports.FilePieChart = FileChartPie;
+ exports.FilePlay = FilePlay;
+ exports.FilePlus = FilePlus;
+ exports.FilePlus2 = FilePlus2;
+ exports.FileQuestion = FileQuestionMark;
+ exports.FileQuestionMark = FileQuestionMark;
+ exports.FileScan = FileScan;
+ exports.FileSearch = FileSearch;
+ exports.FileSearch2 = FileSearch2;
+ exports.FileSignature = FilePenLine;
+ exports.FileSliders = FileSliders;
+ exports.FileSpreadsheet = FileSpreadsheet;
+ exports.FileStack = FileStack;
+ exports.FileSymlink = FileSymlink;
+ exports.FileTerminal = FileTerminal;
+ exports.FileText = FileText;
+ exports.FileType = FileType;
+ exports.FileType2 = FileType2;
+ exports.FileUp = FileUp;
+ exports.FileUser = FileUser;
+ exports.FileVideo = FilePlay;
+ exports.FileVideo2 = FileVideoCamera;
+ exports.FileVideoCamera = FileVideoCamera;
+ exports.FileVolume = FileVolume;
+ exports.FileVolume2 = FileVolume2;
+ exports.FileWarning = FileWarning;
+ exports.FileX = FileX;
+ exports.FileX2 = FileX2;
+ exports.Files = Files;
+ exports.Film = Film;
+ exports.Filter = Funnel;
+ exports.FilterX = FunnelX;
+ exports.Fingerprint = Fingerprint;
+ exports.FireExtinguisher = FireExtinguisher;
+ exports.Fish = Fish;
+ exports.FishOff = FishOff;
+ exports.FishSymbol = FishSymbol;
+ exports.Flag = Flag;
+ exports.FlagOff = FlagOff;
+ exports.FlagTriangleLeft = FlagTriangleLeft;
+ exports.FlagTriangleRight = FlagTriangleRight;
+ exports.Flame = Flame;
+ exports.FlameKindling = FlameKindling;
+ exports.Flashlight = Flashlight;
+ exports.FlashlightOff = FlashlightOff;
+ exports.FlaskConical = FlaskConical;
+ exports.FlaskConicalOff = FlaskConicalOff;
+ exports.FlaskRound = FlaskRound;
+ exports.FlipHorizontal = FlipHorizontal;
+ exports.FlipHorizontal2 = FlipHorizontal2;
+ exports.FlipVertical = FlipVertical;
+ exports.FlipVertical2 = FlipVertical2;
+ exports.Flower = Flower;
+ exports.Flower2 = Flower2;
+ exports.Focus = Focus;
+ exports.FoldHorizontal = FoldHorizontal;
+ exports.FoldVertical = FoldVertical;
+ exports.Folder = Folder;
+ exports.FolderArchive = FolderArchive;
+ exports.FolderCheck = FolderCheck;
+ exports.FolderClock = FolderClock;
+ exports.FolderClosed = FolderClosed;
+ exports.FolderCode = FolderCode;
+ exports.FolderCog = FolderCog;
+ exports.FolderCog2 = FolderCog;
+ exports.FolderDot = FolderDot;
+ exports.FolderDown = FolderDown;
+ exports.FolderEdit = FolderPen;
+ exports.FolderGit = FolderGit;
+ exports.FolderGit2 = FolderGit2;
+ exports.FolderHeart = FolderHeart;
+ exports.FolderInput = FolderInput;
+ exports.FolderKanban = FolderKanban;
+ exports.FolderKey = FolderKey;
+ exports.FolderLock = FolderLock;
+ exports.FolderMinus = FolderMinus;
+ exports.FolderOpen = FolderOpen;
+ exports.FolderOpenDot = FolderOpenDot;
+ exports.FolderOutput = FolderOutput;
+ exports.FolderPen = FolderPen;
+ exports.FolderPlus = FolderPlus;
+ exports.FolderRoot = FolderRoot;
+ exports.FolderSearch = FolderSearch;
+ exports.FolderSearch2 = FolderSearch2;
+ exports.FolderSymlink = FolderSymlink;
+ exports.FolderSync = FolderSync;
+ exports.FolderTree = FolderTree;
+ exports.FolderUp = FolderUp;
+ exports.FolderX = FolderX;
+ exports.Folders = Folders;
+ exports.Footprints = Footprints;
+ exports.ForkKnife = Utensils;
+ exports.ForkKnifeCrossed = UtensilsCrossed;
+ exports.Forklift = Forklift;
+ exports.FormInput = RectangleEllipsis;
+ exports.Forward = Forward;
+ exports.Frame = Frame;
+ exports.Framer = Framer;
+ exports.Frown = Frown;
+ exports.Fuel = Fuel;
+ exports.Fullscreen = Fullscreen;
+ exports.FunctionSquare = SquareFunction;
+ exports.Funnel = Funnel;
+ exports.FunnelPlus = FunnelPlus;
+ exports.FunnelX = FunnelX;
+ exports.GalleryHorizontal = GalleryHorizontal;
+ exports.GalleryHorizontalEnd = GalleryHorizontalEnd;
+ exports.GalleryThumbnails = GalleryThumbnails;
+ exports.GalleryVertical = GalleryVertical;
+ exports.GalleryVerticalEnd = GalleryVerticalEnd;
+ exports.Gamepad = Gamepad;
+ exports.Gamepad2 = Gamepad2;
+ exports.GanttChart = ChartNoAxesGantt;
+ exports.GanttChartSquare = SquareChartGantt;
+ exports.Gauge = Gauge;
+ exports.GaugeCircle = CircleGauge;
+ exports.Gavel = Gavel;
+ exports.Gem = Gem;
+ exports.GeorgianLari = GeorgianLari;
+ exports.Ghost = Ghost;
+ exports.Gift = Gift;
+ exports.GitBranch = GitBranch;
+ exports.GitBranchPlus = GitBranchPlus;
+ exports.GitCommit = GitCommitHorizontal;
+ exports.GitCommitHorizontal = GitCommitHorizontal;
+ exports.GitCommitVertical = GitCommitVertical;
+ exports.GitCompare = GitCompare;
+ exports.GitCompareArrows = GitCompareArrows;
+ exports.GitFork = GitFork;
+ exports.GitGraph = GitGraph;
+ exports.GitMerge = GitMerge;
+ exports.GitPullRequest = GitPullRequest;
+ exports.GitPullRequestArrow = GitPullRequestArrow;
+ exports.GitPullRequestClosed = GitPullRequestClosed;
+ exports.GitPullRequestCreate = GitPullRequestCreate;
+ exports.GitPullRequestCreateArrow = GitPullRequestCreateArrow;
+ exports.GitPullRequestDraft = GitPullRequestDraft;
+ exports.Github = Github;
+ exports.Gitlab = Gitlab;
+ exports.GlassWater = GlassWater;
+ exports.Glasses = Glasses;
+ exports.Globe = Globe;
+ exports.Globe2 = Earth;
+ exports.GlobeLock = GlobeLock;
+ exports.Goal = Goal;
+ exports.Gpu = Gpu;
+ exports.Grab = HandGrab;
+ exports.GraduationCap = GraduationCap;
+ exports.Grape = Grape;
+ exports.Grid = Grid3x3;
+ exports.Grid2X2 = Grid2x2;
+ exports.Grid2X2Check = Grid2x2Check;
+ exports.Grid2X2Plus = Grid2x2Plus;
+ exports.Grid2X2X = Grid2x2X;
+ exports.Grid2x2 = Grid2x2;
+ exports.Grid2x2Check = Grid2x2Check;
+ exports.Grid2x2Plus = Grid2x2Plus;
+ exports.Grid2x2X = Grid2x2X;
+ exports.Grid3X3 = Grid3x3;
+ exports.Grid3x2 = Grid3x2;
+ exports.Grid3x3 = Grid3x3;
+ exports.Grip = Grip;
+ exports.GripHorizontal = GripHorizontal;
+ exports.GripVertical = GripVertical;
+ exports.Group = Group;
+ exports.Guitar = Guitar;
+ exports.Ham = Ham;
+ exports.Hamburger = Hamburger;
+ exports.Hammer = Hammer;
+ exports.Hand = Hand;
+ exports.HandCoins = HandCoins;
+ exports.HandFist = HandFist;
+ exports.HandGrab = HandGrab;
+ exports.HandHeart = HandHeart;
+ exports.HandHelping = HandHelping;
+ exports.HandMetal = HandMetal;
+ exports.HandPlatter = HandPlatter;
+ exports.Handbag = Handbag;
+ exports.Handshake = Handshake;
+ exports.HardDrive = HardDrive;
+ exports.HardDriveDownload = HardDriveDownload;
+ exports.HardDriveUpload = HardDriveUpload;
+ exports.HardHat = HardHat;
+ exports.Hash = Hash;
+ exports.HatGlasses = HatGlasses;
+ exports.Haze = Haze;
+ exports.HdmiPort = HdmiPort;
+ exports.Heading = Heading;
+ exports.Heading1 = Heading1;
+ exports.Heading2 = Heading2;
+ exports.Heading3 = Heading3;
+ exports.Heading4 = Heading4;
+ exports.Heading5 = Heading5;
+ exports.Heading6 = Heading6;
+ exports.HeadphoneOff = HeadphoneOff;
+ exports.Headphones = Headphones;
+ exports.Headset = Headset;
+ exports.Heart = Heart;
+ exports.HeartCrack = HeartCrack;
+ exports.HeartHandshake = HeartHandshake;
+ exports.HeartMinus = HeartMinus;
+ exports.HeartOff = HeartOff;
+ exports.HeartPlus = HeartPlus;
+ exports.HeartPulse = HeartPulse;
+ exports.Heater = Heater;
+ exports.HelpCircle = CircleQuestionMark;
+ exports.HelpingHand = HandHelping;
+ exports.Hexagon = Hexagon;
+ exports.Highlighter = Highlighter;
+ exports.History = History;
+ exports.Home = House;
+ exports.Hop = Hop;
+ exports.HopOff = HopOff;
+ exports.Hospital = Hospital;
+ exports.Hotel = Hotel;
+ exports.Hourglass = Hourglass;
+ exports.House = House;
+ exports.HouseHeart = HouseHeart;
+ exports.HousePlug = HousePlug;
+ exports.HousePlus = HousePlus;
+ exports.HouseWifi = HouseWifi;
+ exports.IceCream = IceCreamCone;
+ exports.IceCream2 = IceCreamBowl;
+ exports.IceCreamBowl = IceCreamBowl;
+ exports.IceCreamCone = IceCreamCone;
+ exports.IdCard = IdCard;
+ exports.IdCardLanyard = IdCardLanyard;
+ exports.Image = Image;
+ exports.ImageDown = ImageDown;
+ exports.ImageMinus = ImageMinus;
+ exports.ImageOff = ImageOff;
+ exports.ImagePlay = ImagePlay;
+ exports.ImagePlus = ImagePlus;
+ exports.ImageUp = ImageUp;
+ exports.ImageUpscale = ImageUpscale;
+ exports.Images = Images;
+ exports.Import = Import;
+ exports.Inbox = Inbox;
+ exports.Indent = ListIndentIncrease;
+ exports.IndentDecrease = ListIndentDecrease;
+ exports.IndentIncrease = ListIndentIncrease;
+ exports.IndianRupee = IndianRupee;
+ exports.Infinity = Infinity;
+ exports.Info = Info;
+ exports.Inspect = SquareMousePointer;
+ exports.InspectionPanel = InspectionPanel;
+ exports.Instagram = Instagram;
+ exports.Italic = Italic;
+ exports.IterationCcw = IterationCcw;
+ exports.IterationCw = IterationCw;
+ exports.JapaneseYen = JapaneseYen;
+ exports.Joystick = Joystick;
+ exports.Kanban = Kanban;
+ exports.KanbanSquare = SquareKanban;
+ exports.KanbanSquareDashed = SquareDashedKanban;
+ exports.Kayak = Kayak;
+ exports.Key = Key;
+ exports.KeyRound = KeyRound;
+ exports.KeySquare = KeySquare;
+ exports.Keyboard = Keyboard;
+ exports.KeyboardMusic = KeyboardMusic;
+ exports.KeyboardOff = KeyboardOff;
+ exports.Lamp = Lamp;
+ exports.LampCeiling = LampCeiling;
+ exports.LampDesk = LampDesk;
+ exports.LampFloor = LampFloor;
+ exports.LampWallDown = LampWallDown;
+ exports.LampWallUp = LampWallUp;
+ exports.LandPlot = LandPlot;
+ exports.Landmark = Landmark;
+ exports.Languages = Languages;
+ exports.Laptop = Laptop;
+ exports.Laptop2 = LaptopMinimal;
+ exports.LaptopMinimal = LaptopMinimal;
+ exports.LaptopMinimalCheck = LaptopMinimalCheck;
+ exports.Lasso = Lasso;
+ exports.LassoSelect = LassoSelect;
+ exports.Laugh = Laugh;
+ exports.Layers = Layers;
+ exports.Layers2 = Layers2;
+ exports.Layers3 = Layers;
+ exports.Layout = PanelsTopLeft;
+ exports.LayoutDashboard = LayoutDashboard;
+ exports.LayoutGrid = LayoutGrid;
+ exports.LayoutList = LayoutList;
+ exports.LayoutPanelLeft = LayoutPanelLeft;
+ exports.LayoutPanelTop = LayoutPanelTop;
+ exports.LayoutTemplate = LayoutTemplate;
+ exports.Leaf = Leaf;
+ exports.LeafyGreen = LeafyGreen;
+ exports.Lectern = Lectern;
+ exports.LetterText = TextInitial;
+ exports.Library = Library;
+ exports.LibraryBig = LibraryBig;
+ exports.LibrarySquare = SquareLibrary;
+ exports.LifeBuoy = LifeBuoy;
+ exports.Ligature = Ligature;
+ exports.Lightbulb = Lightbulb;
+ exports.LightbulbOff = LightbulbOff;
+ exports.LineChart = ChartLine;
+ exports.LineSquiggle = LineSquiggle;
+ exports.Link = Link;
+ exports.Link2 = Link2;
+ exports.Link2Off = Link2Off;
+ exports.Linkedin = Linkedin;
+ exports.List = List;
+ exports.ListCheck = ListCheck;
+ exports.ListChecks = ListChecks;
+ exports.ListChevronsDownUp = ListChevronsDownUp;
+ exports.ListChevronsUpDown = ListChevronsUpDown;
+ exports.ListCollapse = ListCollapse;
+ exports.ListEnd = ListEnd;
+ exports.ListFilter = ListFilter;
+ exports.ListFilterPlus = ListFilterPlus;
+ exports.ListIndentDecrease = ListIndentDecrease;
+ exports.ListIndentIncrease = ListIndentIncrease;
+ exports.ListMinus = ListMinus;
+ exports.ListMusic = ListMusic;
+ exports.ListOrdered = ListOrdered;
+ exports.ListPlus = ListPlus;
+ exports.ListRestart = ListRestart;
+ exports.ListStart = ListStart;
+ exports.ListTodo = ListTodo;
+ exports.ListTree = ListTree;
+ exports.ListVideo = ListVideo;
+ exports.ListX = ListX;
+ exports.Loader = Loader;
+ exports.Loader2 = LoaderCircle;
+ exports.LoaderCircle = LoaderCircle;
+ exports.LoaderPinwheel = LoaderPinwheel;
+ exports.Locate = Locate;
+ exports.LocateFixed = LocateFixed;
+ exports.LocateOff = LocateOff;
+ exports.LocationEdit = MapPinPen;
+ exports.Lock = Lock;
+ exports.LockKeyhole = LockKeyhole;
+ exports.LockKeyholeOpen = LockKeyholeOpen;
+ exports.LockOpen = LockOpen;
+ exports.LogIn = LogIn;
+ exports.LogOut = LogOut;
+ exports.Logs = Logs;
+ exports.Lollipop = Lollipop;
+ exports.Luggage = Luggage;
+ exports.MSquare = SquareM;
+ exports.Magnet = Magnet;
+ exports.Mail = Mail;
+ exports.MailCheck = MailCheck;
+ exports.MailMinus = MailMinus;
+ exports.MailOpen = MailOpen;
+ exports.MailPlus = MailPlus;
+ exports.MailQuestion = MailQuestionMark;
+ exports.MailQuestionMark = MailQuestionMark;
+ exports.MailSearch = MailSearch;
+ exports.MailWarning = MailWarning;
+ exports.MailX = MailX;
+ exports.Mailbox = Mailbox;
+ exports.Mails = Mails;
+ exports.Map = Map;
+ exports.MapMinus = MapMinus;
+ exports.MapPin = MapPin;
+ exports.MapPinCheck = MapPinCheck;
+ exports.MapPinCheckInside = MapPinCheckInside;
+ exports.MapPinHouse = MapPinHouse;
+ exports.MapPinMinus = MapPinMinus;
+ exports.MapPinMinusInside = MapPinMinusInside;
+ exports.MapPinOff = MapPinOff;
+ exports.MapPinPen = MapPinPen;
+ exports.MapPinPlus = MapPinPlus;
+ exports.MapPinPlusInside = MapPinPlusInside;
+ exports.MapPinX = MapPinX;
+ exports.MapPinXInside = MapPinXInside;
+ exports.MapPinned = MapPinned;
+ exports.MapPlus = MapPlus;
+ exports.Mars = Mars;
+ exports.MarsStroke = MarsStroke;
+ exports.Martini = Martini;
+ exports.Maximize = Maximize;
+ exports.Maximize2 = Maximize2;
+ exports.Medal = Medal;
+ exports.Megaphone = Megaphone;
+ exports.MegaphoneOff = MegaphoneOff;
+ exports.Meh = Meh;
+ exports.MemoryStick = MemoryStick;
+ exports.Menu = Menu;
+ exports.MenuSquare = SquareMenu;
+ exports.Merge = Merge;
+ exports.MessageCircle = MessageCircle;
+ exports.MessageCircleCode = MessageCircleCode;
+ exports.MessageCircleDashed = MessageCircleDashed;
+ exports.MessageCircleHeart = MessageCircleHeart;
+ exports.MessageCircleMore = MessageCircleMore;
+ exports.MessageCircleOff = MessageCircleOff;
+ exports.MessageCirclePlus = MessageCirclePlus;
+ exports.MessageCircleQuestion = MessageCircleQuestionMark;
+ exports.MessageCircleQuestionMark = MessageCircleQuestionMark;
+ exports.MessageCircleReply = MessageCircleReply;
+ exports.MessageCircleWarning = MessageCircleWarning;
+ exports.MessageCircleX = MessageCircleX;
+ exports.MessageSquare = MessageSquare;
+ exports.MessageSquareCode = MessageSquareCode;
+ exports.MessageSquareDashed = MessageSquareDashed;
+ exports.MessageSquareDiff = MessageSquareDiff;
+ exports.MessageSquareDot = MessageSquareDot;
+ exports.MessageSquareHeart = MessageSquareHeart;
+ exports.MessageSquareLock = MessageSquareLock;
+ exports.MessageSquareMore = MessageSquareMore;
+ exports.MessageSquareOff = MessageSquareOff;
+ exports.MessageSquarePlus = MessageSquarePlus;
+ exports.MessageSquareQuote = MessageSquareQuote;
+ exports.MessageSquareReply = MessageSquareReply;
+ exports.MessageSquareShare = MessageSquareShare;
+ exports.MessageSquareText = MessageSquareText;
+ exports.MessageSquareWarning = MessageSquareWarning;
+ exports.MessageSquareX = MessageSquareX;
+ exports.MessagesSquare = MessagesSquare;
+ exports.Mic = Mic;
+ exports.Mic2 = MicVocal;
+ exports.MicOff = MicOff;
+ exports.MicVocal = MicVocal;
+ exports.Microchip = Microchip;
+ exports.Microscope = Microscope;
+ exports.Microwave = Microwave;
+ exports.Milestone = Milestone;
+ exports.Milk = Milk;
+ exports.MilkOff = MilkOff;
+ exports.Minimize = Minimize;
+ exports.Minimize2 = Minimize2;
+ exports.Minus = Minus;
+ exports.MinusCircle = CircleMinus;
+ exports.MinusSquare = SquareMinus;
+ exports.Monitor = Monitor;
+ exports.MonitorCheck = MonitorCheck;
+ exports.MonitorCloud = MonitorCloud;
+ exports.MonitorCog = MonitorCog;
+ exports.MonitorDot = MonitorDot;
+ exports.MonitorDown = MonitorDown;
+ exports.MonitorOff = MonitorOff;
+ exports.MonitorPause = MonitorPause;
+ exports.MonitorPlay = MonitorPlay;
+ exports.MonitorSmartphone = MonitorSmartphone;
+ exports.MonitorSpeaker = MonitorSpeaker;
+ exports.MonitorStop = MonitorStop;
+ exports.MonitorUp = MonitorUp;
+ exports.MonitorX = MonitorX;
+ exports.Moon = Moon;
+ exports.MoonStar = MoonStar;
+ exports.MoreHorizontal = Ellipsis;
+ exports.MoreVertical = EllipsisVertical;
+ exports.Motorbike = Motorbike;
+ exports.Mountain = Mountain;
+ exports.MountainSnow = MountainSnow;
+ exports.Mouse = Mouse;
+ exports.MouseOff = MouseOff;
+ exports.MousePointer = MousePointer;
+ exports.MousePointer2 = MousePointer2;
+ exports.MousePointerBan = MousePointerBan;
+ exports.MousePointerClick = MousePointerClick;
+ exports.MousePointerSquareDashed = SquareDashedMousePointer;
+ exports.Move = Move;
+ exports.Move3D = Move3d;
+ exports.Move3d = Move3d;
+ exports.MoveDiagonal = MoveDiagonal;
+ exports.MoveDiagonal2 = MoveDiagonal2;
+ exports.MoveDown = MoveDown;
+ exports.MoveDownLeft = MoveDownLeft;
+ exports.MoveDownRight = MoveDownRight;
+ exports.MoveHorizontal = MoveHorizontal;
+ exports.MoveLeft = MoveLeft;
+ exports.MoveRight = MoveRight;
+ exports.MoveUp = MoveUp;
+ exports.MoveUpLeft = MoveUpLeft;
+ exports.MoveUpRight = MoveUpRight;
+ exports.MoveVertical = MoveVertical;
+ exports.Music = Music;
+ exports.Music2 = Music2;
+ exports.Music3 = Music3;
+ exports.Music4 = Music4;
+ exports.Navigation = Navigation;
+ exports.Navigation2 = Navigation2;
+ exports.Navigation2Off = Navigation2Off;
+ exports.NavigationOff = NavigationOff;
+ exports.Network = Network;
+ exports.Newspaper = Newspaper;
+ exports.Nfc = Nfc;
+ exports.NonBinary = NonBinary;
+ exports.Notebook = Notebook;
+ exports.NotebookPen = NotebookPen;
+ exports.NotebookTabs = NotebookTabs;
+ exports.NotebookText = NotebookText;
+ exports.NotepadText = NotepadText;
+ exports.NotepadTextDashed = NotepadTextDashed;
+ exports.Nut = Nut;
+ exports.NutOff = NutOff;
+ exports.Octagon = Octagon;
+ exports.OctagonAlert = OctagonAlert;
+ exports.OctagonMinus = OctagonMinus;
+ exports.OctagonPause = OctagonPause;
+ exports.OctagonX = OctagonX;
+ exports.Omega = Omega;
+ exports.Option = Option;
+ exports.Orbit = Orbit;
+ exports.Origami = Origami;
+ exports.Outdent = ListIndentDecrease;
+ exports.Package = Package;
+ exports.Package2 = Package2;
+ exports.PackageCheck = PackageCheck;
+ exports.PackageMinus = PackageMinus;
+ exports.PackageOpen = PackageOpen;
+ exports.PackagePlus = PackagePlus;
+ exports.PackageSearch = PackageSearch;
+ exports.PackageX = PackageX;
+ exports.PaintBucket = PaintBucket;
+ exports.PaintRoller = PaintRoller;
+ exports.Paintbrush = Paintbrush;
+ exports.Paintbrush2 = PaintbrushVertical;
+ exports.PaintbrushVertical = PaintbrushVertical;
+ exports.Palette = Palette;
+ exports.Palmtree = TreePalm;
+ exports.Panda = Panda;
+ exports.PanelBottom = PanelBottom;
+ exports.PanelBottomClose = PanelBottomClose;
+ exports.PanelBottomDashed = PanelBottomDashed;
+ exports.PanelBottomInactive = PanelBottomDashed;
+ exports.PanelBottomOpen = PanelBottomOpen;
+ exports.PanelLeft = PanelLeft;
+ exports.PanelLeftClose = PanelLeftClose;
+ exports.PanelLeftDashed = PanelLeftDashed;
+ exports.PanelLeftInactive = PanelLeftDashed;
+ exports.PanelLeftOpen = PanelLeftOpen;
+ exports.PanelLeftRightDashed = PanelLeftRightDashed;
+ exports.PanelRight = PanelRight;
+ exports.PanelRightClose = PanelRightClose;
+ exports.PanelRightDashed = PanelRightDashed;
+ exports.PanelRightInactive = PanelRightDashed;
+ exports.PanelRightOpen = PanelRightOpen;
+ exports.PanelTop = PanelTop;
+ exports.PanelTopBottomDashed = PanelTopBottomDashed;
+ exports.PanelTopClose = PanelTopClose;
+ exports.PanelTopDashed = PanelTopDashed;
+ exports.PanelTopInactive = PanelTopDashed;
+ exports.PanelTopOpen = PanelTopOpen;
+ exports.PanelsLeftBottom = PanelsLeftBottom;
+ exports.PanelsLeftRight = Columns3;
+ exports.PanelsRightBottom = PanelsRightBottom;
+ exports.PanelsTopBottom = Rows3;
+ exports.PanelsTopLeft = PanelsTopLeft;
+ exports.Paperclip = Paperclip;
+ exports.Parentheses = Parentheses;
+ exports.ParkingCircle = CircleParking;
+ exports.ParkingCircleOff = CircleParkingOff;
+ exports.ParkingMeter = ParkingMeter;
+ exports.ParkingSquare = SquareParking;
+ exports.ParkingSquareOff = SquareParkingOff;
+ exports.PartyPopper = PartyPopper;
+ exports.Pause = Pause;
+ exports.PauseCircle = CirclePause;
+ exports.PauseOctagon = OctagonPause;
+ exports.PawPrint = PawPrint;
+ exports.PcCase = PcCase;
+ exports.Pen = Pen;
+ exports.PenBox = SquarePen;
+ exports.PenLine = PenLine;
+ exports.PenOff = PenOff;
+ exports.PenSquare = SquarePen;
+ exports.PenTool = PenTool;
+ exports.Pencil = Pencil;
+ exports.PencilLine = PencilLine;
+ exports.PencilOff = PencilOff;
+ exports.PencilRuler = PencilRuler;
+ exports.Pentagon = Pentagon;
+ exports.Percent = Percent;
+ exports.PercentCircle = CirclePercent;
+ exports.PercentDiamond = DiamondPercent;
+ exports.PercentSquare = SquarePercent;
+ exports.PersonStanding = PersonStanding;
+ exports.PhilippinePeso = PhilippinePeso;
+ exports.Phone = Phone;
+ exports.PhoneCall = PhoneCall;
+ exports.PhoneForwarded = PhoneForwarded;
+ exports.PhoneIncoming = PhoneIncoming;
+ exports.PhoneMissed = PhoneMissed;
+ exports.PhoneOff = PhoneOff;
+ exports.PhoneOutgoing = PhoneOutgoing;
+ exports.Pi = Pi;
+ exports.PiSquare = SquarePi;
+ exports.Piano = Piano;
+ exports.Pickaxe = Pickaxe;
+ exports.PictureInPicture = PictureInPicture;
+ exports.PictureInPicture2 = PictureInPicture2;
+ exports.PieChart = ChartPie;
+ exports.PiggyBank = PiggyBank;
+ exports.Pilcrow = Pilcrow;
+ exports.PilcrowLeft = PilcrowLeft;
+ exports.PilcrowRight = PilcrowRight;
+ exports.PilcrowSquare = SquarePilcrow;
+ exports.Pill = Pill;
+ exports.PillBottle = PillBottle;
+ exports.Pin = Pin;
+ exports.PinOff = PinOff;
+ exports.Pipette = Pipette;
+ exports.Pizza = Pizza;
+ exports.Plane = Plane;
+ exports.PlaneLanding = PlaneLanding;
+ exports.PlaneTakeoff = PlaneTakeoff;
+ exports.Play = Play;
+ exports.PlayCircle = CirclePlay;
+ exports.PlaySquare = SquarePlay;
+ exports.Plug = Plug;
+ exports.Plug2 = Plug2;
+ exports.PlugZap = PlugZap;
+ exports.PlugZap2 = PlugZap;
+ exports.Plus = Plus;
+ exports.PlusCircle = CirclePlus;
+ exports.PlusSquare = SquarePlus;
+ exports.Pocket = Pocket;
+ exports.PocketKnife = PocketKnife;
+ exports.Podcast = Podcast;
+ exports.Pointer = Pointer;
+ exports.PointerOff = PointerOff;
+ exports.Popcorn = Popcorn;
+ exports.Popsicle = Popsicle;
+ exports.PoundSterling = PoundSterling;
+ exports.Power = Power;
+ exports.PowerCircle = CirclePower;
+ exports.PowerOff = PowerOff;
+ exports.PowerSquare = SquarePower;
+ exports.Presentation = Presentation;
+ exports.Printer = Printer;
+ exports.PrinterCheck = PrinterCheck;
+ exports.Projector = Projector;
+ exports.Proportions = Proportions;
+ exports.Puzzle = Puzzle;
+ exports.Pyramid = Pyramid;
+ exports.QrCode = QrCode;
+ exports.Quote = Quote;
+ exports.Rabbit = Rabbit;
+ exports.Radar = Radar;
+ exports.Radiation = Radiation;
+ exports.Radical = Radical;
+ exports.Radio = Radio;
+ exports.RadioReceiver = RadioReceiver;
+ exports.RadioTower = RadioTower;
+ exports.Radius = Radius;
+ exports.RailSymbol = RailSymbol;
+ exports.Rainbow = Rainbow;
+ exports.Rat = Rat;
+ exports.Ratio = Ratio;
+ exports.Receipt = Receipt;
+ exports.ReceiptCent = ReceiptCent;
+ exports.ReceiptEuro = ReceiptEuro;
+ exports.ReceiptIndianRupee = ReceiptIndianRupee;
+ exports.ReceiptJapaneseYen = ReceiptJapaneseYen;
+ exports.ReceiptPoundSterling = ReceiptPoundSterling;
+ exports.ReceiptRussianRuble = ReceiptRussianRuble;
+ exports.ReceiptSwissFranc = ReceiptSwissFranc;
+ exports.ReceiptText = ReceiptText;
+ exports.ReceiptTurkishLira = ReceiptTurkishLira;
+ exports.RectangleCircle = RectangleCircle;
+ exports.RectangleEllipsis = RectangleEllipsis;
+ exports.RectangleGoggles = RectangleGoggles;
+ exports.RectangleHorizontal = RectangleHorizontal;
+ exports.RectangleVertical = RectangleVertical;
+ exports.Recycle = Recycle;
+ exports.Redo = Redo;
+ exports.Redo2 = Redo2;
+ exports.RedoDot = RedoDot;
+ exports.RefreshCcw = RefreshCcw;
+ exports.RefreshCcwDot = RefreshCcwDot;
+ exports.RefreshCw = RefreshCw;
+ exports.RefreshCwOff = RefreshCwOff;
+ exports.Refrigerator = Refrigerator;
+ exports.Regex = Regex;
+ exports.RemoveFormatting = RemoveFormatting;
+ exports.Repeat = Repeat;
+ exports.Repeat1 = Repeat1;
+ exports.Repeat2 = Repeat2;
+ exports.Replace = Replace;
+ exports.ReplaceAll = ReplaceAll;
+ exports.Reply = Reply;
+ exports.ReplyAll = ReplyAll;
+ exports.Rewind = Rewind;
+ exports.Ribbon = Ribbon;
+ exports.Rocket = Rocket;
+ exports.RockingChair = RockingChair;
+ exports.RollerCoaster = RollerCoaster;
+ exports.Rose = Rose;
+ exports.Rotate3D = Rotate3d;
+ exports.Rotate3d = Rotate3d;
+ exports.RotateCcw = RotateCcw;
+ exports.RotateCcwKey = RotateCcwKey;
+ exports.RotateCcwSquare = RotateCcwSquare;
+ exports.RotateCw = RotateCw;
+ exports.RotateCwSquare = RotateCwSquare;
+ exports.Route = Route;
+ exports.RouteOff = RouteOff;
+ exports.Router = Router;
+ exports.Rows = Rows2;
+ exports.Rows2 = Rows2;
+ exports.Rows3 = Rows3;
+ exports.Rows4 = Rows4;
+ exports.Rss = Rss;
+ exports.Ruler = Ruler;
+ exports.RulerDimensionLine = RulerDimensionLine;
+ exports.RussianRuble = RussianRuble;
+ exports.Sailboat = Sailboat;
+ exports.Salad = Salad;
+ exports.Sandwich = Sandwich;
+ exports.Satellite = Satellite;
+ exports.SatelliteDish = SatelliteDish;
+ exports.SaudiRiyal = SaudiRiyal;
+ exports.Save = Save;
+ exports.SaveAll = SaveAll;
+ exports.SaveOff = SaveOff;
+ exports.Scale = Scale;
+ exports.Scale3D = Scale3d;
+ exports.Scale3d = Scale3d;
+ exports.Scaling = Scaling;
+ exports.Scan = Scan;
+ exports.ScanBarcode = ScanBarcode;
+ exports.ScanEye = ScanEye;
+ exports.ScanFace = ScanFace;
+ exports.ScanHeart = ScanHeart;
+ exports.ScanLine = ScanLine;
+ exports.ScanQrCode = ScanQrCode;
+ exports.ScanSearch = ScanSearch;
+ exports.ScanText = ScanText;
+ exports.ScatterChart = ChartScatter;
+ exports.School = School;
+ exports.School2 = University;
+ exports.Scissors = Scissors;
+ exports.ScissorsLineDashed = ScissorsLineDashed;
+ exports.ScissorsSquare = SquareScissors;
+ exports.ScissorsSquareDashedBottom = SquareBottomDashedScissors;
+ exports.ScreenShare = ScreenShare;
+ exports.ScreenShareOff = ScreenShareOff;
+ exports.Scroll = Scroll;
+ exports.ScrollText = ScrollText;
+ exports.Search = Search;
+ exports.SearchCheck = SearchCheck;
+ exports.SearchCode = SearchCode;
+ exports.SearchSlash = SearchSlash;
+ exports.SearchX = SearchX;
+ exports.Section = Section;
+ exports.Send = Send;
+ exports.SendHorizonal = SendHorizontal;
+ exports.SendHorizontal = SendHorizontal;
+ exports.SendToBack = SendToBack;
+ exports.SeparatorHorizontal = SeparatorHorizontal;
+ exports.SeparatorVertical = SeparatorVertical;
+ exports.Server = Server;
+ exports.ServerCog = ServerCog;
+ exports.ServerCrash = ServerCrash;
+ exports.ServerOff = ServerOff;
+ exports.Settings = Settings;
+ exports.Settings2 = Settings2;
+ exports.Shapes = Shapes;
+ exports.Share = Share;
+ exports.Share2 = Share2;
+ exports.Sheet = Sheet;
+ exports.Shell = Shell;
+ exports.Shield = Shield;
+ exports.ShieldAlert = ShieldAlert;
+ exports.ShieldBan = ShieldBan;
+ exports.ShieldCheck = ShieldCheck;
+ exports.ShieldClose = ShieldX;
+ exports.ShieldEllipsis = ShieldEllipsis;
+ exports.ShieldHalf = ShieldHalf;
+ exports.ShieldMinus = ShieldMinus;
+ exports.ShieldOff = ShieldOff;
+ exports.ShieldPlus = ShieldPlus;
+ exports.ShieldQuestion = ShieldQuestionMark;
+ exports.ShieldQuestionMark = ShieldQuestionMark;
+ exports.ShieldUser = ShieldUser;
+ exports.ShieldX = ShieldX;
+ exports.Ship = Ship;
+ exports.ShipWheel = ShipWheel;
+ exports.Shirt = Shirt;
+ exports.ShoppingBag = ShoppingBag;
+ exports.ShoppingBasket = ShoppingBasket;
+ exports.ShoppingCart = ShoppingCart;
+ exports.Shovel = Shovel;
+ exports.ShowerHead = ShowerHead;
+ exports.Shredder = Shredder;
+ exports.Shrimp = Shrimp;
+ exports.Shrink = Shrink;
+ exports.Shrub = Shrub;
+ exports.Shuffle = Shuffle;
+ exports.Sidebar = PanelLeft;
+ exports.SidebarClose = PanelLeftClose;
+ exports.SidebarOpen = PanelLeftOpen;
+ exports.Sigma = Sigma;
+ exports.SigmaSquare = SquareSigma;
+ exports.Signal = Signal;
+ exports.SignalHigh = SignalHigh;
+ exports.SignalLow = SignalLow;
+ exports.SignalMedium = SignalMedium;
+ exports.SignalZero = SignalZero;
+ exports.Signature = Signature;
+ exports.Signpost = Signpost;
+ exports.SignpostBig = SignpostBig;
+ exports.Siren = Siren;
+ exports.SkipBack = SkipBack;
+ exports.SkipForward = SkipForward;
+ exports.Skull = Skull;
+ exports.Slack = Slack;
+ exports.Slash = Slash;
+ exports.SlashSquare = SquareSlash;
+ exports.Slice = Slice;
+ exports.Sliders = SlidersVertical;
+ exports.SlidersHorizontal = SlidersHorizontal;
+ exports.SlidersVertical = SlidersVertical;
+ exports.Smartphone = Smartphone;
+ exports.SmartphoneCharging = SmartphoneCharging;
+ exports.SmartphoneNfc = SmartphoneNfc;
+ exports.Smile = Smile;
+ exports.SmilePlus = SmilePlus;
+ exports.Snail = Snail;
+ exports.Snowflake = Snowflake;
+ exports.SoapDispenserDroplet = SoapDispenserDroplet;
+ exports.Sofa = Sofa;
+ exports.SortAsc = ArrowUpNarrowWide;
+ exports.SortDesc = ArrowDownWideNarrow;
+ exports.Soup = Soup;
+ exports.Space = Space;
+ exports.Spade = Spade;
+ exports.Sparkle = Sparkle;
+ exports.Sparkles = Sparkles;
+ exports.Speaker = Speaker;
+ exports.Speech = Speech;
+ exports.SpellCheck = SpellCheck;
+ exports.SpellCheck2 = SpellCheck2;
+ exports.Spline = Spline;
+ exports.SplinePointer = SplinePointer;
+ exports.Split = Split;
+ exports.SplitSquareHorizontal = SquareSplitHorizontal;
+ exports.SplitSquareVertical = SquareSplitVertical;
+ exports.Spool = Spool;
+ exports.Spotlight = Spotlight;
+ exports.SprayCan = SprayCan;
+ exports.Sprout = Sprout;
+ exports.Square = Square;
+ exports.SquareActivity = SquareActivity;
+ exports.SquareArrowDown = SquareArrowDown;
+ exports.SquareArrowDownLeft = SquareArrowDownLeft;
+ exports.SquareArrowDownRight = SquareArrowDownRight;
+ exports.SquareArrowLeft = SquareArrowLeft;
+ exports.SquareArrowOutDownLeft = SquareArrowOutDownLeft;
+ exports.SquareArrowOutDownRight = SquareArrowOutDownRight;
+ exports.SquareArrowOutUpLeft = SquareArrowOutUpLeft;
+ exports.SquareArrowOutUpRight = SquareArrowOutUpRight;
+ exports.SquareArrowRight = SquareArrowRight;
+ exports.SquareArrowUp = SquareArrowUp;
+ exports.SquareArrowUpLeft = SquareArrowUpLeft;
+ exports.SquareArrowUpRight = SquareArrowUpRight;
+ exports.SquareAsterisk = SquareAsterisk;
+ exports.SquareBottomDashedScissors = SquareBottomDashedScissors;
+ exports.SquareChartGantt = SquareChartGantt;
+ exports.SquareCheck = SquareCheck;
+ exports.SquareCheckBig = SquareCheckBig;
+ exports.SquareChevronDown = SquareChevronDown;
+ exports.SquareChevronLeft = SquareChevronLeft;
+ exports.SquareChevronRight = SquareChevronRight;
+ exports.SquareChevronUp = SquareChevronUp;
+ exports.SquareCode = SquareCode;
+ exports.SquareDashed = SquareDashed;
+ exports.SquareDashedBottom = SquareDashedBottom;
+ exports.SquareDashedBottomCode = SquareDashedBottomCode;
+ exports.SquareDashedKanban = SquareDashedKanban;
+ exports.SquareDashedMousePointer = SquareDashedMousePointer;
+ exports.SquareDashedTopSolid = SquareDashedTopSolid;
+ exports.SquareDivide = SquareDivide;
+ exports.SquareDot = SquareDot;
+ exports.SquareEqual = SquareEqual;
+ exports.SquareFunction = SquareFunction;
+ exports.SquareGanttChart = SquareChartGantt;
+ exports.SquareKanban = SquareKanban;
+ exports.SquareLibrary = SquareLibrary;
+ exports.SquareM = SquareM;
+ exports.SquareMenu = SquareMenu;
+ exports.SquareMinus = SquareMinus;
+ exports.SquareMousePointer = SquareMousePointer;
+ exports.SquareParking = SquareParking;
+ exports.SquareParkingOff = SquareParkingOff;
+ exports.SquarePause = SquarePause;
+ exports.SquarePen = SquarePen;
+ exports.SquarePercent = SquarePercent;
+ exports.SquarePi = SquarePi;
+ exports.SquarePilcrow = SquarePilcrow;
+ exports.SquarePlay = SquarePlay;
+ exports.SquarePlus = SquarePlus;
+ exports.SquarePower = SquarePower;
+ exports.SquareRadical = SquareRadical;
+ exports.SquareRoundCorner = SquareRoundCorner;
+ exports.SquareScissors = SquareScissors;
+ exports.SquareSigma = SquareSigma;
+ exports.SquareSlash = SquareSlash;
+ exports.SquareSplitHorizontal = SquareSplitHorizontal;
+ exports.SquareSplitVertical = SquareSplitVertical;
+ exports.SquareSquare = SquareSquare;
+ exports.SquareStack = SquareStack;
+ exports.SquareStar = SquareStar;
+ exports.SquareStop = SquareStop;
+ exports.SquareTerminal = SquareTerminal;
+ exports.SquareUser = SquareUser;
+ exports.SquareUserRound = SquareUserRound;
+ exports.SquareX = SquareX;
+ exports.SquaresExclude = SquaresExclude;
+ exports.SquaresIntersect = SquaresIntersect;
+ exports.SquaresSubtract = SquaresSubtract;
+ exports.SquaresUnite = SquaresUnite;
+ exports.Squircle = Squircle;
+ exports.SquircleDashed = SquircleDashed;
+ exports.Squirrel = Squirrel;
+ exports.Stamp = Stamp;
+ exports.Star = Star;
+ exports.StarHalf = StarHalf;
+ exports.StarOff = StarOff;
+ exports.Stars = Sparkles;
+ exports.StepBack = StepBack;
+ exports.StepForward = StepForward;
+ exports.Stethoscope = Stethoscope;
+ exports.Sticker = Sticker;
+ exports.StickyNote = StickyNote;
+ exports.StopCircle = CircleStop;
+ exports.Store = Store;
+ exports.StretchHorizontal = StretchHorizontal;
+ exports.StretchVertical = StretchVertical;
+ exports.Strikethrough = Strikethrough;
+ exports.Subscript = Subscript;
+ exports.Subtitles = Captions;
+ exports.Sun = Sun;
+ exports.SunDim = SunDim;
+ exports.SunMedium = SunMedium;
+ exports.SunMoon = SunMoon;
+ exports.SunSnow = SunSnow;
+ exports.Sunrise = Sunrise;
+ exports.Sunset = Sunset;
+ exports.Superscript = Superscript;
+ exports.SwatchBook = SwatchBook;
+ exports.SwissFranc = SwissFranc;
+ exports.SwitchCamera = SwitchCamera;
+ exports.Sword = Sword;
+ exports.Swords = Swords;
+ exports.Syringe = Syringe;
+ exports.Table = Table;
+ exports.Table2 = Table2;
+ exports.TableCellsMerge = TableCellsMerge;
+ exports.TableCellsSplit = TableCellsSplit;
+ exports.TableColumnsSplit = TableColumnsSplit;
+ exports.TableConfig = Columns3Cog;
+ exports.TableOfContents = TableOfContents;
+ exports.TableProperties = TableProperties;
+ exports.TableRowsSplit = TableRowsSplit;
+ exports.Tablet = Tablet;
+ exports.TabletSmartphone = TabletSmartphone;
+ exports.Tablets = Tablets;
+ exports.Tag = Tag;
+ exports.Tags = Tags;
+ exports.Tally1 = Tally1;
+ exports.Tally2 = Tally2;
+ exports.Tally3 = Tally3;
+ exports.Tally4 = Tally4;
+ exports.Tally5 = Tally5;
+ exports.Tangent = Tangent;
+ exports.Target = Target;
+ exports.Telescope = Telescope;
+ exports.Tent = Tent;
+ exports.TentTree = TentTree;
+ exports.Terminal = Terminal;
+ exports.TerminalSquare = SquareTerminal;
+ exports.TestTube = TestTube;
+ exports.TestTube2 = TestTubeDiagonal;
+ exports.TestTubeDiagonal = TestTubeDiagonal;
+ exports.TestTubes = TestTubes;
+ exports.Text = TextAlignStart;
+ exports.TextAlignCenter = TextAlignCenter;
+ exports.TextAlignEnd = TextAlignEnd;
+ exports.TextAlignJustify = TextAlignJustify;
+ exports.TextAlignStart = TextAlignStart;
+ exports.TextCursor = TextCursor;
+ exports.TextCursorInput = TextCursorInput;
+ exports.TextInitial = TextInitial;
+ exports.TextQuote = TextQuote;
+ exports.TextSearch = TextSearch;
+ exports.TextSelect = TextSelect;
+ exports.TextSelection = TextSelect;
+ exports.TextWrap = TextWrap;
+ exports.Theater = Theater;
+ exports.Thermometer = Thermometer;
+ exports.ThermometerSnowflake = ThermometerSnowflake;
+ exports.ThermometerSun = ThermometerSun;
+ exports.ThumbsDown = ThumbsDown;
+ exports.ThumbsUp = ThumbsUp;
+ exports.Ticket = Ticket;
+ exports.TicketCheck = TicketCheck;
+ exports.TicketMinus = TicketMinus;
+ exports.TicketPercent = TicketPercent;
+ exports.TicketPlus = TicketPlus;
+ exports.TicketSlash = TicketSlash;
+ exports.TicketX = TicketX;
+ exports.Tickets = Tickets;
+ exports.TicketsPlane = TicketsPlane;
+ exports.Timer = Timer;
+ exports.TimerOff = TimerOff;
+ exports.TimerReset = TimerReset;
+ exports.ToggleLeft = ToggleLeft;
+ exports.ToggleRight = ToggleRight;
+ exports.Toilet = Toilet;
+ exports.ToolCase = ToolCase;
+ exports.Tornado = Tornado;
+ exports.Torus = Torus;
+ exports.Touchpad = Touchpad;
+ exports.TouchpadOff = TouchpadOff;
+ exports.TowerControl = TowerControl;
+ exports.ToyBrick = ToyBrick;
+ exports.Tractor = Tractor;
+ exports.TrafficCone = TrafficCone;
+ exports.Train = TramFront;
+ exports.TrainFront = TrainFront;
+ exports.TrainFrontTunnel = TrainFrontTunnel;
+ exports.TrainTrack = TrainTrack;
+ exports.TramFront = TramFront;
+ exports.Transgender = Transgender;
+ exports.Trash = Trash;
+ exports.Trash2 = Trash2;
+ exports.TreeDeciduous = TreeDeciduous;
+ exports.TreePalm = TreePalm;
+ exports.TreePine = TreePine;
+ exports.Trees = Trees;
+ exports.Trello = Trello;
+ exports.TrendingDown = TrendingDown;
+ exports.TrendingUp = TrendingUp;
+ exports.TrendingUpDown = TrendingUpDown;
+ exports.Triangle = Triangle;
+ exports.TriangleAlert = TriangleAlert;
+ exports.TriangleDashed = TriangleDashed;
+ exports.TriangleRight = TriangleRight;
+ exports.Trophy = Trophy;
+ exports.Truck = Truck;
+ exports.TruckElectric = TruckElectric;
+ exports.TurkishLira = TurkishLira;
+ exports.Turntable = Turntable;
+ exports.Turtle = Turtle;
+ exports.Tv = Tv;
+ exports.Tv2 = TvMinimal;
+ exports.TvMinimal = TvMinimal;
+ exports.TvMinimalPlay = TvMinimalPlay;
+ exports.Twitch = Twitch;
+ exports.Twitter = Twitter;
+ exports.Type = Type;
+ exports.TypeOutline = TypeOutline;
+ exports.Umbrella = Umbrella;
+ exports.UmbrellaOff = UmbrellaOff;
+ exports.Underline = Underline;
+ exports.Undo = Undo;
+ exports.Undo2 = Undo2;
+ exports.UndoDot = UndoDot;
+ exports.UnfoldHorizontal = UnfoldHorizontal;
+ exports.UnfoldVertical = UnfoldVertical;
+ exports.Ungroup = Ungroup;
+ exports.University = University;
+ exports.Unlink = Unlink;
+ exports.Unlink2 = Unlink2;
+ exports.Unlock = LockOpen;
+ exports.UnlockKeyhole = LockKeyholeOpen;
+ exports.Unplug = Unplug;
+ exports.Upload = Upload;
+ exports.UploadCloud = CloudUpload;
+ exports.Usb = Usb;
+ exports.User = User;
+ exports.User2 = UserRound;
+ exports.UserCheck = UserCheck;
+ exports.UserCheck2 = UserRoundCheck;
+ exports.UserCircle = CircleUser;
+ exports.UserCircle2 = CircleUserRound;
+ exports.UserCog = UserCog;
+ exports.UserCog2 = UserRoundCog;
+ exports.UserLock = UserLock;
+ exports.UserMinus = UserMinus;
+ exports.UserMinus2 = UserRoundMinus;
+ exports.UserPen = UserPen;
+ exports.UserPlus = UserPlus;
+ exports.UserPlus2 = UserRoundPlus;
+ exports.UserRound = UserRound;
+ exports.UserRoundCheck = UserRoundCheck;
+ exports.UserRoundCog = UserRoundCog;
+ exports.UserRoundMinus = UserRoundMinus;
+ exports.UserRoundPen = UserRoundPen;
+ exports.UserRoundPlus = UserRoundPlus;
+ exports.UserRoundSearch = UserRoundSearch;
+ exports.UserRoundX = UserRoundX;
+ exports.UserSearch = UserSearch;
+ exports.UserSquare = SquareUser;
+ exports.UserSquare2 = SquareUserRound;
+ exports.UserStar = UserStar;
+ exports.UserX = UserX;
+ exports.UserX2 = UserRoundX;
+ exports.Users = Users;
+ exports.Users2 = UsersRound;
+ exports.UsersRound = UsersRound;
+ exports.Utensils = Utensils;
+ exports.UtensilsCrossed = UtensilsCrossed;
+ exports.UtilityPole = UtilityPole;
+ exports.Variable = Variable;
+ exports.Vault = Vault;
+ exports.VectorSquare = VectorSquare;
+ exports.Vegan = Vegan;
+ exports.VenetianMask = VenetianMask;
+ exports.Venus = Venus;
+ exports.VenusAndMars = VenusAndMars;
+ exports.Verified = BadgeCheck;
+ exports.Vibrate = Vibrate;
+ exports.VibrateOff = VibrateOff;
+ exports.Video = Video;
+ exports.VideoOff = VideoOff;
+ exports.Videotape = Videotape;
+ exports.View = View;
+ exports.Voicemail = Voicemail;
+ exports.Volleyball = Volleyball;
+ exports.Volume = Volume;
+ exports.Volume1 = Volume1;
+ exports.Volume2 = Volume2;
+ exports.VolumeOff = VolumeOff;
+ exports.VolumeX = VolumeX;
+ exports.Vote = Vote;
+ exports.Wallet = Wallet;
+ exports.Wallet2 = WalletMinimal;
+ exports.WalletCards = WalletCards;
+ exports.WalletMinimal = WalletMinimal;
+ exports.Wallpaper = Wallpaper;
+ exports.Wand = Wand;
+ exports.Wand2 = WandSparkles;
+ exports.WandSparkles = WandSparkles;
+ exports.Warehouse = Warehouse;
+ exports.WashingMachine = WashingMachine;
+ exports.Watch = Watch;
+ exports.Waves = Waves;
+ exports.WavesLadder = WavesLadder;
+ exports.Waypoints = Waypoints;
+ exports.Webcam = Webcam;
+ exports.Webhook = Webhook;
+ exports.WebhookOff = WebhookOff;
+ exports.Weight = Weight;
+ exports.Wheat = Wheat;
+ exports.WheatOff = WheatOff;
+ exports.WholeWord = WholeWord;
+ exports.Wifi = Wifi;
+ exports.WifiCog = WifiCog;
+ exports.WifiHigh = WifiHigh;
+ exports.WifiLow = WifiLow;
+ exports.WifiOff = WifiOff;
+ exports.WifiPen = WifiPen;
+ exports.WifiSync = WifiSync;
+ exports.WifiZero = WifiZero;
+ exports.Wind = Wind;
+ exports.WindArrowDown = WindArrowDown;
+ exports.Wine = Wine;
+ exports.WineOff = WineOff;
+ exports.Workflow = Workflow;
+ exports.Worm = Worm;
+ exports.WrapText = TextWrap;
+ exports.Wrench = Wrench;
+ exports.X = X;
+ exports.XCircle = CircleX;
+ exports.XOctagon = OctagonX;
+ exports.XSquare = SquareX;
+ exports.Youtube = Youtube;
+ exports.Zap = Zap;
+ exports.ZapOff = ZapOff;
+ exports.ZoomIn = ZoomIn;
+ exports.ZoomOut = ZoomOut;
+ exports.createElement = createElement;
+ exports.createIcons = createIcons;
+ exports.icons = iconAndAliases;
+
+}));
+//# sourceMappingURL=lucide.js.map
diff --git a/app/src/atividades/letramento/shared/tailwind-input.css b/app/src/atividades/letramento/shared/tailwind-input.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/app/src/atividades/letramento/shared/tailwind-input.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/app/src/atividades/letramento/teclado/atividade-final/activity.js b/app/src/atividades/letramento/teclado/atividade-final/activity.js
new file mode 100644
index 0000000..3cf2c44
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/atividade-final/activity.js
@@ -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 = ` `;
+ // 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();
+});
diff --git a/app/src/atividades/letramento/teclado/atividade-final/index.html b/app/src/atividades/letramento/teclado/atividade-final/index.html
new file mode 100644
index 0000000..55da857
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/atividade-final/index.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Atividade Final
+
+
+
+
+
+
+
+
+
+
+
Atividade Final
+
Complete todas as etapas e integre habilidades do teclado.
+
+
+
+
+
+
+
+
+
+
Parabéns!
+
Você concluiu a Atividade Final do teclado!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/chuva/activity.js b/app/src/atividades/letramento/teclado/chuva/activity.js
new file mode 100644
index 0000000..5b87723
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/chuva/activity.js
@@ -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);
diff --git a/app/src/atividades/letramento/teclado/chuva/index.html b/app/src/atividades/letramento/teclado/chuva/index.html
new file mode 100644
index 0000000..810270a
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/chuva/index.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Chuva de Letras
+
+
+
+
+
+
+
+
+
+
+
Chuva de Letras
+
Digite exatamente o alvo da tela. A atividade é sensível a maiúsculas e minúsculas.
+
+
+
+
+
+
+
+
+
+
+
Você venceu!
+
Você concluiu a bateria completa da chuva de caracteres.
+
+
+
+
+
+
+
Precisão insuficiente!
+
Você não atingiu 60% nesta etapa.
+
+
+
+ Tentar esta etapa novamente
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/enter-esc/activity.js b/app/src/atividades/letramento/teclado/enter-esc/activity.js
new file mode 100644
index 0000000..84b6099
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/enter-esc/activity.js
@@ -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 dialogs = [
+ {
+ title: 'Salvar arquivo',
+ question: 'Deseja salvar o arquivo?',
+ instruction: 'Pressione Enter para SIM (salvar) ou Esc para NÃO (cancelar)',
+ expected: 'Enter',
+ feedback: 'Arquivo salvo! Enter confirma!',
+ },
+ {
+ title: 'Fechar janela',
+ question: 'Tem certeza que quer fechar?',
+ instruction: 'Pressione Esc para fechar (cancelar a ação)',
+ expected: 'Escape',
+ feedback: 'Janela fechada! Esc cancela!',
+ },
+ {
+ title: 'Enviar mensagem',
+ question: 'Enviar esta mensagem?',
+ instruction: 'Pressione Enter para confirmar o envio',
+ expected: 'Enter',
+ feedback: 'Mensagem enviada! Enter confirma!',
+ },
+ {
+ title: 'Descartar rascunho',
+ question: 'Apagar o rascunho sem salvar?',
+ instruction: 'Pressione Esc para cancelar (não apagar)',
+ expected: 'Escape',
+ feedback: 'Rascunho mantido! Esc cancela a ação!',
+ },
+];
+
+let currentDialog = 0;
+notify('started');
+
+const dialogTitle = document.getElementById('dialogTitle');
+const dialogQuestion = document.getElementById('dialogQuestion');
+const dialogInstruction = document.getElementById('dialogInstruction');
+const feedbackEl = document.getElementById('feedback');
+const progressDots = document.getElementById('progressDots');
+const successBanner = document.getElementById('successBanner');
+const dialogBox = document.getElementById('dialogBox');
+let locked = false;
+
+dialogs.forEach((_, i) => {
+ const dot = document.createElement('div');
+ dot.id = `dot-${i}`;
+ dot.className = 'w-8 h-8 rounded-full border-2 border-gray-300 bg-gray-100 transition-all';
+ progressDots.appendChild(dot);
+});
+
+function showDialog() {
+ const d = dialogs[currentDialog];
+ dialogTitle.textContent = d.title;
+ dialogQuestion.textContent = d.question;
+ dialogInstruction.textContent = d.instruction;
+ feedbackEl.classList.add('hidden');
+ locked = false;
+}
+showDialog();
+
+document.addEventListener('keydown', (e) => {
+ if (locked) return;
+ if (e.key !== 'Enter' && e.key !== 'Escape') return;
+ const d = dialogs[currentDialog];
+ if (e.key === d.expected) {
+ locked = true;
+ feedbackEl.textContent = d.feedback;
+ feedbackEl.className = 'text-2xl font-bold py-1 text-center text-green-600';
+ feedbackEl.classList.remove('hidden');
+ const dot = document.getElementById(`dot-${currentDialog}`);
+ if (dot) dot.className = 'w-8 h-8 rounded-full bg-green-400 border-2 border-green-500 transition-all';
+ notify('running', { step: currentDialog + 1 });
+ currentDialog++;
+ setTimeout(() => {
+ if (currentDialog >= dialogs.length) {
+ dialogBox.classList.add('hidden');
+ progressDots.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ } else {
+ showDialog();
+ }
+ }, 1000);
+ } else {
+ feedbackEl.textContent = e.key === 'Enter' ? 'Aqui não é Enter! Use Esc.' : 'Aqui não é Esc! Use Enter.';
+ feedbackEl.className = 'text-2xl font-bold py-1 text-center text-red-500';
+ feedbackEl.classList.remove('hidden');
+ }
+});
diff --git a/app/src/atividades/letramento/teclado/enter-esc/index.html b/app/src/atividades/letramento/teclado/enter-esc/index.html
new file mode 100644
index 0000000..a197db5
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/enter-esc/index.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Confirmar e Cancelar
+
+
+
+
+
+
+
+
+
+
+
Confirmar e Cancelar
+
Enter confirma. Esc cancela. Aprenda a diferença!
+
+
+
+
+
+
Enter ↵
+
Confirma / Ok / Envia
+
+
+
Esc
+
Cancela / Fecha / Não
+
+
+
+
+
+
+
+
Janela de diálogo
+
+
+
+
+
+
+
+
+
Muito bem!
+
Agora você sabe como confirmar e cancelar ações no computador!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/labirinto/activity.js b/app/src/atividades/letramento/teclado/labirinto/activity.js
new file mode 100644
index 0000000..655d6b3
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/labirinto/activity.js
@@ -0,0 +1,190 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+const LEVELS = [
+ [
+ '#######',
+ '#S...E#',
+ '#.###.#',
+ '#.....#',
+ '#######',
+ ],
+ [
+ '########',
+ '#S....k#',
+ '#.##D###',
+ '#..#..E#',
+ '########',
+ ],
+ [
+ '#########',
+ '#S......#',
+ '#.###D###',
+ '#...#...#',
+ '#.#.###.#',
+ '#..k#..E#',
+ '#########',
+ ],
+ [
+ '##########',
+ '#S..#....#',
+ '#.#.#.##D#',
+ '#.#...#..#',
+ '#.#####.##',
+ '#.....#..#',
+ '#.###k#E.#',
+ '##########',
+ ],
+ [
+ '###########',
+ '#S...#...E#',
+ '#.#.#.#.###',
+ '#.#...#...#',
+ '#.#####.#.#',
+ '#.....#.#.#',
+ '###.#.###.#',
+ '#k..#.D...#',
+ '###########',
+ ],
+];
+
+let levelIndex = 0;
+let grid = [];
+let pr = 0;
+let pc = 0;
+let hasKey = false;
+let openedDoors = 0;
+let levelHasDoor = false;
+let done = false;
+
+notify('started');
+
+const mazeEl = document.getElementById('maze');
+const feedbackEl = document.getElementById('feedback');
+const successBanner = document.getElementById('successBanner');
+const mazeInfo = document.getElementById('mazeInfo');
+
+function loadLevel(index) {
+ grid = LEVELS[index].map((row) => row.split(''));
+ levelHasDoor = false;
+ for (let r = 0; r < grid.length; r += 1) {
+ for (let c = 0; c < grid[r].length; c += 1) {
+ if (grid[r][c] === 'S') {
+ pr = r;
+ pc = c;
+ }
+ if (grid[r][c] === 'D') {
+ levelHasDoor = true;
+ }
+ }
+ }
+ hasKey = false;
+ openedDoors = 0;
+ feedbackEl.classList.add('hidden');
+ mazeInfo.textContent = 'Use as setas para chegar à saída';
+}
+
+function buildMaze() {
+ mazeEl.innerHTML = '';
+ grid.forEach((row, r) => {
+ const rowEl = document.createElement('div');
+ rowEl.style.display = 'flex';
+ row.forEach((cell, c) => {
+ const el = document.createElement('div');
+ el.className = 'cell';
+ if (r === pr && c === pc) {
+ el.innerHTML = 'P
';
+ el.style.background = '#ecfdf5';
+ } else if (cell === '#') {
+ el.style.background = '#374151';
+ } else if (cell === 'E') {
+ el.innerHTML = 'S
';
+ el.style.background = '#fef9c3';
+ } else if (cell === 'k') {
+ el.innerHTML = '
';
+ el.style.background = '#fffbeb';
+ } else if (cell === 'D') {
+ el.innerHTML = '
';
+ el.style.background = '#fffbeb';
+ } else {
+ el.style.background = '#f9fafb';
+ }
+ rowEl.appendChild(el);
+ });
+ mazeEl.appendChild(rowEl);
+ });
+ lucide.createIcons();
+}
+loadLevel(levelIndex);
+buildMaze();
+
+document.addEventListener('keydown', (e) => {
+ if (done) return;
+ const moves = { ArrowUp: [-1,0], ArrowDown: [1,0], ArrowLeft: [0,-1], ArrowRight: [0,1] };
+ if (!moves[e.key]) return;
+ e.preventDefault();
+ const [dr, dc] = moves[e.key];
+ const nr = pr + dr, nc = pc + dc;
+ if (nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length) return;
+ const cell = grid[nr][nc];
+ if (cell === '#') {
+ feedbackEl.textContent = 'Parede! Escolha outra direção.';
+ feedbackEl.className = 'text-xl font-bold py-1 text-center text-orange-500';
+ feedbackEl.classList.remove('hidden');
+ return;
+ }
+ if (cell === 'D' && !hasKey) {
+ feedbackEl.textContent = 'Porta trancada! Encontre a chave.';
+ feedbackEl.className = 'text-xl font-bold py-1 text-center text-amber-600';
+ feedbackEl.classList.remove('hidden');
+ return;
+ }
+
+ feedbackEl.classList.add('hidden');
+ if (cell === 'k') {
+ hasKey = true;
+ mazeInfo.textContent = 'Chave coletada! Agora abra a porta.';
+ grid[nr][nc] = '.';
+ }
+ if (cell === 'D' && hasKey) {
+ openedDoors += 1;
+ grid[nr][nc] = '.';
+ mazeInfo.textContent = 'Porta desbloqueada! Siga até a saída.';
+ }
+
+ if (cell === 'E' && levelHasDoor && openedDoors === 0) {
+ feedbackEl.textContent = 'A saída está bloqueada. Abra a porta primeiro.';
+ feedbackEl.className = 'text-xl font-bold py-1 text-center text-amber-600';
+ feedbackEl.classList.remove('hidden');
+ return;
+ }
+
+ pr = nr; pc = nc;
+ buildMaze();
+ notify('running', { step: levelIndex + 1 });
+
+ if (cell === 'E') {
+ if (levelIndex === LEVELS.length - 1) {
+ done = true;
+ mazeEl.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ return;
+ }
+
+ levelIndex += 1;
+ feedbackEl.textContent = `Fase ${levelIndex} concluída!`;
+ feedbackEl.className = 'text-xl font-bold py-1 text-center text-green-600';
+ feedbackEl.classList.remove('hidden');
+ notify('running', { step: levelIndex + 1 });
+ setTimeout(() => {
+ loadLevel(levelIndex);
+ buildMaze();
+ }, 700);
+ }
+});
diff --git a/app/src/atividades/letramento/teclado/labirinto/index.html b/app/src/atividades/letramento/teclado/labirinto/index.html
new file mode 100644
index 0000000..5eadc5f
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/labirinto/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+ Labirinto das Setas
+
+
+
+
+
+
+
+
+
+
+
Labirinto das Setas
+
Complete 5 mapas com progressão de dificuldade, coletando chaves para abrir portas.
+
+
+
+
+
+
Use as setas para chegar à saída
+
+
+
+
+
Labirintos concluídos!
+
Você dominou setas, chaves e portas.
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/recado-completo/activity.js b/app/src/atividades/letramento/teclado/recado-completo/activity.js
new file mode 100644
index 0000000..25d4721
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/recado-completo/activity.js
@@ -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 }, '*');
+}
+notify('started');
+
+const messageArea = document.getElementById('messageArea');
+const confirmBtn = document.getElementById('confirmBtn');
+const successBanner = document.getElementById('successBanner');
+const messagePreview = document.getElementById('messagePreview');
+const reqWords = document.getElementById('req-words');
+const reqUpper = document.getElementById('req-upper');
+const reqPeriod = document.getElementById('req-period');
+
+messageArea.focus();
+
+function setReq(el, ok) {
+ el.className = ok
+ ? 'px-4 py-2 rounded-full border-2 border-green-400 text-sm font-bold text-green-700 bg-green-50 transition-all'
+ : 'px-4 py-2 rounded-full border-2 border-gray-300 text-sm font-bold text-gray-600 transition-all';
+}
+
+messageArea.addEventListener('input', () => {
+ const v = messageArea.value.trim();
+ const words = v.split(/\s+/).filter(w => w.length > 0);
+ const hasWords = words.length >= 3;
+ const hasUpper = v.length > 0 && v[0] === v[0].toUpperCase() && /[A-Za-zÀ-ú]/.test(v[0]);
+ const hasPeriod = v.endsWith('.');
+
+ setReq(reqWords, hasWords);
+ setReq(reqUpper, hasUpper);
+ setReq(reqPeriod, hasPeriod);
+
+ if (hasWords && hasUpper && hasPeriod) {
+ confirmBtn.classList.remove('hidden');
+ notify('running', { step: 1 });
+ } else {
+ confirmBtn.classList.add('hidden');
+ }
+});
+
+confirmBtn.addEventListener('click', () => {
+ const v = messageArea.value.trim();
+ messagePreview.textContent = `"${v}"`;
+ messageArea.parentElement.classList.add('hidden');
+ document.getElementById('reqChecklist').classList.add('hidden');
+ confirmBtn.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+});
diff --git a/app/src/atividades/letramento/teclado/recado-completo/index.html b/app/src/atividades/letramento/teclado/recado-completo/index.html
new file mode 100644
index 0000000..e8caf1c
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/recado-completo/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Texto Coletivo
+
+
+
+
+
+
+
+
+
+
+
Texto Coletivo
+
Escreva uma mensagem sobre tecnologia, comunidade e soberania digital.
+
+
+
+
+
+ Escreva uma mensagem curta com: letra maiúscula no início , pelo menos 3 palavras e ponto final .
+
+
+
+
Mínimo 3 palavras
+
Começa com Maiúscula
+
Termina com ponto
+
+
+
+
+
+ Enviar Recado ✓
+
+
+
+
Recado enviado!
+
+
Você concluiu a etapa de escrita coletiva. Parabéns!
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/setas-texto/activity.js b/app/src/atividades/letramento/teclado/setas-texto/activity.js
new file mode 100644
index 0000000..27e90e4
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/setas-texto/activity.js
@@ -0,0 +1,84 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+const tasks = [
+ {
+ initial: 'Tecnologia livr fortalece comunidades',
+ expected: 'Tecnologia livre fortalece comunidades',
+ text: 'Falta uma letra no meio da palavra. Use as setas para posicionar o cursor e corrigir.',
+ hint: 'Mova com → até depois de "livr" e adicione "e".',
+ },
+ {
+ initial: 'Soberania digital protege os ddados da comunidade',
+ expected: 'Soberania digital protege os dados da comunidade',
+ text: 'Há uma letra repetida. Volte com as setas e corrija sem apagar tudo.',
+ hint: 'Use ← para voltar até "ddados" e deixe apenas "dados".',
+ },
+];
+
+let currentTask = 0;
+notify('started');
+
+const taskText = document.getElementById('taskText');
+const taskHint = document.getElementById('taskHint');
+const expectedText = document.getElementById('expectedText');
+const editInput = document.getElementById('editInput');
+const feedbackEl = document.getElementById('feedback');
+const cursorPos = document.getElementById('cursorPos');
+const progressDots = document.getElementById('progressDots');
+const successBanner = document.getElementById('successBanner');
+const taskBox = document.getElementById('taskBox');
+
+tasks.forEach((_, i) => {
+ const dot = document.createElement('div');
+ dot.id = `dot-${i}`;
+ dot.className = 'w-8 h-8 rounded-full border-2 border-gray-300 bg-gray-100 transition-all';
+ progressDots.appendChild(dot);
+});
+
+function showTask() {
+ const t = tasks[currentTask];
+ taskText.textContent = t.text;
+ taskHint.textContent = t.hint;
+ expectedText.textContent = t.expected;
+ editInput.value = t.initial;
+ editInput.setSelectionRange(0, 0);
+ feedbackEl.classList.add('hidden');
+ editInput.focus();
+}
+showTask();
+
+editInput.addEventListener('keyup', () => {
+ cursorPos.textContent = editInput.selectionStart;
+});
+editInput.addEventListener('click', () => {
+ cursorPos.textContent = editInput.selectionStart;
+});
+
+editInput.addEventListener('input', () => {
+ const t = tasks[currentTask];
+ if (editInput.value === t.expected) {
+ feedbackEl.textContent = 'Correto!';
+ feedbackEl.className = 'text-2xl font-bold py-1 text-center text-green-600';
+ feedbackEl.classList.remove('hidden');
+ const dot = document.getElementById(`dot-${currentTask}`);
+ if (dot) dot.className = 'w-8 h-8 rounded-full bg-green-400 border-2 border-green-500 transition-all';
+ notify('running', { step: currentTask + 1 });
+ currentTask++;
+ setTimeout(() => {
+ if (currentTask >= tasks.length) {
+ taskBox.classList.add('hidden');
+ progressDots.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ } else {
+ showTask();
+ }
+ }, 800);
+ }
+});
diff --git a/app/src/atividades/letramento/teclado/setas-texto/index.html b/app/src/atividades/letramento/teclado/setas-texto/index.html
new file mode 100644
index 0000000..b8aee3d
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/setas-texto/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ Setas no Texto
+
+
+
+
+
+
+
+
+
+
+
Setas no Texto
+
Use ← e → para corrigir frases sobre tecnologia e cidadania digital.
+
+
+
+
+
+ As setas ← → movem o cursor sem apagar nada . Volte ao meio do texto para corrigir letras com precisão.
+
+
+
+
+
+
+
Resultado esperado:
+
+
+
+
Posição do cursor: 0
+
+
+
+
+
+
+
Parabéns!
+
Você navegou no texto usando apenas as setas do teclado.
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-backspace/activity.js b/app/src/atividades/letramento/teclado/teclado-backspace/activity.js
new file mode 100644
index 0000000..8cea5d9
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-backspace/activity.js
@@ -0,0 +1,129 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+const phases = [
+ { label: 'Fase 1 — Uma letra extra', badgeClass: 'bg-blue-100 text-blue-700', barClass: 'bg-blue-400' },
+ { label: 'Fase 2 — Letra no meio', badgeClass: 'bg-indigo-100 text-indigo-700', barClass: 'bg-indigo-400' },
+ { label: 'Fase 3 — Apagar e redigitar', badgeClass: 'bg-orange-100 text-orange-700', barClass: 'bg-orange-400' },
+ { label: 'Fase 4 — Duas correções', badgeClass: 'bg-red-100 text-red-700', barClass: 'bg-red-400' },
+ { label: 'Fase 5 — Frases longas', badgeClass: 'bg-purple-100 text-purple-700', barClass: 'bg-purple-500' },
+];
+
+const tasks = [
+ // Fase 1 – Uma letra extra no final
+ { error: 'Dadoss', correct: 'Dados', hint: 'Apague a letra repetida no final.' },
+ { error: 'Redee', correct: 'Rede', hint: 'Há um "e" extra no final.' },
+ { error: 'Codigoo', correct: 'Código', hint: 'Apague o "o" repetido, cuidado com o acento.' },
+ { error: 'Aprendisado', correct: 'Aprendizado', hint: 'Corrija a palavra "Aprendisado".' },
+ { error: 'Auulass', correct: 'Aulas', hint: 'Apague o "u" e "s" duplicados.' },
+ { error: 'Comunidadde', correct: 'Comunidade', hint: 'A palavra tem uma letra repetida no final.' },
+
+ // Fase 2 – Letra extra em posição variada
+ { error: 'Tecnoologia livre', correct: 'Tecnologia livre', hint: 'Corrija a palavra "Tecnoologia".' },
+ { error: 'Soberannia digital', correct: 'Soberania digital', hint: 'Há um "n" a mais em "Soberannia".' },
+ { error: 'Internet para toddos', correct: 'Internet para todos', hint: 'Corrija a palavra "toddos".' },
+ { error: 'Laboratóriio de código', correct: 'Laboratório de código', hint: 'Um "i" está sobrando.' },
+ { error: 'Educaçãao popular', correct: 'Educação popular', hint: 'Apague a letra extra em "Educaçãao".' },
+ { error: 'Tecnologia para a comunnidade', correct: 'Tecnologia para a comunidade', hint: 'Há um "n" extra em "comunnidade".' },
+
+ // Fase 3 – Apagar tudo e redigitar
+ { error: 'SOBERANIA DIGITAL', correct: 'soberania digital', hint: 'Apague tudo e redigite em minúsculas.' },
+ { error: 'dados abertos', correct: 'DADOS ABERTOS', hint: 'Reescreva em letras maiúsculas.' },
+ { error: 'REDE COMUNITÁRIA', correct: 'rede comunitária', hint: 'Reescreva em letras minúsculas.' },
+ { error: 'tecnologia social', correct: 'TECNOLOGIA SOCIAL', hint: 'Apague e redigite em MAIÚSCULAS.' },
+ { error: 'CIDADANIA DIGITAL', correct: 'cidadania digital', hint: 'Apague e redigite em minúsculas.' },
+
+ // Fase 4 – Frases com dois erros
+ { error: 'Tecnoologia para toddos', correct: 'Tecnologia para todos', hint: 'Corrija duas palavras com letras extras.' },
+ { error: 'Internet livre para a comunnidade', correct: 'Internet livre para a comunidade', hint: 'A palavra final tem uma letra repetida.' },
+ { error: 'Educaçãao digital fortalece o territórrio', correct: 'Educação digital fortalece o território', hint: 'Corrija as duas palavras com letras sobrando.' },
+ { error: 'Redee comunitária amplia vozess locais', correct: 'Rede comunitária amplia vozes locais', hint: 'Há duas letras repetidas em palavras diferentes.' },
+ { error: 'Soberannia digital protege daddos', correct: 'Soberania digital protege dados', hint: 'Corrija as duas palavras com repetição.' },
+
+ // Fase 5 – Frases mais longas
+ { error: 'A tecnoologia livre fortalece a autonomia da comunnidade.', correct: 'A tecnologia livre fortalece a autonomia da comunidade.', hint: 'Corrija as duas palavras com letras extras.' },
+ { error: 'Educaçãao popular e internet livre ampliam oportuniddades.', correct: 'Educação popular e internet livre ampliam oportunidades.', hint: 'Há duas palavras com letras repetidas.' },
+ { error: 'Dadoss abertos apoiam a participaçãao social.', correct: 'Dados abertos apoiam a participação social.', hint: 'Corrija as duas palavras com letras sobrando.' },
+ { error: 'A redee comunitária fortalece vínculoss no território.', correct: 'A rede comunitária fortalece vínculos no território.', hint: 'Remova as repetições em duas palavras.' },
+ { error: 'Soberannia digital exige cuidado com dadoss pessoais.', correct: 'Soberania digital exige cuidado com dados pessoais.', hint: 'Corrija duas palavras com letras extras.' },
+ { error: 'Você já domina o Backspace e corrige textoss com atenção.', correct: 'Você já domina o Backspace e corrige textos com atenção.', hint: 'Etapa final: encontre a letra repetida.' },
+];
+
+let current = 0;
+let lastNotifiedPhase = -1;
+notify('started');
+
+const phaseBadge = document.getElementById('phaseBadge');
+const stepCounter = document.getElementById('stepCounter');
+const progressBar = document.getElementById('progressBar');
+const errorText = document.getElementById('errorText');
+const correctText = document.getElementById('correctText');
+const taskHint = document.getElementById('taskHint');
+const editInput = document.getElementById('editInput');
+const feedbackEl = document.getElementById('feedback');
+const successBanner = document.getElementById('successBanner');
+const taskBox = document.getElementById('taskBox');
+
+function getPhaseIdx(idx) { return Math.floor(idx / 6); }
+
+function syncInputHeight() {
+ editInput.style.height = 'auto';
+ editInput.style.height = `${Math.max(editInput.scrollHeight, 120)}px`;
+}
+
+function showTask() {
+ const t = tasks[current];
+ const phaseIdx = getPhaseIdx(current);
+ const phase = phases[phaseIdx];
+
+ if (phaseIdx !== lastNotifiedPhase) {
+ notify('running', { step: phaseIdx + 1 });
+ lastNotifiedPhase = phaseIdx;
+ }
+
+ phaseBadge.textContent = phase.label;
+ phaseBadge.className = `text-xs font-bold px-3 py-1 rounded-full transition-all ${phase.badgeClass}`;
+ progressBar.className = `h-2.5 rounded-full transition-all duration-500 ${phase.barClass}`;
+ progressBar.style.width = `${(current / tasks.length) * 100}%`;
+ stepCounter.textContent = `Passo ${current + 1} de ${tasks.length}`;
+
+ errorText.textContent = t.error;
+ correctText.textContent = t.correct;
+ taskHint.textContent = t.hint;
+ editInput.value = t.error;
+ editInput.disabled = false;
+ syncInputHeight();
+ editInput.focus();
+ feedbackEl.classList.add('hidden');
+}
+showTask();
+
+editInput.addEventListener('input', () => {
+ syncInputHeight();
+ if (editInput.value === tasks[current].correct) {
+ feedbackEl.textContent = 'Correto!';
+ feedbackEl.className = 'text-xl font-bold py-1 text-center text-green-600';
+ feedbackEl.classList.remove('hidden');
+ editInput.disabled = true;
+ current++;
+ setTimeout(() => {
+ if (current >= tasks.length) {
+ taskBox.classList.add('hidden');
+ progressBar.style.width = '100%';
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ } else {
+ showTask();
+ }
+ }, 700);
+ }
+});
+
+editInput.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') e.preventDefault();
+});
diff --git a/app/src/atividades/letramento/teclado/teclado-backspace/index.html b/app/src/atividades/letramento/teclado/teclado-backspace/index.html
new file mode 100644
index 0000000..472fc4b
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-backspace/index.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Backspace: Correções no Texto
+
+
+
+
+
+
+
+
+
+
+
+
Backspace: Correções no Texto
+
Posicione o cursor, apague erros e reescreva frases sobre tecnologia cidadã.
+
+
+
+
+
+
+
+
+
+
+
+
Use o Backspace para apagar, depois corrija o texto
+
+
+
+
+
+
+
Excelente!
+
Você concluiu 30 etapas de correção. O Backspace está na ponta dos dedos.
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-escrever-nome/activity.js b/app/src/atividades/letramento/teclado/teclado-escrever-nome/activity.js
new file mode 100644
index 0000000..5900b04
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-escrever-nome/activity.js
@@ -0,0 +1,75 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+notify('started');
+
+const tasks = [
+ { prompt: 'Digite exatamente: computador', target: 'computador', placeholder: 'computador' },
+ { prompt: 'Digite exatamente: 2026', target: '2026', placeholder: '2026' },
+ { prompt: 'Digite exatamente: dados são poder', target: 'dados são poder', placeholder: 'dados são poder' },
+ { prompt: 'Digite exatamente: tecnologia popular', target: 'tecnologia popular', placeholder: 'tecnologia popular' },
+ { prompt: 'Digite exatamente: soberania digital', target: 'soberania digital', placeholder: 'soberania digital' },
+ { prompt: 'Digite exatamente: estou aprendendo tecnologia', target: 'estou aprendendo tecnologia', placeholder: 'estou aprendendo tecnologia' },
+];
+
+let current = 0;
+
+const taskLabel = document.getElementById('taskLabel');
+const stepLabel = document.getElementById('stepLabel');
+const textInput = document.getElementById('textInput');
+const feedback = document.getElementById('feedback');
+const confirmBtn = document.getElementById('confirmBtn');
+const successBanner = document.getElementById('successBanner');
+const formWrap = document.getElementById('formWrap');
+
+function showTask() {
+ const task = tasks[current];
+ stepLabel.textContent = `Passo ${current + 1} de ${tasks.length}`;
+ taskLabel.textContent = task.prompt;
+ textInput.value = '';
+ textInput.placeholder = task.placeholder;
+ feedback.textContent = '';
+ textInput.focus();
+}
+
+function checkInput() {
+ const task = tasks[current];
+ const value = textInput.value;
+ confirmBtn.disabled = value !== task.target;
+ if (!value) {
+ feedback.textContent = '';
+ return;
+ }
+ feedback.textContent = value === task.target ? 'Correto! Pode avançar.' : 'Confira letras, acentos e espaços.';
+ feedback.className = value === task.target ? 'text-base text-emerald-700 font-semibold' : 'text-base text-rose-600 font-semibold';
+}
+
+textInput.addEventListener('input', checkInput);
+
+document.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' && !confirmBtn.disabled) {
+ e.preventDefault();
+ confirmBtn.click();
+ }
+});
+
+confirmBtn.addEventListener('click', () => {
+ notify('running', { step: current + 1 });
+ current += 1;
+ if (current >= tasks.length) {
+ formWrap.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ return;
+ }
+ showTask();
+ confirmBtn.disabled = true;
+});
+
+confirmBtn.disabled = true;
+showTask();
diff --git a/app/src/atividades/letramento/teclado/teclado-escrever-nome/index.html b/app/src/atividades/letramento/teclado/teclado-escrever-nome/index.html
new file mode 100644
index 0000000..2825338
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-escrever-nome/index.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+ Prática de Escrita
+
+
+
+
+
+
+
+
+
+
+
Prática de Escrita
+
Digite palavras, números e frases que serão solicitadas.
+
+
+
+
+
+
+
+
Muito bem!
+
Você completou 10 passos de escrita com foco em tecnologia e cidadania digital.
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-navegacao/activity.js b/app/src/atividades/letramento/teclado/teclado-navegacao/activity.js
new file mode 100644
index 0000000..cf9cb43
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-navegacao/activity.js
@@ -0,0 +1,110 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+notify('started');
+
+const steps = [
+ {
+ text: 'Passo 1: Pressione → três vezes',
+ hint: 'A seta para direita move o cursor uma letra por vez.',
+ action: 'arrow-right',
+ count: 3,
+ },
+ {
+ text: 'Passo 2: Pressione ← duas vezes',
+ hint: 'A seta para esquerda volta o cursor.',
+ action: 'arrow-left',
+ count: 2,
+ },
+ {
+ text: 'Passo 3: Pressione Home',
+ hint: 'Home vai para o INÍCIO da linha.',
+ action: 'home',
+ count: 1,
+ },
+ {
+ text: 'Passo 4: Pressione End',
+ hint: 'End vai para o FINAL da linha.',
+ action: 'end',
+ count: 1,
+ },
+];
+
+let currentStep = 0;
+let actionCount = 0;
+let locked = false;
+
+const textArea = document.getElementById('textArea');
+const stepText = document.getElementById('stepText');
+const stepHint = document.getElementById('stepHint');
+const feedbackEl = document.getElementById('feedback');
+const cursorPos = document.getElementById('cursorPos');
+const progressDots = document.getElementById('progressDots');
+const successBanner = document.getElementById('successBanner');
+const stepBox = document.getElementById('stepBox');
+
+textArea.value = 'Olá mundo!';
+textArea.readOnly = false;
+textArea.focus();
+textArea.setSelectionRange(0, 0);
+
+steps.forEach((_, i) => {
+ const dot = document.createElement('div');
+ dot.id = `dot-${i}`;
+ dot.className = 'w-8 h-8 rounded-full border-2 border-gray-300 bg-gray-100 transition-all';
+ progressDots.appendChild(dot);
+});
+
+function showStep() {
+ const s = steps[currentStep];
+ stepText.textContent = s.text;
+ stepHint.textContent = s.hint;
+ actionCount = 0;
+ feedbackEl.classList.add('hidden');
+ textArea.focus();
+}
+showStep();
+
+textArea.addEventListener('keydown', (e) => {
+ if (locked) return;
+ const s = steps[currentStep];
+ const pos = textArea.selectionStart;
+ cursorPos.textContent = `Posição do cursor: ${pos}`;
+
+ let matched = false;
+ if (s.action === 'arrow-right' && e.key === 'ArrowRight') matched = true;
+ if (s.action === 'arrow-left' && e.key === 'ArrowLeft') matched = true;
+ if (s.action === 'home' && e.key === 'Home') matched = true;
+ if (s.action === 'end' && e.key === 'End') matched = true;
+
+ if (matched) {
+ actionCount++;
+ feedbackEl.textContent = `${actionCount} de ${s.count}`;
+ feedbackEl.className = 'text-2xl font-bold py-1 text-green-600';
+ feedbackEl.classList.remove('hidden');
+
+ if (actionCount >= s.count) {
+ locked = true;
+ const dot = document.getElementById(`dot-${currentStep}`);
+ if (dot) dot.className = 'w-8 h-8 rounded-full bg-green-400 border-2 border-green-500 transition-all';
+ notify('running', { step: currentStep + 1 });
+ currentStep++;
+ setTimeout(() => {
+ locked = false;
+ if (currentStep >= steps.length) {
+ stepBox.classList.add('hidden');
+ progressDots.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ } else {
+ showStep();
+ }
+ }, 700);
+ }
+ }
+});
diff --git a/app/src/atividades/letramento/teclado/teclado-navegacao/index.html b/app/src/atividades/letramento/teclado/teclado-navegacao/index.html
new file mode 100644
index 0000000..dc45daa
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-navegacao/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ Setas de Navegação
+
+
+
+
+
+
+
+
+
+
+
Setas: Mova o Cursor
+
Use as setas para mover o cursor dentro do texto.
+
+
+
+
+
+ ← → move o cursor para os lados . ↑ ↓ movem linha acima/abaixo.
+ Home vai para o início da linha. End vai para o final.
+
+
+
+
+
+
+
+
Posição do cursor: 0
+
+
+
+
+
+
+
+
Muito bem!
+
Agora você sabe navegar no texto sem o mouse!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-numeros/activity.js b/app/src/atividades/letramento/teclado/teclado-numeros/activity.js
new file mode 100644
index 0000000..0a8ec0a
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-numeros/activity.js
@@ -0,0 +1,207 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+const DIGITS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+const STEP_SIZES = [2, 4, 6, 8, 10];
+const TOTAL_STEPS = STEP_SIZES.length;
+
+function shuffle(list) {
+ const copy = [...list];
+ for (let i = copy.length - 1; i > 0; i -= 1) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [copy[i], copy[j]] = [copy[j], copy[i]];
+ }
+ return copy;
+}
+
+function init() {
+ const queryEls = () => ({
+ instruction: document.getElementById('instruction'),
+ hint: document.getElementById('hint'),
+ arena: document.getElementById('arena'),
+ successBanner: document.getElementById('successBanner'),
+ successMsg: document.getElementById('successMsg'),
+ });
+
+ let { instruction, hint, arena, successBanner, successMsg } = queryEls();
+
+ const required = { instruction, hint, arena, successBanner, successMsg };
+ const missing = Object.entries(required)
+ .filter(([, value]) => !value)
+ .map(([name]) => name);
+
+ if (missing.length > 0) {
+ console.warn(`[teclado-numeros] Layout incompleto. Aplicando fallback para: ${missing.join(', ')}`);
+ document.body.innerHTML = `
+
+ `;
+
+ ({ instruction, hint, arena, successBanner, successMsg } = queryEls());
+ }
+
+ notify('started');
+
+ let step = 1;
+ let sequence = [];
+ let index = 0;
+ let done = false;
+
+ function generateSequence(size) {
+ if (size <= DIGITS.length) {
+ return shuffle(DIGITS).slice(0, size);
+ }
+
+ const base = shuffle(DIGITS);
+ const extra = DIGITS[Math.floor(Math.random() * DIGITS.length)];
+ return [...base, extra];
+ }
+
+ function markSequence(indexToMark, state) {
+ const chip = document.getElementById(`seq-chip-${indexToMark}`);
+ if (!chip) return;
+
+ if (state === 'done') {
+ chip.className = 'h-11 w-11 rounded-lg border-2 border-emerald-300 bg-emerald-100 text-emerald-700 font-black text-xl flex items-center justify-center transition-all';
+ return;
+ }
+
+ if (state === 'error') {
+ chip.className = 'h-11 w-11 rounded-lg border-2 border-rose-300 bg-rose-100 text-rose-700 font-black text-xl flex items-center justify-center transition-all';
+ window.setTimeout(() => {
+ if (index === indexToMark) {
+ chip.className = 'h-11 w-11 rounded-lg border-2 border-brand-300 bg-brand-100 text-brand-700 font-black text-xl flex items-center justify-center transition-all';
+ }
+ }, 220);
+ return;
+ }
+
+ chip.className = 'h-11 w-11 rounded-lg border-2 border-brand-300 bg-brand-100 text-brand-700 font-black text-xl flex items-center justify-center transition-all';
+ }
+
+ function setFeedback(ok, typed, expected) {
+ const status = document.getElementById('statusFeedback');
+ if (!status) return;
+
+ if (ok) {
+ status.className = 'mt-4 rounded-xl border border-emerald-300 bg-emerald-50 px-4 py-3 text-emerald-700 font-semibold';
+ status.textContent = `Correto: ${typed}`;
+ return;
+ }
+
+ status.className = 'mt-4 rounded-xl border border-rose-300 bg-rose-50 px-4 py-3 text-rose-700 font-semibold';
+ status.textContent = `Ops: você digitou ${typed}. Próximo esperado: ${expected}`;
+ }
+
+ function renderSequenceChips() {
+ const container = document.getElementById('sequenceChips');
+ if (!container) return;
+
+ container.innerHTML = '';
+ sequence.forEach((digit, i) => {
+ const chip = document.createElement('div');
+ chip.id = `seq-chip-${i}`;
+ chip.className = 'h-11 w-11 rounded-lg border-2 border-slate-300 bg-white text-slate-700 font-black text-xl flex items-center justify-center transition-all';
+ chip.textContent = digit;
+ container.appendChild(chip);
+ });
+
+ markSequence(0, 'current');
+ }
+
+ function renderStep() {
+ sequence = generateSequence(STEP_SIZES[step - 1]);
+ index = 0;
+ hint.textContent = `Digite ${sequence.length} número(s) na ordem mostrada.`;
+
+ arena.innerHTML = `
+
+
Sequência da vez
+
+
Acertos na rodada: 0 /${sequence.length}
+
Digite o primeiro número destacado.
+
+ `;
+
+ renderSequenceChips();
+ }
+
+ function flashKey(digit, ok) {
+ const el = document.getElementById(`kbd-${digit}`);
+ if (!el) return;
+ el.className = ok
+ ? 'w-12 h-12 rounded-lg border-2 border-emerald-300 bg-emerald-100 flex items-center justify-center font-bold text-emerald-700'
+ : 'w-12 h-12 rounded-lg border-2 border-rose-300 bg-rose-100 flex items-center justify-center font-bold text-rose-700';
+ setTimeout(() => {
+ el.className = 'w-12 h-12 rounded-lg border-2 border-slate-300 bg-white flex items-center justify-center font-bold text-slate-700';
+ }, 180);
+ }
+
+ function handleDigit(digit) {
+ if (done) return;
+ const expected = sequence[index];
+ const ok = digit === expected;
+ flashKey(digit, ok);
+ if (!ok) {
+ markSequence(index, 'error');
+ setFeedback(false, digit, expected);
+ return;
+ }
+
+ markSequence(index, 'done');
+ setFeedback(true, digit, expected);
+ index += 1;
+ const seqState = document.getElementById('seqState');
+ if (seqState) seqState.textContent = String(index);
+
+ if (index < sequence.length) {
+ markSequence(index, 'current');
+ }
+
+ if (index >= sequence.length) {
+ notify('running', { step });
+ if (step >= TOTAL_STEPS) {
+ done = true;
+ arena.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ successMsg.textContent = 'Você agora sabe onde estão os números no teclado!';
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ return;
+ }
+
+ step += 1;
+ renderStep();
+ }
+ }
+
+ document.addEventListener('keydown', (event) => {
+ if (!/^Digit[0-9]$/.test(event.code)) return;
+ handleDigit(event.code.replace('Digit', ''));
+ });
+
+ renderStep();
+}
+
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init, { once: true });
+} else {
+ init();
+}
diff --git a/app/src/atividades/letramento/teclado/teclado-numeros/index.html b/app/src/atividades/letramento/teclado/teclado-numeros/index.html
new file mode 100644
index 0000000..fd12d6e
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-numeros/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+ Números do Teclado
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-regioes/activity.js b/app/src/atividades/letramento/teclado/teclado-regioes/activity.js
new file mode 100644
index 0000000..a9c8a9a
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-regioes/activity.js
@@ -0,0 +1,150 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+const regions = [
+ {
+ id: 'letras',
+ label: 'Letras (A–Z)',
+ color: 'bg-blue-400',
+ textColor: 'text-white',
+ title: 'Região das Letras',
+ desc: 'Aqui ficam todas as 26 letras do alfabeto. São as teclas mais usadas para escrever palavras.',
+ keys: ['Q','W','E','R','T','Y','U','I','O','P','A','S','D','F','G','H','J','K','L','Z','X','C','V','B','N','M'],
+ },
+ {
+ id: 'numeros',
+ label: 'Números (0–9)',
+ color: 'bg-green-400',
+ textColor: 'text-white',
+ title: 'Região dos Números',
+ desc: 'A linha superior tem os números de 1 a 0. Também há símbolos quando você usa o Shift.',
+ keys: ['1','2','3','4','5','6','7','8','9','0'],
+ },
+ {
+ id: 'especiais',
+ label: 'Teclas Especiais',
+ color: 'bg-orange-400',
+ textColor: 'text-white',
+ title: 'Teclas Especiais',
+ desc: 'Enter, Backspace, Shift, Caps Lock, Esc, Tab — cada uma tem uma função importante!',
+ keys: ['Enter','Backspace','Shift','Caps Lock','Esc','Tab'],
+ },
+ {
+ id: 'setas',
+ label: 'Setas de Navegação',
+ color: 'bg-purple-400',
+ textColor: 'text-white',
+ title: 'Setas de Navegação',
+ desc: 'As quatro setas movem o cursor no texto sem apagar nada. Home e End vão para início/fim da linha.',
+ keys: ['←','↑','↓','→','Home','End'],
+ },
+];
+
+const visitedRegions = new Set();
+notify('started');
+
+const keyboard = document.getElementById('keyboard');
+const infoBox = document.getElementById('infoBox');
+const infoTitle = document.getElementById('infoTitle');
+const infoDesc = document.getElementById('infoDesc');
+const progressArea = document.getElementById('progressArea');
+const instruction = document.getElementById('instruction');
+const hint = document.getElementById('hint');
+const successBanner = document.getElementById('successBanner');
+let isCompleted = false;
+
+// Build visual keyboard rows
+const rows = [
+ ['Esc','1','2','3','4','5','6','7','8','9','0','Backspace'],
+ ['Tab','Q','W','E','R','T','Y','U','I','O','P'],
+ ['Caps Lock','A','S','D','F','G','H','J','K','L','Enter'],
+ ['Shift','Z','X','C','V','B','N','M','Shift'],
+ ['←','↑','↓','→','Home','End'],
+];
+
+rows.forEach(row => {
+ const rowEl = document.createElement('div');
+ rowEl.className = 'flex gap-1 justify-center';
+ row.forEach(key => {
+ const region = regions.find(r => r.keys.includes(key));
+ const keyEl = document.createElement('div');
+ const isWide = ['Backspace','Tab','Caps Lock','Enter','Shift'].includes(key);
+ keyEl.className = `key px-2 py-2 rounded-md text-sm font-bold shadow flex items-center justify-center min-w-[2.2rem] ${isWide ? 'px-3 min-w-[4rem]' : ''} ${region ? region.color + ' ' + region.textColor : 'bg-gray-200 text-gray-700'}`;
+ keyEl.textContent = key;
+ if (region) {
+ keyEl.addEventListener('click', () => handleRegionClick(region));
+ }
+ rowEl.appendChild(keyEl);
+ });
+ keyboard.appendChild(rowEl);
+});
+
+function normalizeKey(event) {
+ const key = event.key;
+
+ if (key.length === 1) {
+ return key.toUpperCase();
+ }
+
+ const aliases = {
+ ArrowLeft: '←',
+ ArrowUp: '↑',
+ ArrowDown: '↓',
+ ArrowRight: '→',
+ Escape: 'Esc',
+ CapsLock: 'Caps Lock',
+ ' ': 'Space',
+ };
+
+ return aliases[key] ?? key;
+}
+
+function findRegionByKey(keyLabel) {
+ return regions.find((region) => region.keys.includes(keyLabel));
+}
+
+function handleRegionClick(region) {
+ if (isCompleted) return;
+
+ // Highlight all keys of this region
+ document.querySelectorAll('.key').forEach(k => {
+ k.classList.remove('ring-4','ring-yellow-400');
+ if (region.keys.includes(k.textContent.trim())) {
+ k.classList.add('ring-4','ring-yellow-400');
+ }
+ });
+
+ infoTitle.textContent = region.title;
+ infoDesc.textContent = region.desc;
+ infoBox.classList.remove('hidden');
+
+ if (!visitedRegions.has(region.id)) {
+ visitedRegions.add(region.id);
+ notify('running', { step: visitedRegions.size });
+ }
+
+ if (visitedRegions.size >= regions.length) {
+ isCompleted = true;
+ setTimeout(() => {
+ keyboard.classList.add('hidden');
+ infoBox.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ }, 1000);
+ }
+}
+
+document.addEventListener('keydown', (event) => {
+ if (isCompleted) return;
+
+ const normalizedKey = normalizeKey(event);
+ const region = findRegionByKey(normalizedKey);
+ if (!region) return;
+
+ handleRegionClick(region);
+});
diff --git a/app/src/atividades/letramento/teclado/teclado-regioes/index.html b/app/src/atividades/letramento/teclado/teclado-regioes/index.html
new file mode 100644
index 0000000..8329be9
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-regioes/index.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Regiões do Teclado
+
+
+
+
+
+
+
+
+
+
+
Conheça as regiões do teclado
+
Clique em uma região colorida ou pressione qualquer tecla dela.
+
+
+
+
+
+
+
+
+
+
Muito bem!
+
Você conheceu todas as regiões do teclado!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/teclado-simbolos/activity.js b/app/src/atividades/letramento/teclado/teclado-simbolos/activity.js
new file mode 100644
index 0000000..b415b76
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-simbolos/activity.js
@@ -0,0 +1,74 @@
+const channelToken = new URLSearchParams(window.location.hash.slice(1)).get('channelToken');
+
+function notify(type, payload = {}) {
+ window.parent.postMessage({ type, token: channelToken, ...payload }, '*');
+}
+
+// Map: symbol → key hint
+const sequence = [
+ { symbol: '!', hint: 'Shift + 1', key: '!' },
+ { symbol: '@', hint: 'Shift + 2', key: '@' },
+ { symbol: '#', hint: 'Shift + 3', key: '#' },
+ { symbol: '$', hint: 'Shift + 4', key: '$' },
+ { symbol: '%', hint: 'Shift + 5', key: '%' },
+ { symbol: '&', hint: 'Shift + 7', key: '&' },
+ { symbol: '*', hint: 'Shift + 8', key: '*' },
+ { symbol: '(', hint: 'Shift + 9', key: '(' },
+];
+
+let currentIdx = 0;
+let locked = false;
+notify('started');
+
+const targetEl = document.getElementById('target');
+const targetHintEl = document.getElementById('targetHint');
+const feedbackEl = document.getElementById('feedback');
+const progressDots = document.getElementById('progressDots');
+const successBanner = document.getElementById('successBanner');
+
+sequence.forEach((_, i) => {
+ const dot = document.createElement('div');
+ dot.id = `dot-${i}`;
+ dot.className = 'w-8 h-8 rounded-full border-2 border-gray-300 bg-gray-100 transition-all';
+ progressDots.appendChild(dot);
+});
+
+function showTarget() {
+ const s = sequence[currentIdx];
+ targetEl.textContent = s.symbol;
+ targetHintEl.textContent = `Pressione: ${s.hint}`;
+ feedbackEl.classList.add('hidden');
+}
+showTarget();
+
+document.addEventListener('keydown', (e) => {
+ if (locked) return;
+ const s = sequence[currentIdx];
+ if (e.key === s.key) {
+ locked = true;
+ feedbackEl.textContent = 'Isso!';
+ feedbackEl.className = 'text-2xl font-bold py-1 text-green-600';
+ feedbackEl.classList.remove('hidden');
+ const dot = document.getElementById(`dot-${currentIdx}`);
+ if (dot) dot.className = 'w-8 h-8 rounded-full bg-green-400 border-2 border-green-500 transition-all';
+ notify('running', { step: currentIdx + 1 });
+ currentIdx++;
+ setTimeout(() => {
+ locked = false;
+ if (currentIdx >= sequence.length) {
+ targetEl.parentElement.classList.add('hidden');
+ progressDots.classList.add('hidden');
+ successBanner.classList.remove('hidden');
+ lucide.createIcons();
+ notify('success', { score: 100 });
+ notify('completed', { score: 100 });
+ } else {
+ showTarget();
+ }
+ }, 800);
+ } else if (e.key !== 'Shift') {
+ feedbackEl.textContent = `Não é esse. Tente: ${s.hint}`;
+ feedbackEl.className = 'text-2xl font-bold py-1 text-red-500';
+ feedbackEl.classList.remove('hidden');
+ }
+});
diff --git a/app/src/atividades/letramento/teclado/teclado-simbolos/index.html b/app/src/atividades/letramento/teclado/teclado-simbolos/index.html
new file mode 100644
index 0000000..2c39775
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/teclado-simbolos/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+ Símbolos com Shift
+
+
+
+
+
+
+
+
+
+
+
Símbolos com Shift + Número
+
Segure Shift e pressione o número para obter o símbolo.
+
+
+
+
+
+ Cada tecla de número tem um símbolo escondido em cima dela. Para acessá-lo, segure Shift e pressione o número!
+
+
+
+
+
+
+
+
+
+
Incrível!
+
Agora você sabe como digitar símbolos!
+
+
+
+
+
+
+
+
+
diff --git a/app/src/atividades/letramento/teclado/tecladoRegistry.js b/app/src/atividades/letramento/teclado/tecladoRegistry.js
new file mode 100644
index 0000000..fa0560e
--- /dev/null
+++ b/app/src/atividades/letramento/teclado/tecladoRegistry.js
@@ -0,0 +1,182 @@
+export const TECLADO_ATIVIDADES_REGISTRY = {
+ 'teclado-regioes': {
+ id: 'teclado-regioes',
+ titulo: 'Regiões do Teclado',
+ descricao: 'Aprenda onde ficam as letras e os grupos principais do teclado.',
+ categoria: 'teclado',
+ dificuldade: 'iniciante',
+ duracao: 3,
+ htmlFile: '/atividades/letramento/teclado/teclado-regioes/index.html',
+ proxima: 'teclado-numeros',
+ passos: [
+ { id: 1, label: 'Letras' },
+ { id: 2, label: 'Números' },
+ { id: 3, label: 'Teclas especiais' },
+ { id: 4, label: 'Setas, Home e End' },
+ ],
+ },
+ 'teclado-numeros': {
+ id: 'teclado-numeros',
+ titulo: 'Números do Teclado',
+ descricao: 'Pratique sequências numéricas progressivas, começando com 2 e chegando a 10 números.',
+ categoria: 'teclado',
+ dificuldade: 'iniciante',
+ duracao: 5,
+ htmlFile: '/atividades/letramento/teclado/teclado-numeros/index.html',
+ proxima: 'teclado-escrita',
+ passos: [
+ { id: 1, label: '2 números' },
+ { id: 3, label: '4 números' },
+ { id: 5, label: '6 números' },
+ { id: 7, label: '8 números' },
+ { id: 9, label: '10 números' },
+ ],
+ },
+ 'teclado-escrita': {
+ id: 'teclado-escrita',
+ titulo: 'Prática de Escrita',
+ descricao: 'Digite palavras e frases sobre tecnologia, cidadania e soberania digital.',
+ categoria: 'teclado',
+ dificuldade: 'iniciante',
+ duracao: 5,
+ htmlFile: '/atividades/letramento/teclado/teclado-escrever-nome/index.html',
+ proxima: 'teclado-simbolos',
+ passos: [
+ { id: 1, label: 'Palavra 1' },
+ { id: 3, label: 'Número' },
+ { id: 4, label: 'Frase 1' },
+ { id: 5, label: 'Frase 2' },
+ { id: 6, label: 'Frase 3' },
+ { id: 7, label: 'Frase 4' },
+ ],
+ },
+ 'teclado-simbolos': {
+ id: 'teclado-simbolos',
+ titulo: 'Símbolos com Shift',
+ descricao: 'Use Shift para formar símbolos comuns do teclado internacional.',
+ categoria: 'teclado',
+ dificuldade: 'iniciante',
+ duracao: 3,
+ htmlFile: '/atividades/letramento/teclado/teclado-simbolos/index.html',
+ proxima: 'teclado-backspace',
+ passos: [
+ { id: 1, label: 'Shift' },
+ { id: 2, label: 'Símbolos' },
+ ],
+ },
+ 'teclado-backspace': {
+ id: 'teclado-backspace',
+ titulo: 'Backspace e Correção',
+ descricao: 'Corrija frases sobre educação popular, tecnologia e soberania digital.',
+ categoria: 'teclado',
+ dificuldade: 'intermediario',
+ duracao: 4,
+ htmlFile: '/atividades/letramento/teclado/teclado-backspace/index.html',
+ proxima: 'teclado-labirinto',
+ passos: [
+ { id: 1, label: 'Correções simples' },
+ { id: 2, label: 'Correções no meio' },
+ { id: 3, label: 'Reescrita' },
+ { id: 4, label: 'Frases completas' },
+ { id: 5, label: 'Frases longas' },
+ ],
+ },
+ 'teclado-labirinto': {
+ id: 'teclado-labirinto',
+ titulo: 'Labirinto das Setas',
+ descricao: 'Pratique navegação com setas para ganhar fluidez no teclado.',
+ categoria: 'teclado',
+ dificuldade: 'intermediario',
+ duracao: 3,
+ htmlFile: '/atividades/letramento/teclado/labirinto/index.html',
+ proxima: 'teclado-navegacao',
+ passos: [
+ { id: 1, label: 'Fase 1' },
+ { id: 2, label: 'Fase 2' },
+ { id: 3, label: 'Fase 3' },
+ { id: 4, label: 'Fase 4' },
+ { id: 5, label: 'Fase 5' },
+ ],
+ },
+ 'teclado-navegacao': {
+ id: 'teclado-navegacao',
+ titulo: 'Teclas de Navegação',
+ descricao: 'Use setas, Home e End para revisar textos.',
+ categoria: 'teclado',
+ dificuldade: 'intermediario',
+ duracao: 3,
+ htmlFile: '/atividades/letramento/teclado/teclado-navegacao/index.html',
+ proxima: 'teclado-setas-texto',
+ passos: [
+ { id: 1, label: 'Setas' },
+ { id: 2, label: 'Home e End' },
+ ],
+ },
+ 'teclado-setas-texto': {
+ id: 'teclado-setas-texto',
+ titulo: 'Setas no Texto',
+ descricao: 'Edite frases sobre tecnologia e soberania digital sem apagar o conteúdo.',
+ categoria: 'teclado',
+ dificuldade: 'intermediario',
+ duracao: 3,
+ htmlFile: '/atividades/letramento/teclado/setas-texto/index.html',
+ proxima: 'teclado-chuva',
+ passos: [
+ { id: 1, label: 'Mover cursor' },
+ { id: 2, label: 'Inserir no lugar certo' },
+ ],
+ },
+ // 'teclado-recado': {
+ // id: 'teclado-recado',
+ // titulo: 'Texto Coletivo',
+ // descricao: 'Escreva uma mensagem curta sobre tecnologia, comunidade e soberania digital.',
+ // categoria: 'teclado',
+ // dificuldade: 'intermediario',
+ // duracao: 4,
+ // htmlFile: '/atividades/letramento/teclado/recado-completo/index.html',
+ // proxima: 'teclado-chuva',
+ // passos: [
+ // { id: 1, label: 'Escrever' },
+ // { id: 2, label: 'Revisar' },
+ // { id: 3, label: 'Concluir' },
+ // ],
+ // },
+ 'teclado-chuva': {
+ id: 'teclado-chuva',
+ titulo: 'Chuva de Letras',
+ descricao: 'Digite caracteres, palavras e combinações em etapas com velocidade progressiva.',
+ categoria: 'teclado',
+ dificuldade: 'intermediario',
+ duracao: 5,
+ htmlFile: '/atividades/letramento/teclado/chuva/index.html',
+ proxima: 'teclado-atividade-final',
+ passos: [
+ { id: 1, label: '5 caracteres' },
+ { id: 2, label: '5 caracteres' },
+ { id: 3, label: '5 caracteres' },
+ { id: 4, label: '2 caracteres' },
+ { id: 5, label: '3 letras' },
+ { id: 6, label: '4 letras' },
+ ],
+ },
+ 'teclado-atividade-final': {
+ id: 'teclado-atividade-final',
+ titulo: 'Atividade Final',
+ descricao: 'Integre as habilidades de digitação em uma sequência completa de teclado.',
+ categoria: 'teclado',
+ dificuldade: 'avancado',
+ duracao: 5,
+ htmlFile: '/atividades/letramento/teclado/atividade-final/index.html',
+ proxima: null,
+ passos: [
+ { id: 1, label: 'Letra' },
+ { id: 2, label: 'Número' },
+ { id: 3, label: 'Maiúscula' },
+ { id: 4, label: 'Símbolo' },
+ { id: 5, label: 'Seta' },
+ { id: 6, label: 'Enter' },
+ { id: 7, label: 'Esc' },
+ { id: 8, label: 'Final' },
+ ],
+ },
+};
diff --git a/app/src/atividades/programacao/aspirador/AspiradorGame.jsx b/app/src/atividades/programacao/aspirador/AspiradorGame.jsx
new file mode 100644
index 0000000..3cecc12
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/AspiradorGame.jsx
@@ -0,0 +1,74 @@
+/**
+ * @fileoverview Componente React principal para o jogo Aspirador
+ * * @module games.aspirador.AspiradorGame
+ */
+
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
+import { GameStateProvider, useGameState } from "../../../contexts/GameStateContext";
+import { useAspiradorTour } from "./hooks/useAspiradorTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+import { starterBlocks } from "./config/starterBlocks";
+
+/**
+ * Componente interno que monta a cena e o editor do jogo Aspirador.
+ * Registra blocos, configura toolbox dinâmico e injeta o `gameFactory`.
+ * @returns {JSX.Element} Conteúdo do jogo (editor + canvas)
+ */
+function AspiradorContent() {
+ const { setFailureMessage, isDebugMode } = useGameState();
+
+ // Hook para o tutorial passo a passo (será criado depois)
+ useAspiradorTour();
+
+ // Registra os blocos customizados do Blockly ao montar o componente
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ // Memoriza a função geradora do toolbox para evitar re-renderizações desnecessárias
+ const toolboxGenerator = useMemo(() => {
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Componente de página que fornece o contexto de estado do jogo Aspirador.
+ * Envolve `AspiradorContent` com o `GameStateProvider` configurado.
+ * @returns {JSX.Element} Página completa do jogo Aspirador
+ */
+export default function AspiradorGame() {
+ return (
+
+
+
+ );
+}
+
+AspiradorContent.propTypes = {};
+AspiradorGame.propTypes = {};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/assets/image/aspirador.png b/app/src/atividades/programacao/aspirador/assets/image/aspirador.png
new file mode 100644
index 0000000..72aadd4
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/aspirador.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/image/obstaculo1.png b/app/src/atividades/programacao/aspirador/assets/image/obstaculo1.png
new file mode 100644
index 0000000..5dd7501
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/obstaculo1.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/image/obstaculo2.png b/app/src/atividades/programacao/aspirador/assets/image/obstaculo2.png
new file mode 100644
index 0000000..3184b86
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/obstaculo2.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/image/piso.png b/app/src/atividades/programacao/aspirador/assets/image/piso.png
new file mode 100644
index 0000000..6a5763e
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/piso.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/image/piso_2.png b/app/src/atividades/programacao/aspirador/assets/image/piso_2.png
new file mode 100644
index 0000000..fd57c2b
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/piso_2.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/image/sujeira.png b/app/src/atividades/programacao/aspirador/assets/image/sujeira.png
new file mode 100644
index 0000000..62cc984
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/image/sujeira.png differ
diff --git a/app/src/atividades/programacao/aspirador/assets/sound/bg_sound.mp3 b/app/src/atividades/programacao/aspirador/assets/sound/bg_sound.mp3
new file mode 100644
index 0000000..8fb6e4c
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/sound/bg_sound.mp3 differ
diff --git a/app/src/atividades/programacao/aspirador/assets/sound/pop.mp3 b/app/src/atividades/programacao/aspirador/assets/sound/pop.mp3
new file mode 100644
index 0000000..5136169
Binary files /dev/null and b/app/src/atividades/programacao/aspirador/assets/sound/pop.mp3 differ
diff --git a/app/src/atividades/programacao/aspirador/blocks/blocks.js b/app/src/atividades/programacao/aspirador/blocks/blocks.js
new file mode 100644
index 0000000..80a65b7
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/blocks/blocks.js
@@ -0,0 +1,170 @@
+/**
+ * @fileoverview Definição de blocos customizados e geradores para o jogo Aspirador
+ * @module games.aspirador.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import { javascriptGenerator } from "blockly/javascript";
+import { CORES_BLOCKLY, CORES_CUSTOMIZADAS } from "@/blockly/blocklyColors";
+import { configurarGerador, gerarExpressao,gerarStatement, gerarStatementInline, gerarStatementComCampo, gerarExpressaoComCampo, gerarStatementComValor } from "@/blockly/generator";
+import { gerarToolboxDeEstrutura } from "@/blockly/toolbox";
+import { criarBlocoStatementSimples, criarBlocoStatementComDropdown, criarBlocoStatementComValor, criarBlocoExpressaoSimples, criarBlocoExpressaoComDropdown, criarBlocoCondicional, criarBlocoNegacao } from "@/blockly/blockFactory";
+
+const ESTRUTURA_TOOLBOX = [
+ {
+ nome: "Lógica",
+ cssContainer: "cat_logica",
+ blocos: ["robo_if", "robo_if_else", "robo_not"]
+ },
+ {
+ nome: "Repetição",
+ cssContainer: "cat_repeticao",
+ blocos: ["controls_whileUntil", "controls_repeat_ext"]
+ },
+ {
+ nome: "Movimento",
+ cor: CORES_CUSTOMIZADAS.MOVIMENTO,
+ cssContainer: "cat_movimento",
+ blocos: ["robo_mover", "robo_virar"]
+ },
+ {
+ nome: "Sensores",
+ cor: CORES_CUSTOMIZADAS.SENSORES,
+ cssContainer: "cat_sensores",
+ blocos: ["robo_ainda_tem_sujeira", "robo_bloqueado"]
+ },
+ {
+ nome: "Variáveis",
+ cssContainer: "cat_variaveis",
+ blocos: ["robo_passos_set", "robo_passos_get", "robo_passos_change"]
+ },
+ {
+ nome: "Matemática",
+ cssContainer: "cat_matematica",
+ blocos: ["math_number"]
+ },
+];
+
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ return gerarToolboxDeEstrutura(ESTRUTURA_TOOLBOX, allowedBlocks);
+};
+
+const defineBlocks = () => {
+ // LÓGICA
+ criarBlocoCondicional("robo_if", {
+ textoCondicao: "se",
+ statements: [{ nome: "FACA", texto: "faça" }],
+ cor: CORES_BLOCKLY.LOGICA
+ });
+
+ criarBlocoCondicional("robo_if_else", {
+ textoCondicao: "se",
+ statements: [
+ { nome: "FACA", texto: "faça" },
+ { nome: "SENAO", texto: "senão" }
+ ],
+ cor: CORES_BLOCKLY.LOGICA
+ });
+
+ criarBlocoNegacao(
+ "robo_not",
+ "não",
+ CORES_BLOCKLY.LOGICA,
+ "Inverte o resultado do sensor (ex: de 'bloqueado' para 'não bloqueado')."
+ );
+
+ // MOVIMENTO
+ criarBlocoStatementSimples(
+ "robo_mover",
+ "mover para FRENTE",
+ CORES_CUSTOMIZADAS.MOVIMENTO
+ );
+
+ criarBlocoStatementComDropdown(
+ "robo_virar",
+ "virar para a",
+ [["DIREITA", "DIREITA"], ["ESQUERDA", "ESQUERDA"]],
+ "DIRECAO",
+ CORES_CUSTOMIZADAS.MOVIMENTO
+ );
+
+ // SENSORES
+ criarBlocoExpressaoSimples(
+ "robo_ainda_tem_sujeira",
+ "ainda tem sujeira?",
+ "Boolean",
+ CORES_CUSTOMIZADAS.SENSORES
+ );
+
+ criarBlocoExpressaoComDropdown(
+ "robo_bloqueado",
+ "caminho bloqueado à",
+ [["FRENTE", "FRENTE"], ["DIREITA", "DIREITA"], ["ESQUERDA", "ESQUERDA"]],
+ "SENTIDO",
+ "Boolean",
+ CORES_CUSTOMIZADAS.SENSORES
+ );
+
+ // VARIÁVEIS
+ criarBlocoStatementComValor(
+ "robo_passos_set",
+ "definir PASSOS para",
+ "VALOR",
+ "Number",
+ CORES_BLOCKLY.VARIAVEIS
+ );
+
+ criarBlocoExpressaoSimples(
+ "robo_passos_get",
+ "PASSOS",
+ "Number",
+ CORES_BLOCKLY.VARIAVEIS
+ );
+
+ criarBlocoStatementSimples(
+ "robo_passos_change",
+ "aumentar PASSOS em 1",
+ CORES_BLOCKLY.VARIAVEIS
+ );
+};
+
+const defineGenerators = () => {
+ configurarGerador();
+
+ // LÓGICA - Geradores complexos mantidos explícitos
+ javascriptGenerator.forBlock["robo_if"] = function (block) {
+ let condicao = javascriptGenerator.valueToCode(block, "CONDICAO", javascriptGenerator.ORDER_NONE) || "false";
+ return `if (${condicao}) {\n${javascriptGenerator.statementToCode(block, "FACA")}}\n`;
+ };
+
+ javascriptGenerator.forBlock["robo_if_else"] = function (block) {
+ let condicao = javascriptGenerator.valueToCode(block, "CONDICAO", javascriptGenerator.ORDER_NONE) || "false";
+ return `if (${condicao}) {\n${javascriptGenerator.statementToCode(block, "FACA")}} else {\n${javascriptGenerator.statementToCode(block, "SENAO")}}\n`;
+ };
+
+ javascriptGenerator.forBlock["robo_not"] = function (block) {
+ let innerCode = javascriptGenerator.valueToCode(block, "BOOL", javascriptGenerator.ORDER_LOGICAL_NOT) || "false";
+ return [`!${innerCode}`, javascriptGenerator.ORDER_LOGICAL_NOT];
+ };
+
+ // MOVIMENTO - Agora com helpers
+ gerarStatement("robo_mover", "mover");
+ gerarStatementComCampo("robo_virar", "virar", "DIRECAO");
+
+ // SENSORES - Agora com helpers
+ gerarExpressao("robo_ainda_tem_sujeira", "aindaTemSujeira()", javascriptGenerator.ORDER_FUNCTION_CALL);
+ gerarExpressaoComCampo("robo_bloqueado", "caminhoBloqueado", "SENTIDO", javascriptGenerator.ORDER_FUNCTION_CALL);
+
+ // VARIÁVEIS - Agora com helpers
+ gerarStatementComValor("robo_passos_set", "VALOR", (valor) => `var passos = ${valor}`);
+ gerarExpressao("robo_passos_get", "passos");
+ gerarStatementInline("robo_passos_change", "passos = passos + 1");
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/config/config.js b/app/src/atividades/programacao/aspirador/config/config.js
new file mode 100644
index 0000000..d6f31a2
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/config/config.js
@@ -0,0 +1,271 @@
+/**
+ * @fileoverview Configuração completa para o jogo Aspirador (Lógica e Navegação)
+ * Com progressão pedagógica de 10 Fases (Nível 1 ao Nível 4)
+ * @module games.aspirador.config.config
+ */
+
+export const gameConfig = {
+ gameId: "aspirador",
+ gameName: "Aspirador",
+ type: "blocks",
+ icon: "🧹",
+ thumbnail: "/images/atividades/programacao/aspirador-thumbnail.png",
+ descricao:
+ "Programe um aspirador robô reativo. Aprenda do básico aos algoritmos avançados de navegação e variáveis.",
+ dificuldade: "Iniciante",
+ categoria: "Lógica",
+ tempoEstimado: "30-45 min",
+ conceitos: [
+ "Sequenciamento",
+ "Laços de Repetição (While/For)",
+ "Condicionais (If/Else)",
+ "Sensores de Colisão",
+ "Variáveis (Incremento)",
+ "Lógica de Negação (Não)"
+ ],
+ route: "/atividades/programacao/aspirador",
+ component: "AspiradorGame",
+ objectives: [
+ "Compreender sequenciamento e repetições",
+ "Utilizar sensores para tomada de decisão em tempo real",
+ "Criar padrões expansivos usando variáveis numéricas",
+ "Desenvolver algoritmos de cobertura de área (Zigue-zague e Espiral)"
+ ],
+ metadata: {
+ lastUpdated: "2026-03-06",
+ version: "2.0.0",
+ },
+
+ fases: [
+ // ==========================================
+ // NÍVEL 1: MOVIMENTO E PADRÕES
+ // ==========================================
+ {
+ id: 1,
+ nome: "Fase 1: A Linha Reta",
+ descricao: "Use o bloco 'Enquanto houver sujeira' e o comando 'Mover' para ligá-lo.",
+ timeout: 10,
+ maxBlocks: 3,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "robo_mover", "robo_ainda_tem_sujeira"],
+ direcao: 90,
+ matriz: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [4, 1, 2, 2, 2, 2, 2, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ],
+ js: "while (aindaTemSujeira()) {\n mover();\n}"
+ },
+ {
+ id: 2,
+ nome: "Fase 2: A Curva no Corredor",
+ descricao: "Use o bloco de 'Repita X vezes' para andar, virar e andar de novo.",
+ timeout: 15,
+ maxBlocks: 7,
+ background: "piso_claro",
+ // Restringe ao laço numérico para treinar sequenciamento sem sensores ainda
+ allowedBlocks: ["controls_repeat_ext", "math_number", "robo_mover", "robo_virar"],
+ direcao: 90,
+ matriz: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [4, 1, 2, 2, 2, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ],
+ js: "for (var i = 0; i < 4; i++) {\n mover();\n}\nvirar('direita');\nfor (var j = 0; j < 3; j++) {\n mover();\n}"
+ },
+ {
+ id: 3,
+ nome: "Fase 3: A Escadinha",
+ descricao: "Crie uma escada colocando um 'Mover' e 'Virar' repetidas vezes.",
+ timeout: 20,
+ maxBlocks: 7,
+ background: "piso_claro",
+ allowedBlocks: ["controls_repeat_ext", "math_number", "robo_mover", "robo_virar"],
+ direcao: 90,
+ matriz: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [4, 1, 2, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 2, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 2, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 2, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 4, 0, 0, 0, 0]
+ ],
+ js: "for (var i = 0; i < 4; i++) {\n mover();\n virar('direita');\n mover();\n virar('esquerda');\n}"
+ },
+
+ // ==========================================
+ // NÍVEL 2: SENSORES E DECISÕES
+ // ==========================================
+ {
+ id: 4,
+ nome: "Fase 4: O Sensor de Impacto",
+ descricao: "Use 'Se / Senão' e o Sensor: Se a frente estiver bloqueada por um vaso, vire. Senão, mova-se.",
+ timeout: 20,
+ maxBlocks: 6,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "robo_if_else", "robo_mover", "robo_virar", "robo_bloqueado", "robo_ainda_tem_sujeira"],
+ direcao: 90,
+ matriz: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [4, 1, 2, 2, 2, 2, 2, 2, 4, 0],
+ [0, 0, 0, 0, 0, 0, 0, 2, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 2, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 2, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ],
+ js: "while (aindaTemSujeira()) {\n if (caminhoBloqueado('frente')) {\n virar('direita');\n } else {\n mover();\n }\n}"
+ },
+ {
+ id: 5,
+ nome: "Fase 5: Modo Bordas",
+ descricao: "Limpe apenas os cantos da sala seguindo a parede até dar a volta.",
+ timeout: 30,
+ maxBlocks: 6,
+ background: "piso_escuro",
+ allowedBlocks: ["controls_whileUntil", "robo_if_else", "robo_mover", "robo_virar", "robo_bloqueado", "robo_ainda_tem_sujeira"],
+ msgErroValidacao: "No modo bordas, o robô deve seguir a parede virando sempre para o mesmo lado (90° constantes).",
+ direcao: 90,
+ matriz: [
+ [4, 3, 4, 3, 4, 3, 4, 3, 4, 3],
+ [3, 1, 2, 2, 2, 2, 2, 2, 2, 4],
+ [4, 2, 0, 0, 0, 0, 0, 0, 2, 3],
+ [3, 2, 0, 0, 0, 0, 0, 0, 2, 4],
+ [4, 2, 0, 0, 0, 0, 0, 0, 2, 3],
+ [3, 2, 2, 2, 2, 2, 2, 2, 2, 4],
+ [4, 3, 4, 3, 4, 3, 4, 3, 4, 3]
+ ],
+ js: "while (aindaTemSujeira()) {\n if (caminhoBloqueado('frente')) {\n virar('direita');\n } else {\n mover();\n }\n}"
+ },
+ {
+ id: 6,
+ nome: "Fase 6: Desvio de Obstáculo",
+ descricao: "Se o caminho à frente bloquear, você precisa dar a volta por fora e voltar ao trilho.",
+ timeout: 35,
+ maxBlocks: 12,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "robo_if_else", "robo_mover", "robo_virar", "robo_bloqueado", "robo_ainda_tem_sujeira"],
+ direcao: 90,
+ matriz: [
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 2, 2, 0, 0, 0, 0],
+ [4, 1, 2, 4, 0, 2, 2, 2, 3, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ ],
+ js: "while (aindaTemSujeira()) {\n if (caminhoBloqueado('frente')) {\n virar('esquerda');\n mover();\n virar('direita');\n mover();\n mover();\n virar('direita');\n mover();\n virar('esquerda');\n } else {\n mover();\n }\n}"
+ },
+
+ // ==========================================
+ // NÍVEL 3: VARIÁVEIS (PADRÕES CRESCENTES)
+ // ==========================================
+ {
+ id: 7,
+ nome: "Fase 7: A Escada Crescente",
+ descricao: "Use a variável [passos] para fazer o robô acompanhar esse crescimento!",
+ timeout: 30,
+ maxBlocks: 15,
+ background: "piso_claro",
+ allowedBlocks: ["controls_repeat_ext", "math_number", "robo_passos_set", "robo_passos_get", "robo_passos_change", "robo_mover", "robo_virar"],
+ direcao: 180, // Começa olhando para baixo
+ validationRegex: /passos\s*(?:=\s*passos\s*\+\s*1|\+=\s*1|\+\+)/,
+ msgErroIncremento: "Você precisa usar o bloco 'aumentar [passos] em 1' para o degrau crescer!",
+ matriz: [
+ [1, 4, 0, 0, 0, 0, 0, 0, 0, 3],
+ [2, 2, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 2, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 2, 2, 2, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 0, 0, 0, 0, 0, 0],
+ [4, 0, 0, 2, 2, 2, 2, 3, 0, 4]
+ ],
+ js: "var passos = 1;\nfor (var i = 0; i < 3; i++) {\n for (var j = 0; j < passos; j++) {\n mover();\n }\n virar('esquerda');\n for (var k = 0; k < passos; k++) {\n mover();\n }\n virar('direita');\n passos = passos + 1;\n}"
+ },
+ {
+ id: 8,
+ nome: "Fase 8: A Espiral de Limpeza",
+ descricao: "Junte o que aprendeu sobre repetições com o aumento da variável para limpar do centro para as bordas.",
+ timeout: 45,
+ maxBlocks: 15,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "controls_repeat_ext", "math_number", "robo_passos_set", "robo_passos_get", "robo_passos_change", "robo_mover", "robo_virar", "robo_ainda_tem_sujeira", "robo_bloqueado", "robo_if", "robo_not"],
+ direcao: 0,
+ msgErroIncremento: "A espiral exige que a distância (variável passos) aumente a cada volta!",
+ validationRegex: /passos\s*(?:=\s*passos\s*\+\s*1|\+=\s*1|\+\+)/,
+ matriz: [
+ [4, 0, 0, 0, 0, 0, 0, 0, 0, 3],
+ [0, 0, 2, 2, 2, 2, 2, 0, 0, 0],
+ [0, 0, 2, 2, 2, 2, 2, 0, 0, 0],
+ [0, 0, 2, 2, 1, 2, 2, 0, 0, 0],
+ [0, 0, 2, 2, 2, 2, 2, 0, 0, 0],
+ [0, 0, 2, 2, 2, 2, 2, 0, 0, 0],
+ [3, 0, 0, 0, 0, 0, 0, 0, 0, 4]
+ ],
+ js: "var passos = 1;\nwhile (aindaTemSujeira()) {\n for (var i = 0; i < 2; i++) {\n for (var j = 0; j < passos; j++) {\n if (!caminhoBloqueado('frente')) {\n mover();\n }\n }\n virar('direita');\n }\n passos = passos + 1;\n}"
+ },
+
+ // ==========================================
+ // NÍVEL 4: LÓGICA AVANÇADA
+ // ==========================================
+ {
+ id: 9,
+ nome: "Fase 9: O Labirinto Cego",
+ descricao: "Se a frente estiver bloqueada, teste a direita! Se a direita também bloquear, vire à esquerda.",
+ timeout: 45,
+ maxBlocks: 10,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "robo_if_else", "robo_if", "robo_mover", "robo_virar", "robo_bloqueado", "robo_ainda_tem_sujeira"],
+ direcao: 90,
+ matriz: [
+ [1, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [4, 3, 4, 3, 4, 3, 4, 3, 4, 2], // Parede força a descer
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 4, 3, 4, 3, 4, 3, 4, 3, 4], // Parede força a descer pela esquerda
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [4, 3, 4, 3, 4, 3, 4, 3, 4, 2], // Parede força a descer pela direita
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
+ ],
+ js: "while (aindaTemSujeira()) {\n if (caminhoBloqueado('frente')) {\n if (caminhoBloqueado('direita')) {\n virar('esquerda');\n } else {\n virar('direita');\n }\n } else {\n mover();\n }\n}"
+ },
+ {
+ id: 10,
+ nome: "Fase 10: Zigue-Zague",
+ descricao: "Crie um algoritmo de espelhamento (Zigue-Zague) combinando o bloco 'NÃO' e os sensores.",
+ timeout: 60,
+ maxBlocks: 21,
+ background: "piso_claro",
+ allowedBlocks: ["controls_whileUntil", "robo_if", "robo_if_else", "robo_not", "robo_mover", "robo_virar", "robo_bloqueado", "robo_ainda_tem_sujeira"],
+ direcao: 90,
+ msgErroValidacao: "Para limpar tudo sem gastar bateria, você deve alternar as viradas (Esquerda e Direita) num padrão de espelho perfeito!",
+ matriz: [
+ [1, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
+ [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
+ ],
+ js: "while (aindaTemSujeira()) {\n if (caminhoBloqueado('frente')) {\n virar('direita');\n if (!caminhoBloqueado('frente')) {\n mover();\n virar('direita');\n while (!caminhoBloqueado('frente')) {\n mover();\n }\n virar('esquerda');\n if (!caminhoBloqueado('frente')) {\n mover();\n virar('esquerda');\n }\n }\n } else {\n mover();\n }\n}"
+ }
+ ],
+
+ mensagens: {
+ entradaIncorreta: "Seu robô está parado! Verifique se você usou os blocos de Movimento.",
+ saidaIncorreta: "O robô bateu ou ficou preso! Revise sua lógica de sensores e curvas.",
+ erroGeral: "O sistema de navegação falhou. Reinicie os blocos e tente uma nova estratégia.",
+ sucessoGenerico: "Excelente! Missão concluída.",
+ timeoutExcedido: "Bateria esgotada! O robô não conseguiu limpar tudo a tempo. Tente um caminho mais eficiente.",
+ },
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/config/debugSolutions.js b/app/src/atividades/programacao/aspirador/config/debugSolutions.js
new file mode 100644
index 0000000..9337650
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/config/debugSolutions.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ * * @module games.aspirador.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_whileUntil", "id": "z`+9FERBc[#C{DF0qLKo", "x": 38, "y": 63, "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "Q9{W.6:H}mhGgHaW#Jl$" } }, "DO": { "block": { "type": "robo_mover", "id": "GY]faaY{o.T,X3z:ke,[" } } } }] } },
+ 2: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_repeat_ext", "id": "qQiC857Z8^744|;;=#Rl", "x": 88, "y": 38, "inputs": { "TIMES": { "block": { "type": "math_number", "id": "G,6SEx#|Xi`{/=Y7z9%X", "fields": { "NUM": 2 } } }, "DO": { "block": { "type": "controls_repeat_ext", "id": "1)h4Hs##Qlgp~Q3)}{u9", "inputs": { "TIMES": { "block": { "type": "math_number", "id": "ZQ59,M3MoT/ixV#4~MLO", "fields": { "NUM": 4 } } }, "DO": { "block": { "type": "robo_mover", "id": "yU252A1/y]]f3]lFaQnz" } } }, "next": { "block": { "type": "robo_virar", "id": "YiYbJRF7NX,bKc8XmS8H", "fields": { "DIRECAO": "DIREITA" } } } } } } }] } },
+ 3: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_repeat_ext", "id": ".PM/Ok.U-LI9zBcN`Tff", "x": 63, "y": 38, "inputs": { "TIMES": { "block": { "type": "math_number", "id": "uwvH}w+GG%^/-4e_3~Nb", "fields": { "NUM": 4 } } }, "DO": { "block": { "type": "robo_mover", "id": "w%AD5UlheXiD3~+{M4Du", "next": { "block": { "type": "robo_virar", "id": "8(J:{mas+h]+u2BDG]V*", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "robo_mover", "id": "wQ?)#`vK[GZVBT:d1w./", "next": { "block": { "type": "robo_virar", "id": "eM.S;LJ!aLO3$itXe#W-", "fields": { "DIRECAO": "ESQUERDA" } } } } } } } } } } }] } },
+ 4: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_whileUntil", "id": "yIMZ``.@EH/4.([Fe#Y{", "x": 13, "y": 38, "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "(!.D]lmG3$!+X3ZUolVC" } }, "DO": { "block": { "type": "robo_if_else", "id": "vteh/xjeNmGn+nepnTMz", "inputs": { "CONDICAO": { "block": { "type": "robo_bloqueado", "id": "%oNA;tN,!/^?K0`ddBj~", "fields": { "SENTIDO": "FRENTE" } } }, "FACA": { "block": { "type": "robo_virar", "id": "I[mh-8|eV8*l093y8kXM", "fields": { "DIRECAO": "DIREITA" } } }, "SENAO": { "block": { "type": "robo_mover", "id": "x-%P*)*_;H;2@UsUo;|," } } } } } } }] } },
+ 5: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_whileUntil", "id": "xT~lXFK.CqT/w7x*!lHX", "x": 31, "y": 80, "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "fSn99zjFK[9YB|Qt*qiF" } }, "DO": { "block": { "type": "robo_if_else", "id": "FPFLw`jCzRq+99Ox6]9;", "inputs": { "CONDICAO": { "block": { "type": "robo_bloqueado", "id": "]/`EA1Gb@Fhug7/89YY|", "fields": { "SENTIDO": "FRENTE" } } }, "FACA": { "block": { "type": "robo_virar", "id": "$(%,l|Mi542;nAzm]5(M", "fields": { "DIRECAO": "DIREITA" } } }, "SENAO": { "block": { "type": "robo_mover", "id": "|LEg;*WF_2H|RiM?0nak" } } } } } } }] } },
+ 6: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "robo_mover", "id": "!beDf|CKAdE]Pq+u#!+s", "x": 63, "y": 38, "next": { "block": { "type": "robo_virar", "id": "3RBd?a0c)||Ct2#6}S:$", "fields": { "DIRECAO": "ESQUERDA" }, "next": { "block": { "type": "robo_mover", "id": "aYI2OD+D@~W6ZIl9$1~W", "next": { "block": { "type": "robo_virar", "id": "!G|RISODQu5[{tzI_!5`", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "robo_mover", "id": "I/F:Ona`]5A?h[7[r$},", "next": { "block": { "type": "robo_mover", "id": "@*mPCkXi83Wru)rKZ$;M", "next": { "block": { "type": "robo_mover", "id": "bAMMIR~u1Cxzq4nhqU.p", "next": { "block": { "type": "robo_virar", "id": "hBp68K1fY5+vp}m]*Hn*", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "robo_mover", "id": "na+Y)U@2SkSDR06^Hjg0", "next": { "block": { "type": "robo_virar", "id": "t]VH-I19H1hV7K2Q==Zx", "fields": { "DIRECAO": "ESQUERDA" }, "next": { "block": { "type": "robo_mover", "id": "L/.4K.fQTt:piUuPJ_7W", "next": { "block": { "type": "robo_mover", "id": "X[:(])AH5E9p3M#vbs^F" } } } } } } } } } } } } } } } } } } } } } } }] } },
+ 7: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "robo_passos_set", "id": "X+}=8*(nLRcpL!9r2N(~", "x": 29, "y": 140, "inputs": { "VALOR": { "block": { "type": "math_number", "id": "+08WKoLsuMH7vadc5=`@", "fields": { "NUM": 1 } } } }, "next": { "block": { "type": "controls_repeat_ext", "id": "1F(h5%9CMjU@V7fWUIW4", "inputs": { "TIMES": { "block": { "type": "math_number", "id": "W^[`yC.})u!FLB{wdMXT", "fields": { "NUM": 3 } } }, "DO": { "block": { "type": "controls_repeat_ext", "id": "uh2W($TTLO2sKIq99RM~", "inputs": { "TIMES": { "block": { "type": "robo_passos_get", "id": ":Q.w_C,5(%Qe{OUxbXFD" } }, "DO": { "block": { "type": "robo_mover", "id": "T(5A].GBtgT4^+ia,@Nv" } } }, "next": { "block": { "type": "robo_virar", "id": "VLRg3eC|t08ur8+k$=zi", "fields": { "DIRECAO": "ESQUERDA" }, "next": { "block": { "type": "controls_repeat_ext", "id": "zbY3CMg}q`0(Yl/$wxjl", "inputs": { "TIMES": { "block": { "type": "robo_passos_get", "id": "|qswpY1=VH.OhAOQ=0X?" } }, "DO": { "block": { "type": "robo_mover", "id": "B/]oj60w6i/Z:qW^^h!x" } } }, "next": { "block": { "type": "robo_virar", "id": "S9hZUx[A~3zDYFaO*mk`", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "robo_passos_change", "id": ".^E5@/q]9e?:)=lk!sT@" } } } } } } } } } } } } } }] } },
+ 8: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "robo_passos_set", "id": ")2S?VQvKtDbSYH|)[.h6", "x": -12, "y": -362, "inputs": { "VALOR": { "block": { "type": "math_number", "id": "C)o;-^.7+;g#PMc^K1;r", "fields": { "NUM": 1 } } } }, "next": { "block": { "type": "controls_whileUntil", "id": ";+%[|n.,d4~!0}^|Ey3~", "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "A=FlJKG;4KP#4eKS#U$e" } }, "DO": { "block": { "type": "controls_repeat_ext", "id": "(,y.Ls53b9_5-|3/E_B*", "inputs": { "TIMES": { "block": { "type": "math_number", "id": "I$|G5*7L?BWN]A,~mf,h", "fields": { "NUM": 2 } } }, "DO": { "block": { "type": "controls_repeat_ext", "id": "]VPOvSUj%m!N*YcsNtSb", "inputs": { "TIMES": { "block": { "type": "robo_passos_get", "id": "#o{aHXke[Vrxfa)cuPJr" } }, "DO": { "block": { "type": "robo_if", "id": "feY|w[y*GnGKT5Q#5E@h", "inputs": { "CONDICAO": { "block": { "type": "robo_not", "id": "|26~VFf)cim+/OztPIKV", "inputs": { "BOOL": { "block": { "type": "robo_bloqueado", "id": "sRmx!]c576uF3e#-{_J1", "fields": { "SENTIDO": "FRENTE" } } } } } }, "FACA": { "block": { "type": "robo_mover", "id": "hvJse`aK-rxytP:($|3X" } } } } } }, "next": { "block": { "type": "robo_virar", "id": "!1)|e0-^d`+lG:]7c*r*", "fields": { "DIRECAO": "DIREITA" } } } } } }, "next": { "block": { "type": "robo_passos_change", "id": "@1wYZzVADKI}T8qokuWB" } } } } } } } }] } },
+ 9: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_whileUntil", "id": "xsk|NgFG//v)LLzoMDG!", "x": 50, "y": 87, "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "23G7/4N(HIL}92H~(x!~" } }, "DO": { "block": { "type": "robo_if_else", "id": "p,VxRb5M#^9U-Rw+[CIr", "inputs": { "CONDICAO": { "block": { "type": "robo_bloqueado", "id": "WB/+t9+Z(oCnuq7;iEdz", "fields": { "SENTIDO": "FRENTE" } } }, "FACA": { "block": { "type": "robo_if_else", "id": "XPtT|?Xcx:p]`wO:^X/w", "inputs": { "CONDICAO": { "block": { "type": "robo_bloqueado", "id": "l:f6*K[/tx,u)uji(0VE", "fields": { "SENTIDO": "ESQUERDA" } } }, "FACA": { "block": { "type": "robo_virar", "id": "+L*?[V=BlPt,rBO6Az7G", "fields": { "DIRECAO": "DIREITA" } } }, "SENAO": { "block": { "type": "robo_virar", "id": "/OklKuNamD7^y0WljsKV", "fields": { "DIRECAO": "ESQUERDA" } } } } } }, "SENAO": { "block": { "type": "robo_mover", "id": "X#Rr35W]JJoh[F;|1*YZ" } } } } } } }] } },
+ 10: { "blocks": { "languageVersion": 0, "blocks": [{ "type": "controls_whileUntil", "id": "/3Q8xl3RGfxhGe86AF7m", "x": 13, "y": 13, "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_ainda_tem_sujeira", "id": "yvRE91],$![+nEW3C2-;" } }, "DO": { "block": { "type": "robo_if_else", "id": "htM_jv|S#2o~[YV*hGR|", "inputs": { "CONDICAO": { "block": { "type": "robo_bloqueado", "id": "*G8n[NSiy!N*?nk2U-7P", "fields": { "SENTIDO": "FRENTE" } } }, "FACA": { "block": { "type": "robo_virar", "id": "I/Rbq(Z5Skl*R{9,cdk$", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "robo_if", "id": ";j,eV*TRdTUa4=ND9M/!", "inputs": { "CONDICAO": { "block": { "type": "robo_not", "id": "7cO.F;Q3/I{UZSm4M|lh", "inputs": { "BOOL": { "block": { "type": "robo_bloqueado", "id": "g/ZLELf;{y{LkX1j)GFb", "fields": { "SENTIDO": "FRENTE" } } } } } }, "FACA": { "block": { "type": "robo_mover", "id": "SUtlAI09Jy}ExT}LD2H]", "next": { "block": { "type": "robo_virar", "id": "aaNewY:Hdt8`!x8+.V{F", "fields": { "DIRECAO": "DIREITA" }, "next": { "block": { "type": "controls_whileUntil", "id": "4J_#S0)[q9f6iUs?vQa:", "fields": { "MODE": "WHILE" }, "inputs": { "BOOL": { "block": { "type": "robo_not", "id": "@:+j*`]Xa*k#e(CwNqN`", "inputs": { "BOOL": { "block": { "type": "robo_bloqueado", "id": "Xb4mo/4x@Wz17r]CgI[{", "fields": { "SENTIDO": "FRENTE" } } } } } }, "DO": { "block": { "type": "robo_mover", "id": "YeBrVnP8(S*YW2[aPv!u" } } }, "next": { "block": { "type": "robo_virar", "id": "}_J_g;Y^d~jrp9m26Rmd", "fields": { "DIRECAO": "ESQUERDA" }, "next": { "block": { "type": "robo_if", "id": "7L.|=-gEc7U-z8r:eG?+", "inputs": { "CONDICAO": { "block": { "type": "robo_not", "id": "`3IlM;WJApe@AWi6y9k8", "inputs": { "BOOL": { "block": { "type": "robo_bloqueado", "id": "QwOui[*+]`YYx$KsQePj", "fields": { "SENTIDO": "FRENTE" } } } } } }, "FACA": { "block": { "type": "robo_mover", "id": "LV=OVlA|~R|^i5].f?E=", "next": { "block": { "type": "robo_virar", "id": ":yg*F*z$X4@a24TIxM#(", "fields": { "DIRECAO": "ESQUERDA" } } } } } } } } } } } } } } } } } } } } }, "SENAO": { "block": { "type": "robo_mover", "id": ")E4emztur3[Sq?^:UC{(" } } } } } } }] } },
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/config/starterBlocks.js b/app/src/atividades/programacao/aspirador/config/starterBlocks.js
new file mode 100644
index 0000000..d144a78
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/config/starterBlocks.js
@@ -0,0 +1,13 @@
+/**
+ * @fileoverview Utility module for starterBlocks.js
+ *
+ * @module games.aspirador.config.starterBlocks
+ */
+
+export const starterBlocks = {
+ 1: {"blocks":{"languageVersion":0,"blocks":[{"type":"controls_whileUntil","id":"z`+9FERBc[#C{DF0qLKo","x":13,"y":38,"fields":{"MODE":"WHILE"},"inputs":{"DO":{"block":{"type":"robo_mover","id":"GY]faaY{o.T,X3z:ke,["}}}}]}},
+ 2: {"blocks":{"languageVersion":0,"blocks":[{"type":"controls_repeat_ext","id":"qQiC857Z8^744|;;=#Rl","x":13,"y":13,"inputs":{"TIMES":{"block":{"type":"math_number","id":"G,6SEx#|Xi`{/=Y7z9%X","fields":{"NUM":0}}}}}]}},
+ 4: {"blocks":{"languageVersion":0,"blocks":[{"type":"robo_if_else","id":"vteh/xjeNmGn+nepnTMz","x":38,"y":38,"inputs":{"CONDICAO":{"block":{"type":"robo_bloqueado","id":"%oNA;tN,!/^?K0`ddBj~","fields":{"SENTIDO":"FRENTE"}}}}}]}},
+ 7: {"blocks":{"languageVersion":0,"blocks":[{"type":"robo_passos_set","id":"X+}=8*(nLRcpL!9r2N(~","x":29,"y":140,"inputs":{"VALOR":{"block":{"type":"math_number","id":"+08WKoLsuMH7vadc5=`@","fields":{"NUM":1}}}},"next":{"block":{"type":"controls_repeat_ext","id":"1F(h5%9CMjU@V7fWUIW4","inputs":{"TIMES":{"block":{"type":"math_number","id":"W^[`yC.})u!FLB{wdMXT","fields":{"NUM":3}}},"DO":{"block":{"type":"controls_repeat_ext","id":"uh2W($TTLO2sKIq99RM~","inputs":{"TIMES":{"block":{"type":"robo_passos_get","id":":Q.w_C,5(%Qe{OUxbXFD"}}},"next":{"block":{"type":"robo_passos_change","id":".^E5@/q]9e?:)=lk!sT@"}}}}}}}}]}},
+ 8: {"blocks":{"languageVersion":0,"blocks":[{"type":"robo_if","id":"feY|w[y*GnGKT5Q#5E@h","x":363,"y":-287,"inputs":{"CONDICAO":{"block":{"type":"robo_not","id":"|26~VFf)cim+/OztPIKV","inputs":{"BOOL":{"block":{"type":"robo_bloqueado","id":"sRmx!]c576uF3e#-{_J1","fields":{"SENTIDO":"FRENTE"}}}}}}}}]}}
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/config/tourSteps.js b/app/src/atividades/programacao/aspirador/config/tourSteps.js
new file mode 100644
index 0000000..43714d3
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/config/tourSteps.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.aspirador.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const aspiradorTourSteps = [
+ createWelcomeStep({
+ gameName: "Jogo Aspirador",
+ description:
+ "Bem-vindo ao mundo da programação! Aqui você vai aprender os fundamentos de lógica e como controlar um robô aspirador.",
+ challenge:
+ "Use programação em blocos para controlar o robô aspirador e limpar toda a sujeira!",
+ iconSvg: gameIcons.lock,
+ }),
+
+ createGameAreaStep({
+ title: "Área de Jogo",
+ description:
+ "Aqui você vê a sala com pisos, sujeira (pontos marrons) e obstáculos (vasos). O robô aspirador (azul) precisa limpar toda a sujeira sem bater nos obstáculos!",
+ }),
+
+ createToolboxStep({
+ description:
+ "Use blocos de Movimento (mover, virar), Sensores (ainda tem sujeira?, caminho bloqueado?), Lógica (se/senão, não) e Repetição (enquanto, repetir). Arraste-os para programar o robô!",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte sua sequência de comandos aqui. Encaixe os blocos na ordem correta para fazer o robô se movimentar e limpar toda a sujeira da sala.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Execute seu código! Você verá o robô se mover pela sala, limpando a sujeira passo a passo. Ouça o som do motor enquanto ele trabalha!",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Se o robô bater em obstáculos ou não limpar tudo, use o reset para recolocar a sujeira e tentar uma nova estratégia de limpeza.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "O jogo tem 10 fases progressivas: desde linha reta simples até algoritmos complexos de espiral e zigue-zague usando variáveis!",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Acompanhe seu progresso, veja o objetivo da fase atual e quantos blocos você pode usar neste desafio.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Acesse este tour novamente clicando no botão de ajuda sempre que precisar relembrar os controles.",
+ }),
+];
+
+export const aspiradorTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/aspirador/game.js b/app/src/atividades/programacao/aspirador/game.js
new file mode 100644
index 0000000..00439e6
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/game.js
@@ -0,0 +1,355 @@
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupAspiradorAPI } from "./hooks/setupAspiradorAPI.js";
+import { validationSolution } from "./validation/validators.js";
+import { gameConfig } from "./config/config.js";
+import { ConstantesJogo, ConstantesAssets } from "./ui/constants.js";
+import { inicializarLayout } from "./ui/layout.js";
+
+export class AspiradorScene extends BaseGameScene {
+ constructor() {
+ super("AspiradorScene");
+ this.matrizAtiva = [];
+ this.totalSujeiras = 0;
+ this.sujeirasSprites = {};
+ this.roboLogico = { col: 0, lin: 0, angulo: 0 };
+ this.obstaculos = [];
+ this.shouldValidate = false; // Flag para validação manual
+ this.executionStopped = false; // Flag separada para parar loops
+ }
+
+ create() {
+ this.validatorFunc = (historico) => validationSolution(historico, this.configFase, gameConfig, this);
+
+ this.setupStandardController(
+ () => setupAspiradorAPI(this, { animationSpeed: 250 }),
+ this.validatorFunc
+ );
+
+ inicializarLayout(this);
+ this.montarFase();
+ }
+
+ preload() {
+ this.preloadGlobalAssets(); // Som global (BaseGameScene)
+
+ const chaves = ConstantesAssets.CHAVES;
+ const paths = ConstantesAssets.PATHS;
+
+ this.load.image(chaves.PISO, paths.PISO);
+ this.load.image(chaves.ASPIRADOR, paths.ASPIRADOR);
+ this.load.image(chaves.SUJEIRA, paths.SUJEIRA);
+ this.load.image(chaves.OBSTACULO1, paths.OBSTACULO1);
+ this.load.image(chaves.OBSTACULO2, paths.OBSTACULO2);
+
+ // Carregar sons
+ this.load.audio(chaves.SOM_POP, paths.SOM_POP);
+ this.load.audio(chaves.SOM_BG, paths.SOM_BG);
+ }
+
+ onReset() {
+ this.isRunning = false;
+ this.executionStopped = true; // Para qualquer execução pendente
+ this.shouldValidate = false; // Limpa flag de validação pendente
+
+ // Para o som do aspirador imediatamente
+ this.stopAudio(ConstantesAssets.CHAVES.SOM_BG);
+
+ if (this._timerTimeout) {
+ clearTimeout(this._timerTimeout);
+ this._timerTimeout = null;
+ }
+ this.montarFase();
+ }
+
+ /**
+ * Hook Assíncrono de Sucesso: O robô comemora.
+ */
+ async onSuccess() {
+ if (this._timerTimeout) clearTimeout(this._timerTimeout);
+ this.isRunning = false;
+
+ // Para o som do aspirador ao completar
+ this.stopAudio(ConstantesAssets.CHAVES.SOM_BG);
+
+ return new Promise(resolve => {
+ this.tweens.add({
+ targets: this.aspiradorSprite,
+ angle: '+=360',
+ scale: 1,
+ duration: 300,
+ yoyo: true,
+ ease: 'Back.easeOut',
+ onComplete: resolve
+ });
+ });
+ }
+
+ /**
+ * Hook Assíncrono de Falha: O robô treme em pane.
+ */
+ async onFailure() {
+ if (this._timerTimeout) clearTimeout(this._timerTimeout);
+ this.isRunning = false;
+
+ // Para o som do aspirador ao falhar
+ this.stopAudio(ConstantesAssets.CHAVES.SOM_BG);
+
+ return new Promise(resolve => {
+ this.aspiradorSprite.setTint(0xff0000); // Fica vermelho de erro
+ this.tweens.add({
+ targets: this.aspiradorSprite,
+ x: '+=5',
+ yoyo: true,
+ repeat: 10,
+ duration: 50,
+ onComplete: () => {
+ this.aspiradorSprite.clearTint();
+ resolve();
+ }
+ });
+ });
+ }
+
+ onBeforeRun() {
+ this.isRunning = true;
+ this.historico = [];
+ this.shouldValidate = false; // Reset flag de validação manual
+ this.executionStopped = false; // Reset flag de parada
+
+ // Som do aspirador em loop durante execução
+ this.playAudio(ConstantesAssets.CHAVES.SOM_BG, { loop: true, volume: 0.5 });
+
+ // 1. Limpa resquícios de execuções anteriores
+ if (this._timerTimeout) clearTimeout(this._timerTimeout);
+
+ // 2. Inicia o Cronômetro (Timeout)
+ const limiteSegundos = this.configFase?.timeout || 30;
+
+ this._timerTimeout = setTimeout(() => {
+ if (this.isRunning) {
+ this.isRunning = false;
+
+ // Força a falha por tempo
+ const msg = this.gameConfig?.mensagens?.timeoutExcedido || "Tempo esgotado!";
+ this.handleFailure(msg);
+ }
+ }, limiteSegundos * 1000);
+ }
+
+ montarFase() {
+ if (this.aspiradorSprite) this.aspiradorSprite.destroy();
+
+ Object.values(this.sujeirasSprites).forEach(s => s.destroy());
+ this.obstaculos.forEach(o => o.destroy());
+
+ this.sujeirasSprites = {};
+ this.obstaculos = [];
+ this.totalSujeiras = 0;
+
+ const cfg = this.configFase;
+ this.matrizAtiva = JSON.parse(JSON.stringify(cfg.matriz));
+
+ for (let lin = 0; lin < this.matrizAtiva.length; lin++) {
+ for (let col = 0; col < this.matrizAtiva[lin].length; col++) {
+ let v = this.matrizAtiva[lin][col];
+ let pX = col * 80 + 40;
+ let pY = lin * 80 + 40;
+
+ if (v === 1) {
+ // Aspirador
+ this.aspiradorSprite = this.add.image(pX, pY, ConstantesAssets.CHAVES.ASPIRADOR).setDisplaySize(80, 80).setDepth(10);
+ this.aspiradorSprite.angle = cfg.direcao || 0;
+ this.roboLogico = { col, lin, angulo: cfg.direcao || 0 };
+ }
+ else if (v === 2) {
+ // Sujeira
+ this.sujeirasSprites[`${lin}-${col}`] = this.add.image(pX, pY, ConstantesAssets.CHAVES.SUJEIRA).setDisplaySize(64, 64).setDepth(5);
+ this.totalSujeiras++;
+ }
+ else if (v === 3 || v === 4) {
+ // Obstáculos normais (1x1) - Vasos ou Sofás
+ const chave = v === 3 ? ConstantesAssets.CHAVES.OBSTACULO1 : ConstantesAssets.CHAVES.OBSTACULO2;
+ this.obstaculos.push(this.add.image(pX, pY, chave).setDisplaySize(80, 80).setDepth(6));
+ }
+ }
+ }
+ }
+
+ // --- API para o Interpretador (Usado via ApiHelpers) ---
+
+ mover() {
+ if (this.executionStopped) {
+ return Promise.resolve();
+ }
+
+ // 1. Mapeamento estrito de direção (Sem trigonometria)
+ const angNorm = ((this.roboLogico.angulo % 360) + 360) % 360;
+ let dc = 0, dl = 0;
+
+ if (angNorm === 0) dl = -1; // Olhando para Cima (Subindo linha)
+ else if (angNorm === 90) dc = 1; // Olhando para Direita (Avançando coluna)
+ else if (angNorm === 180) dl = 1; // Olhando para Baixo (Descendo linha)
+ else if (angNorm === 270) dc = -1; // Olhando para Esquerda (Voltando coluna)
+
+ let nC = this.roboLogico.col + dc;
+ let nL = this.roboLogico.lin + dl;
+
+ // 2. Proteção: Se a frente está bloqueada, cancela o movimento (NÃO registra no histórico)
+ if (this.checarBloqueio('FRENTE')) {
+ return Promise.resolve();
+ }
+
+ // Só registra movimento no histórico se efetivamente vai se mover
+ this.historico.push({ tipo: "mover", l: nL, c: nC });
+
+ // 3. Atualiza posição lógica e limpa sujeira ANTES da animação
+ // Corrige condição de corrida com aindaTemSujeira()
+ this.roboLogico.col = nC;
+ this.roboLogico.lin = nL;
+
+ // Limpa a matriz IMEDIATAMENTE para que aindaTemSujeira() veja o estado correto
+ if (this.matrizAtiva[nL] && this.matrizAtiva[nL][nC] === 2) {
+ this.matrizAtiva[nL][nC] = 0;
+ this.totalSujeiras--;
+
+ // Toca som pop ao coletar sujeira
+ this.playAudio(ConstantesAssets.CHAVES.SOM_POP, { volume: 0.7 });
+
+ // INTERRUPÇÃO AUTOMÁTICA: Se limpou a última sujeira, para imediatamente
+ if (this.totalSujeiras === 0) {
+ this.executionStopped = true; // Para loops imediatamente
+ this.shouldValidate = true;
+
+ this.stopAudio(ConstantesAssets.CHAVES.SOM_BG);
+
+ // Para o interpretador imediatamente mas sem marcar como parado pelo utilizador,
+ // para que o fluxo de validação em handleValidation() não seja abortado.
+ if (this.gameInterpreter) {
+ this.gameInterpreter.stopInternal();
+ }
+
+ // Agenda validação (deixa um tempo para tweens existentes terminarem)
+ this.time.delayedCall(300, () => {
+ if (this.shouldValidate && this.validatorFunc) {
+ this.handleValidation(this.validatorFunc);
+ this.shouldValidate = false;
+ }
+ });
+ }
+ }
+
+ return new Promise(res => {
+ this.tweens.add({
+ targets: this.aspiradorSprite,
+ x: nC * 80 + 40,
+ y: nL * 80 + 40,
+ duration: 100, // 2x mais rápido (do 200ms para 100ms) para compensar som de 1s
+ onComplete: () => {
+ // Remove sprite visual após animação
+ let key = `${nL}-${nC}`;
+ if (this.sujeirasSprites[key]) {
+ this.sujeirasSprites[key].destroy();
+ delete this.sujeirasSprites[key];
+ }
+ res();
+ }
+ });
+ });
+ }
+
+ virar(dir) {
+ if (this.executionStopped) {
+ return Promise.resolve();
+ }
+ const incremento = (dir === 'DIREITA' ? 90 : -90);
+ this.roboLogico.angulo += incremento;
+ this.historico.push({ tipo: "virar", valor: dir });
+ return new Promise(res => {
+ this.tweens.add({ targets: this.aspiradorSprite, angle: this.roboLogico.angulo, duration: 100, onComplete: res });
+ });
+ }
+
+ checarBloqueio(sentido) {
+ // Se execução parou, sempre retorna true (bloqueado) para evitar mais movimentos
+ if (this.executionStopped) {
+ return true;
+ }
+
+ // 1. Calcula o ângulo que o sensor quer olhar
+ let anguloBase = ((this.roboLogico.angulo % 360) + 360) % 360;
+ let anguloTeste = anguloBase;
+
+ if (sentido === 'DIREITA') anguloTeste += 90;
+ else if (sentido === 'ESQUERDA') anguloTeste -= 90;
+
+ const angNorm = ((anguloTeste % 360) + 360) % 360;
+
+ // 2. Projeta onde o sensor está "batendo"
+ let dc = 0, dl = 0;
+ if (angNorm === 0) dl = -1;
+ else if (angNorm === 90) dc = 1;
+ else if (angNorm === 180) dl = 1;
+ else if (angNorm === 270) dc = -1;
+
+ const nC = this.roboLogico.col + dc;
+ const nL = this.roboLogico.lin + dl;
+
+ // 3. Leitura DINÂMICA do tamanho real da sua fase atual
+ const maxLinhas = this.matrizAtiva.length;
+ const maxCols = this.matrizAtiva[0]?.length || 0;
+
+ let bloqueado = false;
+
+ // Se saiu dos limites da matriz, é parede invisível!
+ if (nC < 0 || nC >= maxCols || nL < 0 || nL >= maxLinhas) {
+ bloqueado = true;
+ }
+ // Se encontrou um obstáculo interno (vaso, sofá, etc)
+ else if (this.matrizAtiva[nL] && this.matrizAtiva[nL][nC] >= 3) {
+ bloqueado = true;
+ }
+
+ this.historico.push({ tipo: "sensor", sentido, resultado: bloqueado });
+ return bloqueado;
+ }
+
+ aindaTemSujeira() {
+ // Se já paramos a execução, sempre retorna false para sair dos loops
+ if (this.executionStopped) {
+ return false;
+ }
+
+ // Usa o contador otimizado
+ const tem = this.totalSujeiras > 0;
+
+ this.historico.push({ tipo: "sensor", acao: "check_sujeira", resultado: tem });
+ return tem;
+ }
+}
+
+/**
+ * Factory para criar a configuração do Phaser para o jogo Aspirador.
+ * @param {HTMLElement} elementoPai - Container DOM
+ * @param {Object} configFaseAtual - Dados da fase selecionada
+ * @returns {Object} Configuração do Phaser
+ */
+export const createGame = (elementoPai, configFaseAtual) => {
+ const scene = new AspiradorScene();
+
+ return {
+ type: Phaser.AUTO,
+ width: ConstantesJogo.LARGURA_TELA || 800,
+ height: ConstantesJogo.ALTURA_TELA || 560,
+ backgroundColor: "#2d2d2d",
+ parent: elementoPai,
+ scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
+ scene: scene,
+ callbacks: {
+ preBoot: function (game) {
+ game.registry.set("configFase", configFaseAtual);
+ game.registry.set("gameConfig", gameConfig);
+ },
+ },
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/hooks/setupAspiradorAPI.js b/app/src/atividades/programacao/aspirador/hooks/setupAspiradorAPI.js
new file mode 100644
index 0000000..3fdd2b9
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/hooks/setupAspiradorAPI.js
@@ -0,0 +1,24 @@
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+export const setupAspiradorAPI = (scene, config) => {
+ const delay = (config && config.animationSpeed) || 100;
+
+ return function(interpreter, globalScope) {
+ // Registra como funções globais para o aluno usar mover() em vez de Robo.mover()
+ ApiHelpers.registerFunction(interpreter, globalScope, "mover",
+ ApiHelpers.createActionWrapper(scene, "mover", delay), true);
+
+ ApiHelpers.registerFunction(interpreter, globalScope, "virar",
+ ApiHelpers.createActionWrapper(scene, "virar", delay), true);
+
+ ApiHelpers.registerFunction(interpreter, globalScope, "caminhoBloqueado",
+ ApiHelpers.createConditionWrapper(scene, "checarBloqueio"), false);
+
+ ApiHelpers.registerFunction(interpreter, globalScope, "aindaTemSujeira",
+ ApiHelpers.createConditionWrapper(scene, "aindaTemSujeira"), false);
+
+ // Necessário para o highlight do Blockly
+ ApiHelpers.registerFunction(interpreter, globalScope, "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene), false);
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/hooks/useAspiradorTour.js b/app/src/atividades/programacao/aspirador/hooks/useAspiradorTour.js
new file mode 100644
index 0000000..4f351df
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/hooks/useAspiradorTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useAspiradorTour.js
+ *
+ * @module games.aspirador.hooks.useAspiradorTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { aspiradorTourSteps, aspiradorTourOptions } from "../config/tourSteps";
+
+export const useAspiradorTour = () => {
+ /**
+ * Hook que retorna o controlador de tour para o jogo Aspirador.
+ * Encapsula `useGameTour` com os passos e opções específicos.
+ * @returns {Object} API do tour (start, stop, etc.)
+ */
+ return useGameTour("aspirador", aspiradorTourSteps, aspiradorTourOptions);
+};
diff --git a/app/src/atividades/programacao/aspirador/ui/constants.js b/app/src/atividades/programacao/aspirador/ui/constants.js
new file mode 100644
index 0000000..42e3eef
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/ui/constants.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Utility module for constants.js
+ */
+
+import imgPiso from "../assets/image/piso.png";
+import imgAspirador from "../assets/image/aspirador.png";
+import imgSujeira from "../assets/image/sujeira.png";
+import imgObstaculo1 from "../assets/image/obstaculo1.png";
+import imgObstaculo2 from "../assets//image/obstaculo2.png";
+
+export const ConstantesJogo = {
+ LARGURA_TELA: 800,
+ ALTURA_TELA: 560,
+ TILE_SIZE: 80,
+};
+
+import sndPop from "../assets/sound/pop.mp3";
+import sndSomBg from "../assets/sound/bg_sound.mp3";
+
+export const ConstantesAssets = {
+ CHAVES: {
+ PISO: "decoda_piso",
+ ASPIRADOR: "decoda_aspirador",
+ SUJEIRA: "decoda_sujeira",
+ OBSTACULO1: "decoda_obstaculo1",
+ OBSTACULO2: "decoda_obstaculo2",
+ SOM_POP: "decoda_som_pop",
+ SOM_BG: "decoda_som_bg"
+ },
+ PATHS: {
+ PISO: imgPiso,
+ ASPIRADOR: imgAspirador,
+ SUJEIRA: imgSujeira,
+ OBSTACULO1: imgObstaculo1,
+ OBSTACULO2: imgObstaculo2,
+ SOM_POP: sndPop,
+ SOM_BG: sndSomBg
+ }
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/ui/layout.js b/app/src/atividades/programacao/aspirador/ui/layout.js
new file mode 100644
index 0000000..7eacd5b
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/ui/layout.js
@@ -0,0 +1,22 @@
+import { ConstantesJogo, ConstantesAssets } from "./constants.js";
+
+/**
+ * Inicializa o layout base do jogo
+ * @param {Phaser.Scene} scene
+ */
+export function inicializarLayout(scene) {
+ const { LARGURA_TELA, ALTURA_TELA, TILE_SIZE } = ConstantesJogo;
+ const { CHAVES } = ConstantesAssets;
+
+ // Piso
+ const piso = scene.add.tileSprite(0, 0, LARGURA_TELA, ALTURA_TELA, CHAVES.PISO).setOrigin(0, 0);
+
+ // Grade de debug suave
+ const grade = scene.add.graphics();
+ grade.lineStyle(1, 0x000000, 0.05);
+ for (let x = 0; x <= LARGURA_TELA; x += TILE_SIZE) grade.moveTo(x, 0).lineTo(x, ALTURA_TELA);
+ for (let y = 0; y <= ALTURA_TELA; y += TILE_SIZE) grade.moveTo(0, y).lineTo(LARGURA_TELA, y);
+ grade.strokePath();
+
+ return { piso, grade };
+}
\ No newline at end of file
diff --git a/app/src/atividades/programacao/aspirador/validation/validators.js b/app/src/atividades/programacao/aspirador/validation/validators.js
new file mode 100644
index 0000000..534c070
--- /dev/null
+++ b/app/src/atividades/programacao/aspirador/validation/validators.js
@@ -0,0 +1,53 @@
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador do jogo Aspirador (Atualizado para 10 Fases).
+ * @class AspiradorValidator
+ * @extends BaseGameValidator
+ */
+export class AspiradorValidator extends BaseGameValidator {
+ validatePhase(history, config, gameConfig, sceneRef) {
+ // 1. Sanity Check: O robô se mexeu?
+ if (!history || history.length === 0) {
+ return this.failure(gameConfig?.mensagens?.semMovimento || "O robô não saiu do lugar!");
+ }
+
+ // 2. Validação Específica por ID de Fase (Regras Pedagógicas)
+ if (config.id === 5) {
+ // Bordas: deve ter virado sempre para o mesmo lado para fazer o contorno
+ const viradas = history.filter(h => h.tipo === 'virar');
+ const soUmLado = new Set(viradas.map(v => v.valor)).size === 1;
+ if (!soUmLado) return this.failure(config.msgErroValidacao);
+ }
+
+ if (config.id === 7 || config.id === 8) {
+ // Verifica se o aluno usou o bloco de criar/incrementar variável
+ const codigo = sceneRef?.currentCode || '';
+ const temDeclaracao = /(?:var|let|const)\s+passos/.test(codigo);
+ const temIncremento = /passos\s*(?:=\s*passos\s*\+\s*1|\+=\s*1|\+\+)/.test(codigo);
+
+ if (!temDeclaracao || !temIncremento) {
+ return this.failure(config.msgErroIncremento || "Você precisa usar a variável e incrementá-la!");
+ }
+ }
+
+ if (config.id === 10) {
+ // Zigue-Zague: deve ter virado para ambos os lados no padrão de espelho
+ const viradas = history.filter(h => h.tipo === 'virar').map(h => h.valor);
+ const usouAmbos = viradas.includes('ESQUERDA') && viradas.includes('DIREITA');
+ if (!usouAmbos) return this.failure(config.msgErroValidacao);
+ }
+
+ // 3. Check Final Universal: Sobrou sujeira?
+ if (sceneRef && sceneRef.aindaTemSujeira()) {
+ return this.failure("Ainda há sujeira na sala! O algoritmo não cobriu toda a área.");
+ }
+
+ return this.success();
+ }
+}
+
+export function validationSolution(history, config, gameConfig, sceneRef) {
+ const validator = new AspiradorValidator();
+ return validator.validate(history, config, gameConfig, sceneRef);
+}
\ No newline at end of file
diff --git a/app/src/atividades/programacao/automato/AutomatoGame.jsx b/app/src/atividades/programacao/automato/AutomatoGame.jsx
new file mode 100644
index 0000000..712050c
--- /dev/null
+++ b/app/src/atividades/programacao/automato/AutomatoGame.jsx
@@ -0,0 +1,84 @@
+/**
+ * @fileoverview React component for AutomatoGame.jsx
+ *
+ * @module games.automato.AutomatoGame
+ */
+
+
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { registerBlocks, generateDynamicToolbox } from "./blocks/blocks";
+import {
+ GameStateProvider,
+ useGameState,
+} from "../../../contexts/GameStateContext";
+import { useAutomatoTour } from "./hooks/useAutomatoTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+function AutomatoGameContent() {
+ const { isDebugMode, setFailureMessage } = useGameState();
+ const { startTour } = useAutomatoTour();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(() => {
+
+
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ const renderEditor = () => {
+ return (
+
+ );
+ };
+
+ return (
+
+ {renderEditor()}
+
+ );
+}
+
+/**
+ * Conteúdo principal do jogo Automato.
+ * Configura toolbox, registra blocos e injeta o `gameFactory` no `GameBase`.
+ * @returns {JSX.Element} Conteúdo do editor e canvas do Automato
+ */
+
+
+export default function AutomatoGame() {
+ return (
+
+
+
+ );
+}
+
+/**
+ * Componente de página que provê o contexto para o jogo Automato
+ * e monta `AutomatoGameContent` dentro do `GameStateProvider`.
+ * @returns {JSX.Element}
+ */
+
+AutomatoGameContent.propTypes = {};
+AutomatoGame.propTypes = {};
diff --git a/app/src/atividades/programacao/automato/__tests__/integration.test.js b/app/src/atividades/programacao/automato/__tests__/integration.test.js
new file mode 100644
index 0000000..675df5d
--- /dev/null
+++ b/app/src/atividades/programacao/automato/__tests__/integration.test.js
@@ -0,0 +1,520 @@
+/**
+ * @fileoverview Utility module for integration.test.js
+ *
+ * @module games.automato.__tests__.integration.test
+ */
+
+import { describe, it, expect, vi } from "vitest";
+import { GameInterpreter } from "../../../../interpreters/GameInterpreter";
+import { setupAutomatoAPI } from "../hooks/interpreterSetup";
+import { validateSolution } from "../validation/validators";
+import { gameConfig } from "../config/config";
+
+// Mock de soluções para teste
+const SOLUTIONS = {
+ fase1: `
+ moverParaFrente();
+ moverParaFrente();
+ `,
+ fase1_fail: `
+ moverParaFrente();
+ `,
+ fase2: `
+ moverParaFrente();
+ virarEsquerda();
+ moverParaFrente();
+ virarDireita();
+ moverParaFrente();
+ `,
+ fase2_fail: `
+ moverParaFrente();
+ virarDireita();
+ moverParaFrente();
+ `,
+ fase3: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ }
+ `,
+ fase3_fail: `
+ moverParaFrente();
+ moverParaFrente();
+ `,
+ fase4: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ virarEsquerda();
+ moverParaFrente();
+ virarDireita();
+ }
+ `,
+ fase4_fail: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ virarEsquerda();
+ virarDireita();
+ }
+ `,
+ fase5: `
+ moverParaFrente();
+ moverParaFrente();
+ virarEsquerda();
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ }
+ `,
+ fase5_fail: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ virarEsquerda();
+ moverParaFrente();
+ virarDireita();
+ }
+ `,
+ fase6: `
+ while (!chegouNoAlvo()) {
+ if (haCaminho('frente')) {
+ moverParaFrente();
+ } else {
+ virarEsquerda();
+ }
+ }
+ `,
+ fase6_fail: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ }
+ `,
+ fase7: `
+ while (!chegouNoAlvo()) {
+ if (haCaminho('esquerda')) {
+ virarEsquerda();
+ moverParaFrente();
+ } else if (haCaminho('frente')) {
+ moverParaFrente();
+ } else {
+ virarDireita();
+ }
+ }
+ `,
+ fase7_fail: `
+ moverParaFrente();
+ moverParaFrente();
+ moverParaFrente();
+ `,
+ fase8: `
+ while (!chegouNoAlvo()) {
+ if (haCaminho('esquerda')) {
+ virarEsquerda();
+ moverParaFrente();
+ } else if (haCaminho('frente')) {
+ moverParaFrente();
+ } else {
+ virarDireita();
+ }
+ }
+ `,
+ fase8_fail: `
+ while (!chegouNoAlvo()) {
+ moverParaFrente();
+ if (haCaminho("frente")) {
+ virarEsquerda();
+ }
+ if (haCaminho("direita")) {
+ virarDireita();
+ }
+ }
+ `,
+ fase9: `
+ while (!chegouNoAlvo()) {
+ if (haCaminho('esquerda')) {
+ virarEsquerda();
+ moverParaFrente();
+ } else if (haCaminho('frente')) {
+ moverParaFrente();
+ } else {
+ virarDireita();
+ }
+ }
+ `,
+ fase9_fail: `
+ if (haCaminho("esquerda")) {
+ moverParaFrente();
+ } else {
+ virarEsquerda();
+ }
+ `,
+ fase10: `
+ while (!chegouNoAlvo()) {
+ if (haCaminho('esquerda')) {
+ virarEsquerda();
+ moverParaFrente();
+ } else if (haCaminho('frente')) {
+ moverParaFrente();
+ } else if (haCaminho('direita')) {
+ virarDireita();
+ moverParaFrente();
+ } else {
+ virarDireita();
+ virarDireita();
+ }
+ }
+ `,
+ fase10_fail: `
+ if (haCaminho('esquerda')) {
+ virarEsquerda();
+ moverParaFrente();
+ } else if (haCaminho('frente')) {
+ moverParaFrente();
+ }
+ `,
+};
+
+// Constantes para tipos de tile
+const TILE_TYPES = {
+ PAREDE: 0,
+ CAMINHO: 1,
+ INICIO: 2,
+ FIM: 3,
+};
+
+// Constantes para direções
+const Direcao = {
+ NORTE: 0,
+ LESTE: 1,
+ SUL: 2,
+ OESTE: 3,
+};
+
+// Cena de teste que replica os métodos reais da AutomatoScene
+class TestAutomatoScene {
+ constructor(configFase) {
+ this.mapa = configFase.mapa;
+ this.historico = [];
+ this.resultadoJogada = "em_andamento";
+ this.configFase = configFase;
+
+ this.posicaoInicial = this.encontrarPosicao(TILE_TYPES.INICIO);
+ this.posicaoFinal = this.encontrarPosicao(TILE_TYPES.FIM);
+ this.posicaoJogador = { ...this.posicaoInicial };
+ this.direcaoJogador = Direcao.LESTE;
+
+ this.pegmanSprite = {
+ setPosition: vi.fn(),
+ play: vi.fn(),
+ setFrame: vi.fn(),
+ on: vi.fn(),
+ off: vi.fn(),
+ x: 0,
+ y: 0,
+ };
+
+ this.tweens = {
+ add: vi.fn((config) => {
+ if (config.onComplete) {
+ setTimeout(config.onComplete, 0);
+ }
+ return { stop: vi.fn() };
+ }),
+ };
+
+ this.sound = {
+ play: vi.fn(),
+ context: { state: "running", resume: vi.fn() },
+ };
+
+ this.anims = {
+ exists: vi.fn(() => true),
+ };
+ }
+
+ encontrarPosicao(tipo) {
+ for (let y = 0; y < this.mapa.length; y++) {
+ for (let x = 0; x < this.mapa[y].length; x++) {
+ if (this.mapa[y][x] === tipo) return { x, y };
+ }
+ }
+ return null;
+ }
+
+ atualizarVisualJogador() {
+ if (this.posicaoJogador) {
+ const TAMANHO_TILE = 50;
+ const posX = this.posicaoJogador.x * TAMANHO_TILE + TAMANHO_TILE / 2;
+ const posY = this.posicaoJogador.y * TAMANHO_TILE + TAMANHO_TILE / 2 - 6;
+ this.pegmanSprite.setPosition(posX, posY);
+
+ const animacoesDirecao = [
+ "pegman_idle_norte",
+ "pegman_idle_leste",
+ "pegman_idle_sul",
+ "pegman_idle_oeste",
+ ];
+ this.pegmanSprite.play(animacoesDirecao[this.direcaoJogador]);
+ }
+ }
+
+ async moverParaFrente() {
+ if (this.resultadoJogada !== "em_andamento") {
+ return Promise.resolve();
+ }
+
+ // Proteção contra loops infinitos no teste
+ if (this.historico.length > 500) {
+ this.resultadoJogada = "falha";
+ console.warn(
+ "⚠️ Limite de ações atingido (500) - possível loop infinito",
+ );
+ return Promise.resolve();
+ }
+
+ let { x, y } = this.posicaoJogador;
+
+ if (this.direcaoJogador === Direcao.NORTE) y--;
+ else if (this.direcaoJogador === Direcao.LESTE) x++;
+ else if (this.direcaoJogador === Direcao.SUL) y++;
+ else if (this.direcaoJogador === Direcao.OESTE) x--;
+
+ const proximoTile =
+ this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;
+
+ if (proximoTile === TILE_TYPES.PAREDE || proximoTile === -1) {
+ this.animarFalha();
+ this.resultadoJogada = "falha";
+ return Promise.resolve();
+ } else {
+ const TAMANHO_TILE = 50;
+ const novaX = x * TAMANHO_TILE + TAMANHO_TILE / 2;
+ const novaY = y * TAMANHO_TILE + TAMANHO_TILE / 2 - 6;
+
+ return new Promise((resolve) => {
+ this.tweens.add({
+ targets: this.pegmanSprite,
+ x: novaX,
+ y: novaY,
+ duration: 0,
+ ease: "Power2",
+ onComplete: () => {
+ this.posicaoJogador = { x, y };
+ this.historico.push({
+ action: "moverParaFrente",
+ position: { ...this.posicaoJogador },
+ direction: this.direcaoJogador,
+ });
+ this.atualizarVisualJogador();
+ resolve();
+ },
+ });
+ });
+ }
+ }
+
+ animarFalha() {
+ this.pegmanSprite.play("pegman_fall");
+ }
+
+ async virarEsquerda() {
+ const novaDirecao = (this.direcaoJogador + 3) % 4;
+ await this.animarRotacao(novaDirecao);
+ this.historico.push({
+ action: "virarEsquerda",
+ direction: this.direcaoJogador,
+ });
+ return Promise.resolve();
+ }
+
+ async virarDireita() {
+ const novaDirecao = (this.direcaoJogador + 1) % 4;
+ await this.animarRotacao(novaDirecao);
+ this.historico.push({
+ action: "virarDireita",
+ direction: this.direcaoJogador,
+ });
+ return Promise.resolve();
+ }
+
+ async animarRotacao(novaDirecao) {
+ this.direcaoJogador = novaDirecao;
+ const nomesDirecoes = ["norte", "leste", "sul", "oeste"];
+ const novaDirecaoNome = nomesDirecoes[novaDirecao];
+
+ this.pegmanSprite.play(`pegman_idle_${novaDirecaoNome}`);
+ this.atualizarVisualJogador();
+
+ return Promise.resolve();
+ }
+
+ chegouNoAlvo() {
+ // Para uso do código do usuário: sair do loop se falhou
+ if (this.resultadoJogada === "falha") {
+ return true;
+ }
+
+ return this.verificarChegadaReal();
+ }
+
+ // Método auxiliar para verificação real (usado pelo validator)
+ verificarChegadaReal() {
+ return (
+ this.posicaoJogador.x === this.posicaoFinal.x &&
+ this.posicaoJogador.y === this.posicaoFinal.y
+ );
+ }
+
+ haCaminho(direcaoRelativa) {
+ let direcaoAbsoluta = this.direcaoJogador;
+ if (direcaoRelativa === "esquerda") {
+ direcaoAbsoluta = (this.direcaoJogador + 3) % 4;
+ } else if (direcaoRelativa === "direita") {
+ direcaoAbsoluta = (this.direcaoJogador + 1) % 4;
+ }
+
+ let { x, y } = this.posicaoJogador;
+ if (direcaoAbsoluta === Direcao.NORTE) y--;
+ else if (direcaoAbsoluta === Direcao.LESTE) x++;
+ else if (direcaoAbsoluta === Direcao.SUL) y++;
+ else if (direcaoAbsoluta === Direcao.OESTE) x--;
+
+ const proximoTile =
+ this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;
+ return proximoTile !== TILE_TYPES.PAREDE && proximoTile !== -1;
+ }
+
+ highlightBlock() {}
+}
+
+describe("Automato - Integração de Lógica (Código -> Validação)", () => {
+ let scene;
+ let interpreter;
+
+ const runFlow = async (code, phaseId) => {
+ const configFase = gameConfig.fases.find((f) => f.id === phaseId);
+ scene = new TestAutomatoScene(configFase);
+ interpreter = new GameInterpreter({ stepDelay: 0, pauseExec: false });
+ const api = setupAutomatoAPI(scene, { animationSpeed: 0 });
+ await interpreter.executeCode(code, api);
+ return validateSolution(scene.historico, configFase, gameConfig, scene);
+ };
+
+ it("Fase 1: Deve aprovar solução correta (Primeiro Passo)", async () => {
+ const result = await runFlow(SOLUTIONS.fase1, 1);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 1: Deve reprovar solução incompleta", async () => {
+ const result = await runFlow(SOLUTIONS.fase1_fail, 1);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 2: Deve aprovar solução correta (Primeira Curva)", async () => {
+ const result = await runFlow(SOLUTIONS.fase2, 2);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 2: Deve reprovar direção errada", async () => {
+ const result = await runFlow(SOLUTIONS.fase2_fail, 2);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 3: Deve aprovar loop correto (Linha Reta)", async () => {
+ const result = await runFlow(SOLUTIONS.fase3, 3);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 3: Deve reprovar sem loop", async () => {
+ const result = await runFlow(SOLUTIONS.fase3_fail, 3);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 4: Deve aprovar escadaria correta", async () => {
+ const result = await runFlow(SOLUTIONS.fase4, 4);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 4: Deve reprovar movimento incompleto", async () => {
+ const result = await runFlow(SOLUTIONS.fase4_fail, 4);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 5: Deve aprovar torre correta", async () => {
+ const result = await runFlow(SOLUTIONS.fase5, 5);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 5: Deve reprovar sequência errada", async () => {
+ const result = await runFlow(SOLUTIONS.fase5_fail, 5);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 6: Deve aprovar caminho com condicionais", async () => {
+ const result = await runFlow(SOLUTIONS.fase6, 6);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 6: Deve reprovar caminho sem validação", async () => {
+ const result = await runFlow(SOLUTIONS.fase6_fail, 6);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 7: Deve aprovar labirinto ramificado", async () => {
+ const result = await runFlow(SOLUTIONS.fase7, 7);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 7: Deve reprovar prioridade errada", async () => {
+ const result = await runFlow(SOLUTIONS.fase7_fail, 7);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 8: Deve aprovar labirinto complexo", async () => {
+ const result = await runFlow(SOLUTIONS.fase8, 8);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 8: Deve reprovar lógica incompleta", async () => {
+ const result = await runFlow(SOLUTIONS.fase8_fail, 8);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 9: Deve aprovar labirinto desafiador", async () => {
+ const result = await runFlow(SOLUTIONS.fase9, 9);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 9: Deve reprovar lógica simplificada demais", async () => {
+ const result = await runFlow(SOLUTIONS.fase9_fail, 9);
+ expect(result.success).toBe(false);
+ });
+
+ it("Fase 10: Deve aprovar labirinto final", async () => {
+ const result = await runFlow(SOLUTIONS.fase10, 10);
+ expect(result.success).toBe(true);
+ });
+
+ it("Fase 10: Deve reprovar lógica incompleta no final", async () => {
+ const result = await runFlow(SOLUTIONS.fase10_fail, 10);
+ expect(result.success).toBe(false);
+ });
+
+ it("Deve detectar colisão com parede", async () => {
+ const badCode = `virarEsquerda(); moverParaFrente();`;
+ const result = await runFlow(badCode, 1);
+ expect(result.success).toBe(false);
+ expect(scene.resultadoJogada).toBe("falha");
+ });
+
+ it("Deve verificar chegouNoAlvo() corretamente", async () => {
+ const configFase = gameConfig.fases.find((f) => f.id === 1);
+ scene = new TestAutomatoScene(configFase);
+ expect(scene.chegouNoAlvo()).toBe(false);
+ scene.posicaoJogador = { ...scene.posicaoFinal };
+ expect(scene.chegouNoAlvo()).toBe(true);
+ });
+
+ it("Deve verificar haCaminho() corretamente", async () => {
+ const configFase = gameConfig.fases.find((f) => f.id === 1);
+ scene = new TestAutomatoScene(configFase);
+ expect(scene.haCaminho("frente")).toBe(true);
+ expect(scene.haCaminho("esquerda")).toBe(false);
+ });
+}, 60000);
diff --git a/app/src/atividades/programacao/automato/assets/marker.png b/app/src/atividades/programacao/automato/assets/marker.png
new file mode 100644
index 0000000..235f316
Binary files /dev/null and b/app/src/atividades/programacao/automato/assets/marker.png differ
diff --git a/app/src/atividades/programacao/automato/assets/pegman.png b/app/src/atividades/programacao/automato/assets/pegman.png
new file mode 100644
index 0000000..1774501
Binary files /dev/null and b/app/src/atividades/programacao/automato/assets/pegman.png differ
diff --git a/app/src/atividades/programacao/automato/assets/tiles_pegman.png b/app/src/atividades/programacao/automato/assets/tiles_pegman.png
new file mode 100644
index 0000000..521c694
Binary files /dev/null and b/app/src/atividades/programacao/automato/assets/tiles_pegman.png differ
diff --git a/app/src/atividades/programacao/automato/blocks/blocks.js b/app/src/atividades/programacao/automato/blocks/blocks.js
new file mode 100644
index 0000000..323603f
--- /dev/null
+++ b/app/src/atividades/programacao/automato/blocks/blocks.js
@@ -0,0 +1,406 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ *
+ * @module games.automato.blocks.blocks
+ */
+
+import * as Blockly from "blockly/core";
+import { javascriptGenerator } from "blockly/javascript";
+
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+/**
+ * Registra os blocos e geradores do Autômato no Blockly.
+ * Chamado durante inicialização do editor para expor os blocos customizados.
+ * @returns {void}
+ */
+
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const blockDefinitions = {
+ moveForward: {
+ kind: "block",
+ type: "automato_move_forward",
+ },
+ turnLeft: {
+ kind: "block",
+ type: "automato_turn_left",
+ },
+ turnRight: {
+ kind: "block",
+ type: "automato_turn_right",
+ },
+ automato_if: {
+ kind: "block",
+ type: "automato_if",
+ },
+ automato_ifElse: {
+ kind: "block",
+ type: "automato_ifElse",
+ },
+ isPathAhead: {
+ kind: "block",
+ type: "automato_is_path_ahead",
+ },
+ isPathLeft: {
+ kind: "block",
+ type: "automato_is_path_left",
+ },
+ isPathRight: {
+ kind: "block",
+ type: "automato_is_path_right",
+ },
+ automato_repeat_until_goal: {
+ kind: "block",
+ type: "automato_repeat_until_goal",
+ },
+ };
+
+ const toolboxContents = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: "#4CAF50",
+ contents: [],
+ cssConfig: {
+ container: "movimento",
+ },
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: "#FF9800",
+ contents: [],
+ cssConfig: {
+ container: "repeticao",
+ },
+ },
+ {
+ kind: "category",
+ name: "Lógica",
+ colour: "#2196F3",
+ contents: [],
+ cssConfig: {
+ container: "logica",
+ },
+ },
+ {
+ kind: "category",
+ name: "Sensores",
+ colour: "#9C27B0",
+ contents: [],
+ cssConfig: {
+ container: "sensores",
+ },
+ },
+ ],
+ };
+
+ allowedBlocks.forEach((blockId) => {
+ const blockDef = blockDefinitions[blockId];
+ if (!blockDef) return;
+
+ if (["moveForward", "turnLeft", "turnRight"].includes(blockId)) {
+ toolboxContents.contents[0].contents.push(blockDef);
+ } else if (["automato_repeat_until_goal"].includes(blockId)) {
+ toolboxContents.contents[1].contents.push(blockDef);
+ } else if (["automato_if", "automato_ifElse"].includes(blockId)) {
+ toolboxContents.contents[2].contents.push(blockDef);
+ } else if (["isPathAhead", "isPathLeft", "isPathRight"].includes(blockId)) {
+ toolboxContents.contents[3].contents.push(blockDef);
+ }
+ });
+
+ toolboxContents.contents = toolboxContents.contents.filter(
+ (category) => category.contents && category.contents.length > 0,
+ );
+
+ return toolboxContents;
+};
+
+/**
+ * Gera a toolbox dinâmica contendo apenas os blocos permitidos para a fase.
+ * @param {Array} [allowedBlocks=[]] - Identificadores de blocos habilitados
+ * @returns {Object} Estrutura de toolbox compatível com Blockly
+ */
+
+const defineBlocks = () => {
+ // Bloco: Mover Frente
+ Blockly.Blocks["automato_move_forward"] = {
+ init: function () {
+ this.appendDummyInput().appendField("mover a frente");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#4CAF50");
+ this.setTooltip("Move o autômato uma posição para frente");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Virar à Esquerda
+ Blockly.Blocks["automato_turn_left"] = {
+ init: function () {
+ this.appendDummyInput().appendField("↺ virar à esquerda");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#9C27B0");
+ this.setTooltip("Vira o autômato 90° para a esquerda");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Virar à Direita
+ Blockly.Blocks["automato_turn_right"] = {
+ init: function () {
+ this.appendDummyInput().appendField("↻ virar à direita");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#9C27B0");
+ this.setTooltip("Vira o autômato 90° para a direita");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Se (condicional simples)
+ Blockly.Blocks["automato_if"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("se")
+ .appendField(
+ new Blockly.FieldDropdown([
+ ["há caminho à frente", "isPathAhead"],
+ ["há caminho à esquerda", "isPathLeft"],
+ ["há caminho à direita", "isPathRight"],
+ ]),
+ "DIR",
+ );
+ this.appendStatementInput("DO").setCheck(null).appendField("faça");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#2196F3");
+ this.setTooltip("Execute comandos se a condição for verdadeira");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Se/Senão (condicional com else)
+ Blockly.Blocks["automato_ifElse"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("se")
+ .appendField(
+ new Blockly.FieldDropdown([
+ ["há caminho à frente", "isPathAhead"],
+ ["há caminho à esquerda", "isPathLeft"],
+ ["há caminho à direita", "isPathRight"],
+ ]),
+ "DIR",
+ );
+ this.appendStatementInput("DO").setCheck(null).appendField("faça");
+ this.appendStatementInput("ELSE").setCheck(null).appendField("senão");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#2196F3");
+ this.setTooltip("Execute comandos diferentes dependendo da condição");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Verificar se há caminho à frente
+ Blockly.Blocks["automato_is_path_ahead"] = {
+ init: function () {
+ this.appendDummyInput().appendField("👁️ há caminho à frente?");
+ this.setOutput(true, "Boolean");
+ this.setColour("#2196F3");
+ this.setTooltip("Verifica se há um caminho livre à frente do autômato");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Verificar se há caminho à esquerda
+ Blockly.Blocks["automato_is_path_left"] = {
+ init: function () {
+ this.appendDummyInput().appendField("há caminho à esquerda?");
+ this.setOutput(true, "Boolean");
+ this.setColour("#2196F3");
+ this.setTooltip("Verifica se há um caminho livre à esquerda do autômato");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Verificar se há caminho à direita
+ Blockly.Blocks["automato_is_path_right"] = {
+ init: function () {
+ this.appendDummyInput().appendField("há caminho à direita?");
+ this.setOutput(true, "Boolean");
+ this.setColour("#2196F3");
+ this.setTooltip("Verifica se há um caminho livre à direita do autômato");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Repita até o objetivo
+ Blockly.Blocks["automato_repeat_until_goal"] = {
+ init: function () {
+ this.appendDummyInput().appendField("repita até o objetivo");
+ this.appendStatementInput("DO").setCheck(null).appendField("fazer");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#FF9800");
+ this.setTooltip("Repete as ações até o objetivo ser alcançado");
+ this.setHelpUrl("");
+ },
+ };
+};
+
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+
+ // Gerador: Mover Frente
+ javascriptGenerator.forBlock["automato_move_forward"] = function () {
+ return "moverParaFrente();\n";
+ };
+
+ // Gerador: Virar à Esquerda
+ javascriptGenerator.forBlock["automato_turn_left"] = function () {
+ return "virarEsquerda();\n";
+ };
+
+ // Gerador: Virar à Direita
+ javascriptGenerator.forBlock["automato_turn_right"] = function () {
+ return "virarDireita();\n";
+ };
+
+ // Gerador: Se (condicional simples)
+ javascriptGenerator.forBlock["automato_if"] = function (block) {
+ // Pega o valor do dropdown: 'isPathAhead', 'isPathLeft', ou 'isPathRight'
+ const direcaoDropdown = block.getFieldValue("DIR");
+
+ // Mapeia o valor do dropdown para o argumento da nossa função 'haCaminho'
+ const mapaDeDirecao = {
+ isPathAhead: '"frente"',
+ isPathLeft: '"esquerda"',
+ isPathRight: '"direita"',
+ };
+ const argumentoFuncao = mapaDeDirecao[direcaoDropdown];
+
+ const statements = javascriptGenerator.statementToCode(block, "DO");
+ // Gera o código correto: if (haCaminho("frente")) { ... }
+ return `if (haCaminho(${argumentoFuncao})) {\n${statements}}\n`;
+ };
+
+ // Gerador: Se/Senão (condicional com else)
+ javascriptGenerator.forBlock["automato_ifElse"] = function (block) {
+ const direcaoDropdown = block.getFieldValue("DIR");
+
+ const mapaDeDirecao = {
+ isPathAhead: '"frente"',
+ isPathLeft: '"esquerda"',
+ isPathRight: '"direita"',
+ };
+ const argumentoFuncao = mapaDeDirecao[direcaoDropdown];
+
+ const statementsIf = javascriptGenerator.statementToCode(block, "DO");
+ const statementsElse = javascriptGenerator.statementToCode(block, "ELSE");
+ // Gera o código correto: if (haCaminho("frente")) { ... } else { ... }
+ return `if (haCaminho(${argumentoFuncao})) {\n${statementsIf}} else {\n${statementsElse}}\n`;
+ };
+
+ // Gerador: Verificar se há caminho à frente / esquerda / direita
+ javascriptGenerator.forBlock["automato_is_path_ahead"] = () => [
+ 'haCaminho("frente")',
+ javascriptGenerator.ORDER_FUNCTION_CALL,
+ ];
+ javascriptGenerator.forBlock["automato_is_path_left"] = () => [
+ 'haCaminho("esquerda")',
+ javascriptGenerator.ORDER_FUNCTION_CALL,
+ ];
+ javascriptGenerator.forBlock["automato_is_path_right"] = () => [
+ 'haCaminho("direita")',
+ javascriptGenerator.ORDER_FUNCTION_CALL,
+ ];
+
+ // Gerador: Repita até o objetivo
+ javascriptGenerator.forBlock["automato_repeat_until_goal"] = function (
+ block,
+ ) {
+ const statements = javascriptGenerator.statementToCode(block, "DO");
+ return `while (!chegouNoAlvo()) {\n${statements}}\n`;
+ };
+};
+
+// Configuração da toolbox padrão (todos os blocos disponíveis)
+export const automatoToolbox = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: "#4CAF50",
+ contents: [
+ {
+ kind: "block",
+ type: "automato_move_forward",
+ },
+ {
+ kind: "block",
+ type: "automato_turn_left",
+ },
+ {
+ kind: "block",
+ type: "automato_turn_right",
+ },
+ ],
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: "#FF9800",
+ contents: [
+ {
+ kind: "block",
+ type: "automato_repeat_until_goal",
+ },
+ ],
+ },
+ {
+ kind: "category",
+ name: "Lógica",
+ colour: "#2196F3",
+ contents: [
+ {
+ kind: "block",
+ type: "automato_if",
+ },
+ {
+ kind: "block",
+ type: "automato_ifElse",
+ },
+ ],
+ },
+ {
+ kind: "category",
+ name: "Sensores",
+ colour: "#9C27B0",
+ contents: [
+ {
+ kind: "block",
+ type: "automato_is_path_ahead",
+ },
+ {
+ kind: "block",
+ type: "automato_is_path_left",
+ },
+ {
+ kind: "block",
+ type: "automato_is_path_right",
+ },
+ ],
+ },
+ ],
+};
diff --git a/app/src/atividades/programacao/automato/config/config.js b/app/src/atividades/programacao/automato/config/config.js
new file mode 100644
index 0000000..bd1348f
--- /dev/null
+++ b/app/src/atividades/programacao/automato/config/config.js
@@ -0,0 +1,293 @@
+/**
+ * @fileoverview Utility module for config.js
+ *
+ * @module games.automato.config.config
+ */
+
+export const gameConfig = {
+ gameId: "automato",
+ gameName: "Autômato",
+ type: "blocks",
+ icon: "🤖",
+ thumbnail: "/images/atividades/programacao/automato-thumbnail.png",
+ descricao: "Aprenda programação navegando por labirintos com blocos Blockly",
+ categoria: "Lógica",
+ tempoEstimado: "15-30 min",
+ dificuldade: "Iniciante",
+ conceitos: [
+ "Sequências",
+ "Loops/Repetição",
+ "Condicionais",
+ "Estruturas de controle",
+ ],
+ route: "/atividades/programacao/automato",
+ component: "AutomatoGame",
+ obetivos: [
+ "Entender sequências de comandos",
+ "Usar loops para otimizar código",
+ "Aplicar condicionais para tomada de decisão",
+ "Resolver problemas de navegação",
+ ],
+ metadata: {
+ ultimaAtualizacao: "2026-08-01",
+ versao: "1.1.0",
+ },
+ mensagens: {
+ naoChegou: "Você não chegou ao objetivo! Verifique seu caminho.",
+ bateuNaParede:
+ "O Pegman bateu na parede! Verifique os comandos de movimento.",
+ erroGeral: "Algo deu errado durante a execução. Verifique seu código.",
+ sucessoGenerico: "Parabéns! Você completou o desafio!",
+ timeoutExcedido:
+ "O tempo de execução foi excedido. Verifique se não há loops infinitos.",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Primeiro Passo",
+ descricao: "Aprenda a mover para frente",
+ maxBlocks: 2,
+ startPosition: { x: 2, y: 4 },
+ allowedBlocks: ["moveForward"],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 2, 1, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 2,
+ nome: "Primeira Curva",
+ descricao: "Aprenda a virar à direita e a esquerda",
+ maxBlocks: 5,
+ startPosition: { x: 2, y: 4 },
+ allowedBlocks: ["moveForward", "turnLeft", "turnRight"],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 1, 3, 0, 0, 0],
+ [0, 0, 2, 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 3,
+ nome: "Linha Reta",
+ descricao: "Use repetição para economizar blocos",
+ maxBlocks: 2,
+ startPosition: { x: 1, y: 4 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 2, 1, 1, 1, 1, 3, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 4,
+ nome: "Escadaria",
+ descricao: "Navegue pela escadaria diagonal",
+ maxBlocks: 5,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 1],
+ [0, 0, 0, 0, 0, 0, 1, 1],
+ [0, 0, 0, 0, 0, 3, 1, 0],
+ [0, 0, 0, 0, 1, 1, 0, 0],
+ [0, 0, 0, 1, 1, 0, 0, 0],
+ [0, 0, 1, 1, 0, 0, 0, 0],
+ [0, 2, 1, 0, 0, 0, 0, 0],
+ [1, 1, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 5,
+ nome: "Torre",
+ descricao: "Suba a torre",
+ maxBlocks: 5,
+ startPosition: { x: 3, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 2, 1, 1, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 6,
+ nome: "Caminho em Bloco",
+ descricao: "Use condicionais - verifique se há caminho à frente",
+ maxBlocks: 5,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 1, 0, 0],
+ [0, 1, 0, 0, 0, 1, 0, 0],
+ [0, 1, 1, 3, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 2, 1, 1, 1, 1, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 7,
+ nome: "Labirinto Ramificado",
+ descricao:
+ "Navegue por caminhos que se ramificam - use condicionais gerais",
+ maxBlocks: 10,
+ startPosition: { x: 1, y: 2 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 1, 1, 0],
+ [0, 2, 1, 1, 1, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 1, 0],
+ [0, 1, 1, 3, 0, 1, 0, 0],
+ [0, 1, 0, 1, 0, 1, 0, 0],
+ [0, 1, 1, 1, 1, 1, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 8,
+ nome: "Caminho Complexo",
+ descricao: "Um labirinto mais desafiador",
+ maxBlocks: 7,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 0, 0, 0],
+ [0, 1, 0, 0, 1, 1, 0, 0],
+ [0, 1, 1, 1, 0, 1, 0, 0],
+ [0, 0, 0, 1, 0, 1, 0, 0],
+ [0, 2, 1, 1, 0, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 9,
+ nome: "Labirinto Avançado",
+ descricao: "Use todas suas habilidades - agora com condicionais if/else",
+ maxBlocks: 10,
+ startPosition: { x: 5, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ "automato_ifElse",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 1, 0, 0],
+ [0, 0, 1, 0, 0, 0, 0, 0],
+ [3, 1, 1, 1, 1, 1, 1, 0],
+ [0, 1, 0, 1, 0, 1, 1, 0],
+ [1, 1, 1, 1, 1, 0, 1, 0],
+ [0, 1, 0, 1, 0, 2, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 10,
+ nome: "Desafio Final",
+ descricao: "O último desafio - use tudo que aprendeu!",
+ maxBlocks: 10,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ "automato_ifElse",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 0, 3, 0, 1, 0],
+ [0, 1, 1, 0, 1, 1, 1, 0],
+ [0, 1, 0, 1, 0, 1, 0, 0],
+ [0, 1, 1, 1, 1, 1, 1, 0],
+ [0, 0, 0, 1, 0, 0, 1, 0],
+ [0, 2, 1, 1, 1, 0, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ ],
+
+ SquareType: {
+ WALL: 0,
+ OPEN: 1,
+ START: 2,
+ FINISH: 3,
+ },
+
+ DirectionType: {
+ NORTH: 0,
+ EAST: 1,
+ SOUTH: 2,
+ WEST: 3,
+ },
+
+ BlockColors: {
+ MOVEMENT: 290, // Roxo para movimento
+ LOOPS: 120, // Verde para loops
+ LOGIC: 210, // Azul para lógica
+ },
+};
diff --git a/app/src/atividades/programacao/automato/config/debugSolutions.js b/app/src/atividades/programacao/automato/config/debugSolutions.js
new file mode 100644
index 0000000..47b2d34
--- /dev/null
+++ b/app/src/atividades/programacao/automato/config/debugSolutions.js
@@ -0,0 +1,400 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.automato.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_move_forward",
+ id: "`P+9hPVc7g2DJ0q16RK,",
+ x: 13,
+ y: 13,
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "zZKB=Au92}qd~WsjdKY5",
+ },
+ },
+ },
+ ],
+ },
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_move_forward",
+ id: "%tpiI~Q)QvKV*j:Utvj#",
+ x: 36,
+ y: 31,
+ next: {
+ block: {
+ type: "automato_turn_left",
+ id: "i9kNT4,rQx%kC*BG+,k^",
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "Wf}J1L`vrB#0),u6I^pM",
+ next: {
+ block: {
+ type: "automato_turn_right",
+ id: "4;eo@)^654.383-EkN9-",
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "neew[~/eKgbzlC[+oi*Q",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "`O0p7RA@Cv99swNQ+M1p",
+ x: 13,
+ y: 13,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "HThd+E5B?r2KAGp54_?l",
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "_GOa9b!@f(I=^l_[!pg3",
+ x: 38,
+ y: 88,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "QUX4{ospcnon~=%Xqpx#",
+ next: {
+ block: {
+ type: "automato_turn_left",
+ id: "nY-ZmPn~1(z6F~iG$b}o",
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "D}G*#p933.aY//p?%P[*",
+ next: {
+ block: {
+ type: "automato_turn_right",
+ id: "!J{Ri5A0hu^R4IBhv~J7",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_move_forward",
+ id: "={d8;tDvVSR;Qsk3)Rj3",
+ x: 38,
+ y: 38,
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "Qud3z!0448y{D@I=%Xk:",
+ next: {
+ block: {
+ type: "automato_turn_left",
+ id: "Z8XmhaM{H#*8.ZXP0*jl",
+ next: {
+ block: {
+ type: "automato_repeat_until_goal",
+ id: "1f6%~iCco}DyQ:cgH86D",
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "iqtmU-HoFmKi#54Y8C;$",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 6: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "N]+p?1(9_O^MX}b*hf[m",
+ x: 34,
+ y: 134,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "F(RCqq2^5`s$pF`=j6dT",
+ next: {
+ block: {
+ type: "automato_if",
+ id: "5cJ_Yx7;cFI,n_jlW02P",
+ fields: { DIR: "isPathLeft" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_left",
+ id: "WMI/e:8FL;UnhP[01~14",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 7: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "lk.$gb7DEMwgOW(D]o?C",
+ x: 38,
+ y: 88,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "FMhkj:m]}N0SfeHc.L@y",
+ next: {
+ block: {
+ type: "automato_if",
+ id: "DNgAG1XXR/8Vv%t}~qHR",
+ fields: { DIR: "isPathRight" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_right",
+ id: "fY[5U1;Twpd083i|5rO4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 8: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "hcd5qweto~5BP@T@Fcwe",
+ x: 38,
+ y: 13,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "stEZ$sLmPJuhn@ZxY::5",
+ next: {
+ block: {
+ type: "automato_if",
+ id: "#6mJ52gC8FHKA_4k3|=`",
+ fields: { DIR: "isPathLeft" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_left",
+ id: "p@R/O;YFznVYn13eIEtr",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "automato_if",
+ id: "MjcW?7[@Onp;daUW[`yn",
+ fields: { DIR: "isPathRight" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_right",
+ id: ":}$)EO]z4?l@,yNQ4oVk",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 9: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: "y,XAdDT6I89o$A3bkpR;",
+ x: 38,
+ y: 13,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_ifElse",
+ id: "Q0:H}+Vw{.*o8s9HvgTH",
+ fields: { DIR: "isPathAhead" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_move_forward",
+ id: "Zaf:rnw2j?vjQk-#.Ug;",
+ },
+ },
+ ELSE: {
+ block: {
+ type: "automato_turn_left",
+ id: "AJ35Foz:[iTEW!r(8L;-",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 10: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "automato_repeat_until_goal",
+ id: ".3)q2,d1Z/Z5C_cC`^_x",
+ x: 38,
+ y: 38,
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_if",
+ id: "D/2h}S=UJC],YD8/?F|a",
+ fields: { DIR: "isPathLeft" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_left",
+ id: "3n-#$Jz_^UC7K5PGmJX}",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "(Zd%Q5ar+q9hWy74pqot",
+ next: {
+ block: {
+ type: "automato_if",
+ id: "oB(:A7l(9}labEfl67$G",
+ fields: { DIR: "isPathLeft" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_left",
+ id: "FKsY*NZS]v0_BEP3O?T}",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "U:Ya=3QLz^VRddRJwCH4",
+ next: {
+ block: {
+ type: "automato_if",
+ id: "?;`/thN5QX84f-3Urf%%",
+ fields: { DIR: "isPathRight" },
+ inputs: {
+ DO: {
+ block: {
+ type: "automato_turn_right",
+ id: "PFs,A8RxNI+F^i-vv!bg",
+ next: {
+ block: {
+ type: "automato_move_forward",
+ id: "D/v2nvg%+kbVG`|YFo[.",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+};
diff --git a/app/src/atividades/programacao/automato/config/tourSteps.js b/app/src/atividades/programacao/automato/config/tourSteps.js
new file mode 100644
index 0000000..77e4008
--- /dev/null
+++ b/app/src/atividades/programacao/automato/config/tourSteps.js
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.automato.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const automatoTourSteps = [
+ createWelcomeStep({
+ gameName: "Jogo do Autômato",
+ description:
+ "Neste jogo, você vai programar um robô para navegar em um labirinto e alcançar o objetivo marcado.",
+ challenge: "Use blocos de programação para guiar o Pegman até a bandeira!",
+ iconSvg: gameIcons.robot,
+ }),
+
+ createGameAreaStep({
+ title: "Área do Labirinto",
+ description:
+ "Aqui você vê o Pegman e o labirinto. Seu objetivo é programar o Pegman para chegar até a bandeira.",
+ }),
+
+ createToolboxStep({
+ description:
+ "Arraste os blocos de movimentação disponíveis para a área de programação.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte sua sequência de comandos. Conecte os blocos na ordem que o Pegman deve executar.",
+ }),
+
+ createRunButtonStep({
+ description: "Clique aqui para ver o Pegman executar seus comandos.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Se o Pegman não chegar ao objetivo, use o botão de reset para tentar outra solução.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "O jogo tem várias fases com diferentes labirintos e níveis de complexidade.",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Aqui você vê o número da fase atual e pode acompanhar seu progresso.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Clique no botão de ajuda para rever este tour a qualquer momento.",
+ }),
+];
+
+export const automatoTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/automato/game.js b/app/src/atividades/programacao/automato/game.js
new file mode 100644
index 0000000..c36c64e
--- /dev/null
+++ b/app/src/atividades/programacao/automato/game.js
@@ -0,0 +1,654 @@
+/**
+ * @fileoverview Utility module for game.js
+ *
+ * @module games.automato.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { gameEventBus } from "../../../utils/gameEvents";
+import { GameInterpreter } from "../../../interpreters/GameInterpreter.js";
+import { setupAutomatoAPI } from "./hooks/interpreterSetup.js";
+import { validateSolution } from "./validation/validators.js";
+
+import tiles from "./assets/tiles_pegman.png";
+import pegman from "./assets/pegman.png";
+import marker from "./assets/marker.png";
+
+const ASSETS = {
+ IMG: {
+ TILES: "tiles",
+ PEGMAN: "pegman",
+ MARKER: "marker",
+ },
+};
+
+const CONSTANTES = {
+ TAMANHO_TILE: 50,
+ PEGMAN_HEIGHT: 51,
+ PEGMAN_WIDTH: 49,
+ VELOCIDADE_ANIMACAO: 200,
+};
+
+const Direcao = {
+ NORTE: 0,
+ LESTE: 1,
+ SUL: 2,
+ OESTE: 3,
+};
+
+const TILE_TYPES = {
+ PAREDE: 0,
+ CAMINHO: 1,
+ INICIO: 2,
+ FIM: 3,
+};
+
+class AutomatoScene extends BaseGameScene {
+ constructor() {
+ super("AutomatoScene");
+
+ // Estado específico do Automato
+ this.mapa = null;
+ this.posicaoInicial = null;
+ this.posicaoFinal = null;
+ this.posicaoJogador = null;
+ this.direcaoJogador = Direcao.LESTE;
+ this.pegmanSprite = null;
+ this.gradeVisual = null;
+ this.resultadoJogada = "em_andamento";
+ }
+
+ preload() {
+ this.preloadGlobalAssets();
+ this.load.image(ASSETS.IMG.MARKER, marker);
+ this.load.spritesheet(ASSETS.IMG.TILES, tiles, {
+ frameWidth: CONSTANTES.TAMANHO_TILE,
+ frameHeight: CONSTANTES.TAMANHO_TILE,
+ });
+ this.load.spritesheet(ASSETS.IMG.PEGMAN, pegman, {
+ frameHeight: CONSTANTES.PEGMAN_HEIGHT,
+ frameWidth: CONSTANTES.PEGMAN_WIDTH,
+ });
+ }
+
+ /**
+ * Preload dos assets do autômato (tiles, spritesheets e sons se houver).
+ * @returns {void}
+ */
+
+ init(data) {
+ super.init(data);
+
+ this.mapa = this.configFase?.mapa || [];
+ this.direcaoJogador = Direcao.LESTE;
+ this.resultadoJogada = "em_andamento";
+ }
+
+ /**
+ * Inicialização específica do AutomatoScene.
+ * Recebe `data` e configura estado derivado da `configFase`.
+ * @param {Object} data - Dados opcionais da cena
+ * @returns {void}
+ */
+
+ onBeforeRun() {
+ this.posicaoJogador = { ...this.posicaoInicial };
+ this.direcaoJogador = Direcao.LESTE;
+ this.resultadoJogada = "em_andamento";
+ this.atualizarVisualJogador();
+ }
+
+ /**
+ * Hook chamado imediatamente antes da execução do código do aluno.
+ * Prepara posição inicial e estado de animação.
+ * @returns {void}
+ */
+
+ onReset() {
+ this.posicaoJogador = { ...this.posicaoInicial };
+ this.direcaoJogador = Direcao.LESTE;
+ this.resultadoJogada = "em_andamento";
+ this.atualizarVisualJogador();
+ }
+
+ /**
+ * Hook chamado quando o usuário reseta a cena manualmente.
+ * Restaura estado mas não altera configurações persistentes.
+ * @returns {void}
+ */
+
+ onSuccess() {
+ this.animarVitoria();
+ }
+
+ /**
+ * Handler chamado quando a validação indica sucesso.
+ * Deve executar animações de vitória e finalizar a execução.
+ * @returns {void}
+ */
+
+ onFailure() {
+ // animarFalha() já foi disparada em moverParaFrente() ao bater na parede;
+ // não repetir aqui para evitar dupla animação.
+ }
+
+ /**
+ * Handler chamado quando a validação indica falha.
+ * Deve executar animações de falha e limpar execução.
+ * @returns {void}
+ */
+
+ /**
+ * Atualiza a posição e animação visual do Pegman
+ * Sincroniza sprite com estado lógico (posição e direção)
+ */
+ atualizarVisualJogador() {
+ if (this.posicaoJogador) {
+ const posX =
+ this.posicaoJogador.x * CONSTANTES.TAMANHO_TILE +
+ CONSTANTES.TAMANHO_TILE / 2;
+ const posY =
+ this.posicaoJogador.y * CONSTANTES.TAMANHO_TILE +
+ CONSTANTES.TAMANHO_TILE / 2 -
+ 6;
+ this.pegmanSprite.setPosition(posX, posY);
+
+ const animacoesDirecao = [
+ "pegman_idle_norte",
+ "pegman_idle_leste",
+ "pegman_idle_sul",
+ "pegman_idle_oeste",
+ ];
+ this.pegmanSprite.play(animacoesDirecao[this.direcaoJogador]);
+ }
+ }
+
+ /**
+ * Move o Pegman para frente na direção atual
+ * Verifica colisão com paredes e atualiza animação
+ * Se colidir, marca como falha e para a execução
+ *
+ * @returns {Promise} Promise que resolve quando movimento completa
+ */
+ moverParaFrente() {
+ this.historico.push({
+ tipo: "move",
+ direcao: this.direcaoJogador,
+ posicao: { ...this.posicaoJogador },
+ });
+
+ if (this.resultadoJogada !== "em_andamento") return Promise.resolve();
+
+ let { x, y } = this.posicaoJogador;
+
+ if (this.direcaoJogador === Direcao.NORTE) y--;
+ else if (this.direcaoJogador === Direcao.LESTE) x++;
+ else if (this.direcaoJogador === Direcao.SUL) y++;
+ else if (this.direcaoJogador === Direcao.OESTE) x--;
+
+ const proximoTile =
+ this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;
+
+ if (proximoTile === TILE_TYPES.PAREDE || proximoTile === -1) {
+ this.resultadoJogada = "falha";
+ this.animarFalha();
+
+ if (this.gameInterpreter) {
+ this.gameInterpreter.stopInternal();
+ }
+
+ return Promise.resolve();
+ } else {
+ return new Promise((resolve) => {
+ const novaX = x * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2;
+ const novaY =
+ y * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 - 6;
+
+ this.tweens.add({
+ targets: this.pegmanSprite,
+ x: novaX,
+ y: novaY,
+ duration: CONSTANTES.VELOCIDADE_ANIMACAO / 2,
+ ease: "Power2",
+ onComplete: () => {
+ this.posicaoJogador = { x, y };
+ this.atualizarVisualJogador();
+ resolve();
+ },
+ });
+ });
+ }
+ }
+
+ /**
+ * Anima a falha do Pegman (batida na parede)
+ * Faz bounce back e depois animação de queda
+ */
+ animarFalha() {
+ const deltaX =
+ this.direcaoJogador === Direcao.LESTE
+ ? 1
+ : this.direcaoJogador === Direcao.OESTE
+ ? -1
+ : 0;
+ const deltaY =
+ this.direcaoJogador === Direcao.NORTE
+ ? -1
+ : this.direcaoJogador === Direcao.SUL
+ ? 1
+ : 0;
+
+ const bounceX =
+ this.pegmanSprite.x + (deltaX * CONSTANTES.TAMANHO_TILE) / 4;
+ const bounceY =
+ this.pegmanSprite.y + (deltaY * CONSTANTES.TAMANHO_TILE) / 4;
+
+ this.tweens.add({
+ targets: this.pegmanSprite,
+ x: bounceX,
+ y: bounceY,
+ duration: CONSTANTES.VELOCIDADE_ANIMACAO / 3,
+ yoyo: true,
+ repeat: 1,
+ ease: "Power2",
+ onComplete: () => {
+ this.pegmanSprite.play("pegman_fall");
+ this.resultadoJogada = "falha";
+ },
+ });
+ }
+
+ /**
+ * Vira o Pegman 90° para a esquerda
+ * Anima a rotação do sprite
+ *
+ * @returns {Promise} Promise que resolve quando rotação completa
+ */
+ virarEsquerda() {
+ this.historico.push({
+ tipo: "turnLeft",
+ de: this.direcaoJogador,
+ para: (this.direcaoJogador + 3) % 4,
+ });
+
+ const novaDirecao = (this.direcaoJogador + 3) % 4;
+ return this.animarRotacao(novaDirecao);
+ }
+
+ /**
+ * Vira o Pegman 90° para a direita
+ * Anima a rotação do sprite
+ *
+ * @returns {Promise} Promise que resolve quando rotação completa
+ */
+ virarDireita() {
+ this.historico.push({
+ tipo: "turnRight",
+ de: this.direcaoJogador,
+ para: (this.direcaoJogador + 1) % 4,
+ });
+
+ const novaDirecao = (this.direcaoJogador + 1) % 4;
+ return this.animarRotacao(novaDirecao);
+ }
+
+ /**
+ * Anima a rotação do Pegman de uma direção para outra
+ * Usa animações pré-definidas ou atualiza visual diretamente
+ *
+ * @param {number} novaDirecao - Nova direção (0=Norte, 1=Leste, 2=Sul, 3=Oeste)
+ * @returns {Promise} Promise que resolve quando animação completa
+ */
+ animarRotacao(novaDirecao) {
+ return new Promise((resolve) => {
+ const direcaoAtual = this.direcaoJogador;
+ const nomesDirecoes = ["norte", "leste", "sul", "oeste"];
+ const direcaoAtualNome = nomesDirecoes[direcaoAtual];
+ const novaDirecaoNome = nomesDirecoes[novaDirecao];
+
+ this.direcaoJogador = novaDirecao;
+
+ const chaveAnimacao = `${direcaoAtualNome}_para_${novaDirecaoNome}`;
+
+ if (this.anims.exists(chaveAnimacao)) {
+ this.pegmanSprite.play(chaveAnimacao);
+
+ const onRotationComplete = () => {
+ this.atualizarVisualJogador();
+ this.pegmanSprite.off("animationcomplete", onRotationComplete);
+ resolve();
+ };
+
+ this.pegmanSprite.on("animationcomplete", onRotationComplete);
+ } else {
+ this.atualizarVisualJogador();
+ resolve();
+ }
+ });
+ }
+
+ /**
+ * Verifica se o Pegman chegou ao objetivo (tile tipo FIM)
+ *
+ * @returns {boolean} true se chegou no alvo, false caso contrário
+ */
+ chegouNoAlvo() {
+ return (
+ this.posicaoJogador.x === this.posicaoFinal.x &&
+ this.posicaoJogador.y === this.posicaoFinal.y
+ );
+ }
+
+ /**
+ * Verifica se há caminho livre em uma direção relativa
+ *
+ * @param {string} direcaoRelativa - 'frente', 'esquerda' ou 'direita'
+ * @returns {boolean} true se há caminho, false se é parede ou fora do mapa
+ */
+ haCaminho(direcaoRelativa) {
+ let direcaoAbsoluta = this.direcaoJogador;
+ if (direcaoRelativa === "esquerda") {
+ direcaoAbsoluta = (this.direcaoJogador + 3) % 4;
+ } else if (direcaoRelativa === "direita") {
+ direcaoAbsoluta = (this.direcaoJogador + 1) % 4;
+ }
+
+ let { x, y } = this.posicaoJogador;
+ if (direcaoAbsoluta === Direcao.NORTE) y--;
+ else if (direcaoAbsoluta === Direcao.LESTE) x++;
+ else if (direcaoAbsoluta === Direcao.SUL) y++;
+ else if (direcaoAbsoluta === Direcao.OESTE) x--;
+
+ const proximoTile =
+ this.mapa[y] && this.mapa[y][x] !== undefined ? this.mapa[y][x] : -1;
+ return proximoTile !== TILE_TYPES.PAREDE && proximoTile !== -1;
+ }
+
+ /**
+ * Anima a vitória do Pegman
+ * Sequência de frames de comemoração
+ */
+ animarVitoria() {
+ const stepSpeed = 150;
+ this.pegmanSprite.setFrame(16);
+ setTimeout(() => this.pegmanSprite.setFrame(18), stepSpeed);
+ setTimeout(() => this.pegmanSprite.setFrame(16), stepSpeed * 2);
+ setTimeout(() => {
+ const framesBase = [0, 4, 8, 12];
+ this.pegmanSprite.setFrame(framesBase[this.direcaoJogador]);
+ }, stepSpeed * 3);
+ }
+
+ /**
+ * Destaca um bloco no workspace do Blockly e sinaliza pausa visual.
+ * Usado pelo interpreter para indicar o bloco atualmente executado.
+ * @param {string} id - Id do bloco a ser destacado
+ * @returns {void}
+ */
+ highlightBlock(id) {
+ if (this.workspace) this.workspace.highlightBlock(id);
+ this.highlightPause = true;
+ }
+
+ /**
+ * Trata o resultado final da execução do código do aluno.
+ * Em caso de sucesso dispara evento de sucesso; caso contrário, falha.
+ * @param {string} result - Resultado retornado pelo interpretador
+ * @returns {void}
+ */
+ handleExecutionResult(result) {
+ if (result === "failure" || this.resultadoJogada === "falha") {
+ gameEventBus.gameFailure();
+ return;
+ }
+ if (this.chegouNoAlvo()) {
+ this.animarVitoria();
+ setTimeout(() => gameEventBus.gameSuccess(), 800);
+ } else {
+ gameEventBus.gameFailure();
+ }
+ }
+
+ createAnimations() {
+ this.anims.create({
+ key: "pegman_idle_norte",
+ frames: [{ key: "pegman", frame: 0 }],
+ frameRate: 1,
+ });
+ this.anims.create({
+ key: "pegman_idle_leste",
+ frames: [{ key: "pegman", frame: 4 }],
+ frameRate: 1,
+ });
+ this.anims.create({
+ key: "pegman_idle_sul",
+ frames: [{ key: "pegman", frame: 8 }],
+ frameRate: 1,
+ });
+ this.anims.create({
+ key: "pegman_idle_oeste",
+ frames: [{ key: "pegman", frame: 12 }],
+ frameRate: 1,
+ });
+ this.anims.create({
+ key: "pegman_fall",
+ frames: this.anims.generateFrameNumbers("pegman", { start: 18, end: 20 }),
+ frameRate: 10,
+ repeat: 0,
+ });
+ this.createRotationAnimations();
+ }
+
+ createRotationAnimations() {
+ this.anims.create({
+ key: "norte_para_leste",
+ frames: [0, 1, 2, 3, 4].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "norte_para_oeste",
+ frames: [0, 15, 14, 13, 12].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "oeste_para_sul",
+ frames: [12, 11, 10, 9, 8].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "oeste_para_norte",
+ frames: [12, 13, 14, 15, 0].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "leste_para_sul",
+ frames: [4, 5, 6, 7, 8].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "leste_para_norte",
+ frames: [4, 3, 2, 1, 0].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "sul_para_oeste",
+ frames: [8, 9, 10, 11, 12].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ this.anims.create({
+ key: "sul_para_leste",
+ frames: [8, 7, 6, 5, 4].map((frame) => ({ key: "pegman", frame })),
+ frameRate: 30,
+ });
+ }
+
+ createVisualGrid(TILE_SHAPES, normalize) {
+ this.gradeVisual = this.add.group();
+ for (let y = 0; y < this.mapa.length; y++) {
+ for (let x = 0; x < this.mapa[y].length; x++) {
+ let tileShape =
+ normalize(x, y) +
+ normalize(x, y - 1) +
+ normalize(x + 1, y) +
+ normalize(x, y + 1) +
+ normalize(x - 1, y);
+ if (!TILE_SHAPES[tileShape]) {
+ tileShape =
+ tileShape === "00000" && Math.random() > 0.3
+ ? "null0"
+ : "null" + Math.floor(1 + Math.random() * 4);
+ }
+ const [tileX, tileY] = TILE_SHAPES[tileShape];
+ const frameIndex = tileY * 5 + tileX;
+ const tileSprite = this.add.sprite(
+ x * CONSTANTES.TAMANHO_TILE,
+ y * CONSTANTES.TAMANHO_TILE,
+ "tiles",
+ frameIndex,
+ );
+ tileSprite.setDisplaySize(
+ CONSTANTES.TAMANHO_TILE,
+ CONSTANTES.TAMANHO_TILE,
+ );
+ tileSprite.setOrigin(0);
+ this.gradeVisual.add(tileSprite);
+ }
+ }
+ for (let y = 0; y < this.mapa.length; y++) {
+ for (let x = 0; x < this.mapa[y].length; x++) {
+ if (this.mapa[y][x] === TILE_TYPES.FIM) {
+ const markerImg = this.add.image(
+ x * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2,
+ y * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 - 10,
+ "marker",
+ );
+ markerImg.setDisplaySize(12, 20.04);
+ markerImg.setOrigin(0.5);
+ this.gradeVisual.add(markerImg);
+ }
+ }
+ }
+ }
+
+ createPegmanSprite() {
+ this.pegmanSprite = this.add.sprite(0, 0, "pegman", 4);
+ this.pegmanSprite.setDisplaySize(
+ CONSTANTES.TAMANHO_TILE * 0.8,
+ CONSTANTES.TAMANHO_TILE * 0.8,
+ );
+ this.pegmanSprite.setOrigin(0.5);
+ this.atualizarVisualJogador();
+ }
+
+ create() {
+ this.mapa = this.configFase?.mapa || [];
+ this.resultadoJogada = "em_andamento";
+
+ const TILE_SHAPES = {
+ 10010: [4, 0],
+ 10001: [3, 3],
+ 11000: [0, 1],
+ 10100: [0, 2],
+ 11010: [4, 1],
+ 10101: [3, 2],
+ 10110: [0, 0],
+ 10011: [2, 0],
+ 11001: [4, 2],
+ 11100: [2, 3],
+ 11110: [1, 1],
+ 10111: [1, 0],
+ 11011: [2, 1],
+ 11101: [1, 2],
+ 11111: [2, 2],
+ null0: [4, 3],
+ null1: [3, 0],
+ null2: [3, 1],
+ null3: [0, 3],
+ null4: [1, 3],
+ };
+
+ const normalize = (x, y) => {
+ if (x < 0 || x >= this.mapa[0].length || y < 0 || y >= this.mapa.length)
+ return "0";
+ return this.mapa[y][x] === TILE_TYPES.PAREDE ? "0" : "1";
+ };
+
+ this.createAnimations();
+
+ const encontrarPosicao = (tipo) => {
+ for (let y = 0; y < this.mapa.length; y++) {
+ for (let x = 0; x < this.mapa[y].length; x++) {
+ if (this.mapa[y][x] === tipo) return { x, y };
+ }
+ }
+ return null;
+ };
+
+ this.posicaoInicial = encontrarPosicao(TILE_TYPES.INICIO);
+ this.posicaoFinal = encontrarPosicao(TILE_TYPES.FIM);
+ this.posicaoJogador = { ...this.posicaoInicial };
+ this.direcaoJogador = Direcao.LESTE;
+
+ this.createVisualGrid(TILE_SHAPES, normalize);
+ this.createPegmanSprite();
+
+ this.gameInterpreter = new GameInterpreter({
+ stepDelay: 20,
+ pauseExec: true,
+ });
+
+ this.setupStandardController(
+ setupAutomatoAPI,
+ (history, config, gameConfig) =>
+ validateSolution(history, config, gameConfig, this),
+ );
+ }
+}
+
+export const createGame = (
+ parentElement,
+ configFaseAtual,
+ customFailureHandler = null,
+ idFaseAtual = null,
+ gameConfig = null,
+) => {
+ const config =
+ idFaseAtual && gameConfig
+ ? gameConfig.fases[idFaseAtual - 1]
+ : configFaseAtual;
+
+ return {
+ type: Phaser.AUTO,
+ width: config.mapa[0].length * CONSTANTES.TAMANHO_TILE,
+ height: config.mapa.length * CONSTANTES.TAMANHO_TILE,
+ scale: {
+ mode: Phaser.Scale.EXPAND,
+ autoCenter: Phaser.Scale.CENTER_BOTH,
+ },
+ backgroundColor: "#F1EEE7",
+ parent: parentElement,
+ scene: [AutomatoScene],
+ callbacks: {
+ preBoot: (game) => {
+ game.registry.merge({
+ configFase: config,
+ gameConfig: gameConfig,
+ customFailureHandler: customFailureHandler,
+ stepDelay: 20,
+ });
+ },
+ },
+ };
+};
+
+/**
+ * Factory que monta a configuração Phaser para o jogo Autômato.
+ * Calcula largura/altura a partir do mapa da fase e injeta callbacks.
+ * @param {HTMLElement} parentElement - Elemento DOM pai para o canvas
+ * @param {Object} configFaseAtual - Configuração da fase (fallback)
+ * @param {Function|null} customFailureHandler - Handler opcional de falha
+ * @param {number|null} idFaseAtual - Índice da fase atual (1-based)
+ * @param {Object|null} gameConfig - Configuração completa do jogo
+ * @returns {Object} Configuração para inicializar `Phaser.Game`
+ */
diff --git a/app/src/atividades/programacao/automato/hooks/interpreterSetup.js b/app/src/atividades/programacao/automato/hooks/interpreterSetup.js
new file mode 100644
index 0000000..14c50c1
--- /dev/null
+++ b/app/src/atividades/programacao/automato/hooks/interpreterSetup.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ *
+ * @module games.automato.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Configura a API do Automato para o js-interpreter
+ * @param {object} scene - Cena do jogo Phaser
+ * @param {object} config - Configurações de velocidade e animação
+ * @returns {function} - Função de setup para o interpreter
+ */
+export const setupAutomatoAPI = (scene, config = {}) => {
+ const animationDelay = config.animationSpeed;
+
+ return (interpreter, globalScope) => {
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "moverParaFrente",
+ ApiHelpers.createActionWrapper(scene, "moverParaFrente", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "virarEsquerda",
+ ApiHelpers.createActionWrapper(scene, "virarEsquerda", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "virarDireita",
+ ApiHelpers.createActionWrapper(scene, "virarDireita", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "chegouNoAlvo",
+ ApiHelpers.createConditionWrapper(scene, "chegouNoAlvo"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "haCaminho",
+ ApiHelpers.createConditionWrapper(scene, "haCaminho"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
+
+export const AUTOMATO_COMMANDS = {
+ ACTIONS: ["moverParaFrente", "virarEsquerda", "virarDireita"],
+ CONDITIONS: ["chegouNoAlvo", "haCaminho"],
+ SPECIAL: ["highlightBlock"],
+};
diff --git a/app/src/atividades/programacao/automato/hooks/useAutomatoTour.js b/app/src/atividades/programacao/automato/hooks/useAutomatoTour.js
new file mode 100644
index 0000000..07c62f5
--- /dev/null
+++ b/app/src/atividades/programacao/automato/hooks/useAutomatoTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useAutomatoTour.js
+ *
+ * @module games.automato.hooks.useAutomatoTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { automatoTourSteps, automatoTourOptions } from "../config/tourSteps";
+
+export const useAutomatoTour = () => {
+ /**
+ * Hook que inicializa e retorna o tour do Autômato.
+ * Fornece handlers para iniciar/pausar o tour da interface do jogo.
+ * @returns {Object} API do tour (start, cancel, next, etc.)
+ */
+ return useGameTour("automato", automatoTourSteps, automatoTourOptions);
+};
diff --git a/app/src/atividades/programacao/automato/validation/validators.js b/app/src/atividades/programacao/automato/validation/validators.js
new file mode 100644
index 0000000..15afb43
--- /dev/null
+++ b/app/src/atividades/programacao/automato/validation/validators.js
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.automato.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador do Automato Game.
+ * Valida se o Pegman chegou ao objetivo (tile marcado como 3) e aplica
+ * mensagens de feedback configuradas em `gameConfig`.
+ *
+ * @class AutomatoValidator
+ * @extends BaseGameValidator
+ */
+class AutomatoValidator extends BaseGameValidator {
+ /**
+ * Valida a solução do aluno
+ *
+ * @param {Array} history - Histórico de ações (para debug/estatísticas)
+ * @param {Object} config - Configuração da fase atual
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena Phaser
+ * @returns {Object} { success: boolean, reason?: string }
+ */
+ validatePhase(history, config, gameConfig, sceneRef) {
+ // Verificar se o jogo falhou (bateu na parede, etc)
+ if (sceneRef && sceneRef.resultadoJogada === "falha") {
+ return this.failure(
+ gameConfig?.mensagens?.falhouExecucao ||
+ "Você bateu em uma parede ou saiu do caminho!",
+ );
+ }
+
+ // Validar se chegou no objetivo
+ // A cena implementa chegouNoAlvo() que verifica se está no tile 3
+ if (sceneRef && sceneRef.chegouNoAlvo && sceneRef.chegouNoAlvo()) {
+ return this.success();
+ }
+
+ // Não chegou ao objetivo
+ return this.failure(
+ gameConfig?.mensagens?.naoChegou ||
+ "Você não chegou ao objetivo! Verifique seu caminho.",
+ );
+ }
+}
+
+// Singleton para reutilização
+const validatorInstance = new AutomatoValidator();
+
+/**
+ * Função exportada para validação de soluções do Automato Game
+ *
+ * @param {Array} history - Histórico de ações
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global
+ * @param {Object} sceneRef - Referência à cena (necessário para validação)
+ * @returns {Object} { success: boolean, reason?: string }
+ */
+export const validateSolution = (history, config, gameConfig, sceneRef) => {
+ return validatorInstance.validatePhase(history, config, gameConfig, sceneRef);
+};
diff --git a/app/src/atividades/programacao/bkp.7z b/app/src/atividades/programacao/bkp.7z
new file mode 100644
index 0000000..9186970
Binary files /dev/null and b/app/src/atividades/programacao/bkp.7z differ
diff --git a/app/src/atividades/programacao/cripto/CriptoGame.jsx b/app/src/atividades/programacao/cripto/CriptoGame.jsx
new file mode 100644
index 0000000..78349f0
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/CriptoGame.jsx
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview React component for CriptoGame.jsx
+ *
+ * @module games.cripto.CriptoGame
+ */
+
+
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
+import {
+ GameStateProvider,
+ useGameState,
+} from "../../../contexts/GameStateContext";
+import { starterBlocks } from "./config/starterBlocks";
+import { useCriptoTour } from "./hooks/useCriptoTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+function CriptoContent() {
+ const { setFailureMessage, isDebugMode } = useGameState();
+ useCriptoTour();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(() => {
+
+
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Componente interno que monta a cena e o editor do jogo Cripto.
+ * Registra blocos, configura toolbox dinâmico e injeta o `gameFactory`.
+ * @returns {JSX.Element} Conteúdo do jogo (editor + canvas)
+ */
+
+
+/**
+ * Componente de página que fornece o contexto de estado do jogo Cripto.
+ * Envolve `CriptoContent` com o `GameStateProvider` configurado.
+ * @returns {JSX.Element} Página completa do jogo Cripto
+ */
+export default function CriptoGame() {
+ return (
+
+
+
+ );
+}
+
+CriptoContent.propTypes = {};
+CriptoGame.propTypes = {};
diff --git a/app/src/atividades/programacao/cripto/assets/background_loop.mp3 b/app/src/atividades/programacao/cripto/assets/background_loop.mp3
new file mode 100644
index 0000000..99f0492
Binary files /dev/null and b/app/src/atividades/programacao/cripto/assets/background_loop.mp3 differ
diff --git a/app/src/atividades/programacao/cripto/blocks/blocks.js b/app/src/atividades/programacao/cripto/blocks/blocks.js
new file mode 100644
index 0000000..5b7ad2c
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/blocks/blocks.js
@@ -0,0 +1,821 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ *
+ * @module games.cripto.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import { javascriptGenerator } from "blockly/javascript";
+
+const HUE_LOGICA = 210;
+const HUE_MATEMATICA = 230;
+const HUE_TEXTO = 160;
+const HUE_REPETICAO = 120;
+const HUE_VARIAVEIS = 330;
+
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+/**
+ * Registra todos os blocos e geradores do jogo Cripto no Blockly.
+ * Deve ser chamado uma vez durante a inicialização do editor.
+ * @returns {void}
+ */
+
+
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const blockDefinitions = {
+ // Matemática
+ math_number: {
+ kind: "block",
+ type: "math_number",
+ },
+ math_arithmetic: {
+ kind: "block",
+ type: "math_arithmetic",
+ },
+ math_modulo: {
+ kind: "block",
+ type: "math_modulo",
+ },
+
+ // Texto
+ text: {
+ kind: "block",
+ type: "text",
+ },
+ text_indexOf: {
+ kind: "block",
+ type: "text_indexOf",
+ },
+ text_charAt: {
+ kind: "block",
+ type: "text_charAt",
+ },
+ text_join: {
+ kind: "block",
+ type: "text_join",
+ },
+ text_length: {
+ kind: "block",
+ type: "text_length",
+ },
+ alfabeto: {
+ kind: "block",
+ type: "alfabeto",
+ },
+ alfabeto_secreto: {
+ kind: "block",
+ type: "alfabeto_secreto",
+ },
+
+ // Lógica
+ controls_if: {
+ kind: "block",
+ type: "controls_if",
+ },
+ logic_compare: {
+ kind: "block",
+ type: "logic_compare",
+ },
+
+ // Repetição
+ controls_whileUntil: {
+ kind: "block",
+ type: "controls_whileUntil",
+ },
+ definir_contador: {
+ kind: "block",
+ type: "definir_contador",
+ },
+ obter_contador: {
+ kind: "block",
+ type: "obter_contador",
+ },
+ // Blocos Customizados de Entrada/Saída
+ definir_entrada: {
+ kind: "block",
+ type: "definir_entrada",
+ },
+ definir_saida: {
+ kind: "block",
+ type: "definir_saida",
+ },
+ concatenar_saida: {
+ kind: "block",
+ type: "concatenar_saida",
+ },
+ obter_entrada: {
+ kind: "block",
+ type: "obter_entrada",
+ },
+ obter_saida: {
+ kind: "block",
+ type: "obter_saida",
+ },
+
+ // Variáveis Customizadas
+ definir_letra: {
+ kind: "block",
+ type: "definir_letra",
+ },
+ obter_letra: {
+ kind: "block",
+ type: "obter_letra",
+ },
+ definir_posicao: {
+ kind: "block",
+ type: "definir_posicao",
+ },
+ obter_posicao: {
+ kind: "block",
+ type: "obter_posicao",
+ },
+ definir_nova_posicao: {
+ kind: "block",
+ type: "definir_nova_posicao",
+ },
+ obter_nova_posicao: {
+ kind: "block",
+ type: "obter_nova_posicao",
+ },
+ definir_nova_letra: {
+ kind: "block",
+ type: "definir_nova_letra",
+ },
+ obter_nova_letra: {
+ kind: "block",
+ type: "obter_nova_letra",
+ },
+ definir_chave: {
+ kind: "block",
+ type: "definir_chave",
+ },
+ obter_chave: {
+ kind: "block",
+ type: "obter_chave",
+ },
+ definir_letra_secreta: {
+ kind: "block",
+ type: "definir_letra_secreta",
+ },
+ obter_letra_secreta: {
+ kind: "block",
+ type: "obter_letra_secreta",
+ },
+ definir_soma: {
+ kind: "block",
+ type: "definir_soma",
+ },
+ obter_soma: {
+ kind: "block",
+ type: "obter_soma",
+ },
+ };
+
+ const toolboxContents = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Entrada/Saída",
+ colour: HUE_VARIAVEIS,
+ contents: [],
+ cssConfig: { container: "variaveis" },
+ },
+ {
+ kind: "category",
+ name: "Lógica",
+ colour: HUE_LOGICA,
+ contents: [],
+ cssConfig: { container: "logica" },
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: HUE_REPETICAO,
+ contents: [],
+ cssConfig: { container: "repeticao" },
+ },
+ {
+ kind: "category",
+ name: "Texto",
+ colour: HUE_TEXTO,
+ contents: [],
+ cssConfig: { container: "texto" },
+ },
+ {
+ kind: "category",
+ name: "Matemática",
+ colour: HUE_MATEMATICA,
+ contents: [],
+ cssConfig: { container: "matematica" },
+ },
+ ],
+ };
+
+ allowedBlocks.forEach((blockId) => {
+ const blockDef = blockDefinitions[blockId];
+
+ if (!blockDef) {
+ console.warn(`Bloco não encontrado: ${blockId}`);
+ return;
+ }
+
+ const categoryMap = {
+ obter_entrada: 0,
+ obter_saida: 0,
+ definir_entrada: 0,
+ definir_saida: 0,
+ concatenar_saida: 0,
+ definir_letra: 0,
+ obter_letra: 0,
+ definir_posicao: 0,
+ obter_posicao: 0,
+ definir_nova_posicao: 0,
+ obter_nova_posicao: 0,
+ definir_nova_letra: 0,
+ obter_nova_letra: 0,
+ definir_chave: 0,
+ obter_chave: 0,
+ definir_letra_secreta: 0,
+ obter_letra_secreta: 0,
+ definir_soma: 0,
+ obter_soma: 0,
+ controls_if: 1,
+ logic_compare: 1,
+ controls_whileUntil: 2,
+ definir_contador: 2,
+ obter_contador: 2,
+ text: 3,
+ text_charAt: 3,
+ text_join: 3,
+ text_length: 3,
+ text_indexOf: 3,
+ alfabeto: 3,
+ alfabeto_secreto: 3,
+ math_number: 4,
+ math_arithmetic: 4,
+ math_modulo: 4,
+ };
+
+ const categoryIndex = categoryMap[blockId];
+
+ if (
+ categoryIndex !== undefined &&
+ categoryIndex >= 0 &&
+ toolboxContents.contents[categoryIndex]
+ ) {
+ if (!toolboxContents.contents[categoryIndex].contents) {
+ toolboxContents.contents[categoryIndex].contents = [];
+ }
+ toolboxContents.contents[categoryIndex].contents.push(blockDef);
+ }
+ });
+
+ return toolboxContents;
+};
+
+/**
+ * Gera a estrutura de toolbox do Blockly contendo apenas blocos permitidos.
+ * Recebe `allowedBlocks` (lista de ids) e retorna o JSON do toolbox.
+ * @param {Array} [allowedBlocks=[]] - Lista de blocos habilitados
+ * @returns {Object} Estrutura `categoryToolbox` para o Blockly
+ */
+
+const defineBlocks = () => {
+ Blockly.Blocks["text_charAt"] = {
+ init: function () {
+ this.setHelpUrl(Blockly.Msg["TEXT_CHARAT_HELPURL"]);
+ this.setColour(HUE_TEXTO);
+ this.setOutput(true, "String");
+ this.appendValueInput("VALUE").setCheck("String").appendField("no texto");
+ this.appendValueInput("AT").setCheck("Number").appendField("obter letra");
+ this.setInputsInline(true);
+ this.setTooltip(Blockly.Msg["TEXT_CHARAT_TOOLTIP"]);
+ },
+ };
+
+ // Bloco: Definir Contador
+ Blockly.Blocks["definir_contador"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir CONTADOR como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_REPETICAO);
+ this.setTooltip("Define o valor do CONTADOR");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir Entrada
+ Blockly.Blocks["definir_entrada"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir ENTRADA como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da ENTRADA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir Saída
+ Blockly.Blocks["definir_saida"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir SAÍDA como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da SAÍDA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Concatenar Saída
+ Blockly.Blocks["concatenar_saida"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("adicionar à SAÍDA");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Adiciona um valor ao final da saída");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter Entrada
+ Blockly.Blocks["obter_entrada"] = {
+ init: function () {
+ this.appendDummyInput().appendField("ENTRADA");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor atual da entrada");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter Saída
+ Blockly.Blocks["obter_saida"] = {
+ init: function () {
+ this.appendDummyInput().appendField("SAÍDA");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor atual da saída");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter Contador
+ Blockly.Blocks["obter_contador"] = {
+ init: function () {
+ this.appendDummyInput().appendField("CONTADOR");
+ this.setOutput(true, null);
+ this.setColour(HUE_REPETICAO);
+ this.setTooltip("Obtém o valor atual do CONTADOR");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Alfabeto (constante)
+ Blockly.Blocks["alfabeto"] = {
+ init: function () {
+ this.appendDummyInput().appendField("ALFABETO");
+ this.setOutput(true, "String");
+ this.setColour(HUE_TEXTO);
+ this.setTooltip(
+ "Retorna o alfabeto completo: ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ );
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Alfabeto Secreto (constante)
+ Blockly.Blocks["alfabeto_secreto"] = {
+ init: function () {
+ this.appendDummyInput().appendField("ALFABETO SECRETO");
+ this.setOutput(true, "String");
+ this.setColour(HUE_TEXTO);
+ this.setTooltip(
+ "Retorna o alfabeto embaralhado: QWERTYUIOPASDFGHJKLZXCVBNM",
+ );
+ this.setHelpUrl("");
+ },
+ };
+
+ // ============ BLOCOS DE VARIÁVEIS CUSTOMIZADAS ============
+
+ // Bloco: Definir letra
+ Blockly.Blocks["definir_letra"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir LETRA como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável LETRA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter letra
+ Blockly.Blocks["obter_letra"] = {
+ init: function () {
+ this.appendDummyInput().appendField("LETRA");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável LETRA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir posicao
+ Blockly.Blocks["definir_posicao"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir POSIÇÃO como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável POSIÇÃO");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter posicao
+ Blockly.Blocks["obter_posicao"] = {
+ init: function () {
+ this.appendDummyInput().appendField("POSIÇÃO");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável POSIÇÃO");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir nova_posicao
+ Blockly.Blocks["definir_nova_posicao"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir nova_posicao como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável nova_posicao");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter nova_posicao
+ Blockly.Blocks["obter_nova_posicao"] = {
+ init: function () {
+ this.appendDummyInput().appendField("nova_posicao");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável nova_posicao");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir nova_letra
+ Blockly.Blocks["definir_nova_letra"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir nova_letra como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável nova_letra");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter nova_letra
+ Blockly.Blocks["obter_nova_letra"] = {
+ init: function () {
+ this.appendDummyInput().appendField("nova_letra");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável nova_letra");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir chave
+ Blockly.Blocks["definir_chave"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir chave como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip(
+ "Define o valor da variável chave (deslocamento da cifra)",
+ );
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter chave
+ Blockly.Blocks["obter_chave"] = {
+ init: function () {
+ this.appendDummyInput().appendField("chave");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável chave");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir letra_secreta
+ Blockly.Blocks["definir_letra_secreta"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir LETRA_SECRETA como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável LETRA_SECRETA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter letra_secreta
+ Blockly.Blocks["obter_letra_secreta"] = {
+ init: function () {
+ this.appendDummyInput().appendField("LETRA_SECRETA");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável LETRA_SECRETA");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Definir soma
+ Blockly.Blocks["definir_soma"] = {
+ init: function () {
+ this.appendValueInput("VALUE")
+ .setCheck(null)
+ .appendField("definir SOMA como");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Define o valor da variável SOMA (acumulador)");
+ this.setHelpUrl("");
+ },
+ };
+
+ // Bloco: Obter soma
+ Blockly.Blocks["obter_soma"] = {
+ init: function () {
+ this.appendDummyInput().appendField("SOMA");
+ this.setOutput(true, null);
+ this.setColour(HUE_VARIAVEIS);
+ this.setTooltip("Obtém o valor da variável SOMA");
+ this.setHelpUrl("");
+ },
+ };
+};
+
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+
+ // Gerador: Definir Entrada
+ javascriptGenerator.forBlock["definir_entrada"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "definirEntrada(" + value + ");\n";
+ };
+
+ // Gerador: Definir Saída
+ javascriptGenerator.forBlock["definir_saida"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "definirSaida(" + value + ");\n";
+ };
+
+ // Gerador: Definir Contador
+ javascriptGenerator.forBlock["definir_contador"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "definirContador(" + value + ");\n";
+ };
+
+ // Gerador: Concatenar Saída
+ javascriptGenerator.forBlock["concatenar_saida"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "concatenarSaida(" + value + ");\n";
+ };
+
+ // Gerador: Obter Entrada
+ javascriptGenerator.forBlock["obter_entrada"] = function (block) {
+ const code = "obterEntrada()";
+ return [code, javascriptGenerator.ORDER_FUNCTION_CALL];
+ };
+
+ // Gerador: Obter Saída
+ javascriptGenerator.forBlock["obter_saida"] = function (block) {
+ const code = "obterSaida()";
+ return [code, javascriptGenerator.ORDER_FUNCTION_CALL];
+ };
+
+ // Gerador: Obter Contador
+ javascriptGenerator.forBlock["obter_contador"] = function (block) {
+ const code = "obterContador()";
+ return [code, javascriptGenerator.ORDER_FUNCTION_CALL];
+ };
+
+ // Gerador: Alfabeto
+ javascriptGenerator.forBlock["alfabeto"] = function (block) {
+ const code = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ"';
+ return [code, javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Gerador: Alfabeto Secreto
+ javascriptGenerator.forBlock["alfabeto_secreto"] = function (block) {
+ const code = '"QWERTYUIOPASDFGHJKLZXCVBNM"';
+ return [code, javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Gerador customizado: text_charAt (0-based, não subtrai 1)
+ // Assume que todos os índices fornecidos já são 0-based (compatível com CONTADOR = 0)
+ javascriptGenerator.forBlock["text_charAt"] = function (block) {
+ const text =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_MEMBER,
+ ) || "''";
+ const at =
+ javascriptGenerator.valueToCode(
+ block,
+ "AT",
+ javascriptGenerator.ORDER_NONE,
+ ) || "0";
+ const code = text + ".charAt(" + at + ")";
+ return [code, javascriptGenerator.ORDER_MEMBER];
+ };
+
+ // Gerador customizado: text_indexOf (0-based, não adiciona 1)
+ // Retorna o índice 0-based direto, compatível com charAt e arrays JavaScript
+ javascriptGenerator.forBlock["text_indexOf"] = function (block) {
+ const text =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_MEMBER,
+ ) || "''";
+ const search =
+ javascriptGenerator.valueToCode(
+ block,
+ "FIND",
+ javascriptGenerator.ORDER_NONE,
+ ) || "''";
+ const code = text + ".indexOf(" + search + ")";
+ return [code, javascriptGenerator.ORDER_MEMBER];
+ };
+
+ // ============ GERADORES PARA VARIÁVEIS CUSTOMIZADAS ============
+
+ // Geradores: letra
+ javascriptGenerator.forBlock["definir_letra"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "var letra = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_letra"] = function (block) {
+ return ["letra", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: posicao
+ javascriptGenerator.forBlock["definir_posicao"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "0";
+ return "var posicao = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_posicao"] = function (block) {
+ return ["posicao", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: nova_posicao
+ javascriptGenerator.forBlock["definir_nova_posicao"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "0";
+ return "var nova_posicao = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_nova_posicao"] = function (block) {
+ return ["nova_posicao", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: nova_letra
+ javascriptGenerator.forBlock["definir_nova_letra"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "var nova_letra = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_nova_letra"] = function (block) {
+ return ["nova_letra", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: chave
+ javascriptGenerator.forBlock["definir_chave"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "0";
+ return "var chave = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_chave"] = function (block) {
+ return ["chave", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: letra_secreta
+ javascriptGenerator.forBlock["definir_letra_secreta"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "''";
+ return "var letra_secreta = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_letra_secreta"] = function (block) {
+ return ["letra_secreta", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ // Geradores: soma
+ javascriptGenerator.forBlock["definir_soma"] = function (block) {
+ const value =
+ javascriptGenerator.valueToCode(
+ block,
+ "VALUE",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "0";
+ return "var soma = " + value + ";\n";
+ };
+
+ javascriptGenerator.forBlock["obter_soma"] = function (block) {
+ return ["soma", javascriptGenerator.ORDER_ATOMIC];
+ };
+};
diff --git a/app/src/atividades/programacao/cripto/config/codeValidations.js b/app/src/atividades/programacao/cripto/config/codeValidations.js
new file mode 100644
index 0000000..ce6b3db
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/config/codeValidations.js
@@ -0,0 +1,175 @@
+/**
+ * @fileoverview Utility module for codeValidations.js
+ *
+ * @module games.cripto.config.codeValidations
+ */
+
+// Código de validações para prevenir loops infinitos e erros comuns nas fases do jogo.
+// As validações são usadas pelo BaseGameScene/config.js para bloquear execuções inseguras.
+
+/**
+ * Valida se um loop while contém incremento ou decremento do contador
+ * Previne loops infinitos onde o contador nunca muda
+ */
+export function validateWhileWithCounter(code) {
+ // Verifica se há um loop while no código
+ const hasWhile = /while\s*\(/i.test(code);
+ if (!hasWhile) return { valid: true };
+
+ // Verifica se o contador é usado na condição do while
+ const whileWithCounter = /while\s*\([^)]*[cC]ontador[^)]*\)/i.test(code);
+ if (!whileWithCounter) {
+ // Se não usa contador na condição, não validamos (pode ser outro tipo de loop)
+ return { valid: true };
+ }
+
+ // Verifica se há incremento/decremento do contador dentro do loop
+ const hasCounterIncrement =
+ /(definirContador|contador\s*[+-]=|\+\+contador|contador\+\+|--contador|contador--)/i.test(
+ code,
+ );
+
+ if (!hasCounterIncrement) {
+ return {
+ valid: false,
+ message:
+ 'Loop Infinito Detectado!\n\nSeu loop WHILE usa o CONTADOR na condição, mas não há incremento/decremento do CONTADOR dentro do loop.\n\nIsso causará um loop infinito!\n\nSolução: Adicione um bloco "definir CONTADOR" para incrementar o contador dentro do loop.',
+ };
+ }
+
+ return { valid: true };
+}
+
+/**
+ * Valida se o loop while tem uma condição que pode eventualmente se tornar falsa
+ */
+export function validateWhileCondition(code) {
+ // Detecta condições sempre verdadeiras óbvias
+ const alwaysTruePatterns = [
+ /while\s*\(\s*true\s*\)/i,
+ /while\s*\(\s*1\s*\)/i,
+ /while\s*\(\s*"[^"]+"\s*\)/i, // string não vazia
+ ];
+
+ for (const pattern of alwaysTruePatterns) {
+ if (pattern.test(code)) {
+ return {
+ valid: false,
+ message:
+ 'Loop Infinito Detectado!\n\nSua condição do WHILE é sempre verdadeira, o que causará um loop infinito.\n\nSolução: Use uma condição que possa se tornar falsa, como "CONTADOR < comprimento de ENTRADA".',
+ };
+ }
+ }
+
+ return { valid: true };
+}
+
+/**
+ * Valida se o loop while que usa length() modifica o que está sendo iterado
+ */
+export function validateWhileWithLength(code) {
+ const hasWhileWithLength = /while\s*\([^)]*\.length[^)]*\)/i.test(code);
+ if (!hasWhileWithLength) return { valid: true };
+
+ // Verifica se há chamada para obterEntrada() dentro da condição
+ const hasGetInputInCondition = /while\s*\([^)]*obterEntrada\(\)[^)]*\)/i.test(
+ code,
+ );
+ if (!hasGetInputInCondition) return { valid: true };
+
+ // Verifica se há incremento do contador
+ const hasCounterIncrement =
+ /(definirContador|contador\s*[+-]=|\+\+contador|contador\+\+)/i.test(
+ code,
+ );
+
+ if (!hasCounterIncrement) {
+ return {
+ valid: false,
+ message:
+ "Loop Infinito Detectado!\n\nSeu loop usa o comprimento da ENTRADA, mas não incrementa o CONTADOR.\n\nIsso causará um loop infinito!\n\nSolução: Adicione incremento do CONTADOR dentro do loop.",
+ };
+ }
+
+ return { valid: true };
+}
+
+/**
+ * Valida se há pelo menos um loop no código (para fases que exigem iteração)
+ */
+export function validateHasLoop(code) {
+ const hasLoop = /while\s*\(|for\s*\(/i.test(code);
+
+ if (!hasLoop) {
+ return {
+ valid: false,
+ message:
+ "Loop Necessário!\n\nEsta fase requer que você use um loop (WHILE) para processar cada caractere da entrada.\n\nSolução: Use um bloco WHILE para percorrer a entrada caractere por caractere.",
+ };
+ }
+
+ return { valid: true };
+}
+
+/**
+ * Valida se o código define a entrada antes de usá-la
+ */
+export function validateInputBeforeUse(code) {
+ const hasGetInput = /obterEntrada\(\)/i.test(code);
+ if (!hasGetInput) return { valid: true }; // Não usa entrada, ok
+
+ const hasSetInput = /definirEntrada\(/i.test(code);
+
+ if (!hasSetInput) {
+ return {
+ valid: false,
+ message:
+ 'Entrada não definida!\n\nVocê está tentando obter a ENTRADA sem defini-la primeiro.\n\nSolução: Use o bloco "definir ENTRADA" antes de usar "obter ENTRADA".',
+ };
+ }
+
+ // Verifica ordem (definir antes de obter) - simplificado
+ const setInputIndex = code.search(/definirEntrada\(/i);
+ const getInputIndex = code.search(/obterEntrada\(\)/i);
+
+ if (getInputIndex < setInputIndex) {
+ return {
+ valid: false,
+ message:
+ 'Ordem incorreta!\n\nVocê está tentando obter a ENTRADA antes de defini-la.\n\nSolução: Mova o bloco "definir ENTRADA" para antes de "obter ENTRADA".',
+ };
+ }
+
+ return { valid: true };
+}
+
+/**
+ * Combina múltiplas validações
+ */
+export function validateCode(code, validations = []) {
+ for (const validation of validations) {
+ const result = validation(code);
+ if (!result.valid) {
+ return result;
+ }
+ }
+ return { valid: true };
+}
+
+/**
+ * Validações padrão para fases com loops
+ */
+export const defaultLoopValidations = [
+ validateWhileCondition,
+ validateWhileWithCounter,
+ validateWhileWithLength,
+ validateInputBeforeUse,
+];
+
+/**
+ * Validações para fases que exigem loops
+ */
+export const requiredLoopValidations = [
+ validateHasLoop,
+ ...defaultLoopValidations,
+];
diff --git a/app/src/atividades/programacao/cripto/config/config.js b/app/src/atividades/programacao/cripto/config/config.js
new file mode 100644
index 0000000..eb3542e
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/config/config.js
@@ -0,0 +1,419 @@
+/**
+ * @fileoverview Utility module for config.js
+ *
+ * @module games.cripto.config.config
+ */
+
+export const gameConfig = {
+ gameId: "cripto",
+ gameName: "Cripto",
+ type: "blocks",
+ icon: "🔐",
+ thumbnail: "/images/atividades/programacao/cripto-thumbnail.png",
+ descricao:
+ "Aprenda fundamentos de criptografia e segurança cibernética programando blocos para proteger dados e comunicações.",
+ dificuldade: "Avançado",
+ categoria: "Lógica",
+ tempoEstimado: "45-60 min",
+ conceitos: [
+ "Criptografia",
+ "Repetição",
+ "Variaveis",
+ "Funções",
+ "Condicionais",
+ ],
+ route: "/atividades/programacao/cripto",
+ component: "CriptoGame",
+ objectives: [
+ "Entender os conceitos básicos de criptografia",
+ "Implementar algoritmos de criptografia simples",
+ "Aplicar lógica de programação para resolver desafios de segurança",
+ ],
+ metadata: {
+ lastUpdated: "2026-02-12",
+ version: "1.0.0",
+ },
+
+ fases: [
+ {
+ id: 1,
+ nome: "De Letra para Número",
+ descricao:
+ 'Converta cada letra do alfabeto em sua posição numérica (A=0, B=1, C=2...). Primeiro, defina a ENTRADA como o alfabeto completo "ABCDEFGHIJKLMNOPQRSTUVWXYZ". Depois, use um loop "enquanto" para percorrer cada posição e adicionar o número correspondente à SAÍDA. Dica: o CONTADOR já representa a posição da letra!',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "obter_saida",
+ "text_length",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "text",
+ ],
+ /**
+ * Garante que há um loop com incremento do contador para evitar loop infinito
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ expectedOutput: "012345678910111213141516171819202122232425",
+ },
+ {
+ id: 2,
+ nome: "De Número para Letra",
+ descricao:
+ 'Reverta o processo! A ENTRADA contém dígitos "0123456789" (cada dígito é a posição de uma letra). Crie a variável "letra" para guardar cada caractere. Use o bloco "no texto obter letra #" para pegar cada dígito, depois busque no ALFABETO a letra correspondente. Por exemplo: dígito "0" → letra "A", dígito "1" → letra "B".',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ ],
+ /**
+ * Garante que há um loop com incremento do contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "0123456789",
+ expectedOutput: "ABCDEFGHIJ",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ },
+ {
+ id: 3,
+ nome: "Cifra de César (+3)",
+ descricao:
+ 'Implemente a Cifra de César com deslocamento fixo de +3. Para a ENTRADA "TECNOLOGIA", cada letra deve avançar 3 posições (exemplo: A→D, B→E, C→F). Crie variáveis: "letra" (use "no texto obter letra #"), "posicao" (use "no texto encontrar primeira ocorrência de" para achar a letra no ALFABETO), "nova_posicao" (calcule: (posicao + 3) RESTO DA DIVISÃO POR 26 - use o bloco "resto da divisão de... por..."), e "nova_letra" (pegue do ALFABETO na nova_posicao).',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "math_modulo",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_nova_posicao",
+ "obter_nova_posicao",
+ "definir_nova_letra",
+ "obter_nova_letra",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "TECNOLOGIA",
+ expectedOutput: "WHFQRORJLD",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ chave: 3,
+ },
+ {
+ id: 4,
+ nome: "Cifra de César (-3)",
+ descricao:
+ 'Descriptografe uma mensagem cifrada com deslocamento -3. Para a ENTRADA "GHFRGD", volte 3 posições: G→D, H→E, etc. Crie as mesmas variáveis da fase anterior: "letra", "posicao", "nova_posicao" e "nova_letra". Importante: calcule nova_posicao como (posicao - 3 + 26) RESTO DA DIVISÃO POR 26 (use o bloco "resto da divisão de... por..."). O +26 evita números negativos!',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "math_modulo",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_nova_posicao",
+ "obter_nova_posicao",
+ "definir_nova_letra",
+ "obter_nova_letra",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "GHFRGD",
+ expectedOutput: "DECODA",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ chave: -3,
+ },
+ {
+ id: 5,
+ nome: "Criptografia com Chave Variável",
+ descricao:
+ 'Use uma chave customizada para criptografar! Para a ENTRADA "NUCLEO" com chave 5, desloque cada letra 5 posições. Crie a variável "chave" com valor 5. Depois use as mesmas variáveis das fases anteriores: "letra", "posicao", "nova_posicao" (use o bloco "resto da divisão" para calcular (posicao + chave) % 26) e "nova_letra". Na fórmula, use a variável "chave" em vez do número fixo 3.',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "math_modulo",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_nova_posicao",
+ "obter_nova_posicao",
+ "definir_nova_letra",
+ "obter_nova_letra",
+ "definir_chave",
+ "obter_chave",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "NUCLEO",
+ expectedOutput: "SZHQJT",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ chave: 5,
+ },
+ {
+ id: 6,
+ nome: "Descriptografia com Chave Variável",
+ descricao:
+ 'Desfaça a criptografia anterior! Para a ENTRADA "SZHQJT" com chave 5, volte 5 posições. Use as mesmas variáveis da Fase 5, mas agora calcule nova_posicao como: (posicao - chave + 26) RESTO DA DIVISÃO POR 26 (use o bloco "resto da divisão de... por..."). Lembre-se: sempre some 26 antes do módulo quando subtrair!',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "math_modulo",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_nova_posicao",
+ "obter_nova_posicao",
+ "definir_nova_letra",
+ "obter_nova_letra",
+ "definir_chave",
+ "obter_chave",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "SZHQJT",
+ expectedOutput: "NUCLEO",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ chave: 5,
+ },
+ {
+ id: 7,
+ nome: "Código Leetspeak",
+ descricao:
+ 'Use condicionais para substituir letras específicas por números! Para a ENTRADA "SOBERANIA", substitua A→4, E→3, S→5 e I→1, mantendo as outras iguais. Crie a variável "letra" para pegar cada caractere (use "no texto obter letra #"). Depois use blocos SE-SENÃO: se letra = "A" então concatene "4", senão se letra = "E" então concatene "3", senão se letra = "S" então concatene "5", senão se letra = "I" então concatene "1", senão concatene a própria letra.',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "controls_whileUntil",
+ "controls_if",
+ "logic_compare",
+ "math_number",
+ "text",
+ "definir_letra",
+ "definir_nova_letra",
+ "obter_nova_letra",
+ "obter_letra",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "SOBERANIA",
+ expectedOutput: "5OB3R4N14",
+ },
+ {
+ id: 8,
+ nome: "Mensagem Invertida",
+ descricao:
+ 'Inverta a ordem das letras! Para a ENTRADA "DECODA", o resultado deve ser "ADOCED". Duas abordagens: (1) Inicie CONTADOR com (comprimento - 1) e decremente até 0, OU (2) Crie variável "letra", pegue cada caractere e use o bloco "criar texto com" para juntar: primeira entrada = letra, segunda entrada = SAÍDA atual. Isso coloca cada letra ANTES das anteriores.',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "obter_saida",
+ "text_length",
+ "text_charAt",
+ "text_join",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "text",
+ "definir_letra",
+ "obter_letra",
+ ],
+ /**
+ * Garante loop com incremento de contador (ou decremento para inversão)
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*(\+|-)/,
+ expectedInput: "DECODA",
+ expectedOutput: "ADOCED",
+ },
+ {
+ id: 9,
+ nome: "Alfabeto Secreto",
+ descricao:
+ 'Use um alfabeto embaralhado para cifrar! Para a ENTRADA "FENALUTA", use os blocos ALFABETO (normal) e ALFABETO_SECRETO (embaralhado). Crie variáveis: "letra" (pegue com "no texto obter letra #"), "posicao" (use "no texto encontrar" para buscar a letra no ALFABETO normal), e "letra_secreta" (pegue com "no texto obter letra #" na mesma posição do ALFABETO_SECRETO). Exemplo: R está na posição 17 do normal → posição 17 do secreto é A.',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "text",
+ "alfabeto",
+ "alfabeto_secreto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_letra_secreta",
+ "obter_letra_secreta",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "FENALUTA",
+ expectedOutput: "YTFQSXZQ",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ alfabetoSecreto: "QWERTYUIOPASDFGHJKLZXCVBNM",
+ },
+ {
+ id: 10,
+ nome: "Somador de Integridade (Hash)",
+ descricao:
+ 'Para garantir que uma mensagem não foi alterada, usamos um "Hash" — uma assinatura numérica única. Para cada letra da ENTRADA "CRIPTOGRAFIA", descubra sua posição no ALFABETO e atualize a variável "SOMA" usando a fórmula: (SOMA * 31 + posicao). Para manter o número dentro de um limite, use o RESTO DA DIVISÃO por 1000000007. Dentro do loop, chame "definir SAÍDA como SOMA" para ver o hash atualizando letra a letra.',
+ timeout: 30,
+ allowedBlocks: [
+ "obter_contador",
+ "definir_contador",
+ "definir_entrada",
+ "obter_entrada",
+ "definir_saida",
+ "concatenar_saida",
+ "text_length",
+ "text_charAt",
+ "text_indexOf",
+ "controls_whileUntil",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "math_modulo",
+ "text",
+ "alfabeto",
+ "definir_letra",
+ "obter_letra",
+ "definir_posicao",
+ "obter_posicao",
+ "definir_soma",
+ "obter_soma",
+ ],
+ /**
+ * Garante loop com incremento de contador
+ */
+ validationRegex: /(while|for)[\s\S]*definirContador[\s\S]*\+/,
+ expectedInput: "CRIPTOGRAFIA",
+ expectedOutput: "911701368",
+ alfabeto: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ },
+ ],
+ mensagens: {
+ entradaIncorreta:
+ "Entrada incorreta. Você deve definir a ENTRADA como o alfabeto completo.",
+ saidaIncorreta:
+ "Saída incorreta. Cada letra deve ser convertida para sua posição numérica (A=0, B=1, C=2...). O CONTADOR já representa essa posição!",
+ erroGeral: "Algo deu errado durante a execução. Verifique seu código.",
+ erroEstrutura:
+ 'Loop Infinito Detectado!\n\nSeu código não incrementa o CONTADOR dentro do loop WHILE. Isso causará um loop infinito!\n\n💡 Solução:\nAdicione um bloco "definir CONTADOR como (obter CONTADOR + 1)" dentro do loop para que o contador avance a cada iteração.',
+ sucessoGenerico: "Parabéns! Você completou o desafio!",
+ timeoutExcedido:
+ "O tempo de execução foi excedido. Verifique se não há loops infinitos ou se o contador está sendo incrementado corretamente.",
+ },
+};
diff --git a/app/src/atividades/programacao/cripto/config/debugSolutions.js b/app/src/atividades/programacao/cripto/config/debugSolutions.js
new file mode 100644
index 0000000..076883a
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/config/debugSolutions.js
@@ -0,0 +1,2430 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.cripto.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "{PvV61SJdGlC|8U+Q7wh",
+ x: 38,
+ y: 38,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "l*}2[;k^9FD.@6oh^.I=",
+ fields: { TEXT: "ABCDEFGHIJKLMNOPQRSTUVWXYZ" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "*a$[m:Od`=`#qSaI}A:e",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "hrc/MS0?Gg#:th]ZUQ=o",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "A/{@R^y6sk~qqt.^R~,j",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "4%=KW|prH;#~~w8o]/5y",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "JHSp`!pO10f}n%m^p%wE",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: ",HosS(izObzhg9w]8x8H",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "rAjR$K}2NNDwB5}k2a42",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "P;5n]y$rPhJ25idp#Mi?",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "9{zMkVo0ytU$U,+13ARH",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "concatenar_saida",
+ id: "1{=`l8Qt,:jqnT_/Y!`{",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_contador",
+ id: "-1_c_WlB^F.`}ui$l~E%",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "HOt/~~y`o#R|*8L|84Hc",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "F7c#(3g_DRb026#+jL)N",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "R/.1Tub8-2DKY/m!x%}k",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "-AF!LUD,u4s=RIM,d%=-",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "O9I3?,G0j-/r=VQ0nzlQ" },
+ { name: "saida", id: "7DWB{cTau*.R}ytfkO)5" },
+ { name: "pos", id: "D)PyB}U?XdjaKb:8:;IU" },
+ ],
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "RQQ~:CJxJx-{*Tk2:CGh",
+ x: 38,
+ y: -12,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "FlSOd)~:ESuKr$-*L9%4",
+ fields: { TEXT: "0123456789" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: ":wL_1K/JVHK/PmPy|C0k",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";w5[5$+sk6!AEJ~+J54|",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "DmI%LXV@AQazVQC/I-KF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "Wlah+;.;c^{o~u@g8Nfp",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "S0Lt_VG,_zYB)mwP0r$b",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "%Ue:]7DLN#{|{[K4v:K?",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "vX6e0Jt(r$t95-mu@rXU",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "Obz:Kkha~W}_pzPy`?GN",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "?u8vtXV}qoa,q%;H~bTc",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "concatenar_saida",
+ id: "F=@j^pbv9WPZdq03LvDw",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "fJ=A%74_XG(YEqvp78i7",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "q^GNQOcJ~+pTS7YEa=MN",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: "2.a#|{?WV}g=9P5kn64(",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "?d57/ryO,0Bvu9}R7UsU",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "~pWbZv@=SerP3LR1MhM(",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "a4T3zxrwk@mIiqJg0=P(",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: ":5NM(S_}4F-=Lb;BvPWB",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "AFx98t.3eW9j/Qw]suES" },
+ { name: "saida", id: "lIH[kiX_`z}(i$%_{/Ez" },
+ { name: "pos", id: "/$qR4C0s1b9,AX[X5I;4" },
+ ],
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 63,
+ y: 63,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "TECNOLOGIA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 13,
+ y: 38,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "GHFRGD" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "NUCLEO" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_chave",
+ id: "DD}I?w!B}_GDPB;ve3b@",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: ".!W$Ah:Y;b[,5x`9pBP?",
+ fields: { NUM: 5 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "obter_chave",
+ id: "|!:3nO3O2GMfrf7c3Q{j",
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: {
+ NUM: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ x: 682,
+ y: 500,
+ disabledReasons: ["MANUALLY_DISABLED"],
+ fields: { NUM: 3 },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 6: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "SZHQJT" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_chave",
+ id: "DD}I?w!B}_GDPB;ve3b@",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: ".!W$Ah:Y;b[,5x`9pBP?",
+ fields: { NUM: 5 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "obter_chave",
+ id: "|!:3nO3O2GMfrf7c3Q{j",
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: {
+ NUM: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ x: 682,
+ y: 500,
+ disabledReasons: ["MANUALLY_DISABLED"],
+ fields: { NUM: 3 },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 7: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 136,
+ y: 56,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "SOBERANIA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_if",
+ id: "Ubvdiqyf_D]o2n~9faIq",
+ extraState: { elseIfCount: 3, hasElse: true },
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "6cf_3Q5TJs$Ii]u2}F({",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_letra",
+ id: "^m`s*na#du@AWgM}(I3N",
+ },
+ },
+ B: {
+ block: {
+ type: "text",
+ id: "Xi8+ZY[VThn:a0Gnjyhy",
+ fields: { TEXT: "A" },
+ },
+ },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "definir_nova_letra",
+ id: "Bq.xlwU252x9(emRVO:I",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "gN%|aXdymweYAc3Qj0m%",
+ fields: { TEXT: "4" },
+ },
+ },
+ },
+ },
+ },
+ IF1: {
+ block: {
+ type: "logic_compare",
+ id: "oB6{:0eX5i7WAF3`]z=Z",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_letra",
+ id: "]@5*JI5N@3!7Wre{BiAG",
+ },
+ },
+ B: {
+ block: {
+ type: "text",
+ id: "!{ru}{+i!i;v~Mn0Iu+T",
+ fields: { TEXT: "E" },
+ },
+ },
+ },
+ },
+ },
+ DO1: {
+ block: {
+ type: "definir_nova_letra",
+ id: "d+=nn94P8]uZng;VX54@",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "9ZuBx0:958GAR!BA$2)-",
+ fields: { TEXT: "3" },
+ },
+ },
+ },
+ },
+ },
+ IF2: {
+ block: {
+ type: "logic_compare",
+ id: "e9bU1L2%q[sDA1:@F2!B",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_letra",
+ id: "FV|]0}M[2q~#EWf-7,we",
+ },
+ },
+ B: {
+ block: {
+ type: "text",
+ id: "T(SI2Qgag_Jb(=X%g}9$",
+ fields: { TEXT: "S" },
+ },
+ },
+ },
+ },
+ },
+ DO2: {
+ block: {
+ type: "definir_nova_letra",
+ id: "z%MCeyF_5+4!,?.wcjxJ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ")3JW--gpA4bGP^x4-tgA",
+ fields: { TEXT: "5" },
+ },
+ },
+ },
+ },
+ },
+ IF3: {
+ block: {
+ type: "logic_compare",
+ id: ".Ju4@#M:+i%ZCwJA5/ie",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_letra",
+ id: "{n9WgoRZq%:9_a4^qTHM",
+ },
+ },
+ B: {
+ block: {
+ type: "text",
+ id: "SPx8ySpnbNWb6K|DWVy}",
+ fields: { TEXT: "I" },
+ },
+ },
+ },
+ },
+ },
+ DO3: {
+ block: {
+ type: "definir_nova_letra",
+ id: "s?%Y]JHm`.cupC3P6Y32",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "tY@(1IRwC1bFhP8I}K(e",
+ fields: { TEXT: "1" },
+ },
+ },
+ },
+ },
+ },
+ ELSE: {
+ block: {
+ type: "definir_nova_letra",
+ id: "gP8_]qKU@csx!B{Zg+2:",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_letra",
+ id: "q=EM_DL?j|j5W$N1|BYs",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "iL3r[TG=E58N*1Vp1aUH",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 8: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "DECODA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "7UC[k=$0XF*F3elO?(-!",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "text_length",
+ id: "AGstr=NNWMIn,XwM`[Q/",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "m=pC#ty^d:fF,.3TgF:r",
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "GTE" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "[q;uUY*2Lb6do%uNaWdq",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "concatenar_saida",
+ id: "OnGE(ok[tVi.1X1?*sv%",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "|F3W691=0CXfC_U$Fn`I",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "B+ry47sOv.IRX+_hp4^S",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: "oN3MF:nOFpg5/`Im#iW*",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 9: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "FENALUTA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_letra_secreta",
+ id: "rEOcUDl{ox`,Y9dO]q}%",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "`n%9+C|S*=+k_@x5oput",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto_secreto",
+ id: "PXRGRLrdz1ra+jFNtYlc",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_posicao",
+ id: "k,s,+_0_/.x!;YhOw7T]",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_letra_secreta",
+ id: "}yP6hL%8868DlWmh}TE|",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 10: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "OQ2E%TGw^V^Ol):C.3Jz",
+ x: -62,
+ y: 388,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "-|ly%n[:;!:zY3!VUFkR",
+ fields: { TEXT: "CRIPTOGRAFIA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "aYolTWVGZV]4hN_)Acj2",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "-yD@+p5fdTAq3zs7US$*",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_soma",
+ id: "U}Q@W!Pa[a9VeS3Zhq/(",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "A9xo`rU{CD8~CPlya,|R",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "SsUf9^5ZxYq+Ns7/o1NM",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "%(];OD:fRGi;[#`Qrw0n",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "gC?jeA-)go3|4T2J9hC6",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "(^gJyYmQr3xx=Y8b]c-x",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "T!`NESbJU.0G[4H/#0//",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "eTlfj{:i6DJu5BN/vVg1",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "7@j0@CRSi+MKlWZ_|ROw",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "@j.F-Z9lmpo2%cO(Ys|6",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "A[jcUgwTDWK:^PQJ7tFq",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "6{SH7+tUsQLOe!VPLF!/",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: "*bZiLpZ1xc:9B,5yOX`t",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "gqAlo#!VG+a@1exAxYNv",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "l#|#%A_p8gZQzN(.Cru/",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "mc~mNENUD3?!foqz7|0N",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: ",lQ7V9.Q[RfeKd*%m+2`",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_soma",
+ id: "sQ@}NDv(,JKh,ID6ktSp",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "#9/eA^3~7@n+tHz~Cxgb",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "Y9?~t0;I~3ztGTdg6,(#",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "math_arithmetic",
+ id: "@h-~0getPWSjC,uCwpQ(",
+ fields: {
+ OP: "MULTIPLY",
+ },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_soma",
+ id: "j0_}/`UY[aIgc?#9+#V]",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "cKp8a:o?B7`n(d9SzU`R",
+ fields: {
+ NUM: 31,
+ },
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "obter_posicao",
+ id: "C2}o_5(Xp-IX`GX=[.@N",
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "NaQ+^Kw@O,.42GCzD_$_",
+ fields: { NUM: 1000000007 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "9UJ-7evZo0QfI^SIs%RF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_soma",
+ id: "F/hj1p~[Bh~QFwgwn2gA",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "y#Sr0b4c6Kj68)UR;sFe",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "),Y$/lx#/Phq*kTk}#Dc",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "Jx.g!u($I_Q{LxxzL[^[",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "MoBAh!GKd?lx1_qwF*^?",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "7K?EI04[`4bo[)xa(iyW" },
+ { name: "saida", id: "FMe5q:,O2QHlC4=zk=ln" },
+ { name: "pos", id: ")1-Jynyj,*CN7UZ6g%6%" },
+ ],
+ },
+};
diff --git a/app/src/atividades/programacao/cripto/config/starterBlocks.js b/app/src/atividades/programacao/cripto/config/starterBlocks.js
new file mode 100644
index 0000000..3fd50ad
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/config/starterBlocks.js
@@ -0,0 +1,1910 @@
+/**
+ * @fileoverview Utility module for starterBlocks.js
+ *
+ * @module games.cripto.config.starterBlocks
+ */
+
+export const starterBlocks = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "{PvV61SJdGlC|8U+Q7wh",
+ x: 38,
+ y: 38,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "l*}2[;k^9FD.@6oh^.I=",
+ fields: { TEXT: "ABC" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "*a$[m:Od`=`#qSaI}A:e",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "hrc/MS0?Gg#:th]ZUQ=o",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "A/{@R^y6sk~qqt.^R~,j",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "4%=KW|prH;#~~w8o]/5y",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "JHSp`!pO10f}n%m^p%wE",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: ",HosS(izObzhg9w]8x8H",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "rAjR$K}2NNDwB5}k2a42",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "P;5n]y$rPhJ25idp#Mi?",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "9{zMkVo0ytU$U,+13ARH",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "concatenar_saida",
+ id: "1{=`l8Qt,:jqnT_/Y!`{",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_contador",
+ id: "-1_c_WlB^F.`}ui$l~E%",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "HOt/~~y`o#R|*8L|84Hc",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "F7c#(3g_DRb026#+jL)N",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "R/.1Tub8-2DKY/m!x%}k",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "-AF!LUD,u4s=RIM,d%=-",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "O9I3?,G0j-/r=VQ0nzlQ" },
+ { name: "saida", id: "7DWB{cTau*.R}ytfkO)5" },
+ { name: "pos", id: "D)PyB}U?XdjaKb:8:;IU" },
+ ],
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "RQQ~:CJxJx-{*Tk2:CGh",
+ x: 38,
+ y: -12,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "FlSOd)~:ESuKr$-*L9%4",
+ fields: { TEXT: "012" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: ":wL_1K/JVHK/PmPy|C0k",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";w5[5$+sk6!AEJ~+J54|",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "DmI%LXV@AQazVQC/I-KF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "Wlah+;.;c^{o~u@g8Nfp",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "S0Lt_VG,_zYB)mwP0r$b",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "%Ue:]7DLN#{|{[K4v:K?",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "vX6e0Jt(r$t95-mu@rXU",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "Obz:Kkha~W}_pzPy`?GN",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "?u8vtXV}qoa,q%;H~bTc",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "AFx98t.3eW9j/Qw]suES" },
+ { name: "saida", id: "lIH[kiX_`z}(i$%_{/Ez" },
+ { name: "pos", id: "/$qR4C0s1b9,AX[X5I;4" },
+ ],
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "ABC" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "TECNOLOGIA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LTE" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "TECNOLOGIA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LTE" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 6: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "NUCLEO" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_chave",
+ id: "DD}I?w!B}_GDPB;ve3b@",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: ".!W$Ah:Y;b[,5x`9pBP?",
+ fields: { NUM: 5 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "VBpO^.0oqIo3!*hpIANo",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "i5a[]`-ipWA{@]W(}uG(",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: "Vd:8qJAe0,t_Z9$V]D])",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_posicao",
+ id: "EI.B2#hi^8pm-ozz,xhI",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_modulo",
+ id: "3#j973W]bp~lJOSKR#`@",
+ inputs: {
+ DIVIDEND: {
+ block: {
+ type: "math_arithmetic",
+ id: "j7`s5ntfz{4u;OogXzNU",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_posicao",
+ id: "/_SleYi#x?*8LFvx47:g",
+ },
+ },
+ B: {
+ block: {
+ type: "obter_chave",
+ id: "|!:3nO3O2GMfrf7c3Q{j",
+ },
+ },
+ },
+ },
+ },
+ DIVISOR: {
+ block: {
+ type: "math_number",
+ id: "#aemA~Fp]f[{/s_-Pzd%",
+ fields: { NUM: 26 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_nova_letra",
+ id: "u?Q-3*,n6#-%x:JO6n?]",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "v?6i8Rl{mT7}+]@tUq8U",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "}G!v]@X9HzUG5IH=!Zj7",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_nova_posicao",
+ id: "C5C[YGXTVx](_~#M3J46",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_nova_letra",
+ id: "n`p*~L]ca=WUD@=79FEv",
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: {
+ NUM: 1,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ type: "math_number",
+ id: "};;3RF^veh|Tf}!1_AD|",
+ x: 682,
+ y: 500,
+ disabledReasons: ["MANUALLY_DISABLED"],
+ fields: { NUM: 3 },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 7: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "SZHQJT" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "Nc4.xTya@tTx_1KWI9x+",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "@ytX~cyh9E}0Pz(?jSo0",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: ",@LsKp|J={I@hhoCYj;?",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_if",
+ id: "Ubvdiqyf_D]o2n~9faIq",
+ extraState: { elseIfCount: 3, hasElse: true },
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "6cf_3Q5TJs$Ii]u2}F({",
+ fields: { OP: "EQ" },
+ inputs: {
+ B: {
+ block: {
+ type: "text",
+ id: "Xi8+ZY[VThn:a0Gnjyhy",
+ fields: { TEXT: "A" },
+ },
+ },
+ },
+ },
+ },
+ IF1: {
+ block: {
+ type: "logic_compare",
+ id: "oB6{:0eX5i7WAF3`]z=Z",
+ fields: { OP: "EQ" },
+ inputs: {
+ B: {
+ block: {
+ type: "text",
+ id: "!{ru}{+i!i;v~Mn0Iu+T",
+ fields: { TEXT: "E" },
+ },
+ },
+ },
+ },
+ },
+ IF2: {
+ block: {
+ type: "logic_compare",
+ id: "e9bU1L2%q[sDA1:@F2!B",
+ fields: { OP: "EQ" },
+ inputs: {
+ B: {
+ block: {
+ type: "text",
+ id: "T(SI2Qgag_Jb(=X%g}9$",
+ fields: { TEXT: "S" },
+ },
+ },
+ },
+ },
+ },
+ IF3: {
+ block: {
+ type: "logic_compare",
+ id: ".Ju4@#M:+i%ZCwJA5/ie",
+ fields: { OP: "EQ" },
+ inputs: {
+ B: {
+ block: {
+ type: "text",
+ id: "SPx8ySpnbNWb6K|DWVy}",
+ fields: { TEXT: "I" },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 8: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 63,
+ y: 38,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "DECODA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "7UC[k=$0XF*F3elO?(-!",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "text_length",
+ id: "AGstr=NNWMIn,XwM`[Q/",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "m=pC#ty^d:fF,.3TgF:r",
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "GTE" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 9: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "J_7d/vs84AFBpTY`G|H-",
+ x: 77,
+ y: 83,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "8[c`Ypf{Dm2m~/el+c^H",
+ fields: { TEXT: "ENTRADA" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "]JE]BI@Wn($9=AC-11oD",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: ";T~tJta3D}sF$;qQa6r^",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "rhtIrQ$YD!DJw=uP@oDR",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iV/k_[xNRM?|LASAj2G=",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "e@AG]L5SQp[TU9Ysr0K1",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "dsA1vd}YJ,45K!c;a);k",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: ";/ogZIGjO*.d#X$JJGB8",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "A3eC5s`Gb(g^pA~1HFjQ",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "-_RK){n3ja7xPksY9?v4",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "j2QyyGM-DfNPZ6d~j:XV",
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "H?RbqDj`bMY6?)u5)-F5",
+ next: {
+ block: {
+ type: "definir_letra_secreta",
+ id: "rEOcUDl{ox`,Y9dO]q}%",
+ next: {
+ block: {
+ type: "concatenar_saida",
+ id: "=eFY%KT{g)#(]N!MqAtB",
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "kp_hPB6./u#JsQ_5PNJF",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "DOSMQ6yr{Yl90jP_t9vD",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "g:fNCOo.RRyaE^H}}gM$",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "Q+(RtBI=kKeSDaTN{pX|",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "}4(9^a!_QJ#^2}(Ccerx" },
+ { name: "saida", id: "r]H+9T#0b:z4#s|(O/@+" },
+ { name: "pos", id: "aL(u;jeFiLYRiuEw0VmM" },
+ ],
+ },
+ 10: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "definir_entrada",
+ id: "OQ2E%TGw^V^Ol):C.3Jz",
+ x: -62,
+ y: 388,
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "-|ly%n[:;!:zY3!VUFkR",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_saida",
+ id: "aYolTWVGZV]4hN_)Acj2",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text",
+ id: "-yD@+p5fdTAq3zs7US$*",
+ fields: { TEXT: "" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_soma",
+ id: "U}Q@W!Pa[a9VeS3Zhq/(",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "A9xo`rU{CD8~CPlya,|R",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "SsUf9^5ZxYq+Ns7/o1NM",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "%(];OD:fRGi;[#`Qrw0n",
+ fields: { NUM: 0 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "gC?jeA-)go3|4T2J9hC6",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_compare",
+ id: "(^gJyYmQr3xx=Y8b]c-x",
+ fields: { OP: "LT" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "T!`NESbJU.0G[4H/#0//",
+ },
+ },
+ B: {
+ block: {
+ type: "text_length",
+ id: "eTlfj{:i6DJu5BN/vVg1",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "7@j0@CRSi+MKlWZ_|ROw",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "definir_letra",
+ id: "@j.F-Z9lmpo2%cO(Ys|6",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_charAt",
+ id: "A[jcUgwTDWK:^PQJ7tFq",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "obter_entrada",
+ id: "6{SH7+tUsQLOe!VPLF!/",
+ },
+ },
+ AT: {
+ block: {
+ type: "obter_contador",
+ id: "*bZiLpZ1xc:9B,5yOX`t",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_posicao",
+ id: "gqAlo#!VG+a@1exAxYNv",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "text_indexOf",
+ id: "l#|#%A_p8gZQzN(.Cru/",
+ fields: { END: "FIRST" },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "alfabeto",
+ id: "mc~mNENUD3?!foqz7|0N",
+ },
+ },
+ FIND: {
+ block: {
+ type: "obter_letra",
+ id: ",lQ7V9.Q[RfeKd*%m+2`",
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "definir_soma",
+ id: "sQ@}NDv(,JKh,ID6ktSp",
+ next: {
+ block: {
+ type: "definir_contador",
+ id: "y#Sr0b4c6Kj68)UR;sFe",
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "),Y$/lx#/Phq*kTk}#Dc",
+ fields: { OP: "ADD" },
+ inputs: {
+ A: {
+ block: {
+ type: "obter_contador",
+ id: "Jx.g!u($I_Q{LxxzL[^[",
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "MoBAh!GKd?lx1_qwF*^?",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "entrada", id: "7K?EI04[`4bo[)xa(iyW" },
+ { name: "saida", id: "FMe5q:,O2QHlC4=zk=ln" },
+ { name: "pos", id: ")1-Jynyj,*CN7UZ6g%6%" },
+ ],
+ },
+};
diff --git a/app/src/atividades/programacao/cripto/config/tourSteps.js b/app/src/atividades/programacao/cripto/config/tourSteps.js
new file mode 100644
index 0000000..6d57d04
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/config/tourSteps.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.cripto.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const criptoTourSteps = [
+ createWelcomeStep({
+ gameName: "Jogo Cripto",
+ description:
+ "Bem-vindo ao mundo da criptografia! Aqui você vai aprender os fundamentos de segurança digital e como transformar informações.",
+ challenge:
+ "Use programação em blocos para converter letras em números e implementar algoritmos de criptografia!",
+ iconSvg: gameIcons.lock,
+ }),
+
+ createGameAreaStep({
+ title: "Monitor Criptográfico",
+ description:
+ "Nesta tela você verá três áreas: valores de ENTRADA e SAÍDA. Seus blocos transformarão a entrada em saída criptografada.",
+ }),
+
+ createToolboxStep({
+ description:
+ "Use os blocos disponíveis: definir entrada/saída, obter valores, concatenar texto, contador, loops e condicionais. Arraste-os para criar seu algoritmo.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte sua sequência lógica aqui. Encaixe os blocos na ordem correta para processar a entrada e gerar a saída esperada.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Execute seu código! Você verá as animações acontecendo passo a passo: cursor piscando na entrada e caracteres embaralhando na saída.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Se algo não funcionar como esperado, use o reset para limpar e tentar uma nova solução.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "O jogo tem várias fases com diferentes desafios de criptografia, desde conversão básica até algoritmos mais complexos.",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Acompanhe seu progresso e veja informações sobre a fase atual aqui.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Acesse este tour novamente clicando no botão de ajuda sempre que precisar de orientação.",
+ }),
+];
+
+export const criptoTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/cripto/game.js b/app/src/atividades/programacao/cripto/game.js
new file mode 100644
index 0000000..afcefc5
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/game.js
@@ -0,0 +1,293 @@
+/**
+ * @fileoverview Utility module for game.js
+ *
+ * @module games.cripto.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupCriptoAPI } from "./hooks/interpreterSetup.js";
+import { validationSolution } from "./validation/validators.js";
+import { gameConfig } from "./config/config.js";
+import { ConstantesJogo } from "./ui/constants.js";
+import { inicializarLayout } from "./ui/layout.js";
+import {
+ animarEntradaCaractere,
+ animarNovoCaractereSaida,
+} from "./ui/animations.js";
+import backgroundLoopSound from "./assets/background_loop.mp3";
+
+const CRIPTO_AUDIO = {
+ BACKGROUND_LOOP: "cripto_background_loop",
+};
+
+class CriptoScene extends BaseGameScene {
+ constructor() {
+ super("CriptoScene");
+
+ // Variáveis globais do jogo
+ this.entrada = "";
+ this.saida = "";
+ this.contador = 0;
+
+ // Textos visuais
+ this.textoEntrada = null;
+ this.textoSaida = null;
+
+ // Animação Matrix
+ this.colunasMatrix = null;
+ this.matrixEffect = null;
+
+ // Grid background animado
+ this.gridBackground = null;
+
+ // Monitor CRT
+ this.crtMonitor = null;
+ }
+
+ /**
+ * Inicializa a cena `CriptoScene` com dados opcionais.
+ * @param {Object} data - Dados passados pela inicialização da cena
+ * @returns {void}
+ */
+ init(data) {
+ super.init(data);
+ this.limparVariaveis();
+ }
+
+ /**
+ * Faz o preload dos recursos necessários para o jogo (áudio, imagens).
+ * Usa `preloadGlobalAssets` para recursos compartilhados.
+ * @returns {void}
+ */
+ preload() {
+ this.preloadGlobalAssets();
+ this.load.audio(CRIPTO_AUDIO.BACKGROUND_LOOP, backgroundLoopSound);
+ }
+
+ /**
+ * Cria elementos visuais da cena e configura o controlador padrão.
+ * É responsável por inicializar o layout, textos e efeitos visuais.
+ * @returns {void}
+ */
+ create() {
+ this.setupStandardController(
+ () => setupCriptoAPI(this, { animationSpeed: 100 }),
+ (historico) =>
+ validationSolution(historico, this.configFase, gameConfig, this),
+ );
+
+ // Inicializar layout visual
+ const layout = inicializarLayout(this);
+ this.textoEntrada = layout.textoEntrada;
+ this.textoSaida = layout.textoSaida;
+ this.colunasMatrix = layout.colunasMatrix;
+ this.matrixEffect = layout.matrixEffect;
+ this.gridBackground = layout.gridBackground;
+ this.crtMonitor = layout.crtMonitor;
+ }
+
+ /**
+ * Loop de atualização da cena, chamado pelo Phaser a cada frame.
+ * Atualiza efeitos visuais dependentes de `time`/`delta`.
+ * @param {number} time - Tempo atual do jogo
+ * @param {number} delta - Intervalo em ms desde o último frame
+ * @returns {void}
+ */
+ update(time, delta) {
+ if (this.gridBackground) {
+ this.gridBackground.update();
+ }
+
+ if (this.matrixEffect) {
+ this.matrixEffect.update(delta);
+ }
+ }
+
+ /**
+ * Preparações feitas imediatamente antes da execução do código do aluno.
+ * Reinicia histórico e variáveis, e inicia áudio de fundo.
+ * @returns {void}
+ */
+ onBeforeRun() {
+ this.historico = [];
+ this.limparVariaveis();
+ this.playAudio(CRIPTO_AUDIO.BACKGROUND_LOOP, { loop: true, volume: 0.5 });
+ }
+
+ /**
+ * Ação executada quando a cena é resetada manualmente pelo usuário.
+ * Deve restaurar estado visual e interromper áudios em execução.
+ * @returns {void}
+ */
+ onReset() {
+ this.limparVariaveis();
+ this.stopAudio(CRIPTO_AUDIO.BACKGROUND_LOOP);
+ }
+
+ /**
+ * Limpa variáveis de estado internas do jogo e reseta textos visuais.
+ * @returns {void}
+ */
+ limparVariaveis() {
+ this.entrada = "";
+ this.saida = "";
+ this.contador = 0;
+
+ if (this.textoEntrada) this.textoEntrada.setText("");
+ if (this.textoSaida) this.textoSaida.setText("");
+ }
+
+ /**
+ * Define o valor de entrada do jogo e registra no histórico.
+ * Retorna uma Promise que resolve quando animação (se houver) termina.
+ * @param {string} valor - Novo valor de entrada
+ * @returns {Promise|Promise}
+ */
+ definirEntrada(valor) {
+ this.entrada = String(valor || "");
+ this.historico.push({ tipo: "definir_entrada", valor: this.entrada });
+
+ if (this.textoEntrada) {
+ return animarEntradaCaractere(this, this.textoEntrada, this.entrada);
+ }
+
+ return Promise.resolve(this.entrada);
+ }
+
+ /**
+ * Atualiza a string de saída e registra ação no histórico.
+ * @param {string} valor - Novo conteúdo da saída
+ * @returns {Promise}
+ */
+ definirSaida(valor) {
+ this.saida = String(valor || "");
+ this.historico.push({ tipo: "definir_saida", valor: this.saida });
+
+ if (this.textoSaida) {
+ this.textoSaida.setText(this.saida);
+ }
+
+ return Promise.resolve();
+ }
+
+ /**
+ * Ajusta o contador interno do jogo.
+ * @param {number} valor - Valor numérico para o contador
+ * @returns {Promise}
+ */
+ definirContador(valor) {
+ this.contador = Number(valor) || 0;
+ this.historico.push({ tipo: "definir_contador", valor: this.contador });
+ return Promise.resolve();
+ }
+
+ /**
+ * Retorna o valor atual de entrada e registra a leitura no histórico.
+ * @returns {string}
+ */
+ obterEntrada() {
+ this.historico.push({ tipo: "obter_entrada", valor: this.entrada });
+ return this.entrada;
+ }
+
+ /**
+ * Retorna a string de saída atual e registra a leitura no histórico.
+ * @returns {string}
+ */
+ obterSaida() {
+ this.historico.push({ tipo: "obter_saida", valor: this.saida });
+ return this.saida;
+ }
+
+ /**
+ * Retorna o valor numérico do contador e registra a leitura.
+ * @returns {number}
+ */
+ obterContador() {
+ const valor = Number(this.contador);
+ this.historico.push({ tipo: "obter_contador", valor: valor });
+ return valor;
+ }
+
+ /**
+ * Concatena texto à saída atual, animando a transição quando aplicável.
+ * @param {string} valor - Valor a ser concatenado à saída
+ * @returns {Promise}
+ */
+ concatenarSaida(valor) {
+ const valorString =
+ valor !== null && valor !== undefined ? String(valor) : "";
+ const saidaAnterior = this.saida;
+ this.saida += valorString;
+ this.historico.push({ tipo: "concatenar_saida", valor: this.saida });
+
+ if (this.textoSaida && valorString) {
+ return animarNovoCaractereSaida(
+ this,
+ this.textoSaida,
+ saidaAnterior,
+ this.saida,
+ );
+ }
+
+ return Promise.resolve();
+ }
+
+ /**
+ * Chamado quando a validação sinaliza sucesso. Limpa áudios/efeitos.
+ * @returns {void}
+ */
+ onSuccess() {
+ this.stopAudio(CRIPTO_AUDIO.BACKGROUND_LOOP);
+ }
+
+ /**
+ * Chamado quando a validação sinaliza falha. Limpa áudios/efeitos.
+ * @returns {void}
+ */
+ onFailure() {
+ this.stopAudio(CRIPTO_AUDIO.BACKGROUND_LOOP);
+ }
+
+ /**
+ * Destaca um bloco no workspace do Blockly e pausa a execução visual.
+ * @param {string} id - Id do bloco a ser destacado
+ * @returns {void}
+ */
+ highlightBlock(id) {
+ if (this.workspace) this.workspace.highlightBlock(id);
+ this.highlightPause = true;
+ }
+
+
+}
+
+/**
+ * Cria a configuração Phaser para o jogo Cripto.
+ * Retorna o objeto de configuração usado por `new Phaser.Game(config)`
+ * @param {HTMLElement} elementoPai - Elemento DOM que conterá o canvas Phaser
+ * @param {Object} configFaseAtual - Configuração da fase atual
+ * @returns {Object} Configuração Phaser para inicializar a cena Cripto
+ */
+export const createGame = (elementoPai, configFaseAtual) => {
+ const scene = new CriptoScene();
+
+ return {
+ type: Phaser.AUTO,
+ width: ConstantesJogo.LARGURA_TELA,
+ height: ConstantesJogo.ALTURA_TELA,
+ backgroundColor: ConstantesJogo.COR_FUNDO,
+ parent: elementoPai,
+ scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
+ scene: scene,
+ callbacks: {
+ preBoot: function (game) {
+ game.registry.set("configFase", configFaseAtual);
+ game.registry.set("gameConfig", gameConfig);
+ },
+ },
+ };
+};
+
+
diff --git a/app/src/atividades/programacao/cripto/hooks/interpreterSetup.js b/app/src/atividades/programacao/cripto/hooks/interpreterSetup.js
new file mode 100644
index 0000000..c69632d
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/hooks/interpreterSetup.js
@@ -0,0 +1,84 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ *
+ * @module games.cripto.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Configura a API disponível ao interpretador para o jogo Cripto.
+ * Registra funções que chamam métodos da `scene` com wrappers do `ApiHelpers`.
+ * @param {Object} scene - Instância da cena Phaser (ex.: `CriptoScene`)
+ * @param {Object} [config] - Opções (ex.: `animationSpeed`)
+ * @returns {Function} Função que realiza o registro no `interpreter` e `globalScope`
+ */
+export const setupCriptoAPI = (scene, config = {}) => {
+ const animationDelay = config.animationSpeed || 500;
+
+ return (interpreter, globalScope) => {
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "definirEntrada",
+ ApiHelpers.createActionWrapper(scene, "definirEntrada", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "definirSaida",
+ ApiHelpers.createActionWrapper(scene, "definirSaida", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "definirContador",
+ ApiHelpers.createActionWrapper(scene, "definirContador", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "concatenarSaida",
+ ApiHelpers.createActionWrapper(scene, "concatenarSaida", animationDelay),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "obterContador",
+ ApiHelpers.createConditionWrapper(scene, "obterContador"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "obterEntrada",
+ ApiHelpers.createConditionWrapper(scene, "obterEntrada"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "obterSaida",
+ ApiHelpers.createConditionWrapper(scene, "obterSaida"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
diff --git a/app/src/atividades/programacao/cripto/hooks/useCriptoTour.js b/app/src/atividades/programacao/cripto/hooks/useCriptoTour.js
new file mode 100644
index 0000000..f9f8599
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/hooks/useCriptoTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useCriptoTour.js
+ *
+ * @module games.cripto.hooks.useCriptoTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { criptoTourSteps, criptoTourOptions } from "../config/tourSteps";
+
+export const useCriptoTour = () => {
+ /**
+ * Hook que retorna o controlador de tour para o jogo Cripto.
+ * Encapsula `useGameTour` com os passos e opções específicos.
+ * @returns {Object} API do tour (start, stop, etc.)
+ */
+ return useGameTour("cripto", criptoTourSteps, criptoTourOptions);
+};
diff --git a/app/src/atividades/programacao/cripto/ui/CRTMonitor.js b/app/src/atividades/programacao/cripto/ui/CRTMonitor.js
new file mode 100644
index 0000000..4a1bf3f
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/CRTMonitor.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Utility module for CRTMonitor.js
+ *
+ * @module games.cripto.ui.CRTMonitor
+ */
+
+export default class CRTMonitor {
+ constructor(scene, width, height) {
+ this.scene = scene;
+ this.width = width;
+ this.height = height;
+
+ this.container = scene.add.container(width / 2, height / 2);
+ this.contentLayer = scene.add.container(-width / 2, -height / 2);
+
+ this.init();
+ }
+
+ init() {
+ // 1. Fundo do Monitor (Verde Escuro)
+ const bg = this.scene.add.rectangle(
+ 0,
+ 0,
+ this.width,
+ this.height,
+ 0x001100,
+ );
+
+ // 2. Scanlines (Linhas horizontais)
+ this.createScanlinesTexture();
+ const scanlines = this.scene.add.image(0, 0, "scanlines-texture");
+ scanlines.setAlpha(0.3); // Opacidade das linhas
+
+ // 3. Vignette (Sombra nos cantos)
+ this.createVignetteTexture();
+ const vignette = this.scene.add.image(0, 0, "vignette-texture");
+ vignette.setAlpha(0.8);
+
+ // --- ORDEM DE MONTAGEM ---
+ this.container.add(bg);
+ this.container.add(this.contentLayer);
+ this.container.add(scanlines);
+ this.container.add(vignette);
+ }
+
+ addContent(gameObject) {
+ this.contentLayer.add(gameObject);
+ }
+
+ clearContent() {
+ this.contentLayer.removeAll(true);
+ }
+
+ createScanlinesTexture() {
+ if (this.scene.textures.exists("scanlines-texture")) return;
+
+ const graphics = this.scene.make.graphics();
+ graphics.fillStyle(0x000000);
+
+ for (let y = 0; y < this.height; y += 3) {
+ graphics.fillRect(0, y, this.width, 1);
+ }
+
+ graphics.generateTexture("scanlines-texture", this.width, this.height);
+ graphics.destroy();
+ }
+
+ createVignetteTexture() {
+ if (this.scene.textures.exists("vignette-texture")) return;
+
+ const canvas = this.scene.textures.createCanvas(
+ "vignette-texture",
+ this.width,
+ this.height,
+ );
+ const ctx = canvas.context;
+
+ const gradient = ctx.createRadialGradient(
+ this.width / 2,
+ this.height / 2,
+ this.width * 0.3, // Centro
+ this.width / 2,
+ this.height / 2,
+ this.width * 0.7, // Bordas
+ );
+
+ gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
+ gradient.addColorStop(1, "rgba(0, 0, 0, 0.8)"); // Borda escura
+
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, this.width, this.height);
+
+ canvas.refresh();
+ }
+
+ getContainer() {
+ return this.container;
+ }
+
+ getContentLayer() {
+ return this.contentLayer;
+ }
+}
diff --git a/app/src/atividades/programacao/cripto/ui/GridBackground.js b/app/src/atividades/programacao/cripto/ui/GridBackground.js
new file mode 100644
index 0000000..f6b75b9
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/GridBackground.js
@@ -0,0 +1,120 @@
+/**
+ * @fileoverview Utility module for GridBackground.js
+ *
+ * @module games.cripto.ui.GridBackground
+ */
+
+export default class GridBackground {
+ constructor(scene, container = null) {
+ this.scene = scene;
+ this.container = container;
+ this.graphics = scene.add.graphics();
+
+ if (container) container.add(this.graphics);
+
+ this.width = scene.scale.width;
+ this.height = scene.scale.height;
+ this.horizonY = this.height / 2;
+ this.centerX = this.width / 2;
+
+ this.gridColor = 0x33ff33;
+ this.gapSize = 50;
+ this.numVerticalLines = 20;
+ this.spreadBase = this.width * 4;
+ this.numHorizontalLines = 10;
+ this.lineAlpha = 0.1;
+
+ // Controle de animação
+ this.offset = 0;
+ this.speed = 1;
+ }
+
+ update() {
+ this.offset += this.speed;
+
+ // Resetar offset quando completar um ciclo
+ if (this.offset >= 100) {
+ this.offset = 0;
+ }
+
+ this.draw();
+ }
+
+ draw() {
+ this.graphics.clear();
+
+ const horizonY = this.horizonY;
+ const centerX = this.centerX;
+ const width = this.width;
+ const height = this.height;
+ const gapSize = this.gapSize;
+
+ this.graphics.lineStyle(2, this.gridColor, this.lineAlpha);
+
+ // 1. Linhas Verticais (estáticas)
+ for (
+ let i = -this.numVerticalLines / 2;
+ i <= this.numVerticalLines / 2;
+ i++
+ ) {
+ const xBase = centerX + (i / this.numVerticalLines) * this.spreadBase;
+ const totalDistanceY = height / 2;
+ const skipFactor = gapSize / totalDistanceY;
+ const xStart = centerX + (xBase - centerX) * skipFactor;
+
+ // Chão
+ this.graphics.beginPath();
+ this.graphics.moveTo(xStart, horizonY + gapSize);
+ this.graphics.lineTo(xBase, height);
+ this.graphics.strokePath();
+
+ // Teto
+ this.graphics.beginPath();
+ this.graphics.moveTo(xStart, horizonY - gapSize);
+ this.graphics.lineTo(xBase, 0);
+ this.graphics.strokePath();
+ }
+
+ // 2. Linhas Horizontais (animadas)
+ const numLines = this.numHorizontalLines + 1;
+ for (let i = 0; i <= numLines; i++) {
+ // Adicionar offset para criar movimento
+ const adjustedI = i - this.offset / 100;
+ if (adjustedI < 0) continue;
+
+ const t = adjustedI / this.numHorizontalLines;
+ if (t > 1) continue;
+
+ const perspectiveFactor = Math.pow(t, 2);
+ const drawingSpace = height / 2 - gapSize;
+ const relativeY = perspectiveFactor * drawingSpace;
+
+ // Chão
+ const yPosFloor = horizonY + gapSize + relativeY;
+ this.graphics.beginPath();
+ this.graphics.moveTo(0, yPosFloor);
+ this.graphics.lineTo(width, yPosFloor);
+ this.graphics.strokePath();
+
+ // Teto
+ const yPosCeiling = horizonY - gapSize - relativeY;
+ this.graphics.beginPath();
+ this.graphics.moveTo(0, yPosCeiling);
+ this.graphics.lineTo(width, yPosCeiling);
+ this.graphics.strokePath();
+ }
+
+ // 3. Gap (Horizonte)
+ this.graphics.lineStyle(2, this.gridColor, 0.3);
+
+ this.graphics.beginPath();
+ this.graphics.moveTo(0, horizonY - gapSize);
+ this.graphics.lineTo(width, horizonY - gapSize);
+ this.graphics.strokePath();
+
+ this.graphics.beginPath();
+ this.graphics.moveTo(0, horizonY + gapSize);
+ this.graphics.lineTo(width, horizonY + gapSize);
+ this.graphics.strokePath();
+ }
+}
diff --git a/app/src/atividades/programacao/cripto/ui/MatrixEffect.js b/app/src/atividades/programacao/cripto/ui/MatrixEffect.js
new file mode 100644
index 0000000..6c57b7c
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/MatrixEffect.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Utility module for MatrixEffect.js
+ *
+ * @module games.cripto.ui.MatrixEffect
+ */
+
+import { ConstantesAnimacao } from "./constants.js";
+
+export default class MatrixEffect {
+ constructor(scene, colunas) {
+ this.scene = scene;
+ this.colunas = colunas;
+
+ this.caracteres = ConstantesAnimacao.MATRIX.CARACTERES_HEX;
+ this.numCaracteres = ConstantesAnimacao.MATRIX.CARACTERES_POR_COLUNA;
+ this.intervalo = ConstantesAnimacao.MATRIX.INTERVALO_ATUALIZACAO;
+
+ // Controle de animação via update()
+ this.acumulador = 0;
+
+ // Gerar conteúdo inicial
+ this.atualizarColunas();
+ }
+
+ gerarCaractereAleatorio() {
+ return this.caracteres[Math.floor(Math.random() * this.caracteres.length)];
+ }
+
+ atualizarColuna(coluna) {
+ const textoColuna = [];
+ for (let i = 0; i < this.numCaracteres; i++) {
+ textoColuna.push(this.gerarCaractereAleatorio());
+ }
+ coluna.setText(textoColuna.join("\n"));
+ }
+
+ atualizarColunas() {
+ this.colunas.forEach((coluna) => this.atualizarColuna(coluna));
+ }
+
+ /**
+ * Método chamado a cada frame do Phaser
+ * @param {number} delta - Tempo decorrido desde o último frame (ms)
+ */
+ update(delta = 16) {
+ this.acumulador += delta;
+
+ // Atualizar apenas quando o intervalo for atingido
+ if (this.acumulador >= this.intervalo) {
+ this.atualizarColunas();
+ this.acumulador = 0;
+ }
+ }
+}
diff --git a/app/src/atividades/programacao/cripto/ui/animations.js b/app/src/atividades/programacao/cripto/ui/animations.js
new file mode 100644
index 0000000..65ded37
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/animations.js
@@ -0,0 +1,215 @@
+/**
+ * @fileoverview Utility module for animations.js
+ *
+ * @module games.cripto.ui.animations
+ */
+
+import { ConstantesAnimacao } from "./constants.js";
+
+export function iniciarAnimacaoMatrix(scene, colunas) {
+ const caracteres = ConstantesAnimacao.MATRIX.CARACTERES_HEX;
+ const numCaracteres = ConstantesAnimacao.MATRIX.CARACTERES_POR_COLUNA;
+ const intervalo = ConstantesAnimacao.MATRIX.INTERVALO_ATUALIZACAO;
+
+ const gerarCaractereAleatorio = () => {
+ return caracteres[Math.floor(Math.random() * caracteres.length)];
+ };
+
+ const atualizarColuna = (coluna) => {
+ if (!scene.isRunning) return;
+
+ const textoColuna = [];
+ for (let i = 0; i < numCaracteres; i++) {
+ textoColuna.push(gerarCaractereAleatorio());
+ }
+ coluna.setText(textoColuna.join("\n"));
+ };
+
+ colunas.forEach((coluna) => atualizarColuna(coluna));
+
+ const timer = scene.time.addEvent({
+ delay: intervalo,
+ callback: () => {
+ if (scene.isRunning) {
+ colunas.forEach((coluna) => atualizarColuna(coluna));
+ }
+ },
+ loop: true,
+ });
+
+ return timer;
+}
+
+export function pararAnimacaoMatrix(colunas, timer) {
+ if (timer) {
+ timer.remove();
+ }
+ colunas.forEach((coluna) => coluna.setText(""));
+}
+
+/**
+ * Anima a entrada de texto caractere por caractere com cursor piscando
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Phaser.GameObjects.Text} textoEntrada - O objeto de texto da entrada
+ * @param {string} textoFinal - O texto final a ser exibido
+ * @returns {Promise} Promise que resolve quando a animação termina
+ */
+export function animarEntradaCaractere(scene, textoEntrada, textoFinal) {
+ return new Promise((resolve) => {
+ const cursor = ConstantesAnimacao.CURSOR;
+ const velocidade = ConstantesAnimacao.ENTRADA.VELOCIDADE_DIGITACAO;
+ const intervaloPiscar = ConstantesAnimacao.ENTRADA.INTERVALO_PISCAR_CURSOR;
+
+ let posicaoAtual = 0;
+ let cursorVisivel = true;
+ let timerPiscar = null;
+
+ const piscarCursor = () => {
+ if (!scene.isRunning) return;
+
+ cursorVisivel = !cursorVisivel;
+ const textoAtual = textoFinal.substring(0, posicaoAtual);
+ textoEntrada.setText(textoAtual + (cursorVisivel ? cursor : ""));
+
+ timerPiscar = scene.time.delayedCall(intervaloPiscar, piscarCursor);
+ };
+
+ const digitarProximoCaractere = () => {
+ if (!scene.isRunning) {
+ if (timerPiscar) timerPiscar.remove();
+ textoEntrada.setText(textoFinal);
+ resolve(textoFinal);
+ return;
+ }
+
+ if (posicaoAtual >= textoFinal.length) {
+ if (timerPiscar) timerPiscar.remove();
+ textoEntrada.setText(textoFinal);
+ resolve(textoFinal);
+ return;
+ }
+
+ posicaoAtual++;
+ const textoAtual = textoFinal.substring(0, posicaoAtual);
+ textoEntrada.setText(textoAtual + cursor);
+ cursorVisivel = true;
+
+ scene.time.delayedCall(velocidade, digitarProximoCaractere);
+ };
+
+ textoEntrada.setText(cursor);
+ cursorVisivel = true;
+
+ timerPiscar = scene.time.delayedCall(intervaloPiscar, piscarCursor);
+
+ scene.time.delayedCall(velocidade, digitarProximoCaractere);
+ });
+}
+
+/**
+ * Anima a saída de texto completa com embaralhamento
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Phaser.GameObjects.Text} textoSaida - O objeto de texto da saída
+ * @param {string} textoFinal - O texto final a ser exibido
+ * @returns {Promise} Promise que resolve quando a animação termina
+ */
+export function animarSaidaCaractere(scene, textoSaida, textoFinal) {
+ return new Promise((resolve) => {
+ const caracteres = ConstantesAnimacao.CARACTERES_EMBARALHAMENTO;
+ const ultimaPosicao = textoFinal.length;
+ let posicaoAtual = 0;
+ const duracaoEmbaralhamento =
+ ConstantesAnimacao.SAIDA.DURACAO_EMBARALHAMENTO;
+ const repeticoesEmbaralhamento =
+ ConstantesAnimacao.SAIDA.REPETICOES_EMBARALHAMENTO;
+
+ const proximoCaractere = () => {
+ if (!scene.isRunning) {
+ textoSaida.setText(textoFinal);
+ resolve();
+ return;
+ }
+
+ if (posicaoAtual >= ultimaPosicao) {
+ resolve();
+ return;
+ }
+
+ let repeticao = 0;
+ const embaralhar = () => {
+ if (!scene.isRunning) {
+ textoSaida.setText(textoFinal);
+ resolve();
+ return;
+ }
+
+ if (repeticao < repeticoesEmbaralhamento) {
+ const charAleatorio =
+ caracteres[Math.floor(Math.random() * caracteres.length)];
+ const textoAtual =
+ textoFinal.substring(0, posicaoAtual) + charAleatorio;
+ textoSaida.setText(textoAtual);
+
+ repeticao++;
+ scene.time.delayedCall(duracaoEmbaralhamento, embaralhar);
+ } else {
+ posicaoAtual++;
+ textoSaida.setText(textoFinal.substring(0, posicaoAtual));
+ scene.time.delayedCall(
+ ConstantesAnimacao.SAIDA.PAUSA_ENTRE_CARACTERES,
+ proximoCaractere,
+ );
+ }
+ };
+
+ embaralhar();
+ };
+
+ proximoCaractere();
+ });
+}
+
+/**
+ * Anima apenas um novo caractere adicionado à saída (para concatenação)
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Phaser.GameObjects.Text} textoSaida - O objeto de texto da saída
+ * @param {string} textoAnterior - O texto antes da concatenação
+ * @param {string} textoFinal - O texto após a concatenação
+ * @returns {Promise} Promise que resolve quando a animação termina
+ */
+export function animarNovoCaractereSaida(
+ scene,
+ textoSaida,
+ textoAnterior,
+ textoFinal,
+) {
+ return new Promise((resolve) => {
+ const caracteres = ConstantesAnimacao.CARACTERES_EMBARALHAMENTO;
+ const duracaoEmbaralhamento =
+ ConstantesAnimacao.CONCATENACAO.DURACAO_EMBARALHAMENTO;
+ const repeticoesEmbaralhamento =
+ ConstantesAnimacao.CONCATENACAO.REPETICOES_EMBARALHAMENTO;
+ let repeticao = 0;
+
+ const embaralhar = () => {
+ if (!scene.isRunning) {
+ textoSaida.setText(textoFinal);
+ resolve();
+ return;
+ }
+
+ if (repeticao < repeticoesEmbaralhamento) {
+ const charAleatorio =
+ caracteres[Math.floor(Math.random() * caracteres.length)];
+ textoSaida.setText(textoAnterior + charAleatorio);
+ repeticao++;
+ scene.time.delayedCall(duracaoEmbaralhamento, embaralhar);
+ } else {
+ textoSaida.setText(textoFinal);
+ resolve();
+ }
+ };
+
+ embaralhar();
+ });
+}
diff --git a/app/src/atividades/programacao/cripto/ui/constants.js b/app/src/atividades/programacao/cripto/ui/constants.js
new file mode 100644
index 0000000..1f1d125
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/constants.js
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview Utility module for constants.js
+ *
+ * @module games.cripto.ui.constants
+ */
+
+export const ConstantesJogo = {
+ LARGURA_TELA: 800,
+ ALTURA_TELA: 600,
+ COR_FUNDO: "#242527",
+};
+
+export const ConstantesLayout = {
+ MARGEM_PERCENTUAL: 0.03,
+ LARGURA_QUADRO_ESQ_PERCENTUAL: 0.2,
+ COR_BORDA: 0x00ff00,
+ ESPESSURA_BORDA: 5,
+ RAIO_ARREDONDAMENTO: 12,
+ PADDING_TEXTO: 20,
+};
+
+export const ConstantesTexto = {
+ ENTRADA: {
+ TAMANHO_FONTE: "64px",
+ COR: "#00ff00",
+ ALINHAMENTO: "left",
+ PESO_FONTE: "bold",
+ },
+ SAIDA: {
+ TAMANHO_FONTE: "64px",
+ COR: "#ffff00",
+ ALINHAMENTO: "left",
+ PESO_FONTE: "bold",
+ },
+};
+
+export const ConstantesAnimacao = {
+ CARACTERES_EMBARALHAMENTO: String.fromCharCode(
+ ...Array.from({ length: 51 }, (_, i) => 128 + i),
+ ),
+ CURSOR: "▓",
+
+ ENTRADA: {
+ VELOCIDADE_DIGITACAO: 50,
+ INTERVALO_PISCAR_CURSOR: 400,
+ },
+
+ SAIDA: {
+ DURACAO_EMBARALHAMENTO: 20,
+ REPETICOES_EMBARALHAMENTO: 2,
+ PAUSA_ENTRE_CARACTERES: 15,
+ },
+
+ CONCATENACAO: {
+ DURACAO_EMBARALHAMENTO: 20,
+ REPETICOES_EMBARALHAMENTO: 2,
+ },
+
+ MATRIX: {
+ CARACTERES_HEX: "0123456789ABCDEF",
+ NUMERO_COLUNAS: 7,
+ TAMANHO_FONTE: "20px",
+ COR_TEXTO: "#00ff00",
+ INTERVALO_ATUALIZACAO: 250,
+ CARACTERES_POR_COLUNA: 21,
+ },
+};
diff --git a/app/src/atividades/programacao/cripto/ui/index.js b/app/src/atividades/programacao/cripto/ui/index.js
new file mode 100644
index 0000000..65d3b0d
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/index.js
@@ -0,0 +1,8 @@
+/**
+ * Módulo UI do jogo Cripto
+ * Exporta todas as funções e constantes relacionadas à interface visual
+ */
+
+export * from "./constants.js";
+export * from "./layout.js";
+export * from "./animations.js";
diff --git a/app/src/atividades/programacao/cripto/ui/layout.js b/app/src/atividades/programacao/cripto/ui/layout.js
new file mode 100644
index 0000000..43dddf0
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/ui/layout.js
@@ -0,0 +1,241 @@
+/**
+ * @fileoverview Utility module for layout.js
+ *
+ * @module games.cripto.ui.layout
+ */
+
+import {
+ ConstantesJogo,
+ ConstantesLayout,
+ ConstantesTexto,
+ ConstantesAnimacao,
+} from "./constants.js";
+import GridBackground from "./GridBackground.js";
+import CRTMonitor from "./CRTMonitor.js";
+import MatrixEffect from "./MatrixEffect.js";
+
+/**
+ * Calcula as dimensões dos quadros do layout
+ * @returns {Object} Objeto contendo as dimensões dos quadros esquerdo, superior direito e inferior direito
+ */
+export function calcularDimensoesLayout() {
+ const margemX =
+ ConstantesJogo.LARGURA_TELA * ConstantesLayout.MARGEM_PERCENTUAL;
+ const margemY =
+ ConstantesJogo.ALTURA_TELA * ConstantesLayout.MARGEM_PERCENTUAL;
+
+ // Quadro esquerdo
+ const quadroEsq = {
+ x: margemX,
+ y: margemY,
+ largura:
+ ConstantesJogo.LARGURA_TELA *
+ ConstantesLayout.LARGURA_QUADRO_ESQ_PERCENTUAL,
+ altura:
+ ConstantesJogo.ALTURA_TELA * (1 - 2 * ConstantesLayout.MARGEM_PERCENTUAL),
+ };
+
+ // Quadros direitos
+ const espacoEntreQuadros = margemY;
+ const xQuadrosDir = quadroEsq.x + quadroEsq.largura + margemX;
+ const larguraQuadrosDir = ConstantesJogo.LARGURA_TELA - xQuadrosDir - margemX;
+ const alturaQuadrosDir = (quadroEsq.altura - espacoEntreQuadros) / 2;
+
+ const quadroSupDir = {
+ x: xQuadrosDir,
+ y: margemY,
+ largura: larguraQuadrosDir,
+ altura: alturaQuadrosDir,
+ };
+
+ const quadroInfDir = {
+ x: xQuadrosDir,
+ y: quadroSupDir.y + quadroSupDir.altura + espacoEntreQuadros,
+ largura: larguraQuadrosDir,
+ altura: alturaQuadrosDir,
+ };
+
+ return {
+ quadroEsquerdo: quadroEsq,
+ quadroSuperiorDireito: quadroSupDir,
+ quadroInferiorDireito: quadroInfDir,
+ };
+}
+
+/**
+ * Desenha as bordas dos quadros do layout
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Object} dimensoes - Dimensões dos quadros retornadas por calcularDimensoesLayout()
+ * @param {Phaser.GameObjects.Container} container - Container opcional onde adicionar os gráficos
+ * @returns {Phaser.GameObjects.Graphics} O objeto gráfico criado
+ */
+export function desenharBordasQuadros(scene, dimensoes, container = null) {
+ const graphics = scene.add.graphics();
+ graphics.lineStyle(
+ ConstantesLayout.ESPESSURA_BORDA,
+ ConstantesLayout.COR_BORDA,
+ 1,
+ );
+
+ const raio = ConstantesLayout.RAIO_ARREDONDAMENTO;
+
+ // Quadro esquerdo
+ graphics.strokeRoundedRect(
+ dimensoes.quadroEsquerdo.x,
+ dimensoes.quadroEsquerdo.y,
+ dimensoes.quadroEsquerdo.largura,
+ dimensoes.quadroEsquerdo.altura,
+ raio,
+ );
+
+ // Quadro superior direito (Entrada)
+ graphics.strokeRoundedRect(
+ dimensoes.quadroSuperiorDireito.x,
+ dimensoes.quadroSuperiorDireito.y,
+ dimensoes.quadroSuperiorDireito.largura,
+ dimensoes.quadroSuperiorDireito.altura,
+ raio,
+ );
+
+ // Quadro inferior direito (Saída)
+ graphics.strokeRoundedRect(
+ dimensoes.quadroInferiorDireito.x,
+ dimensoes.quadroInferiorDireito.y,
+ dimensoes.quadroInferiorDireito.largura,
+ dimensoes.quadroInferiorDireito.altura,
+ raio,
+ );
+
+ if (container) {
+ container.add(graphics);
+ }
+
+ return graphics;
+}
+
+/**
+ * Cria os textos de entrada e saída dentro dos quadros
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Object} dimensoes - Dimensões dos quadros retornadas por calcularDimensoesLayout()
+ * @param {Phaser.GameObjects.Container} container - Container opcional onde adicionar os textos
+ * @returns {Object} Objeto contendo as referências aos textos de entrada e saída
+ */
+export function criarTextosVisuais(scene, dimensoes, container = null) {
+ const padding = ConstantesLayout.PADDING_TEXTO;
+
+ const textoEntrada = scene.add
+ .text(
+ dimensoes.quadroSuperiorDireito.x + padding,
+ dimensoes.quadroSuperiorDireito.y + padding,
+ "",
+ {
+ fontSize: ConstantesTexto.ENTRADA.TAMANHO_FONTE,
+ fontStyle: ConstantesTexto.ENTRADA.PESO_FONTE,
+ fill: ConstantesTexto.ENTRADA.COR,
+ align: ConstantesTexto.ENTRADA.ALINHAMENTO,
+ wordWrap: {
+ width: dimensoes.quadroSuperiorDireito.largura - padding * 2,
+ useAdvancedWrap: true,
+ },
+ },
+ )
+ .setOrigin(0, 0);
+
+ const textoSaida = scene.add
+ .text(
+ dimensoes.quadroInferiorDireito.x + padding,
+ dimensoes.quadroInferiorDireito.y + padding,
+ "",
+ {
+ fontSize: ConstantesTexto.SAIDA.TAMANHO_FONTE,
+ fontStyle: ConstantesTexto.SAIDA.PESO_FONTE,
+ fill: ConstantesTexto.SAIDA.COR,
+ align: ConstantesTexto.SAIDA.ALINHAMENTO,
+ wordWrap: {
+ width: dimensoes.quadroInferiorDireito.largura - padding * 2,
+ useAdvancedWrap: true,
+ },
+ },
+ )
+ .setOrigin(0, 0);
+
+ if (container) {
+ container.add([textoEntrada, textoSaida]);
+ }
+
+ return {
+ textoEntrada,
+ textoSaida,
+ };
+}
+
+/**
+ * Cria as colunas de texto para animação Matrix
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @param {Object} dimensoes - Dimensões dos quadros
+ * @param {Phaser.GameObjects.Container} container - Container opcional onde adicionar as colunas
+ * @returns {Array} Array de objetos de texto para as colunas
+ */
+export function criarColunasMatrix(scene, dimensoes, container = null) {
+ const padding = ConstantesLayout.PADDING_TEXTO + 5;
+ const numColunas = ConstantesAnimacao.MATRIX.NUMERO_COLUNAS;
+ const larguraDisponivel = dimensoes.quadroEsquerdo.largura - padding * 2;
+ const espacoEntreColunas = larguraDisponivel / numColunas;
+
+ const colunas = [];
+
+ for (let i = 0; i < numColunas; i++) {
+ const x = dimensoes.quadroEsquerdo.x + padding + i * espacoEntreColunas + 5;
+ const y = dimensoes.quadroEsquerdo.y + padding;
+
+ const coluna = scene.add
+ .text(x, y, "", {
+ fontSize: ConstantesAnimacao.MATRIX.TAMANHO_FONTE,
+ fill: ConstantesAnimacao.MATRIX.COR_TEXTO,
+ align: "center",
+ lineSpacing: 6,
+ })
+ .setOrigin(0.6, 0);
+
+ colunas.push(coluna);
+ }
+
+ if (container) {
+ container.add(colunas);
+ }
+
+ return colunas;
+}
+
+/**
+ * Inicializa o layout completo do jogo
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @returns {Object} Objeto contendo as referências aos textos criados
+ */
+export function inicializarLayout(scene) {
+ const crtMonitor = new CRTMonitor(
+ scene,
+ scene.scale.width,
+ scene.scale.height,
+ );
+ const contentLayer = crtMonitor.getContentLayer();
+
+ const gridBackground = new GridBackground(scene, contentLayer);
+ gridBackground.draw();
+
+ const dimensoes = calcularDimensoesLayout();
+ desenharBordasQuadros(scene, dimensoes, contentLayer);
+ const textos = criarTextosVisuais(scene, dimensoes, contentLayer);
+ const colunasMatrix = criarColunasMatrix(scene, dimensoes, contentLayer);
+
+ const matrixEffect = new MatrixEffect(scene, colunasMatrix);
+
+ return {
+ ...textos,
+ colunasMatrix,
+ gridBackground,
+ matrixEffect,
+ crtMonitor,
+ dimensoes,
+ };
+}
diff --git a/app/src/atividades/programacao/cripto/validation/validators.js b/app/src/atividades/programacao/cripto/validation/validators.js
new file mode 100644
index 0000000..07d5f94
--- /dev/null
+++ b/app/src/atividades/programacao/cripto/validation/validators.js
@@ -0,0 +1,252 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.cripto.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador do jogo Criptografia.
+ * Implementa validações por fase para verificar entrada/saída esperadas.
+ * @class CriptoValidator
+ * @extends BaseGameValidator
+ */
+export class CriptoValidator extends BaseGameValidator {
+ /**
+ * Dispara a validação específica da fase configurada.
+ * Delegates para `validateFase{n}` quando disponível.
+ * @param {Array} history - Histórico de comandos
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena (opcional)
+ * @returns {{success:boolean, reason?:string}}
+ */
+ validatePhase(history, config, gameConfig, sceneRef) {
+ const phaseMethod = this[`validateFase${config.id}`];
+ if (phaseMethod) {
+ return phaseMethod.call(this, history, config, gameConfig, sceneRef);
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 1: verifica entrada e saída esperadas.
+ * @param {Array} history
+ * @param {Object} config
+ * @param {Object} gameConfig
+ * @param {Object} sceneRef
+ * @returns {{success:boolean, reason?:string}}
+ */
+ validateFase1(history, config, gameConfig, sceneRef) {
+ const entradaEsperada = config.expectedInput;
+ const saidaEsperada = config.expectedOutput;
+
+ if (sceneRef.entrada !== entradaEsperada) {
+ const mensagemBase =
+ gameConfig?.mensagens?.entradaIncorreta ||
+ "Entrada incorreta. Você deve definir a ENTRADA como o alfabeto completo.";
+
+ return this.failure(
+ `${mensagemBase}\n\nEsperado: "${entradaEsperada}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== saidaEsperada) {
+ const mensagemBase =
+ gameConfig?.mensagens?.saidaIncorreta ||
+ "Saída incorreta. Cada letra deve ser convertida para sua posição numérica (A=0, B=1, C=2...). O CONTADOR já representa essa posição!";
+
+ return this.failure(
+ `${mensagemBase}\n\nEsperado: ${saidaEsperada}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 2: valida entrada/saída conforme esperado.
+ */
+ validateFase2(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Cada número deve ser convertido na letra correspondente do alfabeto.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 3: deslocamento de letras.
+ */
+ validateFase3(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Cada letra deve ser deslocada 3 posições para a frente (A→D, B→E, Z→C).\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 4: inversão de deslocamento.
+ */
+ validateFase4(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Cada letra deve voltar 3 posições (D→A, E→B, C→Z).\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 5: usa chave de deslocamento fornecida.
+ */
+ validateFase5(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Use a chave ${config.chave} para deslocar cada letra.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 6: descriptografia com chave.
+ */
+ validateFase6(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Volte ${config.chave} posições para descriptografar.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 7: substituições específicas (ex: A→4).
+ */
+ validateFase7(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Substitua A→4 e E→3, mantendo as outras letras.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 8: inverte a ordem das letras.
+ */
+ validateFase8(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Inverta a ordem das letras.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 9: usa alfabeto secreto para substituição.
+ */
+ validateFase9(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Use o alfabeto secreto para substituir cada letra.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Validação da Fase 10: soma posições das letras no alfabeto.
+ */
+ validateFase10(history, config, gameConfig, sceneRef) {
+ if (sceneRef.entrada !== config.expectedInput) {
+ return this.failure(
+ `Entrada incorreta.\n\nEsperado: "${config.expectedInput}"\nRecebido: "${sceneRef.entrada}"`,
+ );
+ }
+
+ if (sceneRef.saida !== config.expectedOutput) {
+ return this.failure(
+ `Saída incorreta. Some as posições de todas as letras no alfabeto.\n\nEsperado: ${config.expectedOutput}\nRecebido: ${sceneRef.saida}`,
+ );
+ }
+
+ return this.success();
+ }
+}
+
+/**
+ * Função exportada para validação de soluções do jogo Criptografia.
+ * @param {Array} history - Histórico de ações do usuário
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena (opcional)
+ * @returns {{success:boolean, reason?:string}}
+ */
+export function validationSolution(history, config, gameConfig, sceneRef) {
+ const validator = new CriptoValidator();
+ return validator.validatePhase(history, config, gameConfig, sceneRef);
+}
diff --git a/app/src/atividades/programacao/mole-mash/MoleMash.jsx b/app/src/atividades/programacao/mole-mash/MoleMash.jsx
new file mode 100644
index 0000000..b7d64aa
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/MoleMash.jsx
@@ -0,0 +1,85 @@
+/**
+ * @fileoverview React component for MoleMash.jsx
+ *
+ * @module games.mole-mash.MoleMash
+ */
+
+
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { registerBlocks, generateDynamicToolbox } from "./blocks/blocks";
+import {
+ GameStateProvider,
+ useGameState,
+} from "../../../contexts/GameStateContext";
+import { useMoleMashTour } from "./hooks/useMoleMashTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+/**
+ * Componente que monta o conteúdo do jogo Toupeira.
+ * Registra blocos e provê o editor Blockly.
+ * @returns {JSX.Element} Elemento do jogo com editor Blockly.
+ */
+/**
+ * Componente que monta o conteúdo do jogo Toupeira.
+ * Registra blocos e provê o editor Blockly.
+ * @returns {JSX.Element} Elemento do jogo com editor Blockly.
+ */
+function MoleMashContent() {
+ const { startTour } = useMoleMashTour();
+ const { setFailureMessage, isDebugMode } = useGameState();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(() => {
+
+
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Componente wrapper que provê `GameStateProvider` para o jogo Toupeira.
+ * @returns {JSX.Element} Componente principal do jogo.
+ */
+/**
+ * Componente wrapper que provê `GameStateProvider` para o jogo Toupeira.
+ * @returns {JSX.Element} Componente principal do jogo.
+ */
+export default function MoleMashGame() {
+ return (
+
+
+
+ );
+}
+
+MoleMashContent.propTypes = {};
+MoleMashGame.propTypes = {};
diff --git a/app/src/atividades/programacao/mole-mash/assets/background.png b/app/src/atividades/programacao/mole-mash/assets/background.png
new file mode 100644
index 0000000..6b6be17
Binary files /dev/null and b/app/src/atividades/programacao/mole-mash/assets/background.png differ
diff --git a/app/src/atividades/programacao/mole-mash/assets/ground_pop.mp3 b/app/src/atividades/programacao/mole-mash/assets/ground_pop.mp3
new file mode 100644
index 0000000..9b3dff9
Binary files /dev/null and b/app/src/atividades/programacao/mole-mash/assets/ground_pop.mp3 differ
diff --git a/app/src/atividades/programacao/mole-mash/assets/sprites.png b/app/src/atividades/programacao/mole-mash/assets/sprites.png
new file mode 100644
index 0000000..62c13f4
Binary files /dev/null and b/app/src/atividades/programacao/mole-mash/assets/sprites.png differ
diff --git a/app/src/atividades/programacao/mole-mash/blocks/blocks.js b/app/src/atividades/programacao/mole-mash/blocks/blocks.js
new file mode 100644
index 0000000..41da5d4
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/blocks/blocks.js
@@ -0,0 +1,291 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ *
+ * @module games.mole-mash.blocks.blocks
+ */
+
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import { javascriptGenerator } from "blockly/javascript";
+
+const HUE_CONDICIONAIS = "#5B80A5";
+const HUE_MOVIMENTO = "#8B4513";
+const HUE_LOGICA = "#59C059";
+const HUE_MATEMATICA = "#8BC34A";
+const HUE_VARIAVEIS = "#9C27B0";
+const HUE_REPETICAO = "#FF9800";
+const HUE_TEMPO = "#FF5722";
+
+/**
+ * Registra todos os blocos personalizados do MoleMash no Blockly.
+ * Executa definição de blocos e geradores.
+ * @returns {void}
+ */
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+const defineBlocks = () => {
+ Blockly.Blocks["mover_toupeira"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "mover para Linha %1 Coluna %2",
+ args0: [
+ { type: "input_value", name: "LINHA", check: "Number" },
+ { type: "input_value", name: "COLUNA", check: "Number" },
+ ],
+ inputsInline: true,
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_MOVIMENTO,
+ tooltip:
+ "Move a toupeira para uma posição específica (1 a 3) na matriz.",
+ });
+ },
+ };
+
+ Blockly.Blocks["aguardar"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "aguardar %1 segundos",
+ args0: [
+ {
+ type: "field_number",
+ name: "TEMPO",
+ value: 1,
+ min: 0,
+ max: 10,
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_TEMPO,
+ tooltip: "Pausa a execução antes do próximo movimento.",
+ });
+ },
+ };
+
+ Blockly.Blocks["repetir_sempre"] = {
+ init: function () {
+ this.appendDummyInput().appendField("repetir para sempre");
+ this.appendStatementInput("STACK").setCheck(null).appendField("faça");
+ this.setPreviousStatement(true, null);
+ this.setColour(HUE_REPETICAO);
+ this.setTooltip("Executa os blocos dentro dele em um ciclo infinito.");
+ },
+ };
+};
+
+/**
+ * Define os geradores JavaScript associados aos blocos do MoleMash.
+ * @private
+ * @returns {void}
+ */
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+
+ javascriptGenerator.forBlock["mover_toupeira"] = (block) => {
+ const linha =
+ javascriptGenerator.valueToCode(
+ block,
+ "LINHA",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "1";
+ const coluna =
+ javascriptGenerator.valueToCode(
+ block,
+ "COLUNA",
+ javascriptGenerator.ORDER_ATOMIC,
+ ) || "1";
+ return `moverToupeira(${linha}, ${coluna});\n`;
+ };
+
+ javascriptGenerator.forBlock["aguardar"] = (block) => {
+ const tempo = block.getFieldValue("TEMPO");
+ return `aguardar(${tempo * 1000});\n`;
+ };
+
+ javascriptGenerator.forBlock["repetir_sempre"] = (block) => {
+ const branch = javascriptGenerator.statementToCode(block, "STACK");
+ return `while (true) {\n${branch} await esperar(10); // Segurança para não travar\n}\n`;
+ };
+};
+
+/**
+ * Gera uma toolbox dinâmica filtrada pelos blocos permitidos para a fase.
+ * @param {string[]} [allowedBlocks=[]] - Lista de tipos de blocos permitidos.
+ * @returns {Object} Estrutura JSON compatível com a toolbox do Blockly.
+ */
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const blockDefinitions = {
+ // Movimento
+ mover_toupeira: {
+ kind: "block",
+ type: "mover_toupeira",
+ },
+ // Repetição
+ repetir_sempre: {
+ kind: "block",
+ type: "repetir_sempre",
+ },
+ controls_repeat_ext: {
+ kind: "block",
+ type: "controls_repeat_ext",
+ },
+ controls_whileUntil: {
+ kind: "block",
+ type: "controls_whileUntil",
+ },
+ // Tempo
+ aguardar: {
+ kind: "block",
+ type: "aguardar",
+ },
+ // Lógica
+ logic_compare: {
+ kind: "block",
+ type: "logic_compare",
+ },
+ logic_operation: {
+ kind: "block",
+ type: "logic_operation",
+ },
+ logic_negate: {
+ kind: "block",
+ type: "logic_negate",
+ },
+ logic_boolean: {
+ kind: "block",
+ type: "logic_boolean",
+ },
+ controls_if: {
+ kind: "block",
+ type: "controls_if",
+ },
+ controls_ifelse: {
+ kind: "block",
+ type: "controls_ifelse",
+ },
+ // Matemática
+ math_number: {
+ kind: "block",
+ type: "math_number",
+ },
+ math_arithmetic: {
+ kind: "block",
+ type: "math_arithmetic",
+ },
+ math_random_int: {
+ kind: "block",
+ type: "math_random_int",
+ inputs: {
+ FROM: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ TO: { shadow: { type: "math_number", fields: { NUM: 3 } } },
+ },
+ },
+ };
+
+ const toolboxContents = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: HUE_MOVIMENTO,
+ contents: [],
+ cssConfig: { container: "movimento" },
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: HUE_REPETICAO,
+ contents: [],
+ cssConfig: { container: "repeticao" },
+ },
+ {
+ kind: "category",
+ name: "Lógica",
+ colour: HUE_LOGICA,
+ contents: [],
+ cssConfig: { container: "logica" },
+ },
+ {
+ kind: "category",
+ name: "Condicionais",
+ colour: HUE_CONDICIONAIS,
+ contents: [],
+ cssConfig: { container: "condicionais" },
+ },
+ {
+ kind: "category",
+ name: "Matemática",
+ colour: HUE_MATEMATICA,
+ contents: [],
+ cssConfig: { container: "matematica" },
+ },
+ {
+ kind: "category",
+ name: "Tempo",
+ colour: HUE_TEMPO,
+ contents: [],
+ cssConfig: { container: "tempo" },
+ },
+ {
+ kind: "category",
+ name: "Variáveis",
+ colour: HUE_VARIAVEIS,
+ custom: "VARIABLE",
+ cssConfig: { container: "variaveis" },
+ },
+ ],
+ };
+
+ allowedBlocks.forEach((blockId) => {
+ const blockDef = blockDefinitions[blockId];
+
+ if (blockId === "mover_toupeira") {
+ toolboxContents.contents[0].contents.push(blockDef);
+ } else if (
+ ["repetir_sempre", "controls_repeat_ext", "controls_whileUntil"].includes(
+ blockId,
+ )
+ ) {
+ toolboxContents.contents[1].contents.push(blockDef);
+ } else if (
+ [
+ "logic_compare",
+ "logic_operation",
+ "logic_negate",
+ "logic_boolean",
+ ].includes(blockId)
+ ) {
+ toolboxContents.contents[2].contents.push(blockDef);
+ } else if (["controls_if", "controls_ifelse"].includes(blockId)) {
+ toolboxContents.contents[3].contents.push(blockDef);
+ } else if (
+ ["math_number", "math_random_int", "math_arithmetic"].includes(blockId)
+ ) {
+ toolboxContents.contents[4].contents.push(blockDef);
+ } else if (blockId === "aguardar") {
+ toolboxContents.contents[5].contents.push(blockDef);
+ }
+ });
+
+ const hasVariables =
+ allowedBlocks.includes("variables_set") ||
+ allowedBlocks.includes("variables_get") ||
+ allowedBlocks.includes("variables");
+
+ toolboxContents.contents = toolboxContents.contents.filter(
+ (category, index) => {
+ if (category.custom === "VARIABLE") {
+ return hasVariables;
+ }
+ return category.contents && category.contents.length > 0;
+ },
+ );
+
+ return toolboxContents;
+};
diff --git a/app/src/atividades/programacao/mole-mash/config/config.js b/app/src/atividades/programacao/mole-mash/config/config.js
new file mode 100644
index 0000000..e4c88bf
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/config/config.js
@@ -0,0 +1,299 @@
+/**
+ * @fileoverview Utility module for config.js
+ *
+ * @module games.mole-mash.config.config
+ */
+
+"use strict";
+
+export const gameConfig = {
+ gameId: "molemash",
+ gameName: "Toupeira",
+ type: "blocks",
+ icon: "🐹",
+ thumbnail: "/images/atividades/programacao/molemash-thumbnail.png",
+ descricao:
+ "Programe o comportamento de uma toupeira em uma matriz 3x3 usando lógica, variáveis e aleatoriedade.",
+ dificuldade: "Intermediário",
+ categoria: "Lógica",
+ tempoEstimado: "40-60 min",
+ conceitos: [
+ "Matrizes",
+ "Loops Aninhados",
+ "Aleatoriedade",
+ "Estado e Variáveis",
+ ],
+ route: "/atividades/programacao/molemash",
+ component: "MoleMashGame",
+ objectives: [
+ "Dominar o sistema de coordenadas 3x3",
+ "Utilizar loops para varredura de área",
+ "Implementar lógica de memória para evitar repetições",
+ "Criar um ciclo de jogo (game loop) infinito",
+ ],
+ metadata: { lastUpdated: "2026-01-02", version: "1.0.0" },
+
+ mensagens: {
+ semMovimento:
+ "O código rodou, mas nenhum comando de movimento foi executado. Verifique suas conexões.",
+ foraTabuleiro:
+ "A toupeira tentou saltar para fora do tabuleiro! Use coordenadas entre 1 e 3.",
+ faltamSaltos:
+ "A meta era de {requiredHits} saltos, mas a execução parou em {actualHits}. Verifique seu laço de repetição.",
+ erroEstrutura:
+ "Hmm, parece que falta algo importante no seu código. Revise a estrutura dos blocos e verifique se está usando os comandos necessários para completar o desafio.",
+ caminhoErrado:
+ "A toupeira não seguiu o caminho exato planejado. Verifique a ordem dos seus blocos de movimento.",
+ },
+
+ fases: [
+ {
+ id: 1,
+ showMatrix: true,
+ ignoreSequenceOrder: true,
+ nome: "Movimento Sequencial",
+ descricao:
+ "A toupeira deve aparecer na primeira linha, passando pelas colunas 1, 2 e 3.",
+ allowedBlocks: ["mover_toupeira", "math_number"],
+ timeout: 10,
+ /**
+ * Verifica se há chamadas moverToupeira com linha 1 e coluna numérica
+ */
+ validationRegex: /moverToupeira\s*\(\s*1\s*,\s*(\d+)\s*\)\s*;/g,
+ expectedSequence: [
+ { l: 1, c: 1 },
+ { l: 1, c: 2 },
+ { l: 1, c: 3 },
+ ],
+ },
+ {
+ id: 2,
+ showMatrix: true,
+ ignoreSequenceOrder: true,
+ nome: "Os Quatro Cantos",
+ descricao:
+ "Faça a toupeira saltar pelos cantos do tabuleiro: (1,1), (1,3), (3,3) e (3,1).",
+ allowedBlocks: ["mover_toupeira", "math_number"],
+ timeout: 10,
+ /**
+ * Verifica se há chamadas moverToupeira com ambos os parâmetros numéricos
+ */
+ validationRegex: /moverToupeira\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)\s*;/g,
+ expectedSequence: [
+ { l: 1, c: 1 },
+ { l: 1, c: 3 },
+ { l: 3, c: 3 },
+ { l: 3, c: 1 },
+ ],
+ },
+ {
+ id: 3,
+ showMatrix: true,
+ nome: "O Pêndulo Vertical",
+ descricao:
+ "Use um loop para fazer a toupeira alternar entre o centro (2,2) e o topo (1,2) 5 vezes.",
+ allowedBlocks: ["mover_toupeira", "math_number", "controls_repeat_ext"],
+ timeout: 15,
+ requiredHits: 10, // 5 vezes o par de movimentos
+ /**
+ * Garante que existe um loop (for/while) com duas chamadas moverToupeira dentro
+ */
+ validationRegex: /(for|while)[\s\S]*moverToupeira[\s\S]*moverToupeira/,
+ mensagens: {
+ saltoInsuficiente:
+ "Para esta análise, precisamos de exatamente 10 saltos registrados.",
+ saltoErrado:
+ "No salto {numero}, a toupeira deveria ter ido para o {local}. Verifique sua lógica de repetição.",
+ },
+ },
+ {
+ id: 4,
+ showMatrix: true,
+ nome: "Varredura Completa",
+ descricao:
+ "Use loops aninhados para percorrer todas as 9 casas do tabuleiro (Linhas 1 a 3 e Colunas 1 a 3).",
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_arithmetic",
+ "controls_repeat_ext",
+ "variables_get",
+ "variables_set",
+ ],
+ timeout: 20,
+ /**
+ * Verifica se há loops aninhados (um loop dentro de outro)
+ */
+ validationRegex: /(for|while).*\{.*(for|while).*\}/s,
+ mensagens: {
+ buracosFaltando:
+ "A toupeira visitou {visitados} buracos, mas para mapear o jardim completo, ela precisa passar por todos os 9!",
+ },
+ },
+ {
+ id: 5,
+ nome: "A Toupeira Inquieta",
+ descricao:
+ "A toupeira está agitada! Faça ela saltar 10 vezes para uma coluna aleatória (1-3), mas sempre permanecendo na Linha 2.",
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "controls_repeat_ext",
+ ],
+ timeout: 15,
+ /**
+ * Garante loop com moverToupeira(2, mathRandomInt(1,3)) - linha fixa, coluna aleatória
+ */
+ validationRegex:
+ /(for|while)[\s\S]*moverToupeira\s*\(\s*2\s*,\s*\(?mathRandomInt\s*\(\s*1\s*,\s*3\s*\)\)?\s*\)/,
+ requiredHits: 10,
+ mensagens: {
+ linhaErrada:
+ "Atenção: nesta fase a toupeira deve se mover apenas horizontalmente na Linha 2.",
+ semMovimento:
+ "A toupeira ficou parada! Certifique-se de usar o bloco de número aleatório para as colunas.",
+ },
+ },
+ {
+ id: 6,
+ nome: "Coordenadas na Memória",
+ descricao:
+ "A toupeira quer organização! Sorteie a linha (1-3) e a coluna (1-3), guarde cada valor em uma variável e só então use essas variáveis para mover a toupeira 20 vezes.",
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "controls_repeat_ext",
+ "variables_get",
+ "variables_set",
+ ],
+ timeout: 20,
+ /**
+ * O Regex agora é "intolerante":
+ * 1. Busca (\w+) = mathRandomInt(1, 3) -> Grupo 1
+ * 2. Busca (\w+) = mathRandomInt(1, 3) -> Grupo 2
+ * 3. Busca moverToupeira(\1, \2)
+ */
+ validationRegex:
+ /(for|while)[\s\S]*(\w+)\s*=\s*\(?mathRandomInt\s*\(\s*1\s*,\s*3\s*\)\)?[\s\S]*(\w+)\s*=\s*\(?mathRandomInt\s*\(\s*1\s*,\s*3\s*\)\)?[\s\S]*moverToupeira\s*\(\s*\2\s*,\s*\3\s*\)/,
+ requiredHits: 20,
+ },
+ {
+ id: 7,
+ nome: "O Centro Proibido",
+ descricao:
+ 'A toupeira jurou nunca mais pisar na Linha 2! Repita 20 vezes: sorteie um "número de sorte" entre 1 e 10 e guarde-o numa variável. Se o número for 5 ou menos, ela vai para a Linha 1. Senão (Else), ela vai para a Linha 3. A coluna é sempre aleatória.',
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "controls_repeat_ext",
+ "variables_get",
+ "variables_set",
+ "controls_if",
+ "controls_ifelse",
+ "logic_compare",
+ ],
+ timeout: 25,
+ /**
+ * O Regex garante:
+ * 1. Sorteio (1-10) guardado em variável (\w+)
+ * 2. O IF usa essa mesma variável (\1)
+ * 3. O ELSE está presente
+ * 4. moverToupeira é chamado
+ */
+ validationRegex:
+ /(\w+)\s*=\s*\(?mathRandomInt\s*\(\s*1\s*,\s*10\s*\)\)?[\s\S]*if\s*\(\s*\1\s*(<=|<)\s*(5|6)[\s\S]*else\s*\{[\s\S]*moverToupeira/,
+ requiredHits: 20,
+ },
+ {
+ id: 8,
+ nome: "Memória de Curto Prazo",
+ descricao:
+ 'A toupeira só consegue lembrar em qual LINHA ela estava. A coluna não importa agora! Sorteie uma nova linha: se for igual à "ultima_linha", a toupeira deve ir para a Linha 2 por segurança. Se for diferente, use a linha sorteada. A coluna deve ser sempre sorteada e não precisa de memória. Precisa executar 10 vezes.',
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "controls_repeat_ext",
+ "variables_get",
+ "variables_set",
+ "controls_ifelse",
+ "logic_compare",
+ ],
+ requiredHits: 10,
+ timeout: 40,
+ /**
+ * O Regex foca na variável da linha:
+ * 1. Sorteio da linha (\w+)
+ * 2. Comparação dessa variável com a de memória (\w+)
+ * 3. Atualização da memória no final do loop
+ */
+ validationRegex:
+ /(\w+)\s*=\s*\(?mathRandomInt[\s\S]*if\s*\(\s*\1\s*==\s*(\w+)\s*\)[\s\S]*moverToupeira[\s\S]*\2\s*=\s*\1/,
+ mensagens: {
+ repeticaoLinha:
+ "A IA detectou uma repetição de linha! Se a toupeira não estiver na linha de segurança (2), ela deve obrigatoriamente mudar de linha.",
+ },
+ },
+ {
+ id: 9,
+ nome: "O Algoritmo Perfeito",
+ descricao:
+ 'Mova a toupeira 30 vezes para posições aleatórias entre 1 e 3 sem repetir o buraco anterior: use o bloco "Enquanto" para sortear de novo se a posição for igual à "ultima_linha" e "ultima_coluna", e salve a nova posição na memória no final.',
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "controls_repeat_ext",
+ "controls_whileUntil",
+ "variables_get",
+ "variables_set",
+ "logic_compare",
+ "logic_operation",
+ ],
+ timeout: 40,
+ /**
+ * Verifica mathRandomInt(1,3), depois um while com comparação (==|!=) e operador lógico (&&||)
+ */
+ validationRegex:
+ /mathRandomInt\s*\(\s*1\s*,\s*3\s*\)[\s\S]*while\s*\([\s\S]*(==|!=)[\s\S]*(&&|\|\|)/,
+ },
+ {
+ id: 10,
+ nome: "O Desafio da IA Final",
+ descricao:
+ "Crie a sequência final da toupeira: use um loop para repetir 30 vezes sem repetir o buraco anterior, garantindo que se ela sair da Linha 2 o próximo salto seja para a Linha 1 ou 3, e use o bloco Aguardar entre os saltos.",
+ allowedBlocks: [
+ "mover_toupeira",
+ "math_number",
+ "math_random_int",
+ "variables_get",
+ "variables_set",
+ "controls_repeat_ext",
+ "logic_compare",
+ "logic_operation",
+ "controls_ifelse",
+ "aguardar",
+ ],
+ requiredHits: 30,
+ timeout: 60,
+ /**
+ * 1. (for|while) -> Procura o início do loop.
+ * 2. (?=.*if\s*\([\s\S]*==\s*2\)) -> (Lookahead) Garante que existe o IF da linha 2.
+ * 3. (?=.*moverToupeira) -> (Lookahead) Garante que existe o movimento.
+ * 4. (?=.*aguardar) -> (Lookahead) Garante que existe a pausa.
+ * 5. (?=.*ult_l\s*=\s*) -> (Lookahead) Garante que a memória é atualizada.
+ */
+ validationRegex:
+ /for[\s\S]*mathRandomInt\s*\(\s*1\s*,\s*3\s*\)[\s\S]*if\s*\(\s*ult_l\s*==\s*2\s*\)[\s\S]*moverToupeira[\s\S]*ult_l\s*=\s*l[\s\S]*aguardar/,
+ mensagens: {
+ mesmoLugar:
+ "Falha de imprevisibilidade: a toupeira não pode saltar no mesmo buraco duas vezes seguidas.",
+ linhaCentral:
+ "Falha de fuga: ao sair da linha central (2), a IA deve mover a toupeira para as extremidades (1 ou 3).",
+ },
+ },
+ ],
+};
diff --git a/app/src/atividades/programacao/mole-mash/config/debugSolutions.js b/app/src/atividades/programacao/mole-mash/config/debugSolutions.js
new file mode 100644
index 0000000..b0d5495
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/config/debugSolutions.js
@@ -0,0 +1,1597 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.mole-mash.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mover_toupeira",
+ id: "Ui0ZQXF@UG:s`k}1m!)}",
+ x: 38,
+ y: 38,
+ inputs: {
+ LINHA: { block: { type: "math_number", fields: { NUM: 1 } } },
+ COLUNA: { block: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ inputs: {
+ LINHA: { block: { type: "math_number", fields: { NUM: 1 } } },
+ COLUNA: { block: { type: "math_number", fields: { NUM: 2 } } },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ inputs: {
+ LINHA: {
+ block: { type: "math_number", fields: { NUM: 3 } },
+ },
+ COLUNA: {
+ block: { type: "math_number", fields: { NUM: 3 } },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mover_toupeira",
+ id: "Y?ess]*,1/Q/[|U]HLWb",
+ x: 38,
+ y: 38,
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "2O^g[G{Jn,S@48{$:^Z`",
+ fields: { NUM: 1 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: ",lYDa.$ul+dod;x^H/l(",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "0p-gfa?/Ou@d:+|0]u8T",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "yA{+8_+=8ob}b8:VSIb=",
+ fields: { NUM: 1 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: "3czfXk!DM*f0!baMf|+V",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "BSzqYQbCvSHYhpE;I.#d",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "KVCfkXjsg`{N8)@Cb=WZ",
+ fields: { NUM: 3 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: "N9#eC^*R#yph|]fDF1K%",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "i?OJiG2Il_I@@8I/k(q^",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "G9Zdyl4n]VzX5|Lbby%S",
+ fields: { NUM: 3 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: "CSM73?!Rp2}.kG9TV(Gc",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "tRH*}8sPr3J_+Zu@4xWt",
+ x: 13,
+ y: 38,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "0DM?!PFkGHB@jQ=TRA;~",
+ fields: { NUM: 5 },
+ },
+ },
+ DO: {
+ block: {
+ type: "mover_toupeira",
+ id: "*]?4}W3-wTk!3dtSmX4}",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "YJYH}nug[egJV1ti$`?2",
+ fields: { NUM: 2 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: "spY]mIXBi@m,?1zo)UmH",
+ fields: { NUM: 2 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: ",mzDSElR2$]%67UQ)0|=",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "Z?NtAGZRPQlN!j8.bOQf",
+ fields: { NUM: 1 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_number",
+ id: "(__?nLOl/A1rE#Nlkg3#",
+ fields: { NUM: 2 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "variables_set",
+ id: "VkbAod4o@^DJvU7ezMa|",
+ x: 38,
+ y: 13,
+ fields: { VAR: { id: "IcFS@B1;bpM5{gp4Cr@S" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "iUAiNG:6tScN8B=-yVmz",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_repeat_ext",
+ id: "DF7HU`$$zxOtCxW%/}mz",
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "qDm6v0V4R/)4G2rkrjcV",
+ fields: { NUM: 3 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "_G(.p-B9XnBD#*xNh72G",
+ fields: { VAR: { id: "[U$S1BQ?$N=v(d7iq@%a" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "9`+$+MzqnQsA~]:qXhZO",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_repeat_ext",
+ id: ",dIS/^~h[T*ZZGmnUxpn",
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "FZdm3C4(7e18%J(HMQZ3",
+ fields: { NUM: 3 },
+ },
+ },
+ DO: {
+ block: {
+ type: "mover_toupeira",
+ id: "0PXn[fs1N;kr?8,@}PgY",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "variables_get",
+ id: ":se)UXQP;SEM?H[qT~^V",
+ fields: {
+ VAR: { id: "IcFS@B1;bpM5{gp4Cr@S" },
+ },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: ".f[SL:|!jmqKXMa}p#o,",
+ fields: {
+ VAR: { id: "[U$S1BQ?$N=v(d7iq@%a" },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "rQz_d}a:{bz1]6sr-vwl",
+ fields: {
+ VAR: { id: "[U$S1BQ?$N=v(d7iq@%a" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "z@kR]@vEwvRM_^MZ//VO",
+ fields: { OP: "MINUS" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "365OHqhnFKzueB:g*{N1",
+ fields: {
+ VAR: {
+ id: "[U$S1BQ?$N=v(d7iq@%a",
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "d+O-2ZlUs=x8K-S[aXaR",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "math_change",
+ id: "XV(,)y45)DVP@HN?1x}?",
+ fields: { VAR: { id: "IcFS@B1;bpM5{gp4Cr@S" } },
+ inputs: {
+ DELTA: {
+ shadow: {
+ type: "math_number",
+ id: ")jcsux;Y/W@]Z[:;5Z@:",
+ fields: { NUM: -1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "linha", id: "IcFS@B1;bpM5{gp4Cr@S" },
+ { name: "coluna", id: "[U$S1BQ?$N=v(d7iq@%a" },
+ ],
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "Y[gaYM#L,7W7)1AJM_]I",
+ x: 13,
+ y: 38,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "BMm.~Cq299-T6JqVU`Rb",
+ fields: { NUM: 10 },
+ },
+ },
+ DO: {
+ block: {
+ type: "mover_toupeira",
+ id: "dr4.1X[DZ-6i=2f5t=(i",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "xx*fJekYx(e,YXVSfB#z",
+ fields: { NUM: 2 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "math_random_int",
+ id: "*S[6T;7s=6!H%A|4knN?",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "Q`MM#qTT!qezKdPt26nL",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "DOuMGN#Z9:R~ehthSIx}",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 6: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "+z.EcwjVc~6..p_),$!Q",
+ x: 30,
+ y: 81,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "k$%+$K=?szHnmO!Yi/4K",
+ fields: { NUM: 20 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "qS0CUsDS95j,yJn}h)a~",
+ fields: { VAR: { id: "(uzXP5CQZDN`M2#dO9b)" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "1PI{pTAN;;f!`b^hWvxK",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "a;nZyf[OMY`{g[Lq30/,",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "Y4TXxC).g*ubS2*fJ4Iy",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "QoH4=hnS9PW!kBzTZCs0",
+ fields: { VAR: { id: "%8qjB7?W?hpOu_AWV#1o" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "Z9BuvMmM28u,nb7T{{Y?",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "3T(?/%tiYp-hcsvxD=pK",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "YQejfRt}q9QSG3=K)yb=",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "oG9e_kS9gT*3~}h*6^]#",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "variables_get",
+ id: "-8Q[gu.p[HIRi^C);4WQ",
+ fields: { VAR: { id: "(uzXP5CQZDN`M2#dO9b)" } },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "FkY@i9h#pJ?!q}/:A`Ms",
+ fields: { VAR: { id: "%8qjB7?W?hpOu_AWV#1o" } },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "linha", id: "(uzXP5CQZDN`M2#dO9b)" },
+ { name: "coluna", id: "%8qjB7?W?hpOu_AWV#1o" },
+ ],
+ },
+ 7: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "WFv_!!-xGxH*QGge3Pb9",
+ x: 94,
+ y: 77,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "f^2t?)@j~/7u~*ISW.0V",
+ fields: { NUM: 20 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "^xVmId,}%fRDpS%O}{dR",
+ fields: { VAR: { id: "FSZFx}iq6px{q/rycc6p" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "+cK)gV36M]cpNl_Hm(#7",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "dtSex49sV2V,C8tGyYXy",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "?^t{?yZz-2ROS%x3ea~L",
+ fields: { NUM: 10 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "JrZSBD,U-^|sD_wL.8a}",
+ fields: { VAR: { id: "H3U_($}.52WR[J%FsBhA" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "m@=zY4r@z2u#N6avhQb_",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "D/lh,)HTr*CvPWyr$V0G",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "8Yore.s_$jV=BdOeA$:W",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_ifelse",
+ id: "+N=d`%|UMP~=Ndm(-991",
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "I-sfs7!QS6VwkO|4K=cn",
+ fields: { OP: "LTE" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: ":iW9^kWC+)ll|^;)dD?n",
+ fields: {
+ VAR: { id: "FSZFx}iq6px{q/rycc6p" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "?L]C9I8uj^V/0nna:XyR",
+ fields: { NUM: 5 },
+ },
+ },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "mover_toupeira",
+ id: "X9TK4hZzlQjCfG@568[H",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "_iT@=vocF,E=,`!i5|;X",
+ fields: { NUM: 1 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: ",VM4@wg-WTV88`BlO?-H",
+ fields: {
+ VAR: { id: "H3U_($}.52WR[J%FsBhA" },
+ },
+ },
+ },
+ },
+ },
+ },
+ ELSE: {
+ block: {
+ type: "mover_toupeira",
+ id: "c#RohO|K;#O,!|P%u-)E",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: ";Fp;1Ugr1,1rKC|91ks$",
+ fields: { NUM: 3 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "ms!gBppDHL:yA_d!b?5/",
+ fields: {
+ VAR: { id: "H3U_($}.52WR[J%FsBhA" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "x", id: "FSZFx}iq6px{q/rycc6p" },
+ { name: "y", id: "H3U_($}.52WR[J%FsBhA" },
+ ],
+ },
+ 8: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "sT1O+y_gL]7586rz5`-x",
+ x: 41,
+ y: 37,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "w1=[;V6UIvE%b*V+vpI}",
+ fields: { NUM: 20 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "}B4=P=Gy,Xn1G*~aq05n",
+ fields: { VAR: { id: "~,OF,d;k6N])%gb]=hKZ" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "{mJhJ_tLausz*oT-T2;B",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "=h{VH89B7tPYitA4MaQ1",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "mJ0KwIW`qQP;jUzj=]!Q",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "`I[:`cioWlD=[R6v`ni3",
+ fields: { VAR: { id: "?hePy2dE0g/iq!)(6-*5" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "`im%s.M|m^0G4c+}/54I",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "tSh2?g68Z$Hm.[h_gK+3",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "b!fx^qmGXl*!.JR*_E+o",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_ifelse",
+ id: "A:NDZoVx_*{qpunq!tL~",
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "lS44Rw;2Y6++bBmM(m8`",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "Qv@D@Z)_|If~]qx-z_M3",
+ fields: {
+ VAR: { id: "~,OF,d;k6N])%gb]=hKZ" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "f0OEI*U2xkwhh}.FPcgk",
+ fields: {
+ VAR: { id: "!4)yP=@lyVf*bihkIHK!" },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "mover_toupeira",
+ id: "NY~?lI[e1eXL1ZLi]HTY",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "math_number",
+ id: "Ze#@;miyL$oT40jucIRZ",
+ fields: { NUM: 2 },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "_@LR[PbkG9Q=0x].)~nC",
+ fields: {
+ VAR: { id: "?hePy2dE0g/iq!)(6-*5" },
+ },
+ },
+ },
+ },
+ },
+ },
+ ELSE: {
+ block: {
+ type: "mover_toupeira",
+ id: "`cB)?#;x.%ae*iTkO|S!",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "variables_get",
+ id: "~)?n.JKM8q1TeT-81%kA",
+ fields: {
+ VAR: { id: "~,OF,d;k6N])%gb]=hKZ" },
+ },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "=5-wSs-X=nt6k/~^g4.w",
+ fields: {
+ VAR: { id: "?hePy2dE0g/iq!)(6-*5" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "%H}C[mU=pt(SG}ywbCzB",
+ fields: { VAR: { id: "!4)yP=@lyVf*bihkIHK!" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "variables_get",
+ id: "Q(+ty6fbV2tq*Hk/B;vi",
+ fields: {
+ VAR: { id: "~,OF,d;k6N])%gb]=hKZ" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "linha_atual", id: "~,OF,d;k6N])%gb]=hKZ" },
+ { name: "coluna", id: "?hePy2dE0g/iq!)(6-*5" },
+ { name: "ultima_linha", id: "!4)yP=@lyVf*bihkIHK!" },
+ ],
+ },
+ 9: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "?32.}I/68h6NBD)=dCfw",
+ x: 25,
+ y: -318,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "Fz`$hA`iE5T!1nL{jC-U",
+ fields: { NUM: 30 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "pN`J((L@tZY0iUmL0Tm]",
+ fields: { VAR: { id: "LgaE3kgK$+90Tx;djnBj" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "9^:`6_`,253i~GLc{pqn",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "yGP/5F@(4{X!+m]--vmd",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "37_-@v`a]UjiTt3%hYjh",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "3SN3*DID$JlY#ok1tVsm",
+ fields: { VAR: { id: "I5#.Yzs)EL~s#;Zr*lnb" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "^_c#UKs3b8C1R~)YFW`h",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "l*@Hc0YnVfN1:mMVZ[`V",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "#^0rpcNx+wFJ1~%[5}M#",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "?K8MlZ!j?@Ti_2J-p]q_",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_operation",
+ id: "+f~T2#8~3cG%N+1h9+M*",
+ fields: { OP: "AND" },
+ inputs: {
+ A: {
+ block: {
+ type: "logic_compare",
+ id: ".RHz]Es=BTnB]{ee6nM7",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "K(Xb0vf%_l~-4S)a$h=-",
+ fields: {
+ VAR: { id: "LgaE3kgK$+90Tx;djnBj" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "1#^MOL@{^~Qn=cM]|g+%",
+ fields: {
+ VAR: { id: "8sl+49(HWI`k|Hj!VdBa" },
+ },
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "logic_compare",
+ id: "Y4A2Ly*e:~uQ$S`aHZ0t",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "%8.YT#0!+!;~%m~r(nc0",
+ fields: {
+ VAR: { id: "I5#.Yzs)EL~s#;Zr*lnb" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "dYV,*eVOIq$v:sWHVpFy",
+ fields: {
+ VAR: { id: "QpFz72Lvp`Zu?Q2jrxE9" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: ":xw3+}fy/)4:.e?+Q1o6",
+ fields: { VAR: { id: "LgaE3kgK$+90Tx;djnBj" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "9%z79c5|?Fj+$E;y=UEs",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "%{vT(:lE8f|1E/q@Gh1E",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "fIBokNf%]ZSBOsGQ6VtI",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "V|j)vUT%cGwwTz]r-b,%",
+ fields: {
+ VAR: { id: "I5#.Yzs)EL~s#;Zr*lnb" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "ZsT~6^@0.K}L1f`qEl@@",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "_^9OmRyFmUc[t/l1$ii5",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "y+m|Z%[dEf}S{o3/(Bes",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "Eks.)pyS@7S;}?=gR|=[",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "variables_get",
+ id: "x9cA^Qan5x7/Y(fUYs_#",
+ fields: {
+ VAR: { id: "LgaE3kgK$+90Tx;djnBj" },
+ },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "YfUV49dK?UB/gN]F~qg{",
+ fields: {
+ VAR: { id: "I5#.Yzs)EL~s#;Zr*lnb" },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "by+0:GWGn}CM/4G83+!6",
+ fields: { VAR: { id: "8sl+49(HWI`k|Hj!VdBa" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "variables_get",
+ id: "H0seiGvVABQ{{w4C%]eY",
+ fields: {
+ VAR: { id: "LgaE3kgK$+90Tx;djnBj" },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "5b/~-*H;mUR~*Hi#FnK5",
+ fields: {
+ VAR: { id: "QpFz72Lvp`Zu?Q2jrxE9" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "variables_get",
+ id: "nS$+en$ARJ5J)!2!M2j,",
+ fields: {
+ VAR: { id: "I5#.Yzs)EL~s#;Zr*lnb" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "linha", id: "LgaE3kgK$+90Tx;djnBj" },
+ { name: "coluna", id: "I5#.Yzs)EL~s#;Zr*lnb" },
+ { name: "ultima_linha", id: "8sl+49(HWI`k|Hj!VdBa" },
+ { name: "ultima_coluna", id: "QpFz72Lvp`Zu?Q2jrxE9" },
+ ],
+ },
+ 10: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_repeat_ext",
+ id: "jq#M-?|lNokGz%jQ^zJ(",
+ x: 13,
+ y: 13,
+ inputs: {
+ TIMES: {
+ block: {
+ type: "math_number",
+ id: "wcuMMh*Q*`q,Ju/wD-N(",
+ fields: { NUM: 30 },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "sNqN%(a2/[Bkt^SlE5)_",
+ fields: { VAR: { id: "=]/jiP^l[T6XzaDl,+*_" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "le$53Qdm{fL-ADToDuIA",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "TU=ScXNb`i(MGNG-MDw5",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "+2./WE{gy0mND+Nf2,R#",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "nA|2]Gp;-X~4Cez+xo5u",
+ fields: { VAR: { id: "#w)4;Qs-Wi!M=KhFo+Bd" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "s1x~5.O@amg!ySP^x^s=",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "#q0/b0rMng!~u0VZ*W|c",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "lmg7Nt3N%nUOF/u-9o#?",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_whileUntil",
+ id: "YRtMg!QJNV)mUIzott[|",
+ fields: { MODE: "WHILE" },
+ inputs: {
+ BOOL: {
+ block: {
+ type: "logic_operation",
+ id: "S}IUWFK/).Leqm{H6Fb,",
+ fields: { OP: "AND" },
+ inputs: {
+ A: {
+ block: {
+ type: "logic_compare",
+ id: "]o!~O;`xiv%FMI]cQO)F",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "1a{dQ;4b,RWvcru|x#!?",
+ fields: {
+ VAR: { id: "=]/jiP^l[T6XzaDl,+*_" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "edcOfDiaZ:/.g`(6ti|*",
+ fields: {
+ VAR: { id: "K=.B[-AT2?V+r4UO?ATF" },
+ },
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "logic_compare",
+ id: "dDxGbr6YoeSelpXCttwm",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "#l#alwy;D+7v1CJkzcv{",
+ fields: {
+ VAR: { id: "#w)4;Qs-Wi!M=KhFo+Bd" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "Yw~:ZYrslkcU$ASIVi4_",
+ fields: {
+ VAR: { id: "i-KE/k]DEL,8Tj0oxC3i" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ DO: {
+ block: {
+ type: "variables_set",
+ id: "%P)anuoInQ[*:H~#zuRr",
+ fields: { VAR: { id: "=]/jiP^l[T6XzaDl,+*_" } },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "|MXP$kf[AZYm|#+$6pRJ",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "%P;Ra6:y2Ee}yP]ueHgp",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "bY[p*NMTs*C5LGYb`EIb",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "oY]EwRs#doJWY/E#/W~_",
+ fields: {
+ VAR: { id: "#w)4;Qs-Wi!M=KhFo+Bd" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_random_int",
+ id: "GuT9q|Eo8l3,6hjLiVq#",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "Mw%^FKr`t}/S3+-,YJ]^",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "|Q|ijxC~-Uva(p1.?yMk",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_if",
+ id: "`vO9w8|?#*,x}^-NpG5z",
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "iOhGI1p0vsGCOk)pVG?L",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "il+$f/}Z[D_4Lv|/{iyp",
+ fields: {
+ VAR: { id: "K=.B[-AT2?V+r4UO?ATF" },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: ".Gh#kkN~huOh8JnUx5RW",
+ fields: { NUM: 2 },
+ },
+ },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "controls_ifelse",
+ id: "z(][;=$E[(EF_7cr8Es!",
+ inputs: {
+ IF0: {
+ block: {
+ type: "logic_compare",
+ id: "JGDTd0E!9#+svQ7t:|[J",
+ fields: { OP: "EQ" },
+ inputs: {
+ A: {
+ block: {
+ type: "math_random_int",
+ id: "yU0Ib2PU2E;P2HD~AQ0c",
+ inputs: {
+ FROM: {
+ shadow: {
+ type: "math_number",
+ id: "FD)%ji#1qG,~n@XFjbr:",
+ fields: { NUM: 1 },
+ },
+ },
+ TO: {
+ shadow: {
+ type: "math_number",
+ id: "Z3*#J-l]]5U6Qs|dTy`E",
+ fields: { NUM: 2 },
+ },
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "math_number",
+ id: "%j63oQN6?S3:=RFHnJ^{",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "variables_set",
+ id: "rryqbtg$%}5qtjs_4f``",
+ fields: {
+ VAR: { id: "=]/jiP^l[T6XzaDl,+*_" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "CZ_uX*euqoER6A%H{!47",
+ fields: { NUM: 1 },
+ },
+ },
+ },
+ },
+ },
+ ELSE: {
+ block: {
+ type: "variables_set",
+ id: "TyfoIY.-}V*M;Pw`6uMc",
+ fields: {
+ VAR: { id: "=]/jiP^l[T6XzaDl,+*_" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "NSj{2_!(;3=O=h.et$*h",
+ fields: { NUM: 3 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "mover_toupeira",
+ id: "aPgVuNe4iCNyKOxkR?Dh",
+ inputs: {
+ LINHA: {
+ block: {
+ type: "variables_get",
+ id: "^zL2QaAY^Q?]w5n$bv5Y",
+ fields: {
+ VAR: { id: "=]/jiP^l[T6XzaDl,+*_" },
+ },
+ },
+ },
+ COLUNA: {
+ block: {
+ type: "variables_get",
+ id: "rwk1fxZL`;)PfI;w8zpn",
+ fields: {
+ VAR: { id: "#w)4;Qs-Wi!M=KhFo+Bd" },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "DLO*v|N)xB7+E~$wT5u?",
+ fields: {
+ VAR: { id: "K=.B[-AT2?V+r4UO?ATF" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "variables_get",
+ id: "PoO[0~sMI^@`,#0U|Y#f",
+ fields: {
+ VAR: { id: "=]/jiP^l[T6XzaDl,+*_" },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "a)-FlD@v-$izkS[r0H^L",
+ fields: {
+ VAR: { id: "i-KE/k]DEL,8Tj0oxC3i" },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "variables_get",
+ id: "q`v5a[6~66J:=5~E?ZQi",
+ fields: {
+ VAR: {
+ id: "#w)4;Qs-Wi!M=KhFo+Bd",
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "aguardar",
+ id: "T/93(_}Z!@zy,8Mye,;{",
+ fields: { TEMPO: 0.5 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ { name: "l", id: "=]/jiP^l[T6XzaDl,+*_" },
+ { name: "c", id: "#w)4;Qs-Wi!M=KhFo+Bd" },
+ { name: "ult_l", id: "K=.B[-AT2?V+r4UO?ATF" },
+ { name: "ult_c", id: "i-KE/k]DEL,8Tj0oxC3i" },
+ ],
+ },
+};
diff --git a/app/src/atividades/programacao/mole-mash/config/matrixConfig.js b/app/src/atividades/programacao/mole-mash/config/matrixConfig.js
new file mode 100644
index 0000000..9947735
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/config/matrixConfig.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview Configuração visual da matriz auxiliar de coordenadas do MoleMash.
+ *
+ * Este arquivo centraliza todos os parâmetros visuais da grade 3×3 exibida
+ * nas fases introdutórias para orientar o aluno no sistema de coordenadas.
+ *
+ * A exibição é ativada fase a fase através da propriedade `showMatrix: true`
+ * em cada objeto de fase dentro de `config.js`.
+ *
+ * @module games.mole-mash.config.matrixConfig
+ */
+
+export const matrixConfig = {
+ /** Número de linhas da grade (deve refletir o tabuleiro do jogo) */
+ linhas: 3,
+
+ /** Número de colunas da grade (deve refletir o tabuleiro do jogo) */
+ colunas: 3,
+
+ /** Estilo das linhas divisórias e borda externa da grade */
+ linha: {
+ cor: 0x000000,
+ alpha: 0.4,
+ espessura: 2,
+ },
+
+ /** Estilo do preenchimento alternado das células (padrão xadrez leve) */
+ celula: {
+ cor: 0x000000,
+ alpha: 0.06,
+ },
+
+ /** Estilo dos rótulos numéricos de linhas e colunas */
+ rotulo: {
+ tamanhoFonte: "20px",
+ familia: "Arial Black, Arial",
+ estilo: "bold",
+ cor: "#111111",
+ /** Distância em px da borda esquerda da grade para os rótulos de linha */
+ offsetLinhaX: -36,
+ /** Distância em px da borda superior da grade para os rótulos de coluna */
+ offsetColunaY: -34,
+ },
+
+ /**
+ * Rótulo exibido no canto superior esquerdo para indicar os eixos (Linha \ Coluna).
+ * Ajuda o aluno a entender a notação (L, C) usada nos blocos.
+ */
+ rotuloCanto: {
+ visivel: true,
+ texto: "L\\C",
+ tamanhoFonte: "13px",
+ cor: "#555555",
+ },
+};
diff --git a/app/src/atividades/programacao/mole-mash/config/tourSteps.js b/app/src/atividades/programacao/mole-mash/config/tourSteps.js
new file mode 100644
index 0000000..dfc0da7
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/config/tourSteps.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.mole-mash.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const moleMashTourSteps = [
+ createWelcomeStep({
+ gameName: "Toupeira no Buraco",
+ description:
+ "Bem-vindo ao jardim lógico! Sua missão é controlar os saltos da toupeira através de código e lógica de programação.",
+ challenge:
+ "Aprenda a usar coordenadas, loops, variáveis de memória e algoritmos de IA para criar o salto perfeito.",
+ iconSvg: gameIcons.mole,
+ }),
+
+ createGameAreaStep({
+ title: "O Jardim (Grid 3x3)",
+ description:
+ "Aqui é onde a toupeira aparece. O tabuleiro possui 3 linhas e 3 colunas. Lembre-se: qualquer movimento fora desse intervalo (1 a 3) fará a toupeira fugir do jardim!",
+ }),
+
+ createToolboxStep({
+ description:
+ "Sua caixa de ferramentas! Comece com movimentos simples e desbloqueie blocos de sorteio aleatório, variáveis de memória e loops complexos conforme avança.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Arraste os blocos para cá para montar a lógica. Você aprenderá a guardar a posição anterior da toupeira em variáveis para que ela nunca salte no mesmo lugar duas vezes.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Hora do show! Clique aqui para executar seu script. Observe a toupeira saltar e confira se ela está seguindo as regras da fase atual.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Algo deu errado ou o código travou? O botão de reset limpa o histórico de movimentos e coloca a toupeira de volta na posição central (2, 2).",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "A jornada do programador: comece movendo a toupeira manualmente e termine criando um ciclo infinito de saltos inteligentes na Fase 10.",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Fique de olho nos objetivos! Algumas fases exigem que você use variáveis específicas (como ult_l e ult_c) ou que a toupeira realize 50 saltos seguidos sem errar.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ 'Dúvidas sobre como usar o bloco "Enquanto" ou como funcionam as coordenadas? Abra o tour de ajuda a qualquer momento.',
+ }),
+];
+
+export const moleMashTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/mole-mash/game.js b/app/src/atividades/programacao/mole-mash/game.js
new file mode 100644
index 0000000..9af1f31
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/game.js
@@ -0,0 +1,431 @@
+/**
+ * @fileoverview Utility module for game.js
+ *
+ * @module games.mole-mash.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupMoleMashAPI } from "./hooks/interpreterSetup.js";
+import { validateSolution } from "./validation/validators.js";
+import { matrixConfig } from "./config/matrixConfig.js";
+
+import sprites from "./assets/sprites.png";
+import background from "./assets/background.png";
+import ground_pop from "./assets/ground_pop.mp3";
+import game_loop from "./../../../assets/game_loop.mp3";
+
+const ASSETS = {
+ IMG: { SPRITES: "sprites", BG: "background" },
+ AUDIO: { POP: "ground_pop", LOOP: "game_loop" },
+};
+
+const CONSTANTES = {
+ LARGURA: 800,
+ ALTURA: 600,
+ TAMANHO_TILE: 120,
+ OFFSET_VERTICAL: 0.16,
+ COR_FUNDO: "#242527",
+};
+
+class MoleMashScene extends BaseGameScene {
+ constructor() {
+ super("MoleMashScene");
+ this.toupeira = null;
+ }
+
+ preload() {
+ // 1. Carrega os assets globais (win/fail) da Base
+ this.preloadGlobalAssets();
+
+ // 2. Carrega assets específicos do Mole Mash
+ this.load.image(ASSETS.IMG.BG, background);
+ this.load.spritesheet(ASSETS.IMG.SPRITES, sprites, {
+ frameWidth: 190,
+ frameHeight: 144,
+ });
+
+ this.load.audio(ASSETS.AUDIO.POP, ground_pop);
+ this.load.audio(ASSETS.AUDIO.LOOP, game_loop);
+ }
+ /**
+ * Preload dos assets específicos do Mole Mash (sprites e áudio).
+ * @returns {void}
+ */
+ preload() {
+ // 1. Carrega os assets globais (win/fail) da Base
+ this.preloadGlobalAssets();
+
+ // 2. Carrega assets específicos do Mole Mash
+ this.load.image(ASSETS.IMG.BG, background);
+ this.load.spritesheet(ASSETS.IMG.SPRITES, sprites, {
+ frameWidth: 190,
+ frameHeight: 144,
+ });
+
+ this.load.audio(ASSETS.AUDIO.POP, ground_pop);
+ this.load.audio(ASSETS.AUDIO.LOOP, game_loop);
+ }
+
+ /**
+ * Inicializa a cena: elementos visuais, animações e controlador padrão.
+ * @returns {void}
+ */
+ create() {
+ this.setupVisuals();
+ this.setupAnimations();
+ this.setupMatrix();
+ this.setupStandardController(setupMoleMashAPI, validateSolution);
+ }
+
+ // --- HOOKS DA BASE ---
+
+ /**
+ * Executado antes da execução do código do aluno; prepara áudio e visual.
+ * @returns {Promise}
+ */
+ // Chamado automaticamente antes de rodar o código do aluno
+ async onBeforeRun() {
+ this.playAudio(ASSETS.AUDIO.LOOP, { loop: true, volume: 0.1 });
+ this.toupeira.setVisible(false);
+ return this.aguardar(0.5);
+ }
+ /**
+ * Executado quando o usuário reseta a cena; restaura visual e áudio.
+ * @returns {void}
+ */
+ // Chamado quando o usuário clica em Resetar
+ onReset() {
+ this.stopAudio(ASSETS.AUDIO.LOOP);
+ this.toupeira.setVisible(false);
+ // Reseta posição visualmente
+ const pos = this.getGridPixels(2, 2);
+ this.toupeira.setPosition(pos.x, pos.y);
+ }
+ /**
+ * Chamado quando a validação for bem sucedida; limpa áudios/efeitos.
+ * @returns {void}
+ */
+ // Chamado no Sucesso
+ onSuccess() {
+ this.stopAudio(ASSETS.AUDIO.LOOP);
+ }
+
+ /**
+ * Chamado quando a validação falha; limpa áudios/efeitos.
+ * @returns {void}
+ */
+ // Chamado na Falha
+ onFailure() {
+ this.stopAudio(ASSETS.AUDIO.LOOP);
+ }
+
+ // --- LÓGICA VISUAL ESPECÍFICA ---
+
+ /**
+ * Configura elementos visuais da cena (fundo, sprites, posições iniciais).
+ * @returns {void}
+ */
+ setupVisuals() {
+ // Fundo
+ const bg = this.add
+ .image(CONSTANTES.LARGURA / 2, 0, ASSETS.IMG.BG)
+ .setOrigin(0.5, 0);
+ bg.setScale(this.scale.width / bg.width);
+
+ // Toupeira
+ const pos = this.getGridPixels(2, 2);
+ this.toupeira = this.add.sprite(pos.x, pos.y, ASSETS.IMG.SPRITES, 0);
+ this.toupeira.setScale(0.8).setVisible(false).setDepth(2);
+ }
+ setupAnimations() {
+ if (!this.anims.exists("aparecer")) {
+ this.anims.create({
+ key: "aparecer",
+ frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
+ start: 0,
+ end: 7,
+ }),
+ frameRate: 20,
+ hideOnComplete: false,
+ });
+ }
+ if (!this.anims.exists("sumir")) {
+ this.anims.create({
+ key: "sumir",
+ frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
+ start: 7,
+ end: 0,
+ }),
+ frameRate: 20,
+ });
+ }
+ }
+ getGridPixels(l, c) {
+ const larguraGrade = 3 * CONSTANTES.TAMANHO_TILE;
+ const alturaGrade = 3 * CONSTANTES.TAMANHO_TILE;
+
+ const offsetX = (CONSTANTES.LARGURA - larguraGrade) / 2;
+ const offsetY =
+ (CONSTANTES.ALTURA - alturaGrade) / 2 +
+ CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;
+
+ const x =
+ (c - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetX;
+ const y =
+ (l - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetY;
+
+ return { x, y };
+ }
+ /**
+ * Registra animações usadas pela cena (aparecer/sumir da toupeira).
+ * @returns {void}
+ */
+ setupAnimations() {
+ if (!this.anims.exists("aparecer")) {
+ this.anims.create({
+ key: "aparecer",
+ frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
+ start: 0,
+ end: 7,
+ }),
+ frameRate: 20,
+ hideOnComplete: false,
+ });
+ }
+ if (!this.anims.exists("sumir")) {
+ this.anims.create({
+ key: "sumir",
+ frames: this.anims.generateFrameNumbers(ASSETS.IMG.SPRITES, {
+ start: 7,
+ end: 0,
+ }),
+ frameRate: 20,
+ });
+ }
+ }
+
+ /**
+ * Converte coordenadas de grade (linha, coluna) para pixels na tela.
+ * @param {number} l - Linha (1..3)
+ * @param {number} c - Coluna (1..3)
+ * @returns {{x:number,y:number}} Coordenadas em pixels
+ */
+ getGridPixels(l, c) {
+ const larguraGrade = 3 * CONSTANTES.TAMANHO_TILE;
+ const alturaGrade = 3 * CONSTANTES.TAMANHO_TILE;
+
+ const offsetX = (CONSTANTES.LARGURA - larguraGrade) / 2;
+ const offsetY =
+ (CONSTANTES.ALTURA - alturaGrade) / 2 +
+ CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;
+
+ const x =
+ (c - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetX;
+ const y =
+ (l - 1) * CONSTANTES.TAMANHO_TILE + CONSTANTES.TAMANHO_TILE / 2 + offsetY;
+
+ return { x, y };
+ }
+
+ // --- SETUP AUXILIAR ---
+
+ /**
+ * Desenha a grade auxiliar de coordenadas na tela, se ativada pela fase.
+ * Ativada via `configFase.showMatrix: true`; parametrizada em `matrixConfig.js`.
+ * @returns {void}
+ */
+ setupMatrix() {
+ if (!this.configFase?.showMatrix) return;
+
+ const { linhas, colunas, linha, celula, rotulo, rotuloCanto } = matrixConfig;
+ const tileSize = CONSTANTES.TAMANHO_TILE;
+ const larguraGrade = colunas * tileSize;
+ const alturaGrade = linhas * tileSize;
+ const originX = (CONSTANTES.LARGURA - larguraGrade) / 2;
+ const originY =
+ (CONSTANTES.ALTURA - alturaGrade) / 2 +
+ CONSTANTES.ALTURA * CONSTANTES.OFFSET_VERTICAL;
+
+ const graphics = this.add.graphics();
+ graphics.setDepth(1);
+
+ // Preenchimento alternado leve (padrão xadrez)
+ for (let r = 0; r < linhas; r++) {
+ for (let c = 0; c < colunas; c++) {
+ if ((r + c) % 2 === 0) {
+ graphics.fillStyle(celula.cor, celula.alpha);
+ graphics.fillRect(
+ originX + c * tileSize,
+ originY + r * tileSize,
+ tileSize,
+ tileSize,
+ );
+ }
+ }
+ }
+
+ // Borda externa e linhas divisórias internas
+ graphics.lineStyle(linha.espessura, linha.cor, linha.alpha);
+ graphics.strokeRect(originX, originY, larguraGrade, alturaGrade);
+ for (let r = 1; r < linhas; r++) {
+ graphics.beginPath();
+ graphics.moveTo(originX, originY + r * tileSize);
+ graphics.lineTo(originX + larguraGrade, originY + r * tileSize);
+ graphics.strokePath();
+ }
+ for (let c = 1; c < colunas; c++) {
+ graphics.beginPath();
+ graphics.moveTo(originX + c * tileSize, originY);
+ graphics.lineTo(originX + c * tileSize, originY + alturaGrade);
+ graphics.strokePath();
+ }
+
+ const estiloTexto = {
+ fontSize: rotulo.tamanhoFonte,
+ fontFamily: rotulo.familia,
+ fontStyle: rotulo.estilo,
+ color: rotulo.cor,
+ };
+
+ // Rótulos de coluna — acima da grade (C1, C2, C3)
+ for (let c = 0; c < colunas; c++) {
+ this.add
+ .text(
+ originX + c * tileSize + tileSize / 2,
+ originY + rotulo.offsetColunaY,
+ String(c + 1),
+ estiloTexto,
+ )
+ .setOrigin(0.5, 1)
+ .setDepth(1);
+ }
+
+ // Rótulos de linha — à esquerda da grade (L1, L2, L3)
+ for (let r = 0; r < linhas; r++) {
+ this.add
+ .text(
+ originX + rotulo.offsetLinhaX,
+ originY + r * tileSize + tileSize / 2,
+ String(r + 1),
+ estiloTexto,
+ )
+ .setOrigin(1, 0.5)
+ .setDepth(1);
+ }
+
+ // Rótulo do canto (L\C) para orientar a leitura dos eixos
+ if (rotuloCanto.visivel) {
+ this.add
+ .text(
+ originX + rotulo.offsetLinhaX,
+ originY + rotulo.offsetColunaY,
+ rotuloCanto.texto,
+ {
+ fontSize: rotuloCanto.tamanhoFonte,
+ fontFamily: rotulo.familia,
+ color: rotuloCanto.cor,
+ },
+ )
+ .setOrigin(1, 1)
+ .setDepth(1);
+ }
+ }
+
+ // --- MÉTODOS DA API PÚBLICA ---
+
+ /**
+ * Move a toupeira para a célula indicada (linha, coluna) e toca efeitos sonoros.
+ * Registra a ação no histórico herdado da `BaseGameScene`.
+ * @param {number} linha - Linha alvo (1..3)
+ * @param {number} coluna - Coluna alvo (1..3)
+ * @returns {void}
+ */
+/**
+ * Move a toupeira para a célula indicada (linha, coluna) e toca efeitos sonoros.
+ * Registra a ação no histórico herdado da `BaseGameScene`.
+ * @param {number} linha - Linha alvo (1..3)
+ * @param {number} coluna - Coluna alvo (1..3)
+ * @returns {void}
+ */
+ moverToupeira(linha, coluna) {
+ // 1. Registra no histórico herdado da Base
+ this.historico.push({ l: linha, c: coluna, t: Date.now() });
+
+ // 2. Lógica Visual
+ const l = Phaser.Math.Clamp(linha, 1, 3);
+ const c = Phaser.Math.Clamp(coluna, 1, 3);
+ const { x, y } = this.getGridPixels(l, c);
+
+ if (!this.toupeira) return;
+
+ // Toca o som "pop" local
+ this.playAudio(ASSETS.AUDIO.POP, { rate: 3, volume: 0.3 });
+
+ // Lógica de aparecer/sumir
+ if (!this.toupeira.visible) {
+ // Primeira aparição
+ this.toupeira.setPosition(x, y);
+ this.toupeira.setVisible(true);
+ this.toupeira.play("aparecer");
+ } else {
+ // Movimento subsequente
+ this.toupeira.play("sumir");
+ this.toupeira.once("animationcomplete-sumir", () => {
+ if (!this.toupeira.scene) return;
+ this.toupeira.setPosition(x, y);
+ this.toupeira.play("aparecer");
+ });
+ }
+ }
+}
+
+// Factory Padrão do Phaser
+/**
+ * Fábrica que cria a configuração do Phaser para o MoleMash.
+ * @param {HTMLElement} parentElement - Elemento DOM que hospeda o canvas do jogo
+ * @param {Object} configFaseAtual - Configuração da fase ativa
+ * @returns {Object} Configuração do Phaser usada para inicializar o jogo
+ */
+/**
+ * Fábrica que cria a configuração do Phaser para o MoleMash.
+ * @param {HTMLElement} parentElement - Elemento DOM que hospeda o canvas do jogo
+ * @param {Object} configFaseAtual - Configuração da fase ativa
+ * @returns {Object} Configuração do Phaser usada para inicializar o jogo
+ */
+export const createGame = (
+ parentElement,
+ configFaseAtual,
+ customFailureHandler = null,
+ idFaseAtual = null,
+ gameConfig = null,
+) => {
+ const config =
+ idFaseAtual && gameConfig
+ ? gameConfig.fases[idFaseAtual - 1]
+ : configFaseAtual;
+
+ return {
+ type: Phaser.AUTO,
+ width: CONSTANTES.LARGURA,
+ height: CONSTANTES.ALTURA,
+ backgroundColor: CONSTANTES.COR_FUNDO,
+ parent: parentElement,
+ scale: {
+ mode: Phaser.Scale.FIT,
+ autoCenter: Phaser.Scale.CENTER_BOTH,
+ },
+ scene: [MoleMashScene],
+ callbacks: {
+ preBoot: (game) => {
+ // Injeção de dependências no Registry para a BaseGameScene ler no init()
+ game.registry.merge({
+ configFase: config,
+ gameConfig: gameConfig,
+ customFailureHandler: customFailureHandler,
+ stepDelay: 50,
+ });
+ },
+ },
+ };
+};
diff --git a/app/src/atividades/programacao/mole-mash/hooks/interpreterSetup.js b/app/src/atividades/programacao/mole-mash/hooks/interpreterSetup.js
new file mode 100644
index 0000000..079024e
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/hooks/interpreterSetup.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ *
+ * @module games.mole-mash.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Inicializador do interpretador para o jogo MoleMash.
+ * Registra funções assíncronas expostas ao código do aluno (moverToupeira, aguardar, highlightBlock).
+ * @param {Object} sceneInstance - Instância da cena Phaser (MoleMashScene)
+ * @param {Object} [options={}] - Opções adicionais (não utilizadas atualmente)
+ * @returns {function(interpreter:Object, globalObject:Object):void} Inicializador do interpretador
+ */
+export const setupMoleMashAPI = (sceneInstance, options = {}) => {
+ return (interpreter, globalObject) => {
+ const highlightWrapper = function (id, callback) {
+ sceneInstance.highlightBlock(id).then(callback);
+ };
+
+ const moverToupeiraWrapper = function (linha, coluna, callback) {
+ sceneInstance.moverToupeira(linha, coluna);
+ setTimeout(callback, 800);
+ };
+
+ const aguardarWrapper = function (ms, callback) {
+ setTimeout(callback, ms);
+ };
+
+ ApiHelpers.registerMultipleFunctions(interpreter, globalObject, [
+ { name: "highlightBlock", wrapper: highlightWrapper, isAsync: true },
+ { name: "moverToupeira", wrapper: moverToupeiraWrapper, isAsync: true },
+ { name: "aguardar", wrapper: aguardarWrapper, isAsync: true },
+ ]);
+ };
+};
diff --git a/app/src/atividades/programacao/mole-mash/hooks/useMoleMashTour.js b/app/src/atividades/programacao/mole-mash/hooks/useMoleMashTour.js
new file mode 100644
index 0000000..d34342d
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/hooks/useMoleMashTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useMoleMashTour.js
+ *
+ * @module games.mole-mash.hooks.useMoleMashTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { moleMashTourSteps, moleMashTourOptions } from "../config/tourSteps";
+
+/**
+ * Hook que inicia o tour guiado do MoleMash/Toupeira.
+ * Retorna a API do tour por delegação a `useGameTour`.
+ * @returns {Object} API do tour (ex.: startTour).
+ */
+export const useMoleMashTour = () => {
+ return useGameTour("molemash", moleMashTourSteps, moleMashTourOptions);
+};
diff --git a/app/src/atividades/programacao/mole-mash/validation/validators.js b/app/src/atividades/programacao/mole-mash/validation/validators.js
new file mode 100644
index 0000000..fe26e8b
--- /dev/null
+++ b/app/src/atividades/programacao/mole-mash/validation/validators.js
@@ -0,0 +1,193 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.mole-mash.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+class MoleMashValidator extends BaseGameValidator {
+ validate(history, configFase, gameConfig, sceneRef) {
+ if (!configFase || Object.keys(configFase).length === 0) {
+ console.error("Validação falhou: Configuração da fase não encontrada.");
+ return this.failure("Erro Técnico: Fase não configurada.");
+ }
+
+ const sanityCheck = this.checkSanity(history, gameConfig);
+ if (!sanityCheck.success) return sanityCheck;
+
+ if (configFase.expectedSequence) {
+ const sequenceCheck = configFase.ignoreSequenceOrder
+ ? this.checkSequenceIgnoreOrder(
+ history,
+ configFase.expectedSequence,
+ gameConfig,
+ )
+ : this.checkSequence(history, configFase.expectedSequence, gameConfig);
+
+ if (!sequenceCheck.success) return sequenceCheck;
+ }
+
+ return this.validatePhase(history, configFase, gameConfig, sceneRef);
+ }
+
+ validatePhase(history, config, gameConfig) {
+ // 1. Regra de Domínio: Verifica se saiu do tabuleiro (3x3)
+ if (this.hasOutOfBounds(history)) {
+ return this.failure(
+ gameConfig?.mensagens?.foraTabuleiro || "Movimento fora do tabuleiro.",
+ );
+ }
+
+ // 2. Regra de Configuração: Verifica Contagem Mínima de Hits
+ if (config.requiredHits && history.length < config.requiredHits) {
+ return this.failure(
+ gameConfig.mensagens.faltamSaltos
+ .replace("{requiredHits}", config.requiredHits)
+ .replace("{actualHits}", history.length),
+ );
+ }
+
+ // 3. Roteamento Dinâmico (Strategy Pattern)
+ // Procura se existe um método com o nome "faseX" (ex: fase3, fase4)
+ const phaseMethod = this[`fase${config.id}`];
+ if (phaseMethod) {
+ // Chama o método passando o contexto correto (this)
+ return phaseMethod.call(this, history, config, gameConfig);
+ }
+
+ // Se passou por tudo e não tem regra específica, é sucesso.
+ return this.success();
+ }
+
+ // --- Helpers Internos do MoleMash ---
+
+ hasOutOfBounds(history) {
+ return history.some(
+ (h) =>
+ (h.l !== undefined && (h.l < 1 || h.l > 3)) ||
+ (h.c !== undefined && (h.c < 1 || h.c > 3)),
+ );
+ }
+
+ checkSequenceIgnoreOrder(history, expected, gameConfig) {
+ const historyToCompare = history.slice(0, expected.length);
+
+ if (historyToCompare.length < expected.length) {
+ return this.failure(
+ gameConfig?.mensagens?.caminhoErrado || "Sequência incorreta.",
+ );
+ }
+
+ const keys = Array.from(
+ new Set(expected.flatMap((step) => Object.keys(step))),
+ ).sort();
+
+ const serialize = (item) =>
+ keys.map((key) => `${key}:${item?.[key]}`).join("|");
+
+ const expectedCount = new Map();
+ expected.forEach((item) => {
+ const key = serialize(item);
+ expectedCount.set(key, (expectedCount.get(key) || 0) + 1);
+ });
+
+ const historyCount = new Map();
+ historyToCompare.forEach((item) => {
+ const key = serialize(item);
+ historyCount.set(key, (historyCount.get(key) || 0) + 1);
+ });
+
+ const isMatch = Array.from(expectedCount.entries()).every(
+ ([key, count]) => historyCount.get(key) === count,
+ );
+
+ return isMatch
+ ? this.success()
+ : this.failure(
+ gameConfig?.mensagens?.caminhoErrado || "Sequência incorreta.",
+ );
+ }
+
+ // --- Lógicas Específicas por Fase ---
+ // Alternar entre Centro (2,2) e Cima (1,2)
+ fase3(hist, config, gameConfig) {
+ if (hist.length !== 10)
+ return this.failure(gameConfig.mensagens.saltoInsuficiente);
+
+ const isCenter = (m) => m.l === 2 && m.c === 2;
+ const isTopCenter = (m) => m.l === 1 && m.c === 2;
+
+ for (let i = 0; i < hist.length; i++) {
+ const isEven = i % 2 === 0;
+ const correct = isEven ? isCenter(hist[i]) : isTopCenter(hist[i]);
+
+ if (!correct) {
+ const local = isEven ? "centro (2,2)" : "cima (1,2)";
+ return this.failure(
+ gameConfig.mensagens.saltoErrado
+ .replace("{numero}", i + 1)
+ .replace("{local}", local),
+ );
+ }
+ }
+ return this.success();
+ }
+
+ // Visitar 9 buracos únicos
+ fase4(hist, config, gameConfig) {
+ const visitadas = new Set(hist.map((p) => `${p.l},${p.c}`));
+ if (visitadas.size < 9) {
+ return this.failure(
+ gameConfig.mensagens.buracosFaltando.replace(
+ "{visitados}",
+ visitadas.size,
+ ),
+ );
+ }
+ return this.success();
+ }
+
+ // Apenas linha 2, mas várias colunas
+ fase5(hist, config, gameConfig) {
+ if (!hist.every((h) => h.l === 2))
+ return this.failure(gameConfig.mensagens.linhaErrada);
+
+ const colunas = new Set(hist.map((h) => h.c));
+ if (colunas.size <= 1)
+ return this.failure(gameConfig.mensagens.semMovimento);
+
+ return this.success();
+ }
+
+ // Não repetir linha consecutiva (exceto linha 2)
+ fase8(hist, config, gameConfig) {
+ for (let i = 1; i < hist.length; i++) {
+ if (hist[i].l === hist[i - 1].l && hist[i].l !== 2) {
+ return this.failure(gameConfig.mensagens.repeticaoLinha);
+ }
+ }
+ return this.success();
+ }
+
+ // Lógica complexa de "Não ficar parado" e "Não repetir centro"
+ fase10(hist, config, gameConfig) {
+ for (let i = 1; i < hist.length; i++) {
+ const ant = hist[i - 1];
+ const atual = hist[i];
+
+ if (atual.l === ant.l && atual.c === ant.c)
+ return this.failure(gameConfig.mensagens.mesmoLugar);
+
+ if (ant.l === 2 && atual.l === 2)
+ return this.failure(gameConfig.mensagens.linhaCentral);
+ }
+ return this.success();
+ }
+}
+
+const validatorInstance = new MoleMashValidator();
+
+export const validateSolution = (history, config, gameConfig) => {
+ return validatorInstance.validate(history, config, gameConfig);
+};
diff --git a/app/src/atividades/programacao/ordenacao/OrdenacaoGame.jsx b/app/src/atividades/programacao/ordenacao/OrdenacaoGame.jsx
new file mode 100644
index 0000000..776631c
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/OrdenacaoGame.jsx
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Componente React principal do jogo Ordenação.
+ * @module games.ordenacao.OrdenacaoGame
+ */
+
+import { useEffect, useMemo } from "react";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
+import { GameStateProvider, useGameState } from "../../../contexts/GameStateContext";
+import { starterBlocks } from "./config/starterBlock";
+
+function OrdenacaoContent() {
+ const { setFailureMessage } = useGameState();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(
+ () => (allowedBlocks) => generateDynamicToolbox(allowedBlocks),
+ []
+ );
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default function OrdenacaoGame() {
+ return (
+
+
+
+ );
+}
diff --git a/app/src/atividades/programacao/ordenacao/algoritmos.md b/app/src/atividades/programacao/ordenacao/algoritmos.md
new file mode 100644
index 0000000..e9dcd46
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/algoritmos.md
@@ -0,0 +1,544 @@
+# Algoritmos de Ordenação em JavaScript
+
+Este material reúne os principais algoritmos de ordenação para apoiar a modelagem da atividade de Ordenação.
+
+## Como ler este guia
+
+- Todas as funções abaixo recebem um array numérico.
+- Para facilitar testes e evitar efeitos colaterais, as implementações retornam um novo array quando possível.
+- Cada algoritmo inclui uma explicação breve e sua complexidade.
+
+---
+
+## 1) Bubble Sort (Ordenação por Bolha)
+
+Compara pares adjacentes e troca quando estão fora de ordem. A cada passada, o maior elemento "sobe" para o fim.
+
+- Melhor: O(n) (com otimização de parada)
+- Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Sim
+
+```javascript
+export function bubbleSort(input) {
+ const arr = [...input];
+ const n = arr.length;
+
+ for (let i = 0; i < n - 1; i++) {
+ let trocou = false;
+
+ for (let j = 0; j < n - 1 - i; j++) {
+ if (arr[j] > arr[j + 1]) {
+ [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
+ trocou = true;
+ }
+ }
+
+ if (!trocou) break;
+ }
+
+ return arr;
+}
+```
+
+## 2) Selection Sort (Ordenação por Seleção)
+
+Encontra o menor elemento da parte não ordenada e o coloca na posição correta.
+
+- Melhor/Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Não
+
+```javascript
+export function selectionSort(input) {
+ const arr = [...input];
+ const n = arr.length;
+
+ for (let i = 0; i < n - 1; i++) {
+ let min = i;
+ for (let j = i + 1; j < n; j++) {
+ if (arr[j] < arr[min]) min = j;
+ }
+ if (min !== i) [arr[i], arr[min]] = [arr[min], arr[i]];
+ }
+
+ return arr;
+}
+```
+
+## 3) Insertion Sort (Ordenação por Inserção)
+
+Constrói a parte ordenada inserindo cada elemento em sua posição correta.
+
+- Melhor: O(n)
+- Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Sim
+
+```javascript
+export function insertionSort(input) {
+ const arr = [...input];
+
+ for (let i = 1; i < arr.length; i++) {
+ const chave = arr[i];
+ let j = i - 1;
+
+ while (j >= 0 && arr[j] > chave) {
+ arr[j + 1] = arr[j];
+ j--;
+ }
+
+ arr[j + 1] = chave;
+ }
+
+ return arr;
+}
+```
+
+## 4) Merge Sort (Ordenação por Intercalação)
+
+Divide o array ao meio recursivamente e intercala as metades já ordenadas.
+
+- Melhor/Médio/Pior: O(n log n)
+- Espaço: O(n)
+- Estável: Sim
+
+```javascript
+function merge(esq, dir) {
+ const resultado = [];
+ let i = 0;
+ let j = 0;
+
+ while (i < esq.length && j < dir.length) {
+ if (esq[i] <= dir[j]) {
+ resultado.push(esq[i++]);
+ } else {
+ resultado.push(dir[j++]);
+ }
+ }
+
+ return [...resultado, ...esq.slice(i), ...dir.slice(j)];
+}
+
+export function mergeSort(input) {
+ if (input.length <= 1) return [...input];
+
+ const meio = Math.floor(input.length / 2);
+ const esq = mergeSort(input.slice(0, meio));
+ const dir = mergeSort(input.slice(meio));
+
+ return merge(esq, dir);
+}
+```
+
+## 5) Quick Sort (Ordenação Rápida)
+
+Escolhe um pivô e particiona os elementos em menores e maiores que ele, ordenando recursivamente.
+
+- Melhor/Médio: O(n log n)
+- Pior: O(n²)
+- Espaço: O(log n) em média (recursão)
+- Estável: Não
+
+```javascript
+export function quickSort(input) {
+ const arr = [...input];
+
+ function particionar(inicio, fim) {
+ const pivo = arr[fim];
+ let i = inicio - 1;
+
+ for (let j = inicio; j < fim; j++) {
+ if (arr[j] <= pivo) {
+ i++;
+ [arr[i], arr[j]] = [arr[j], arr[i]];
+ }
+ }
+
+ [arr[i + 1], arr[fim]] = [arr[fim], arr[i + 1]];
+ return i + 1;
+ }
+
+ function ordenar(inicio, fim) {
+ if (inicio >= fim) return;
+ const p = particionar(inicio, fim);
+ ordenar(inicio, p - 1);
+ ordenar(p + 1, fim);
+ }
+
+ ordenar(0, arr.length - 1);
+ return arr;
+}
+```
+
+## 6) Heap Sort
+
+Transforma o array em um heap máximo e remove repetidamente o maior elemento para o fim.
+
+- Melhor/Médio/Pior: O(n log n)
+- Espaço: O(1)
+- Estável: Não
+
+```javascript
+export function heapSort(input) {
+ const arr = [...input];
+
+ function heapify(n, i) {
+ let maior = i;
+ const esq = 2 * i + 1;
+ const dir = 2 * i + 2;
+
+ if (esq < n && arr[esq] > arr[maior]) maior = esq;
+ if (dir < n && arr[dir] > arr[maior]) maior = dir;
+
+ if (maior !== i) {
+ [arr[i], arr[maior]] = [arr[maior], arr[i]];
+ heapify(n, maior);
+ }
+ }
+
+ for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
+ heapify(arr.length, i);
+ }
+
+ for (let i = arr.length - 1; i > 0; i--) {
+ [arr[0], arr[i]] = [arr[i], arr[0]];
+ heapify(i, 0);
+ }
+
+ return arr;
+}
+```
+
+## 7) Shell Sort
+
+Generaliza o Insertion Sort usando intervalos (gaps) que diminuem ao longo das passadas.
+
+- Melhor: depende da sequência de gaps
+- Médio/Pior: em geral entre O(n^1.3) e O(n²)
+- Espaço: O(1)
+- Estável: Não
+
+```javascript
+export function shellSort(input) {
+ const arr = [...input];
+ let gap = Math.floor(arr.length / 2);
+
+ while (gap > 0) {
+ for (let i = gap; i < arr.length; i++) {
+ const temp = arr[i];
+ let j = i;
+
+ while (j >= gap && arr[j - gap] > temp) {
+ arr[j] = arr[j - gap];
+ j -= gap;
+ }
+
+ arr[j] = temp;
+ }
+
+ gap = Math.floor(gap / 2);
+ }
+
+ return arr;
+}
+```
+
+## 8) Counting Sort
+
+Conta quantas vezes cada valor aparece e reconstrói o array ordenado. Muito eficiente para inteiros em faixa limitada.
+
+- Melhor/Médio/Pior: O(n + k), onde k é a faixa de valores
+- Espaço: O(k)
+- Estável: Pode ser estável (implementação abaixo é estável)
+
+```javascript
+export function countingSort(input) {
+ if (input.length === 0) return [];
+
+ const arr = [...input];
+ const min = Math.min(...arr);
+ const max = Math.max(...arr);
+ const faixa = max - min + 1;
+
+ const contagem = new Array(faixa).fill(0);
+ const saida = new Array(arr.length);
+
+ for (const valor of arr) contagem[valor - min]++;
+ for (let i = 1; i < contagem.length; i++) contagem[i] += contagem[i - 1];
+
+ for (let i = arr.length - 1; i >= 0; i--) {
+ const valor = arr[i];
+ const idx = valor - min;
+ saida[contagem[idx] - 1] = valor;
+ contagem[idx]--;
+ }
+
+ return saida;
+}
+```
+
+## 9) Radix Sort (LSD)
+
+Ordena inteiros por dígitos, do menos significativo para o mais significativo, usando um sort estável por dígito.
+
+- Melhor/Médio/Pior: O(d * (n + b))
+: d = número de dígitos, b = base (10)
+- Espaço: O(n + b)
+- Estável: Sim
+
+```javascript
+export function radixSort(input) {
+ if (input.length === 0) return [];
+ if (input.some((n) => n < 0)) {
+ throw new Error("radixSort desta versão aceita apenas inteiros não negativos.");
+ }
+
+ let arr = [...input];
+ const max = Math.max(...arr);
+
+ for (let exp = 1; Math.floor(max / exp) > 0; exp *= 10) {
+ const saida = new Array(arr.length);
+ const contagem = new Array(10).fill(0);
+
+ for (const valor of arr) {
+ const digito = Math.floor(valor / exp) % 10;
+ contagem[digito]++;
+ }
+
+ for (let i = 1; i < 10; i++) contagem[i] += contagem[i - 1];
+
+ for (let i = arr.length - 1; i >= 0; i--) {
+ const valor = arr[i];
+ const digito = Math.floor(valor / exp) % 10;
+ saida[contagem[digito] - 1] = valor;
+ contagem[digito]--;
+ }
+
+ arr = saida;
+ }
+
+ return arr;
+}
+```
+
+## 10) Bucket Sort
+
+Distribui valores em "baldes" por faixa, ordena cada balde e concatena. Funciona bem para dados distribuídos uniformemente.
+
+- Melhor/Médio: próximo de O(n)
+- Pior: O(n²) (se concentrar muitos valores no mesmo balde)
+- Espaço: O(n + k)
+- Estável: Depende da ordenação interna
+
+```javascript
+export function bucketSort(input, bucketSize = 5) {
+ if (input.length === 0) return [];
+
+ const arr = [...input];
+ const min = Math.min(...arr);
+ const max = Math.max(...arr);
+
+ const qtdBuckets = Math.floor((max - min) / bucketSize) + 1;
+ const buckets = Array.from({ length: qtdBuckets }, () => []);
+
+ for (const valor of arr) {
+ const idx = Math.floor((valor - min) / bucketSize);
+ buckets[idx].push(valor);
+ }
+
+ const resultado = [];
+ for (const bucket of buckets) {
+ if (bucket.length > 0) {
+ bucket.sort((a, b) => a - b);
+ resultado.push(...bucket);
+ }
+ }
+
+ return resultado;
+}
+```
+
+## 11) Comb Sort
+
+Melhora o Bubble Sort comparando elementos com uma distância (gap) maior que vai diminuindo.
+
+- Melhor: O(n log n)
+- Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Não
+
+```javascript
+export function combSort(input) {
+ const arr = [...input];
+ const fator = 1.3;
+ let gap = arr.length;
+ let trocou = true;
+
+ while (gap > 1 || trocou) {
+ gap = Math.floor(gap / fator);
+ if (gap < 1) gap = 1;
+
+ trocou = false;
+
+ for (let i = 0; i + gap < arr.length; i++) {
+ if (arr[i] > arr[i + gap]) {
+ [arr[i], arr[i + gap]] = [arr[i + gap], arr[i]];
+ trocou = true;
+ }
+ }
+ }
+
+ return arr;
+}
+```
+
+## 12) Cocktail Shaker Sort
+
+Variação do Bubble Sort que percorre em dois sentidos (ida e volta), movendo maiores e menores em cada ciclo.
+
+- Melhor: O(n)
+- Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Sim
+
+```javascript
+export function cocktailShakerSort(input) {
+ const arr = [...input];
+ let inicio = 0;
+ let fim = arr.length - 1;
+ let trocou = true;
+
+ while (trocou) {
+ trocou = false;
+
+ for (let i = inicio; i < fim; i++) {
+ if (arr[i] > arr[i + 1]) {
+ [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
+ trocou = true;
+ }
+ }
+
+ if (!trocou) break;
+ trocou = false;
+ fim--;
+
+ for (let i = fim; i > inicio; i--) {
+ if (arr[i - 1] > arr[i]) {
+ [arr[i - 1], arr[i]] = [arr[i], arr[i - 1]];
+ trocou = true;
+ }
+ }
+
+ inicio++;
+ }
+
+ return arr;
+}
+```
+
+## 13) Gnome Sort
+
+Parecido com Insertion Sort, caminhando para trás quando encontra inversões e para frente quando está em ordem.
+
+- Melhor: O(n)
+- Médio/Pior: O(n²)
+- Espaço: O(1)
+- Estável: Sim
+
+```javascript
+export function gnomeSort(input) {
+ const arr = [...input];
+ let i = 1;
+
+ while (i < arr.length) {
+ if (i === 0 || arr[i - 1] <= arr[i]) {
+ i++;
+ } else {
+ [arr[i], arr[i - 1]] = [arr[i - 1], arr[i]];
+ i--;
+ }
+ }
+
+ return arr;
+}
+```
+
+## 14) Tim Sort (versão didática simplificada)
+
+Algoritmo híbrido usado em engines reais (como Python e Java). Combina Insertion Sort em pequenos blocos e Merge Sort para juntar blocos.
+
+- Melhor: O(n)
+- Médio/Pior: O(n log n)
+- Espaço: O(n)
+- Estável: Sim
+
+```javascript
+export function timSortDidatico(input) {
+ const arr = [...input];
+ const RUN = 32;
+
+ function insertionRange(esq, dir) {
+ for (let i = esq + 1; i <= dir; i++) {
+ const temp = arr[i];
+ let j = i - 1;
+ while (j >= esq && arr[j] > temp) {
+ arr[j + 1] = arr[j];
+ j--;
+ }
+ arr[j + 1] = temp;
+ }
+ }
+
+ function merge(esq, meio, dir) {
+ const esquerda = arr.slice(esq, meio + 1);
+ const direita = arr.slice(meio + 1, dir + 1);
+
+ let i = 0;
+ let j = 0;
+ let k = esq;
+
+ while (i < esquerda.length && j < direita.length) {
+ if (esquerda[i] <= direita[j]) arr[k++] = esquerda[i++];
+ else arr[k++] = direita[j++];
+ }
+
+ while (i < esquerda.length) arr[k++] = esquerda[i++];
+ while (j < direita.length) arr[k++] = direita[j++];
+ }
+
+ for (let i = 0; i < arr.length; i += RUN) {
+ insertionRange(i, Math.min(i + RUN - 1, arr.length - 1));
+ }
+
+ for (let size = RUN; size < arr.length; size *= 2) {
+ for (let esq = 0; esq < arr.length; esq += 2 * size) {
+ const meio = Math.min(esq + size - 1, arr.length - 1);
+ const dir = Math.min(esq + 2 * size - 1, arr.length - 1);
+ if (meio < dir) merge(esq, meio, dir);
+ }
+ }
+
+ return arr;
+}
+```
+
+---
+
+## Resumo rápido para uso pedagógico
+
+- Começo didático (conceitos de comparação e troca): Bubble, Selection, Insertion.
+- Transição para eficiência: Merge e Quick.
+- Estruturas/variações importantes: Heap e Shell.
+- Ordenação não baseada em comparação (cenários específicos): Counting, Radix e Bucket.
+- Extensões para desafio e curiosidade: Comb, Cocktail, Gnome e Tim Sort simplificado.
+
+## Observação importante
+
+Não existe um único algoritmo "melhor" para todos os cenários. A escolha depende de:
+
+- tamanho da lista,
+- distribuição dos dados,
+- necessidade de estabilidade,
+- limite de memória,
+- custo de implementação/ensino.
diff --git a/app/src/atividades/programacao/ordenacao/blocks/blocks.js b/app/src/atividades/programacao/ordenacao/blocks/blocks.js
new file mode 100644
index 0000000..ceb72e9
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/blocks/blocks.js
@@ -0,0 +1,438 @@
+/**
+ * @fileoverview Definição e registro dos blocos Blockly para o jogo Ordenação.
+ *
+ * Adota operações de lista zero-based alinhadas ao estudo:
+ * - lista_tamanho
+ * - lista_ler(indice)
+ * - lista_escrever(indice, valor)
+ * - lista_trocar(i, j)
+ * - lista_comparar(i, j, operador)
+ *
+ * @module games.ordenacao.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import { javascriptGenerator } from "blockly/javascript";
+import { CORES_BLOCKLY, CORES_CUSTOMIZADAS } from "@/blockly/blocklyColors";
+
+const COR_LISTA = CORES_BLOCKLY.LISTAS;
+
+export const registerBlocks = () => {
+ _defineBlocks();
+ _defineGenerators();
+};
+
+/**
+ * Gera o objeto toolbox com blocos nativos + lista customizada.
+ */
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const contents = [];
+
+ const listBlocks = [];
+
+ if (allowedBlocks.includes("ord_lista")) {
+ listBlocks.push({ kind: "block", type: "ord_lista" });
+ }
+
+ if (allowedBlocks.includes("ord_lista_tamanho")) {
+ listBlocks.push({ kind: "block", type: "ord_lista_tamanho" });
+ }
+
+ if (allowedBlocks.includes("ord_lista_ler")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_ler",
+ inputs: {
+ INDICE: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_escrever")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_escrever",
+ inputs: {
+ INDICE: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ VALOR: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_trocar")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_trocar",
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_comparar")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_comparar",
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_contagem")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_contagem",
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_contagem_ler")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_contagem_ler",
+ inputs: {
+ INDICE: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("ord_lista_contagem_escrever")) {
+ listBlocks.push({
+ kind: "block",
+ type: "ord_lista_contagem_escrever",
+ inputs: {
+ INDICE: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ VALOR: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ },
+ });
+ }
+
+ if (listBlocks.length > 0) {
+ contents.push({
+ kind: "category",
+ name: "Listas",
+ colour: COR_LISTA,
+ contents: listBlocks,
+ });
+ }
+
+ if (allowedBlocks.some(b => ["logic_compare", "logic_boolean", "logic_negate", "logic_operation", "controls_if"].includes(b))) {
+ const logica = [];
+ if (allowedBlocks.includes("logic_compare")) {
+ logica.push({ kind: "block", type: "logic_compare" });
+ }
+ if (allowedBlocks.includes("logic_boolean")) {
+ logica.push({ kind: "block", type: "logic_boolean" });
+ }
+ if (allowedBlocks.includes("logic_negate")) {
+ logica.push({ kind: "block", type: "logic_negate" });
+ }
+ if (allowedBlocks.includes("controls_if")) {
+ logica.push({ kind: "block", type: "controls_if" });
+ }
+ if (allowedBlocks.includes("logic_operation")) {
+ logica.push({ kind: "block", type: "logic_operation" });
+ }
+ if (logica.length > 0) {
+ contents.push({
+ kind: "category",
+ name: "Lógica",
+ colour: CORES_BLOCKLY.LOGICA,
+ contents: logica,
+ });
+ }
+ }
+
+ if (allowedBlocks.some((b) => ["controls_for", "controls_while", "ord_for_custom"].includes(b))) {
+ const repeticao = [];
+
+ if (allowedBlocks.includes("ord_for_custom")) {
+ repeticao.push({
+ kind: "block",
+ type: "ord_for_custom",
+ inputs: {
+ FROM: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ },
+ });
+ }
+
+ if (allowedBlocks.includes("controls_for")) {
+ repeticao.push({
+ kind: "block",
+ type: "controls_for",
+ });
+ }
+
+ if (allowedBlocks.includes("controls_while")) {
+ repeticao.push({ kind: "block", type: "controls_whileUntil" });
+ }
+
+ if (allowedBlocks.includes("controls_flow_statements")) {
+ repeticao.push({ kind: "block", type: "ord_break" });
+ }
+
+ contents.push({
+ kind: "category",
+ name: "Repetição",
+ colour: CORES_BLOCKLY.LOOPS,
+ contents: repeticao,
+ });
+ }
+
+ if (allowedBlocks.includes("variables")) {
+ contents.push({
+ kind: "category",
+ name: "Variáveis",
+ colour: CORES_BLOCKLY.VARIAVEIS,
+ custom: "VARIABLE",
+ });
+ }
+
+ if (allowedBlocks.some(b => ["math_number", "math_arithmetic", "math_round"].includes(b))) {
+ const math = [];
+ if (allowedBlocks.includes("math_number")) {
+ math.push({ kind: "block", type: "math_number" });
+ }
+ if (allowedBlocks.includes("math_arithmetic")) {
+ math.push({ kind: "block", type: "math_arithmetic" });
+ }
+ if (allowedBlocks.includes("math_round")) {
+ math.push({ kind: "block", type: "math_round" });
+ }
+ if (math.length > 0) {
+ contents.push({
+ kind: "category",
+ name: "Matemática",
+ colour: CORES_BLOCKLY.MATEMATICA,
+ contents: math,
+ });
+ }
+ }
+
+ return { kind: "categoryToolbox", contents };
+};
+
+const _defineBlocks = () => {
+ Blockly.Blocks["ord_lista"] = {
+ init() {
+ this.appendDummyInput().appendField("LISTA DE NÚMEROS");
+ this.setOutput(true, "Array");
+ this.setColour(COR_LISTA);
+ this.setTooltip("A lista de números para ordenar.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_contagem"] = {
+ init() {
+ this.appendDummyInput().appendField("LISTA CONTAGEM");
+ this.setOutput(true, "Array");
+ this.setColour("#6d4c41");
+ this.setTooltip("Array auxiliar 'contagem' para Counting Sort.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_contagem_ler"] = {
+ init() {
+ this.appendValueInput("INDICE")
+ .setCheck("Number")
+ .appendField("lista contagem ler POSICAO");
+ this.setOutput(true, null);
+ this.setColour(COR_LISTA);
+ this.setTooltip("Lê lista[indice] com índice zero-based.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_contagem_escrever"] = {
+ init() {
+ this.appendValueInput("INDICE")
+ .setCheck("Number")
+ .appendField("lista contagem escrever POSICAO");
+ this.appendValueInput("VALOR")
+ .appendField("valor");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(COR_LISTA);
+ this.setTooltip("Escreve em lista[indice] com índice zero-based.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_tamanho"] = {
+ init() {
+ this.appendDummyInput().appendField("lista tamanho");
+ this.setOutput(true, "Number");
+ this.setColour(COR_LISTA);
+ this.setTooltip("Retorna o tamanho da lista (índice inicia em 0).");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_ler"] = {
+ init() {
+ this.appendValueInput("INDICE")
+ .setCheck("Number")
+ .appendField("lista ler POSICAO");
+ this.setOutput(true, null);
+ this.setColour(COR_LISTA);
+ this.setTooltip("Lê lista[indice] com índice zero-based.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_escrever"] = {
+ init() {
+ this.appendValueInput("INDICE")
+ .setCheck("Number")
+ .appendField("lista escrever POSICAO");
+ this.appendValueInput("VALOR")
+ .appendField("valor");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(COR_LISTA);
+ this.setTooltip("Escreve em lista[indice] com índice zero-based.");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_trocar"] = {
+ init() {
+ this.appendValueInput("I")
+ .setCheck("Number")
+ .appendField("lista trocar POSICAO");
+ this.appendValueInput("J")
+ .setCheck("Number")
+ .appendField("com POSICAO");
+ this.setInputsInline(true);
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(COR_LISTA);
+ this.setTooltip("Troca os valores de lista[i] e lista[j].");
+ },
+ };
+
+ Blockly.Blocks["ord_for_custom"] = {
+ init() {
+ this.appendValueInput("FROM")
+ .setCheck("Number")
+ .appendField("PARA")
+ .appendField(new Blockly.FieldVariable("i"), "VAR")
+ .appendField("=");
+ this.appendValueInput("COND")
+ .setCheck("Boolean")
+ .appendField("enquanto");
+ this.appendValueInput("UPDATE")
+ .appendField("incremento");
+ this.appendStatementInput("DO")
+ .appendField("fazer");
+ this.setInputsInline(true);
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(CORES_BLOCKLY.LOOPS);
+ this.setTooltip("Loop PARA estilo JavaScript: variável = início; enquanto condição; incremento.");
+ },
+ };
+
+ Blockly.Blocks["ord_break"] = {
+ init() {
+ this.appendDummyInput().appendField("parar loop");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(CORES_BLOCKLY.LOOPS);
+ this.setTooltip("Interrompe o loop atual (break).");
+ },
+ };
+
+ Blockly.Blocks["ord_lista_comparar"] = {
+ init() {
+ this.appendValueInput("I")
+ .setCheck("Number")
+ .appendField("lista POSICAO");
+ this.appendDummyInput()
+ .appendField(new Blockly.FieldDropdown([
+ [">", ">"],
+ ["<", "<"],
+ [">=", ">="],
+ ["<=", "<="],
+ ["==", "=="],
+ ["!=", "!="],
+ ]), "OP");
+ this.appendValueInput("J")
+ .setCheck("Number")
+ .appendField("lista POSICAO");
+ this.setOutput(true, "Boolean");
+ this.setColour(COR_LISTA);
+ this.setTooltip("Compara lista[i] OP lista[j].");
+ },
+ };
+
+};
+
+const _defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock,lista,contagem,lista_tamanho,lista_ler,lista_escrever,lista_trocar,lista_comparar");
+
+ javascriptGenerator.forBlock["ord_lista"] = function () {
+ return ["lista", javascriptGenerator.ORDER_MEMBER];
+ };
+
+ javascriptGenerator.forBlock["ord_lista_contagem"] = function () {
+ return ["contagem", javascriptGenerator.ORDER_ATOMIC];
+ };
+
+ javascriptGenerator.forBlock["ord_lista_tamanho"] = function () {
+ return ["lista_tamanho()", javascriptGenerator.ORDER_FUNCTION_CALL];
+ };
+
+ javascriptGenerator.forBlock["ord_lista_contagem_ler"] = function (block, generator) {
+ const index = generator.valueToCode(block, "INDICE", generator.ORDER_NONE) ?? "0";
+ return [`lista_contagem_ler(${index})`, generator.ORDER_FUNCTION_CALL];
+ };
+
+ javascriptGenerator.forBlock["ord_lista_contagem_escrever"] = function (block, generator) {
+ const index = generator.valueToCode(block, "INDICE", generator.ORDER_NONE) ?? "0";
+ const value = generator.valueToCode(block, "VALOR", generator.ORDER_NONE) ?? "0";
+ return `lista_contagem_escrever(${index}, ${value});\n`;
+ };
+
+ javascriptGenerator.forBlock["ord_lista_ler"] = function (block, generator) {
+ const index = generator.valueToCode(block, "INDICE", generator.ORDER_NONE) ?? "0";
+ return [`lista_ler(${index})`, generator.ORDER_FUNCTION_CALL];
+ };
+
+ javascriptGenerator.forBlock["ord_lista_escrever"] = function (block, generator) {
+ const index = generator.valueToCode(block, "INDICE", generator.ORDER_NONE) ?? "0";
+ const value = generator.valueToCode(block, "VALOR", generator.ORDER_NONE) ?? "0";
+ return `lista_escrever(${index}, ${value});\n`;
+ };
+
+ javascriptGenerator.forBlock["ord_lista_trocar"] = function (block, generator) {
+ const i = generator.valueToCode(block, "I", generator.ORDER_NONE) ?? "0";
+ const j = generator.valueToCode(block, "J", generator.ORDER_NONE) ?? "0";
+ return `lista_trocar(${i}, ${j});\n`;
+ };
+
+ javascriptGenerator.forBlock["ord_break"] = function () {
+ return "break;\n";
+ };
+
+ javascriptGenerator.forBlock["ord_lista_comparar"] = function (block, generator) {
+ const i = generator.valueToCode(block, "I", generator.ORDER_NONE) ?? "0";
+ const j = generator.valueToCode(block, "J", generator.ORDER_NONE) ?? "0";
+ const op = block.getFieldValue("OP") || ">";
+ return [`lista_comparar(${i}, ${j}, "${op}")`, generator.ORDER_FUNCTION_CALL];
+ };
+
+ javascriptGenerator.forBlock["ord_for_custom"] = function (block, generator) {
+ const varName = generator.nameDB_.getName(
+ block.getFieldValue("VAR"),
+ Blockly.Names.NameType.VARIABLE
+ );
+ const from = generator.valueToCode(block, "FROM", generator.ORDER_ASSIGNMENT) || "0";
+ const cond = generator.valueToCode(block, "COND", generator.ORDER_NONE) || "false";
+ const update = generator.valueToCode(block, "UPDATE", generator.ORDER_ASSIGNMENT) || varName;
+ const body = generator.statementToCode(block, "DO");
+ return `for (var ${varName} = ${from}; ${cond}; ${varName} = ${update}) {\n${body}}\n`;
+ };
+
+};
diff --git a/app/src/atividades/programacao/ordenacao/config/config.js b/app/src/atividades/programacao/ordenacao/config/config.js
new file mode 100644
index 0000000..eb87b2c
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/config/config.js
@@ -0,0 +1,302 @@
+/**
+ * @fileoverview Configuração do jogo Ordenação.
+ * @module games.ordenacao.config.config
+ */
+
+export const gameConfig = {
+ gameId: "ordenacao",
+ gameName: "Ordenação",
+ type: "blocks",
+ icon: "🔢",
+ thumbnail: "/images/atividades/programacao/ordenacao-thumbnail.png",
+ descricao: "Ordene os números em ordem crescente usando lógica e estruturas de repetição.",
+ dificuldade: "Avançado",
+ categoria: "Lógica",
+ tempoEstimado: "20-40 min",
+ conceitos: [
+ "Listas",
+ "Comparação de Valores",
+ "Estruturas Condicionais",
+ "Estruturas de Repetição",
+ "Algoritmos de Ordenação",
+ ],
+ route: "/atividades/programacao/ordenacao",
+ component: "OrdenacaoGame",
+ objectives: [
+ "Entender como comparar elementos em uma lista",
+ "Usar estruturas condicionais para tomar decisões",
+ "Implementar loops para repetir ações",
+ "Aplicar o algoritmo Bubble Sort para ordenar números",
+ ],
+ metadata: {
+ lastUpdated: "2026-05-12",
+ version: "1.0.0",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Ordenar 2 Números",
+ descricao: "Use uma estrutura condicional para comparar e trocar, se necessário, 2 números.",
+ maxBlocks: 10,
+ lista: [2, 1],
+ validation: {
+ mode: "result_only",
+ messages: {
+ success: "Ótimo! Os dois números estão na ordem certa.",
+ listaDesordenada: "Os números ainda não estão em ordem crescente. Dica: compare o primeiro com o segundo e troque se o maior vier antes.",
+ valoresAlterados: "Um dos valores foi perdido ou duplicado. Certifique-se de trocar os elementos sem apagar nenhum.",
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_comparar",
+ "ord_lista_trocar",
+ "controls_if",
+ "math_number",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 2,
+ nome: "Ordenar 3 Números",
+ descricao: "Use estruturas de repetição e condicionais para ordenar 3 números.",
+ maxBlocks: 22,
+ lista: [3, 1, 2],
+ validation: {
+ mode: "result_only",
+ messages: {
+ success: "Muito bem! Os três números estão em ordem crescente.",
+ listaDesordenada: "A lista ainda não está ordenada. Verifique se você está comparando todos os pares necessários e repetindo as comparações o suficiente.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. Troque apenas as posições dos elementos, sem sobrescrever nenhum deles.",
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_comparar",
+ "ord_lista_trocar",
+ "controls_if",
+ "ord_for_custom",
+ "logic_compare",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 3,
+ nome: "Bubble Sort",
+ descricao: "Compare pares adjacentes e troque quando estiverem fora de ordem, repetindo as passadas até que nenhuma troca seja necessária.",
+ maxBlocks: 46,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ // A partir da fase 3, a assinatura do algoritmo é SEMPRE validada.
+ // O aluno pode escrever sua solução com variações de estilo, mas
+ // o padrão comportamental (comparações e trocas adjacentes, passadas
+ // com limite decrescente) deve estar presente na trilha de execução.
+ mode: "specific_algorithm",
+ expectedAlgorithm: "bubble",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Excelente! Você implementou o Bubble Sort corretamente.",
+ listaDesordenada: "A lista não ficou ordenada. O Bubble Sort precisa comparar todos os pares adjacentes em cada passada até que nenhuma troca seja necessária.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. Verifique se você está trocando os elementos corretamente, sem sobrescrever nenhum deles.",
+ algoritmoNaoDetectado: "Não consegui identificar um padrão de ordenação no seu código. O Bubble Sort compara posições vizinhas (j e j+1) e troca quando estão fora de ordem — em repetidas passadas.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Bubble Sort. Lembre-se: o Bubble Sort só troca elementos lado a lado (adjacentes), passada por passada.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "ord_lista_comparar",
+ "ord_lista_trocar",
+ "controls_if",
+ "controls_while",
+ "ord_for_custom",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 3,
+ nome: "Bubble Sort com Otimização",
+ descricao: "Adicione ao Bubble Sort uma variável que encerra as passadas assim que nenhuma troca ocorrer.",
+ maxBlocks: 56,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ // Variação do Bubble Sort: aceita a versão com flag de parada antecipada.
+ // A assinatura continua sendo bubble (comparações e trocas adjacentes),
+ // mas a presença da otimização é incentivada, não obrigatória.
+ mode: "specific_algorithm",
+ expectedAlgorithm: "bubble",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Perfeito! Bubble Sort com otimização implementado corretamente. Se você usou uma variável para detectar quando nenhuma troca ocorre, ainda melhor!",
+ listaDesordenada: "A lista não ficou ordenada. Verifique se as passadas cobrem todos os pares adjacentes e se o loop externo repete o suficiente.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. A troca de dois elementos exige uma variável auxiliar ou desestruturação — verifique seu bloco de troca.",
+ algoritmoNaoDetectado: "Não consegui identificar o padrão do Bubble Sort. Certifique-se de comparar e trocar pares adjacentes (posições j e j+1) em laços aninhados.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Bubble Sort. Nesta fase o Bubble Sort é obrigatório — compare apenas elementos lado a lado e use passadas repetidas.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "ord_lista_comparar",
+ "ord_lista_trocar",
+ "logic_boolean",
+ "logic_negate",
+ "controls_if",
+ "controls_while",
+ "ord_for_custom",
+ "controls_flow_statements",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 5,
+ nome: "Selection Sort",
+ descricao: "Implemente o Selection Sort: para cada posição, encontre o menor elemento da parte não ordenada e coloque-o no lugar certo.",
+ maxBlocks: 50,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ mode: "specific_algorithm",
+ expectedAlgorithm: "selection",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Ótimo! Você implementou o Selection Sort corretamente.",
+ listaDesordenada: "A lista não ficou ordenada. No Selection Sort, para cada posição você deve varrer o restante da lista inteira para achar o menor elemento e colocá-lo no lugar certo.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. Verifique se a troca entre a posição atual e o mínimo encontrado está correta.",
+ algoritmoNaoDetectado: "Não consegui identificar um padrão de ordenação no seu código. O Selection Sort usa uma varredura interna para encontrar o mínimo a cada passada — sem esse passo, o algoritmo não está completo.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Selection Sort. Lembre-se: o Selection Sort encontra o menor elemento da parte não ordenada e o posiciona com uma única troca por passada.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "ord_lista_comparar",
+ "ord_lista_trocar",
+ "controls_if",
+ "ord_for_custom",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 6,
+ nome: "Insertion Sort",
+ descricao: "Implemente o Insertion Sort: pegue cada elemento e insira-o na posição correta dentro da parte já ordenada, deslocando os demais.",
+ maxBlocks: 60,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ mode: "specific_algorithm",
+ expectedAlgorithm: "insertion",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Muito bem! Você implementou o Insertion Sort corretamente.",
+ listaDesordenada: "A lista não ficou ordenada. No Insertion Sort, cada elemento deve ser inserido na posição correta dentro da parte já ordenada, deslocando os elementos maiores para a direita.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. Cuidado ao deslocar elementos — você deve mover, não apagar.",
+ algoritmoNaoDetectado: "Não consegui identificar um padrão de ordenação no seu código. O Insertion Sort percorre a lista para trás a partir do elemento atual, deslocando os maiores até encontrar o lugar certo para inserir.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Insertion Sort. Lembre-se: o Insertion Sort retrocede na parte ordenada deslocando elementos, ao invés de fazer varreduras completas ou trocas adjacentes repetidas.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "logic_operation",
+ "ord_lista_ler",
+ "ord_lista_escrever",
+ "controls_if",
+ "controls_while",
+ "ord_for_custom",
+ "logic_boolean",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 7,
+ nome: "Shell Sort",
+ descricao: "Compare elementos separados por metade do tamanho da lista, reduza esse intervalo pela metade a cada rodada e finalize com uma passada completa adjacente.",
+ maxBlocks: 76,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ mode: "specific_algorithm",
+ expectedAlgorithm: "shell",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Parabéns! Shell Sort implementado corretamente.",
+ listaDesordenada: "A lista não ficou ordenada. Verifique se o intervalo é reduzido corretamente a cada rodada e se, quando intervalo = 1, o comportamento equivale ao Insertion Sort.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. Verifique o deslocamento de elementos dentro do loop interno — nenhum valor deve ser apagado.",
+ algoritmoNaoDetectado: "Não consegui identificar o padrão do Shell Sort. O algoritmo deve usar uma variável de intervalo que começa em tamanho / 2 e é dividida pela metade a cada rodada, realizando inserção com esse intervalo.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Shell Sort. O Shell Sort se distingue pelo uso de um intervalo maior que 1 nas passadas iniciais, reduzindo-o progressivamente.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "logic_operation",
+ "ord_lista_ler",
+ "ord_lista_escrever",
+ "controls_if",
+ "controls_while",
+ "ord_for_custom",
+ "logic_boolean",
+ "math_number",
+ "math_arithmetic",
+ "math_round",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+ {
+ id: 8,
+ nome: "Counting Sort",
+ descricao: "Conte quantas vezes cada valor aparece e reconstrua a lista a partir dessas contagens, sem comparar elementos entre si.",
+ maxBlocks: 84,
+ lista: [34, 2, 8, 1, 0, 55, 3, 13, 21, 5],
+ validation: {
+ mode: "specific_algorithm",
+ expectedAlgorithm: "counting",
+ allowEquivalentStrategies: false,
+ messages: {
+ success: "Excelente! Counting Sort implementado corretamente.",
+ listaDesordenada: "A lista não ficou ordenada. Verifique se você está contando todos os valores, acumulando as contagens e reconstruindo a lista na ordem correta.",
+ valoresAlterados: "Algum valor foi perdido ou duplicado. A lista de contagem deve registrar exatamente quantas vezes cada valor aparece — sem incrementar ou decrementar errado.",
+ algoritmoNaoDetectado: "Não consegui identificar o padrão do Counting Sort. O algoritmo precisa de uma lista auxiliar de contagem e da reconstrução da lista de saída a partir dessas contagens — sem comparar elementos entre si.",
+ algoritmoErrado: (detectado) => `A lista ficou ordenada, mas o padrão detectado foi "${detectado}", não Counting Sort. O Counting Sort não compara elementos: ele conta ocorrências e reconstrói a lista a partir dessas frequências.`,
+ },
+ },
+ allowedBlocks: [
+ "ord_lista_tamanho",
+ "logic_compare",
+ "ord_lista_ler",
+ "ord_lista_escrever",
+ "ord_lista_contagem_escrever",
+ "ord_lista_contagem_ler",
+ "controls_if",
+ "controls_while",
+ "ord_for_custom",
+ "logic_boolean",
+ "math_number",
+ "math_arithmetic",
+ "variables",
+ ],
+ toolbox: null,
+ starterBlock: null,
+ },
+]
+};
diff --git a/app/src/atividades/programacao/ordenacao/config/debugSolutions.js b/app/src/atividades/programacao/ordenacao/config/debugSolutions.js
new file mode 100644
index 0000000..22383d0
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/config/debugSolutions.js
@@ -0,0 +1,10 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.ordenacao.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {"blocks":{"languageVersion":0,"blocks":[{"type":"controls_if","id":"e,xo_OLQcg4`-7F=ki1*","x":63,"y":63,"inputs":{"IF0":{"block":{"type":"ord_lista_comparar","id":"$X[GkGv|GNi~;!-A;xoZ","fields":{"OP":">"},"inputs":{"I":{"shadow":{"type":"math_number","id":"FHRQ^ihfSG4[Px=X5Ba+","fields":{"NUM":0}}},"J":{"shadow":{"type":"math_number","id":"~!B~5iVm;f-`)31L`x|R","fields":{"NUM":1}}}}}},"DO0":{"block":{"type":"ord_lista_trocar","id":"5MO#fT_;T.X+n8t8GHd-","inputs":{"I":{"shadow":{"type":"math_number","id":"GIU_w#JaASvT}Fq4/*x+","fields":{"NUM":0}}},"J":{"shadow":{"type":"math_number","id":"Il[T(=U|HIn2VkJ:;|?*","fields":{"NUM":1}}}}}}}}]}},
+ 2: {"blocks":{"languageVersion":0,"blocks":[{"type":"controls_if","id":"q~9tdS{/)IX5$,3+of8%","x":38,"y":113,"inputs":{"IF0":{"block":{"type":"ord_lista_comparar","id":"atSU|Udv;ZGR4[iv),Jw","fields":{"OP":">"},"inputs":{"I":{"shadow":{"type":"math_number","id":"0WJR[W:k3CGIh/H3W5uE","fields":{"NUM":0}}},"J":{"shadow":{"type":"math_number","id":"n|lEuq;UnrBF0oJ3mZup","fields":{"NUM":1}}}}}},"DO0":{"block":{"type":"ord_lista_trocar","id":"ba4Xdb}]i-h3Zx$cssq~","inputs":{"I":{"shadow":{"type":"math_number","id":"YMuk@+#4:j{yMB]!$n{U","fields":{"NUM":0}}},"J":{"shadow":{"type":"math_number","id":"{nSr.@]z`)7^fi5cy~U3","fields":{"NUM":1}}}}}}},"next":{"block":{"type":"controls_if","id":"gk!!{luM(x3{G*@GB/s4","inputs":{"IF0":{"block":{"type":"ord_lista_comparar","id":"@*oB#rAIMJ[~,w^26[Et","fields":{"OP":">"},"inputs":{"I":{"shadow":{"type":"math_number","id":",ANXZH2T{rE]GgvZazb_","fields":{"NUM":1}}},"J":{"shadow":{"type":"math_number","id":"MptQ7#HLG2^/a=_PV6ZD","fields":{"NUM":2}}}}}},"DO0":{"block":{"type":"ord_lista_trocar","id":"AFo_B|qcw4%2c2%Xm4gZ","inputs":{"I":{"shadow":{"type":"math_number","id":"4n+#~Wn{6uG.MrNRbw)%","fields":{"NUM":1}}},"J":{"shadow":{"type":"math_number","id":"xdt{X;X(GI`imbbny*4~","fields":{"NUM":2}}}}}}}}}}]},"variables":[{"name":"i","id":"~]Mhx,?ZvaHDeMx7)%~z"}]},,
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/ordenacao/config/starterBlock.js b/app/src/atividades/programacao/ordenacao/config/starterBlock.js
new file mode 100644
index 0000000..76e30e9
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/config/starterBlock.js
@@ -0,0 +1,121 @@
+/**
+ * @fileoverview Blocos iniciais por fase do jogo Ordenação.
+ * @module games.ordenacao.config.starterBlock
+ *
+ * Observação:
+ * Mantemos starters mínimos para não impor solução pronta. O objetivo é apenas
+ * evitar workspace totalmente vazio em fases iniciais sem usar tipos inválidos.
+ */
+export const starterBlocks = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_if",
+ id: "start_f1_if",
+ x: 30,
+ y: 30,
+ inputs: {
+ IF0: {
+ block: {
+ type: "ord_lista_comparar",
+ id: "start_f1_cmp",
+ fields: { OP: ">" },
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "ord_lista_trocar",
+ id: "start_f1_swap",
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "controls_if",
+ id: "start_f2_if_1",
+ x: 30,
+ y: 30,
+ inputs: {
+ IF0: {
+ block: {
+ type: "ord_lista_comparar",
+ id: "start_f2_cmp_1",
+ fields: { OP: ">" },
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "ord_lista_trocar",
+ id: "start_f2_swap_1",
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 0 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "controls_if",
+ id: "start_f2_if_2",
+ inputs: {
+ IF0: {
+ block: {
+ type: "ord_lista_comparar",
+ id: "start_f2_cmp_2",
+ fields: { OP: ">" },
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 2 } } },
+ },
+ },
+ },
+ DO0: {
+ block: {
+ type: "ord_lista_trocar",
+ id: "start_f2_swap_2",
+ inputs: {
+ I: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ J: { shadow: { type: "math_number", fields: { NUM: 2 } } },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+
+ 3: null,
+ 4: null,
+ 5: null,
+ 6: null,
+ 7: null,
+ 8: null,
+ 9: null,
+ 10: null,
+};
diff --git a/app/src/atividades/programacao/ordenacao/estudo.md b/app/src/atividades/programacao/ordenacao/estudo.md
new file mode 100644
index 0000000..2c3452b
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/estudo.md
@@ -0,0 +1,327 @@
+# Estudo: Arquitetura para validar múltiplos algoritmos de ordenação
+
+## Contexto
+
+A atividade de Ordenação não deve ficar presa apenas ao Bubble Sort. O objetivo é permitir que estudantes resolvam com diferentes estratégias (Bubble, Selection, Insertion, etc.) e que a validação identifique:
+
+1. Se a saída está correta.
+2. Se os valores foram preservados.
+3. Qual padrão de algoritmo foi usado (sempre).
+
+Além disso, os blocos de lista devem ser alinhados com programação real: índice iniciando em 0.
+
+---
+
+## Problemas atuais
+
+1. Validação baseada apenas no resultado final não distingue algoritmo.
+2. Blocos padrão de lista do Blockly mascaram indexação e confundem o aluno.
+3. Não existe trilha de execução estruturada para inferir estratégia.
+
+---
+
+## Objetivo da proposta
+
+Criar uma arquitetura com três pilares:
+
+1. Blocos de lista customizados zero-based.
+2. Instrumentação de execução (trilha de eventos).
+3. Engine de validação por perfis de algoritmo.
+
+Essa arquitetura permite evoluir de forma incremental: primeiro validar resultado + rastrear eventos, depois ligar detectores de algoritmos por fase.
+
+---
+
+## 1) Design de blocos (genérico para vários algoritmos)
+
+### Blocos essenciais (zero-based)
+
+- `lista_tamanho()`
+- `lista_ler(indice)`
+- `lista_escrever(indice, valor)`
+- `lista_trocar(i, j)`
+- `lista_comparar(i, j, operador)`
+
+### Regras
+
+1. Índice sempre começa em 0.
+2. Leitura/escrita fora do intervalo gera erro pedagógico claro.
+3. Cada operação relevante gera evento de trilha (trace).
+
+### Vantagem
+
+Com esses blocos, o aluno consegue implementar Bubble, Selection, Insertion, Shell, etc., sem ficar preso a um único algoritmo.
+
+---
+
+## 2) Modelo de trilha de execução (Execution Trace)
+
+### Evento base
+
+```js
+{
+ type: "compare" | "swap" | "read" | "write" | "pass_start" | "pass_end",
+ i: number,
+ j?: number,
+ value?: number,
+ pass?: number,
+ snapshot?: number[]
+}
+```
+
+### Exemplo de traço do Bubble Sort
+
+```js
+[
+ { type: "pass_start", pass: 0 },
+ { type: "compare", i: 0, j: 1 },
+ { type: "swap", i: 0, j: 1 },
+ { type: "compare", i: 1, j: 2 },
+ { type: "pass_end", pass: 0 }
+]
+```
+
+### Observação
+
+Não é necessário salvar snapshot em todos os eventos (custo alto). Pode salvar apenas em `pass_end` ou em modo debug.
+
+---
+
+## 3) Engine de validação unificada
+
+### Interface proposta
+
+```js
+class SortingValidationEngine {
+ validate({ initialList, finalList, trace, expectedAlgorithm, gameConfig }) {
+ // 1) integridade de valores
+ // 2) ordenação final
+ // 3) detecção obrigatória do algoritmo esperado
+ // 4) retorno padronizado
+ }
+}
+```
+
+### Retorno padrão
+
+```js
+{
+ success: boolean,
+ message: string,
+ detectedAlgorithm: "bubble" | "selection" | "insertion" | "unknown",
+ checks: {
+ valuesPreserved: boolean,
+ sorted: boolean,
+ algorithmMatch: boolean
+ },
+ debug?: {
+ compareCount: number,
+ swapCount: number,
+ traceSize: number
+ }
+}
+```
+
+---
+
+## 4) Estratégia de detecção por perfis
+
+A detecção deve ser por "assinatura comportamental" (heurística forte), não por comparação de código fonte.
+
+### 4.1 Bubble Sort (assinatura)
+
+Critérios fortes:
+
+1. Comparações majoritariamente adjacentes (`j = i + 1`).
+2. Trocas apenas adjacentes.
+3. Passadas com limite superior decrescente.
+4. Estado final ordenado.
+
+### 4.2 Selection Sort (assinatura)
+
+Critérios fortes:
+
+1. Para cada posição `i`, varredura de `j = i+1 .. n-1`.
+2. No máximo uma troca "principal" por passada externa.
+3. Troca pode ser não adjacente (`swap(i, minIndex)`).
+
+### 4.3 Insertion Sort (assinatura)
+
+Critérios fortes:
+
+1. Comparações caminhando para trás (`j--`) na sublista ordenada.
+2. Sequência de deslocamentos (`write`) antes da inserção final da chave.
+3. Poucas trocas diretas; mais escrita de valores.
+
+### Importante: variações de implementação são esperadas
+
+Um mesmo algoritmo pode ser escrito de formas diferentes e ainda ser correto. O detector **não deve exigir uma implementação canônica**, mas sim verificar se o comportamento observado na trilha respeita os invariantes do algoritmo.
+
+Exemplos de variações legítimas que devem ser aceitas:
+
+| Algoritmo | Variação aceita |
+|---|---|
+| Bubble Sort | Com ou sem flag de parada antecipada (otimização) |
+| Bubble Sort | Loop externo crescente ou decrescente de limite |
+| Selection Sort | Encontrar mínimo ou máximo (ordenando do início ou do fim) |
+| Insertion Sort | Deslocamento com escrita ou com trocas adjacentes |
+| Shell Sort | Qualquer sequência de gaps válida (n/2, Hibbard, etc.) |
+
+O que **não** deve ser aceito:
+- Comparações não adjacentes em algoritmo declarado como Bubble Sort.
+- Ausência de varredura interna em Selection Sort (sem busca do mínimo).
+- Insertion Sort sem retrocesso na sublista ordenada.
+
+A heurística deve usar **combinação de critérios** e tolerar ruído (variáveis auxiliares extras, loops com limites ligeiramente diferentes), mas exigir os invariantes centrais.
+
+### Importante: algoritmos avançados no currículo atual
+
+Para Merge/Quick/Heap em modo blocos iniciante, a detecção robusta pode ser mais difícil. Recomendação:
+
+1. Introduzir esses algoritmos em fases avançadas com blocos adicionais.
+2. Nesses casos, usar validação por "família" (divide and conquer, heap-based) + métricas.
+
+---
+
+## 5) Modos de validação por fase
+
+Cada fase deve declarar explicitamente qual algoritmo é cobrado, e a validação deve exigir correspondência de assinatura:
+
+```js
+validation: {
+ mode: "specific_algorithm",
+ expectedAlgorithm: "bubble",
+ allowEquivalentStrategies: false // por padrão, deve permanecer false
+}
+```
+
+### Exemplos
+
+1. Fase de Bubble: `specific_algorithm` com `bubble`.
+2. Fase de Selection: `specific_algorithm` com `selection`.
+3. Fase de Insertion: `specific_algorithm` com `insertion`.
+
+### Regra pedagógica
+
+Não aceitar apenas "lista ordenada" quando a fase cobra um algoritmo específico. O aluno deve demonstrar compreensão da estratégia pedida, implementando sua lógica com blocos, sem função pronta.
+
+---
+
+## 6) POC antes dos blocos (recomendado)
+
+Antes de acoplar ao Blockly, montar uma POC só de validação usando uma classe de lista instrumentada.
+
+## Escopo da POC
+
+1. Receber função de solução em JS puro.
+2. Expor API de lista instrumentada (`read`, `write`, `swap`, `compare`).
+3. Capturar trace.
+4. Rodar engine de validação.
+5. Exibir resultado e algoritmo detectado.
+
+### Adaptação para a atividade real (estado atual)
+
+Hoje a atividade usa blocos nativos (`lists_getIndex`, `lists_setIndex`) sobre a variável `lista`, e não a API idealizada (`lista_ler`, `lista_escrever`, `lista_trocar`, `lista_comparar`).
+
+Por isso, a instrumentação da versão real deve ocorrer no **Proxy da lista** dentro do `interpreterSetup`:
+
+1. `get` em índice numérico gera evento `read`.
+2. `set` em índice numérico gera evento `write`.
+3. Duas escritas que caracterizam troca (`a <- b` seguido de `b <- a`) geram evento `swap` inferido.
+
+Com essa estratégia, a engine já pode validar assinatura de algoritmo sem exigir refatoração imediata dos blocos.
+
+### Limitações conhecidas da adaptação real
+
+1. Eventos `compare` não existem explicitamente com blocos nativos; alguns perfis dependem de heurística baseada em `read/write/swap`.
+2. Detecção é robusta para Bubble, Selection, Insertion, Shell e Counting no cenário didático atual, mas pode exigir ajuste fino para soluções muito criativas.
+3. A migração para blocos zero-based customizados continua recomendada para aumentar precisão da trilha.
+
+### Estrutura sugerida
+
+- `src/atividades/programacao/ordenacao/validation/core/ExecutionTrace.js`
+- `src/atividades/programacao/ordenacao/validation/core/InstrumentedList.js`
+- `src/atividades/programacao/ordenacao/validation/core/SortingValidationEngine.js`
+- `src/atividades/programacao/ordenacao/validation/profiles/bubbleProfile.js`
+- `src/atividades/programacao/ordenacao/validation/profiles/selectionProfile.js`
+- `src/atividades/programacao/ordenacao/validation/profiles/insertionProfile.js`
+- `src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js`
+
+### Exemplo didático da POC
+
+```js
+import { InstrumentedList } from "../core/InstrumentedList.js";
+import { SortingValidationEngine } from "../core/SortingValidationEngine.js";
+
+function bubbleSolution(vm) {
+ const n = vm.length();
+ for (let i = 0; i < n - 1; i++) {
+ for (let j = 0; j < n - 1 - i; j++) {
+ if (vm.compare(j, j + 1, ">")) {
+ vm.swap(j, j + 1);
+ }
+ }
+ }
+}
+
+const initial = [5, 2, 8, 1, 9];
+const vm = new InstrumentedList(initial);
+bubbleSolution(vm);
+
+const engine = new SortingValidationEngine();
+const result = engine.validate({
+ initialList: initial,
+ finalList: vm.toArray(),
+ trace: vm.getTrace(),
+ expectedAlgorithm: "bubble"
+});
+
+console.log(result);
+```
+
+---
+
+## 7) Integração progressiva com o jogo
+
+1. Implementar POC standalone e validar detectores.
+2. Conectar `InstrumentedList` ao `interpreterSetup` da atividade.
+3. Atualizar `validators.js` para delegar ao `SortingValidationEngine`.
+4. Trocar blocos nativos de lista por blocos customizados zero-based.
+5. Configurar por fase qual algoritmo deve ser aceito.
+
+---
+
+## 8) Riscos e mitigação
+
+1. Heurísticas podem gerar falso positivo.
+Mitigação: usar combinação de critérios e testes de regressão.
+
+2. Soluções criativas podem fugir da assinatura esperada.
+Mitigação: configurar `allowEquivalentStrategies` em fases específicas.
+
+3. Custo de instrumentação em runtime.
+Mitigação: snapshot opcional e modo debug desligado em produção.
+
+---
+
+## 9) Recomendação prática para iniciar
+
+MVP da validação multi-algoritmo:
+
+1. Perfis: Bubble, Selection e Insertion.
+2. Modo de fase: `specific_algorithm`.
+3. Reprovar quando a assinatura não corresponder ao algoritmo cobrado.
+4. Mensagens pedagógicas claras do porquê falhou.
+
+Com isso, a aplicação já suporta múltiplas estratégias com validação orientada ao objetivo da fase.
+
+---
+
+## 10) Critério de pronto da POC
+
+A POC será considerada pronta quando:
+
+1. Detectar corretamente Bubble, Selection e Insertion em casos válidos.
+2. Rejeitar ao menos 3 casos de falso positivo por perfil.
+3. Retornar mensagem pedagógica clara em todos os cenários.
+4. Produzir relatório simples (`detectedAlgorithm`, `checks`, `message`).
diff --git a/app/src/atividades/programacao/ordenacao/game.js b/app/src/atividades/programacao/ordenacao/game.js
new file mode 100644
index 0000000..90ce57c
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/game.js
@@ -0,0 +1,285 @@
+/**
+ * @fileoverview Cena Phaser para o jogo Ordenação.
+ * Responsabilidades:
+ * - Renderizar a lista como barras coloridas
+ * - Expor a lista ao interpretador
+ * - Receber atualizações da lista durante a execução
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupOrdenacaoAPI } from "./hooks/interpreterSetup.js";
+import { validationSolution } from "./validation/validators.js";
+import { gameConfig } from "./config/config.js";
+import { ConstantesJogo, BAR_W, MIN_H, MAX_H, GROUND_Y, Cores } from "./ui/constants.js";
+
+const { LARGURA_TELA: W, ALTURA_TELA: H } = ConstantesJogo;
+const BAR_GAP = 20;
+const SPACING = BAR_W + BAR_GAP;
+const DELAY_COMPARE_MS = 70;
+const DELAY_WRITE_MS = 90;
+const DELAY_SWAP_MS = 170;
+
+class OrdenacaoScene extends BaseGameScene {
+ constructor() {
+ super("OrdenacaoScene");
+ this.lista = [];
+ this.contagem = [];
+ this.listaInicial = [];
+ this.barras = [];
+ this._labelResultado = null;
+ this._passos = 0;
+ this._segundos = 0;
+ this._timerEvento = null;
+ this._timeDisplay = null;
+ this._stepsDisplay = null;
+ }
+
+ init(data) {
+ super.init(data);
+ this.listaInicial = [...(this.configFase?.lista ?? [2, 1])];
+ this.lista = [...this.listaInicial];
+ }
+
+ preload() {
+ this.preloadGlobalAssets();
+ }
+
+ create() {
+ this.setupStandardController(
+ setupOrdenacaoAPI,
+ (historico) => validationSolution(historico, this.configFase, gameConfig, this)
+ );
+ this._criarHeaderDom();
+ this._criarBarras();
+ }
+
+ _criarHeaderDom() {
+ const container = document.createElement("div");
+ container.innerHTML = `
+
+ `;
+ this.add.dom(W / 2, 48, container).setDepth(20);
+ this._timeDisplay = container.querySelector("#ord-time");
+ this._stepsDisplay = container.querySelector("#ord-steps");
+ }
+
+ _atualizarDisplays() {
+ if (this._timeDisplay) {
+ const m = Math.floor(this._segundos / 60).toString().padStart(2, "0");
+ const s = (this._segundos % 60).toString().padStart(2, "0");
+ this._timeDisplay.textContent = `${m}:${s}`;
+ }
+ if (this._stepsDisplay) {
+ this._stepsDisplay.textContent = String(this._passos).padStart(4, "0");
+ }
+ }
+
+ _iniciarTimer() {
+ this._pararTimer();
+ this._timerEvento = this.time.addEvent({
+ delay: 1000,
+ callback: () => {
+ this._segundos++;
+ this._atualizarDisplays();
+ },
+ loop: true,
+ });
+ }
+
+ _pararTimer() {
+ if (this._timerEvento) {
+ this._timerEvento.remove();
+ this._timerEvento = null;
+ }
+ }
+
+ _criarBarras() {
+ const n = this.lista.length;
+ const maxVal = Math.max(...this.lista);
+ const minVal = Math.min(...this.lista);
+ const range = maxVal - minVal || 1;
+ const totalW = n * BAR_W + (n - 1) * BAR_GAP;
+ const startX = (W - totalW) / 2 + BAR_W / 2;
+
+ this.barras = this.lista.map((valor, i) => {
+ const x = startX + i * SPACING;
+ const height = MIN_H + ((valor - minVal) / range) * (MAX_H - MIN_H);
+ const hue = (valor / (maxVal + 1)) * 0.72;
+ const color = Phaser.Display.Color.HSVToRGB(hue, 0.85, 0.92).color;
+ const container = this.add.container(x, GROUND_Y);
+ const rect = this.add.rectangle(0, -height / 2, BAR_W, height, color)
+ .setStrokeStyle(2, Cores.BORDA_PADRAO);
+ const numLabel = this.add.text(0, -height - 14, String(valor), {
+ fontSize: "22px", fontFamily: "Arial Black, sans-serif", color: "#FFFFFF",
+ stroke: "#222222", strokeThickness: 4,
+ }).setOrigin(0.5, 1);
+ const idxLabel = this.add.text(0, 14, String(i), {
+ fontSize: "13px", fontFamily: "Arial, sans-serif", color: "#E8E8E8",
+ }).setOrigin(0.5, 0.5);
+ container.add([rect, numLabel, idxLabel]);
+ return { container, rect, numLabel, idxLabel, valor };
+ });
+ }
+
+ _recriarBarras() {
+ this.tweens.killAll();
+ this.barras.forEach((b) => b.container.destroy());
+ this.barras = [];
+ this._criarBarras();
+ }
+
+ onBeforeRun() {
+ this.historico = [];
+ this.lista = [...this.listaInicial];
+ this._recriarBarras();
+ this._limparLabel();
+ this._passos = 0;
+ this._segundos = 0;
+ this._atualizarDisplays();
+ this._iniciarTimer();
+ }
+
+ onReset() {
+ this.lista = [...this.listaInicial];
+ this._recriarBarras();
+ this.historico = [];
+ this._limparLabel();
+ this._pararTimer();
+ this._passos = 0;
+ this._segundos = 0;
+ this._atualizarDisplays();
+ }
+
+ async onSuccess() {
+ this._pararTimer();
+ for (let i = 0; i < this.barras.length; i++) {
+ this.barras[i].rect.setFillStyle(Cores.ORDENADO);
+ this.barras[i].rect.setStrokeStyle(3, 0xFFFFFF);
+ await this._sleep(90);
+ }
+ this._labelResultado = this.add.text(W / 2, 36, "✓ Lista Ordenada!", {
+ fontSize: "26px", fontFamily: "Arial Black, sans-serif",
+ color: "#00FF88", stroke: "#003322", strokeThickness: 5,
+ }).setOrigin(0.5, 0).setDepth(10);
+ }
+
+ async onFailure() {
+ this._pararTimer();
+ for (const b of this.barras) {
+ b.rect.setFillStyle(Cores.ERRO);
+ }
+ await this._sleep(600);
+ this._recriarBarras();
+ }
+
+ _setHighlight(index, color) {
+ if (!this.barras[index]) return;
+ if (color === null) {
+ this.barras[index].rect.setStrokeStyle(2, Cores.BORDA_PADRAO);
+ } else {
+ this.barras[index].rect.setStrokeStyle(4, color);
+ }
+ }
+
+ async animarComparacao(i, j) {
+ this._passos++;
+ this._atualizarDisplays();
+ this._setHighlight(i, 0xffeb3b);
+ this._setHighlight(j, 0xffeb3b);
+ await this._sleep(DELAY_COMPARE_MS);
+ this._setHighlight(i, null);
+ this._setHighlight(j, null);
+ }
+
+ async animarEscrita(i) {
+ this._passos++;
+ this._atualizarDisplays();
+ // Recria barras para refletir altura/valor atualizados na lista.
+ this._recriarBarras();
+ this._setHighlight(i, 0xff9800);
+ await this._sleep(DELAY_WRITE_MS);
+ this._setHighlight(i, null);
+ }
+
+ async animarTroca(i, j) {
+ this._passos++;
+ this._atualizarDisplays();
+ const barraI = this.barras[i];
+ const barraJ = this.barras[j];
+ if (!barraI || !barraJ || i === j) return;
+
+ this._setHighlight(i, 0xffd54f);
+ this._setHighlight(j, 0xffd54f);
+
+ const xI = barraI.container.x;
+ const xJ = barraJ.container.x;
+
+ await new Promise((resolve) => {
+ let done = 0;
+ const onComplete = () => {
+ done += 1;
+ if (done === 2) resolve();
+ };
+
+ this.tweens.add({
+ targets: barraI.container,
+ x: xJ,
+ duration: DELAY_SWAP_MS,
+ ease: "Sine.easeInOut",
+ onComplete,
+ });
+
+ this.tweens.add({
+ targets: barraJ.container,
+ x: xI,
+ duration: DELAY_SWAP_MS,
+ ease: "Sine.easeInOut",
+ onComplete,
+ });
+ });
+
+ // Após a animação, reconstrói visual com a ordem atual da lista.
+ this._recriarBarras();
+ }
+
+ _limparLabel() {
+ if (this._labelResultado) {
+ this._labelResultado.destroy();
+ this._labelResultado = null;
+ }
+ }
+
+ _sleep(ms) {
+ return new Promise((resolve) => this.time.delayedCall(ms, resolve));
+ }
+}
+
+export const createGame = (elementoPai, configFaseAtual) => {
+ const scene = new OrdenacaoScene();
+ return {
+ type: Phaser.AUTO,
+ width: ConstantesJogo.LARGURA_TELA,
+ height: ConstantesJogo.ALTURA_TELA,
+ backgroundColor: ConstantesJogo.COR_FUNDO,
+ parent: elementoPai,
+ dom: { createContainer: true },
+ scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
+ scene,
+ callbacks: {
+ preBoot(game) {
+ game.registry.set("configFase", configFaseAtual);
+ game.registry.set("gameConfig", gameConfig);
+ },
+ },
+ };
+};
diff --git a/app/src/atividades/programacao/ordenacao/hooks/interpreterSetup.js b/app/src/atividades/programacao/ordenacao/hooks/interpreterSetup.js
new file mode 100644
index 0000000..130c452
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/hooks/interpreterSetup.js
@@ -0,0 +1,207 @@
+/**
+ * @fileoverview Configuração da API do interpretador para o jogo Ordenação.
+ * @module games.ordenacao.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+import { createTraceableArrayProxy } from "../validation/core/createTraceableArrayProxy.js";
+
+const toNative = (value) => {
+ if (value && typeof value === "object" && "data" in value) return value.data;
+ return value;
+};
+
+const toIndex = (value) => Number(toNative(value));
+
+const assertIndex = (scene, i, operacao) => {
+ const n = scene.lista.length;
+ if (!Number.isInteger(i) || i < 0 || i >= n) {
+ throw new Error(
+ `Índice inválido ao tentar ${operacao}: posição ${i} está fora dos limites [0, ${n - 1}].`,
+ );
+ }
+};
+
+const assertIndexContagem = (scene, i, operacao) => {
+ if (!Number.isInteger(i) || i < 0) {
+ throw new Error(
+ `Índice inválido ao tentar ${operacao} na contagem: posição ${i} é inválida (deve ser inteiro >= 0).`,
+ );
+ }
+ if (operacao === "ler" && scene.contagem.length > 0 && i >= scene.contagem.length) {
+ const maxIdx = scene.contagem.length - 1;
+ throw new Error(
+ `Índice inválido ao tentar ${operacao} na contagem: posição ${i} está fora dos limites [0, ${maxIdx}].`,
+ );
+ }
+};
+
+/**
+ * Cria um proxy para o array que sincroniza mutações
+ * com scene.lista. Necessário porque interpreter.nativeToPseudo()
+ * cria uma cópia, não uma referência.
+ *
+ * @param {Array} originalArray - o scene.lista original
+ * @param {Object} interpreter - instância do js-interpreter
+ * @returns {Proxy} proxy que reflete mudanças de volta ao original
+ */
+const createArrayProxy = (originalArray, historico) =>
+ createTraceableArrayProxy(originalArray, historico, {
+ trackReads: true,
+ inferSwap: true,
+ });
+
+/**
+ * Registra a API mínima para a atividade de ordenação.
+ *
+ * Funções/valores expostos:
+ * - lista - array com os números da fase (com proxy de sincronização)
+ * - highlightBlock(id) - destaque de bloco (padrão da plataforma)
+ *
+ * @param {Object} scene - Instância de OrdenacaoScene
+ * @returns {Function} Callback (interpreter, globalScope) para GameInterpreter
+ */
+export const setupOrdenacaoAPI = (scene) => {
+ return (interpreter, globalScope) => {
+ // Proxy sincroniza mutações e coleta trilha de eventos no scene.historico.
+ const listaProxy = createArrayProxy(scene.lista, scene.historico);
+
+ interpreter.setProperty(
+ globalScope,
+ "lista",
+ interpreter.nativeToPseudo(listaProxy),
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_tamanho",
+ () => scene.lista.length,
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_ler",
+ (iArg) => {
+ const i = toIndex(iArg);
+ assertIndex(scene, i, "ler");
+ scene.historico.push({ type: "read", i });
+ return scene.lista[i];
+ },
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_escrever",
+ (iArg, valueArg, callback) => {
+ const i = toIndex(iArg);
+ const value = Number(toNative(valueArg));
+ assertIndex(scene, i, "escrever");
+ scene.lista[i] = value;
+ scene.historico.push({ type: "write", i, value });
+
+ Promise.resolve(scene.animarEscrita(i))
+ .then(() => callback())
+ .catch(() => callback());
+ },
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_contagem_ler",
+ (iArg) => {
+ const i = toIndex(iArg);
+ assertIndexContagem(scene, i, "ler");
+ scene.historico.push({ type: "contagem_read", i });
+ return scene.contagem[i] ?? 0;
+ },
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_contagem_escrever",
+ (iArg, valueArg, callback) => {
+ const i = toIndex(iArg);
+ const value = Number(toNative(valueArg));
+ assertIndexContagem(scene, i, "escrever");
+ scene.contagem[i] = value;
+ scene.historico.push({ type: "contagem_write", i, value });
+
+ Promise.resolve(scene.animarEscrita(i))
+ .then(() => callback())
+ .catch(() => callback());
+ },
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_trocar",
+ (iArg, jArg, callback) => {
+ const i = toIndex(iArg);
+ const j = toIndex(jArg);
+ assertIndex(scene, i, "trocar");
+ assertIndex(scene, j, "trocar");
+ const temp = scene.lista[i];
+ scene.lista[i] = scene.lista[j];
+ scene.lista[j] = temp;
+ scene.historico.push({ type: "swap", i, j });
+
+ Promise.resolve(scene.animarTroca(i, j))
+ .then(() => callback())
+ .catch(() => callback());
+ },
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "lista_comparar",
+ (iArg, jArg, opArg, callback) => {
+ const i = toIndex(iArg);
+ const j = toIndex(jArg);
+ const op = String(toNative(opArg));
+ assertIndex(scene, i, "comparar");
+ assertIndex(scene, j, "comparar");
+ scene.historico.push({ type: "compare", i, j });
+
+ const a = scene.lista[i];
+ const b = scene.lista[j];
+ let result;
+ switch (op) {
+ case ">": result = a > b; break;
+ case "<": result = a < b; break;
+ case ">=": result = a >= b; break;
+ case "<=": result = a <= b; break;
+ case "==": result = a === b; break;
+ case "!=": result = a !== b; break;
+ default:
+ throw new Error(`Operador inválido em lista_comparar: ${op}`);
+ }
+
+ Promise.resolve(scene.animarComparacao(i, j))
+ .then(() => callback(result))
+ .catch(() => callback(result));
+ },
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
diff --git a/app/src/atividades/programacao/ordenacao/poc.html b/app/src/atividades/programacao/ordenacao/poc.html
new file mode 100644
index 0000000..56cdece
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/poc.html
@@ -0,0 +1,366 @@
+
+
+
+
+
+ Visualizador de Algoritmos - Didático
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/atividades/programacao/ordenacao/solucoes-testes.md b/app/src/atividades/programacao/ordenacao/solucoes-testes.md
new file mode 100644
index 0000000..7a90c87
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/solucoes-testes.md
@@ -0,0 +1,287 @@
+# Soluções de Teste - Ordenação (JS + Blocos ASCII)
+
+Este arquivo traz soluções funcionais para testar as fases da atividade.
+
+Observações:
+- Todas as soluções usam índice iniciado em 0.
+- Em blocos ASCII, os nomes seguem os blocos customizados da atividade (`ord_lista_*`) e blocos condicionais padrão.
+- **Blocos por fase**:
+ - Fases 1–2: sem loops, apenas `SE` e comparação/troca.
+ - Fases 3–6: usam `ENQUANTO` (while), aritmética, e lógica.
+ - Fase 6 em específico: inclui `logic_boolean` para a variável `trocou` (esse bloco NÃO está disponível na fase 3).
+ - Fases 7+: seguem a mesma lógica das fases anteriores com listas maiores.
+
+---
+
+## Fase 1 - Ordenar 2 números
+
+### JavaScript (funciona)
+```javascript
+if (lista_comparar(0, 1, ">")) {
+ lista_trocar(0, 1);
+}
+```
+
+### Blocos (ASCII)
+```text
+[SE]
+ condicao: [ord_lista_comparar POSICAO:(0) OP:(>) POSICAO:(1)]
+ [ENTAO]
+ [ord_lista_trocar POSICAO:(0) com POSICAO:(1)]
+```
+
+---
+
+## Fase 2 - Ordenar 3 números
+
+### JavaScript (funciona)
+```javascript
+if (lista_comparar(0, 1, ">")) lista_trocar(0, 1);
+if (lista_comparar(1, 2, ">")) lista_trocar(1, 2);
+if (lista_comparar(0, 1, ">")) lista_trocar(0, 1);
+```
+
+### Blocos (ASCII)
+```text
+[SE]
+ condicao: [ord_lista_comparar POSICAO:(0) OP:(>) POSICAO:(1)]
+ [ENTAO]
+ [ord_lista_trocar POSICAO:(0) com POSICAO:(1)]
+
+[SE]
+ condicao: [ord_lista_comparar POSICAO:(1) OP:(>) POSICAO:(2)]
+ [ENTAO]
+ [ord_lista_trocar POSICAO:(1) com POSICAO:(2)]
+
+[SE]
+ condicao: [ord_lista_comparar POSICAO:(0) OP:(>) POSICAO:(1)]
+ [ENTAO]
+ [ord_lista_trocar POSICAO:(0) com POSICAO:(1)]
+```
+
+---
+
+## Fase 3 - Bubble Sort
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+var i = 0;
+while (i < n - 1) {
+ var j = 0;
+ while (j < n - 1 - i) {
+ if (lista_comparar(j, j + 1, ">")) {
+ lista_trocar(j, j + 1);
+ }
+ j = j + 1;
+ }
+ i = i + 1;
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+[PARA i = 0 enquanto [i < [n - 1]] incremento [i + 1]]
+ [PARA j = 0 enquanto [j < [[n - 1] - i]] incremento [j + 1]]
+ [SE lista_comparar(j, >, j+1)]
+ [lista_trocar(j, j+1)]
+```
+
+---
+
+## Fase 4 - Bubble Sort com otimização
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+var i = 0;
+while (i < n - 1) {
+ var trocou = false;
+ var j = 0;
+ while (j < n - 1 - i) {
+ if (lista_comparar(j, j + 1, ">")) {
+ lista_trocar(j, j + 1);
+ trocou = true;
+ }
+ j = j + 1;
+ }
+ if (!trocou) break;
+ i = i + 1;
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+[PARA i = 0 enquanto [i < [n - 1]] incremento [i + 1]]
+ definir trocou <- [FALSO]
+ [PARA j = 0 enquanto [j < [[n - 1] - i]] incremento [j + 1]]
+ [SE lista_comparar(j, >, j+1)]
+ [lista_trocar(j, j+1)]
+ definir trocou <- [VERDADEIRO]
+ [SE [NÃO trocou]]
+ [parar loop]
+```
+
+---
+
+## Fase 5 - Selection Sort
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+for (var i = 0; i < n - 1; i++) {
+ var min = i;
+ for (var j = i + 1; j < n; j++) {
+ if (lista_comparar(j, min, "<")) {
+ min = j;
+ }
+ }
+ if (min != i) {
+ lista_trocar(i, min);
+ }
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+[PARA i = 0 enquanto [i < [n - 1]] incremento [i + 1]]
+ definir min <- i
+ [PARA j = [i + 1] enquanto [j < n] incremento [j + 1]]
+ [SE lista_comparar(j, <, min)]
+ definir min <- j
+ [SE lista_comparar(min, !=, i)]
+ [lista_trocar(i, min)]
+```
+
+---
+
+## Fase 6 - Insertion Sort
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+for (var i = 1; i < n; i++) {
+ var chave = lista_ler(i);
+ var j = i - 1;
+ while (j >= 0 && lista_ler(j) > chave) {
+ lista_escrever(j + 1, lista_ler(j));
+ j = j - 1;
+ }
+ lista_escrever(j + 1, chave);
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+[PARA i = 1 enquanto [i < n] incremento [i + 1]]
+ definir chave <- lista_ler(i)
+ definir j <- [i - 1]
+ [ENQUANTO [[j >= 0] E [lista_ler(j) > chave]]]
+ [lista_escrever([j + 1], lista_ler(j))]
+ definir j <- [j - 1]
+ [lista_escrever([j + 1], chave)]
+```
+
+---
+
+## Fase 7- Shell Sort
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+var gap = Math.floor(n / 2);
+
+while (gap > 0) {
+ for (var i = gap; i < n; i++) {
+ var temp = lista_ler(i);
+ var j = i;
+
+ while (j >= gap && lista_ler(j - gap) > temp) {
+ lista_escrever(j, lista_ler(j - gap));
+ j = j - gap;
+ }
+
+ lista_escrever(j, temp);
+ }
+
+ gap = Math.floor(gap / 2);
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+definir gap <- arredondar_para_baixo([n / 2])
+[ENQUANTO [gap > 0]]
+ [PARA i = gap enquanto [i < n] incremento [i + 1]]
+ definir temp <- lista_ler(i)
+ definir j <- i
+ [ENQUANTO [[j >= gap] E [lista_ler([j - gap]) > temp]]]
+ [lista_escrever(j, lista_ler([j - gap]))]
+ definir j <- [j - gap]
+ [lista_escrever(j, temp)]
+ definir gap <- arredondar_para_baixo([gap / 2])
+```
+
+---
+
+## Fase 8 - Counting Sort
+
+### JavaScript (funciona)
+```javascript
+var n = lista_tamanho();
+var max = lista_ler(0);
+
+for (var i = 1; i < n; i++) {
+ if (lista_ler(i) > max) {
+ max = lista_ler(i);
+ }
+}
+
+var contagem = [];
+for (var k = 0; k <= max; k++) {
+ contagem[k] = 0;
+}
+
+for (var i2 = 0; i2 < n; i2++) {
+ var v = lista_ler(i2);
+ contagem[v] = contagem[v] + 1;
+}
+
+var pos = 0;
+for (var valor = 0; valor <= max; valor++) {
+ while (contagem[valor] > 0) {
+ lista_escrever(pos, valor);
+ pos = pos + 1;
+ contagem[valor] = contagem[valor] - 1;
+ }
+}
+```
+
+### Blocos (ASCII)
+```text
+definir n <- lista_tamanho()
+definir max <- lista_ler(0)
+[PARA i = 1 enquanto [i < n] incremento [i + 1]]
+ [SE [lista_ler(i) > max]]
+ definir max <- lista_ler(i)
+
+definir contagem <- []
+[PARA k = 0 enquanto [k <= max] incremento [k + 1]]
+ contagem[k] <- 0
+
+[PARA i = 0 enquanto [i < n] incremento [i + 1]]
+ definir v <- lista_ler(i)
+ contagem[v] <- [contagem[v] + 1]
+
+definir pos <- 0
+[PARA valor = 0 enquanto [valor <= max] incremento [valor + 1]]
+ [ENQUANTO [contagem[valor] > 0]]
+ [lista_escrever(pos, valor)]
+ definir pos <- [pos + 1]
+ contagem[valor] <- [contagem[valor] - 1]
+```
diff --git a/app/src/atividades/programacao/ordenacao/ui/constants.js b/app/src/atividades/programacao/ordenacao/ui/constants.js
new file mode 100644
index 0000000..403e28a
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/ui/constants.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Constantes visuais e de configuração para o jogo Ordenação.
+ * @module games.ordenacao.ui.constants
+ */
+
+export const ConstantesJogo = {
+ LARGURA_TELA: 800,
+ ALTURA_TELA: 500,
+ COR_FUNDO: "#242527",
+};
+
+/** Largura de cada barra */
+export const BAR_W = 60;
+/** Altura mínima (valor menor) */
+export const MIN_H = 40;
+/** Altura máxima (valor maior) */
+export const MAX_H = 280;
+/** Linha do chão (Y base das barras) */
+export const GROUND_Y = 440;
+
+/** Paleta de destaque */
+export const Cores = {
+ COMPARANDO: 0xFFFF00, // amarelo — dois elementos sendo comparados
+ TROCANDO: 0xFF8C00, // laranja — swap em andamento
+ BORDA_PADRAO: 0xFFFFFF,
+ ORDENADO: 0x00C875, // verde — lista ordenada com sucesso
+ ERRO: 0xFF4444, // vermelho — lista fora de ordem
+};
diff --git a/app/src/atividades/programacao/ordenacao/validation/core/ExecutionTrace.js b/app/src/atividades/programacao/ordenacao/validation/core/ExecutionTrace.js
new file mode 100644
index 0000000..ac21fef
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/core/ExecutionTrace.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Modelo de evento da trilha de execução.
+ *
+ * Cada operação relevante feita pelo aluno na InstrumentedList gera um
+ * evento com a estrutura abaixo. A trilha completa (array de eventos) é
+ * passada ao SortingValidationEngine para detectar qual algoritmo foi usado.
+ *
+ * Tipos de evento:
+ *
+ * compare — lista_comparar(i, j, op): comparação entre posições i e j.
+ * swap — lista_trocar(i, j): troca dos valores nas posições i e j.
+ * read — lista_ler(i): leitura do valor na posição i.
+ * write — lista_escrever(i, v): escrita do valor v na posição i.
+ *
+ * Exemplos:
+ *
+ * { type: "compare", i: 0, j: 1 }
+ * { type: "swap", i: 0, j: 1 }
+ * { type: "read", i: 2 }
+ * { type: "write", i: 3, value: 7 }
+ *
+ * @typedef {Object} TraceEvent
+ * @property {"compare"|"swap"|"read"|"write"} type
+ * @property {number} i
+ * @property {number} [j]
+ * @property {number} [value]
+ */
+
+/**
+ * Cria um evento de comparação entre posições i e j.
+ * @param {number} i
+ * @param {number} j
+ * @returns {TraceEvent}
+ */
+export function compareEvent(i, j) {
+ return { type: "compare", i, j };
+}
+
+/**
+ * Cria um evento de troca entre posições i e j.
+ * @param {number} i
+ * @param {number} j
+ * @returns {TraceEvent}
+ */
+export function swapEvent(i, j) {
+ return { type: "swap", i, j };
+}
+
+/**
+ * Cria um evento de leitura na posição i.
+ * @param {number} i
+ * @returns {TraceEvent}
+ */
+export function readEvent(i) {
+ return { type: "read", i };
+}
+
+/**
+ * Cria um evento de escrita na posição i com valor value.
+ * @param {number} i
+ * @param {number} value
+ * @returns {TraceEvent}
+ */
+export function writeEvent(i, value) {
+ return { type: "write", i, value };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/core/InstrumentedList.js b/app/src/atividades/programacao/ordenacao/validation/core/InstrumentedList.js
new file mode 100644
index 0000000..7895501
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/core/InstrumentedList.js
@@ -0,0 +1,132 @@
+/**
+ * @fileoverview Lista instrumentada para captura de trilha de execução.
+ *
+ * Cada operação relevante (comparar, trocar, ler, escrever) registra um evento
+ * na trilha interna. Ao final da execução, o aluno chama getTrace() e a trilha
+ * é passada ao SortingValidationEngine para identificar qual algoritmo foi usado.
+ *
+ * API pública (equivale aos blocos customizados do Blockly):
+ *
+ * vm.length() → tamanho da lista
+ * vm.read(i) → lê o valor na posição i
+ * vm.write(i, value) → escreve value na posição i
+ * vm.swap(i, j) → troca os valores das posições i e j
+ * vm.compare(i, j, op) → compara arr[i] op arr[j] e retorna boolean
+ * vm.toArray() → retorna cópia do estado atual
+ * vm.getTrace() → retorna a trilha de eventos capturada
+ *
+ * Operadores aceitos por compare(): ">" | "<" | ">=" | "<=" | "==" | "!="
+ *
+ * Erros de índice lançam exceções com mensagens pedagógicas claras, em vez
+ * de silenciosamente retornar undefined.
+ */
+
+import { compareEvent, swapEvent, readEvent, writeEvent } from "./ExecutionTrace.js";
+
+export class InstrumentedList {
+ /**
+ * @param {number[]} initialList - Lista de números a ser ordenada.
+ */
+ constructor(initialList) {
+ if (!Array.isArray(initialList) || initialList.length === 0) {
+ throw new Error("InstrumentedList: a lista inicial deve ser um array não vazio.");
+ }
+ this._arr = [...initialList];
+ this._trace = [];
+ }
+
+ // ── Acesso ────────────────────────────────────────────────────────────────
+
+ /** Retorna o tamanho da lista. Não gera evento de trilha. */
+ length() {
+ return this._arr.length;
+ }
+
+ /**
+ * Lê o valor na posição i.
+ * Gera evento { type: "read", i }.
+ * @param {number} i
+ * @returns {number}
+ */
+ read(i) {
+ this._assertBounds(i, "ler");
+ this._trace.push(readEvent(i));
+ return this._arr[i];
+ }
+
+ /**
+ * Escreve value na posição i.
+ * Gera evento { type: "write", i, value }.
+ * @param {number} i
+ * @param {number} value
+ */
+ write(i, value) {
+ this._assertBounds(i, "escrever");
+ this._arr[i] = value;
+ this._trace.push(writeEvent(i, value));
+ }
+
+ /**
+ * Troca os valores das posições i e j.
+ * Gera evento { type: "swap", i, j }.
+ * @param {number} i
+ * @param {number} j
+ */
+ swap(i, j) {
+ this._assertBounds(i, "trocar");
+ this._assertBounds(j, "trocar");
+ [this._arr[i], this._arr[j]] = [this._arr[j], this._arr[i]];
+ this._trace.push(swapEvent(i, j));
+ }
+
+ /**
+ * Compara os valores nas posições i e j usando o operador fornecido.
+ * Gera evento { type: "compare", i, j }.
+ *
+ * @param {number} i
+ * @param {number} j
+ * @param {">" | "<" | ">=" | "<=" | "==" | "!="} op
+ * @returns {boolean}
+ */
+ compare(i, j, op) {
+ this._assertBounds(i, "comparar");
+ this._assertBounds(j, "comparar");
+ this._trace.push(compareEvent(i, j));
+ const a = this._arr[i];
+ const b = this._arr[j];
+ switch (op) {
+ case ">": return a > b;
+ case "<": return a < b;
+ case ">=": return a >= b;
+ case "<=": return a <= b;
+ case "==": return a === b;
+ case "!=": return a !== b;
+ default:
+ throw new Error(`InstrumentedList: operador inválido "${op}". Use >, <, >=, <=, == ou !=.`);
+ }
+ }
+
+ // ── Saída ─────────────────────────────────────────────────────────────────
+
+ /** Retorna cópia do estado atual da lista. Não gera eventos. */
+ toArray() {
+ return [...this._arr];
+ }
+
+ /** Retorna a trilha de eventos capturada durante a execução. */
+ getTrace() {
+ return [...this._trace];
+ }
+
+ // ── Interno ───────────────────────────────────────────────────────────────
+
+ _assertBounds(i, operacao) {
+ const n = this._arr.length;
+ if (!Number.isInteger(i) || i < 0 || i >= n) {
+ throw new Error(
+ `Índice inválido ao tentar ${operacao}: posição ${i} está fora dos limites ` +
+ `[0, ${n - 1}]. Lembre-se: a lista começa no índice 0.`
+ );
+ }
+ }
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/core/SortingValidationEngine.js b/app/src/atividades/programacao/ordenacao/validation/core/SortingValidationEngine.js
new file mode 100644
index 0000000..6225017
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/core/SortingValidationEngine.js
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Engine de validação unificada para algoritmos de ordenação.
+ *
+ * Fluxo de validação:
+ * 1. Valores preservados — nenhum elemento foi perdido ou duplicado.
+ * 2. Ordenação correta — lista em ordem crescente.
+ * 3. Algoritmo detectado — a trilha é avaliada por todos os perfis registrados.
+ * 4. Correspondência — o algoritmo detectado deve bater com o esperado.
+ *
+ * Uso:
+ *
+ * import { SortingValidationEngine } from "./SortingValidationEngine.js";
+ * import { bubbleProfile } from "../profiles/bubbleProfile.js";
+ * import { selectionProfile } from "../profiles/selectionProfile.js";
+ * import { insertionProfile } from "../profiles/insertionProfile.js";
+ *
+ * const engine = new SortingValidationEngine({
+ * bubble: bubbleProfile,
+ * selection: selectionProfile,
+ * insertion: insertionProfile,
+ * });
+ *
+ * const result = engine.validate({
+ * initialList: [5, 2, 8, 1, 9],
+ * finalList: vm.toArray(),
+ * trace: vm.getTrace(),
+ * expectedAlgorithm: "bubble",
+ * messages: configFase.validation.messages,
+ * });
+ *
+ * Retorno:
+ * {
+ * success: boolean,
+ * message: string,
+ * detectedAlgorithm: string,
+ * checks: { valuesPreserved, sorted, algorithmMatch },
+ * debug: { compareCount, swapCount, writeCount, readCount, traceSize }
+ * }
+ */
+
+// ── Helpers ───────────────────────────────────────────────────────────────────
+
+function checkValuesPreserved(initial, final) {
+ if (initial.length !== final.length) return false;
+ const a = [...initial].sort((x, y) => x - y);
+ const b = [...final].sort((x, y) => x - y);
+ return a.every((v, i) => v === b[i]);
+}
+
+function checkSorted(list) {
+ return list.every((v, i) => i === 0 || list[i - 1] <= v);
+}
+
+function buildDebug(trace) {
+ return {
+ compareCount: trace.filter(e => e.type === "compare").length,
+ swapCount: trace.filter(e => e.type === "swap").length,
+ writeCount: trace.filter(e => e.type === "write").length,
+ readCount: trace.filter(e => e.type === "read").length,
+ traceSize: trace.length,
+ };
+}
+
+// ── Engine ────────────────────────────────────────────────────────────────────
+
+export class SortingValidationEngine {
+ /**
+ * @param {Record { match: boolean, failReason?: string }>} profiles
+ * Mapa de nome-do-algoritmo → função de perfil.
+ * A ordem das chaves define a prioridade de detecção.
+ */
+ constructor(profiles) {
+ if (!profiles || Object.keys(profiles).length === 0) {
+ throw new Error("SortingValidationEngine: ao menos um perfil de algoritmo é necessário.");
+ }
+ this._profiles = profiles;
+ }
+
+ /**
+ * Valida a solução do aluno.
+ *
+ * @param {Object} params
+ * @param {number[]} params.initialList - Lista original antes da execução.
+ * @param {number[]} params.finalList - Lista após a execução do código do aluno.
+ * @param {TraceEvent[]} params.trace - Trilha capturada pela InstrumentedList.
+ * @param {string} params.expectedAlgorithm - Algoritmo cobrado pela fase (ex: "bubble").
+ * @param {Object} [params.messages] - Mensagens pedagógicas da fase (configFase.validation.messages).
+ * @returns {ValidationResult}
+ */
+ validate({ initialList, finalList, trace, expectedAlgorithm, messages = {} }) {
+ const n = initialList.length;
+ const debug = buildDebug(trace);
+
+ // ── 1) Valores preservados ─────────────────────────────────────────────
+ if (!checkValuesPreserved(initialList, finalList)) {
+ return {
+ success: false,
+ message: messages.valoresAlterados ?? "Algum valor foi perdido ou duplicado.",
+ detectedAlgorithm: "unknown",
+ checks: { valuesPreserved: false, sorted: false, algorithmMatch: false },
+ debug,
+ };
+ }
+
+ // ── 2) Ordenação final ─────────────────────────────────────────────────
+ if (!checkSorted(finalList)) {
+ return {
+ success: false,
+ message: messages.listaDesordenada ?? "A lista não ficou ordenada.",
+ detectedAlgorithm: "unknown",
+ checks: { valuesPreserved: true, sorted: false, algorithmMatch: false },
+ debug,
+ };
+ }
+
+ // ── 3) Detecção do algoritmo ───────────────────────────────────────────
+ let detectedAlgorithm = "unknown";
+ let detectionFailReason = null;
+
+ for (const [algo, profileFn] of Object.entries(this._profiles)) {
+ const result = profileFn(trace, n);
+ if (result.match) {
+ detectedAlgorithm = algo;
+ break;
+ }
+ // Guarda o motivo de falha do perfil esperado, para mensagem mais precisa
+ if (algo === expectedAlgorithm) {
+ detectionFailReason = result.failReason ?? null;
+ }
+ }
+
+ // ── 4) Correspondência ─────────────────────────────────────────────────
+ const algorithmMatch = detectedAlgorithm === expectedAlgorithm;
+
+ if (!algorithmMatch) {
+ let message;
+ if (detectedAlgorithm === "unknown") {
+ // O perfil do algoritmo esperado explicou por que falhou
+ message = detectionFailReason
+ ?? messages.algoritmoNaoDetectado
+ ?? `Não consegui identificar o padrão do algoritmo esperado (${expectedAlgorithm}).`;
+ } else {
+ const fn = messages.algoritmoErrado;
+ message = typeof fn === "function"
+ ? fn(detectedAlgorithm)
+ : `A lista ficou ordenada, mas o padrão detectado foi "${detectedAlgorithm}", não "${expectedAlgorithm}".`;
+ }
+
+ return {
+ success: false,
+ message,
+ detectedAlgorithm,
+ checks: { valuesPreserved: true, sorted: true, algorithmMatch: false },
+ debug,
+ };
+ }
+
+ // ── Sucesso ────────────────────────────────────────────────────────────
+ return {
+ success: true,
+ message: messages.success ?? "Correto!",
+ detectedAlgorithm,
+ checks: { valuesPreserved: true, sorted: true, algorithmMatch: true },
+ debug,
+ };
+ }
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/core/createTraceableArrayProxy.js b/app/src/atividades/programacao/ordenacao/validation/core/createTraceableArrayProxy.js
new file mode 100644
index 0000000..a79d250
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/core/createTraceableArrayProxy.js
@@ -0,0 +1,80 @@
+/**
+ * @fileoverview Proxy de array com rastreamento de eventos para validação de ordenação.
+ *
+ * Objetivo: instrumentar a lista real da atividade (blocos nativos do Blockly)
+ * sem exigir API customizada (`compare`, `swap`, etc.).
+ *
+ * Eventos gerados no historico:
+ * - read { type: "read", i }
+ * - write { type: "write", i, value }
+ * - swap { type: "swap", i, j } (inferido por par de writes)
+ */
+
+function isArrayIndex(prop) {
+ if (typeof prop === "symbol") return false;
+ if (prop === "length") return false;
+ const n = Number(prop);
+ return Number.isInteger(n) && n >= 0;
+}
+
+/**
+ * Cria um Proxy que sincroniza mutações no array original e registra eventos no historico.
+ *
+ * @param {number[]} originalArray
+ * @param {Array} historico
+ * @param {{ trackReads?: boolean, inferSwap?: boolean }} [options]
+ * @returns {Proxy}
+ */
+export function createTraceableArrayProxy(originalArray, historico, options = {}) {
+ const trackReads = options.trackReads !== false;
+ const inferSwap = options.inferSwap !== false;
+
+ const target = [...originalArray];
+
+ // Guarda a última escrita para inferir swap por padrão de dupla atribuição.
+ let lastWrite = null;
+
+ return new Proxy(target, {
+ get(arr, prop, receiver) {
+ if (isArrayIndex(prop)) {
+ const i = Number(prop);
+ if (trackReads) historico.push({ type: "read", i });
+ // Se houve leitura entre duas escritas, não é uma troca direta típica.
+ lastWrite = null;
+ }
+ return Reflect.get(arr, prop, receiver);
+ },
+
+ set(arr, prop, value, receiver) {
+ if (isArrayIndex(prop)) {
+ const i = Number(prop);
+ const oldValue = arr[i];
+
+ // Atualiza array da cena e array do proxy
+ originalArray[i] = value;
+ const ok = Reflect.set(arr, prop, value, receiver);
+
+ historico.push({ type: "write", i, value });
+
+ if (inferSwap) {
+ if (
+ lastWrite &&
+ lastWrite.i !== i &&
+ lastWrite.oldValue === value &&
+ oldValue === lastWrite.newValue
+ ) {
+ historico.push({ type: "swap", i: lastWrite.i, j: i });
+ lastWrite = null;
+ } else {
+ lastWrite = { i, oldValue, newValue: value };
+ }
+ }
+
+ return ok;
+ }
+
+ lastWrite = null;
+ return Reflect.set(arr, prop, value, receiver);
+ },
+ });
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js b/app/src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js
new file mode 100644
index 0000000..5978c17
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js
@@ -0,0 +1,560 @@
+/**
+ * @fileoverview POC: Validação de algoritmos de ordenação.
+ *
+ * Executa todos os casos de teste definidos abaixo e imprime um relatório.
+ *
+ * Uso (Node.js com ESM):
+ * node --experimental-vm-modules src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js
+ *
+ * Ou via script no package.json:
+ * "poc:ordenacao": "node src/atividades/programacao/ordenacao/validation/poc/runValidationPoc.js"
+ *
+ * Critérios de pronto da POC (do estudo.md):
+ * ✓ Detectar corretamente Bubble, Selection e Insertion em casos válidos.
+ * ✓ Rejeitar ao menos 3 casos de falso positivo por perfil.
+ * ✓ Retornar mensagem pedagógica clara em todos os cenários.
+ * ✓ Produzir relatório com detectedAlgorithm, checks e message.
+ */
+
+import { InstrumentedList } from "../core/InstrumentedList.js";
+import { SortingValidationEngine } from "../core/SortingValidationEngine.js";
+import { bubbleProfile } from "../profiles/bubbleProfile.js";
+import { selectionProfile } from "../profiles/selectionProfile.js";
+import { insertionProfile } from "../profiles/insertionProfile.js";
+import { shellProfile } from "../profiles/shellProfile.js";
+import { countingProfile } from "../profiles/countingProfile.js";
+import { createTraceableArrayProxy } from "../core/createTraceableArrayProxy.js";
+
+// ── Engine singleton ──────────────────────────────────────────────────────────
+
+const engine = new SortingValidationEngine({
+ bubble: bubbleProfile,
+ selection: selectionProfile,
+ // Shell e Counting ANTES de Insertion: ambos usam read+write sem swap,
+ // igual à variante shift do Insertion. Os critérios específicos de cada
+ // um (gap>1 para Shell, writes sequenciais para Counting) devem ser
+ // verificados primeiro para evitar falso positivo de Insertion.
+ shell: shellProfile,
+ counting: countingProfile,
+ insertion: insertionProfile,
+});
+
+// ── Implementações de referência ──────────────────────────────────────────────
+
+function bubbleSortCorreto(vm) {
+ const n = vm.length();
+ for (let i = 0; i < n - 1; i++) {
+ for (let j = 0; j < n - 1 - i; j++) {
+ if (vm.compare(j, j + 1, ">")) vm.swap(j, j + 1);
+ }
+ }
+}
+
+function bubbleSortComFlag(vm) {
+ const n = vm.length();
+ for (let i = 0; i < n - 1; i++) {
+ let trocou = false;
+ for (let j = 0; j < n - 1 - i; j++) {
+ if (vm.compare(j, j + 1, ">")) { vm.swap(j, j + 1); trocou = true; }
+ }
+ if (!trocou) break;
+ }
+}
+
+function selectionSortCorreto(vm) {
+ const n = vm.length();
+ for (let i = 0; i < n - 1; i++) {
+ let min = i;
+ for (let j = i + 1; j < n; j++) {
+ if (vm.compare(j, min, "<")) min = j;
+ }
+ if (min !== i) vm.swap(i, min);
+ }
+}
+
+function selectionSortMaximo(vm) {
+ // Variação: encontra o máximo e posiciona no fim
+ const n = vm.length();
+ for (let i = n - 1; i > 0; i--) {
+ let max = 0;
+ for (let j = 1; j <= i; j++) {
+ if (vm.compare(j, max, ">")) max = j;
+ }
+ if (max !== i) vm.swap(i, max);
+ }
+}
+
+function insertionSortShift(vm) {
+ const n = vm.length();
+ for (let i = 1; i < n; i++) {
+ const chave = vm.read(i);
+ let j = i - 1;
+ while (j >= 0 && vm.read(j) > chave) {
+ vm.write(j + 1, vm.read(j));
+ j--;
+ }
+ vm.write(j + 1, chave);
+ }
+}
+
+function insertionSortSwap(vm) {
+ const n = vm.length();
+ for (let i = 1; i < n; i++) {
+ let j = i;
+ while (j > 0 && vm.compare(j - 1, j, ">")) {
+ vm.swap(j - 1, j);
+ j--;
+ }
+ }
+}
+
+function shellSortCorreto(vm) {
+ const n = vm.length();
+ let gap = Math.floor(n / 2);
+ while (gap > 0) {
+ for (let i = gap; i < n; i++) {
+ const temp = vm.read(i);
+ let j = i;
+ while (j >= gap && vm.read(j - gap) > temp) {
+ vm.write(j, vm.read(j - gap));
+ j -= gap;
+ }
+ vm.write(j, temp);
+ }
+ gap = Math.floor(gap / 2);
+ }
+}
+
+function countingSortCorreto(vm) {
+ const n = vm.length();
+ // Determina o máximo sem gerar eventos (leitura fora do vm)
+ const snapshot = vm.toArray();
+ const max = Math.max(...snapshot);
+ const count = new Array(max + 1).fill(0);
+
+ // Fase 1: contar (gera read events)
+ for (let i = 0; i < n; i++) count[vm.read(i)]++;
+
+ // Fase 2: reconstruir (gera write events)
+ let pos = 0;
+ for (let v = 0; v <= max; v++) {
+ while (count[v] > 0) {
+ vm.write(pos++, v);
+ count[v]--;
+ }
+ }
+}
+
+// ── Falsos positivos (algoritmo errado que ordena a lista) ────────────────────
+
+// Usa Selection Sort mas dizemos que é Bubble
+function selectionMascaradoComoBubble(vm) { selectionSortCorreto(vm); }
+
+// Usa Bubble Sort mas dizemos que é Insertion
+function bubbleMascaradoComoInsertion(vm) { bubbleSortCorreto(vm); }
+
+// Usa Insertion Sort mas dizemos que é Selection
+function insertionMascaradoComoSelection(vm) { insertionSortShift(vm); }
+
+// Usa Bubble Sort mas dizemos que é Shell
+function bubbleMascaradoComoShell(vm) { bubbleSortCorreto(vm); }
+
+// Usa Selection Sort mas dizemos que é Counting
+function selectionMascaradoComoCounting(vm) { selectionSortCorreto(vm); }
+
+// Solução ingênua: copia a lista ordenada diretamente (sem algoritmo)
+function cheatSort(vm) {
+ const sorted = vm.toArray().sort((a, b) => a - b);
+ for (let i = 0; i < sorted.length; i++) vm.write(i, sorted[i]);
+}
+
+// ── Implementações no modo REAL (blocos nativos: lista[i] = ... ) ───────────
+
+function bubbleSortReal(lista) {
+ const n = lista.length;
+ for (let i = 0; i < n - 1; i++) {
+ for (let j = 0; j < n - 1 - i; j++) {
+ if (lista[j] > lista[j + 1]) {
+ const tmp = lista[j];
+ lista[j] = lista[j + 1];
+ lista[j + 1] = tmp;
+ }
+ }
+ }
+}
+
+function selectionSortReal(lista) {
+ const n = lista.length;
+ for (let i = 0; i < n - 1; i++) {
+ let min = i;
+ for (let j = i + 1; j < n; j++) {
+ if (lista[j] < lista[min]) min = j;
+ }
+ if (min !== i) {
+ const tmp = lista[i];
+ lista[i] = lista[min];
+ lista[min] = tmp;
+ }
+ }
+}
+
+function insertionSortReal(lista) {
+ for (let i = 1; i < lista.length; i++) {
+ const chave = lista[i];
+ let j = i - 1;
+ while (j >= 0 && lista[j] > chave) {
+ lista[j + 1] = lista[j];
+ j--;
+ }
+ lista[j + 1] = chave;
+ }
+}
+
+function shellSortReal(lista) {
+ let gap = Math.floor(lista.length / 2);
+ while (gap > 0) {
+ for (let i = gap; i < lista.length; i++) {
+ const temp = lista[i];
+ let j = i;
+ while (j >= gap && lista[j - gap] > temp) {
+ lista[j] = lista[j - gap];
+ j -= gap;
+ }
+ lista[j] = temp;
+ }
+ gap = Math.floor(gap / 2);
+ }
+}
+
+function countingSortReal(lista) {
+ const max = Math.max(...lista);
+ const count = new Array(max + 1).fill(0);
+ for (let i = 0; i < lista.length; i++) count[lista[i]]++;
+ let pos = 0;
+ for (let v = 0; v <= max; v++) {
+ while (count[v] > 0) {
+ lista[pos++] = v;
+ count[v]--;
+ }
+ }
+}
+
+// ── Definição dos casos de teste ──────────────────────────────────────────────
+
+const casos = [
+ // ── Bubble Sort ────────────────────────────────────────────────────────────
+ {
+ descricao: "[BUBBLE] implementação padrão",
+ lista: [5, 2, 8, 1, 9],
+ solucao: bubbleSortCorreto,
+ expectedAlgorithm: "bubble",
+ esperado: true,
+ },
+ {
+ descricao: "[BUBBLE] com flag de parada antecipada (otimização)",
+ lista: [3, 1, 4, 2, 5],
+ solucao: bubbleSortComFlag,
+ expectedAlgorithm: "bubble",
+ esperado: true,
+ },
+ {
+ descricao: "[BUBBLE] lista já ordenada (zero trocas, otimização encerra rápido)",
+ lista: [1, 2, 3, 4, 5],
+ solucao: bubbleSortComFlag,
+ expectedAlgorithm: "bubble",
+ esperado: true,
+ },
+
+ // ── Selection Sort ─────────────────────────────────────────────────────────
+ {
+ descricao: "[SELECTION] implementação padrão (busca mínimo)",
+ lista: [4, 7, 1, 6, 3],
+ solucao: selectionSortCorreto,
+ expectedAlgorithm: "selection",
+ esperado: true,
+ },
+ {
+ descricao: "[SELECTION] variação: busca máximo e posiciona no fim",
+ lista: [8, 2, 6, 1, 4, 7, 3],
+ solucao: selectionSortMaximo,
+ expectedAlgorithm: "selection",
+ esperado: true,
+ },
+
+ // ── Insertion Sort ─────────────────────────────────────────────────────────
+ {
+ descricao: "[INSERTION] variante shift (deslocamento com write)",
+ lista: [6, 3, 8, 2, 9],
+ solucao: insertionSortShift,
+ expectedAlgorithm: "insertion",
+ esperado: true,
+ },
+ {
+ descricao: "[INSERTION] variante swap (trocas adjacentes para trás)",
+ lista: [5, 9, 2, 8, 4, 1, 7],
+ solucao: insertionSortSwap,
+ expectedAlgorithm: "insertion",
+ esperado: true,
+ },
+ {
+ descricao: "[INSERTION] lista quase ordenada (um elemento fora do lugar)",
+ lista: [1, 2, 4, 3, 5],
+ solucao: insertionSortShift,
+ expectedAlgorithm: "insertion",
+ esperado: true,
+ },
+
+ // ── Shell Sort ─────────────────────────────────────────────────────────────
+ {
+ descricao: "[SHELL] implementação com gap = n/2",
+ lista: [8, 3, 6, 1, 9, 2, 7, 4],
+ solucao: shellSortCorreto,
+ expectedAlgorithm: "shell",
+ esperado: true,
+ },
+
+ // ── Counting Sort ──────────────────────────────────────────────────────────
+ {
+ descricao: "[COUNTING] implementação padrão",
+ lista: [3, 1, 4, 1, 5, 9, 2, 6],
+ solucao: countingSortCorreto,
+ expectedAlgorithm: "counting",
+ esperado: true,
+ },
+
+ // ── Falsos positivos: algoritmo certo mas solução errada ───────────────────
+ {
+ descricao: "[FALSO POSITIVO] Selection Sort declarado como Bubble",
+ lista: [5, 2, 8, 1, 9],
+ solucao: selectionMascaradoComoBubble,
+ expectedAlgorithm: "bubble",
+ esperado: false,
+ },
+ {
+ descricao: "[FALSO POSITIVO] Bubble Sort declarado como Insertion",
+ lista: [5, 2, 8, 1, 9],
+ solucao: bubbleMascaradoComoInsertion,
+ expectedAlgorithm: "insertion",
+ esperado: false,
+ },
+ {
+ descricao: "[FALSO POSITIVO] Insertion Sort declarado como Selection",
+ lista: [6, 3, 8, 2, 9],
+ solucao: insertionMascaradoComoSelection,
+ expectedAlgorithm: "selection",
+ esperado: false,
+ },
+ {
+ descricao: "[FALSO POSITIVO] Bubble Sort declarado como Shell",
+ lista: [5, 2, 8, 1, 9],
+ solucao: bubbleMascaradoComoShell,
+ expectedAlgorithm: "shell",
+ esperado: false,
+ },
+ {
+ descricao: "[FALSO POSITIVO] Selection Sort declarado como Counting",
+ lista: [4, 7, 1, 6, 3],
+ solucao: selectionMascaradoComoCounting,
+ expectedAlgorithm: "counting",
+ esperado: false,
+ },
+ {
+ descricao: "[FALSO POSITIVO] cheatSort (escreve direto sem algoritmo) declarado como Bubble",
+ lista: [5, 2, 8, 1, 9],
+ solucao: cheatSort,
+ expectedAlgorithm: "bubble",
+ esperado: false,
+ },
+
+ // ── Erros de resultado ─────────────────────────────────────────────────────
+ {
+ descricao: "[ERRO] lista não ordenada (Bubble Sort com loop errado)",
+ lista: [5, 2, 8, 1, 9],
+ solucao: (vm) => {
+ // Loop externo com apenas 1 passada — não ordena a lista toda
+ const n = vm.length();
+ for (let j = 0; j < n - 1; j++) {
+ if (vm.compare(j, j + 1, ">")) vm.swap(j, j + 1);
+ }
+ },
+ expectedAlgorithm: "bubble",
+ esperado: false,
+ },
+ {
+ descricao: "[ERRO] valores alterados (sobrescreve sem variável auxiliar)",
+ lista: [5, 2, 8, 1, 9],
+ solucao: (vm) => {
+ // Troca errada: perde um valor
+ vm.write(0, vm.read(1)); // sobrescreve arr[0] sem guardar
+ vm.write(1, vm.read(0));
+ },
+ expectedAlgorithm: "bubble",
+ esperado: false,
+ },
+];
+
+const casosReais = [
+ {
+ descricao: "[REAL/BUBBLE] acesso por índice + troca com variável temporária",
+ lista: [5, 2, 8, 1, 9],
+ solucao: bubbleSortReal,
+ expectedAlgorithm: "bubble",
+ esperado: true,
+ },
+ {
+ descricao: "[REAL/SELECTION] acesso por índice + mínimo por varredura",
+ lista: [4, 7, 1, 6, 3],
+ solucao: selectionSortReal,
+ expectedAlgorithm: "selection",
+ esperado: true,
+ },
+ {
+ descricao: "[REAL/INSERTION] deslocamento com writes",
+ lista: [6, 3, 8, 2, 9],
+ solucao: insertionSortReal,
+ expectedAlgorithm: "insertion",
+ esperado: true,
+ },
+ {
+ descricao: "[REAL/SHELL] gap decrescente",
+ lista: [8, 3, 6, 1, 9, 2, 7, 4],
+ solucao: shellSortReal,
+ expectedAlgorithm: "shell",
+ esperado: true,
+ },
+ {
+ descricao: "[REAL/COUNTING] contagem e reconstrução",
+ lista: [3, 1, 4, 1, 5, 2, 6, 0],
+ solucao: countingSortReal,
+ expectedAlgorithm: "counting",
+ esperado: true,
+ },
+ {
+ descricao: "[REAL/FALSO POSITIVO] Selection declarado como Bubble",
+ lista: [5, 2, 8, 1, 9],
+ solucao: selectionSortReal,
+ expectedAlgorithm: "bubble",
+ esperado: false,
+ },
+ {
+ descricao: "[REAL/FALSO POSITIVO] Bubble declarado como Counting",
+ lista: [5, 2, 8, 1, 9],
+ solucao: bubbleSortReal,
+ expectedAlgorithm: "counting",
+ esperado: false,
+ },
+];
+
+// ── Runner ────────────────────────────────────────────────────────────────────
+
+function runCase(caso) {
+ const initial = [...caso.lista];
+ const vm = new InstrumentedList(initial);
+
+ let runtimeError = null;
+ try {
+ caso.solucao(vm);
+ } catch (err) {
+ runtimeError = err.message;
+ }
+
+ const finalList = vm.toArray();
+ const trace = vm.getTrace();
+
+ const result = runtimeError
+ ? {
+ success: false,
+ message: `Erro durante a execução: ${runtimeError}`,
+ detectedAlgorithm: "error",
+ checks: { valuesPreserved: false, sorted: false, algorithmMatch: false },
+ debug: { traceSize: trace.length },
+ }
+ : engine.validate({
+ initialList: initial,
+ finalList,
+ trace,
+ expectedAlgorithm: caso.expectedAlgorithm,
+ });
+
+ const passou = result.success === caso.esperado;
+ return { caso, result, passou };
+}
+
+function runCaseReal(caso) {
+ const initial = [...caso.lista];
+ const final = [...initial];
+ const historico = [];
+ const lista = createTraceableArrayProxy(final, historico, {
+ trackReads: true,
+ inferSwap: true,
+ });
+
+ let runtimeError = null;
+ try {
+ caso.solucao(lista);
+ } catch (err) {
+ runtimeError = err.message;
+ }
+
+ const result = runtimeError
+ ? {
+ success: false,
+ message: `Erro durante a execução: ${runtimeError}`,
+ detectedAlgorithm: "error",
+ checks: { valuesPreserved: false, sorted: false, algorithmMatch: false },
+ debug: { traceSize: historico.length },
+ }
+ : engine.validate({
+ initialList: initial,
+ finalList: final,
+ trace: historico,
+ expectedAlgorithm: caso.expectedAlgorithm,
+ });
+
+ const passou = result.success === caso.esperado;
+ return { caso, result, passou };
+}
+
+function imprimir(resultados, titulo) {
+ let aprovados = 0;
+ let reprovados = 0;
+
+ console.log("\n══════════════════════════════════════════════════════════════");
+ console.log(` ${titulo}`);
+ console.log("══════════════════════════════════════════════════════════════\n");
+
+ for (const { caso, result, passou } of resultados) {
+ const icone = passou ? "✓" : "✗";
+ const status = passou ? "OK" : "FALHOU";
+ console.log(`${icone} ${caso.descricao}`);
+ console.log(` Esperado: ${caso.esperado ? "sucesso" : "falha"} | Obtido: ${result.success ? "sucesso" : "falha"} | ${status}`);
+ console.log(` Algoritmo detectado: ${result.detectedAlgorithm}`);
+ console.log(` Mensagem: ${result.message}`);
+ if (result.debug) {
+ const d = result.debug;
+ const parts = [];
+ if (d.compareCount !== undefined) parts.push(`compares=${d.compareCount}`);
+ if (d.swapCount !== undefined) parts.push(`swaps=${d.swapCount}`);
+ if (d.writeCount !== undefined) parts.push(`writes=${d.writeCount}`);
+ if (d.readCount !== undefined) parts.push(`reads=${d.readCount}`);
+ parts.push(`trace=${d.traceSize}`);
+ console.log(` Debug: ${parts.join(", ")}`);
+ }
+ console.log();
+ passou ? aprovados++ : reprovados++;
+ }
+
+ console.log("══════════════════════════════════════════════════════════════");
+ console.log(` Resultado: ${aprovados} aprovados, ${reprovados} reprovados (total: ${resultados.length})`);
+ console.log("══════════════════════════════════════════════════════════════\n");
+
+ if (reprovados > 0) process.exit(1);
+}
+
+// ── Entrada principal ─────────────────────────────────────────────────────────
+
+const resultadosInstrumented = casos.map(runCase);
+imprimir(resultadosInstrumented, "POC — Validação (modo InstrumentedList)");
+
+const resultadosReais = casosReais.map(runCaseReal);
+imprimir(resultadosReais, "POC — Validação (modo Atividade Real / Proxy)");
diff --git a/app/src/atividades/programacao/ordenacao/validation/profiles/bubbleProfile.js b/app/src/atividades/programacao/ordenacao/validation/profiles/bubbleProfile.js
new file mode 100644
index 0000000..8f319b3
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/profiles/bubbleProfile.js
@@ -0,0 +1,111 @@
+/**
+ * @fileoverview Perfil de detecção: Bubble Sort.
+ *
+ * Invariantes obrigatórios:
+ * 1. TODAS as trocas são entre posições adjacentes (|i - j| === 1).
+ * 2. A maioria das comparações é entre posições adjacentes
+ * (razão >= ADJACENT_COMPARE_THRESHOLD).
+ * 3. O padrão de comparações é de varredura progressiva para frente
+ * (índices crescem dentro de cada passada).
+ *
+ * Variações aceitas (toleradas pela heurística):
+ * - Com ou sem flag de parada antecipada (otimização).
+ * - Loop externo crescente (i = 0..n-2) ou decrescente (limite diminuindo).
+ * - Poucas trocas (lista quase ordenada).
+ *
+ * O que NÃO é aceito:
+ * - Qualquer troca não adjacente.
+ * - Razão de comparações adjacentes abaixo do limiar.
+ * - Padrão de varredura para trás dentro da passada (sinal de Insertion Sort).
+ */
+
+const ADJACENT_COMPARE_THRESHOLD = 0.80;
+
+/**
+ * @param {import("../core/ExecutionTrace.js").TraceEvent[]} trace
+ * @param {number} n - Tamanho da lista.
+ * @returns {{ match: boolean, failReason?: string }}
+ */
+export function bubbleProfile(trace, n) {
+ const compares = trace.filter(e => e.type === "compare");
+ const swaps = trace.filter(e => e.type === "swap");
+ const reads = trace.filter(e => e.type === "read");
+
+ // Sem operações relevantes
+ if (compares.length === 0 && swaps.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Nenhuma troca foi detectada. No Bubble Sort, quando há inversões, " +
+ "o algoritmo deve realizar trocas entre pares vizinhos (j e j+1).",
+ };
+ }
+
+ // Leitura mínima ajuda a filtrar soluções que apenas sobrescrevem a lista.
+ if (swaps.length > 0 && reads.length === 0 && compares.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Não houve leituras da lista antes das trocas. Verifique se você está " +
+ "comparando valores para decidir quando trocar elementos adjacentes.",
+ };
+ }
+
+ // No modo real (sem compare explícito), exige evidência mínima de padrão Bubble.
+ // Um único swap inferido pode aparecer como ruído em algoritmos de deslocamento.
+ if (compares.length === 0 && swaps.length < 2) {
+ return {
+ match: false,
+ failReason:
+ "Poucas trocas para caracterizar Bubble Sort. " +
+ "No Bubble, em listas não ordenadas, é esperado observar várias trocas adjacentes ao longo das passadas.",
+ };
+ }
+
+ // Invariante 1: todas as trocas devem ser adjacentes
+ const nonAdjacentSwap = swaps.find(e => Math.abs(e.i - e.j) !== 1);
+ if (nonAdjacentSwap) {
+ return {
+ match: false,
+ failReason:
+ `Troca não adjacente detectada (posições ${nonAdjacentSwap.i} e ${nonAdjacentSwap.j}). ` +
+ "O Bubble Sort só troca elementos que estão lado a lado — posições j e j+1.",
+ };
+ }
+
+ // Invariante 2: razão de comparações adjacentes (quando compare existe)
+ if (compares.length > 0) {
+ const adjacentCompares = compares.filter(e => Math.abs(e.i - e.j) === 1);
+ const adjacentRatio = adjacentCompares.length / compares.length;
+ if (adjacentRatio < ADJACENT_COMPARE_THRESHOLD) {
+ return {
+ match: false,
+ failReason:
+ `Apenas ${Math.round(adjacentRatio * 100)}% das comparações foram entre posições adjacentes. ` +
+ "O Bubble Sort percorre a lista comparando pares vizinhos (j e j+1) em cada passada.",
+ };
+ }
+ }
+
+ // Invariante 3: padrão de varredura para frente (detectar Insertion Sort swap-variant)
+ // Conta pares consecutivos de comparações onde o índice cai (padrão de inserção)
+ if (compares.length > 1) {
+ let backwardPairs = 0;
+ for (let k = 1; k < compares.length; k++) {
+ const prev = Math.min(compares[k - 1].i, compares[k - 1].j);
+ const curr = Math.min(compares[k].i, compares[k].j);
+ if (curr < prev) backwardPairs++;
+ }
+ const backwardRatio = backwardPairs / (compares.length - 1);
+ if (backwardRatio > 0.5) {
+ return {
+ match: false,
+ failReason:
+ "O padrão de comparações indica varredura para trás (Insertion Sort), não para frente. " +
+ "O Bubble Sort percorre a lista da esquerda para a direita em cada passada.",
+ };
+ }
+ }
+
+ return { match: true };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/profiles/countingProfile.js b/app/src/atividades/programacao/ordenacao/validation/profiles/countingProfile.js
new file mode 100644
index 0000000..eeb08fd
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/profiles/countingProfile.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Perfil de detecção: Counting Sort.
+ *
+ * Invariantes obrigatórios:
+ * 1. Nenhuma troca (swapCount === 0).
+ * 2. Nenhuma comparação posição-a-posição (compareCount === 0).
+ * 3. Exatamente n escritas, nas posições 0, 1, 2, ..., n-1 em ordem sequencial.
+ * Isso é a assinatura da fase de reconstrução do Counting Sort.
+ * 4. Pelo menos n leituras (fase de contagem — ler cada elemento da lista original).
+ *
+ * Por que o critério 3 é forte:
+ * - Insertion Sort: escreve de volta nas mesmas posições que deslocou → não sequencial.
+ * - Shell Sort: escreve nas posições do gap → não sequencial.
+ * - Counting Sort: reconstrói a lista de 0 a n-1, em ordem crescente de valor.
+ *
+ * Variações aceitas:
+ * - Contagem com array de tamanho max+1 ou com offset min..max.
+ * - Reconstrução com loop de valores (v=0..max) ou com prefixos acumulados.
+ *
+ * O que NÃO é aceito:
+ * - Qualquer swap ou compare.
+ * - Menos de n escritas ou escritas fora de ordem sequencial.
+ */
+
+/**
+ * @param {import("../core/ExecutionTrace.js").TraceEvent[]} trace
+ * @param {number} n - Tamanho da lista.
+ * @returns {{ match: boolean, failReason?: string }}
+ */
+export function countingProfile(trace, n) {
+ const compares = trace.filter(e => e.type === "compare");
+ const swaps = trace.filter(e => e.type === "swap");
+ const writes = trace.filter(e => e.type === "write");
+ const reads = trace.filter(e => e.type === "read");
+ const contagemWrites = trace.filter(e => e.type === "contagem_write");
+
+ // Invariante 0: deve haver escritas na contagem — assinatura do Counting Sort
+ if (contagemWrites.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Nenhuma escrita na lista de contagem detectada. O Counting Sort requer o uso de " +
+ "lista_contagem_escrever para registrar as frequências de cada valor.",
+ };
+ }
+
+ // Invariante 1: sem trocas
+ if (swaps.length > 0) {
+ return {
+ match: false,
+ failReason:
+ "Trocas (swap) foram detectadas. O Counting Sort não realiza trocas entre elementos. " +
+ "Ele conta quantas vezes cada valor aparece e reconstrói a lista a partir dessas contagens.",
+ };
+ }
+
+ // Invariante 2: sem comparações posição-a-posição
+ if (compares.length > 0) {
+ return {
+ match: false,
+ failReason:
+ "Comparações entre posições da lista foram detectadas. O Counting Sort não compara " +
+ "elementos entre si — use lista_ler(i) para obter o valor e incrementar a contagem, " +
+ "sem usar lista_comparar.",
+ };
+ }
+
+ // Invariante 3: exatamente n escritas em posições sequenciais 0..n-1
+ if (writes.length < n) {
+ return {
+ match: false,
+ failReason:
+ `Apenas ${writes.length} escrita(s) detectada(s), mas a lista tem ${n} elementos. ` +
+ "O Counting Sort precisa reescrever todos os elementos na lista de saída. " +
+ "Verifique se o loop de reconstrução percorre todas as contagens e escreve os valores na ordem certa.",
+ };
+ }
+
+ // Pega as últimas n escritas (a reconstrução final, caso haja escritas auxiliares antes)
+ const lastNWrites = writes.slice(-n);
+ const isSequential = lastNWrites.every((e, idx) => e.i === idx);
+ if (!isSequential) {
+ return {
+ match: false,
+ failReason:
+ "As escritas na lista não foram sequenciais (posições 0, 1, 2, ..., n-1 em ordem). " +
+ "O Counting Sort reconstrói a lista escrevendo cada valor na próxima posição livre (pos++). " +
+ "Verifique se o loop de reconstrução incrementa a posição a cada escrita.",
+ };
+ }
+
+ // Invariante 4: pelo menos n leituras (contagem dos valores originais)
+ if (reads.length < n) {
+ return {
+ match: false,
+ failReason:
+ `Apenas ${reads.length} leitura(s) detectada(s), mas a lista tem ${n} elementos. ` +
+ "O Counting Sort deve ler cada elemento da lista original para incrementar sua contagem.",
+ };
+ }
+
+ return { match: true };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/profiles/insertionProfile.js b/app/src/atividades/programacao/ordenacao/validation/profiles/insertionProfile.js
new file mode 100644
index 0000000..321f97c
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/profiles/insertionProfile.js
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview Perfil de detecção: Insertion Sort.
+ *
+ * Dois variantes são aceitos:
+ *
+ * Variante A — Deslocamento (shift):
+ * O aluno lê a chave com vm.read(i), depois desloca elementos para a direita
+ * com vm.write(j+1, vm.read(j)) até encontrar a posição correta.
+ * Sinal: writeCount >= n-1 e swapCount === 0.
+ *
+ * Variante B — Trocas adjacentes para trás:
+ * O aluno usa vm.compare(j-1, j, ">") + vm.swap(j-1, j) e j-- até o lugar certo.
+ * Sinal: todas as trocas são adjacentes E o padrão de comparações é decrescente
+ * (índices caem dentro de cada inserção).
+ *
+ * Variações aceitas:
+ * - Variante A ou B indiferentemente.
+ * - Inserção encerrada antes do fim do array (elemento já na posição certa).
+ *
+ * O que NÃO é aceito:
+ * - Trocas não adjacentes na variante B.
+ * - Writes sem reads correspondentes (escrita sem deslocamento real).
+ * - Ausência de qualquer padrão de retrocesso (sem reads+writes nem backward swaps).
+ */
+
+const MIN_WRITE_THRESHOLD = 0; // escrita mínima para variant A (pode ser 0 se lista já quase ordenada)
+
+/**
+ * @param {import("../core/ExecutionTrace.js").TraceEvent[]} trace
+ * @param {number} n - Tamanho da lista.
+ * @returns {{ match: boolean, variant?: string, failReason?: string }}
+ */
+export function insertionProfile(trace, n) {
+ const compares = trace.filter(e => e.type === "compare");
+ const swaps = trace.filter(e => e.type === "swap");
+ const writes = trace.filter(e => e.type === "write");
+ const reads = trace.filter(e => e.type === "read");
+
+ if (compares.length === 0 && swaps.length === 0 && writes.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Nenhuma operação relevante detectada. O Insertion Sort lê cada elemento, " +
+ "desloca os maiores para a direita e insere o elemento na posição correta.",
+ };
+ }
+
+ // ── Variante A: deslocamento (writes dominam, sem swaps) ──────────────────
+ // Requer reads >= n-1 para confirmar que os deslocamentos são reais.
+ // Sem reads, pode ser cheat sort ou outra solução inválida.
+ if (swaps.length === 0 && writes.length >= n - 1 && reads.length >= n - 1) {
+ return { match: true, variant: "shift" };
+ }
+
+ // ── Variante B: trocas adjacentes para trás ────────────────────────────────
+ if (swaps.length > 0) {
+ // Todas as trocas devem ser adjacentes
+ const nonAdjacentSwap = swaps.find(e => Math.abs(e.i - e.j) !== 1);
+ if (nonAdjacentSwap) {
+ return {
+ match: false,
+ failReason:
+ `Troca não adjacente detectada (posições ${nonAdjacentSwap.i} e ${nonAdjacentSwap.j}). ` +
+ "O Insertion Sort na variante de trocas opera apenas com elementos vizinhos (j-1 e j).",
+ };
+ }
+
+ // Verificar padrão de retrocesso: comparações com índices decrescentes
+ // Conta pares consecutivos de comparações onde o índice mínimo cai
+ let backwardPairs = 0;
+ let forwardPairs = 0;
+ for (let k = 1; k < compares.length; k++) {
+ const prevMin = Math.min(compares[k - 1].i, compares[k - 1].j);
+ const currMin = Math.min(compares[k].i, compares[k].j);
+ if (currMin < prevMin) backwardPairs++;
+ if (currMin > prevMin) forwardPairs++;
+ }
+
+ // Sinal forte de inserção: retrocesso presente
+ if (backwardPairs > 0) {
+ return { match: true, variant: "swap-backward" };
+ }
+
+ // Lista quase ordenada: poucos elementos precisaram se mover,
+ // então o padrão de retrocesso pode não aparecer. Ainda assim,
+ // se as trocas foram poucas (≤ n-1), aceitar como possível insertion sort.
+ if (swaps.length <= n - 1 && forwardPairs === 0) {
+ return { match: true, variant: "swap-minimal" };
+ }
+
+ // Apenas trocas para frente com muitas trocas → provavelmente Bubble Sort
+ return {
+ match: false,
+ failReason:
+ "O padrão de trocas adjacentes é progressivo (esquerda → direita), não regressivo. " +
+ "O Insertion Sort deve mover cada elemento para trás até encontrar sua posição — " +
+ "use j-- no loop interno, com comparações do tipo arr[j-1] > arr[j].",
+ };
+ }
+
+ // Writes presentes mas poucos e sem swaps (lista quase ordenada, poucos elementos movidos)
+ if (writes.length > 0 && reads.length > 0) {
+ return { match: true, variant: "shift-partial" };
+ }
+
+ return {
+ match: false,
+ failReason:
+ "O padrão de operações não corresponde ao Insertion Sort. " +
+ "Verifique se cada elemento é inserido na posição correta: " +
+ "desloque os maiores com vm.write(j+1, vm.read(j)) ou use trocas regressivas (j--).",
+ };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/profiles/selectionProfile.js b/app/src/atividades/programacao/ordenacao/validation/profiles/selectionProfile.js
new file mode 100644
index 0000000..8753875
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/profiles/selectionProfile.js
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Perfil de detecção: Selection Sort.
+ *
+ * Invariantes obrigatórios:
+ * 1. swapCount <= n - 1 (no máximo uma troca por passada externa).
+ * 2. compareCount >= n - 1 (a varredura interna sempre ocorre).
+ * 3. Razão comparações/trocas acima do limiar mínimo (varredura interna presente).
+ *
+ * Variações aceitas:
+ * - Buscar o mínimo (ordenação crescente) ou o máximo (decrescente).
+ * - Trocas adjacentes ou não adjacentes.
+ * - Omitir troca quando mínimo já está na posição certa (swap guardado por condicional).
+ *
+ * O que NÃO é aceito:
+ * - Mais de n-1 trocas (descarta Bubble Sort mascarado como Selection).
+ * - Poucas comparações relativas às trocas (sem varredura interna real).
+ */
+
+const MIN_COMPARE_PER_SWAP_RATIO = 2.0;
+
+/**
+ * @param {import("../core/ExecutionTrace.js").TraceEvent[]} trace
+ * @param {number} n - Tamanho da lista.
+ * @returns {{ match: boolean, failReason?: string }}
+ */
+export function selectionProfile(trace, n) {
+ const compares = trace.filter(e => e.type === "compare");
+ const swaps = trace.filter(e => e.type === "swap");
+ const reads = trace.filter(e => e.type === "read");
+ const writes = trace.filter(e => e.type === "write");
+
+ // Variante de escrita em massa sem swaps tende a Insertion/Shell/Counting, não Selection.
+ if (swaps.length === 0 && writes.length > n) {
+ return {
+ match: false,
+ failReason:
+ "Muitas operações de escrita detectadas. O Selection Sort usa trocas simples, " +
+ "não deslocamentos em massa.",
+ };
+ }
+
+ // Invariante 1: no máximo n-1 trocas
+ if (swaps.length > n - 1) {
+ return {
+ match: false,
+ failReason:
+ `${swaps.length} trocas detectadas, mas o Selection Sort realiza no máximo ${n - 1} trocas ` +
+ "(uma por passada externa). Verifique se está trocando somente ao final de cada varredura.",
+ };
+ }
+
+ // Modo atividade real: sem compare explícito (blocos nativos), usa reads + padrão de swaps.
+ if (compares.length === 0) {
+ if (swaps.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Nenhuma troca detectada. O Selection Sort normalmente realiza uma troca por passada " +
+ "(ou nenhuma quando o mínimo já está na posição certa). Verifique se o índice do mínimo " +
+ "está sendo encontrado e usado na troca final.",
+ };
+ }
+
+ const nonAdjacentSwaps = swaps.filter(e => Math.abs(e.i - e.j) > 1).length;
+ const nonAdjacentRatio = nonAdjacentSwaps / swaps.length;
+
+ if (nonAdjacentRatio < 0.5) {
+ return {
+ match: false,
+ failReason:
+ "As trocas detectadas foram majoritariamente adjacentes. Isso não parece Selection Sort. " +
+ "No Selection Sort, a troca principal normalmente ocorre entre a posição atual e o índice do mínimo.",
+ };
+ }
+
+ if (reads.length < n) {
+ return {
+ match: false,
+ failReason:
+ "Poucas leituras para caracterizar a varredura do Selection Sort. " +
+ "A cada passada, é preciso varrer a parte não ordenada buscando o menor valor.",
+ };
+ }
+
+ // Selection costuma ter poucas escritas: ~2 por troca principal.
+ // Muitas escritas indicam mais deslocamentos (Insertion/Shell) do que trocas pontuais.
+ if (writes.length > 2 * (swaps.length + 1) + 2) {
+ return {
+ match: false,
+ failReason:
+ "Foram detectadas muitas escritas para um padrão de Selection Sort. " +
+ "No Selection Sort, a escrita acontece principalmente na troca final de cada passada, " +
+ "não em deslocamentos repetidos de elementos.",
+ };
+ }
+
+ return { match: true };
+ }
+
+ // Invariante 3: razão mínima comparações/trocas
+ const ratio = compares.length / Math.max(swaps.length, 1);
+ if (ratio < MIN_COMPARE_PER_SWAP_RATIO) {
+ return {
+ match: false,
+ failReason:
+ `Poucas comparações em relação às trocas (${compares.length} comparações, ${swaps.length} trocas). ` +
+ "O Selection Sort varre toda a parte não ordenada antes de cada troca — " +
+ "o número de comparações deve ser bem maior que o de trocas.",
+ };
+ }
+
+ // Sinal adicional: comparações não adjacentes indicam varredura interna (compare j vs minIdx)
+ // O Selection Sort geralmente gera compare(j, minIdx) onde j-minIdx > 1 em muitos casos.
+ // Mas isso é um sinal de reforço, não um critério de reprovação isolado.
+
+ return { match: true };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/profiles/shellProfile.js b/app/src/atividades/programacao/ordenacao/validation/profiles/shellProfile.js
new file mode 100644
index 0000000..f3f974c
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/profiles/shellProfile.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Perfil de detecção: Shell Sort.
+ *
+ * Invariantes obrigatórios:
+ * 1. Presença de pelo menos um par consecutivo (read, write) no trace onde
+ * |write.i - read.i| > 1. Isso ocorre porque, nas passadas com gap > 1,
+ * o argumento da escrita vm.write(j, vm.read(j-gap)) gera read(j-gap)
+ * imediatamente seguido de write(j), com distância = gap.
+ *
+ * Por que isso distingue Shell Sort:
+ * - Insertion Sort shift: todos os pares (read, write) têm delta = 1 (gap=1).
+ * - Bubble/Selection: usam compare+swap, não read-write adjacentes.
+ * - Shell Sort: passes iniciais com gap > 1 geram pares com delta > 1.
+ *
+ * Variações aceitas:
+ * - Qualquer sequência de gaps: n/2, Hibbard, Knuth, etc.
+ * - Shift-based (write) ou swap-based (swap com gap > 1 também aceito).
+ *
+ * O que NÃO é aceito:
+ * - Ausência de deslocamentos com gap > 1 (todos delta = 1 → provavelmente Insertion Sort).
+ */
+
+/**
+ * @param {import("../core/ExecutionTrace.js").TraceEvent[]} trace
+ * @param {number} n - Tamanho da lista.
+ * @returns {{ match: boolean, failReason?: string }}
+ */
+export function shellProfile(trace, n) {
+ const writes = trace.filter(e => e.type === "write");
+ const reads = trace.filter(e => e.type === "read");
+
+ if (writes.length === 0 && reads.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Nenhuma leitura ou escrita detectada. " +
+ "O Shell Sort deve comparar e deslocar elementos com gap = tamanho/2 nas primeiras passadas.",
+ };
+ }
+
+ // Detectar pares consecutivos (read(p), write(q)) com |q - p| > 1
+ // Isso ocorre no Shell Sort quando vm.write(j, vm.read(j-gap)) gera read(j-gap) → write(j).
+ let nonAdjacentShifts = 0;
+ for (let k = 0; k < trace.length - 1; k++) {
+ const curr = trace[k];
+ const next = trace[k + 1];
+ if (curr.type === "read" && next.type === "write") {
+ // Shell Sort: vm.write(j, vm.read(j-gap)) → write.i = j, read.i = j-gap
+ // Portanto SEMPRE write.i > read.i (shift para a direita com gap > 0).
+ // No Counting Sort existe um par espúrio (read(n-1), write(0)) onde
+ // write.i < read.i — esse é descartado pela condição next.i > curr.i.
+ if (next.i - curr.i > 1) nonAdjacentShifts++;
+ }
+ }
+
+ // Também aceitar swap não adjacente (variante swap-based Shell Sort)
+ const nonAdjacentSwaps = trace.filter(
+ e => e.type === "swap" && Math.abs(e.i - e.j) > 1
+ );
+
+ if (nonAdjacentShifts === 0 && nonAdjacentSwaps.length === 0) {
+ return {
+ match: false,
+ failReason:
+ "Todos os deslocamentos e trocas foram entre posições adjacentes (gap = 1). " +
+ "O Shell Sort começa com gaps grandes (gap = tamanho/2) e os reduz progressivamente. " +
+ "Certifique-se de usar a variável de gap nas comparações: leia arr[j-gap] e escreva em arr[j].",
+ };
+ }
+
+ return { match: true };
+}
diff --git a/app/src/atividades/programacao/ordenacao/validation/validators.js b/app/src/atividades/programacao/ordenacao/validation/validators.js
new file mode 100644
index 0000000..564c0cd
--- /dev/null
+++ b/app/src/atividades/programacao/ordenacao/validation/validators.js
@@ -0,0 +1,109 @@
+/**
+ * @fileoverview Validação da solução no jogo Ordenação.
+ *
+ * Fluxo de validação:
+ * 1. Valores preservados — nenhum elemento foi perdido ou duplicado.
+ * 2. Ordenação correta — lista em ordem crescente.
+ * 3. Assinatura do algoritmo — obrigatória em fases "specific_algorithm".
+ * (delegado ao SortingValidationEngine quando integrado; stub até lá)
+ *
+ * Todas as mensagens de feedback vêm de configFase.validation.messages,
+ * permitindo personalização por fase sem alterar este arquivo.
+ */
+
+"use strict";
+
+import { SortingValidationEngine } from "./core/SortingValidationEngine.js";
+import { bubbleProfile } from "./profiles/bubbleProfile.js";
+import { selectionProfile } from "./profiles/selectionProfile.js";
+import { insertionProfile } from "./profiles/insertionProfile.js";
+import { shellProfile } from "./profiles/shellProfile.js";
+import { countingProfile } from "./profiles/countingProfile.js";
+
+const validationEngine = new SortingValidationEngine({
+ bubble: bubbleProfile,
+ selection: selectionProfile,
+ shell: shellProfile,
+ counting: countingProfile,
+ insertion: insertionProfile,
+});
+
+/**
+ * Verifica se todos os valores originais foram preservados (sem perdas nem duplicatas).
+ * @param {number[]} listaInicial
+ * @param {number[]} listaFinal
+ * @returns {boolean}
+ */
+function verificarValoresPreservados(listaInicial, listaFinal) {
+ if (listaInicial.length !== listaFinal.length) return false;
+ const orig = [...listaInicial].sort((a, b) => a - b);
+ const atual = [...listaFinal].sort((a, b) => a - b);
+ return orig.every((v, i) => v === atual[i]);
+}
+
+/**
+ * Verifica se a lista está em ordem crescente (não-decrescente).
+ * @param {number[]} lista
+ * @returns {boolean}
+ */
+function verificarOrdenacao(lista) {
+ return lista.every((v, i) => i === 0 || lista[i - 1] <= v);
+}
+
+/**
+ * Valida a solução do aluno para a fase atual.
+ * @param {Array} historico - Histórico de execução do interpretador
+ * @param {Object} configFase - Configuração da fase (inclui validation.messages)
+ * @param {Object} gameConfig - Configuração geral do jogo
+ * @param {Object} sceneRef - Referência à cena Phaser (contém lista atual)
+ * @returns {{ sucesso: boolean, mensagem: string }}
+ */
+export const validationSolution = (historico, configFase, gameConfig, sceneRef) => {
+ const lista = sceneRef.lista;
+ const listaInicial = configFase.lista;
+ const validation = configFase.validation ?? { mode: "result_only", messages: {} };
+ const msgs = validation.messages ?? {};
+
+ const fail = (message) => ({
+ success: false,
+ reason: message,
+ // Compatibilidade retroativa com chamadas antigas
+ sucesso: false,
+ mensagem: message,
+ });
+
+ const ok = (message) => ({
+ success: true,
+ reason: message,
+ // Compatibilidade retroativa com chamadas antigas
+ sucesso: true,
+ mensagem: message,
+ });
+
+ // 1) Valores preservados — detecta apagar ou duplicar elementos por engano
+ if (!verificarValoresPreservados(listaInicial, lista)) {
+ return fail(msgs.valoresAlterados);
+ }
+
+ // 2) Ordenação final
+ if (!verificarOrdenacao(lista)) {
+ return fail(msgs.listaDesordenada);
+ }
+
+ // 3) Assinatura do algoritmo (obrigatória em fases specific_algorithm)
+ if (validation.mode === "specific_algorithm") {
+ const resultado = validationEngine.validate({
+ initialList: listaInicial,
+ finalList: lista,
+ trace: historico,
+ expectedAlgorithm: validation.expectedAlgorithm,
+ messages: msgs,
+ });
+
+ if (!resultado.success) {
+ return fail(resultado.message);
+ }
+ }
+
+ return ok(msgs.success ?? "Correto!");
+};
diff --git a/app/src/atividades/programacao/puzzle/PuzzleGame.jsx b/app/src/atividades/programacao/puzzle/PuzzleGame.jsx
new file mode 100644
index 0000000..965aaa3
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/PuzzleGame.jsx
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview React component for PuzzleGame.jsx
+ *
+ * @module games.puzzle.PuzzleGame
+ */
+
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
+import { GameStateProvider, useGameState } from "../../../contexts/GameStateContext";
+import { starterBlocks } from "./config/starterBlock";
+import { usePuzzleTour } from "./hooks/usePuzzleTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+function PuzzleContent() {
+ const { setFailureMessage, isDebugMode } = useGameState();
+ usePuzzleTour();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(() => {
+
+
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Componente interno que monta a cena e o editor do jogo Puzzle.
+ * Registra blocos, configura toolbox dinâmico e injeta o `gameFactory`.
+ * @returns {JSX.Element} Conteúdo do jogo (editor + canvas)
+ */
+
+
+/**
+ * Componente de página que fornece o contexto de estado do jogo Puzzle.
+ * Envolve `PuzzleContent` com o `GameStateProvider` configurado.
+ * @returns {JSX.Element} Página completa do jogo Puzzle
+ */
+export default function PuzzleGame() {
+ return (
+
+
+
+ );
+}
+
+PuzzleContent.propTypes = {};
+PuzzleGame.propTypes = {};
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/abelha.png b/app/src/atividades/programacao/puzzle/assets/animais/abelha.png
new file mode 100644
index 0000000..a8dbbe4
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/abelha.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/aranha.png b/app/src/atividades/programacao/puzzle/assets/animais/aranha.png
new file mode 100644
index 0000000..bf99481
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/aranha.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/cachorro.png b/app/src/atividades/programacao/puzzle/assets/animais/cachorro.png
new file mode 100644
index 0000000..729bfb7
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/cachorro.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/caracol.png b/app/src/atividades/programacao/puzzle/assets/animais/caracol.png
new file mode 100644
index 0000000..f2c1930
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/caracol.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/cobra.png b/app/src/atividades/programacao/puzzle/assets/animais/cobra.png
new file mode 100644
index 0000000..915f315
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/cobra.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/gato.png b/app/src/atividades/programacao/puzzle/assets/animais/gato.png
new file mode 100644
index 0000000..abba19f
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/gato.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/leao.png b/app/src/atividades/programacao/puzzle/assets/animais/leao.png
new file mode 100644
index 0000000..27504df
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/leao.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/pato.png b/app/src/atividades/programacao/puzzle/assets/animais/pato.png
new file mode 100644
index 0000000..e096a59
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/pato.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/peixe.png b/app/src/atividades/programacao/puzzle/assets/animais/peixe.png
new file mode 100644
index 0000000..652b321
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/peixe.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/animais/sapo.png b/app/src/atividades/programacao/puzzle/assets/animais/sapo.png
new file mode 100644
index 0000000..ecb94fd
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/animais/sapo.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/backgrounds/background_1.png b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_1.png
new file mode 100644
index 0000000..8e86288
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_1.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/backgrounds/background_2.png b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_2.png
new file mode 100644
index 0000000..5ba3d4e
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_2.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/backgrounds/background_3.png b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_3.png
new file mode 100644
index 0000000..336de7c
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_3.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/backgrounds/background_4.png b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_4.png
new file mode 100644
index 0000000..8b2f768
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/backgrounds/background_4.png differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/abelha.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/abelha.mp3
new file mode 100644
index 0000000..4be6e93
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/abelha.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/bg_sound.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/bg_sound.mp3
new file mode 100644
index 0000000..8fb6e4c
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/bg_sound.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/cachorro.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/cachorro.mp3
new file mode 100644
index 0000000..24520f0
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/cachorro.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/gato.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/gato.mp3
new file mode 100644
index 0000000..f4fe07f
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/gato.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/leao.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/leao.mp3
new file mode 100644
index 0000000..12724ee
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/leao.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/pato.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/pato.mp3
new file mode 100644
index 0000000..72d4d74
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/pato.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/assets/sound/sapo.mp3 b/app/src/atividades/programacao/puzzle/assets/sound/sapo.mp3
new file mode 100644
index 0000000..7ef4e33
Binary files /dev/null and b/app/src/atividades/programacao/puzzle/assets/sound/sapo.mp3 differ
diff --git a/app/src/atividades/programacao/puzzle/blocks/blocks.js b/app/src/atividades/programacao/puzzle/blocks/blocks.js
new file mode 100644
index 0000000..c2aa277
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/blocks/blocks.js
@@ -0,0 +1,220 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ * @module games.quebracabecas.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import { javascriptGenerator } from "blockly/javascript";
+
+
+const HUE_TIPO = "#ff6b9d";
+const HUE_PATAS = "#c41e3a";
+const HUE_CORPO = "#9333ea";
+const HUE_MOVIMENTO = "#ed1b2f";
+const HUE_SOM = "#ed0973";
+
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const blockDefinitions = {
+ animal_tipo: { kind: "block", type: "animal_tipo" },
+ animal_patas: { kind: "block", type: "animal_patas" },
+ animal_cobertura: { kind: "block", type: "animal_cobertura" },
+ animal_locomocao: { kind: "block", type: "animal_locomocao" },
+ animal_som: { kind: "block", type: "animal_som" },
+ };
+
+ const toolboxContents = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Tipo de Animal",
+ colour: HUE_TIPO,
+ contents: [],
+ cssConfig: { container: "cat_tipo" },
+ },
+ {
+ kind: "category",
+ name: "Patas",
+ colour: HUE_PATAS,
+ contents: [],
+ cssConfig: { container: "cat_patas" },
+ },
+ {
+ kind: "category",
+ name: "Corpo",
+ colour: HUE_CORPO,
+ contents: [],
+ cssConfig: { container: "cat_corpo" },
+ },
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: HUE_MOVIMENTO,
+ contents: [],
+ cssConfig: { container: "cat_movimento" },
+ },
+ {
+ kind: "category",
+ name: "Som",
+ colour: HUE_SOM,
+ contents: [],
+ cssConfig: { container: "cat_som" },
+ },
+ ],
+ };
+
+ const categoryMap = {
+ animal_tipo: 0,
+ animal_patas: 1,
+ animal_cobertura: 2,
+ animal_locomocao: 3,
+ animal_som: 4,
+ };
+
+ allowedBlocks.forEach((blockId) => {
+ const blockDef = blockDefinitions[blockId];
+ if (!blockDef) return;
+
+ const categoryIndex = categoryMap[blockId];
+ if (categoryIndex !== undefined && toolboxContents.contents[categoryIndex]) {
+ if (!toolboxContents.contents[categoryIndex].contents) {
+ toolboxContents.contents[categoryIndex].contents = [];
+ }
+ toolboxContents.contents[categoryIndex].contents.push(blockDef);
+ }
+ });
+
+ // Filtra categorias vazias
+ toolboxContents.contents = toolboxContents.contents.filter(
+ (cat) => cat.contents && cat.contents.length > 0
+ );
+
+ return toolboxContents;
+};
+
+const defineBlocks = () => {
+ // Bloco Pai: Tipo (Abre o escopo para as características)
+ Blockly.Blocks["animal_tipo"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("Definir o tipo do animal:")
+ .appendField(new Blockly.FieldDropdown([
+ ["Mamífero", "mamifero"],
+ ["Ave", "ave"],
+ ["Réptil", "reptil"],
+ ["Anfíbio", "anfibio"],
+ ["Peixe", "peixe"],
+ ["Inseto", "inseto"],
+ ["Aracnídeo", "aracnideo"],
+ ["Molusco", "molusco"]
+ ]), "TIPO");
+
+ this.appendStatementInput("CARACTERISTICAS")
+ .setCheck(null)
+ .appendField("com as características:");
+
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_TIPO);
+ this.setTooltip("Define o tipo do animal e agrupa suas características.");
+ },
+ };
+
+ Blockly.Blocks["animal_patas"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("Quantidade de patas:")
+ .appendField(new Blockly.FieldDropdown([
+ ["0", "0"], ["2", "2"], ["4", "4"], ["6", "6"], ["8", "8"]
+ ]), "VALOR");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_PATAS);
+ },
+ };
+
+ Blockly.Blocks["animal_cobertura"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("Cobertura do corpo:")
+ .appendField(new Blockly.FieldDropdown([
+ ["Pelos", "Pelos"], ["Penas", "Penas"], ["Escamas", "Escamas"],
+ ["Casco", "Casco"], ["Pele Úmida", "Pele Úmida"], ["Pele Escamosa", "Pele Escamosa"], ["Exoesqueleto", "Exoesqueleto"]
+ ]), "VALOR");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_CORPO);
+ },
+ };
+
+ Blockly.Blocks["animal_locomocao"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("Como se locomove:")
+ .appendField(new Blockly.FieldDropdown([
+ ["Anda", "Anda"], ["Voa", "Voa"], ["Nada", "Nada"],
+ ["Rasteja", "Rasteja"], ["Pula", "Pula"]
+ ]), "VALOR");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_MOVIMENTO);
+ },
+ };
+
+ Blockly.Blocks["animal_som"] = {
+ init: function () {
+ this.appendDummyInput()
+ .appendField("Som que emite:")
+ .appendField(new Blockly.FieldDropdown([
+ ["Miado", "Miau"], ["Latido", "Auau"], ["Grasnado", "Quack"],
+ ["Zumbido", "Zumbido"], ["Rugido", "Rugido"], ["Coaxado", "Coaxado"], ["Silencioso", "Silencioso"]
+ ]), "VALOR");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(HUE_SOM);
+ },
+ };
+};
+
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+
+ // Gerador do Bloco Pai – gera código sequencial, sem callback
+ javascriptGenerator.forBlock["animal_tipo"] = function (block) {
+ const tipo = block.getFieldValue("TIPO");
+ const caracteristicasInternas = javascriptGenerator.statementToCode(block, "CARACTERISTICAS");
+
+ return `definirTipoAnimal("${tipo}");
+${caracteristicasInternas}`;
+ };
+
+ // Geradores dos Blocos Filhos
+ javascriptGenerator.forBlock["animal_patas"] = function (block) {
+ const valor = block.getFieldValue("VALOR");
+ return `definirCaracteristica("patas", ${parseInt(valor, 10)});\n`;
+ };
+
+ javascriptGenerator.forBlock["animal_cobertura"] = function (block) {
+ const valor = block.getFieldValue("VALOR");
+ return `definirCaracteristica("cobertura", "${valor}");\n`;
+ };
+
+ javascriptGenerator.forBlock["animal_locomocao"] = function (block) {
+ const valor = block.getFieldValue("VALOR");
+ return `definirCaracteristica("locomocao", "${valor}");\n`;
+ };
+
+ javascriptGenerator.forBlock["animal_som"] = function (block) {
+ const valor = block.getFieldValue("VALOR");
+ return `definirCaracteristica("som", "${valor}");\n`;
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/config/config.js b/app/src/atividades/programacao/puzzle/config/config.js
new file mode 100644
index 0000000..470a924
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/config/config.js
@@ -0,0 +1,216 @@
+/**
+ * @fileoverview Configuração para o jogo Quebra Cabeças (Propriedades com Combos)
+ * @module games.quebracabecas.config.config
+ */
+
+export const gameConfig = {
+ gameId: "puzzle",
+ gameName: "Quebra Cabeças",
+ type: "blocks",
+ icon: "🧩",
+ thumbnail: "/images/atividades/programacao/puzzle-thumbnail.png",
+ descricao:
+ "Encaixe as peças corretas para montar a ficha técnica de cada animal. Aprenda sobre propriedades e classificação usando blocos de seleção.",
+ dificuldade: "Iniciante",
+ categoria: "Variáveis",
+ tempoEstimado: "20-30 min",
+ conceitos: [
+ "Propriedades",
+ "Classificação",
+ "Atribuição de Valores",
+ ],
+ route: "/atividades/programacao/puzzle",
+ component: "QuebraCabecasGame",
+ objectives: [
+ "Entender como objetos possuem características específicas",
+ "Selecionar valores apropriados de uma lista pré-definida",
+ "Completar a ficha técnica de diferentes animais",
+ ],
+ metadata: {
+ lastUpdated: "2026-02-21",
+ version: "2.1.0",
+ },
+
+ fases: [
+ {
+ id: 1,
+ nome: "Fase 1",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Mamífero, 4 patas, cobertura Pelos, locomoção Anda e som Miado.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"mamifero","patas":4,"cobertura":"Pelos","locomocao":"Anda","som":"Miau"}',
+ chave: "GATO",
+ },
+ {
+ id: 2,
+ nome: "Fase 2",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Mamífero, 4 patas, cobertura Pelos, locomoção Anda e som Latido.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"mamifero","patas":4,"cobertura":"Pelos","locomocao":"Anda","som":"Auau"}',
+ chave: "CACHORRO",
+ },
+ {
+ id: 3,
+ nome: "Fase 3",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Inseto, 6 patas, cobertura Exoesqueleto, locomoção Voa e som Zumbido.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "flores",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"inseto","patas":6,"cobertura":"Exoesqueleto","locomocao":"Voa","som":"Zumbido"}',
+ chave: "ABELHA",
+ },
+ {
+ id: 4,
+ nome: "Fase 4",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Molusco, 0 patas, cobertura Casco, locomoção Rasteja e som Silencioso.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"molusco","patas":0,"cobertura":"Casco","locomocao":"Rasteja","som":"Silencioso"}',
+ chave: "CARACOL",
+ },
+ {
+ id: 5,
+ nome: "Fase 5",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Peixe, 0 patas, cobertura Escamas, locomoção Nada e som Silencioso.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "fundo_rio",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"peixe","patas":0,"cobertura":"Escamas","locomocao":"Nada","som":"Silencioso"}',
+ chave: "PEIXE",
+ },
+ {
+ id: 6,
+ nome: "Fase 6",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Aracnídeo, 8 patas, cobertura Exoesqueleto, locomoção Anda e som Silencioso.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"aracnideo","patas":8,"cobertura":"Exoesqueleto","locomocao":"Anda","som":"Silencioso"}',
+ chave: "ARANHA",
+ },
+ {
+ id: 7,
+ nome: "Fase 7",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Anfíbio, 4 patas, cobertura Pele Úmida, locomoção Pula e som Coaxado.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "lago",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"anfibio","patas":4,"cobertura":"Pele Úmida","locomocao":"Pula","som":"Coaxado"}',
+ chave: "SAPO",
+ },
+ {
+ id: 8,
+ nome: "Fase 8",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Ave, 2 patas, cobertura Penas, locomoção Voa e som Grasnado.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "lago",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"ave","patas":2,"cobertura":"Penas","locomocao":"Voa","som":"Quack"}',
+ chave: "PATO",
+ },
+ {
+ id: 9,
+ nome: "Fase 9",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Réptil, 0 patas, cobertura Escamas, locomoção Rasteja e som Silencioso.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"reptil","patas":0,"cobertura":"Pele Escamosa","locomocao":"Rasteja","som":"Silencioso"}',
+ chave: "COBRA",
+ },
+ {
+ id: 10,
+ nome: "Fase 10",
+ descricao: "Use o bloco de tipo e os 4 blocos de características. Configure: tipo Mamífero, 4 patas, cobertura Pelos, locomoção Anda e som Rugido.",
+ timeout: 30,
+ maxBlocks: 5,
+ background: "floresta",
+ allowedBlocks: [
+ "animal_tipo",
+ "animal_patas",
+ "animal_cobertura",
+ "animal_locomocao",
+ "animal_som",
+ ],
+ expectedOutput: '{"tipo":"mamifero","patas":4,"cobertura":"Pelos","locomocao":"Anda","som":"Rugido"}',
+ chave: "LEAO",
+ },
+ ],
+ mensagens: {
+ entradaIncorreta:
+ "Ainda faltam peças no seu quebra-cabeças. Certifique-se de adicionar e configurar todos os blocos na área de trabalho.",
+ saidaIncorreta:
+ "Alguma peça não se encaixa com este animal! Verifique as seleções nos menus e tente novamente.",
+ erroGeral: "Ocorreu um erro. Limpe a área de trabalho e tente montar novamente.",
+ sucessoGenerico: "Perfeito! Você montou a ficha do animal corretamente.",
+ timeoutExcedido:
+ "O tempo esgotou! Verifique suas peças e tente mais uma vez.",
+ },
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/config/debugSolutions.js b/app/src/atividades/programacao/puzzle/config/debugSolutions.js
new file mode 100644
index 0000000..11b5c4a
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/config/debugSolutions.js
@@ -0,0 +1,18 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.puzzle.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"K5~S`b5yI.9NiRz?S2L{","x":13,"y":13,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"(H$#^dMx/bcYl{@mke6D","fields":{"VALOR":"4"},"next":{"block":{"type":"animal_cobertura","id":"`1o67NBj?p;/bYk4_S76","fields":{"VALOR":"Pelos"},"next":{"block":{"type":"animal_locomocao","id":"S`7PLu75Atg}^O7J!F^m","fields":{"VALOR":"Anda"},"next":{"block":{"type":"animal_som","id":"rF2)7!*v{.9fjv)jB.ox","fields":{"VALOR":"Miau"}}}}}}}}}}}]}},
+ 2: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"TJW)g9GwDS@Z4-!i4law","x":63,"y":38,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"rLIMUfgLl{v]|Q:edyTD","fields":{"VALOR":"4"},"next":{"block":{"type":"animal_cobertura","id":"O`Qvhb)10/c^y2_(3+jR","fields":{"VALOR":"Pelos"},"next":{"block":{"type":"animal_locomocao","id":"VvV_uNK_PTDkrK;DV6`w","fields":{"VALOR":"Anda"},"next":{"block":{"type":"animal_som","id":"JE@ctR6TyNcPSR1]Ha/r","fields":{"VALOR":"Auau"}}}}}}}}}}}]}},
+ 3: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"O)[|/:hoLV6GWc4?dXBt","x":13,"y":13,"fields":{"TIPO":"inseto"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"oR_+63ndID?[yW0C9?N3","fields":{"VALOR":"6"},"next":{"block":{"type":"animal_cobertura","id":"9[`B,(W%#TYW^yjjzY`a","fields":{"VALOR":"Exoesqueleto"},"next":{"block":{"type":"animal_locomocao","id":"Mou}FRrs~b2^nA[Js53M","fields":{"VALOR":"Voa"},"next":{"block":{"type":"animal_som","id":"f:lF5HN{qCpWLMdPMm[$","fields":{"VALOR":"Zumbido"}}}}}}}}}}}]}},
+ 4: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"C`OZkv!pbnh;-77#SwZl","x":38,"y":38,"fields":{"TIPO":"molusco"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"!m*[%!Mx(94OIpNm;,~-","fields":{"VALOR":"0"},"next":{"block":{"type":"animal_cobertura","id":"^DxvLh@l)t{nZP8w,^]I","fields":{"VALOR":"Casco"},"next":{"block":{"type":"animal_locomocao","id":"PTp$]A6Jx{jZbMl*$KRw","fields":{"VALOR":"Rasteja"},"next":{"block":{"type":"animal_som","id":"!r.OS6qety!CirCq}0Jc","fields":{"VALOR":"Silencioso"}}}}}}}}}}}]}},
+ 5: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"3hgtX:jrjB0u~r]n=T%D","x":13,"y":13,"fields":{"TIPO":"peixe"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_som","id":"emK,.!hE2ayz~ve{Xw$p","fields":{"VALOR":"Silencioso"},"next":{"block":{"type":"animal_patas","id":"zxr}h]m}VYvWA3g}=%c~","fields":{"VALOR":"0"},"next":{"block":{"type":"animal_cobertura","id":"](jM3wt/C#s-$nGUubUo","fields":{"VALOR":"Escamas"},"next":{"block":{"type":"animal_locomocao","id":":Hq9+F5LGoXQV.`-Fy(=","fields":{"VALOR":"Nada"}}}}}}}}}}}]}},
+ 6: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"=p%M%VUi=IZ^mtTNF;HJ","x":13,"y":13,"fields":{"TIPO":"aracnideo"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"X?,KiQm9sW;G_RVcllOS","fields":{"VALOR":"8"},"next":{"block":{"type":"animal_cobertura","id":"10I/eVKNEaSuLb:9QME8","fields":{"VALOR":"Exoesqueleto"},"next":{"block":{"type":"animal_locomocao","id":"i]@}H1|1%Cg:*{k:-K`}","fields":{"VALOR":"Anda"},"next":{"block":{"type":"animal_som","id":"Gw7N*`EM/%C`02)aZ?(Z","fields":{"VALOR":"Silencioso"}}}}}}}}}}}]}},
+ 7: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"p,=sNBk5T:N3%z%u^Kt(","x":13,"y":13,"fields":{"TIPO":"anfibio"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"dQW3~ujAQ)CkT:%P#I.5","fields":{"VALOR":"4"},"next":{"block":{"type":"animal_cobertura","id":"m-TYA,fEp6D|^jO4nDiZ","fields":{"VALOR":"Pele Úmida"},"next":{"block":{"type":"animal_locomocao","id":"Rn`@9^I(|q8:$tm#}Jze","fields":{"VALOR":"Pula"},"next":{"block":{"type":"animal_som","id":"slk~sH2Cg6qe[vnYw7^2","fields":{"VALOR":"Coaxado"}}}}}}}}}}}]}},
+ 8: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"=a1Ks;#*woxAc8CgY.F@","x":13,"y":13,"fields":{"TIPO":"ave"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"iJdDzC|p7yi#B;t6={P`","fields":{"VALOR":"2"},"next":{"block":{"type":"animal_cobertura","id":"nK$,6x4sCVn$QmB%naP3","fields":{"VALOR":"Penas"},"next":{"block":{"type":"animal_locomocao","id":"9yK}d=b;mbYVGi$3{kx5","fields":{"VALOR":"Voa"},"next":{"block":{"type":"animal_som","id":"%X!MFKvoJYsr^Q}2lu`I","fields":{"VALOR":"Quack"}}}}}}}}}}}]}},
+ 9: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"bh)Afv8i_cpV%V~j%%@Z","x":13,"y":13,"fields":{"TIPO":"reptil"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"Q3oZd$=i-/jMS-823Tr0","fields":{"VALOR":"0"},"next":{"block":{"type":"animal_cobertura","id":"K9N6DmVJ1WGC(Vz%|$kW","fields":{"VALOR":"Pele Escamosa"},"next":{"block":{"type":"animal_locomocao","id":"#Hy,T_0,V/0DYg0fLgmi","fields":{"VALOR":"Rasteja"},"next":{"block":{"type":"animal_som","id":"m;:p`j)p~WuLj$@m}A)b","fields":{"VALOR":"Silencioso"}}}}}}}}}}}]}},
+ 10: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"7Dcy*gc7#wdVxBeGjn+6","x":34,"y":80,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"K`a_VPv(+szsSYg[4%w/","fields":{"VALOR":"4"},"next":{"block":{"type":"animal_cobertura","id":";/}=40DUxVm0/ceT[j?w","fields":{"VALOR":"Pelos"},"next":{"block":{"type":"animal_locomocao","id":"G0C^FD54_sX(7|h:oFwh","fields":{"VALOR":"Anda"},"next":{"block":{"type":"animal_som","id":"+0:-_K|{*8r!@Y(dBH_m","fields":{"VALOR":"Rugido"}}}}}}}}}}}]}},
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/config/starterBlock.js b/app/src/atividades/programacao/puzzle/config/starterBlock.js
new file mode 100644
index 0000000..27be03d
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/config/starterBlock.js
@@ -0,0 +1,12 @@
+/**
+ * @fileoverview Utility module for starterBlocks.js
+ *
+ * @module games.puzzle.config.starterBlocks
+ */
+
+export const starterBlocks = {
+ 1: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"K5~S`b5yI.9NiRz?S2L{","x":13,"y":13,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"(H$#^dMx/bcYl{@mke6D","fields":{"VALOR":"2"},"next":{"block":{"type":"animal_cobertura","id":"`1o67NBj?p;/bYk4_S76","fields":{"VALOR":"Penas"},"next":{"block":{"type":"animal_locomocao","id":"S`7PLu75Atg}^O7J!F^m","fields":{"VALOR":"Anda"},"next":{"block":{"type":"animal_som","id":"rF2)7!*v{.9fjv)jB.ox","fields":{"VALOR":"Auau"}}}}}}}}}}}]}},
+ 2: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"TJW)g9GwDS@Z4-!i4law","x":63,"y":38,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"rLIMUfgLl{v]|Q:edyTD","fields":{"VALOR":"0"},"next":{"block":{"type":"animal_cobertura","id":"O`Qvhb)10/c^y2_(3+jR","fields":{"VALOR":"Pelos"}}}}}}}]}},
+ 3: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"O)[|/:hoLV6GWc4?dXBt","x":13,"y":13,"fields":{"TIPO":"mamifero"},"inputs":{"CARACTERISTICAS":{"block":{"type":"animal_patas","id":"oR_+63ndID?[yW0C9?N3","fields":{"VALOR":"0"},"next":{"block":{"type":"animal_cobertura","id":"9[`B,(W%#TYW^yjjzY`a","fields":{"VALOR":"Pelos"},"next":{"block":{"type":"animal_locomocao","id":"Mou}FRrs~b2^nA[Js53M","fields":{"VALOR":"Anda"}}}}}}}}}]}},
+ 4: {"blocks":{"languageVersion":0,"blocks":[{"type":"animal_tipo","id":"C`OZkv!pbnh;-77#SwZl","x":38,"y":38,"fields":{"TIPO":"mamifero"}}]}},
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/config/tourSteps.js b/app/src/atividades/programacao/puzzle/config/tourSteps.js
new file mode 100644
index 0000000..1894737
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/config/tourSteps.js
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview Tour steps para o jogo Quebra-Cabeças.
+ * @module games.puzzle.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+const puzzleIcon = `
+
+ `;
+
+export const puzzleTourSteps = [
+ createWelcomeStep({
+ gameName: "Quebra-Cabeças Animal",
+ description:
+ "Bem-vindo ao Quebra-Cabeças! Aqui você vai aprender como os objetos têm propriedades e como atribuir valores corretos a cada característica.",
+ challenge:
+ "Use os blocos de seleção para montar a ficha técnica completa de cada animal — tipo, patas, cobertura, locomoção e som!",
+ iconSvg: puzzleIcon,
+ }),
+
+ createGameAreaStep({
+ title: "Arena dos Animais",
+ description:
+ "Nesta tela você verá os animais aparecendo enquanto seu código é executado. Ao acertar, o animal correto aparece na tela!",
+ }),
+
+ createToolboxStep({
+ description:
+ "Aqui ficam as peças do puzzle! Cada categoria corresponde a uma característica do animal: Tipo de Animal, Patas, Corpo, Movimento e Som. Arraste os blocos para a área de trabalho.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte a ficha do animal aqui. Primeiro coloque o bloco de Tipo de Animal e, dentro dele, encaixe os blocos das características na ordem correta.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Execute seu código para ver as peças encaixando! Cada bloco exibirá uma frase na tela — por exemplo: \"É um MAMÍFERO!\", \"Tem 4 patas!\". Acompanhe o feedback em tempo real.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Se as peças não encaixarem, use o reset para limpar a área e montar o animal novamente do zero.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "O jogo tem 10 animais para montar, cada um com suas características únicas — de gatos e leões a polvos e aranhas. Avance pelas fases para desbloquear todos!",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Leia com atenção o enunciado de cada fase — ele descreve as características exatas do animal que você precisa montar.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Ficou perdido? Clique no botão de ajuda para rever este tour a qualquer momento.",
+ }),
+];
+
+export const puzzleTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/puzzle/game.js b/app/src/atividades/programacao/puzzle/game.js
new file mode 100644
index 0000000..675966d
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/game.js
@@ -0,0 +1,276 @@
+/**
+ * @fileoverview Utility module for game.js
+ * * @module games.puzzle.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupQuebraCabecasAPI } from "./hooks/interpreterSetup.js";
+import { validationSolution } from "./validation/validators.js";
+import { gameConfig } from "./config/config.js";
+import { ConstantesJogo, ConstantesAssets, ConstantesSons } from "./ui/constants.js";
+import { inicializarLayout } from "./ui/layout.js";
+import { pulsarImagem, silhuetaTremendo, mostrarFeedback, iniciarLoopAnimais, pararLoopAnimais } from "./ui/animations.js";
+
+class QuebraCabecasScene extends BaseGameScene {
+ constructor() {
+ super("QuebraCabecasScene");
+
+ // Estado principal do jogo: armazena as propriedades escolhidas pelo aluno
+ this.animalMontado = {};
+
+ // Aqui você pode adicionar variáveis para guardar referências aos Sprites
+ // ex: this.spriteCorpo = null; this.spritePatas = null;
+ }
+
+ /**
+ * Inicializa a cena com dados opcionais passados pelo React/Router.
+ * @param {Object} data - Dados de inicialização
+ * @returns {void}
+ */
+ init(data) {
+ super.init(data);
+ this.limparVariaveis();
+ }
+
+ /**
+ * Faz o preload dos recursos necessários.
+ * @returns {void}
+ */
+ preload() {
+ this.preloadGlobalAssets();
+
+ const { CHAVES, PATHS } = ConstantesAssets;
+
+ // Carrega os backgrounds
+ this.load.image(CHAVES.BACKGROUND_1, PATHS.BACKGROUND_1);
+ this.load.image(CHAVES.BACKGROUND_2, PATHS.BACKGROUND_2);
+ this.load.image(CHAVES.BACKGROUND_3, PATHS.BACKGROUND_3);
+ this.load.image(CHAVES.BACKGROUND_4, PATHS.BACKGROUND_4);
+
+ // Carrega os animais
+ const chavesAnimais = ['ABELHA', 'ARANHA', 'CACHORRO', 'CARACOL', 'COBRA', 'GATO', 'LEAO', 'PATO', 'PEIXE', 'SAPO'];
+ chavesAnimais.forEach(animal => {
+ this.load.image(CHAVES[animal], PATHS[animal]);
+ });
+
+ // Carrega os sons do puzzle
+ const { CHAVES: SC, PATHS: SP } = ConstantesSons;
+ this.load.audio(SC.BG, SP.BG);
+ this.load.audio(SC.CACHORRO, SP.CACHORRO);
+ this.load.audio(SC.GATO, SP.GATO);
+ this.load.audio(SC.PATO, SP.PATO);
+ this.load.audio(SC.LEAO, SP.LEAO);
+ this.load.audio(SC.SAPO, SP.SAPO);
+ }
+
+ /**
+ * Cria os elementos visuais e liga o interpretador JavaScript aos blocos.
+ * @returns {void}
+ */
+ create() {
+ // Liga o controlador base com a nossa API customizada e o validador
+ this.setupStandardController(
+ () => setupQuebraCabecasAPI(this, { animationSpeed: 100 }),
+ (historico) => validationSolution(historico, this.configFase, gameConfig, this),
+ );
+
+ // Inicialização da interface
+ const layout = inicializarLayout(this);
+ this.layout = layout;
+ }
+
+ /**
+ * Hook da BaseGameScene: chamado exatamente antes de rodar o código do aluno.
+ * @returns {void}
+ */
+ onBeforeRun() {
+ this.historico = [];
+ this.limparVariaveis();
+ this.playAudio(ConstantesSons.CHAVES.BG, { loop: true, volume: 0.3 });
+
+ // Loop visual de todos os animais enquanto o aluno executa o código
+ const keys = ['ABELHA', 'ARANHA', 'CACHORRO', 'CARACOL', 'COBRA', 'GATO', 'LEAO', 'PATO', 'PEIXE', 'SAPO']
+ .map(k => ConstantesAssets.CHAVES[k]);
+ iniciarLoopAnimais(this, keys, this._centroX(), this._centroY());
+ }
+
+ /**
+ * Hook da BaseGameScene: chamado quando o aluno clica em "Resetar" ou cancela.
+ * Para o som de background imediatamente.
+ * @returns {void}
+ */
+ onReset() {
+ this.stopAudio(ConstantesSons.CHAVES.BG);
+ pararLoopAnimais(this);
+ this.limparVariaveis();
+ if (this.imagemChave) {
+ this.imagemChave.destroy();
+ this.imagemChave = null;
+ }
+ }
+
+ /**
+ * Reseta o objeto do animal e limpa os sprites da tela.
+ * @returns {void}
+ */
+ limparVariaveis() {
+ this.animalMontado = {};
+ this.historico = [];
+ if (this.imagemChave) {
+ this.imagemChave.destroy();
+ this.imagemChave = null;
+ }
+ if (this._textoFeedback) {
+ this._textoFeedback.destroy();
+ this._textoFeedback = null;
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+ // Helpers
+ // ---------------------------------------------------------------------------
+
+ _centroX() { return this.layout?.centroX ?? ConstantesJogo.LARGURA_TELA / 2; }
+ _centroY() { return this.layout?.centroY ?? ConstantesJogo.ALTURA_TELA / 2; }
+ _topoY() { return 30; }
+
+ /**
+ * Converte chave/valor do bloco numa frase natural para exibição.
+ * @param {string} chave - nome da propriedade (tipo, patas, cobertura, locomocao, som)
+ * @param {string|number} valor - valor seleccionado no bloco
+ * @returns {string}
+ */
+ _mensagemNatural(chave, valor) {
+ const TIPO = {
+ mamifero: "É um MAMÍFERO!",
+ ave: "É uma AVE!",
+ reptil: "É um RÉPTIL!",
+ anfibio: "É um ANFÍBIO!",
+ peixe: "É um PEIXE!",
+ inseto: "É um INSETO!",
+ aracnideo: "É um ARACNÍDEO!",
+ molusco: "É um MOLUSCO!",
+ };
+ const LOCOMOCAO = {
+ Anda: "Ele ANDA!",
+ Voa: "Ele VOA!",
+ Nada: "Ele NADA!",
+ Rasteja: "Ele RASTEJA!",
+ Pula: "Ele PULA!",
+ };
+ const SOM = {
+ Miau: "Ele MIA!",
+ Auau: "Ele LATE!",
+ Quack: "Ele GRASNA!",
+ Zumbido: "Ele ZUMBE!",
+ Rugido: "Ele RUGE!",
+ Coaxado: "Ele COAXA!",
+ Silencioso:"Não emite sons!",
+ };
+
+ if (chave === "tipo") return TIPO[valor] ?? `É um ${String(valor).toUpperCase()}!`;
+ if (chave === "patas") return String(valor) === "0" ? "Não tem patas!" : `Tem ${valor} patas!`;
+ if (chave === "cobertura") return `Tem ${String(valor).toUpperCase()}!`;
+ if (chave === "locomocao") return LOCOMOCAO[valor] ?? `Ele ${String(valor).toUpperCase()}!`;
+ if (chave === "som") return SOM[valor] ?? `Ele ${String(valor).toUpperCase()}!`;
+ return `${chave}: ${valor}`;
+ }
+
+ // ---------------------------------------------------------------------------
+ // API chamada pelo interpretador
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Salva o tipo "Pai" do animal e exibe feedback visual temporário (1s).
+ * Assíncrono: o interpretador aguarda a Promise antes de continuar.
+ * @param {string} tipoValor - mamifero, ave, reptil, etc.
+ * @returns {Promise}
+ */
+ async definirTipoAnimal(tipoValor) {
+ this.animalMontado["tipo"] = tipoValor;
+ this.historico.push({ tipo: "criar_animal", valor: tipoValor });
+ await mostrarFeedback(this, this._mensagemNatural("tipo", tipoValor), this._centroX(), this._topoY());
+ }
+
+ /**
+ * Registra uma característica do animal e exibe feedback visual temporário (1s).
+ * Assíncrono: o interpretador aguarda a Promise antes de continuar.
+ * @param {string} chave - A propriedade (ex: "patas", "cobertura")
+ * @param {string|number} valor - O valor selecionado no combo (ex: 4, "pelo")
+ * @returns {Promise}
+ */
+ async definirCaracteristica(chave, valor) {
+ this.animalMontado[chave] = valor;
+ this.historico.push({ tipo: "definir_caracteristica", chave: chave, valor: valor });
+
+ // Toca o som correspondente ao valor, se for o bloco de som
+ if (chave === "som") {
+ const chaveSom = ConstantesSons.BLOCO_PARA_CHAVE[valor];
+ if (chaveSom) this.playAudio(chaveSom);
+ }
+
+ await mostrarFeedback(this, this._mensagemNatural(chave, valor), this._centroX(), this._topoY());
+ }
+
+ // ---------------------------------------------------------------------------
+ // Hooks de resultado
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Hook async: BaseGameScene aguarda esta Promise antes de disparar gameSuccess().
+ * Para o background, exibe a imagem do animal com efeito Pulsar.
+ * @returns {Promise}
+ */
+ async onSuccess() {
+ this.stopAudio(ConstantesSons.CHAVES.BG);
+ await pararLoopAnimais(this);
+ const chave = this.configFase?.chave;
+ if (chave && ConstantesAssets.CHAVES[chave]) {
+ if (this.imagemChave) { this.imagemChave.destroy(); this.imagemChave = null; }
+ this.imagemChave = await pulsarImagem(
+ this, ConstantesAssets.CHAVES[chave], this._centroX(), this._centroY(),
+ );
+ }
+ }
+
+ async onFailure() {
+ this.stopAudio(ConstantesSons.CHAVES.BG);
+ await pararLoopAnimais(this);
+ const chave = this.configFase?.chave;
+ if (chave && ConstantesAssets.CHAVES[chave]) {
+ if (this.imagemChave) { this.imagemChave.destroy(); this.imagemChave = null; }
+ this.imagemChave = await silhuetaTremendo(
+ this, ConstantesAssets.CHAVES[chave], this._centroX(), this._centroY(),
+ );
+ }
+ }
+}
+
+/**
+ * Cria a configuração Phaser para o jogo Quebra Cabeças.
+ * Retorna o objeto de configuração usado por `new Phaser.Game(config)`
+ * * @param {HTMLElement} elementoPai - Elemento DOM que conterá o canvas Phaser
+ * @param {Object} configFaseAtual - Configuração da fase atual
+ * @returns {Object} Configuração Phaser
+ */
+export const createGame = (elementoPai, configFaseAtual) => {
+ const scene = new QuebraCabecasScene();
+
+ // Caso não tenha importado ConstantesJogo, defina as dimensões e cor de fundo diretamente aqui
+ return {
+ type: Phaser.AUTO,
+ width: ConstantesJogo.LARGURA_TELA,
+ height: ConstantesJogo.ALTURA_TELA,
+ backgroundColor: ConstantesJogo.COR_FUNDO,
+ parent: elementoPai,
+ scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
+ scene: scene,
+ callbacks: {
+ preBoot: function (game) {
+ game.registry.set("configFase", configFaseAtual);
+ game.registry.set("gameConfig", gameConfig);
+ },
+ },
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/hooks/interpreterSetup.js b/app/src/atividades/programacao/puzzle/hooks/interpreterSetup.js
new file mode 100644
index 0000000..ecbe454
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/hooks/interpreterSetup.js
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ * * @module games.quebracabecas.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Configura a API disponível ao interpretador para o jogo Quebra Cabeças.
+ * Registra funções que chamam métodos da `scene` com wrappers do `ApiHelpers`.
+ * @param {Object} scene - Instância da cena Phaser (ex.: `QuebraCabecasScene`)
+ * @param {Object} [config] - Opções (ex.: `animationSpeed`)
+ * @returns {Function} Função que realiza o registro no `interpreter` e `globalScope`
+ */
+export const setupQuebraCabecasAPI = (scene, config = {}) => {
+ return (interpreter, globalScope) => {
+
+ // 1. definirTipoAnimal(tipo) – registra o tipo do animal
+ const definirTipoAnimalWrapper = function(tipo, done) {
+ const tipoValor = tipo && tipo.data !== undefined ? tipo.data : String(tipo ?? "");
+ const result = scene.definirTipoAnimal(tipoValor);
+ if (result && typeof result.then === "function") {
+ result.then(() => done()).catch(() => done());
+ } else {
+ done();
+ }
+ };
+
+ interpreter.setProperty(
+ globalScope,
+ "definirTipoAnimal",
+ interpreter.createAsyncFunction(definirTipoAnimalWrapper)
+ );
+
+ // 2. definirCaracteristica(chave, valor) – wrapper async com dois argumentos reais.
+ const definirCaracteristicaWrapper = function(chave, valor, done) {
+ const chaveReal = chave && chave.data !== undefined ? chave.data : String(chave ?? "");
+ const valorReal = valor && valor.data !== undefined ? valor.data : (valor instanceof Object ? String(valor) : valor);
+
+ const result = scene.definirCaracteristica(chaveReal, valorReal);
+ if (result && typeof result.then === "function") {
+ result.then(() => done()).catch(() => done());
+ } else {
+ done();
+ }
+ };
+
+ interpreter.setProperty(
+ globalScope,
+ "definirCaracteristica",
+ interpreter.createAsyncFunction(definirCaracteristicaWrapper)
+ );
+
+ // 3. Highlight padrão do Blockly
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/hooks/usePuzzleTour.js b/app/src/atividades/programacao/puzzle/hooks/usePuzzleTour.js
new file mode 100644
index 0000000..b1c3fb0
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/hooks/usePuzzleTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for usePuzzleTour.js
+ *
+ * @module games.puzzle.hooks.usePuzzleTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { puzzleTourSteps, puzzleTourOptions } from "../config/tourSteps";
+
+export const usePuzzleTour = () => {
+ /**
+ * Hook que retorna o controlador de tour para o jogo Quebra-Cabeças.
+ * Encapsula `useGameTour` com os passos e opções específicos.
+ * @returns {Object} API do tour (start, stop, etc.)
+ */
+ return useGameTour("puzzle", puzzleTourSteps, puzzleTourOptions);
+};
diff --git a/app/src/atividades/programacao/puzzle/ui/animations.js b/app/src/atividades/programacao/puzzle/ui/animations.js
new file mode 100644
index 0000000..a8934f5
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/ui/animations.js
@@ -0,0 +1,266 @@
+/**
+ * @fileoverview Animações da cena do Quebra-Cabeças.
+ * Cada função recebe a cena Phaser e retorna uma Promise que resolve
+ * quando a animação termina — permitindo await no game.js.
+ *
+ * @module games.puzzle.ui.animations
+ */
+
+import sfFont from "../../../../assets/fonts/SF Slapstick Comic Shaded.ttf";
+import { registerFont } from "../../../../utils/loadFont.js";
+
+registerFont("SFSlapstick", sfFont, "truetype");
+
+/** Estilo base para textos de feedback (cartoon + contorno). */
+const ESTILO_TEXTO = {
+ fontFamily: '"SFSlapstick", cursive',
+ fontSize: "60px",
+ color: "#ffffff",
+ stroke: "#302f2f",
+ strokeThickness: 12,
+ shadow: { offsetX: 2, offsetY: 2, color: "#302f2f", blur: 4, fill: false },
+};
+
+// ---------------------------------------------------------------------------
+// Loop de animais durante a execução
+// ---------------------------------------------------------------------------
+
+/**
+ * Inicia um loop que exibe todos os animais em sequência com efeito de pulsar.
+ * Corre enquanto `scene._loopAnimaisAtivo` for true.
+ * Chame `pararLoopAnimais(scene)` para parar e aguardar o fim da iteração atual.
+ *
+ * @param {Phaser.Scene} scene - Cena Phaser activa
+ * @param {string[]} assetKeys - Array de chaves de assets já carregados
+ * @param {number} x - Posição X central
+ * @param {number} y - Posição Y central
+ */
+/** Embaralha um array usando Fisher-Yates (cópia). */
+function _shuffle(arr) {
+ const a = [...arr];
+ for (let i = a.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [a[i], a[j]] = [a[j], a[i]];
+ }
+ return a;
+}
+
+export function iniciarLoopAnimais(scene, assetKeys, x, y) {
+ scene._loopAnimaisAtivo = true;
+ scene._imagemLoopAtual = null;
+ scene._loopResolveIteracao = null;
+
+ // Promise que resolve quando o ciclo sair do while
+ scene._loopParadoPromessa = new Promise((stopResolve) => {
+ async function ciclo() {
+ let fila = [];
+ while (scene._loopAnimaisAtivo) {
+ // Repõe a fila embaralhada quando estiver vazia
+ if (fila.length === 0) fila = _shuffle(assetKeys);
+ const key = fila.shift();
+
+ await new Promise((resolve) => {
+ // Guarda o resolver para poder cancelar externamente
+ scene._loopResolveIteracao = resolve;
+
+ const img = scene.add
+ .image(x, y, key)
+ .setAlpha(0)
+ .setScale(0)
+ .setDepth(49)
+ .setTintFill(0xffffff);
+ scene._imagemLoopAtual = img;
+
+ if (!scene._loopAnimaisAtivo) { img.destroy(); resolve(); return; }
+
+ // Surgir
+ scene.tweens.add({
+ targets: img, alpha: 1, scale: 1.0,
+ duration: 350, ease: "Back.easeOut",
+ onComplete: () => {
+ if (!scene._loopAnimaisAtivo) { img.destroy(); resolve(); return; }
+
+ // Aguardar brevemente e sumir
+ scene.time.delayedCall(300, () => {
+ if (!scene._loopAnimaisAtivo) { img.destroy(); resolve(); return; }
+
+ scene.tweens.add({
+ targets: img, alpha: 0, scale: 0.8,
+ duration: 250, ease: "Sine.easeIn",
+ onComplete: () => {
+ img.destroy();
+ scene._imagemLoopAtual = null;
+ resolve();
+ },
+ });
+ });
+ },
+ });
+ });
+ }
+ stopResolve();
+ }
+
+ ciclo();
+ });
+}
+
+/**
+ * Para o loop de animais imediatamente (destrói a imagem actual)
+ * e retorna uma Promise que resolve quando o ciclo encerrou por completo.
+ *
+ * @param {Phaser.Scene} scene
+ * @returns {Promise}
+ */
+export function pararLoopAnimais(scene) {
+ if (!scene._loopAnimaisAtivo) return scene._loopParadoPromessa ?? Promise.resolve();
+
+ scene._loopAnimaisAtivo = false;
+
+ // Destrói imagem visível agora
+ if (scene._imagemLoopAtual && !scene._imagemLoopAtual.destroyed) {
+ scene.tweens.killTweensOf(scene._imagemLoopAtual);
+ scene._imagemLoopAtual.destroy();
+ scene._imagemLoopAtual = null;
+ }
+
+ // Desbloqueia o await interno do ciclo
+ if (scene._loopResolveIteracao) {
+ scene._loopResolveIteracao();
+ scene._loopResolveIteracao = null;
+ }
+
+ return scene._loopParadoPromessa ?? Promise.resolve();
+}
+
+// ---------------------------------------------------------------------------
+// Animações de resultado
+// ---------------------------------------------------------------------------
+
+/**
+ * Exibe a imagem do animal com efeito de "Pulsar":
+ * 1. Surge do centro (scale 0 → 1.15) com fade-in rápido
+ * 2. Recua levemente (1.15 → 0.95)
+ * 3. Assenta no tamanho final (0.95 → 1.0)
+ * Resolve a Promise quando a sequência completa (~700ms).
+ *
+ * @param {Phaser.Scene} scene - Cena Phaser activa
+ * @param {string} assetKey - Chave do asset já carregado
+ * @param {number} x - Posição X central
+ * @param {number} y - Posição Y central
+ * @returns {Promise}
+ */
+export function pulsarImagem(scene, assetKey, x, y) {
+ return new Promise((resolve) => {
+ const img = scene.add
+ .image(x, y, assetKey)
+ .setAlpha(0)
+ .setScale(0)
+ .setDepth(50);
+
+ scene.tweens.add({
+ targets: img, alpha: 1, scale: 1.15,
+ duration: 400, ease: "Back.easeOut",
+ onComplete: () => {
+ scene.tweens.add({
+ targets: img, scale: 0.95,
+ duration: 150, ease: "Sine.easeIn",
+ onComplete: () => {
+ scene.tweens.add({
+ targets: img, scale: 1.0,
+ duration: 150, ease: "Sine.easeOut",
+ onComplete: () => resolve(img),
+ });
+ },
+ });
+ },
+ });
+ });
+}
+
+/**
+ * Exibe a silhueta branca do animal (tintFill) com animação de tremor.
+ * Resolve a Promise quando a sequência completa (~900ms).
+ *
+ * @param {Phaser.Scene} scene - Cena Phaser activa
+ * @param {string} assetKey - Chave do asset já carregado
+ * @param {number} x - Posição X central
+ * @param {number} y - Posição Y central
+ * @returns {Promise}
+ */
+export function silhuetaTremendo(scene, assetKey, x, y) {
+ return new Promise((resolve) => {
+ const img = scene.add
+ .image(x, y, assetKey)
+ .setAlpha(0)
+ .setScale(0)
+ .setDepth(50)
+ .setTintFill(0xffffff);
+
+ scene.tweens.add({
+ targets: img, alpha: 1, scale: 1.0,
+ duration: 300, ease: "Back.easeOut",
+ onComplete: () => {
+ scene.tweens.add({
+ targets: img,
+ x: { from: x - 12, to: x + 12 },
+ duration: 60, ease: "Sine.easeInOut",
+ yoyo: true, repeat: 5,
+ onComplete: () => { img.setX(x); resolve(img); },
+ });
+ },
+ });
+ });
+}
+
+// ---------------------------------------------------------------------------
+// Feedback de bloco (texto temporário)
+// ---------------------------------------------------------------------------
+
+/**
+ * Exibe texto de feedback com fonte cartoon + contorno.
+ * O texto some automaticamente (fade-out) após 700ms — total ~1000ms.
+ * Substitui qualquer texto de feedback anterior.
+ *
+ * @param {Phaser.Scene} scene - Cena Phaser activa
+ * @param {string} texto - Conteúdo a exibir
+ * @param {number} x - Posição X (centralizado)
+ * @param {number} y - Posição Y
+ * @returns {Promise}
+ */
+export function mostrarFeedback(scene, texto, x, y) {
+ return new Promise((resolve) => {
+ if (scene._textoFeedback) {
+ scene.tweens.killTweensOf(scene._textoFeedback);
+ scene._textoFeedback.destroy();
+ scene._textoFeedback = null;
+ }
+
+ // Garante que a fonte está carregada antes de renderizar o texto
+ const fontLoad = document.fonts
+ ? document.fonts.load(`normal normal 60px 'SFSlapstick'`).catch(() => {})
+ : Promise.resolve();
+
+ fontLoad.then(() => {
+ const txt = scene.add
+ .text(x, y, texto, ESTILO_TEXTO)
+ .setOrigin(0.5, 0)
+ .setDepth(100)
+ .setAlpha(1);
+
+ scene._textoFeedback = txt;
+
+ scene.time.delayedCall(700, () => {
+ scene.tweens.add({
+ targets: txt, alpha: 0,
+ duration: 300, ease: "Linear",
+ onComplete: () => {
+ txt.destroy();
+ if (scene._textoFeedback === txt) scene._textoFeedback = null;
+ resolve();
+ },
+ });
+ });
+ }); // fontLoad.then
+ });
+}
diff --git a/app/src/atividades/programacao/puzzle/ui/constants.js b/app/src/atividades/programacao/puzzle/ui/constants.js
new file mode 100644
index 0000000..957cc70
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/ui/constants.js
@@ -0,0 +1,105 @@
+/**
+ * @fileoverview Utility module for constants.js
+ * @module games.quebracabecas.ui.constants
+ */
+
+// import assets so that Vite will copy and return correct URLs
+import bg1 from "../assets/backgrounds/background_1.png";
+import bg2 from "../assets/backgrounds/background_2.png";
+import bg3 from "../assets/backgrounds/background_3.png";
+import bg4 from "../assets/backgrounds/background_4.png";
+
+import animalAbelha from "../assets/animais/abelha.png";
+import animalAranha from "../assets/animais/aranha.png";
+import animalCachorro from "../assets/animais/cachorro.png";
+import animalCaracol from "../assets/animais/caracol.png";
+import animalCobra from "../assets/animais/cobra.png";
+import animalGato from "../assets/animais/gato.png";
+import animalLeao from "../assets/animais/leao.png";
+import animalPato from "../assets/animais/pato.png";
+import animalPeixe from "../assets/animais/peixe.png";
+import animalSapo from "../assets/animais/sapo.png";
+
+import soundBg from "../assets/sound/bg_sound.mp3";
+import soundAbelha from "../assets/sound/abelha.mp3";
+import soundCachorro from "../assets/sound/cachorro.mp3";
+import soundGato from "../assets/sound/gato.mp3";
+import soundPato from "../assets/sound/pato.mp3";
+import soundLeao from "../assets/sound/leao.mp3";
+import soundSapo from "../assets/sound/sapo.mp3";
+
+export const ConstantesJogo = {
+ LARGURA_TELA: 800,
+ ALTURA_TELA: 600,
+ COR_FUNDO: "#242527",
+};
+
+// Mapeamento direto da árvore de arquivos para o preload
+export const ConstantesAssets = {
+ CHAVES: {
+ BACKGROUND_1: "bg_1",
+ BACKGROUND_2: "bg_2",
+ BACKGROUND_3: "bg_3",
+ BACKGROUND_4: "bg_4",
+ ABELHA: "animal_abelha",
+ ARANHA: "animal_aranha",
+ CACHORRO: "animal_cachorro",
+ CARACOL: "animal_caracol",
+ COBRA: "animal_cobra",
+ GATO: "animal_gato",
+ LEAO: "animal_leao",
+ PATO: "animal_pato",
+ PEIXE: "animal_peixe",
+ SAPO: "animal_sapo",
+ },
+ PATHS: {
+ BACKGROUND_1: bg1,
+ BACKGROUND_2: bg2,
+ BACKGROUND_3: bg3,
+ BACKGROUND_4: bg4,
+ ABELHA: animalAbelha,
+ ARANHA: animalAranha,
+ CACHORRO: animalCachorro,
+ CARACOL: animalCaracol,
+ COBRA: animalCobra,
+ GATO: animalGato,
+ LEAO: animalLeao,
+ PATO: animalPato,
+ PEIXE: animalPeixe,
+ SAPO: animalSapo,
+ }
+};
+
+// Sons do jogo
+export const ConstantesSons = {
+ // Chaves Phaser (usadas em load.audio e play)
+ CHAVES: {
+ BG: "puzzle_bg",
+ ABELHA: "puzzle_abelha",
+ CACHORRO: "puzzle_cachorro",
+ GATO: "puzzle_gato",
+ PATO: "puzzle_pato",
+ LEAO: "puzzle_leao",
+ SAPO: "puzzle_sapo",
+ },
+ // URLs processadas pelo Vite
+ PATHS: {
+ BG: soundBg,
+ ABELHA: soundAbelha,
+ CACHORRO: soundCachorro,
+ GATO: soundGato,
+ PATO: soundPato,
+ LEAO: soundLeao,
+ SAPO: soundSapo,
+ },
+ // Mapeamento valor do bloco → chave Phaser
+ // As chaves devem corresponder aos valores do dropdown do bloco animal_som
+ BLOCO_PARA_CHAVE: {
+ Zumbido: "puzzle_abelha",
+ Miau: "puzzle_gato",
+ Auau: "puzzle_cachorro",
+ Quack: "puzzle_pato",
+ Rugido: "puzzle_leao",
+ Coaxado: "puzzle_sapo",
+ },
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/ui/layout.js b/app/src/atividades/programacao/puzzle/ui/layout.js
new file mode 100644
index 0000000..eddc2d6
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/ui/layout.js
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Utility module for layout.js
+ * @module games.puzzle.ui.layout
+ */
+
+import { ConstantesJogo, ConstantesAssets } from "./constants.js";
+
+/**
+ * Mapeamento das strings usadas no config.js para as chaves de assets.
+ */
+const MAPA_BACKGROUNDS = {
+ floresta: ConstantesAssets.CHAVES.BACKGROUND_1,
+ fundo_rio: ConstantesAssets.CHAVES.BACKGROUND_2,
+ flores: ConstantesAssets.CHAVES.BACKGROUND_3,
+ lago: ConstantesAssets.CHAVES.BACKGROUND_4,
+};
+
+/**
+ * Inicializa o layout base do jogo (Background)
+ * @param {Phaser.Scene} scene - A cena do Phaser
+ * @returns {Object} Objeto contendo referências visuais criadas no layout
+ */
+export function inicializarLayout(scene) {
+ // Pega a configuração da fase atual que foi injetada no preBoot da cena
+ const configFase = scene.registry.get("configFase") || {};
+
+ // Lê a propriedade "background" do config da fase, ou usa o default
+ const nomeBackground = configFase.background || "default";
+ const chaveAsset = MAPA_BACKGROUNDS[nomeBackground] || MAPA_BACKGROUNDS.default;
+
+ // Centro da tela (baseado nas constantes)
+ const centroX = ConstantesJogo.LARGURA_TELA / 2;
+ const centroY = ConstantesJogo.ALTURA_TELA / 2;
+
+ // Adiciona a imagem de fundo centrada
+ const bgImage = scene.add.image(centroX, centroY, chaveAsset);
+
+ // Ajusta a escala da imagem para cobrir a tela inteira (caso a imagem seja menor ou maior que 800x600)
+ // Usa o math.max para garantir que cubra tanto largura quanto altura (estilo "cover" do CSS)
+ const scaleX = ConstantesJogo.LARGURA_TELA / bgImage.width;
+ const scaleY = ConstantesJogo.ALTURA_TELA / bgImage.height;
+ const scale = Math.max(scaleX, scaleY);
+ bgImage.setScale(scale);
+
+ // Retorna os elementos criados, para que a Cena principal possa manipulá-los se necessário
+ return {
+ background: bgImage,
+ centroX: centroX,
+ centroY: centroY
+ };
+}
\ No newline at end of file
diff --git a/app/src/atividades/programacao/puzzle/validation/validators.js b/app/src/atividades/programacao/puzzle/validation/validators.js
new file mode 100644
index 0000000..f377874
--- /dev/null
+++ b/app/src/atividades/programacao/puzzle/validation/validators.js
@@ -0,0 +1,93 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.puzzle.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador do jogo Quebra-Cabeças.
+ * Implementa validações por fase para verificar entrada/saída esperadas.
+ * @class PuzzleValidator
+ * @extends BaseGameValidator
+ */
+export class PuzzleValidator extends BaseGameValidator {
+ /**
+ * Dispara a validação específica da fase configurada.
+ * Usa `expectedOutput` declarado no config da fase para comparar
+ * com o objeto montado pela cena. Retorna erro com mensagens
+ * configuráveis em `gameConfig.mensagens`.
+ * @param {Array} history - Histórico de comandos
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena (opcional)
+ * @returns {{success:boolean, reason?:string}}
+ */
+ validatePhase(history, config, gameConfig, sceneRef) {
+ // se não houver referência à cena não dá para validar propriedades
+ if (!sceneRef || typeof sceneRef.animalMontado !== "object") {
+ return this.failure(
+ gameConfig?.mensagens?.erroGeral ||
+ "Não foi possível validar a solução."
+ );
+ }
+
+ // tenta interpretar o expectedOutput como JSON
+ let expected = {};
+ try {
+ if (config.expectedOutput) {
+ expected = JSON.parse(config.expectedOutput);
+ }
+ } catch (e) {
+ console.warn("expectedOutput inválido", e);
+ }
+
+ // compara cada par esperado com o animal montado
+ const nomesPropriedades = {
+ tipo: "Tipo", patas: "Patas", cobertura: "Cobertura",
+ locomocao: "Locomoção", som: "Som",
+ };
+
+ // 1ª passagem: verifica se alguma propriedade obrigatória está ausente
+ const ausentes = Object.keys(expected).filter((key) => {
+ const montado = sceneRef.animalMontado[key];
+ return montado === undefined || montado === null;
+ });
+ if (ausentes.length > 0) {
+ const nomes = ausentes.map((k) => nomesPropriedades[k] || k).join(", ");
+ return this.failure(
+ gameConfig?.mensagens?.entradaIncorreta ||
+ `Ainda faltam peças: ${nomes}. Certifique-se de adicionar todos os blocos necessários.`
+ );
+ }
+
+ // 2ª passagem: verifica se algum valor está errado
+ for (const [key, value] of Object.entries(expected)) {
+ const montado = sceneRef.animalMontado[key];
+ // converter ambos para string para não falhar por tipo (ex: 0 vs "0")
+ if (String(montado) !== String(value)) {
+ const nomeProp = nomesPropriedades[key] || key;
+ return this.failure(
+ `A peça "${nomeProp}" está errada! Você colocou "${montado}" — verifique e tente novamente.`
+ );
+ }
+ }
+
+ // passou em todas as checagens
+ return this.success();
+ }
+}
+
+/**
+ * Função exportada para validação de soluções do jogo Quebra-Cabeças.
+ * @param {Array} history - Histórico de ações do usuário
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena (opcional)
+ * @returns {{success:boolean, reason?:string}}
+ */
+export function validationSolution(history, config, gameConfig, sceneRef) {
+ const validator = new PuzzleValidator();
+ return validator.validatePhase(history, config, gameConfig, sceneRef);
+}
diff --git a/app/src/atividades/programacao/semaforo/SemaforoGame.jsx b/app/src/atividades/programacao/semaforo/SemaforoGame.jsx
new file mode 100644
index 0000000..f36b8fa
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/SemaforoGame.jsx
@@ -0,0 +1,75 @@
+/*
+ MIGRATION: Wave 2 — Portuguese -> English identifier migration
+ TODO: Replace Portuguese aliases (ex.: `faseAtual` -> `currentPhase`) in this file.
+ See app/migrations/wave-2-candidates.txt for context.
+*/
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import { generateDynamicToolbox, registerBlocks } from "./blocks/blocks";
+import {
+ GameStateProvider,
+ useGameState,
+} from "../../../contexts/GameStateContext";
+import { useSemaforoTour } from "./hooks/useSemaforoTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+function SemaforoContent() {
+ const { startTour } = useSemaforoTour();
+ const { setFailureMessage, isDebugMode } = useGameState();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ const toolboxGenerator = useMemo(() => {
+ return (allowedBlocks) => generateDynamicToolbox(allowedBlocks);
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Componente que monta o conteúdo do jogo Semáforo.
+ * Registra blocos, configura a toolbox dinâmica e provê o editor do jogo.
+ * Não recebe props e utiliza contextos internos (`GameStateContext`).
+ * @returns {JSX.Element} Elemento que representa a área do jogo com editor Blockly.
+ */
+
+export default function SemaforoGame() {
+ return (
+
+
+
+ );
+}
+
+/**
+ * Componente wrapper que injeta `GameStateProvider` e expõe o jogo Semáforo.
+ * @returns {JSX.Element} Componente raiz do jogo carregável pela rota.
+ */
+
+SemaforoContent.propTypes = {};
+SemaforoGame.propTypes = {};
diff --git a/app/src/atividades/programacao/semaforo/__tests__/integration.test.js b/app/src/atividades/programacao/semaforo/__tests__/integration.test.js
new file mode 100644
index 0000000..7adf189
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/__tests__/integration.test.js
@@ -0,0 +1,332 @@
+/**
+ * @fileoverview Utility module for integration.test.js
+ *
+ * @module games.semaforo.__tests__.integration.test
+ */
+
+import { describe, test, expect, beforeEach } from "vitest";
+import { validateSolution } from "../validation/validators";
+
+/**
+ * Testes de integração para o jogo Semáforo
+ *
+ * Estes testes verificam:
+ * - Validação de cada fase
+ * - Casos de sucesso e falha
+ * - Histórico de comandos
+ * - Mensagens de erro apropriadas
+ */
+
+describe("SemaforoGame - Validação de Fases", () => {
+ let gameConfig;
+
+ beforeEach(() => {
+ gameConfig = {
+ mensagens: {
+ semComandos: "Você não executou nenhum comando!",
+ sequenciaIncorreta: "A sequência de cores está incorreta.",
+ faltaAguardar: "Você esqueceu de aguardar entre as mudanças.",
+ pedestreErrado: "O semáforo de pedestres deve estar sincronizado.",
+ semPiscar: "Você esqueceu de fazer a luz piscar.",
+ semSom: "Você não configurou os sons corretamente.",
+ erroGeral: "Algo deu errado. Verifique seu código.",
+ },
+ };
+ });
+
+ // ========================================================================
+ // FASE 1: Sequência básica de cores
+ // ========================================================================
+ describe("Fase 1 - Sequência de Cores", () => {
+ const configFase = {
+ id: 1,
+ expectedSequence: ["vermelho", "amarelo", "verde"],
+ };
+
+ test("deve aceitar sequência correta: vermelho → amarelo → verde", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+
+ test("deve rejeitar sequência vazia", () => {
+ const historico = [];
+ const resultado = validateSolution(historico, configFase, gameConfig);
+
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.semComandos);
+ });
+
+ test("deve rejeitar sequência incompleta", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "amarelo" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.sequenciaIncorreta);
+ });
+
+ test("deve rejeitar sequência em ordem errada", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "semaforo", cor: "vermelho" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.sequenciaIncorreta);
+ });
+
+ test("deve rejeitar cor duplicada", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ });
+ });
+
+ // ========================================================================
+ // FASE 2: Sequência com aguardar
+ // ========================================================================
+ describe("Fase 2 - Sequência com Aguardar", () => {
+ const configFase = {
+ id: 2,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "aguardar" },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar" },
+ { tipo: "semaforo", cor: "verde" },
+ ],
+ };
+
+ test("deve aceitar sequência com aguardar entre mudanças", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "aguardar", seg: 2 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 2 },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+
+ test("deve rejeitar sequência sem aguardar", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.faltaAguardar);
+ });
+
+ test("deve rejeitar ordem errada dos comandos", () => {
+ const historico = [
+ { tipo: "aguardar", seg: 2 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 2 },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ });
+ });
+
+ // ========================================================================
+ // FASE 3: Carros e Pedestres
+ // ========================================================================
+ describe("Fase 3 - Semáforo de Pedestres", () => {
+ const configFase = {
+ id: 3,
+ temPedestre: true,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ],
+ };
+
+ test("deve aceitar sincronização correta carros/pedestres", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+
+ test("deve rejeitar semáforo de pedestres não sincronizado", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.pedestreErrado);
+ });
+
+ test("deve rejeitar falta de comandos de pedestre", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ });
+ });
+
+ // ========================================================================
+ // FASE 4: Piscar Luz de Pedestre
+ // ========================================================================
+ describe("Fase 4 - Piscar Luz", () => {
+ const configFase = {
+ id: 4,
+ temPedestre: true,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ],
+ };
+
+ test("deve aceitar sequência com piscar", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+
+ test("deve rejeitar sequência sem piscar", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.semPiscar);
+ });
+
+ test("deve validar cor e duração do piscar", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+ });
+
+ // ========================================================================
+ // FASE 5: Controle de Sons
+ // ========================================================================
+ describe("Fase 5 - Controle de Sons", () => {
+ const configFase = {
+ id: 5,
+ temPedestre: true,
+ expectedCommands: [
+ { tipo: "tocar", som: "barulho_cidade" },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "tocar", som: "beep_semaforo" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "parar", som: "beep_semaforo" },
+ { tipo: "semaforo", cor: "verde" },
+ ],
+ };
+
+ test("deve aceitar sequência completa com sons", () => {
+ const historico = [
+ { tipo: "tocar", som: "barulho_cidade" },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "tocar", som: "beep_semaforo" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "parar", som: "beep_semaforo" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(true);
+ });
+
+ test("deve rejeitar sequência sem controle de sons", () => {
+ const historico = [
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ { tipo: "piscar", cor: "verde", seg: 5 },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "semaforo", cor: "verde" },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ expect(resultado.reason).toBe(gameConfig.mensagens.semSom);
+ });
+
+ test("deve validar ordem correta dos sons", () => {
+ const historico = [
+ { tipo: "parar", som: "beep_semaforo" }, // Ordem errada
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "tocar", som: "barulho_cidade" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 3 },
+ ];
+
+ const resultado = validateSolution(historico, configFase, gameConfig);
+ expect(resultado.success).toBe(false);
+ });
+ });
+});
diff --git a/app/src/atividades/programacao/semaforo/assets/beep.mp3 b/app/src/atividades/programacao/semaforo/assets/beep.mp3
new file mode 100644
index 0000000..c734804
Binary files /dev/null and b/app/src/atividades/programacao/semaforo/assets/beep.mp3 differ
diff --git a/app/src/atividades/programacao/semaforo/assets/city_sound.mp3 b/app/src/atividades/programacao/semaforo/assets/city_sound.mp3
new file mode 100644
index 0000000..3f53df3
Binary files /dev/null and b/app/src/atividades/programacao/semaforo/assets/city_sound.mp3 differ
diff --git a/app/src/atividades/programacao/semaforo/blocks/blocks.js b/app/src/atividades/programacao/semaforo/blocks/blocks.js
new file mode 100644
index 0000000..54f041f
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/blocks/blocks.js
@@ -0,0 +1,305 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ *
+ * @module games.semaforo.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import { javascriptGenerator } from "blockly/javascript";
+
+const HUE_SEMAFORO = "#2196F3";
+const HUE_PEDRESTRE = "#E072A4";
+const HUE_CONTROLE = "#FF9800";
+const HUE_MIDIA = "#26979f";
+const HUE_TEMPO = "#9C27B0";
+
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+/**
+ * Registra todos os blocos personalizados do Semáforo no Blockly.
+ * Executa tanto a definição dos blocos quanto a definição dos geradores de código.
+ * @returns {void}
+ */
+
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ const filteredToolbox = JSON.parse(JSON.stringify(semaforoToolbox));
+
+ filteredToolbox.contents.forEach((category) => {
+ if (category.contents) {
+ category.contents = category.contents.filter(
+ (block) => block.type && allowedBlocks.includes(block.type),
+ );
+ }
+ });
+
+ filteredToolbox.contents = filteredToolbox.contents.filter(
+ (category) => category.contents && category.contents.length > 0,
+ );
+
+ return filteredToolbox;
+};
+
+/**
+ * Gera uma toolbox dinâmica filtrada pelos blocos permitidos para a fase.
+ * @param {string[]} [allowedBlocks=[]] - Lista de tipos de blocos permitidos.
+ * @returns {Object} Estrutura JSON compatível com a toolbox do Blockly.
+ */
+
+/**
+ * Define a coleção de blocos do semáforo (chamada internamente por `registerBlocks`).
+ * Blocos definidos: `mudar_semaforo`, `mudar_semaforo_pedestre`, `piscar_luz_pedestre`,
+ * `tocar_som`, `parar_som`, `aguardar_segundos`, `repetir`.
+ * @private
+ * @returns {void}
+ */
+const defineBlocks = () => {
+ Blockly.Blocks["mudar_semaforo"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "mudar semáforo para %1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "COR",
+ options: [
+ ["🟢 verde", "verde"],
+ ["🟡 amarelo", "amarelo"],
+ ["🔴 vermelho", "vermelho"],
+ ],
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_SEMAFORO,
+ tooltip: "Muda a cor do semáforo dos carros.",
+ });
+ },
+ };
+
+ Blockly.Blocks["mudar_semaforo_pedestre"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "mudar semáforo de pedestre para %1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "COR",
+ options: [
+ ["🟢 verde", "verde"],
+ ["🔴 vermelho", "vermelho"],
+ ],
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_PEDRESTRE,
+ tooltip: "Muda a cor do semáforo de pedestre.",
+ });
+ },
+ };
+
+ Blockly.Blocks["piscar_luz_pedestre"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "piscar luz do pedestre %1 por %2 segundos",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "COR",
+ options: [
+ ["🟢 verde", "verde"],
+ ["🔴 vermelho", "vermelho"],
+ ],
+ },
+ {
+ type: "field_number",
+ name: "TEMPO",
+ value: 5,
+ min: 0,
+ max: 60,
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_PEDRESTRE,
+ tooltip: "Faz a luz do semáforo de pedestre piscar na cor escolhida.",
+ });
+ },
+ };
+
+ Blockly.Blocks["tocar_som"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "🔈 tocar som %1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "SOM",
+ options: [["🚦 beep semáforo", "beep_semaforo"]],
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_MIDIA,
+ tooltip: "Toca um som do jogo.",
+ });
+ },
+ };
+
+ Blockly.Blocks["parar_som"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "🔇parar som %1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "SOM",
+ options: [["🚦 beep semáforo", "beep_semaforo"]],
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: HUE_MIDIA,
+ tooltip: "Para um som do jogo.",
+ });
+ },
+ };
+
+ Blockly.Blocks["aguardar_segundos"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "aguardar %1 segundos",
+ args0: [
+ {
+ type: "field_number",
+ name: "TEMPO",
+ value: 1,
+ min: 0,
+ max: 60,
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ colour: "#9C27B0",
+ tooltip: "Espera o tempo definido antes de continuar.",
+ });
+ },
+ };
+
+ Blockly.Blocks["repetir"] = {
+ init: function () {
+ this.appendDummyInput().appendField("enquanto estiver em execução");
+ this.appendStatementInput("DO").setCheck(null).appendField("faça");
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour("#FF9800");
+ this.setTooltip("Repete as ações enquanto problema estiver em execução");
+ this.setHelpUrl("");
+ },
+ };
+};
+
+/**
+ * Define os geradores JavaScript associados aos blocos do semáforo.
+ * Configura prefixos de highlight e tem geradores para cada bloco de ação.
+ * @private
+ * @returns {void}
+ */
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+
+ javascriptGenerator.forBlock["mudar_semaforo"] = (block) => {
+ const cor = block.getFieldValue("COR");
+ return `mudarSemaforo('${cor}');\n`;
+ };
+
+ javascriptGenerator.forBlock["mudar_semaforo_pedestre"] = (block) => {
+ const cor = block.getFieldValue("COR");
+ return `mudarSemaforoPedestre('${cor}');\n`;
+ };
+
+ javascriptGenerator.forBlock["aguardar_segundos"] = (block) => {
+ const tempo = Number(block.getFieldValue("TEMPO"));
+ return `aguardarSegundos(${tempo});\n`;
+ };
+
+ javascriptGenerator.forBlock["repetir"] = (block) => {
+ const statements = javascriptGenerator.statementToCode(block, "DO");
+ return `while(true) {\n${statements}};\n`;
+ };
+
+ javascriptGenerator.forBlock["piscar_luz_pedestre"] = (block) => {
+ const cor = block.getFieldValue("COR");
+ const tempo = Number(block.getFieldValue("TEMPO"));
+ return `piscarLuzPedestre('${cor}', ${tempo});\n`;
+ };
+
+ javascriptGenerator.forBlock["tocar_som"] = (block) => {
+ const som = block.getFieldValue("SOM");
+ return `tocarSom('${som}');\n`;
+ };
+
+ javascriptGenerator.forBlock["parar_som"] = (block) => {
+ const som = block.getFieldValue("SOM");
+ return `pararSom('${som}');\n`;
+ };
+};
+
+export const semaforoToolbox = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: HUE_CONTROLE,
+ contents: [{ kind: "block", type: "repetir" }],
+ cssConfig: {
+ container: "repeticao",
+ },
+ },
+ {
+ kind: "category",
+ name: "Tempo",
+ colour: HUE_TEMPO,
+ contents: [{ kind: "block", type: "aguardar_segundos" }],
+ cssConfig: {
+ container: "aguardar",
+ },
+ },
+ {
+ kind: "category",
+ name: "Semáforo Carros",
+ colour: HUE_SEMAFORO,
+ contents: [{ kind: "block", type: "mudar_semaforo" }],
+ },
+ {
+ kind: "category",
+ name: "Semáforo Pedestre",
+ colour: HUE_PEDRESTRE,
+ contents: [
+ { kind: "block", type: "mudar_semaforo_pedestre" },
+ { kind: "block", type: "piscar_luz_pedestre" },
+ ],
+ },
+ {
+ kind: "category",
+ name: "Multimídia",
+ colour: HUE_MIDIA,
+ contents: [
+ { kind: "block", type: "tocar_som" },
+ { kind: "block", type: "parar_som" },
+ ],
+ },
+ ],
+};
+
+/**
+ * Toolbox padrão do semáforo usado como base para `generateDynamicToolbox`.
+ * @type {Object}
+ */
diff --git a/app/src/atividades/programacao/semaforo/config/config.js b/app/src/atividades/programacao/semaforo/config/config.js
new file mode 100644
index 0000000..6e66149
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/config/config.js
@@ -0,0 +1,152 @@
+/**
+ * @fileoverview Utility module for config.js
+ *
+ * @module games.semaforo.config.config
+ */
+
+export const gameConfig = {
+ gameId: "semaforo",
+ gameName: "Semáforo",
+ type: "blocks",
+ icon: "🚦",
+ thumbnail: "/images/atividades/programacao/semaforo-thumbnail.png",
+ descricao: "Controle o semáforo de carros e pedestres programando os blocos.",
+ dificuldade: "Iniciante",
+ categoria: "Lógica",
+ tempoEstimado: "30-45 min",
+ conceitos: ["Sequência", "Eventos", "Condicionais"],
+ route: "/atividades/programacao/semaforo",
+ component: "SemaforoGame",
+ allowMultipleTopBlocks: true,
+ objectives: [
+ "Controlar o semáforo de carros",
+ "Usar eventos para interação",
+ "Aplicar lógica condicional para segurança",
+ ],
+ metadata: {
+ lastUpdated: "2025-08-17",
+ version: "1.0.0",
+ },
+
+ fases: [
+ {
+ id: 1,
+ nome: "Controle básico do semáforo de carros",
+ descricao:
+ "Mude o semáforo dos carros na ordem correta: verde, depois amarelo e por fim vermelho.",
+ allowedBlocks: ["mudar_semaforo"],
+ temPedestre: false,
+ timeout: 15,
+ expectedSequence: ["verde", "amarelo", "vermelho"],
+ },
+ {
+ id: 2,
+ nome: "Controle básico do semáforo de carros",
+ descricao:
+ "Mude o semáforo na ordem verde, amarelo e vermelho, aguardando 5 segundos após cada mudança de cor.",
+ allowedBlocks: ["mudar_semaforo", "aguardar_segundos"],
+ temPedestre: false,
+ timeout: 30,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "aguardar", seg: 5 },
+ ],
+ },
+ {
+ id: 3,
+ nome: "Controle do semáforo de pedestres",
+ descricao:
+ "Sincronize os dois semáforos: quando o de carros estiver verde, o de pedestres deve estar vermelho, e quando o de carros ficar vermelho, libere o pedestre (verde). O intervalo mantém-se o mesmo: 5 segundos para cada mudança.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "mudar_semaforo_pedestre",
+ ],
+ temPedestre: true,
+ timeout: 25,
+ expectedCommandGroups: [
+ [{ tipo: "semaforo", cor: "verde" }, { tipo: "pedestre", cor: "vermelho" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "semaforo", cor: "amarelo" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "pedestre", cor: "verde" }, { tipo: "semaforo", cor: "vermelho" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ ],
+ },
+ {
+ id: 4,
+ nome: "Semáforo de pedestres com luz piscante",
+ descricao:
+ "Repita a fase anterior, mas após aguardar 5 segundos com o pedestre verde, faça a luz piscar em vermelho por 5 segundos para avisar que vai fechar.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "mudar_semaforo_pedestre",
+ "piscar_luz_pedestre",
+ ],
+ temPedestre: true,
+ timeout: 35,
+ expectedCommandGroups: [
+ [{ tipo: "semaforo", cor: "verde" }, { tipo: "pedestre", cor: "vermelho" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "semaforo", cor: "amarelo" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "semaforo", cor: "vermelho" }, { tipo: "pedestre", cor: "verde" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "piscar", cor: "vermelho", seg: 5 }],
+ ],
+ },
+ {
+ id: 5,
+ nome: "Semáforo com sons",
+ descricao:
+ "Complete a fase anterior adicionando o beep do semáforo quando o pedestre ficar verde e parando o beep após o piscar.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "mudar_semaforo_pedestre",
+ "piscar_luz_pedestre",
+ "tocar_som",
+ "parar_som",
+ ],
+ temPedestre: true,
+ timeout: 35,
+ expectedCommandGroups: [
+ [{ tipo: "semaforo", cor: "verde" }, { tipo: "pedestre", cor: "vermelho" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "semaforo", cor: "amarelo" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "semaforo", cor: "vermelho" }, { tipo: "pedestre", cor: "verde" }],
+ [{ tipo: "tocar", som: "beep_semaforo" }],
+ [{ tipo: "aguardar", seg: 5 }],
+ [{ tipo: "piscar", cor: "vermelho", seg: 5 }],
+ [{ tipo: "parar", som: "beep_semaforo" }],
+ ],
+ },
+ ],
+
+ mensagens: {
+ semComandos: "Você não executou nenhum comando! Use os blocos disponíveis.",
+ sequenciaIncorreta:
+ "A sequência de cores está incorreta. Verifique a ordem.",
+ sequenciaIncompleta:
+ "A sequência está incompleta. Certifique-se de mudar todas as cores necessárias.",
+ faltaAguardar: "Você esqueceu de aguardar entre as mudanças de semáforo.",
+ tempoErrado: "O tempo de espera está incorreto. Verifique os segundos.",
+ pedestreErrado:
+ "O semáforo de pedestres deve estar sincronizado com o de carros.",
+ semPiscar:
+ "Você esqueceu de fazer a luz do pedestre piscar antes de fechar.",
+ piscarErrado:
+ "O piscar da luz de pedestre está com cor ou tempo incorreto.",
+ semSom: "Você não configurou os sons corretamente.",
+ somErrado: "O som usado está incorreto ou fora de ordem.",
+ erroGeral: "Algo deu errado. Verifique seu código.",
+ sucessoGenerico: "Parabéns! Você completou o desafio!",
+ timeoutExcedido: "O tempo de execução foi excedido.",
+ },
+};
diff --git a/app/src/atividades/programacao/semaforo/config/debugSolutions.js b/app/src/atividades/programacao/semaforo/config/debugSolutions.js
new file mode 100644
index 0000000..254725d
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/config/debugSolutions.js
@@ -0,0 +1,307 @@
+/**
+ * Soluções de debug para o jogo Semáforo.
+ * Usado apenas em modo de debug para popular o editor com possíveis soluções.
+ * @module games.semaforo.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mudar_semaforo",
+ id: "PvStJ%_iIyICp;DR|~`[",
+ x: 36,
+ y: 37,
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "k[`vlI2F?+zzIO=,VV1Z",
+ fields: { COR: "amarelo" },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "hB#~6GUfG_QN^.)},)l;",
+ fields: { COR: "vermelho" },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mudar_semaforo",
+ id: "VkkX=P1R5$vj*ngNTGyk",
+ x: 38,
+ y: 38,
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "XS]X0,ZYZc(/?R2$l)du",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "ni0p}N-{tZSO}APrxoYs",
+ fields: { COR: "amarelo" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "~^s=63J(]RO[`Y6w`,Z2",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "M3/(Wp;v4gVq^c;yEW@+",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "%Is_uj-GpGX09f|lto[$",
+ fields: { TEMPO: 5 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mudar_semaforo",
+ id: "80sTj-}e3jCWgH9L%6Fg",
+ x: 49,
+ y: 41,
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: "kB(:a*_i3TqE7l#kn(]~",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "wX..qsg[D[L$Y?)-z=-c",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "/zK(fvquN|E=#)!|SO5C",
+ fields: { COR: "amarelo" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "*Y;?0C7jA|hSzmUOm4YT",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "LN4dl(9I@hIctWUnRU*D",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: "/I5_2f/U%3YEW~vW9H6w",
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "H]~FepP%i!X}LeO@`iXJ",
+ fields: { TEMPO: 5 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mudar_semaforo",
+ id: "7jT6Y@e?6(Ru@kM=sCiL",
+ x: 29,
+ y: 34,
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: ":mQWi3G(wb%*s]:11(u2",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "QT@Zs{(tHwDT6%h9TaLP",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "l}63t},j:f#wI}wP/2Ri",
+ fields: { COR: "amarelo" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "U-4C;Szg*fqvD87yVzO]",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "S0sk%slR]+1pn@7ctOw5",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: "=_MV6e=Sz.xS7{hpivI2",
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "d7Kr;+n_H.GvgX5k8F)-",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "piscar_luz_pedestre",
+ id: "]-@)-iz,-`+lbW7H1I7U",
+ fields: { COR: "vermelho", TEMPO: 5 },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "mudar_semaforo",
+ id: "!W8e`3O,m?So_/.0k92V",
+ x: 63,
+ y: 38,
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: "b|Gp9Gv..#uE$vr(|8r6",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "Q+_mb:lj0_JkBEASW_xE",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "x)gGxj5[Jp:~Ik+5U!b#",
+ fields: { COR: "amarelo" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: "nNWeiH{Ax:Cy/2(u^o7h",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "mudar_semaforo",
+ id: "X[O0)^$4z=f40%S(j$=d",
+ fields: { COR: "vermelho" },
+ next: {
+ block: {
+ type: "mudar_semaforo_pedestre",
+ id: "@Vv]vHHr4KxvL|2nT}iT",
+ fields: { COR: "verde" },
+ next: {
+ block: {
+ type: "tocar_som",
+ id: "C1.I+*ys7I[:|Hsk6YS}",
+ fields: { SOM: "beep_semaforo" },
+ next: {
+ block: {
+ type: "aguardar_segundos",
+ id: ":nS6l=AYR[bmS8#:)_lE",
+ fields: { TEMPO: 5 },
+ next: {
+ block: {
+ type: "piscar_luz_pedestre",
+ id: "},g`[f#Xco*pPbfcxn(K",
+ fields: {
+ COR: "vermelho",
+ TEMPO: 5,
+ },
+ next: {
+ block: {
+ type: "parar_som",
+ id: "gK/k6FQG_*Dn.2X):*Dw",
+ fields: {
+ SOM: "beep_semaforo",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+};
diff --git a/app/src/atividades/programacao/semaforo/config/tourSteps.js b/app/src/atividades/programacao/semaforo/config/tourSteps.js
new file mode 100644
index 0000000..315d319
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/config/tourSteps.js
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.semaforo.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const semaforoTourSteps = [
+ createWelcomeStep({
+ gameName: "Jogo do Semáforo",
+ description:
+ "Aprenda a controlar o trânsito! Programe o semáforo para carros e pedestres funcionarem corretamente.",
+ challenge:
+ "Use blocos para criar a sequência correta de luzes e garantir a segurança de todos.",
+ iconSvg: gameIcons.traffic,
+ }),
+
+ createGameAreaStep({
+ title: "Sistema de Semáforos",
+ description:
+ "Aqui você vê o semáforo de carros e o semáforo de pedestres. Controle-os para evitar acidentes.",
+ }),
+
+ createToolboxStep({
+ description:
+ "Use blocos de comandos de semáforo, espera e repetição para criar a lógica de controle do trânsito.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte a lógica do semáforo: quando os carros param, os pedestres podem andar, e vice-versa.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Execute sua programação e veja os semáforos funcionarem. Ouça os sons do trânsito.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Se o semáforo não funcionar corretamente, use o reset para ajustar sua lógica.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "Diferentes fases trazem situações mais complexas: semáforos com tempo, botões de pedestres e muito mais.",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Veja qual desafio você está resolvendo e aprenda novos conceitos de programação.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Se tiver dúvidas sobre como programar o semáforo, clique aqui para rever as instruções.",
+ }),
+];
+
+export const semaforoTourOptions = defaultGameTourOptions;
+
+/**
+ * Passos do tour guiado para o jogo Semáforo.
+ * Estrutura compatível com a API de tours da aplicação.
+ * @type {Array}
+ */
+export const _semaforoTourStepsDoc = semaforoTourSteps; // helper para documentação estática
+
+/**
+ * Opções padrão do tour do semáforo (reexport de defaults).
+ * @type {Object}
+ */
+export const _semaforoTourOptionsDoc = semaforoTourOptions;
diff --git a/app/src/atividades/programacao/semaforo/game.js b/app/src/atividades/programacao/semaforo/game.js
new file mode 100644
index 0000000..e0b6779
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/game.js
@@ -0,0 +1,916 @@
+/**
+ * @fileoverview Utility module for game.js
+ *
+ * @module games.semaforo.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { setupSemaforoAPI } from "./hooks/interpreterSetup.js";
+import { validateSolution } from "./validation/validators.js";
+import { gameConfig } from "./config/config.js";
+
+import car from "./../../../assets/car.png";
+import motoca from "./../../../assets/motoca.png";
+import police from "./../../../assets/police.png";
+import truck from "./../../../assets/truck.png";
+import beep_sound from "./assets/beep.mp3";
+import city_sound from "./assets/city_sound.mp3";
+
+const ConstantesJogo = {
+ LARGURA_TELA: 800,
+ ALTURA_TELA: 600,
+ COR_FUNDO: "#242527",
+ DEBUG_FISICA: false,
+ CAMADAS: {
+ CENARIO: 0,
+ VEICULOS: 1,
+ PEDESTRES: 2,
+ SEMAFORO_FUNDO: 10,
+ SEMAFORO_LUZES: 11,
+ UI: 20,
+ },
+ CHAVES_ASSETS: {
+ imagens: {
+ carro: "carro",
+ motoca: "motoca",
+ policia: "policia",
+ caminhao: "caminhao",
+ pedestre: "pedestre",
+ },
+ audio: { beep: "beep", som_cidade: "som_cidade" },
+ },
+ CORES: {
+ semaforoPrincipal: {
+ verdeAceso: 0x00ff00,
+ verdeApagado: 0x005e00,
+ amareloAceso: 0xffff00,
+ amareloApagado: 0x5e5e00,
+ vermelhoAceso: 0xff0000,
+ vermelhoApagado: 0x5e0000,
+ },
+ semaforoPedestre: {
+ fundoAceso: 0xff0000,
+ fundoLiberado: 0x00ff00,
+ figura: 0x000000,
+ brilho: 0xffffff,
+ },
+ cenario: {
+ calcada: 0x808080,
+ faixaDePedestres: 0xffffff,
+ faixaDePista: 0xffffff,
+ posteSemaforo: 0x333333,
+ caixaSemaforo: 0x1a1a1a,
+ },
+ },
+ VELOCIDADE_VEICULO: -200,
+ INTERVALO_SPAWN_VEICULO: 1200,
+ INTERVALO_SPAWN_PEDESTRE: 500,
+ VELOCIDADE_PEDESTRE: -450,
+};
+
+/**
+ * Classe principal da cena do jogo Semáforo
+ * @extends BaseGameScene
+ */
+class SemaforoScene extends BaseGameScene {
+ constructor() {
+ super("SemaforoScene");
+
+ this.alturaBloco = 100;
+ this.larguraBloco = 35;
+ this.velocidadeQuedaBloco = ConstantesJogo.VELOCIDADE_VEICULO;
+ this.configSemaforo = {
+ coresSemaforoPrincipal: ConstantesJogo.CORES.semaforoPrincipal,
+ coresSemaforoPedestre: ConstantesJogo.CORES.semaforoPedestre,
+ };
+ this.estadoSemaforo = { carros: "verde", pedestre: "vermelho" };
+
+ this.veiculos = null;
+ this.pedestres = null;
+ this.timerPedestre = null;
+
+ this.semaforoPrincipalContainer = null;
+ this.luzVerde = null;
+ this.luzAmarela = null;
+ this.luzVermelha = null;
+ this.semaforoPedestreEsquerda = null;
+ this.semaforoPedestreDireita = null;
+ this.botaoAtravessar = null;
+
+ this.somCidade = null;
+ this.somBeep = null;
+
+ this.highlightPause = false;
+ this.pausaDestaque = false;
+ this.resultadoJogada = "em_andamento";
+
+ this.larguraMargem = 0;
+ this.faixaSuperiorY = 0;
+ this.faixaInferiorY = 0;
+ this.posicoesPista = [];
+ this.texturasVeiculos = [];
+ }
+
+ /**
+ * Inicializa a cena do Semáforo com dados vindos do registry/boot.
+ * @param {Object} data - Dados opcionais passados por `scene.start` ou pelo `registry`
+ * @returns {void}
+ */
+ init(data) {
+ super.init(data);
+ }
+
+ /**
+ * Preload dos assets específicos do jogo (imagens e áudios locais).
+ * Também chama `preloadGlobalAssets` da `BaseGameScene` para sons compartilhados.
+ * @returns {void}
+ */
+ preload() {
+ this.preloadGlobalAssets();
+
+ const { imagens, audio } = ConstantesJogo.CHAVES_ASSETS;
+ this.load.image(imagens.carro, car);
+ this.load.image(imagens.motoca, motoca);
+ this.load.image(imagens.policia, police);
+ this.load.image(imagens.caminhao, truck);
+ this.load.audio(audio.beep, beep_sound);
+ this.load.audio(audio.som_cidade, city_sound);
+
+ const graficosPedestre = this.make.graphics({ add: false });
+ graficosPedestre.fillStyle(0x0000ff, 1);
+ graficosPedestre.fillCircle(15, 15, 15);
+ graficosPedestre.generateTexture(imagens.pedestre, 30, 30);
+ graficosPedestre.destroy();
+ }
+
+ /**
+ * Cria a cena: configura elementos gráficos, grupos de física, timers
+ * e registra o controller padrão que conecta o interpretador.
+ * @returns {void}
+ */
+ create() {
+ const larguraTela = this.sys.game.config.width;
+ const alturaTela = this.sys.game.config.height;
+
+ this.texturasVeiculos = ["carro", "motoca", "policia", "caminhao"];
+ this.somCidade = this.sound.add(
+ ConstantesJogo.CHAVES_ASSETS.audio.som_cidade,
+ { loop: true, volume: 0.1 },
+ );
+ this.somBeep = this.sound.add(ConstantesJogo.CHAVES_ASSETS.audio.beep, {
+ loop: true,
+ volume: 0.3,
+ });
+
+ this.desenharCenario(larguraTela, alturaTela);
+ this.criarSemaforoPrincipal();
+
+ if (this.configFase.temPedestre) {
+ this.criarSemaforosPedestres();
+ }
+
+ if (this.configFase.temBotaoPedreste) {
+ this.criarBotaoAtravessar();
+ }
+
+ this.veiculos = this.physics.add.group();
+ this.pedestres = this.physics.add.group();
+ this.timerPedestre = null;
+
+ this.time.addEvent({
+ delay: ConstantesJogo.INTERVALO_SPAWN_VEICULO,
+ callback: this.spawnVeiculo,
+ callbackScope: this,
+ loop: true,
+ });
+
+ this.atualizarEstadoSemaforos();
+
+ this.setupStandardController(
+ () => setupSemaforoAPI(this),
+ (historico) =>
+ validateSolution(historico, this.configFase, gameConfig, this),
+ );
+ }
+
+ /**
+ * Loop principal de atualização da cena (chamado a cada frame).
+ * Limpa entidades que saem da tela e mantém o estado visual consistente.
+ * @returns {void}
+ */
+ update() {
+ this.veiculos.getChildren().forEach((veiculo) => {
+ if (veiculo.y < -100) {
+ veiculo.destroy();
+ }
+ });
+
+ this.pedestres.getChildren().forEach((pedestre) => {
+ if (pedestre.x < -20) {
+ pedestre.destroy();
+ }
+ });
+ }
+
+ /**
+ * Hook executado imediatamente antes de iniciar a execução do código do aluno.
+ * Deve garantir que sons estejam prontos, limpar highlights e preparar o estado.
+ * @returns {void}
+ */
+ onBeforeRun() {
+ this.somCidade.stop();
+ this.somBeep.stop();
+ this.somCidade.play({ loop: true });
+
+ this.highlightPause = false;
+ this.resultadoJogada = "em_andamento";
+
+ if (this.veiculos) {
+ this.veiculos.getChildren().forEach((veiculo) => {
+ veiculo.destroy();
+ });
+ }
+ }
+
+ /**
+ * Hook chamado quando a cena é resetada manualmente (botão Reset).
+ * Restaura estado visual sem alterar a configuração da fase.
+ * @returns {void}
+ */
+ onReset() {
+ this.resultadoJogada = "em_andamento";
+ this.highlightPause = false;
+
+ if (this.veiculos) {
+ this.veiculos.getChildren().forEach((veiculo) => {
+ veiculo.destroy();
+ });
+ }
+
+ this.somCidade.stop();
+ this.somBeep.stop();
+ }
+
+ /**
+ * Handler chamado quando a validação indica sucesso.
+ * Pode reproduzir som/animar vitória; aqui delega a lógica padrão da cena.
+ * @returns {void}
+ */
+ onSuccess() {}
+
+ /**
+ * Handler chamado quando a validação indica falha.
+ * Deve apresentar feedback ao jogador e limpar estados se necessário.
+ * @returns {void}
+ */
+ onFailure() {}
+
+ desenharCenario(larguraTela, alturaTela) {
+ const percentualMargem = 0.1;
+ this.larguraMargem = larguraTela * percentualMargem;
+ const areaJogoX = this.larguraMargem;
+ const larguraAreaJogo = larguraTela - this.larguraMargem * 2;
+ const graficos = this.add.graphics({
+ depth: ConstantesJogo.CAMADAS.CENARIO,
+ });
+ graficos
+ .fillStyle(ConstantesJogo.CORES.cenario.calcada, 1)
+ .fillRect(0, 0, this.larguraMargem, alturaTela)
+ .fillRect(
+ larguraTela - this.larguraMargem,
+ 0,
+ this.larguraMargem,
+ alturaTela,
+ );
+ graficos
+ .fillStyle(0x000000, 0.4)
+ .fillRect(this.larguraMargem - 5, 0, 5, alturaTela)
+ .fillRect(larguraTela - this.larguraMargem, 0, 5, alturaTela);
+ const centroY = alturaTela / 2;
+ const alturaFaixa = alturaTela * 0.05 * 1.25;
+ this.faixaSuperiorY = centroY - alturaFaixa;
+ this.faixaInferiorY = centroY + alturaFaixa;
+ graficos.fillStyle(ConstantesJogo.CORES.cenario.faixaDePedestres, 1);
+ for (let i = 0; i <= Math.floor(larguraAreaJogo / 60); i++) {
+ graficos.fillRect(
+ areaJogoX + i * 60,
+ this.faixaSuperiorY,
+ 30,
+ this.faixaInferiorY - this.faixaSuperiorY,
+ );
+ }
+ graficos.fillStyle(ConstantesJogo.CORES.cenario.faixaDePista, 0.5);
+ const larguraPista = larguraAreaJogo / 4;
+ this.posicoesPista = [
+ areaJogoX + larguraPista * 0.5,
+ areaJogoX + larguraPista * 1.5,
+ areaJogoX + larguraPista * 2.5,
+ areaJogoX + larguraPista * 3.5,
+ ];
+ for (let i = 1; i < 4; i++) {
+ for (let y = 0; y < alturaTela; y += 65) {
+ graficos.fillRect(areaJogoX + i * larguraPista - 4, y, 8, 40);
+ }
+ }
+ }
+
+ /**
+ * Desenha o cenário base (calçadas, faixas e pistas) e inicializa métricas de layout.
+ * @param {number} larguraTela
+ * @param {number} alturaTela
+ * @returns {void}
+ */
+ desenharCenario(larguraTela, alturaTela) {
+ const percentualMargem = 0.1;
+ this.larguraMargem = larguraTela * percentualMargem;
+ const areaJogoX = this.larguraMargem;
+ const larguraAreaJogo = larguraTela - this.larguraMargem * 2;
+ const graficos = this.add.graphics({
+ depth: ConstantesJogo.CAMADAS.CENARIO,
+ });
+ graficos
+ .fillStyle(ConstantesJogo.CORES.cenario.calcada, 1)
+ .fillRect(0, 0, this.larguraMargem, alturaTela)
+ .fillRect(
+ larguraTela - this.larguraMargem,
+ 0,
+ this.larguraMargem,
+ alturaTela,
+ );
+ graficos
+ .fillStyle(0x000000, 0.4)
+ .fillRect(this.larguraMargem - 5, 0, 5, alturaTela)
+ .fillRect(larguraTela - this.larguraMargem, 0, 5, alturaTela);
+ const centroY = alturaTela / 2;
+ const alturaFaixa = alturaTela * 0.05 * 1.25;
+ this.faixaSuperiorY = centroY - alturaFaixa;
+ this.faixaInferiorY = centroY + alturaFaixa;
+ graficos.fillStyle(ConstantesJogo.CORES.cenario.faixaDePedestres, 1);
+ for (let i = 0; i <= Math.floor(larguraAreaJogo / 60); i++) {
+ graficos.fillRect(
+ areaJogoX + i * 60,
+ this.faixaSuperiorY,
+ 30,
+ this.faixaInferiorY - this.faixaSuperiorY,
+ );
+ }
+ graficos.fillStyle(ConstantesJogo.CORES.cenario.faixaDePista, 0.5);
+ const larguraPista = larguraAreaJogo / 4;
+ this.posicoesPista = [
+ areaJogoX + larguraPista * 0.5,
+ areaJogoX + larguraPista * 1.5,
+ areaJogoX + larguraPista * 2.5,
+ areaJogoX + larguraPista * 3.5,
+ ];
+ for (let i = 1; i < 4; i++) {
+ for (let y = 0; y < alturaTela; y += 65) {
+ graficos.fillRect(areaJogoX + i * larguraPista - 4, y, 8, 40);
+ }
+ }
+ }
+
+ criarSemaforoPrincipal() {
+ const { width } = this.sys.game.config;
+ const { coresSemaforoPrincipal: cores } = this.configSemaforo;
+
+ const graficos = this.add.graphics();
+ graficos
+ .fillStyle(ConstantesJogo.CORES.cenario.posteSemaforo, 1)
+ .fillRect(-width / 2, -7, width, 10);
+ graficos
+ .fillStyle(ConstantesJogo.CORES.cenario.caixaSemaforo, 1)
+ .fillRoundedRect(-70, -22, 140, 45, 8);
+
+ this.luzVerde = this.add.circle(-40, 0, 14, cores.verdeApagado);
+ this.luzAmarela = this.add.circle(0, 0, 14, cores.amareloApagado);
+ this.luzVermelha = this.add.circle(40, 0, 14, cores.vermelhoApagado);
+
+ this.semaforoPrincipalContainer = this.add.container(width / 2, 82, [
+ graficos,
+ this.luzVerde,
+ this.luzAmarela,
+ this.luzVermelha,
+ ]);
+
+ this.semaforoPrincipalContainer.setDepth(
+ ConstantesJogo.CAMADAS.SEMAFORO_LUZES,
+ );
+ this.semaforoPrincipalContainer.setSize(140, 45);
+ }
+
+ /**
+ * Cria os elementos visuais do semáforo principal (carros) e inicializa
+ * as referências às luzes (verde, amarelo, vermelho).
+ * @returns {void}
+ */
+
+ criarSemaforosPedestres() {
+ const posicoes = [
+ { x: this.larguraMargem / 2, y: this.faixaInferiorY - 100 },
+ {
+ x: this.sys.game.config.width - this.larguraMargem / 2,
+ y: this.faixaInferiorY - 100,
+ },
+ ];
+ const graficos = this.add.graphics({
+ depth: ConstantesJogo.CAMADAS.SEMAFORO_FUNDO,
+ });
+ posicoes.forEach((pos) => {
+ graficos.fillStyle(0x4a4a4a, 1).fillRect(pos.x - 8, pos.y - 50, 16, 50);
+ graficos
+ .fillStyle(ConstantesJogo.CORES.cenario.caixaSemaforo, 1)
+ .fillRect(pos.x - 20, pos.y - 80, 40, 35);
+ });
+ this.semaforoPedestreEsquerda = this.criarConteudoSemaforoPedestre(
+ posicoes[0].x,
+ posicoes[0].y - 62,
+ );
+ this.semaforoPedestreDireita = this.criarConteudoSemaforoPedestre(
+ posicoes[1].x,
+ posicoes[1].y - 62,
+ );
+ }
+
+ /**
+ * Cria os semáforos de pedestre nas laterais da faixa e retorna os containers
+ * com os componentes internos (fundo, figura, brilho).
+ * @returns {void}
+ */
+
+ criarConteudoSemaforoPedestre(x, y) {
+ const { coresSemaforoPedestre: cores } = this.configSemaforo;
+ const fundo = this.add.circle(0, 0, 12);
+ const figura = this.add.graphics();
+ const brilho = this.add
+ .circle(0, 0, 18, cores.brilho, 0.5)
+ .setVisible(false);
+
+ const container = this.add.container(x, y, [brilho, fundo, figura]);
+ container.setDepth(ConstantesJogo.CAMADAS.SEMAFORO_LUZES);
+ container.setData("componentes", { fundo, figura, brilho });
+ return container;
+ }
+
+ /**
+ * Desenha a figura do pedestre no gráfico fornecido, alternando entre
+ * postura andando ou parado.
+ * @param {PIXI.Graphics} graficos - Instância de gráficos do Phaser
+ * @param {number} cor - Cor a ser usada para desenho (hex)
+ * @param {boolean} estaAndando - Se true, desenha perna em movimento
+ * @returns {void}
+ */
+
+ desenharFiguraPedestre(graficos, cor, estaAndando) {
+ graficos.clear().fillStyle(cor, 1).lineStyle(4, cor, 1);
+ graficos.fillCircle(0, -8, 4);
+ graficos.beginPath().moveTo(0, -4).lineTo(0, 6).moveTo(-5, 0).lineTo(5, 0);
+ if (estaAndando) {
+ graficos.moveTo(0, 6).lineTo(-6, 12).moveTo(0, 6).lineTo(6, 12);
+ } else {
+ graficos.moveTo(0, 6).lineTo(-4, 12).moveTo(0, 6).lineTo(4, 12);
+ }
+ graficos.strokePath();
+ }
+
+ /**
+ * Cria um botão de UI para simular pedido de travessia por pedestre.
+ * O handler atual apenas registra o clique no console.
+ * @returns {void}
+ */
+
+ criarBotaoAtravessar() {
+ const { width, height } = this.sys.game.config;
+ const estiloBotao = {
+ fontSize: "24px",
+ fill: "#000",
+ fontStyle: "bold",
+ backgroundColor: "#e0e0e0",
+ padding: { x: 15, y: 8 },
+ };
+ this.botaoAtravessar = this.add
+ .text(width / 2, height - 45, "Atravessar", estiloBotao)
+ .setOrigin(0.5)
+ .setInteractive({ useHandCursor: true })
+ .setDepth(ConstantesJogo.CAMADAS.UI);
+ this.botaoAtravessar.on("pointerdown", () =>
+ console.log("Botão de travessia clicado!"),
+ );
+ }
+
+ /**
+ * Atualiza visualmente o estado dos semáforos (carros e pedestres)
+ * e gerencia timers/respawn de pedestres conforme o estado.
+ * @returns {void}
+ */
+
+ atualizarEstadoSemaforos() {
+ const {
+ coresSemaforoPrincipal: coresPrincipais,
+ coresSemaforoPedestre: coresPedestre,
+ } = this.configSemaforo;
+ this.luzVerde.setFillStyle(
+ this.estadoSemaforo.carros === "verde"
+ ? coresPrincipais.verdeAceso
+ : coresPrincipais.verdeApagado,
+ );
+ this.luzAmarela.setFillStyle(
+ this.estadoSemaforo.carros === "amarelo"
+ ? coresPrincipais.amareloAceso
+ : coresPrincipais.amareloApagado,
+ );
+ this.luzVermelha.setFillStyle(
+ this.estadoSemaforo.carros === "vermelho"
+ ? coresPrincipais.vermelhoAceso
+ : coresPrincipais.vermelhoApagado,
+ );
+ this.gerenciarTransicaoVeiculos(this.estadoSemaforo.carros === "vermelho");
+
+ if (!this.configFase.temPedestre) return;
+
+ [this.semaforoPedestreEsquerda, this.semaforoPedestreDireita].forEach(
+ (semaforo) => {
+ if (!semaforo) return;
+ const componentes = semaforo.getData("componentes");
+ if (!componentes) return;
+
+ componentes.brilho.setVisible(true);
+ if (this.estadoSemaforo.pedestre === "verde") {
+ componentes.fundo.setFillStyle(coresPedestre.fundoLiberado);
+ this.desenharFiguraPedestre(
+ componentes.figura,
+ coresPedestre.figura,
+ true,
+ );
+ componentes.brilho.setFillStyle(coresPedestre.fundoLiberado);
+ } else {
+ componentes.fundo.setFillStyle(coresPedestre.fundoAceso);
+ this.desenharFiguraPedestre(
+ componentes.figura,
+ coresPedestre.figura,
+ false,
+ );
+ componentes.brilho.setFillStyle(coresPedestre.fundoAceso);
+ }
+
+ if (this.estadoSemaforo.pedestre === "verde") {
+ if (!this.timerPedestre) {
+ this.timerPedestre = this.time.addEvent({
+ delay: ConstantesJogo.INTERVALO_SPAWN_PEDESTRE,
+ callback: () => {
+ this.spawnPedestre();
+ },
+ loop: true,
+ });
+ }
+ } else if (this.estadoSemaforo.pedestre === "vermelho") {
+ if (this.timerPedestre) {
+ this.timerPedestre.remove(false);
+ this.timerPedestre = null;
+ }
+ }
+ },
+ );
+ }
+
+ /**
+ * Gerencia a transição de veículos: pausa ou libera o movimento conforme
+ * o sinal de semáforo.
+ * @param {boolean} devePausar - Indica se os veículos devem pausar.
+ * @returns {void}
+ */
+
+ gerenciarTransicaoVeiculos(devePausar) {
+ if (!this.sys || !this.sys.game || !this.sys.game.config) {
+ return;
+ }
+
+ const margemParada = this.sys.game.config.height * 0.05;
+ const posicaoParadaY =
+ this.faixaInferiorY + this.alturaBloco / 2 + margemParada;
+ this.veiculos.getChildren().forEach((veiculo) => {
+ if (devePausar) {
+ if (
+ veiculo.body.velocity.y < 0 &&
+ veiculo.y - this.alturaBloco / 2 > this.faixaInferiorY
+ ) {
+ veiculo.body.setVelocityY(0);
+ this.tweens.add({
+ targets: veiculo,
+ y: posicaoParadaY,
+ duration: 500,
+ ease: "Power2",
+ });
+
+ /**
+ * Cria um container de veículo com física e adiciona ao grupo de veículos.
+ * @param {number} x - Posição x inicial
+ * @param {number} y - Posição y inicial
+ * @returns {Phaser.GameObjects.Container} Container criado para o veículo
+ */
+ }
+ } else {
+ if (veiculo.body.velocity.y === 0) {
+
+ /**
+ * Adiciona um veículo parado na pista (usado quando semáforo está vermelho).
+ * Posiciona corretamente respeitando veículos já parados.
+ * @returns {void}
+ */
+ veiculo.body.setVelocityY(this.velocidadeQuedaBloco);
+ }
+ }
+ });
+ }
+
+ criarContainerVeiculo(x, y) {
+ const textura = Phaser.Math.RND.pick(this.texturasVeiculos);
+ const imagemVeiculo = this.add.image(0, 0, textura);
+ let escalaBase = this.larguraBloco / imagemVeiculo.width;
+ if (textura === "caminhao") imagemVeiculo.setScale(escalaBase * 1.2);
+ else if (textura === "carro") imagemVeiculo.setScale(escalaBase * 0.9);
+ else if (textura === "motoca") imagemVeiculo.setScale(escalaBase * 0.75);
+ else imagemVeiculo.setScale(escalaBase);
+
+ const container = this.add.container(x, y, [imagemVeiculo]);
+ container.setSize(this.larguraBloco, this.alturaBloco);
+
+ this.veiculos.add(container);
+ this.physics.world.enable(container);
+ container.body.setImmovable(true);
+
+ container.setDepth(ConstantesJogo.CAMADAS.VEICULOS);
+
+ return container;
+ }
+
+ /**
+ * Spawna um veículo na pista de acordo com o estado do semáforo.
+ * Não retorna valor; registros de ações são empurrados em `this.historico`.
+ * @returns {void}
+ */
+ spawnVeiculo() {
+ if (
+ this.estadoSemaforo.carros === "verde" ||
+ this.estadoSemaforo.carros === "amarelo"
+ ) {
+ const xPos = Phaser.Math.RND.pick(this.posicoesPista);
+ const veiculo = this.criarContainerVeiculo(
+ xPos,
+ this.sys.game.config.height + 100,
+ );
+ if (veiculo) {
+ veiculo.body.setVelocityY(this.velocidadeQuedaBloco);
+ }
+ } else if (this.estadoSemaforo.carros === "vermelho") {
+ this.adicionarVeiculoParado();
+ }
+ }
+
+ adicionarVeiculoParado() {
+ const xPos = Phaser.Math.RND.pick(this.posicoesPista);
+ const margemParada = this.sys.game.config.height * 0.05;
+ let yAlvo = this.faixaInferiorY + this.alturaBloco / 2 + margemParada;
+ const veiculosNaPista = this.veiculos
+ .getChildren()
+ .filter((v) => v.x === xPos && v.body.velocity.y === 0);
+ if (veiculosNaPista.length > 0) {
+ const ultimoVeiculo = veiculosNaPista.reduce((prev, current) =>
+ prev.y > current.y ? prev : current,
+ );
+ yAlvo = ultimoVeiculo.y + this.alturaBloco + 10;
+ }
+ if (yAlvo > this.sys.game.config.height) return;
+ const veiculo = this.criarContainerVeiculo(
+ xPos,
+ this.sys.game.config.height + 100,
+ );
+ if (veiculo) {
+ veiculo.body.setEnable(false);
+ this.tweens.add({
+ targets: veiculo,
+ y: yAlvo,
+ duration: 800,
+ ease: "Cubic.easeOut",
+ onComplete: () => {
+ veiculo.body.setEnable(true);
+ veiculo.body.setVelocityY(0);
+ },
+ });
+ }
+ }
+
+ /**
+ * Spawna um pedestre em posição aleatória dentro da faixa configurada.
+ * @returns {void}
+ */
+ spawnPedestre() {
+ const larguraTela = this.sys.game.config.width;
+ const yPos = Phaser.Math.Between(this.faixaSuperiorY, this.faixaInferiorY);
+ const pedestre = this.pedestres.create(
+ larguraTela + 20,
+ yPos,
+ ConstantesJogo.CHAVES_ASSETS.imagens.pedestre,
+ );
+ if (pedestre) {
+ pedestre.setDepth(ConstantesJogo.CAMADAS.PEDESTRES);
+ pedestre.setVelocityX(ConstantesJogo.VELOCIDADE_PEDESTRE);
+ }
+ }
+
+ /**
+ * Muda a cor do semáforo de carros
+ * @param {string} cor - Cor do semáforo ('verde', 'amarelo', 'vermelho')
+ * @returns {Promise} Promise que resolve após delay (apenas fase 1)
+ */
+ mudarSemaforoCarros(cor) {
+ this.historico.push({ tipo: "semaforo", cor });
+
+ this.estadoSemaforo.carros = cor;
+ this.atualizarEstadoSemaforos();
+
+ if (this.configFase?.id === 1) {
+ return new Promise((resolve) => {
+ this.time.delayedCall(2000, resolve);
+ });
+ }
+ }
+
+ /**
+ * Muda a cor do semáforo de pedestres
+ * @param {string} cor - Cor do semáforo ('verde', 'vermelho')
+ * @returns {Promise} Promise que resolve após delay (apenas fase 1)
+ */
+ mudarSemaforoPedestre(cor) {
+ this.historico.push({ tipo: "pedestre", cor });
+
+ this.estadoSemaforo.pedestre = cor;
+ this.atualizarEstadoSemaforos();
+
+ if (this.configFase?.id === 1) {
+ return new Promise((resolve) => {
+ this.time.delayedCall(5000, resolve);
+ });
+ }
+ }
+
+ /**
+ * Aguarda um tempo específico antes de continuar a execução
+ * @param {number} segundos - Tempo de espera em segundos
+ * @returns {Promise} Promise que resolve após o tempo especificado
+ */
+ aguardarSegundos(segundos) {
+ this.historico.push({ tipo: "aguardar", seg: segundos });
+
+ return new Promise((resolve) => {
+ this.time.delayedCall(segundos * 1000, resolve);
+ });
+ }
+
+ /**
+ * Toca um som do jogo
+ * @param {string} nomeSom - Nome do som a tocar ('beep_semaforo' ou 'barulho_cidade')
+ */
+ tocarSom(nomeSom) {
+ let som;
+ if (nomeSom === "beep_semaforo") som = this.somBeep;
+ else if (nomeSom === "barulho_cidade") som = this.somCidade;
+ else return;
+
+ this.historico.push({ tipo: "tocar", som: nomeSom });
+
+ som.play();
+ }
+
+ /**
+ * Para um som do jogo que está tocando
+ * @param {string} nomeSom - Nome do som a parar ('beep_semaforo' ou 'barulho_cidade')
+ */
+ pararSom(nomeSom) {
+ let som;
+ if (nomeSom === "beep_semaforo") som = this.somBeep;
+ else if (nomeSom === "barulho_cidade") som = this.somCidade;
+ else return;
+
+ this.historico.push({ tipo: "parar", som: nomeSom });
+
+ som.stop();
+ }
+
+ /**
+ * Faz a luz do semáforo de pedestre piscar por um tempo determinado
+ * @param {string} cor - Cor da luz piscante ('verde' ou 'vermelho')
+ * @param {number} duracaoSegundos - Duração do piscar em segundos (padrão: 5)
+ * @returns {Promise} Promise que resolve após o piscar terminar
+ */
+ async piscarLuzPedestre(cor = "vermelho", duracaoSegundos = 5) {
+ this.historico.push({ tipo: "piscar", cor, seg: duracaoSegundos });
+
+ if (this.timerPedestre) {
+ this.timerPedestre.remove(false);
+ this.timerPedestre = null;
+ }
+
+ const interval = 300;
+ const total = Math.floor((duracaoSegundos * 1000) / interval);
+ let visivel = true;
+
+ [this.semaforoPedestreEsquerda, this.semaforoPedestreDireita].forEach(
+ (semaforo) => {
+ if (semaforo) {
+ const componentes = semaforo.getData("componentes");
+ if (componentes) {
+ componentes.fundo.setFillStyle(
+ cor === "verde"
+ ? this.configSemaforo.coresSemaforoPedestre.fundoLiberado
+ : this.configSemaforo.coresSemaforoPedestre.fundoAceso,
+ );
+ componentes.brilho.setFillStyle(
+ cor === "verde"
+ ? this.configSemaforo.coresSemaforoPedestre.fundoLiberado
+ : this.configSemaforo.coresSemaforoPedestre.fundoAceso,
+ );
+ }
+ }
+ },
+ );
+
+ for (let i = 0; i < total; i++) {
+ [this.semaforoPedestreEsquerda, this.semaforoPedestreDireita].forEach(
+ (semaforo) => {
+ if (semaforo) {
+ const componentes = semaforo.getData("componentes");
+ if (componentes) {
+ componentes.fundo.setVisible(visivel);
+ componentes.brilho.setVisible(visivel);
+ }
+ }
+ },
+ );
+ visivel = !visivel;
+ await new Promise((resolve) => this.time.delayedCall(interval, resolve));
+ }
+
+ [this.semaforoPedestreEsquerda, this.semaforoPedestreDireita].forEach(
+ (semaforo) => {
+ if (semaforo) {
+ const componentes = semaforo.getData("componentes");
+ if (componentes) {
+ componentes.fundo.setVisible(true);
+ componentes.brilho.setVisible(true);
+ }
+ }
+ },
+ );
+ }
+
+ destacarBloco(id) {
+ if (this.workspace) this.workspace.highlightBlock(id);
+ this.pausaDestaque = true;
+ }
+
+
+ /**
+ * Destaca visualmente um bloco no workspace (API usada pelo interpretador).
+ * @param {string} id - ID do bloco a destacar
+ * @returns {void}
+ */
+ highlightBlock(id) {
+ if (this.workspace) this.workspace.highlightBlock(id);
+ this.highlightPause = true;
+ }
+}
+
+/**
+ * Fábrica que cria a configuração do Phaser para o jogo Semáforo.
+ * É usada pelo componente `GameBase` para instanciar a cena com a `configFaseAtual`.
+ * @param {HTMLElement} elementoPai - Elemento DOM que hospeda o canvas do jogo
+ * @param {Object} configFaseAtual - Configuração da fase ativa (de `gameConfig.fases`)
+ * @returns {Object} Configuração do Phaser usada para inicializar o jogo
+ */
+export const createGame = (elementoPai, configFaseAtual) => {
+ const scene = new SemaforoScene();
+
+ return {
+ type: Phaser.AUTO,
+ width: ConstantesJogo.LARGURA_TELA,
+ height: ConstantesJogo.ALTURA_TELA,
+ backgroundColor: ConstantesJogo.COR_FUNDO,
+ parent: elementoPai,
+ scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
+ antialias: true,
+ roundPixels: true,
+ pixelArt: false,
+ physics: {
+ default: "arcade",
+ arcade: { gravity: { y: 0 }, debug: ConstantesJogo.DEBUG_FISICA },
+ },
+ scene: scene,
+ callbacks: {
+ preBoot: function (game) {
+ game.registry.set("configFase", configFaseAtual);
+ game.registry.set("gameConfig", gameConfig);
+ },
+ },
+ };
+};
\ No newline at end of file
diff --git a/app/src/atividades/programacao/semaforo/hooks/interpreterSetup.js b/app/src/atividades/programacao/semaforo/hooks/interpreterSetup.js
new file mode 100644
index 0000000..14f6e10
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/hooks/interpreterSetup.js
@@ -0,0 +1,91 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ *
+ * @module games.semaforo.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Configura e registra as funções do intérprete para o jogo Semáforo.
+ * Retorna uma função que será passada ao ambiente do interpretador para
+ * expor ações como `mudarSemaforo`, `aguardarSegundos`, `tocarSom`, entre outras.
+ *
+ * @param {Object} scene - Referência à cena do Phaser (SemaforoScene)
+ * @param {Object} [config={}] - Opções de configuração (não utilizadas atualmente)
+ * @returns {function(interpreter: Object, globalScope: Object): void} Função de inicialização do interpretador
+ */
+export const setupSemaforoAPI = (scene, config = {}) => {
+ const getAnimationDelay = () => {
+ if (scene.executionSpeed >= 75) return 1;
+ if (scene.executionSpeed >= 50) return 2;
+ if (scene.executionSpeed >= 25) return 5;
+ return 10;
+ };
+
+ return (interpreter, globalScope) => {
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "mudarSemaforo",
+ ApiHelpers.createActionWrapper(
+ scene,
+ "mudarSemaforoCarros",
+ getAnimationDelay(),
+ ),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "mudarSemaforoPedestre",
+ ApiHelpers.createActionWrapper(
+ scene,
+ "mudarSemaforoPedestre",
+ getAnimationDelay(),
+ ),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "aguardarSegundos",
+ ApiHelpers.createActionWrapper(scene, "aguardarSegundos"),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "piscarLuzPedestre",
+ ApiHelpers.createActionWrapper(scene, "piscarLuzPedestre"),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "tocarSom",
+ ApiHelpers.createActionWrapper(scene, "tocarSom"),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "pararSom",
+ ApiHelpers.createActionWrapper(scene, "pararSom"),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
diff --git a/app/src/atividades/programacao/semaforo/hooks/useSemaforoTour.js b/app/src/atividades/programacao/semaforo/hooks/useSemaforoTour.js
new file mode 100644
index 0000000..4a0f516
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/hooks/useSemaforoTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useSemaforoTour.js
+ *
+ * @module games.semaforo.hooks.useSemaforoTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { semaforoTourSteps, semaforoTourOptions } from "../config/tourSteps";
+
+export const useSemaforoTour = () => {
+ /**
+ * Hook que inicia e controla o tour guiado específico do jogo Semáforo.
+ * Retorna os mesmos helpers que `useGameTour` (ex.: `startTour`, `isRunning`).
+ * @returns {Object} API do tour (por delegação a `useGameTour`).
+ */
+ return useGameTour("semaforo", semaforoTourSteps, semaforoTourOptions);
+};
diff --git a/app/src/atividades/programacao/semaforo/validation/validators.js b/app/src/atividades/programacao/semaforo/validation/validators.js
new file mode 100644
index 0000000..fd8d744
--- /dev/null
+++ b/app/src/atividades/programacao/semaforo/validation/validators.js
@@ -0,0 +1,358 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.semaforo.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador para o jogo Semáforo
+ *
+ * @class SemaforoValidator
+ * @extends BaseGameValidator
+ *
+ * @description
+ * Responsável por validar as ações do usuário em cada fase do jogo.
+ * Utiliza validação baseada em histórico de comandos executados,
+ * verificando sequências de cores, tempos de espera, sincronização
+ * de semáforos, piscar de luzes e controle de sons.
+ *
+ * Validações por fase:
+ * - Fase 1: Sequência básica de cores (verde → amarelo → vermelho)
+ * - Fase 2: Sequência com pausas entre mudanças
+ * - Fase 3: Sincronização de semáforos de carros e pedestres
+ * - Fase 4: Adiciona piscar da luz de pedestre
+ * - Fase 5: Adiciona controle de sons
+ */
+export class SemaforoValidator extends BaseGameValidator {
+ /**
+ * Método principal de validação - delega para métodos específicos por fase
+ *
+ * @param {Array} history - Histórico de comandos executados
+ * @param {Object} config - Configuração da fase atual
+ * @param {Object} gameConfig - Configuração geral do jogo (inclui mensagens)
+ * @param {Object} sceneRef - Referência à cena do jogo (opcional)
+ * @returns {Object} Resultado da validação {success: boolean, reason?: string}
+ */
+ validatePhase(history, config, gameConfig, sceneRef) {
+ // Verificar se tem histórico
+ if (!history || history.length === 0) {
+ return this.failure(
+ gameConfig.mensagens?.semComandos || "Nenhum comando executado",
+ );
+ }
+
+ // Roteamento por fase
+ const phaseMethod = this[`validateFase${config.id}`];
+ if (phaseMethod) {
+ return phaseMethod.call(this, history, config, gameConfig);
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Fase 1: Sequência básica de cores do semáforo
+ * Valida: verde → amarelo → vermelho
+ *
+ * @param {Array} history - Histórico de comandos
+ * @param {Object} config - Configuração da fase (contém expectedSequence)
+ * @param {Object} gameConfig - Config do jogo (contém mensagens)
+ * @returns {Object} Resultado da validação
+ *
+ * @note Fase 1 injeta automaticamente aguardarSegundos(2) entre cada mudança
+ * para melhorar a visualização, então ignoramos comandos 'aguardar' na validação
+ */
+ validateFase1(history, config, gameConfig) {
+ // Extrair apenas comandos de semáforo (ignorar aguardares automáticos)
+ const cores = history
+ .filter((h) => h.tipo === "semaforo")
+ .map((h) => h.cor);
+
+ const esperado = config.expectedSequence || [
+ "vermelho",
+ "amarelo",
+ "verde",
+ ];
+
+ if (cores.length !== esperado.length) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta || "Sequência incompleta",
+ );
+ }
+
+ const match = cores.every((cor, i) => cor === esperado[i]);
+ return match
+ ? this.success()
+ : this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Sequência de cores incorreta",
+ );
+ }
+
+ /**
+ * Fase 2: Sequência com aguardar
+ * Valida: sequência + pausas entre mudanças
+ */
+ validateFase2(history, config, gameConfig) {
+ const esperado = config.expectedCommands;
+
+ if (history.length !== esperado.length) {
+ return this.failure(
+ gameConfig.mensagens?.faltaAguardar ||
+ "Comandos faltando ou em excesso",
+ );
+ }
+
+ for (let i = 0; i < esperado.length; i++) {
+ if (history[i].tipo !== esperado[i].tipo) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Sequência de comandos incorreta",
+ );
+ }
+
+ if (
+ esperado[i].tipo === "semaforo" &&
+ history[i].cor !== esperado[i].cor
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Cor do semáforo incorreta",
+ );
+ }
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Verifica se um comando do histórico satisfaz um comando esperado.
+ * Compara apenas as propriedades presentes no esperado.
+ *
+ * @param {Object} h - Comando do histórico
+ * @param {Object} expected - Comando esperado
+ * @returns {boolean}
+ */
+ _commandMatches(h, expected) {
+ if (h.tipo !== expected.tipo) return false;
+ if (expected.cor !== undefined && h.cor !== expected.cor) return false;
+ if (expected.seg !== undefined && h.seg !== expected.seg) return false;
+ if (expected.som !== undefined && h.som !== expected.som) return false;
+ return true;
+ }
+
+ /**
+ * Valida o histórico contra um array de grupos ordenados.
+ * Dentro de cada grupo, os comandos podem aparecer em qualquer ordem.
+ * Os grupos devem aparecer na ordem definida.
+ *
+ * @param {Array} history - Histórico de comandos executados
+ * @param {Array>} groups - Grupos de comandos esperados
+ * @returns {boolean}
+ */
+ _matchCommandGroups(history, groups) {
+ const totalExpected = groups.reduce((sum, g) => sum + g.length, 0);
+ if (history.length !== totalExpected) return false;
+
+ let histIdx = 0;
+ for (const group of groups) {
+ const slice = history.slice(histIdx, histIdx + group.length);
+ const usedIndices = new Set();
+ for (const expected of group) {
+ const foundIdx = slice.findIndex(
+ (h, i) => !usedIndices.has(i) && this._commandMatches(h, expected),
+ );
+ if (foundIdx === -1) return false;
+ usedIndices.add(foundIdx);
+ }
+ histIdx += group.length;
+ }
+ return true;
+ }
+
+ /**
+ * Fase 3: Carros + pedestres
+ * Valida: sincronização entre semáforos de carros e pedestres.
+ * Suporta expectedCommandGroups (ordem livre dentro de cada grupo)
+ * e expectedCommands (ordem estrita, retrocompatível).
+ */
+ validateFase3(history, config, gameConfig) {
+ if (config.expectedCommandGroups) {
+ return this._matchCommandGroups(history, config.expectedCommandGroups)
+ ? this.success()
+ : this.failure(
+ gameConfig.mensagens?.pedestreErrado ||
+ "Sincronização dos semáforos incorreta",
+ );
+ }
+
+ const esperado = config.expectedCommands;
+
+ if (history.length !== esperado.length) {
+ return this.failure(
+ gameConfig.mensagens?.pedestreErrado ||
+ "Comandos faltando ou em excesso",
+ );
+ }
+
+ for (let i = 0; i < esperado.length; i++) {
+ if (history[i].tipo !== esperado[i].tipo) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Tipo de comando incorreto",
+ );
+ }
+
+ if (
+ (history[i].tipo === "semaforo" || history[i].tipo === "pedestre") &&
+ history[i].cor !== esperado[i].cor
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.pedestreErrado || "Cor incorreta",
+ );
+ }
+
+ if (
+ history[i].tipo === "aguardar" &&
+ history[i].seg !== esperado[i].seg
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.faltaAguardar || "Tempo de espera incorreto",
+ );
+ }
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Fase 4: + piscar
+ * Valida: inclui piscar da luz do pedestre.
+ * Suporta expectedCommandGroups (ordem livre dentro de cada grupo)
+ * e expectedCommands (ordem estrita, retrocompatível).
+ */
+ validateFase4(history, config, gameConfig) {
+ if (config.expectedCommandGroups) {
+ return this._matchCommandGroups(history, config.expectedCommandGroups)
+ ? this.success()
+ : this.failure(
+ gameConfig.mensagens?.semPiscar ||
+ "Sequência com piscar incorreta",
+ );
+ }
+
+ const esperado = config.expectedCommands;
+
+ if (history.length !== esperado.length) {
+ return this.failure(
+ gameConfig.mensagens?.semPiscar || "Comandos faltando ou em excesso",
+ );
+ }
+
+ for (let i = 0; i < esperado.length; i++) {
+ if (history[i].tipo !== esperado[i].tipo) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Tipo de comando incorreto",
+ );
+ }
+
+ if (
+ (history[i].tipo === "semaforo" ||
+ history[i].tipo === "pedestre" ||
+ history[i].tipo === "piscar") &&
+ history[i].cor !== esperado[i].cor
+ ) {
+ return this.failure(gameConfig.mensagens?.semPiscar || "Cor incorreta");
+ }
+
+ if (
+ (history[i].tipo === "aguardar" || history[i].tipo === "piscar") &&
+ history[i].seg !== esperado[i].seg
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.faltaAguardar || "Tempo incorreto",
+ );
+ }
+ }
+
+ return this.success();
+ }
+
+ /**
+ * Fase 5: + sons
+ * Valida: inclui controle de sons (tocar e parar).
+ * Suporta expectedCommandGroups (ordem livre dentro de cada grupo)
+ * e expectedCommands (ordem estrita, retrocompatível).
+ */
+ validateFase5(history, config, gameConfig) {
+ if (config.expectedCommandGroups) {
+ return this._matchCommandGroups(history, config.expectedCommandGroups)
+ ? this.success()
+ : this.failure(
+ gameConfig.mensagens?.semSom || "Sequência com sons incorreta",
+ );
+ }
+
+ const esperado = config.expectedCommands;
+
+ if (history.length !== esperado.length) {
+ return this.failure(
+ gameConfig.mensagens?.semSom || "Comandos faltando ou em excesso",
+ );
+ }
+
+ for (let i = 0; i < esperado.length; i++) {
+ if (history[i].tipo !== esperado[i].tipo) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta ||
+ "Tipo de comando incorreto",
+ );
+ }
+
+ if (
+ (history[i].tipo === "semaforo" ||
+ history[i].tipo === "pedestre" ||
+ history[i].tipo === "piscar") &&
+ history[i].cor !== esperado[i].cor
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.sequenciaIncorreta || "Cor incorreta",
+ );
+ }
+
+ if (
+ (history[i].tipo === "aguardar" || history[i].tipo === "piscar") &&
+ history[i].seg !== esperado[i].seg
+ ) {
+ return this.failure(
+ gameConfig.mensagens?.faltaAguardar || "Tempo incorreto",
+ );
+ }
+
+ if (
+ (history[i].tipo === "tocar" || history[i].tipo === "parar") &&
+ history[i].som !== esperado[i].som
+ ) {
+ return this.failure(gameConfig.mensagens?.semSom || "Som incorreto");
+ }
+ }
+
+ return this.success();
+ }
+}
+
+/**
+ * Função exportada para validação de soluções do Semáforo.
+ * @param {Array} history - Histórico de ações
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena (opcional)
+ * @returns {{success:boolean, reason?:string}}
+ */
+export function validateSolution(history, config, gameConfig, sceneRef) {
+ const validator = new SemaforoValidator();
+ return validator.validatePhase(history, config, gameConfig, sceneRef);
+}
diff --git a/app/src/atividades/programacao/turtle/TurtleGame.jsx b/app/src/atividades/programacao/turtle/TurtleGame.jsx
new file mode 100644
index 0000000..6658129
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/TurtleGame.jsx
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview React component for TurtleGame.jsx
+ *
+ * @module games.turtle.TurtleGame
+ */
+
+/*
+ MIGRATION: Wave 2 — Portuguese -> English identifier migration
+ TODO: Replace Portuguese aliases (ex.: `currentPhase` -> `currentPhase`) in this file.
+ See app/migrations/wave-2-candidates.txt for context.
+*/
+import React, { useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import GameBase from "../../../components/game/GameBase";
+import GameEditor from "../../../components/game/GameEditor";
+import BlocklyEditor from "../../../components/game/editors/BlocklyEditor";
+import CodeEditor from "../../../components/game/editors/CodeEditor";
+import { createGame } from "./game";
+import { gameConfig } from "./config/config";
+import {
+ registerBlocks,
+ turtleToolbox,
+ generateDynamicToolbox,
+} from "./blocks/blocks";
+import {
+ GameStateProvider,
+ useGameState,
+} from "../../../contexts/GameStateContext";
+import { useTurtleTour } from "./hooks/useTurtleTour";
+import { debugSolutions } from "./config/debugSolutions";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../../styles/shepherd-theme.css";
+
+/**
+ * Componente que monta a interface e o editor do jogo Tartaruga.
+ * Registra blocos, seleciona toolbox por fase e injeta o editor correto.
+ * @returns {JSX.Element} Elemento do jogo com editor Blockly/Code.
+ */
+function TurtleGameContent() {
+ const { currentPhase, setEditorType, isDebugMode, setFailureMessage } =
+ useGameState();
+ const { startTour } = useTurtleTour();
+
+ useEffect(() => {
+ registerBlocks();
+ }, []);
+
+ useEffect(() => {
+ if (currentPhase === 10) {
+ setEditorType("code");
+ } else {
+ setEditorType("blockly");
+ }
+ }, [currentPhase, setEditorType]);
+
+ const toolboxGenerator = useMemo(() => {
+ const currentPhaseConfig = gameConfig.fases.find(
+ (fase) => fase.id === currentPhase,
+ );
+
+
+
+ return () => {
+ if (
+ currentPhaseConfig?.allowedBlocks &&
+ currentPhaseConfig.allowedBlocks.length > 0
+ ) {
+ return generateDynamicToolbox(currentPhaseConfig.allowedBlocks);
+ }
+ return turtleToolbox;
+ };
+ }, [currentPhase]);
+
+ const renderEditor = () => {
+ if (currentPhase === 10) {
+ return ;
+ }
+ return (
+
+ );
+ };
+
+ return (
+
+ {renderEditor()}
+
+ );
+}
+
+/**
+ * Componente wrapper que provê `GameStateProvider` para o jogo Tartaruga.
+ * @returns {JSX.Element} Componente de alto nível do jogo.
+ */
+export default function TurtleGame() {
+ return (
+
+
+
+ );
+}
+
+TurtleGameContent.propTypes = {};
+TurtleGame.propTypes = {};
diff --git a/app/src/atividades/programacao/turtle/assets/1.png b/app/src/atividades/programacao/turtle/assets/1.png
new file mode 100644
index 0000000..326c2c4
Binary files /dev/null and b/app/src/atividades/programacao/turtle/assets/1.png differ
diff --git a/app/src/atividades/programacao/turtle/assets/2.png b/app/src/atividades/programacao/turtle/assets/2.png
new file mode 100644
index 0000000..5db282b
Binary files /dev/null and b/app/src/atividades/programacao/turtle/assets/2.png differ
diff --git a/app/src/atividades/programacao/turtle/assets/fail.mp3 b/app/src/atividades/programacao/turtle/assets/fail.mp3
new file mode 100644
index 0000000..cd8cc21
Binary files /dev/null and b/app/src/atividades/programacao/turtle/assets/fail.mp3 differ
diff --git a/app/src/atividades/programacao/turtle/assets/win.mp3 b/app/src/atividades/programacao/turtle/assets/win.mp3
new file mode 100644
index 0000000..5ae3d1d
Binary files /dev/null and b/app/src/atividades/programacao/turtle/assets/win.mp3 differ
diff --git a/app/src/atividades/programacao/turtle/blocks/blocks.js b/app/src/atividades/programacao/turtle/blocks/blocks.js
new file mode 100644
index 0000000..52d50c2
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/blocks/blocks.js
@@ -0,0 +1,366 @@
+/**
+ * @fileoverview Utility module for blocks.js
+ *
+ * @module games.turtle.blocks.blocks
+ */
+
+"use strict";
+
+import * as Blockly from "blockly/core";
+import { javascriptGenerator } from "blockly/javascript";
+
+const HUE_TARTARUGA = "#4CAF50";
+const HUE_COR = "#9C27B0";
+const HUE_REPETICAO = "#FF9800";
+const HUE_CANETA = "#1982c4";
+
+// Opções do Dropdowns
+const MOVE_OPTIONS = [
+ ["avançar", "forward"],
+ ["recuar", "backward"],
+];
+const DISTANCE_OPTIONS = [
+ ["20", "20"],
+ ["50", "50"],
+ ["100", "100"],
+ ["150", "150"],
+];
+const TURN_OPTIONS = [
+ [`virar a direita ↻`, "right"],
+ [`virar a esquerda ↺`, "left"],
+];
+const ANGLE_OPTIONS = [
+ ["1°", "1"],
+ ["45°", "45"],
+ ["72°", "72"],
+ ["90°", "90"],
+ ["120°", "120"],
+ ["144°", "144"],
+];
+const PEN_OPTIONS = [
+ ["abaixar caneta", "true"],
+ ["levantar caneta", "false"],
+];
+const REPEAT_OPTIONS = [
+ ["3", "3"],
+ ["4", "4"],
+ ["5", "5"],
+ ["360", "360"],
+];
+
+const COLOUR_OPTIONS = [
+ ["🔴 vermelho", "#ff0000"],
+ ["🟠 laranja", "#ffa500"],
+ ["🟡 amarelo", "#ffff00"],
+ ["🟣 roxo", "#800080"],
+ ["🟢 verde", "#008000"],
+ ["🔵 azul", "#0000ff"],
+ ["⚫ preto", "#000000"],
+ ["⚪ branco", "#ffffff"],
+];
+
+/**
+ * Registra todos os blocos personalizados do Turtle no Blockly.
+ * Executa definição de blocos e geradores.
+ * @returns {void}
+ */
+export const registerBlocks = () => {
+ defineBlocks();
+ defineGenerators();
+};
+
+const defineBlocks = () => {
+ Blockly.Blocks["turtle_move_internal"] = {
+ init: function () {
+ this.jsonInit({
+ type: "turtle_move_internal",
+ message0: "%1 %2",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "DIR",
+ options: MOVE_OPTIONS,
+ },
+ { type: "field_dropdown", name: "VALUE", options: DISTANCE_OPTIONS },
+ ],
+ previousStatement: true,
+ nextStatement: true,
+ colour: HUE_TARTARUGA,
+ tooltip: "Move a tartaruga para frente ou para trás.",
+ });
+ },
+ };
+
+ Blockly.Blocks["turtle_turn_internal"] = {
+ init: function () {
+ this.jsonInit({
+ type: "turtle_turn_internal",
+ message0: "%1 %2",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "DIR",
+ options: TURN_OPTIONS,
+ },
+ { type: "field_dropdown", name: "VALUE", options: ANGLE_OPTIONS },
+ ],
+ previousStatement: true,
+ nextStatement: true,
+ colour: "#ED0973",
+ tooltip: "Vira a tartaruga para a esquerda ou direita.",
+ });
+ },
+ };
+
+ Blockly.Blocks["turtle_pen"] = {
+ init: function () {
+ this.jsonInit({
+ type: "turtle_pen",
+ message0: "%1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "STATE",
+ options: PEN_OPTIONS,
+ },
+ ],
+ previousStatement: true,
+ nextStatement: true,
+ colour: HUE_CANETA,
+ tooltip: "Levanta ou abaixa a caneta.",
+ });
+ },
+ };
+
+ Blockly.Blocks["turtle_repeat_internal"] = {
+ init: function () {
+ this.jsonInit({
+ type: "turtle_repeat_internal",
+ message0: "repita %1 vezes",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "TIMES",
+ options: REPEAT_OPTIONS,
+ },
+ ],
+ message1: "faça %1",
+ args1: [
+ {
+ type: "input_statement",
+ name: "DO",
+ },
+ ],
+ previousStatement: true,
+ nextStatement: true,
+ colour: HUE_REPETICAO,
+ tooltip: "Repete os comandos internos um número fixo de vezes.",
+ });
+ },
+ };
+
+ Blockly.Blocks["turtle_colour_internal"] = {
+ init: function () {
+ this.jsonInit({
+ message0: "definir cor para %1",
+ args0: [
+ {
+ type: "field_dropdown",
+ name: "COLOUR",
+ options: COLOUR_OPTIONS,
+ },
+ ],
+ previousStatement: true,
+ nextStatement: true,
+ colour: HUE_COR,
+ tooltip: "Define a cor da caneta.",
+ });
+ },
+ };
+};
+
+/**
+ * Gera uma toolbox dinâmica filtrada pelos blocos permitidos para a fase.
+ * @param {string[]} [allowedBlocks=[]] - Lista de tipos de blocos permitidos.
+ * @returns {Object} Estrutura JSON compatível com a toolbox do Blockly.
+ */
+export const generateDynamicToolbox = (allowedBlocks = []) => {
+ // Se não receber allowedBlocks, retornar toolbox completa
+ if (
+ !allowedBlocks ||
+ !Array.isArray(allowedBlocks) ||
+ allowedBlocks.length === 0
+ ) {
+ return turtleToolbox;
+ }
+
+ // Definições de todos os blocos disponíveis
+ const blockDefinitions = {
+ turtle_move_internal: {
+ kind: "block",
+ type: "turtle_move_internal",
+ },
+ turtle_turn_internal: {
+ kind: "block",
+ type: "turtle_turn_internal",
+ },
+ turtle_colour_internal: {
+ kind: "block",
+ type: "turtle_colour_internal",
+ },
+ turtle_pen: {
+ kind: "block",
+ type: "turtle_pen",
+ },
+ turtle_repeat_internal: {
+ kind: "block",
+ type: "turtle_repeat_internal",
+ },
+ };
+
+ // Estrutura base da toolbox com todas as categorias
+ const toolboxContents = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: HUE_TARTARUGA,
+ contents: [],
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: HUE_REPETICAO,
+ contents: [],
+ },
+ {
+ kind: "category",
+ name: "Cor",
+ colour: HUE_COR,
+ contents: [],
+ },
+ {
+ kind: "category",
+ name: "Caneta",
+ colour: HUE_CANETA,
+ contents: [],
+ },
+ ],
+ };
+
+ allowedBlocks.forEach((blockId) => {
+ const blockDef = blockDefinitions[blockId];
+
+ if (!blockDef) {
+ console.warn(`generateDynamicToolbox: Bloco desconhecido: ${blockId}`);
+ return;
+ }
+
+ // Adicionar à categoria correta
+ if (["turtle_move_internal", "turtle_turn_internal"].includes(blockId)) {
+ toolboxContents.contents[0].contents.push(blockDef);
+ } else if (blockId === "turtle_repeat_internal") {
+ toolboxContents.contents[1].contents.push(blockDef);
+ } else if (blockId === "turtle_colour_internal") {
+ toolboxContents.contents[2].contents.push(blockDef);
+ } else if (blockId === "turtle_pen") {
+ toolboxContents.contents[3].contents.push(blockDef);
+ }
+ });
+
+ toolboxContents.contents = toolboxContents.contents.filter(
+ (category) => category.contents && category.contents.length > 0,
+ );
+
+ // Se não houver categorias válidas, retornar toolbox completa como fallback
+ if (toolboxContents.contents.length === 0) {
+ console.warn(
+ "generateDynamicToolbox: Nenhum bloco válido encontrado, usando toolbox completa",
+ );
+ return turtleToolbox;
+ }
+
+ return toolboxContents;
+};
+
+// --- GERADORES DE CÓDIGO ---
+/**
+ * Define os geradores JavaScript associados aos blocos do Turtle.
+ * @private
+ * @returns {void}
+ */
+const defineGenerators = () => {
+ javascriptGenerator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ javascriptGenerator.addReservedWords("highlightBlock");
+ let loopCounter = 0;
+
+ // Gera: move(100) ou move(-100)
+ javascriptGenerator.forBlock["turtle_move_internal"] = (b) => {
+ let value = b.getFieldValue("VALUE");
+ if (b.getFieldValue("DIR") === "backward") {
+ value = -value;
+ }
+ return `move(${value});\n`;
+ };
+
+ // Gera: turn(90) ou turn(-90)
+ javascriptGenerator.forBlock["turtle_turn_internal"] = (b) => {
+ let value = b.getFieldValue("VALUE");
+ if (b.getFieldValue("DIR") === "right") {
+ value = -value;
+ }
+ return `turn(${value});\n`;
+ };
+
+ // Gera: penDown(true) ou penDown(false)
+ javascriptGenerator.forBlock["turtle_pen"] = (b) => {
+ const state = b.getFieldValue("STATE");
+ return `penDown(${state});\n`;
+ };
+
+ // Gera: penColour('#ff0000')
+ javascriptGenerator.forBlock["turtle_colour_internal"] = (b) =>
+ `penColour('${b.getFieldValue("COLOUR")}');\n`;
+
+ // Gera o laço de repetição
+ javascriptGenerator.forBlock["turtle_repeat_internal"] = (b) => {
+ const r = b.getFieldValue("TIMES");
+ const s = javascriptGenerator.statementToCode(b, "DO");
+ const varName = `count${loopCounter++}`;
+ return `var ${varName} = 0;\nwhile (${varName} < ${r}) {\n${s} ${varName} = ${varName} + 1;\n}\n`;
+ };
+};
+
+export const turtleToolbox = {
+ kind: "categoryToolbox",
+ contents: [
+ {
+ kind: "category",
+ name: "Movimento",
+ colour: HUE_TARTARUGA,
+ contents: [
+ { kind: "block", type: "turtle_move_internal" },
+ { kind: "block", type: "turtle_turn_internal" },
+ ],
+ },
+ {
+ kind: "category",
+ name: "Cor",
+ colour: HUE_COR,
+ contents: [{ kind: "block", type: "turtle_colour_internal" }],
+ },
+ {
+ kind: "category",
+ name: "Caneta",
+ colour: HUE_CANETA,
+ contents: [{ kind: "block", type: "turtle_pen" }],
+ },
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: HUE_REPETICAO,
+ contents: [{ kind: "block", type: "turtle_repeat_internal" }],
+ },
+ ],
+};
diff --git a/app/src/atividades/programacao/turtle/config/config.js b/app/src/atividades/programacao/turtle/config/config.js
new file mode 100644
index 0000000..3f6681a
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/config/config.js
@@ -0,0 +1,441 @@
+/**
+ * @fileoverview Utility module for config.js
+ *
+ * @module games.turtle.config.config
+ */
+
+export const gameConfig = {
+ gameId: "turtle",
+ gameName: "Tartaruga",
+ descricao: "Aprenda a programar criando desenhos incríveis com a Tartaruga!",
+ type: "blocks",
+ icon: "🐢",
+ thumbnail: "/images/atividades/programacao/turtle-thumbnail.png",
+ dificuldade: "Iniciante",
+ categoria: "Lógica",
+ tempoEstimado: "60-120 min",
+ conceitos: ["Movimento", "Repetição", "Funções"],
+ route: "/atividades/programacao/turtle",
+ component: "TurtleGame",
+ objetivos: [
+ "Entender sequências de comandos",
+ "Usar loops para otimar código",
+ "Criar e usar funções",
+ ],
+ metadata: {
+ lastUpdated: "2026-01-05",
+ version: "1.1.0",
+ },
+ mensagens: {
+ semDesenho:
+ "Você não desenhou nada! Certifique-se de usar os comandos de movimento com a caneta abaixada.",
+ desenhoNaoConfere:
+ "O desenho não está correto. Verifique a forma, posição e cores.",
+ erroGeral: "Algo deu errado durante a execução. Verifique seu código.",
+ sucessoGenerico: "Parabéns! Você completou o desafio!",
+ timeoutExcedido:
+ "O tempo de execução foi excedido. Verifique se não há loops infinitos.",
+ },
+ functionDocumentation: {
+ move: {
+ description: "Move a tartaruga para frente (positivo) ou trás (negativo)",
+ syntax: "move(distância)",
+ parameters: [
+ "distância: number - Pixels para mover (positivo=frente, negativo=trás)",
+ ],
+ returns: "void",
+ example: "move(100);",
+ },
+ turn: {
+ description: "Vira a tartaruga. Positivo = esquerda, Negativo = direita",
+ syntax: "turn(ângulo)",
+ parameters: [
+ "ângulo: number - Graus para virar (positivo=esquerda, negativo=direita)",
+ ],
+ returns: "void",
+ example: "turn(90);",
+ },
+ penDown: {
+ description:
+ "Controla se a caneta está abaixada (desenha) ou levantada (não desenha)",
+ syntax: "penDown(estado)",
+ parameters: [
+ "estado: boolean - true para desenhar, false para não desenhar",
+ ],
+ returns: "void",
+ example: "penDown(true);",
+ },
+ penColour: {
+ description: "Define a cor da caneta para desenhar",
+ syntax: "penColour(cor)",
+ parameters: [
+ 'cor: string - Cor em hexadecimal (#ff0000) ou nome ("vermelho")',
+ ],
+ returns: "void",
+ example: 'penColour("#ff0000");',
+ },
+ pintarFundo: {
+ description: "Permite pintar o fundo da cena de uma cor",
+ syntax: "pintarFundo(cor)",
+ parameters: [
+ 'cor: string - Cor em hexadecimal (#ff0000) ou nome ("vermelho")',
+ ],
+ returns: "void",
+ example: 'pintarFundo("#000080");',
+ },
+ },
+ allowedControlStructures: ["if", "else", "while", "for", "var", "function"],
+ fases: [
+ {
+ id: 1,
+ nome: "O Quadrado",
+ descricao: "Desenhe um quadrado.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ ],
+ maxBlocks: 10,
+ timeout: 15,
+ requiredDrawingMatch: true,
+ solutionCode:
+ "var count = 0;\n" +
+ "while (count < 4) {\n" +
+ " move(100);\n" +
+ " turn(90);\n" +
+ " count = count + 1;\n" +
+ "}\n",
+ },
+ {
+ id: 2,
+ nome: "O Pentágono",
+ descricao: "Desenhe um pentágono.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ ],
+ maxBlocks: 10,
+ timeout: 15,
+ requiredDrawingMatch: true,
+ solutionCode:
+ "for (var count = 0; count < 5; count++) {\n" +
+ " move(100);\n" +
+ " turn(72);\n" +
+ "}\n",
+ },
+ {
+ id: 3,
+ nome: "A Estrela",
+ descricao: "Desenhe uma estrela amarela.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 12,
+ timeout: 20,
+ requiredDrawingMatch: true,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "drawStar(100);\n",
+ },
+ {
+ id: 4,
+ nome: "Estrela e Linha",
+ descricao: "Desenhe uma estrela e depois uma linha ao lado.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 15,
+ timeout: 20,
+ requiredDrawingMatch: true,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "drawStar(50);\n" +
+ "penDown(false);\n" +
+ "move(150);\n" +
+ "penDown(true);\n" +
+ "move(20);\n",
+ },
+ {
+ id: 5,
+ nome: "Quatro Estrelas",
+ timeout: 25,
+ requiredDrawingMatch: true,
+ descricao: "Desenhe quatro estrelas em um padrão circular.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 18,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "for (var count = 0; count < 4; count++) {\n" +
+ " drawStar(50);\n" +
+ " penDown(false);\n" +
+ " move(150);\n" +
+ " turn(90);\n" +
+ " penDown(true);\n" +
+ "}\n",
+ },
+ {
+ id: 6,
+ nome: "Três Estrelas e uma Linha",
+ timeout: 25,
+ requiredDrawingMatch: true,
+ descricao: "Desenhe três estrelas e uma linha branca.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 20,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "for (var count = 0; count < 3; count++) {\n" +
+ " drawStar(50);\n" +
+ " penDown(false);\n" +
+ " move(150);\n" +
+ " turn(120);\n" +
+ " penDown(true);\n" +
+ "}\n" +
+ "penDown(false);\n" +
+ "turn(-90);\n" +
+ "move(100);\n" +
+ "penDown(true);\n" +
+ 'penColour("#ffffff");\n' +
+ "move(50);\n",
+ },
+ {
+ id: 7,
+ nome: "Três Estrelas e Raios",
+ timeout: 30,
+ requiredDrawingMatch: true,
+ descricao: "Desenhe três estrelas e quatro raios brancos.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 25,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "for (var count = 0; count < 3; count++) {\n" +
+ " drawStar(50);\n" +
+ " penDown(false);\n" +
+ " move(150);\n" +
+ " turn(120);\n" +
+ " penDown(true);\n" +
+ "}\n" +
+ "penDown(false);\n" +
+ "turn(-90);\n" +
+ "move(100);\n" +
+ "penDown(true);\n" +
+ 'penColour("#ffffff");\n' +
+ "for (var count2 = 0; count2 < 4; count2++) {\n" +
+ " move(50);\n" +
+ " move(-50);\n" +
+ " turn(45);\n" +
+ "}\n",
+ },
+ {
+ id: 8,
+ nome: "Três Estrelas e um Círculo",
+ timeout: 320,
+ requiredDrawingMatch: true,
+ descricao: "Desenhe três estrelas e um círculo branco.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 25,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "for (var count = 0; count < 3; count++) {\n" +
+ " drawStar(50);\n" +
+ " penDown(false);\n" +
+ " move(150);\n" +
+ " turn(120);\n" +
+ " penDown(true);\n" +
+ "}\n" +
+ "penDown(false);\n" +
+ "turn(-90);\n" +
+ "move(100);\n" +
+ "penDown(true);\n" +
+ 'penColour("#ffffff");\n' +
+ "for (var count2 = 0; count2 < 360; count2++) {\n" +
+ " move(50);\n" +
+ " move(-50);\n" +
+ " turn(1);\n" +
+ "}\n",
+ },
+ {
+ timeout: 320,
+ requiredDrawingMatch: true,
+ id: 9,
+ nome: "Lua Crescente",
+ descricao: "Desenhe três estrelas e uma lua crescente.",
+ allowedBlocks: [
+ "turtle_move_internal",
+ "turtle_turn_internal",
+ "turtle_repeat_internal",
+ "turtle_pen",
+ "turtle_colour_internal",
+ ],
+ maxBlocks: 35,
+ solutionCode:
+ "function drawStar(length) {\n" +
+ " for (var count = 0; count < 5; count++) {\n" +
+ " move(length);\n" +
+ " turn(144);\n" +
+ " }\n" +
+ "}\n" +
+ 'penColour("#ffff00");\n' +
+ "for (var count = 0; count < 3; count++) {\n" +
+ " drawStar(50);\n" +
+ " penDown(false);\n" +
+ " move(150);\n" +
+ " turn(120);\n" +
+ " penDown(true);\n" +
+ "}\n" +
+ "penDown(false);\n" +
+ "turn(-90);\n" +
+ "move(100);\n" +
+ "penDown(true);\n" +
+ 'penColour("#ffffff");\n' +
+ "for (var count2 = 0; count2 < 360; count2++) {\n" +
+ " move(50);\n" +
+ " move(-50);\n" +
+ " turn(1);\n" +
+ "}\n" +
+ "turn(120);\n" +
+ "move(20);\n" +
+ 'penColour("#000000");\n' +
+ "for (var count3 = 0; count3 < 360; count3++) {\n" +
+ " move(50);\n" +
+ " move(-50);\n" +
+ " turn(1);\n" +
+ "}\n",
+ },
+ {
+ id: 10,
+ nome: "Desafio Livre",
+ descricao:
+ "Crie seu próprio desenho, agora com código! Use sua criatividade e divirta-se.",
+ timeout: 320,
+ requiredDrawingMatch: false,
+ mensagens: {
+ sucessoGenerico: "Incrível! Continue explorando e criando!",
+ },
+ allowedGlobals: [
+ "move",
+ "turn",
+ "penDown",
+ "penColour",
+ "desenharCirculo",
+ "var",
+ "while",
+ "for",
+ "if",
+ "else",
+ "function",
+ "return",
+ "count",
+ "true",
+ "false",
+ ],
+ solutionCode: "",
+ initialCode: `// === INSTRUÇÕES DE USO ===
+// A tartaruga começa no centro da tela, virada para a direita
+
+// MOVIMENTOS BÁSICOS:
+// move(100); // Move para frente 100 pixels
+// move(-50); // Move para trás 50 pixels
+// turn(90); // Vira 90 graus para esquerda
+// turn(-90); // Vira 90 graus para direita
+
+// CONTROLE DA CANETA:
+// penDown(true); // Abaixa a caneta (desenha)
+// penDown(false); // Levanta a caneta (não desenha)
+
+// CORES DISPONÍVEIS:
+// penColour("#ff0000"); // Vermelho
+// penColour("#00ff00"); // Verde
+// penColour("#0000ff"); // Azul
+// penColour("#ffff00"); // Amarelo
+// penColour("#ffffff"); // Branco
+// penColour("#000000"); // Preto
+// penColour("vermelho"); // Ou use nomes em português
+
+// OUTROS COMANDOS:
+// pintarFundo("#000080"); // Muda cor do fundo
+
+// EXEMPLO - QUADRADO VERMELHO:
+//penColour("#ff0000");
+//for (var i = 0; i < 4; i++) {
+// move(100);
+// turn(90);
+//}
+
+// EXEMPLO - CÍRCULO AZUL:
+//penColour("#0000ff");
+//for (var i = 0; i < 360; i++) {
+// move(2);
+// turn(1);
+//}
+`,
+ },
+ ],
+};
diff --git a/app/src/atividades/programacao/turtle/config/debugSolutions.js b/app/src/atividades/programacao/turtle/config/debugSolutions.js
new file mode 100644
index 0000000..190753e
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/config/debugSolutions.js
@@ -0,0 +1,1094 @@
+/**
+ * @fileoverview Utility module for debugSolutions.js
+ *
+ * @module games.turtle.config.debugSolutions
+ */
+
+export const debugSolutions = {
+ 1: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_repeat_internal",
+ id: "_x(ha.G3,XeyEMm5!UEI",
+ x: 38,
+ y: 38,
+ fields: { TIMES: "4" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "U79Hxe_Ny0uQ4w^2:OZ(",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "iYf9f6tj2KR;Pj;L2x=^",
+ fields: { DIR: "left", VALUE: "90" },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 2: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_repeat_internal",
+ id: "WcA{h0DVJQ.VD5uINzV_",
+ x: 88,
+ y: 88,
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "uRq;+l5$*6-,uZUpGo)a",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "Lb^Xs+]G:!kEqR2ncaio",
+ fields: { DIR: "left", VALUE: "72" },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 3: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_turn_internal",
+ id: "}sZ~xxY%v]NAMK4cQM/7",
+ x: 63,
+ y: 13,
+ fields: { DIR: "right", VALUE: "90" },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "l=-NxXQ)5BX~YJWOizlA",
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "AeD2o}%p)Hj{HTK|I0/a",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "Ee:0_5@ENK#|KM]G@*MP",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "rCFfV,A%/dd.Oqs#pdsz",
+ fields: { DIR: "right", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 4: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "1EVaqptqXGanQU:?Du,(",
+ x: 138,
+ y: 63,
+ fields: { COLOUR: "#ff0000" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "@OXNXaJJu})U}r(7OACt",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "Wc+TbbF?.ThuwYKNhWru",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "JF#/bt(]7vsx,+F:8Uv:",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "4.)mFqk~_cJDy[0X?=#L",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "@q5:L(^5O0ms{J8CiH@~",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "xM(x3AQ3A6V^ljAavK+C",
+ fields: { STATE: "true" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "n5ye3zwie+~~/WRDPf{U",
+ fields: { DIR: "forward", VALUE: "20" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 5: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "bWIoxw|~U5qzlrCq^E-:",
+ x: 88,
+ y: 63,
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "sLkEe(gsjbB)sJUj@W~y",
+ fields: { TIMES: "4" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "wMWh{25h`3qQ^=aNm!D6",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "|[U_{;,jOz%?xg984R8l",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "Vo}r8r6-Tf)*pGk]ghho",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "7676Bwzt(;/SmVOW@9y3",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "YWJ|fHc[I3F[zfSkyUbt",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "[hHcq]Q6{kI[w/iaIdI:",
+ fields: { DIR: "left", VALUE: "90" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "CtYxbK(8-[*$@HBC,)sR",
+ fields: { STATE: "true" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 6: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "QEXrm=(v-FrRoeFd](L:",
+ x: -187,
+ y: -112,
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "3iUtMN]97{X,n1h6$f:]",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "btyp=I_S.toF)cPzWdd_",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "rjZf27Pi~~zO/**:jbhP",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "`{J/gS2DANoWGI5TKuim",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "S5,N!6IQc+6yP^}/~%)j",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "1Fmz96q:.R}OB}lMn{dU",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "m3#Na97##^5I|wK{e_kJ",
+ fields: { DIR: "left", VALUE: "120" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "X=W3]M.2K8tC|Aly1l=3",
+ fields: { STATE: "true" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "Z6vN}jx70Ad**AN!R*c2",
+ fields: { COLOUR: "#ffffff" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: ",D^Q9N8fZN[Riu`mW@:*",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "Owor=d@8UPxU#UWmNg=9",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "+WAD)F7T%rqkYmRXi(=z",
+ fields: { DIR: "right", VALUE: "90" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "^ZIQ*:vvb:9S+sr.y9@$",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "X.{=DlO6$Xs~Olo#tC{`",
+ fields: { STATE: "true" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "8!AjA/q|0(+ap()1Ztxt",
+ fields: { DIR: "forward", VALUE: "50" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 7: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "?_Hw1t)-bxilKUCfNbm{",
+ x: -12,
+ y: -87,
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "r.DqH_JN/-i544sOME1P",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "Xms$+^*)WJM5F4.|XFUG",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "g6CekBxM+Dg?~:3_+/[h",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "hnO+.(H~Ejeyd5U3teTl",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "5-]1b5{~h]x$D748)~1P",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "KN=e[)(+i85_Kyd4O$3o",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "@Ki-%KXq|_edN4*)[/Ql",
+ fields: { DIR: "left", VALUE: "120" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "R!s}=k])E*|IE![GfRYz",
+ fields: { STATE: "true" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "UeFw~4H)7aTc,Eh$%;P6",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "KgvjVgq0Ebhfee]`WDmi",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_turn_internal",
+ id: ";8g/|R1P1!fkZ%kS,tC@",
+ fields: { DIR: "left", VALUE: "90" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "M!8kDF:I^TI`yi@l]8Cb",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "@KU!|AyYDZd^^-AbS[B[",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "NX-$ikSDp`izPU1RSRYz",
+ fields: { COLOUR: "#ffffff" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "90s/2HV|cIi~Kc,./t$n",
+ fields: { STATE: "true" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "N+T,*+`vV.PW|KdZsbX[",
+ fields: { TIMES: "4" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "5Zd_ESbCk}q~EyuVXP{/",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "haUj:]SlX}{DbEMy_Hrc",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "Yr];mQ0TJ^9A6i-*sfU`",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "Sl%dX,)$idtH;WLPK_+^",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "@js)8dWFis7aY:4;i9Os",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "Z8@Lk?ik)PB1n!:~R97_",
+ fields: {
+ DIR: "right",
+ VALUE:
+ "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "9u1~fNkOv?v%0!:V]wxn",
+ fields:
+ {
+ DIR: "left",
+ VALUE:
+ "45",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 8: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "initial_color",
+ x: 10,
+ y: 10,
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "loop_stars",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "loop_points",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "star_move",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "star_turn",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "pen_up_move",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "move_gap",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "turn_gap",
+ fields: { DIR: "left", VALUE: "120" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "pen_down_ready",
+ fields: { STATE: "true" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "pen_up_turn",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "loop_turn_right",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "turn_90",
+ fields: { DIR: "left", VALUE: "90" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "move_100",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "pen_down_circle",
+ fields: { STATE: "true" },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "color_white",
+ fields: { COLOUR: "#ffffff" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "loop_circle",
+ fields: { TIMES: "360" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "c_move1",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "c_turn1",
+ fields: {
+ DIR: "left",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "c_turn2",
+ fields: {
+ DIR: "left",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "c_move2",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "c_turn3",
+ fields: {
+ DIR: "left",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "c_turn4",
+ fields: {
+ DIR: "left",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "c_step",
+ fields: {
+ DIR: "left",
+ VALUE:
+ "1",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ 9: {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "turtle_colour_internal",
+ id: "$qr4FRAe3CwQdlcUS`Mb",
+ x: -862,
+ y: -1287,
+ fields: { COLOUR: "#ffff00" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "a1$nfYR^I*3,)QjT0q}#",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "Jr-04}nDuRe#NjQZPtf%",
+ fields: { TIMES: "5" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "U|E#DYV?/*.*,.Ww/i,c",
+ fields: { DIR: "forward", VALUE: "50" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "GC`~t/}%YgE0Ox!k~dw%",
+ fields: { DIR: "left", VALUE: "144" },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "uz;.27r4t+CfBlhnMzft",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "k^~Ilu@MP_]|@^SuFi)=",
+ fields: { DIR: "forward", VALUE: "150" },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: ";|q$o^`BI:zFdRUwmmyY",
+ fields: { DIR: "left", VALUE: "120" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "xRo?HYkQI[eCLpXjlQ[L",
+ fields: { STATE: "true" },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "=2Mm/RGI~/Nb~r1E%-2w",
+ fields: { STATE: "false" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "H5}Fv5@UT5E@`HDlU|ZH",
+ fields: { TIMES: "3" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "p=-!UF[n{cmL/i]%L`KI",
+ fields: { DIR: "left", VALUE: "90" },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "((WaPIE;WPan,5$9%55G",
+ fields: { DIR: "forward", VALUE: "100" },
+ next: {
+ block: {
+ type: "turtle_pen",
+ id: "aayonU^4Gb9*h47Dq5eu",
+ fields: { STATE: "true" },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "z|PjPls#fM?HNJ!n;pQo",
+ fields: { COLOUR: "#ffffff" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "(Jz(M@@.+`/2MeC`JbqG",
+ fields: { TIMES: "360" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "wx-Irdt9pgO,SEibP`C/",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "(JG?a85+/nmzoth6G9ti",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "+2F3WRHo@H98b+T{=]w/",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "5#_S3uiqJvD-J!-#DWZf",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "%.}E_v]H1`(2}75TBh$T",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "U~}YWx*14wmPd6iXCu(]",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "n42r6IW,y}I5(|]{cr~f",
+ fields: {
+ DIR: "left",
+ VALUE:
+ "1",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "AX`Uo=4e,DfVkn+Q3.RN",
+ fields: { DIR: "left", VALUE: "120" },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "nq{BZfY7#Ah9SSESU|Kr",
+ fields: {
+ DIR: "forward",
+ VALUE: "20",
+ },
+ next: {
+ block: {
+ type: "turtle_colour_internal",
+ id: "sxe1yyt;~qxCH6!yIQgv",
+ fields: { COLOUR: "#000000" },
+ next: {
+ block: {
+ type: "turtle_repeat_internal",
+ id: "Iy?N]EC8gm4W(/;D{@lO",
+ fields: { TIMES: "360" },
+ inputs: {
+ DO: {
+ block: {
+ type: "turtle_move_internal",
+ id: "[#6In/n1,-e]R|HP7PHb",
+ fields: {
+ DIR: "forward",
+ VALUE: "50",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "K.*F^d-47Rg:n)IQQm$P",
+ fields: {
+ DIR: "right",
+ VALUE: "90",
+ },
+ next: {
+ block: {
+ type: "turtle_turn_internal",
+ id: "aDqCdK?R*Jefg[tMgDJV",
+ fields: {
+ DIR: "right",
+ VALUE:
+ "90",
+ },
+ next: {
+ block: {
+ type: "turtle_move_internal",
+ id: "~J+2y^Jy[gx5$8XsG.S:",
+ fields:
+ {
+ DIR: "forward",
+ VALUE:
+ "50",
+ },
+ next: {
+ block:
+ {
+ type: "turtle_turn_internal",
+ id: "sBvmRovLDFYP!i5?Q*uV",
+ fields:
+ {
+ DIR: "right",
+ VALUE:
+ "90",
+ },
+ next: {
+ block:
+ {
+ type: "turtle_turn_internal",
+ id: "N}Pv/9/s^n1u6wC$TAb7",
+ fields:
+ {
+ DIR: "right",
+ VALUE:
+ "90",
+ },
+ next: {
+ block:
+ {
+ type: "turtle_turn_internal",
+ id: "R.z62qY(8:MS}SQ6f{ls",
+ fields:
+ {
+ DIR: "left",
+ VALUE:
+ "1",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+};
diff --git a/app/src/atividades/programacao/turtle/config/tourSteps.js b/app/src/atividades/programacao/turtle/config/tourSteps.js
new file mode 100644
index 0000000..cebc3fd
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/config/tourSteps.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module games.turtle.config.tourSteps
+ */
+
+import {
+ createWelcomeStep,
+ createGameAreaStep,
+ createToolboxStep,
+ createWorkspaceStep,
+ createRunButtonStep,
+ createResetInfoStep,
+ createPhaseSelectorStep,
+ createPhaseInfoStep,
+ createHelpButtonStep,
+ gameIcons,
+ defaultGameTourOptions,
+} from "../../../../utils/tourHelpers";
+
+export const turtleTourSteps = [
+ createWelcomeStep({
+ gameName: "Jogo da Tartaruga",
+ description:
+ "Aprenda a programar criando desenhos! A tartaruga deixa um rastro enquanto se move pelo canvas.",
+ challenge:
+ "Use comandos de movimento, giro e funções para criar formas geométricas e arte generativa.",
+ iconSvg: gameIcons.turtle,
+ }),
+
+ createGameAreaStep({
+ title: "Canvas de Desenho",
+ description:
+ "Aqui a tartaruga desenha. Ela começa no centro e deixa uma linha colorida por onde passa.",
+ }),
+
+ createToolboxStep({
+ description:
+ "Use blocos de movimento, loops e funções para criar desenhos complexos com menos código.",
+ }),
+
+ createWorkspaceStep({
+ description:
+ "Monte sequências criativas. Combine movimentos e rotações para desenhar usando loops.",
+ }),
+
+ createRunButtonStep({
+ description:
+ "Execute e veja a tartaruga desenhar. Observe a animação e os padrões criados.",
+ }),
+
+ createResetInfoStep({
+ description:
+ "Quer começar um novo desenho? Use o reset para limpar o canvas.",
+ }),
+
+ createPhaseSelectorStep({
+ description:
+ "Cada fase propõe um desafio diferente: criar uma forma específica, reproduzir um desenho ou criar arte livre.",
+ }),
+
+ createPhaseInfoStep({
+ description:
+ "Veja qual desenho você precisa criar e dicas de como usar loops e funções.",
+ }),
+
+ createHelpButtonStep({
+ description:
+ "Dúvidas sobre como mover ou girar a tartaruga? Clique aqui para rever as instruções.",
+ }),
+];
+
+export const turtleTourOptions = defaultGameTourOptions;
diff --git a/app/src/atividades/programacao/turtle/exemplo_logo.txt b/app/src/atividades/programacao/turtle/exemplo_logo.txt
new file mode 100644
index 0000000..3814e98
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/exemplo_logo.txt
@@ -0,0 +1,76 @@
+function desenharT() {
+ penDown(false)
+ turn(135)
+ move(10)
+ turn(90)
+ move(20)
+ penDown(true)
+ move(170)
+ penDown(false)
+ turn(180)
+ move(50)
+ turn(270)
+ penDown(true)
+ move(50)
+ penDown(false)
+ turn(90)
+ move(60)
+ turn(90)
+ penDown(true)
+ move(50)
+}
+
+function desenharM() {
+ turn(45)
+ move(100)
+ turn(270)
+ move(50)
+ turn(90)
+ move(50)
+ turn(270)
+ move(100)
+}
+
+function desenharS() {
+ penDown(false)
+ turn(90)
+ move(10)
+ turn(0)
+ move(10)
+ turn(90)
+ move(10)
+ turn(270)
+ penDown(true)
+ move(20)
+ turn(90)
+ move(15)
+ turn(90)
+ move(20)
+ turn(270)
+ move(15)
+ turn(270)
+ move(20)
+}
+
+function desenharCirculo(raio) {
+ for (var i = 0; i < 360; i++) {
+ move(raio)
+ turn(1)
+ }
+}
+
+pintarFundo("vermelho")
+penDown(false)
+move(-100)
+penDown(true)
+desenharM();
+desenharT();
+desenharS();
+penDown(false);
+turn(180);
+move(140);
+turn(270);
+move(-50);
+turn(180);
+penDown(true);
+desenharCirculo(2.2);
\ No newline at end of file
diff --git a/app/src/atividades/programacao/turtle/game.js b/app/src/atividades/programacao/turtle/game.js
new file mode 100644
index 0000000..01f75c8
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/game.js
@@ -0,0 +1,908 @@
+/**
+ * @fileoverview Utility module for game.js
+ *
+ * @module games.turtle.game
+ */
+
+import Phaser from "phaser";
+import { BaseGameScene } from "../../../shared/BaseGameScene.js";
+import { GameInterpreter } from "../../../interpreters/GameInterpreter.js";
+import { setupTurtleAPI } from "./hooks/interpreterSetup.js";
+import { validateSolution } from "./validation/validators.js";
+import turtleImg1 from "./assets/1.png";
+import turtleImg2 from "./assets/2.png";
+import win from "./assets/win.mp3";
+import fail from "./assets/fail.mp3";
+
+const ASSETS = {
+ IMG: { TURTLE1: "turtle1", TURTLE2: "turtle2" },
+ AUDIO: { WIN: "win", FAIL: "fail" },
+};
+
+const CONSTANTES = {
+ LARGURA: 640,
+ ALTURA: 480,
+};
+
+class TurtleScene extends BaseGameScene {
+ constructor() {
+ super("TurtleScene");
+
+ this.turtleState = null;
+ this.turtleSprite = null;
+ this.solutionGraphics = null;
+ this.playerGraphics = null;
+ this.validationGraphics = null;
+ this.activeGraphics = null;
+ this.backgroundRect = null;
+ this.playerColors = [];
+ this.solutionColors = [];
+ this.executionSpeed = 50;
+ this.sliderWidth = 0;
+ this.sliderFill = null;
+ this.sliderHandle = null;
+ this.speedText = null;
+ this.playerRT = null;
+ this.validationRT = null;
+ this.resultadoJogada = "em_andamento";
+ }
+
+ preload() {
+ this.preloadGlobalAssets();
+ this.load.image(ASSETS.IMG.TURTLE1, turtleImg1);
+ this.load.image(ASSETS.IMG.TURTLE2, turtleImg2);
+ this.load.audio(ASSETS.AUDIO.WIN, win);
+ this.load.audio(ASSETS.AUDIO.FAIL, fail);
+ }
+
+ /**
+ * Preload de recursos (imagens e áudios) usados pela cena `TurtleScene`.
+ * @returns {void}
+ */
+
+ init(data) {
+ super.init(data);
+ this.executionSpeed = 50;
+ this.turtleState = {
+ x: 0,
+ y: 0,
+ angle: 0,
+ penDown: true,
+ penColour: 0xffffff,
+ };
+ this.playerColors = [];
+ this.solutionColors = [];
+ this.resultadoJogada = "em_andamento";
+ }
+
+ /**
+ * Inicializa o estado da tartaruga e configurações padrão da cena.
+ * @param {Object} data - Dados da cena (opcional)
+ * @returns {void}
+ */
+
+ setExecutionSpeed(speed) {
+ this.executionSpeed = Math.max(1, Math.min(100, speed));
+
+ if (typeof this.updateSpeedUI === "function") {
+ this.updateSpeedUI();
+ }
+ }
+
+ /**
+ * Ajusta a velocidade de execução (percentual) e atualiza UI.
+ * @param {number} speed - Valor entre 1 e 100 representando a velocidade
+ * @returns {void}
+ */
+
+ updateSpeedUI() {
+ if (this.speedText) {
+ this.speedText.setText(`Velocidade: ${this.executionSpeed}%`);
+ }
+ if (this.sliderFill) {
+ this.sliderFill.setSize(
+ (this.executionSpeed / 100) * this.sliderWidth,
+ 12,
+ );
+ }
+ if (this.sliderHandle) {
+ this.sliderHandle.x =
+ (this.executionSpeed / 100 - 0.5) * this.sliderWidth;
+ }
+ }
+
+ createSpeedControl() {
+ const gameWidth = this.game.config.width;
+ const gameHeight = this.game.config.height;
+
+ this.sliderWidth = Math.min(gameWidth - 40, 300);
+ const controlHeight = 50;
+ const centerX = gameWidth / 2;
+ const bottomY = gameHeight - 35;
+
+ const controlsContainer = this.add.container(centerX, bottomY);
+
+ const bg = this.add.rectangle(
+ 0,
+ 0,
+ this.sliderWidth + 80,
+ controlHeight,
+ 0x000000,
+ 0.85,
+ );
+ bg.setOrigin(0.5, 0.5);
+ controlsContainer.add(bg);
+
+ const sliderBg = this.add.rectangle(0, 8, this.sliderWidth, 12, 0x444444);
+ controlsContainer.add(sliderBg);
+
+ this.sliderFill = this.add.rectangle(
+ -this.sliderWidth / 2,
+ 8,
+ (this.executionSpeed / 100) * this.sliderWidth,
+ 28,
+ 0x00ff88,
+ );
+ this.sliderFill.setOrigin(0, 0.5);
+ controlsContainer.add(this.sliderFill);
+
+ this.sliderHandle = this.add.circle(
+ (this.executionSpeed / 100 - 0.5) * this.sliderWidth,
+ 8,
+ 16,
+ 0xffffff,
+ );
+ this.sliderHandle.setInteractive({ useHandCursor: true });
+ this.sliderHandle.setStrokeStyle(2, 0x333333);
+ controlsContainer.add(this.sliderHandle);
+
+ this.speedText = this.add.text(
+ 0,
+ -12,
+ `Velocidade: ${this.executionSpeed}%`,
+ {
+ fontSize: "14px",
+ fill: "#ffffff",
+ fontFamily: "Arial",
+ },
+ );
+ this.speedText.setOrigin(0.5, 0.5);
+ controlsContainer.add(this.speedText);
+
+ let isDragging = false;
+
+ this.sliderHandle.on("pointerdown", () => {
+ isDragging = true;
+ });
+
+ this.input.on("pointermove", (pointer) => {
+ if (isDragging) {
+ const localX = pointer.x - centerX;
+ const clampedX = Math.max(
+ -this.sliderWidth / 2,
+ Math.min(this.sliderWidth / 2, localX),
+ );
+ const newSpeed = Math.round(
+ ((clampedX + this.sliderWidth / 2) / this.sliderWidth) * 100,
+ );
+ this.setExecutionSpeed(newSpeed);
+ }
+ });
+
+ this.input.on("pointerup", () => {
+ isDragging = false;
+ });
+
+ sliderBg.setInteractive();
+ sliderBg.on("pointerdown", (pointer) => {
+ const localX = pointer.x - centerX;
+ const clampedX = Math.max(
+ -this.sliderWidth / 2,
+ Math.min(this.sliderWidth / 2, localX),
+ );
+ const newSpeed = Math.round(
+ ((clampedX + this.sliderWidth / 2) / this.sliderWidth) * 100,
+ );
+ this.setExecutionSpeed(newSpeed);
+ });
+
+ controlsContainer.setDepth(9999);
+ }
+
+ /**
+ * Cria controles de UI para ajustar a velocidade de execução.
+ * @returns {void}
+ */
+
+ calcDuration() {
+ return 500 + ((1 - 500) / (100 - 1)) * (this.executionSpeed - 1);
+ }
+
+ /**
+ * Calcula a duração de animações baseado na velocidade de execução.
+ * @returns {number} Duração em milissegundos
+ */
+
+ resetTurtle() {
+ this.turtleState.x = this.game.config.width / 2;
+ this.turtleState.y = this.game.config.height / 2;
+ this.turtleState.angle = 0;
+ this.turtleState.penDown = true;
+ this.turtleState.penColour = 0xffffff;
+
+ if (this.turtleSprite) {
+ this.turtleSprite.setPosition(this.turtleState.x, this.turtleState.y);
+ this.turtleSprite.setAngle(0);
+ this.turtleSprite.stop();
+ this.turtleSprite.setTexture(ASSETS.IMG.TURTLE1);
+ }
+ }
+
+ /**
+ * Restaura a tartaruga para a posição/estado inicial do canvas.
+ * @returns {void}
+ */
+
+ pintarFundo(color) {
+ let colorValue;
+
+ if (typeof color === "string") {
+ if (color.startsWith("#")) {
+ colorValue = parseInt(color.substring(1), 16);
+ } else {
+ const colorMap = {
+ vermelho: 0xff0000,
+ azul: 0x0000ff,
+ verde: 0x00ff00,
+ amarelo: 0xffff00,
+ branco: 0xffffff,
+ preto: 0x000000,
+ };
+ colorValue = colorMap[color.toLowerCase()] || 0x000000;
+ }
+ } else {
+ colorValue = color;
+ }
+
+ if (this.backgroundRect) {
+ this.backgroundRect.destroy();
+ }
+
+ this.backgroundRect = this.add.rectangle(
+ this.game.config.width / 2,
+ this.game.config.height / 2,
+ this.game.config.width,
+ this.game.config.height,
+ colorValue,
+ );
+ this.backgroundRect.setDepth(-1000);
+ }
+
+ /**
+ * Pinta o fundo da cena com a cor especificada.
+ * Aceita valores numéricos ou nomes/hex strings.
+ * @param {string|number} color - Cor a ser aplicada
+ * @returns {void}
+ */
+
+ addColorToActiveGraphics() {
+ const colorArray =
+ this.activeGraphics === this.playerGraphics
+ ? this.playerColors
+ : this.solutionColors;
+
+ if (!colorArray.includes(this.turtleState.penColour)) {
+ colorArray.push(this.turtleState.penColour);
+ }
+ }
+
+ /**
+ * Adiciona a cor atual da caneta à lista de cores usadas no gráfico ativo.
+ * @returns {void}
+ */
+
+ _moveInstant(distance) {
+ const lastX = this.turtleState.x;
+ const lastY = this.turtleState.y;
+ const angleRad = Phaser.Math.DegToRad(this.turtleState.angle);
+
+ this.turtleState.x += Math.cos(angleRad) * distance;
+ this.turtleState.y -= Math.sin(angleRad) * distance;
+
+ if (this.turtleState.penDown && this.activeGraphics) {
+ this.activeGraphics.lineStyle(4, this.turtleState.penColour, 1);
+ this.activeGraphics.lineBetween(
+ lastX,
+ lastY,
+ this.turtleState.x,
+ this.turtleState.y,
+ );
+ this.addColorToActiveGraphics();
+ }
+ }
+
+ /**
+ * Move a tartaruga instantaneamente sem animação (utilitário interno).
+ * @param {number} distance - Distância a percorrer
+ * @returns {void}
+ */
+
+ _turnInstant(angle) {
+ this.turtleState.angle += angle;
+ this.turtleState.angle = this.turtleState.angle % 360;
+ if (this.turtleState.angle < 0) {
+ this.turtleState.angle += 360;
+ }
+ }
+
+ /**
+ * Rotaciona a tartaruga instantaneamente (utilitário interno).
+ * @param {number} angle - Ângulo em graus a ser somado
+ * @returns {void}
+ */
+
+ highlightBlock(id) {
+ if (this.workspace) this.workspace.highlightBlock(id);
+ this.highlightPause = true;
+ }
+
+ /**
+ * Destaca um bloco do workspace e pausa o destaque automático.
+ * @param {string} id - Id do bloco a destacar
+ * @returns {void}
+ */
+
+ move(distance) {
+ this.historico.push({ tipo: "move", distancia: distance });
+
+ return new Promise((resolve) => {
+ const duration = this.calcDuration();
+ const startX = this.turtleState.x;
+ const startY = this.turtleState.y;
+ const angleRad = Phaser.Math.DegToRad(this.turtleState.angle);
+ const targetX = this.turtleState.x + Math.cos(angleRad) * distance;
+ const targetY = this.turtleState.y - Math.sin(angleRad) * distance;
+
+ this.turtleState.x = targetX;
+ this.turtleState.y = targetY;
+
+ this.turtleSprite.play("turtle_walk");
+
+ this.tweens.add({
+ targets: this.turtleSprite,
+ x: targetX,
+ y: targetY,
+ duration: duration,
+ ease: "Linear",
+ onComplete: () => {
+ this.turtleSprite.stop();
+ this.turtleSprite.setTexture(ASSETS.IMG.TURTLE1);
+
+ if (this.turtleState.penDown && this.activeGraphics) {
+ this.activeGraphics.lineStyle(4, this.turtleState.penColour, 1);
+ this.activeGraphics.lineBetween(
+ startX,
+ startY,
+ this.turtleState.x,
+ this.turtleState.y,
+ );
+ this.addColorToActiveGraphics();
+ }
+
+ resolve();
+ },
+ });
+ });
+ }
+
+ /**
+ * Move a tartaruga com animação e desenha a linha se a caneta estiver para baixo.
+ * Retorna uma Promise que resolve quando a animação termina.
+ * @param {number} distance - Distância a percorrer
+ * @returns {Promise}
+ */
+
+ turn(angle) {
+ this.historico.push({ tipo: "turn", angulo: angle });
+
+ return new Promise((resolve) => {
+ const duration = this.calcDuration();
+ this._turnInstant(angle);
+
+ const currentSpriteAngle = this.turtleSprite.angle;
+ const targetSpriteAngle = -this.turtleState.angle;
+
+ let angleDiff = targetSpriteAngle - currentSpriteAngle;
+
+ while (angleDiff > 180) angleDiff -= 360;
+ while (angleDiff < -180) angleDiff += 360;
+
+ const finalAngle = currentSpriteAngle + angleDiff;
+
+ this.tweens.add({
+ targets: this.turtleSprite,
+ angle: finalAngle,
+ duration: duration,
+ ease: "Linear",
+ onComplete: () => {
+ resolve();
+ },
+ });
+ });
+ }
+
+ /**
+ * Rotaciona a tartaruga com animação e ajusta o sprite.
+ * Retorna uma Promise que resolve quando a rotação termina.
+ * @param {number} angle - Ângulo em graus
+ * @returns {Promise}
+ */
+
+ penDown(isDown) {
+ this.historico.push({ tipo: "penDown", valor: isDown });
+ this.turtleState.penDown = isDown;
+ }
+
+ /**
+ * Seta o estado da caneta (para desenhar ou não).
+ * @param {boolean} isDown - true para caneta para baixo
+ * @returns {void}
+ */
+
+ penColour(color) {
+ this.historico.push({ tipo: "penColour", cor: color });
+
+ if (typeof color === "string") {
+ if (color.startsWith("#")) {
+ this.turtleState.penColour = parseInt(color.substring(1), 16);
+ } else {
+ const colorMap = {
+ vermelho: 0xff0000,
+ azul: 0x0000ff,
+ verde: 0x00ff00,
+ amarelo: 0xffff00,
+ branco: 0xffffff,
+ preto: 0x000000,
+ };
+ this.turtleState.penColour = colorMap[color.toLowerCase()] || 0xffffff;
+ }
+ } else {
+ this.turtleState.penColour = color;
+ }
+
+ this.addColorToActiveGraphics();
+ }
+
+ /**
+ * Ajusta a cor da caneta; aceita nome, hex string ou valor numérico.
+ * @param {string|number} color - Cor a ser aplicada
+ * @returns {void}
+ */
+
+ extractPointsFromGraphics(graphics) {
+ const points = [];
+ let i = 0;
+
+ while (i < graphics.commandBuffer.length) {
+ const cmd = graphics.commandBuffer[i];
+ if (
+ cmd === 6 &&
+ i + 11 < graphics.commandBuffer.length &&
+ graphics.commandBuffer[i + 1] === 4 &&
+ graphics.commandBuffer[i + 5] === 5 &&
+ graphics.commandBuffer[i + 8] === 4 &&
+ graphics.commandBuffer[i + 11] === 9
+ ) {
+ const x1 = graphics.commandBuffer[i + 6];
+ const y1 = graphics.commandBuffer[i + 7];
+ const x2 = graphics.commandBuffer[i + 9];
+ const y2 = graphics.commandBuffer[i + 10];
+
+ points.push({ x: x1, y: y1 });
+ points.push({ x: x2, y: y2 });
+ i += 12;
+ } else {
+ i++;
+ }
+ }
+
+ return points;
+ }
+
+ /**
+ * Extrai pontos relevantes do buffer de comandos de um objeto Graphics.
+ * Usado para gerar assinaturas de desenho para comparação.
+ * @param {Phaser.GameObjects.Graphics} graphics
+ * @returns {Array<{x:number,y:number}>}
+ */
+
+ getDrawingSignature(graphics) {
+ if (!graphics.commandBuffer || graphics.commandBuffer.length === 0) {
+ return null;
+ }
+
+ const points = this.extractPointsFromGraphics(graphics);
+ if (points.length === 0) {
+ return null;
+ }
+
+ let sumX = 0,
+ sumY = 0;
+ let minX = Infinity,
+ minY = Infinity;
+ let maxX = -Infinity,
+ maxY = -Infinity;
+
+ points.forEach((p) => {
+ sumX += p.x;
+ sumY += p.y;
+ minX = Math.min(minX, p.x);
+ minY = Math.min(minY, p.y);
+ maxX = Math.max(maxX, p.x);
+ maxY = Math.max(maxY, p.y);
+ });
+
+ const pointCount = points.length;
+ const centroid = { x: sumX / pointCount, y: sumY / pointCount };
+ const normalizedCentroid = {
+ x: centroid.x - minX,
+ y: centroid.y - minY,
+ };
+
+ return {
+ bounds: { x: minX, y: minY, width: maxX - minX, height: maxY - minY },
+ centroid: normalizedCentroid,
+ };
+ }
+
+ /**
+ * Calcula uma assinatura geométrica (bounds e centróide) do desenho.
+ * Retorna `null` se não houver comandos suficientes.
+ * @param {Phaser.GameObjects.Graphics} graphics
+ * @returns {Object|null}
+ */
+
+ compareColors(playerColors, solutionColors) {
+ if (playerColors.length !== solutionColors.length) return false;
+
+ const sortedPlayer = [...playerColors].sort();
+ const sortedSolution = [...solutionColors].sort();
+
+ return sortedSolution.every(
+ (color, index) => sortedPlayer[index] === color,
+ );
+ }
+
+ /**
+ * Compara paletas de cores usadas pelo jogador e pela solução.
+ * @param {Array} playerColors - Cores usadas pelo jogador
+ * @param {Array} solutionColors - Cores esperadas na solução
+ * @returns {boolean} true se as paletas coincidirem
+ */
+
+ /**
+ * Calcula uma assinatura topológica do desenho (curvatura, pontoCount, sentido).
+ * Retorna null ou objeto com propriedades para comparação.
+ * @param {Phaser.GameObjects.Graphics} graphics
+ * @returns {Object|null}
+ */
+ getTopologicalSignature(graphics) {
+ if (!graphics.commandBuffer || graphics.commandBuffer.length === 0) {
+ return null;
+ }
+
+ const points = this.extractPointsFromGraphics(graphics);
+
+ if (points.length < 3) {
+ return { curvatureSum: 0, pointCount: points.length };
+ }
+
+ let curvatureSum = 0;
+ for (let j = 1; j < points.length - 1; j++) {
+ const p0 = points[j - 1];
+ const p1 = points[j];
+ const p2 = points[j + 1];
+
+ const v1 = { x: p1.x - p0.x, y: p1.y - p0.y };
+ const v2 = { x: p2.x - p1.x, y: p2.y - p1.y };
+
+ const crossProduct = v1.x * v2.y - v1.y * v2.x;
+ curvatureSum += crossProduct;
+ }
+
+ return {
+ curvatureSum: curvatureSum,
+ pointCount: points.length,
+ isClockwise: curvatureSum < 0,
+ absoluteCurvature: Math.abs(curvatureSum),
+ };
+ }
+
+ /**
+ * Compara o desenho do jogador com o desenho de referência.
+ * Leva em conta cores, dimensões, posição e topologia.
+ * @returns {boolean} true se os desenhos forem considerados equivalentes
+ */
+ compareDrawings() {
+ if (!this.compareColors(this.playerColors, this.solutionColors)) {
+ return false;
+ }
+
+ const playerSig = this.getDrawingSignature(this.playerGraphics);
+ const solutionSig = this.getDrawingSignature(this.validationGraphics);
+
+ if (!playerSig || !solutionSig) {
+ return false;
+ }
+
+ const pb = playerSig.bounds;
+ const sb = solutionSig.bounds;
+
+ const dimensionTolerance = Math.max(sb.width, sb.height, 1) * 0.05;
+ if (
+ Math.abs(pb.width - sb.width) > dimensionTolerance ||
+ Math.abs(pb.height - sb.height) > dimensionTolerance
+ ) {
+ return false;
+ }
+
+ const posX_diff = Math.abs(pb.x - sb.x);
+ const posY_diff = Math.abs(pb.y - sb.y);
+ if (posX_diff > 1.5 || posY_diff > 1.5) {
+ return false;
+ }
+
+ const centroidTolerance = 1.5;
+ const centroidDeltaX = Math.abs(
+ playerSig.centroid.x - solutionSig.centroid.x,
+ );
+ const centroidDeltaY = Math.abs(
+ playerSig.centroid.y - solutionSig.centroid.y,
+ );
+
+ if (
+ centroidDeltaX > centroidTolerance ||
+ centroidDeltaY > centroidTolerance
+ ) {
+ return false;
+ }
+
+ const playerTopo = this.getTopologicalSignature(this.playerGraphics);
+ const solutionTopo = this.getTopologicalSignature(this.validationGraphics);
+
+ if (!playerTopo || !solutionTopo) {
+ return false;
+ }
+
+ if (
+ playerTopo.isClockwise !== solutionTopo.isClockwise &&
+ Math.abs(playerTopo.absoluteCurvature) > 10
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Compara o desenho do jogador com o desenho de referência.
+ * Leva em conta cores, dimensões, posição e topologia.
+ * @returns {boolean} true se os desenhos forem considerados equivalentes
+ */
+
+ /**
+ * Desenha a solução esperada usando o código presente em `configFase.solutionCode`.
+ * Não lança erros para não interromper a cena se a solução estiver inválida.
+ * @returns {void}
+ */
+ drawSolution() {
+ const configFase = this.registry.get("configFase");
+ if (!configFase || !configFase.solutionCode) {
+ return;
+ }
+
+ this.solutionColors = [];
+ this.activeGraphics = this.solutionGraphics;
+ this.resetTurtle();
+ this.turtleState.penColour = 0xffffff;
+
+ const move = this._moveInstant.bind(this);
+ const turn = this._turnInstant.bind(this);
+ const penDown = this.penDown.bind(this);
+ const penColour = this.penColour.bind(this);
+
+ try {
+ const fn = new Function(
+ "move",
+ "turn",
+ "penDown",
+ "penColour",
+ configFase.solutionCode,
+ );
+ fn(move, turn, penDown, penColour);
+ } catch (e) {
+ // Erro silencioso - desenho de solução não é crítico
+ }
+ }
+
+ /**
+ * Gera o desenho de validação (internal) a partir de `configFase.solutionCode`.
+ * @returns {void}
+ */
+ generateValidationDrawing() {
+ const configFase = this.registry.get("configFase");
+ if (!configFase || !configFase.solutionCode) {
+ return;
+ }
+
+ this.validationGraphics.clear();
+ this.solutionColors = [];
+ this.activeGraphics = this.validationGraphics;
+ this.resetTurtle();
+ this.turtleState.penColour = 0xffffff;
+
+ const move = this._moveInstant.bind(this);
+ const turn = this._turnInstant.bind(this);
+ const penDown = this.penDown.bind(this);
+ const penColour = this.penColour.bind(this);
+
+ try {
+ const fn = new Function(
+ "move",
+ "turn",
+ "penDown",
+ "penColour",
+ configFase.solutionCode,
+ );
+ fn(move, turn, penDown, penColour);
+ } catch (e) {
+ // Erro silencioso - desenho de validação não é crítico
+ }
+ }
+
+ /**
+ * Gera o desenho de validação (usado para comparar com o desenho do jogador).
+ * @returns {void}
+ */
+
+ onBeforeRun() {
+ this.playerGraphics.clear();
+ this.activeGraphics = this.playerGraphics;
+ this.resetTurtle();
+ this.playerColors = [];
+ this.resultadoJogada = "em_andamento";
+ this.solutionGraphics.clear();
+ this.drawSolution();
+ this.generateValidationDrawing();
+ this.activeGraphics = this.playerGraphics;
+ this.resetTurtle();
+ }
+
+ /**
+ * Hook executado antes da execução do código do aluno; prepara gráficos e estado.
+ * @returns {void}
+ */
+
+ onReset() {
+ this.playerGraphics.clear();
+ this.solutionGraphics.clear();
+ this.resetTurtle();
+ this.activeGraphics = this.playerGraphics;
+ this.drawSolution();
+ this.playerColors = [];
+ this.solutionColors = [];
+ this.resultadoJogada = "em_andamento";
+ }
+
+ /**
+ * Hook executado quando o usuário reseta a cena; restaura estados visuais.
+ * @returns {void}
+ */
+
+ onSuccess() {
+ const configFase = this.registry.get("configFase");
+ if (configFase && configFase.id === 10) return;
+ this.playAudio(ASSETS.AUDIO.WIN);
+ }
+
+ /**
+ * Chamado quando a solução é considerada correta; executa som de vitória.
+ * @returns {void}
+ */
+
+ onFailure() {
+ this.playAudio(ASSETS.AUDIO.FAIL);
+ }
+
+ /**
+ * Chamado quando a solução é considerada incorreta; executa som de falha.
+ * @returns {void}
+ */
+
+ create() {
+ this.createSpeedControl();
+
+ this.anims.create({
+ key: "turtle_walk",
+ frames: [{ key: ASSETS.IMG.TURTLE1 }, { key: ASSETS.IMG.TURTLE2 }],
+ frameRate: 8,
+ repeat: -1,
+ });
+
+ this.solutionGraphics = this.add.graphics();
+ this.playerGraphics = this.add.graphics();
+ this.solutionGraphics.setAlpha(0.3);
+ this.validationGraphics = this.add.graphics().setVisible(false);
+ this.playerRT = this.add
+ .renderTexture(0, 0, CONSTANTES.LARGURA, CONSTANTES.ALTURA)
+ .setVisible(false);
+ this.validationRT = this.add
+ .renderTexture(0, 0, CONSTANTES.LARGURA, CONSTANTES.ALTURA)
+ .setVisible(false);
+ this.activeGraphics = this.playerGraphics;
+
+ this.turtleSprite = this.add.sprite(
+ CONSTANTES.LARGURA / 2,
+ CONSTANTES.ALTURA / 2,
+ ASSETS.IMG.TURTLE1,
+ );
+ this.turtleSprite.setOrigin(0.5, 0.5);
+ this.turtleSprite.setAngle(0);
+ this.turtleSprite.setDepth(1000);
+
+ this.gameInterpreter = new GameInterpreter({ stepDelay: 500 });
+ this.setExecutionSpeed(this.executionSpeed);
+ this.drawSolution();
+
+ this.setupStandardController(
+ setupTurtleAPI,
+ (history, config, gameConfig) =>
+ validateSolution(history, config, gameConfig, this),
+ );
+ }
+
+ /**
+ * Cria elementos iniciais da cena: sprites, gráficos, intérprete e controlador.
+ * @returns {void}
+ */
+}
+
+export const createGame = (
+ parentElement,
+ configFaseAtual,
+ customFailureHandler = null,
+ idFaseAtual = null,
+ gameConfig = null,
+) => {
+ const config =
+ idFaseAtual && gameConfig
+ ? gameConfig.fases[idFaseAtual - 1]
+ : configFaseAtual;
+
+ return {
+ type: Phaser.AUTO,
+ width: CONSTANTES.LARGURA,
+ height: CONSTANTES.ALTURA,
+ backgroundColor: "#242527",
+ antialias: true,
+ roundPixels: true,
+ pixelArt: false,
+ parent: parentElement,
+ scale: {
+ mode: Phaser.Scale.FIT,
+ autoCenter: Phaser.Scale.CENTER_BOTH,
+ zoom: 5,
+ },
+ scene: [TurtleScene],
+ callbacks: {
+ preBoot: (game) => {
+ game.registry.merge({
+ configFase: config,
+ gameConfig: gameConfig,
+ customFailureHandler: customFailureHandler,
+ stepDelay: 500,
+ });
+ },
+ },
+ };
+};
diff --git a/app/src/atividades/programacao/turtle/hooks/interpreterSetup.js b/app/src/atividades/programacao/turtle/hooks/interpreterSetup.js
new file mode 100644
index 0000000..e53b4ae
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/hooks/interpreterSetup.js
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Utility module for interpreterSetup.js
+ *
+ * @module games.turtle.hooks.interpreterSetup
+ */
+
+import { ApiHelpers } from "../../../../interpreters/ApiHelpers.js";
+
+/**
+ * Configura e registra as funções do intérprete para o jogo Tartaruga.
+ * @param {Object} scene - Referência à cena do Phaser (TurtleScene)
+ * @returns {function(interpreter: Object, globalScope: Object): void} Inicializador do interpretador
+ */
+export const setupTurtleAPI = (scene) => {
+ const getAnimationDelay = () => {
+ if (scene.executionSpeed >= 75) return 2;
+ if (scene.executionSpeed >= 50) return 4;
+ if (scene.executionSpeed >= 25) return 10;
+ return 10;
+ };
+
+ return (interpreter, globalScope) => {
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "move",
+ ApiHelpers.createActionWrapper(scene, "move", getAnimationDelay()),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "turn",
+ ApiHelpers.createActionWrapper(scene, "turn", getAnimationDelay()),
+ true,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "penDown",
+ ApiHelpers.createConditionWrapper(scene, "penDown"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "penColour",
+ ApiHelpers.createConditionWrapper(scene, "penColour"),
+ false,
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "pintarFundo",
+ ApiHelpers.createConditionWrapper(scene, "pintarFundo"),
+ );
+
+ ApiHelpers.registerFunction(
+ interpreter,
+ globalScope,
+ "highlightBlock",
+ ApiHelpers.createHighlightWrapper(scene),
+ false,
+ );
+ };
+};
+
+export const TURTLE_COMMANDS = {
+ ACTIONS: ["move", "turn"],
+ CONDITIONS: ["penDown", "penColour"],
+ SPECIAL: ["highlightBlock"],
+};
diff --git a/app/src/atividades/programacao/turtle/hooks/useTurtleTour.js b/app/src/atividades/programacao/turtle/hooks/useTurtleTour.js
new file mode 100644
index 0000000..5ddfa78
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/hooks/useTurtleTour.js
@@ -0,0 +1,17 @@
+/**
+ * @fileoverview Utility module for useTurtleTour.js
+ *
+ * @module games.turtle.hooks.useTurtleTour
+ */
+
+import { useGameTour } from "../../../../hooks/useGameTour";
+import { turtleTourSteps, turtleTourOptions } from "../config/tourSteps";
+
+/**
+ * Hook que inicia e controla o tour guiado do Turtle.
+ * Retorna a API do tour por delegação a `useGameTour`.
+ * @returns {Object} API do tour (ex.: startTour).
+ */
+export const useTurtleTour = () => {
+ return useGameTour("turtle", turtleTourSteps, turtleTourOptions);
+};
diff --git a/app/src/atividades/programacao/turtle/validation/validators.js b/app/src/atividades/programacao/turtle/validation/validators.js
new file mode 100644
index 0000000..1f5ffb2
--- /dev/null
+++ b/app/src/atividades/programacao/turtle/validation/validators.js
@@ -0,0 +1,132 @@
+/**
+ * @fileoverview Utility module for validators.js
+ *
+ * @module games.turtle.validation.validators
+ */
+
+import { BaseGameValidator } from "../../../../shared/BaseGameValidator";
+
+/**
+ * Validador específico do Turtle Game
+ *
+ * Diferente de outros jogos, o Turtle valida principalmente através de
+ * comparação visual de desenhos, não apenas por sequência de ações.
+ */
+class TurtleValidator extends BaseGameValidator {
+ /**
+ * Valida a solução do aluno baseado no desenho produzido
+ *
+ * @param {Array} history - Histórico de ações (para debug/estatísticas)
+ * @param {Object} config - Configuração da fase atual
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} sceneRef - Referência à cena Phaser (para acessar métodos de comparação)
+ * @returns {Object} { success: boolean, reason?: string }
+ */
+ validatePhase(history, config, gameConfig, sceneRef) {
+ // 1. Fail-Safe: Verificar se a configuração foi passada
+ if (!config || Object.keys(config).length === 0) {
+ return this.failure(
+ gameConfig?.mensagens?.erroGeral ||
+ "Erro técnico: Fase não configurada.",
+ );
+ }
+
+ // 2. Fase 10 (Desafio Livre) - Sempre sucesso
+ // Também aceita fases com requiredDrawingMatch = false
+ if (config.id === 10 || config.requiredDrawingMatch === false) {
+ return this.success();
+ }
+
+ // 3. Verificar se há referência à cena (necessária para comparação visual)
+ if (!sceneRef) {
+ return this.failure(
+ gameConfig?.mensagens?.erroGeral || "Erro técnico na validação.",
+ );
+ }
+
+ // 4. Fases 1-9: Validar desenho produzido
+ return this._validateDrawing(sceneRef, config, gameConfig);
+ }
+
+ /**
+ * Valida se o desenho do jogador corresponde à solução esperada
+ * Método privado chamado apenas por validatePhase() para fases 1-9
+ *
+ * @param {Object} scene - Referência à cena Phaser
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global
+ * @returns {Object} { success: boolean, reason?: string }
+ * @private
+ */
+ _validateDrawing(scene, config, gameConfig) {
+ try {
+ // 1. Verificar se o jogador desenhou algo
+ const hasPlayerDrawing = this._hasPlayerDrawing(scene);
+
+ if (!hasPlayerDrawing) {
+ return this.failure(
+ config?.mensagens?.semDesenho ||
+ gameConfig?.mensagens?.semDesenho ||
+ "Você não desenhou nada! Certifique-se de usar os comandos move() com a caneta abaixada.",
+ );
+ }
+
+ // 2. Comparar desenhos usando método da scene (compareDrawings)
+ // O método scene.compareDrawings() acessa validationGraphics e playerGraphics
+ // que já foram preparados no onBeforeRun() através de generateValidationDrawing()
+ const drawingsMatch = scene.compareDrawings();
+
+ if (!drawingsMatch) {
+ return this.failure(
+ config?.mensagens?.desenhoNaoConfere ||
+ gameConfig?.mensagens?.desenhoNaoConfere ||
+ "O desenho não está correto. Verifique a forma, posição e cores.",
+ );
+ }
+
+ // 3. Sucesso - desenho corresponde à solução!
+ return this.success();
+ } catch (error) {
+ return this.failure(
+ gameConfig?.mensagens?.erroGeral ||
+ "Erro inesperado durante a validação.",
+ );
+ }
+ }
+
+ /**
+ * Verifica se o jogador produziu algum desenho
+ * Checa se o commandBuffer do playerGraphics possui comandos de desenho
+ *
+ * @param {Object} scene - Referência à cena Phaser
+ * @returns {boolean} true se há desenho, false caso contrário
+ * @private
+ */
+ _hasPlayerDrawing(scene) {
+ return (
+ scene.playerGraphics &&
+ scene.playerGraphics.commandBuffer &&
+ scene.playerGraphics.commandBuffer.length > 0
+ );
+ }
+
+ // --- Métodos Herdados da Base ---
+ // success() - retorna { success: true }
+ // failure(reason) - retorna { success: false, reason }
+}
+
+// Singleton para reutilização
+const validatorInstance = new TurtleValidator();
+
+/**
+ * Função exportada para validação de soluções do Turtle Game
+ *
+ * @param {Array} history - Histórico de ações
+ * @param {Object} config - Configuração da fase
+ * @param {Object} gameConfig - Configuração global
+ * @param {Object} sceneRef - Referência à cena (necessário para comparação visual)
+ * @returns {Object} { success: boolean, reason?: string }
+ */
+export const validateSolution = (history, config, gameConfig, sceneRef) => {
+ return validatorInstance.validatePhase(history, config, gameConfig, sceneRef);
+};
diff --git a/app/src/blockly/__tests__/generator.test.js b/app/src/blockly/__tests__/generator.test.js
new file mode 100644
index 0000000..d9ef8a9
--- /dev/null
+++ b/app/src/blockly/__tests__/generator.test.js
@@ -0,0 +1,146 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+
+// ---------------------------------------------------------------------------
+// vi.hoisted garante que mockGenerator e inicializado antes do vi.mock ser
+// executado (vi.mock e hoisted para o topo do arquivo pelo compilador).
+// ---------------------------------------------------------------------------
+const { mockForBlock, mockGenerator } = vi.hoisted(() => {
+ const mockForBlock = {};
+ const mockGenerator = {
+ forBlock: mockForBlock,
+ STATEMENT_PREFIX: "",
+ ORDER_ATOMIC: 0,
+ ORDER_ASSIGNMENT: 3,
+ ORDER_FUNCTION_CALL: 2,
+ addReservedWords: vi.fn(),
+ valueToCode: vi.fn(() => "42"),
+ };
+ return { mockForBlock, mockGenerator };
+});
+
+vi.mock("blockly/javascript", () => ({
+ javascriptGenerator: mockGenerator,
+}));
+
+import {
+ configurarGerador,
+ gerarExpressao,
+ gerarStatement,
+ gerarStatementInline,
+ gerarStatementComCampo,
+ gerarExpressaoComCampo,
+ gerarStatementComValor,
+} from "../generator";
+
+beforeEach(() => {
+ Object.keys(mockForBlock).forEach((k) => delete mockForBlock[k]);
+ mockGenerator.STATEMENT_PREFIX = "";
+ vi.clearAllMocks();
+});
+
+describe("configurarGerador()", () => {
+ it("define STATEMENT_PREFIX no gerador padrao", () => {
+ configurarGerador();
+ expect(mockGenerator.STATEMENT_PREFIX).toBe("highlightBlock(%1);\n");
+ });
+
+ it("adiciona 'highlightBlock' as palavras reservadas", () => {
+ configurarGerador();
+ expect(mockGenerator.addReservedWords).toHaveBeenCalledWith("highlightBlock");
+ });
+
+ it("aceita gerador customizado", () => {
+ const custom = { STATEMENT_PREFIX: "", addReservedWords: vi.fn() };
+ configurarGerador(custom);
+ expect(custom.STATEMENT_PREFIX).toBe("highlightBlock(%1);\n");
+ expect(custom.addReservedWords).toHaveBeenCalledWith("highlightBlock");
+ });
+});
+
+describe("gerarExpressao()", () => {
+ it("registra forBlock que retorna [codigo, ORDER_ATOMIC] por default", () => {
+ gerarExpressao("bloco_var", "passos");
+ const result = mockForBlock["bloco_var"]();
+ expect(result).toEqual(["passos", mockGenerator.ORDER_ATOMIC]);
+ });
+
+ it("usa precedencia customizada quando fornecida", () => {
+ gerarExpressao("bloco_fn", "fn()", mockGenerator.ORDER_FUNCTION_CALL);
+ const result = mockForBlock["bloco_fn"]();
+ expect(result[1]).toBe(mockGenerator.ORDER_FUNCTION_CALL);
+ });
+
+ it("registra no blockId correto", () => {
+ gerarExpressao("meu_bloco", "x");
+ expect(mockForBlock["meu_bloco"]).toBeDefined();
+ });
+});
+
+describe("gerarStatement()", () => {
+ it("registra forBlock que gera 'funcao();\\n'", () => {
+ gerarStatement("robo_mover", "mover");
+ expect(mockForBlock["robo_mover"]()).toBe("mover();\n");
+ });
+
+ it("usa o nome da funcao fornecido", () => {
+ gerarStatement("robo_virar", "virar");
+ expect(mockForBlock["robo_virar"]()).toBe("virar();\n");
+ });
+});
+
+describe("gerarStatementInline()", () => {
+ it("registra forBlock que gera 'codigo;\\n'", () => {
+ gerarStatementInline("robo_change", "passos = passos + 1");
+ expect(mockForBlock["robo_change"]()).toBe("passos = passos + 1;\n");
+ });
+});
+
+describe("gerarStatementComCampo()", () => {
+ it("registra forBlock que usa getFieldValue para gerar codigo", () => {
+ gerarStatementComCampo("robo_virar", "virar", "DIRECAO");
+ const fakeBlock = { getFieldValue: vi.fn(() => "DIREITA") };
+ const code = mockForBlock["robo_virar"](fakeBlock);
+ expect(fakeBlock.getFieldValue).toHaveBeenCalledWith("DIRECAO");
+ expect(code).toBe("virar('DIREITA');\n");
+ });
+});
+
+describe("gerarExpressaoComCampo()", () => {
+ it("registra forBlock que retorna [codigo, precedencia]", () => {
+ gerarExpressaoComCampo(
+ "robo_sensor",
+ "caminhoBloqueado",
+ "SENTIDO",
+ mockGenerator.ORDER_FUNCTION_CALL
+ );
+ const fakeBlock = { getFieldValue: vi.fn(() => "FRENTE") };
+ const result = mockForBlock["robo_sensor"](fakeBlock);
+ expect(fakeBlock.getFieldValue).toHaveBeenCalledWith("SENTIDO");
+ expect(result).toEqual([
+ "caminhoBloqueado('FRENTE')",
+ mockGenerator.ORDER_FUNCTION_CALL,
+ ]);
+ });
+});
+
+describe("gerarStatementComValor()", () => {
+ it("registra forBlock que chama valueToCode e aplica template", () => {
+ gerarStatementComValor("robo_set", "VALOR", (v) => `var passos = ${v}`);
+ const fakeBlock = {};
+ mockGenerator.valueToCode.mockReturnValueOnce("10");
+ const code = mockForBlock["robo_set"](fakeBlock);
+ expect(mockGenerator.valueToCode).toHaveBeenCalledWith(
+ fakeBlock,
+ "VALOR",
+ mockGenerator.ORDER_ASSIGNMENT
+ );
+ expect(code).toBe("var passos = 10;\n");
+ });
+
+ it("usa '0' como fallback quando valueToCode retorna string vazia", () => {
+ gerarStatementComValor("robo_set2", "VALOR", (v) => `x = ${v}`);
+ mockGenerator.valueToCode.mockReturnValueOnce("");
+ const code = mockForBlock["robo_set2"]({});
+ expect(code).toBe("x = 0;\n");
+ });
+});
diff --git a/app/src/blockly/__tests__/toolbox.test.js b/app/src/blockly/__tests__/toolbox.test.js
new file mode 100644
index 0000000..2072219
--- /dev/null
+++ b/app/src/blockly/__tests__/toolbox.test.js
@@ -0,0 +1,194 @@
+import { describe, it, expect, vi } from "vitest";
+import {
+ criarToolboxBase,
+ criarCategoria,
+ filtrarCategoriasVazias,
+ adicionarBlocoCategoria,
+ criarDefinicaoBloco,
+ validarBloco,
+ gerarToolboxDeEstrutura,
+} from "../toolbox";
+
+vi.mock("@/blockly/blocklyColors", () => ({
+ obterCorCategoria: vi.fn(() => 210),
+}));
+
+describe("criarToolboxBase()", () => {
+ it("retorna objeto com kind 'categoryToolbox' e contents vazio", () => {
+ const tb = criarToolboxBase();
+ expect(tb).toEqual({ kind: "categoryToolbox", contents: [] });
+ });
+
+ it("cada chamada retorna um novo objeto", () => {
+ expect(criarToolboxBase()).not.toBe(criarToolboxBase());
+ });
+});
+
+describe("criarCategoria()", () => {
+ it("retorna objeto com kind 'category' e contents vazio", () => {
+ const cat = criarCategoria("Movimento");
+ expect(cat.kind).toBe("category");
+ expect(cat.name).toBe("Movimento");
+ expect(cat.contents).toEqual([]);
+ });
+
+ it("usa cor fornecida quando especificada", () => {
+ const cat = criarCategoria("X", 180);
+ expect(cat.colour).toBe(180);
+ });
+
+ it("usa obterCorCategoria quando cor e null", () => {
+ const cat = criarCategoria("Logica");
+ expect(cat.colour).toBe(210);
+ });
+
+ it("adiciona cssConfig quando cssContainer e fornecido", () => {
+ const cat = criarCategoria("X", null, "cat_x");
+ expect(cat.cssConfig).toEqual({ container: "cat_x" });
+ });
+
+ it("nao adiciona cssConfig quando cssContainer e omitido", () => {
+ const cat = criarCategoria("X");
+ expect(cat.cssConfig).toBeUndefined();
+ });
+});
+
+describe("filtrarCategoriasVazias()", () => {
+ it("remove categorias sem blocos", () => {
+ const tb = {
+ contents: [
+ { kind: "category", contents: [] },
+ { kind: "category", contents: [{ kind: "block" }] },
+ ],
+ };
+ filtrarCategoriasVazias(tb);
+ expect(tb.contents).toHaveLength(1);
+ });
+
+ it("remove categorias sem propriedade contents", () => {
+ const tb = { contents: [{ kind: "category" }, { kind: "category", contents: [{}] }] };
+ filtrarCategoriasVazias(tb);
+ expect(tb.contents).toHaveLength(1);
+ });
+
+ it("mantem toolbox com todas as categorias preenchidas", () => {
+ const tb = {
+ contents: [
+ { contents: [{ kind: "block" }] },
+ { contents: [{ kind: "block" }] },
+ ],
+ };
+ filtrarCategoriasVazias(tb);
+ expect(tb.contents).toHaveLength(2);
+ });
+
+ it("muta e retorna o mesmo objeto toolbox", () => {
+ const tb = { contents: [] };
+ expect(filtrarCategoriasVazias(tb)).toBe(tb);
+ });
+});
+
+describe("adicionarBlocoCategoria()", () => {
+ it("adiciona um bloco a categoria pelo indice", () => {
+ const tb = criarToolboxBase();
+ tb.contents.push(criarCategoria("A"));
+ const bloco = { kind: "block", type: "mover" };
+ adicionarBlocoCategoria(tb, 0, bloco);
+ expect(tb.contents[0].contents).toHaveLength(1);
+ expect(tb.contents[0].contents[0]).toBe(bloco);
+ });
+
+ it("nao faz nada quando categoryIndex esta fora do range", () => {
+ const tb = criarToolboxBase();
+ expect(() => adicionarBlocoCategoria(tb, 99, { kind: "block" })).not.toThrow();
+ });
+
+ it("nao faz nada quando blockDef e falsy", () => {
+ const tb = criarToolboxBase();
+ tb.contents.push(criarCategoria("A"));
+ adicionarBlocoCategoria(tb, 0, null);
+ expect(tb.contents[0].contents).toHaveLength(0);
+ });
+
+ it("nao faz nada quando categoryIndex e undefined", () => {
+ const tb = criarToolboxBase();
+ tb.contents.push(criarCategoria("A"));
+ adicionarBlocoCategoria(tb, undefined, { kind: "block" });
+ expect(tb.contents[0].contents).toHaveLength(0);
+ });
+});
+
+describe("criarDefinicaoBloco()", () => {
+ it("retorna objeto com kind 'block' e type correto", () => {
+ expect(criarDefinicaoBloco("mover")).toEqual({ kind: "block", type: "mover" });
+ });
+
+ it("nao inclui inputs quando nao fornecido", () => {
+ expect(criarDefinicaoBloco("x").inputs).toBeUndefined();
+ });
+
+ it("inclui inputs quando fornecidos", () => {
+ const inputs = { FROM: { shadow: { type: "math_number" } } };
+ expect(criarDefinicaoBloco("x", inputs).inputs).toBe(inputs);
+ });
+});
+
+describe("validarBloco()", () => {
+ it("retorna definicao quando bloco existe", () => {
+ const defs = { mover: { code: "move()" } };
+ expect(validarBloco("mover", defs)).toEqual({ code: "move()" });
+ });
+
+ it("retorna null quando bloco nao existe", () => {
+ expect(validarBloco("inexistente", {})).toBeNull();
+ });
+
+ it("emite console.warn quando bloco nao existe", () => {
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ validarBloco("x", {});
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining('"x"'));
+ spy.mockRestore();
+ });
+});
+
+describe("gerarToolboxDeEstrutura()", () => {
+ const estrutura = [
+ { nome: "Logica", blocos: ["robo_if", "robo_if_else"] },
+ { nome: "Movimento", cor: 210, blocos: ["robo_mover", "robo_virar"] },
+ ];
+
+ it("retorna toolbox com apenas blocos permitidos", () => {
+ const tb = gerarToolboxDeEstrutura(estrutura, ["robo_if", "robo_mover"]);
+ expect(tb.kind).toBe("categoryToolbox");
+ expect(tb.contents).toHaveLength(2);
+ expect(tb.contents[0].contents[0].type).toBe("robo_if");
+ expect(tb.contents[1].contents[0].type).toBe("robo_mover");
+ });
+
+ it("omite categorias onde nenhum bloco e permitido", () => {
+ const tb = gerarToolboxDeEstrutura(estrutura, ["robo_mover"]);
+ expect(tb.contents).toHaveLength(1);
+ expect(tb.contents[0].name).toBe("Movimento");
+ });
+
+ it("retorna toolbox vazia quando allowedBlocks esta vazio", () => {
+ const tb = gerarToolboxDeEstrutura(estrutura, []);
+ expect(tb.contents).toHaveLength(0);
+ });
+
+ it("inclui todos os blocos permitidos de uma categoria", () => {
+ const tb = gerarToolboxDeEstrutura(estrutura, ["robo_mover", "robo_virar"]);
+ expect(tb.contents[0].contents).toHaveLength(2);
+ });
+
+ it("cada bloco na toolbox tem kind 'block'", () => {
+ const tb = gerarToolboxDeEstrutura(estrutura, ["robo_if"]);
+ expect(tb.contents[0].contents[0].kind).toBe("block");
+ });
+
+ it("passa cssContainer quando definido na estrutura", () => {
+ const est = [{ nome: "Cat", cssContainer: "cat_css", blocos: ["b1"] }];
+ const tb = gerarToolboxDeEstrutura(est, ["b1"]);
+ expect(tb.contents[0].cssConfig).toEqual({ container: "cat_css" });
+ });
+});
diff --git a/app/src/blockly/__tests__/validation.test.js b/app/src/blockly/__tests__/validation.test.js
new file mode 100644
index 0000000..130168d
--- /dev/null
+++ b/app/src/blockly/__tests__/validation.test.js
@@ -0,0 +1,195 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import {
+ BlocklyWorkspaceValidator,
+ defaultValidator,
+ validateBlocklyWorkspace,
+} from "../validation";
+
+const makeBlock = (type, overrides = {}) => ({
+ type,
+ disposed: false,
+ setDisabledReason: vi.fn(),
+ ...overrides,
+});
+
+const makeWorkspace = (blocks) => ({
+ getTopBlocks: vi.fn(() => blocks),
+});
+
+describe("BlocklyWorkspaceValidator — construtor", () => {
+ it("usa defaults quando chamado sem opcoes", () => {
+ const v = new BlocklyWorkspaceValidator();
+ expect(v.options.allowMultipleTopBlocks).toBe(false);
+ expect(v.options.preferredStartBlocks).toEqual(["start", "when_run", "main"]);
+ });
+
+ it("mescla opcoes fornecidas sobre os defaults", () => {
+ const v = new BlocklyWorkspaceValidator({ allowMultipleTopBlocks: true });
+ expect(v.options.allowMultipleTopBlocks).toBe(true);
+ });
+
+ it("aceita preferredStartBlocks customizado", () => {
+ const v = new BlocklyWorkspaceValidator({ preferredStartBlocks: ["begin"] });
+ expect(v.options.preferredStartBlocks).toEqual(["begin"]);
+ });
+});
+
+describe("BlocklyWorkspaceValidator — validateAndClean()", () => {
+ it("retorna isValid:false quando workspace e null", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const r = v.validateAndClean(null);
+ expect(r.isValid).toBe(false);
+ expect(r.warnings).toContain("Workspace não disponível");
+ });
+
+ it("retorna isValid:false quando nao ha blocos", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const r = v.validateAndClean(makeWorkspace([]));
+ expect(r.isValid).toBe(false);
+ expect(r.warnings[0]).toMatch(/nenhum bloco/i);
+ });
+
+ it("retorna isValid:true com um unico bloco", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const block = makeBlock("mover");
+ const r = v.validateAndClean(makeWorkspace([block]));
+ expect(r.isValid).toBe(true);
+ expect(r.warnings).toHaveLength(0);
+ expect(r.mainBlock).toBe(block);
+ expect(r.reEnableBlocks).toBeNull();
+ });
+
+ it("desabilita blocos extras quando ha multiplos top-blocks", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const b1 = makeBlock("start");
+ const b2 = makeBlock("outro");
+ const r = v.validateAndClean(makeWorkspace([b1, b2]));
+ expect(r.isValid).toBe(true);
+ expect(r.mainBlock).toBe(b1);
+ expect(b2.setDisabledReason).toHaveBeenCalledWith(true, "MANUALLY_DISABLED");
+ expect(r.disabledCount).toBe(1);
+ expect(typeof r.reEnableBlocks).toBe("function");
+ });
+
+ it("re-habilita blocos ao chamar reEnableBlocks()", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const b1 = makeBlock("start");
+ const b2 = makeBlock("outro");
+ const r = v.validateAndClean(makeWorkspace([b1, b2]));
+ r.reEnableBlocks();
+ expect(b2.setDisabledReason).toHaveBeenCalledWith(false);
+ });
+
+ it("nao desabilita blocos quando allowMultipleTopBlocks:true", () => {
+ const v = new BlocklyWorkspaceValidator({ allowMultipleTopBlocks: true });
+ const b1 = makeBlock("start");
+ const b2 = makeBlock("outro");
+ const r = v.validateAndClean(makeWorkspace([b1, b2]));
+ expect(r.isValid).toBe(true);
+ expect(b2.setDisabledReason).not.toHaveBeenCalled();
+ expect(r.reEnableBlocks).toBeNull();
+ });
+
+ it("emite warning quando ha multiplos programas", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const r = v.validateAndClean(makeWorkspace([makeBlock("a"), makeBlock("b")]));
+ expect(r.warnings.some((w) => /2 programas/i.test(w))).toBe(true);
+ });
+});
+
+describe("BlocklyWorkspaceValidator — selectMainBlock()", () => {
+ it("prefere bloco do tipo 'start'", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const blocks = [makeBlock("outro"), makeBlock("start"), makeBlock("when_run")];
+ expect(v.selectMainBlock(blocks).type).toBe("start");
+ });
+
+ it("prefere 'when_run' quando nao ha 'start'", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const blocks = [makeBlock("outro"), makeBlock("when_run")];
+ expect(v.selectMainBlock(blocks).type).toBe("when_run");
+ });
+
+ it("retorna primeiro bloco quando nenhum preferido existe", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const blocks = [makeBlock("primeiro"), makeBlock("segundo")];
+ expect(v.selectMainBlock(blocks).type).toBe("primeiro");
+ });
+
+ it("ignora blocos disposed", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const blocks = [makeBlock("start", { disposed: true }), makeBlock("outro")];
+ expect(v.selectMainBlock(blocks).type).toBe("outro");
+ });
+
+ it("retorna null quando todos os blocos estao disposed", () => {
+ const v = new BlocklyWorkspaceValidator();
+ const blocks = [makeBlock("a", { disposed: true })];
+ expect(v.selectMainBlock(blocks)).toBeNull();
+ });
+
+ it("aceita tipo parcial (inclui preferredStartBlock no nome)", () => {
+ const v = new BlocklyWorkspaceValidator({ preferredStartBlocks: ["main"] });
+ const blocks = [makeBlock("game_main"), makeBlock("outro")];
+ expect(v.selectMainBlock(blocks).type).toBe("game_main");
+ });
+});
+
+describe("BlocklyWorkspaceValidator — isBlockControllable()", () => {
+ it("retorna true para bloco com setDisabledReason e nao disposed", () => {
+ const v = new BlocklyWorkspaceValidator();
+ expect(v.isBlockControllable(makeBlock("x"))).toBe(true);
+ });
+
+ it("retorna false para null", () => {
+ const v = new BlocklyWorkspaceValidator();
+ expect(v.isBlockControllable(null)).toBeFalsy();
+ });
+
+ it("retorna false para bloco disposed", () => {
+ const v = new BlocklyWorkspaceValidator();
+ expect(v.isBlockControllable(makeBlock("x", { disposed: true }))).toBe(false);
+ });
+
+ it("retorna false quando falta setDisabledReason", () => {
+ const v = new BlocklyWorkspaceValidator();
+ expect(v.isBlockControllable({ type: "x", disposed: false })).toBe(false);
+ });
+});
+
+describe("defaultValidator", () => {
+ it("e instancia de BlocklyWorkspaceValidator", () => {
+ expect(defaultValidator).toBeInstanceOf(BlocklyWorkspaceValidator);
+ });
+
+ it("tem allowMultipleTopBlocks:false", () => {
+ expect(defaultValidator.options.allowMultipleTopBlocks).toBe(false);
+ });
+
+ it("inclui 'begin' nos preferredStartBlocks", () => {
+ expect(defaultValidator.options.preferredStartBlocks).toContain("begin");
+ });
+});
+
+describe("validateBlocklyWorkspace()", () => {
+ it("valida workspace com um bloco", () => {
+ const block = makeBlock("start");
+ const r = validateBlocklyWorkspace(makeWorkspace([block]));
+ expect(r.isValid).toBe(true);
+ expect(r.mainBlock).toBe(block);
+ });
+
+ it("retorna isValid:false para workspace null", () => {
+ const r = validateBlocklyWorkspace(null);
+ expect(r.isValid).toBe(false);
+ });
+
+ it("passa options para o validador", () => {
+ const b1 = makeBlock("a");
+ const b2 = makeBlock("b");
+ const r = validateBlocklyWorkspace(makeWorkspace([b1, b2]), {
+ allowMultipleTopBlocks: true,
+ });
+ expect(b2.setDisabledReason).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/src/blockly/blockFactory.js b/app/src/blockly/blockFactory.js
new file mode 100644
index 0000000..993e7b3
--- /dev/null
+++ b/app/src/blockly/blockFactory.js
@@ -0,0 +1,212 @@
+/**
+ * @fileoverview Factories para criação declarativa de blocos Blockly
+ * @module blockly.blockFactory
+ */
+
+import * as Blockly from "blockly/core";
+
+/**
+ * Cria um bloco statement simples sem inputs
+ * @param {string} tipo - ID único do bloco
+ * @param {string} texto - Texto exibido no bloco
+ * @param {number} cor - HSV hue (0-360) ou código de cor
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoStatementSimples("robo_mover", "mover para FRENTE", 210);
+ */
+export const criarBlocoStatementSimples = (tipo, texto, cor, tooltip = "") => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ this.appendDummyInput().appendField(texto);
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco statement com dropdown
+ * @param {string} tipo - ID único do bloco
+ * @param {string} textoAntes - Texto antes do dropdown
+ * @param {Array<[string, string]>} opcoes - Opções do dropdown [[label, value], ...]
+ * @param {string} nomeCampo - Nome do campo para recuperar valor
+ * @param {number} cor - HSV hue ou código de cor
+ * @param {string} [textoDepois] - Texto após o dropdown (opcional)
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoStatementComDropdown("robo_virar", "virar para a",
+ * [["DIREITA ↻", "DIREITA"], ["ESQUERDA ↺", "ESQUERDA"]], "DIRECAO", 210);
+ */
+export const criarBlocoStatementComDropdown = (
+ tipo,
+ textoAntes,
+ opcoes,
+ nomeCampo,
+ cor,
+ textoDepois = "",
+ tooltip = ""
+) => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ const input = this.appendDummyInput().appendField(textoAntes);
+ input.appendField(new Blockly.FieldDropdown(opcoes), nomeCampo);
+ if (textoDepois) input.appendField(textoDepois);
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco statement com value input
+ * @param {string} tipo - ID único do bloco
+ * @param {string} textoAntes - Texto antes do input
+ * @param {string} nomeInput - Nome do input de valor
+ * @param {string|null} tipoCheck - Tipo esperado ("Number", "Boolean", null)
+ * @param {number} cor - HSV hue ou código de cor
+ * @param {string} [textoDepois] - Texto após o input (opcional)
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoStatementComValor("robo_passos_set", "definir PASSOS para",
+ * "VALOR", "Number", 330);
+ */
+export const criarBlocoStatementComValor = (
+ tipo,
+ textoAntes,
+ nomeInput,
+ tipoCheck,
+ cor,
+ textoDepois = "",
+ tooltip = ""
+) => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ const input = this.appendValueInput(nomeInput).setCheck(tipoCheck);
+ input.appendField(textoAntes);
+ if (textoDepois) input.appendField(textoDepois);
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco expressão simples (retorna valor)
+ * @param {string} tipo - ID único do bloco
+ * @param {string} texto - Texto exibido no bloco
+ * @param {string|null} tipoOutput - Tipo de saída ("Number", "Boolean", null)
+ * @param {number} cor - HSV hue ou código de cor
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoExpressaoSimples("robo_passos_get", "PASSOS", "Number", 330);
+ */
+export const criarBlocoExpressaoSimples = (tipo, texto, tipoOutput, cor, tooltip = "") => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ this.appendDummyInput().appendField(texto);
+ this.setOutput(true, tipoOutput);
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco expressão com dropdown
+ * @param {string} tipo - ID único do bloco
+ * @param {string} textoAntes - Texto antes do dropdown
+ * @param {Array<[string, string]>} opcoes - Opções do dropdown
+ * @param {string} nomeCampo - Nome do campo
+ * @param {string|null} tipoOutput - Tipo de saída
+ * @param {number} cor - HSV hue ou código de cor
+ * @param {string} [textoDepois] - Texto após o dropdown (opcional)
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoExpressaoComDropdown("robo_bloqueado", "caminho bloqueado à",
+ * [["FRENTE", "FRENTE"], ["DIREITA", "DIREITA"]], "SENTIDO", "Boolean", 270);
+ */
+export const criarBlocoExpressaoComDropdown = (
+ tipo,
+ textoAntes,
+ opcoes,
+ nomeCampo,
+ tipoOutput,
+ cor,
+ textoDepois = "",
+ tooltip = ""
+) => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ const input = this.appendDummyInput().appendField(textoAntes);
+ input.appendField(new Blockly.FieldDropdown(opcoes), nomeCampo);
+ if (textoDepois) input.appendField(textoDepois);
+ this.setOutput(true, tipoOutput);
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco com value input e statement input (como if/else)
+ * @param {string} tipo - ID único do bloco
+ * @param {Object} config - Configuração do bloco
+ * @param {string} config.textoCondicao - Label do value input
+ * @param {Array<{nome: string, texto: string}>} config.statements - Statements [{nome: "FACA", texto: "faça"}, ...]
+ * @param {number} config.cor - Cor do bloco
+ * @param {string} [config.tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoCondicional("robo_if", {
+ * textoCondicao: "se",
+ * statements: [{nome: "FACA", texto: "faça"}],
+ * cor: 210
+ * });
+ */
+export const criarBlocoCondicional = (tipo, config) => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ this.appendValueInput("CONDICAO")
+ .setCheck("Boolean")
+ .appendField(config.textoCondicao);
+
+ config.statements.forEach(stmt => {
+ this.appendStatementInput(stmt.nome)
+ .setCheck(null)
+ .appendField(stmt.texto);
+ });
+
+ this.setPreviousStatement(true, null);
+ this.setNextStatement(true, null);
+ this.setColour(config.cor);
+ if (config.tooltip) this.setTooltip(config.tooltip);
+ }
+ };
+};
+
+/**
+ * Cria um bloco de negação (operador unário)
+ * @param {string} tipo - ID único do bloco
+ * @param {string} texto - Texto do operador (ex: "não")
+ * @param {number} cor - Cor do bloco
+ * @param {string} [tooltip] - Texto de ajuda (opcional)
+ * @example
+ * criarBlocoNegacao("robo_not", "não", 210, "Inverte o resultado do sensor");
+ */
+export const criarBlocoNegacao = (tipo, texto, cor, tooltip = "") => {
+ Blockly.Blocks[tipo] = {
+ init: function () {
+ this.appendValueInput("BOOL")
+ .setCheck("Boolean")
+ .appendField(texto);
+ this.setOutput(true, "Boolean");
+ this.setColour(cor);
+ if (tooltip) this.setTooltip(tooltip);
+ }
+ };
+};
diff --git a/app/src/blockly/blocklyColors.js b/app/src/blockly/blocklyColors.js
new file mode 100644
index 0000000..0b0e33c
--- /dev/null
+++ b/app/src/blockly/blocklyColors.js
@@ -0,0 +1,86 @@
+/**
+ * @fileoverview Constantes de cores para blocos Blockly
+ * @module constants.blocklyColors
+ */
+
+/**
+ * Cores oficiais das categorias padrão do Blockly.
+ * Baseado em: https://developers.google.com/blockly/guides/create-custom-blocks/define-blocks
+ */
+export const CORES_BLOCKLY = {
+ LOGICA: 210,
+ LOOPS: 120,
+ MATEMATICA: 230,
+ TEXTO: 160,
+ LISTAS: 260,
+ COR: 20,
+ VARIAVEIS: 330,
+};
+
+/**
+ * Cores para categorias customizadas comuns nos jogos.
+ */
+export const CORES_CUSTOMIZADAS = {
+ MOVIMENTO: 210, // Mesma cor de Lógica
+ SENSORES: 270,
+ TEMPO: 290,
+ MIDIA: 180,
+ CANETA: 180,
+ CONTROLE: 120, // Mesma cor de Loops
+ CONDICIONAL: 210, // Mesma cor de Lógica
+};
+
+/**
+ * Mapeamento de nomes de categorias para cores.
+ * Prioriza cores padrão do Blockly quando o nome corresponde.
+ */
+export const MAPA_CORES_CATEGORIAS = {
+ // Categorias padrão Blockly (PT-BR)
+ "Lógica": CORES_BLOCKLY.LOGICA,
+ "Repetição": CORES_BLOCKLY.LOOPS,
+ "Matemática": CORES_BLOCKLY.MATEMATICA,
+ "Texto": CORES_BLOCKLY.TEXTO,
+ "Listas": CORES_BLOCKLY.LISTAS,
+ "Cor": CORES_BLOCKLY.COR,
+ "Variáveis": CORES_BLOCKLY.VARIAVEIS,
+
+ // Categorias padrão Blockly (EN)
+ "Logic": CORES_BLOCKLY.LOGICA,
+ "Loops": CORES_BLOCKLY.LOOPS,
+ "Math": CORES_BLOCKLY.MATEMATICA,
+ "Text": CORES_BLOCKLY.TEXTO,
+ "Lists": CORES_BLOCKLY.LISTAS,
+ "Colour": CORES_BLOCKLY.COR,
+ "Variables": CORES_BLOCKLY.VARIAVEIS,
+
+ // Categorias customizadas
+ "Movimento": CORES_CUSTOMIZADAS.MOVIMENTO,
+ "Sensores": CORES_CUSTOMIZADAS.SENSORES,
+ "Tempo": CORES_CUSTOMIZADAS.TEMPO,
+ "Multimídia": CORES_CUSTOMIZADAS.MIDIA,
+ "Caneta": CORES_CUSTOMIZADAS.CANETA,
+ "Controle": CORES_CUSTOMIZADAS.CONTROLE,
+ "Condicionais": CORES_CUSTOMIZADAS.CONDICIONAL,
+ "Entrada/Saída": CORES_BLOCKLY.VARIAVEIS,
+};
+
+/**
+ * Retorna a cor padrão para uma categoria.
+ * @param {string} nomeCategoria - Nome da categoria
+ * @returns {number} Cor HSV
+ */
+export const obterCorCategoria = (nomeCategoria) => {
+ return MAPA_CORES_CATEGORIAS[nomeCategoria] || CORES_BLOCKLY.LOGICA;
+};
+
+// Compatibilidade com exports antigos (deprecated)
+/** @deprecated Use CORES_BLOCKLY.LOGICA */
+export const COR_LOGICA = CORES_BLOCKLY.LOGICA;
+/** @deprecated Use CORES_BLOCKLY.MATEMATICA */
+export const COR_MATEMATICA = CORES_BLOCKLY.MATEMATICA;
+/** @deprecated Use CORES_BLOCKLY.TEXTO */
+export const COR_TEXTO = CORES_BLOCKLY.TEXTO;
+/** @deprecated Use CORES_BLOCKLY.LOOPS */
+export const COR_REPETICAO = CORES_BLOCKLY.LOOPS;
+/** @deprecated Use CORES_BLOCKLY.VARIAVEIS */
+export const COR_VARIAVEIS = CORES_BLOCKLY.VARIAVEIS;
\ No newline at end of file
diff --git a/app/src/blockly/blocklyConfig.js b/app/src/blockly/blocklyConfig.js
new file mode 100644
index 0000000..2568288
--- /dev/null
+++ b/app/src/blockly/blocklyConfig.js
@@ -0,0 +1,12 @@
+/**
+ * Configuração global do Blockly
+ *
+ * Este arquivo configura o locale do Blockly para português brasileiro.
+ * IMPORTANTE: Deve ser importado ANTES de qualquer arquivo que use 'blockly/blocks'
+ * para evitar conflitos de definição de blocos.
+ */
+import * as Blockly from "blockly/core";
+import * as ptBr from "blockly/msg/pt-br";
+
+// Configurar locale globalmente
+Blockly.setLocale(ptBr);
diff --git a/app/src/blockly/fieldAngle.js b/app/src/blockly/fieldAngle.js
new file mode 100644
index 0000000..f58884e
--- /dev/null
+++ b/app/src/blockly/fieldAngle.js
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Utility module for fieldAngleLoader.js
+ *
+ * @module blockly.fieldAngle
+ */
+
+import * as Blockly from "blockly/core";
+
+/**
+ * Intercepta e cachea registro do CSS do Blockly para evitar injeção duplicada.
+ * Problema: @blockly/field-angle tenta injetar CSS toda vez que é importado.
+ * Solução: Cachear conteúdo CSS e pular re-registro.
+ *
+ * @private
+ * @type {Set} Set de conteúdo CSS já registrado
+ */
+const cssAlreadyInjected = new Set();
+
+/**
+ * Referência à implementação original de registro de CSS do Blockly.
+ * Usada para delegar comportamento após checagem de cache.
+ *
+ * @private
+ * @type {Function}
+ */
+const originalRegister = Blockly.Css.register;
+
+/**
+ * Substitui `Blockly.Css.register` para evitar registro duplicado de CSS.
+ * Concatena arrays em string, verifica cache e delega para a implementação
+ * original quando necessário. Em caso de erro pré-existente de injeção, ignora.
+ *
+ * @private
+ * @param {string|Array} content - Conteúdo CSS para registrar
+ * @returns {void}
+ */
+Blockly.Css.register = function (content) {
+ const contentStr = Array.isArray(content) ? content.join("") : content;
+
+ if (cssAlreadyInjected.has(contentStr)) {
+ return;
+ }
+
+ cssAlreadyInjected.add(contentStr);
+
+ try {
+ originalRegister.call(Blockly.Css, content);
+ } catch (error) {
+ // Alguns ambientes lançam erro quando CSS já foi injetado; ignoramos
+ if (!error.message?.includes("CSS already injected")) {
+ throw error;
+ }
+ }
+};
+
+/**
+ * Re-export seguro de `FieldAngle` do pacote `@blockly/field-angle`.
+ * Deve ser importado através deste módulo para evitar problemas de CSS injection
+ * múltipla.
+ *
+ * @exports FieldAngle
+ */
+export { FieldAngle } from "@blockly/field-angle";
diff --git a/app/src/blockly/generator.js b/app/src/blockly/generator.js
new file mode 100644
index 0000000..3ef7120
--- /dev/null
+++ b/app/src/blockly/generator.js
@@ -0,0 +1,140 @@
+/**
+ * @fileoverview Helpers para configuração de geradores Blockly
+ * @module blockly.generator
+ */
+
+import { javascriptGenerator } from "blockly/javascript";
+
+/**
+ * Configura o gerador JavaScript com prefixo de highlight.
+ * Deve ser chamado no início de `defineGenerators()` em cada jogo.
+ *
+ * @param {Object} [generator=javascriptGenerator] - Instância do gerador
+ * @returns {void}
+ *
+ * @example
+ * const defineGenerators = () => {
+ * configurarGerador();
+ * // ... resto dos geradores
+ * };
+ */
+export const configurarGerador = (generator = javascriptGenerator) => {
+ generator.STATEMENT_PREFIX = "highlightBlock(%1);\n";
+ generator.addReservedWords("highlightBlock");
+};
+
+/**
+ * Cria gerador para expressão (retorna valor).
+ * Usa ORDER_ATOMIC por padrão, ideal para variáveis e constantes.
+ * Use ORDER_FUNCTION_CALL para chamadas de função.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} codigo - Código JavaScript a retornar
+ * @param {number} [precedencia] - Precedência da expressão (opcional)
+ * @returns {void}
+ *
+ * @example
+ * // Variável
+ * gerarExpressao("robo_passos_get", "passos");
+ *
+ * // Função
+ * gerarExpressao("robo_sensor", "aindaTemSujeira()", javascriptGenerator.ORDER_FUNCTION_CALL);
+ */
+export const gerarExpressao = (blockId, codigo, precedencia = null) => {
+ const order = precedencia ?? javascriptGenerator.ORDER_ATOMIC;
+ javascriptGenerator.forBlock[blockId] = () => [codigo, order];
+};
+
+/**
+ * Cria gerador para statement (comando sem retorno).
+ * Gera código do tipo: funcao();\n
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} funcao - Nome da função JavaScript
+ * @returns {void}
+ *
+ * @example
+ * gerarStatement("robo_mover", "mover");
+ * // Gera: mover();\n
+ */
+export const gerarStatement = (blockId, funcao) => {
+ javascriptGenerator.forBlock[blockId] = () => `${funcao}();\n`;
+};
+
+/**
+ * Cria gerador para statement com código inline.
+ * Útil para operações simples sem função externa.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} codigo - Código JavaScript completo
+ * @returns {void}
+ *
+ * @example
+ * gerarStatementInline("robo_passos_change", "passos = passos + 1");
+ * // Gera: passos = passos + 1;\n
+ */
+export const gerarStatementInline = (blockId, codigo) => {
+ javascriptGenerator.forBlock[blockId] = () => `${codigo};\n`;
+};
+
+/**
+ * Cria gerador para statement que usa valor de campo dropdown.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} funcao - Nome da função a chamar
+ * @param {string} nomeCampo - Nome do campo dropdown
+ * @returns {void}
+ *
+ * @example
+ * gerarStatementComCampo("robo_virar", "virar", "DIRECAO");
+ * // Gera: virar('DIREITA');\n
+ */
+export const gerarStatementComCampo = (blockId, funcao, nomeCampo) => {
+ javascriptGenerator.forBlock[blockId] = function (block) {
+ return `${funcao}('${block.getFieldValue(nomeCampo)}');\n`;
+ };
+};
+
+/**
+ * Cria gerador para expressão que usa valor de campo dropdown.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} funcao - Nome da função a chamar
+ * @param {string} nomeCampo - Nome do campo dropdown
+ * @param {number} precedencia - Precedência do operador
+ * @returns {void}
+ *
+ * @example
+ * gerarExpressaoComCampo("robo_bloqueado", "caminhoBloqueado", "SENTIDO",
+ * javascriptGenerator.ORDER_FUNCTION_CALL);
+ * // Gera: ['caminhoBloqueado(\'FRENTE\')', ORDER_FUNCTION_CALL]
+ */
+export const gerarExpressaoComCampo = (blockId, funcao, nomeCampo, precedencia) => {
+ javascriptGenerator.forBlock[blockId] = function (block) {
+ return [`${funcao}('${block.getFieldValue(nomeCampo)}')`, precedencia];
+ };
+};
+
+/**
+ * Cria gerador para statement que usa valor de value input.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {string} nomeInput - Nome do value input
+ * @param {Function} template - Função que recebe o valor e retorna código
+ * @returns {void}
+ *
+ * @example
+ * gerarStatementComValor("robo_passos_set", "VALOR",
+ * (valor) => `var passos = ${valor}`);
+ * // Gera: var passos = 42;\n
+ */
+export const gerarStatementComValor = (blockId, nomeInput, template) => {
+ javascriptGenerator.forBlock[blockId] = function (block) {
+ const valor = javascriptGenerator.valueToCode(
+ block,
+ nomeInput,
+ javascriptGenerator.ORDER_ASSIGNMENT
+ ) || "0";
+ return `${template(valor)};\n`;
+ };
+};
\ No newline at end of file
diff --git a/app/src/blockly/mobileToolbox.js b/app/src/blockly/mobileToolbox.js
new file mode 100644
index 0000000..72a5149
--- /dev/null
+++ b/app/src/blockly/mobileToolbox.js
@@ -0,0 +1,195 @@
+/**
+ * Configura ícones da toolbox do Blockly para mobile.
+ * Ajusta tamanho e visibilidade de ícones em viewports pequenas (< 480px).
+ * Usa múltiplas tentativas com retry para esperar renderização do Blockly.
+ *
+ * @function configureMobileToolboxIcons
+ * @param {Blockly.Workspace} workspace - Workspace do Blockly
+ * @returns {void}
+ *
+ * Comportamento:
+ * - Faz nada se viewport > 480px (desktop)
+ * - Tenta até 5 vezes reconectando com delay de 200ms
+ * - Busca elementos da toolbox com múltiplos seletores fallback
+ * - Ajusta CSS inline para ícones ficarem visíveis em mobile
+ *
+ * @internal
+ */
+export function configureMobileToolboxIcons(workspace) {
+ if (!workspace || window.innerWidth > 480) return;
+
+ const tryConfigureIcons = (attempt = 1) => {
+ // Tentar diferentes seletores para encontrar as categorias
+ const possibleSelectors = [
+ ".blocklyToolboxCategory",
+ ".blocklyTreeRow",
+ '[role="treeitem"]',
+ ".blocklyToolboxContents > div",
+ ".blocklyToolbox .blocklyTreeRow",
+ ];
+
+ let categories = [];
+ for (const selector of possibleSelectors) {
+ categories = document.querySelectorAll(selector);
+ if (categories.length > 0) {
+ break;
+ }
+ }
+
+ if (categories.length === 0) {
+ // Tentar novamente até 5 vezes
+ if (attempt < 5) {
+ setTimeout(() => tryConfigureIcons(attempt + 1), 200);
+ }
+ return;
+ }
+
+ categories.forEach((categoryElement, index) => {
+ try {
+ // Tentar diferentes seletores para encontrar os elementos internos
+ const possibleIconSelectors = [
+ ".blocklyToolboxCategoryIcon",
+ ".blocklyTreeIcon",
+ ".blocklyTreeRowIcon",
+ 'span[class*="Icon"]',
+ ".fa",
+ "i",
+ ];
+
+ const possibleLabelSelectors = [
+ ".blocklyToolboxCategoryLabel",
+ ".blocklyTreeLabel",
+ ".blocklyTreeRowLabel",
+ 'span[class*="Label"]',
+ 'span:not([class*="Icon"])',
+ ];
+
+ let iconElement = null;
+ let labelElement = null;
+
+ // Procurar ícone
+ for (const selector of possibleIconSelectors) {
+ iconElement = categoryElement.querySelector(selector);
+ if (iconElement) {
+ break;
+ }
+ }
+
+ // Procurar label
+ for (const selector of possibleLabelSelectors) {
+ labelElement = categoryElement.querySelector(selector);
+ if (labelElement && labelElement.textContent.trim()) {
+ break;
+ }
+ }
+
+ // Se não encontrou, criar elementos
+ if (!iconElement) {
+ iconElement = document.createElement("i");
+ iconElement.className = "blocklyToolboxCategoryIcon";
+ categoryElement.insertBefore(iconElement, categoryElement.firstChild);
+ }
+
+ if (!labelElement) {
+ labelElement = categoryElement.querySelector("*");
+ if (!labelElement || !labelElement.textContent.trim()) {
+ return;
+ }
+ }
+
+ const categoryName = labelElement.textContent.trim().toLowerCase();
+
+ // Mapeamento de ícones
+ const iconMap = {
+ // Movimento (com e sem emojis)
+ "🤖 movimento": "fas fa-running",
+ movimento: "fas fa-running",
+ movimentos: "fas fa-running",
+ mover: "fas fa-running",
+
+ // Repetição/Loops
+ "🔄 repetição": "fas fa-sync-alt",
+ repetição: "fas fa-sync-alt",
+ repeticao: "fas fa-sync-alt",
+ loops: "fas fa-sync-alt",
+ loop: "fas fa-sync-alt",
+
+ // Lógica
+ "🤔 lógica": "fas fa-brain",
+ lógica: "fas fa-brain",
+ logica: "fas fa-brain",
+ logic: "fas fa-brain",
+
+ // Outros
+ matemática: "fas fa-calculator",
+ matematica: "fas fa-calculator",
+ math: "fas fa-calculator",
+ texto: "fas fa-font",
+ text: "fas fa-font",
+ variáveis: "fas fa-box",
+ variaveis: "fas fa-box",
+ variables: "fas fa-box",
+ funções: "fas fa-cog",
+ funcoes: "fas fa-cog",
+ functions: "fas fa-cog",
+ controle: "fas fa-gamepad",
+ control: "fas fa-gamepad",
+ eventos: "fas fa-bolt",
+ events: "fas fa-bolt",
+ };
+
+ const iconClass = iconMap[categoryName] || "fas fa-puzzle-piece";
+
+ // Aplicar classe do ícone
+ iconElement.className = `blocklyToolboxCategoryIcon ${iconClass}`;
+
+ // Aplicar estilos diretamente no elemento
+ const iconStyles = {
+ display: "flex",
+ "align-items": "center",
+ "justify-content": "center",
+ width: "32px",
+ height: "32px",
+ "font-family": '"Font Awesome 6 Free", "FontAwesome", sans-serif',
+ "font-weight": "900",
+ "font-size": "20px",
+ color: "#C41E3A",
+ margin: "0 auto",
+ "text-rendering": "auto",
+ "-webkit-font-smoothing": "antialiased",
+ position: "relative",
+ "z-index": "50",
+ };
+
+ Object.entries(iconStyles).forEach(([prop, value]) => {
+ iconElement.style.setProperty(prop, value, "important");
+ });
+
+ // Ocultar o texto em mobile
+ labelElement.style.setProperty("display", "none", "important");
+
+ // Garantir que a categoria seja clicável
+ categoryElement.style.setProperty(
+ "pointer-events",
+ "auto",
+ "important",
+ );
+ categoryElement.style.setProperty("cursor", "pointer", "important");
+ } catch (error) {
+ // Silently ignore per-category configuration errors
+ }
+ });
+ };
+
+ // Começar tentativas
+ tryConfigureIcons();
+
+ // Reconfigurar quando houver mudanças no workspace
+ if (workspace.addChangeListener) {
+ workspace.addChangeListener((event) => {
+ if (event.type === "selected" && window.innerWidth <= 480) {
+ setTimeout(() => tryConfigureIcons(), 100);
+ }
+ });
+ }
+}
diff --git a/app/src/blockly/toolbox.js b/app/src/blockly/toolbox.js
new file mode 100644
index 0000000..b6e3a13
--- /dev/null
+++ b/app/src/blockly/toolbox.js
@@ -0,0 +1,161 @@
+/**
+ * @fileoverview Helpers para criação e manipulação de toolbox Blockly
+ * @module blockly.toolbox
+ */
+
+import { obterCorCategoria } from "@/blockly/blocklyColors";
+
+/**
+ * Cria a estrutura base de uma toolbox vazia.
+ * @returns {Object} Estrutura categoryToolbox
+ *
+ * @example
+ * const toolbox = criarToolboxBase();
+ */
+export const criarToolboxBase = () => ({
+ kind: "categoryToolbox",
+ contents: [],
+});
+
+/**
+ * Cria uma categoria para a toolbox.
+ * Se a cor não for fornecida, usa a cor padrão baseada no nome.
+ *
+ * @param {string} nome - Nome da categoria
+ * @param {number|string} [cor] - Cor HSV ou hex (opcional)
+ * @param {string} [cssContainer] - Nome do container CSS (opcional)
+ * @returns {Object} Categoria do Blockly
+ *
+ * @example
+ * // Usa cor padrão (210)
+ * const logica = criarCategoria("Lógica", null, "cat_logica");
+ *
+ * // Usa cor customizada
+ * const custom = criarCategoria("Custom", 180, "cat_custom");
+ */
+export const criarCategoria = (nome, cor = null, cssContainer = null) => ({
+ kind: "category",
+ name: nome,
+ colour: cor ?? obterCorCategoria(nome),
+ contents: [],
+ ...(cssContainer && { cssConfig: { container: cssContainer } }),
+});
+
+/**
+ * Remove categorias vazias da toolbox.
+ *
+ * @param {Object} toolbox - Objeto toolbox
+ * @returns {Object} Toolbox filtrada
+ *
+ * @example
+ * const toolbox = criarToolboxBase();
+ * // ... adicionar categorias
+ * return filtrarCategoriasVazias(toolbox);
+ */
+export const filtrarCategoriasVazias = (toolbox) => {
+ toolbox.contents = toolbox.contents.filter(
+ (cat) => cat.contents && cat.contents.length > 0
+ );
+ return toolbox;
+};
+
+/**
+ * Adiciona um bloco a uma categoria específica.
+ *
+ * @param {Object} toolbox - Objeto toolbox
+ * @param {number} categoryIndex - Índice da categoria
+ * @param {Object} blockDef - Definição do bloco
+ * @returns {void}
+ *
+ * @example
+ * adicionarBlocoCategoria(toolbox, 0, { kind: "block", type: "robo_if" });
+ */
+export const adicionarBlocoCategoria = (toolbox, categoryIndex, blockDef) => {
+ if (
+ categoryIndex !== undefined &&
+ toolbox.contents[categoryIndex] &&
+ blockDef
+ ) {
+ if (!toolbox.contents[categoryIndex].contents) {
+ toolbox.contents[categoryIndex].contents = [];
+ }
+ toolbox.contents[categoryIndex].contents.push(blockDef);
+ }
+};
+
+/**
+ * Cria a definição de um bloco para toolbox.
+ *
+ * @param {string} tipo - Tipo do bloco
+ * @param {Object} [inputs] - Inputs shadow opcionais
+ * @returns {Object} Definição do bloco
+ *
+ * @example
+ * const bloco = criarDefinicaoBloco("math_number");
+ * const blocoComInputs = criarDefinicaoBloco("math_random_int", {
+ * FROM: { shadow: { type: "math_number", fields: { NUM: 1 } } }
+ * });
+ */
+export const criarDefinicaoBloco = (tipo, inputs = null) => ({
+ kind: "block",
+ type: tipo,
+ ...(inputs && { inputs }),
+});
+
+/**
+ * Valida se um bloco existe nas definições.
+ *
+ * @param {string} blockId - ID do bloco
+ * @param {Object} blockDefinitions - Objeto com definições
+ * @returns {Object|null} Definição do bloco ou null
+ *
+ * @example
+ * const blockDef = validarBloco("robo_if", blockDefinitions);
+ * if (blockDef) {
+ * // usar blockDef
+ * }
+ */
+export const validarBloco = (blockId, blockDefinitions) => {
+ const blockDef = blockDefinitions[blockId];
+ if (!blockDef) {
+ console.warn(`[Blockly] Bloco "${blockId}" não encontrado`);
+ return null;
+ }
+ return blockDef;
+};
+
+/**
+ * Gera toolbox completa a partir de estrutura declarativa.
+ * Elimina necessidade de categoryMap e lógica manual de distribuição.
+ *
+ * @param {Array} estrutura - Array de {nome, cor?, cssContainer?, blocos[]}
+ * @param {Array} allowedBlocks - Blocos permitidos nesta fase
+ * @returns {Object} Toolbox do Blockly
+ *
+ * @example
+ * const toolbox = gerarToolboxDeEstrutura([
+ * { nome: "Lógica", cssContainer: "cat_logica", blocos: ["robo_if", "robo_if_else"] },
+ * { nome: "Movimento", cor: 210, blocos: ["robo_mover"] }
+ * ], ["robo_if", "robo_mover"]);
+ */
+export const gerarToolboxDeEstrutura = (estrutura, allowedBlocks) => {
+ const toolbox = criarToolboxBase();
+
+ estrutura.forEach(categoria => {
+ const cat = criarCategoria(categoria.nome, categoria.cor, categoria.cssContainer);
+
+ // Adiciona apenas blocos permitidos desta categoria
+ categoria.blocos
+ .filter(blockId => allowedBlocks.includes(blockId))
+ .forEach(blockId => {
+ cat.contents.push(criarDefinicaoBloco(blockId));
+ });
+
+ // Só adiciona categoria se tiver blocos
+ if (cat.contents.length > 0) {
+ toolbox.contents.push(cat);
+ }
+ });
+
+ return toolbox;
+};
\ No newline at end of file
diff --git a/app/src/blockly/validation.js b/app/src/blockly/validation.js
new file mode 100644
index 0000000..d9897e9
--- /dev/null
+++ b/app/src/blockly/validation.js
@@ -0,0 +1,198 @@
+/**
+ * Validador para workspace do Blockly antes de execução.
+ * Verifica blocos, gerencia múltiplos top-blocks e oferece feedback.
+ *
+ * Responsabilidades:
+ * - Validar presença de blocos
+ * - Detectar e informar sobre múltiplos programas
+ * - Desabilitar blocos que não devem ser executados
+ * - Marcar o bloco principal para execução
+ *
+ * @module blockly/validation
+ * @class BlocklyWorkspaceValidator
+ * @param {Object} [options={}] - Opções de validação
+ * @param {boolean} [options.allowMultipleTopBlocks=false] - Permite múltiplos blocos independentes
+ * @param {Array} [options.preferredStartBlocks=['start','when_run','main']] - Tipos de bloco preferidos como ponto de inicio
+ *
+ * @example
+ * const validator = new BlocklyWorkspaceValidator({ allowMultipleTopBlocks: false });
+ * const result = validator.validateAndClean(workspace);
+ * if (!result.isValid) {
+ * console.log('Problemas:', result.warnings);
+ * }
+ */
+export class BlocklyWorkspaceValidator {
+ /**
+ * Inicializa o validador.
+ *
+ * @constructor
+ * @param {Object} [options={}] - Opções de validação
+ */
+ constructor(options = {}) {
+ this.options = {
+ allowMultipleTopBlocks: false,
+ preferredStartBlocks: ["start", "when_run", "main"],
+ ...options,
+ };
+ }
+
+ /**
+ * Valida e limpa a workspace antes da execução
+ * @param {Blockly.Workspace} workspace - A workspace do Blockly
+ * @returns {{isValid:boolean, warnings:Array, mainBlock?:Object, reEnableBlocks?:function|null}} Resultado da validação
+ */
+ validateAndClean(workspace) {
+ if (!workspace) {
+ return { isValid: false, warnings: ["Workspace não disponível"] };
+ }
+
+ const topBlocks = workspace.getTopBlocks(false);
+ const warnings = [];
+
+ if (topBlocks.length === 0) {
+ return {
+ isValid: false,
+ warnings: ["Nenhum bloco encontrado na workspace"],
+ };
+ }
+
+ if (topBlocks.length === 1) {
+ return {
+ isValid: true,
+ warnings: [],
+ mainBlock: topBlocks[0],
+ reEnableBlocks: null, // Não há blocos para reabilitar
+ };
+ }
+
+ warnings.push(
+ `Encontrados ${topBlocks.length} programas separados. Apenas um será executado.`,
+ );
+
+ if (!this.options.allowMultipleTopBlocks) {
+ const result = this.handleMultipleTopBlocks(topBlocks, warnings);
+ return result;
+ }
+
+ return {
+ isValid: true,
+ warnings,
+ mainBlock: topBlocks[0],
+ reEnableBlocks: null,
+ };
+ }
+
+ /**
+ * Verifica se um bloco tem o método setDisabledReason
+ * @param {Object} block - Bloco do Blockly
+ * @returns {boolean} Se o bloco é válido para controle
+ */
+ isBlockControllable(block) {
+ return (
+ block && typeof block.setDisabledReason === "function" && !block.disposed
+ );
+ }
+
+ /**
+ * Lida com múltiplos blocos top-level
+ * @param {Array} topBlocks - Array de blocos top-level
+ * @param {Array} warnings - Array de warnings acumulados
+ * @returns {Object} Resultado do processamento
+ */
+ handleMultipleTopBlocks(topBlocks, warnings) {
+ const mainBlock = this.selectMainBlock(topBlocks);
+
+ if (mainBlock) {
+ warnings.push(`Programa principal selecionado: "${mainBlock.type}"`);
+ }
+
+ // Desabilitar todos os outros blocos top-level (diferentes do mainBlock)
+ const disabledBlocks = [];
+ const reEnableCallbacks = [];
+
+ topBlocks.forEach((block, _index) => {
+ // Pular o bloco principal
+ if (block === mainBlock) {
+ return;
+ }
+
+ if (!this.isBlockControllable(block)) {
+ return;
+ }
+
+ try {
+ block.setDisabledReason(true, "MANUALLY_DISABLED");
+ disabledBlocks.push(block);
+ warnings.push(`Programa "${block.type}" desabilitado temporariamente`);
+
+ reEnableCallbacks.push(() => {
+ try {
+ if (this.isBlockControllable(block)) {
+ block.setDisabledReason(false);
+ }
+ } catch {
+ // Falhou ao reabilitar, mas não precisamos fazer nada
+ }
+ });
+ } catch {
+ // Falhou ao desabilitar, mas não precisamos fazer nada
+ }
+ });
+
+ const reEnableAllBlocks = () => {
+ if (reEnableCallbacks.length > 0) {
+ reEnableCallbacks.forEach((callback) => callback());
+ }
+ };
+
+ return {
+ isValid: true,
+ warnings,
+ mainBlock,
+ disabledBlocks,
+ disabledCount: disabledBlocks.length,
+ reEnableBlocks: disabledBlocks.length > 0 ? reEnableAllBlocks : null,
+ };
+ }
+
+ /**
+ * Seleciona o bloco principal entre múltiplos blocos top-level
+ * @param {Array} topBlocks - Array de blocos top-level
+ * @returns {Object} Bloco principal selecionado
+ */
+ selectMainBlock(topBlocks) {
+ const validBlocks = topBlocks.filter((block) => block && !block.disposed);
+
+ if (validBlocks.length === 0) {
+ return null;
+ }
+
+ for (const preferredType of this.options.preferredStartBlocks) {
+ const startBlock = validBlocks.find(
+ (block) =>
+ block.type === preferredType || block.type.includes(preferredType),
+ );
+ if (startBlock) {
+ return startBlock;
+ }
+ }
+
+ return validBlocks[0];
+ }
+}
+
+export const defaultValidator = new BlocklyWorkspaceValidator({
+ allowMultipleTopBlocks: false,
+ preferredStartBlocks: ["start", "when_run", "main", "begin"],
+});
+
+/**
+ * Função de conveniência para validação rápida
+ * @param {Blockly.Workspace} workspace - A workspace do Blockly
+ * @param {Object} options - Opções de validação
+ * @returns {Object} Resultado da validação
+ */
+export function validateBlocklyWorkspace(workspace, options = {}) {
+ const validator = new BlocklyWorkspaceValidator(options);
+ return validator.validateAndClean(workspace);
+}
diff --git a/app/src/components/DesktopOnlyTapume.jsx b/app/src/components/DesktopOnlyTapume.jsx
new file mode 100644
index 0000000..d0e26b7
--- /dev/null
+++ b/app/src/components/DesktopOnlyTapume.jsx
@@ -0,0 +1,58 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { useNavigate } from "react-router-dom";
+import { MonitorOff, Construction, House } from "lucide-react";
+
+export default function DesktopOnlyTapume({
+ title = "Área disponível apenas no desktop",
+ message = "Esta experiência foi preparada para telas maiores. Acesse em um computador para aproveitar todos os recursos.",
+ homePath = "/",
+ homeLabel = "Voltar para Home",
+}) {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Uso em desktop
+
+
+
+
{title}
+
+ {message}
+
+
+
+ Dica: em telas maiores, esta área libera editor e interações completas.
+
+
+
navigate(homePath)}
+ className="mt-6 inline-flex items-center gap-2 rounded-full bg-[var(--action-green)] text-black px-5 py-3 font-semibold shadow-lg transition-transform duration-200 hover:scale-[1.02]"
+ >
+
+ {homeLabel}
+
+
+
+
+ );
+}
+
+DesktopOnlyTapume.propTypes = {
+ title: PropTypes.string,
+ message: PropTypes.string,
+ homePath: PropTypes.string,
+ homeLabel: PropTypes.string,
+};
diff --git a/app/src/components/Navbar.jsx b/app/src/components/Navbar.jsx
new file mode 100644
index 0000000..6ae6b8e
--- /dev/null
+++ b/app/src/components/Navbar.jsx
@@ -0,0 +1,240 @@
+/**
+ * @fileoverview React component for Navbar.jsx
+ *
+ * @module components.Navbar
+ */
+
+import { isSession, Link } from "react-router-dom";
+import { Menu, X, ChevronDown } from "lucide-react";
+import { useState, useEffect } from "react";
+import logo from "../assets/logo_decoda.svg";
+
+const Navbar = () => {
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+ const [scrolled, setScrolled] = useState(false);
+ const [openDropdown, setOpenDropdown] = useState(null);
+ const [closeTimeout, setCloseTimeout] = useState(null);
+ const isOffline = import.meta.env.VITE_IS_OFFLINE === "true";
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setScrolled(window.scrollY > 50);
+ };
+
+ window.addEventListener("scroll", handleScroll, { passive: true });
+ handleScroll(); // Check initial state
+
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ const handleMouseEnter = (dropdown) => {
+ if (closeTimeout) {
+ clearTimeout(closeTimeout);
+ setCloseTimeout(null);
+ }
+ setOpenDropdown(dropdown);
+ };
+
+ const handleMouseLeave = () => {
+ const timeout = setTimeout(() => {
+ setOpenDropdown(null);
+ }, 150);
+ setCloseTimeout(timeout);
+ };
+
+ return (
+
+
+ {/* Logo - Esquerda */}
+
+
+
+
+
+
+ {/* Espaço Central vazio para empurrar o menu para a direita */}
+
+
+ {/* Menu Desktop - Direita */}
+
+
+ {/* Links diretos - Primeiros Passos */}
+
+ Primeiros Passos
+
+ {/* Links diretos - Atividades */}
+
+ Programação
+
+
+ {/* Dropdown - Laboratórios */}
+
handleMouseEnter("laboratorios")}
+ onMouseLeave={handleMouseLeave}
+ >
+
+ Laboratórios
+
+
+ {openDropdown === "laboratorios" && (
+
+
+ Laboratório de Blocos
+
+ {(isOffline === false) && (
+
+ Laboratório Python
+
+ )}
+
+ )}
+
+
+ {/* Links diretos - Iniciativas */}
+
+ Iniciativas
+
+
+
+ Quem somos
+
+
+
+ Perguntas frequentes
+
+
+ {(isOffline === false) && (
+
+ Para educadores
+
+ )}
+
+
+ {/* Botão Mobile Menu */}
+
setIsMenuOpen(!isMenuOpen)}
+ className="md:hidden p-2 rounded-lg hover:bg-white/10 transition-colors"
+ aria-label="Toggle menu"
+ >
+ {isMenuOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {/* Menu Mobile */}
+ {isMenuOpen && (
+
+
+ {/* Links diretos Mobile */}
+
setIsMenuOpen(false)}
+ >
+ Atividades
+
+
+
setIsMenuOpen(false)}
+ >
+ Primeiros Passos
+
+
+ {/* Laboratórios no mobile */}
+
+
+ Laboratórios
+
+
setIsMenuOpen(false)}
+ >
+ Laboratório de Blocos
+
+
setIsMenuOpen(false)}
+ >
+ Laboratório Python
+
+
+
+
setIsMenuOpen(false)}
+ >
+ Iniciativas
+
+
+
setIsMenuOpen(false)}
+ >
+ Quem somos
+
+
setIsMenuOpen(false)}
+ >
+ Perguntas frequentes
+
+
setIsMenuOpen(false)}
+ >
+ Para educadores
+
+
+
+ )}
+
+ );
+};
+
+export default Navbar;
diff --git a/app/src/components/ScrollToTop.jsx b/app/src/components/ScrollToTop.jsx
new file mode 100644
index 0000000..c48fbd8
--- /dev/null
+++ b/app/src/components/ScrollToTop.jsx
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Component to scroll to top on route change
+ *
+ * @module components.ScrollToTop
+ */
+
+import { useEffect } from "react";
+import { useLocation } from "react-router-dom";
+
+/**
+ * Component that scrolls window to top whenever the route changes.
+ * Must be placed inside Router.
+ */
+export default function ScrollToTop() {
+ const { pathname } = useLocation();
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, [pathname]);
+
+ return null;
+}
diff --git a/app/src/components/game/ConfettiOverlay.jsx b/app/src/components/game/ConfettiOverlay.jsx
new file mode 100644
index 0000000..177f46f
--- /dev/null
+++ b/app/src/components/game/ConfettiOverlay.jsx
@@ -0,0 +1,99 @@
+/**
+ * @fileoverview React component for ConfettiOverlay.jsx
+ *
+ * @module components.game.ConfettiOverlay
+ */
+
+
+import React, { useEffect, useRef } from "react";
+import PropTypes from "prop-types";
+import { confetti } from "@tsparticles/confetti";
+
+const ConfettiOverlay = ({ isActive, onComplete, // english alias
+ active, onFinish }) => {
+ const isActiveEffective = isActive ?? active;
+ const onCompleteEffective = onComplete ?? onFinish;
+ const canvasRef = useRef(null);
+ const timeoutRef = useRef(null);
+
+ useEffect(() => {
+ if (isActiveEffective && canvasRef.current) {
+ const canvas = canvasRef.current;
+
+ const randomInRange = (min, max) => {
+ return Math.random() * (max - min) + min;
+ };
+
+ const triggerConfettiBlast = async () => {
+ const defaultOptions = {
+ angle: randomInRange(55, 125),
+ spread: randomInRange(50, 70),
+ particleCount: randomInRange(50, 100),
+ origin: { y: 0.6 },
+ canvas: canvas,
+ };
+
+ await confetti(defaultOptions);
+ };
+
+ triggerConfettiBlast();
+
+ timeoutRef.current = setTimeout(() => {
+ if (onCompleteEffective) {
+ onCompleteEffective();
+ }
+ }, 500);
+ } else if (!isActive && canvasRef.current) {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext("2d");
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+
+
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, [isActiveEffective, onCompleteEffective]);
+
+ useEffect(() => {
+ const updateCanvasSize = () => {
+ if (canvasRef.current) {
+ canvasRef.current.width = window.innerWidth;
+ canvasRef.current.height = window.innerHeight;
+ }
+ };
+
+ updateCanvasSize();
+ window.addEventListener("resize", updateCanvasSize);
+ return () => window.removeEventListener("resize", updateCanvasSize);
+ }, []);
+
+ return (
+
+ );
+};
+
+ConfettiOverlay.propTypes = {
+ isActive: PropTypes.bool,
+ active: PropTypes.bool,
+ onComplete: PropTypes.func,
+ onFinish: PropTypes.func,
+};
+
+export default ConfettiOverlay;
diff --git a/app/src/components/game/ConfirmacaoModal.jsx b/app/src/components/game/ConfirmacaoModal.jsx
new file mode 100644
index 0000000..1169811
--- /dev/null
+++ b/app/src/components/game/ConfirmacaoModal.jsx
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview React component for ConfirmacaoModal.jsx
+ *
+ * @module components.game.ConfirmacaoModal
+ */
+
+// components/game/ConfirmacaoModal.jsx
+import { X } from "lucide-react";
+import PropTypes from "prop-types";
+
+export default function ConfirmacaoModal({
+ isOpen,
+ onClose,
+ onConfirm,
+ title,
+ message,
+}) {
+ if (!isOpen) return null;
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
{title}
+
+
+
+
+
+ {/* Mensagem */}
+
{message}
+
+ {/* Ações */}
+
+
+ Cancelar
+
+ {
+ if (onConfirm) onConfirm();
+ onClose();
+ }}
+ >
+ Confirmar
+
+
+
+
+ );
+}
+
+ConfirmacaoModal.propTypes = {
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func,
+ onConfirm: PropTypes.func,
+ title: PropTypes.string,
+ message: PropTypes.string,
+};
diff --git a/app/src/components/game/FalhaModal.jsx b/app/src/components/game/FalhaModal.jsx
new file mode 100644
index 0000000..320e958
--- /dev/null
+++ b/app/src/components/game/FalhaModal.jsx
@@ -0,0 +1,100 @@
+/**
+ * @fileoverview React component for FalhaModal.jsx
+ *
+ * @module components.game.FalhaModal
+ */
+
+
+import React from "react";
+import PropTypes from "prop-types";
+import { RefreshCw } from "lucide-react";
+
+import { ModalBase } from "./modal/ModalBase";
+import { ModalHeader } from "./modal/ModalHeader";
+import { CodeArea } from "./modal/CodeArea";
+import { FeedbackBox } from "./modal/FeedbackBox";
+
+const FalhaModal = ({
+ isOpen,
+ onClose,
+ onRetry,
+ customMessage,
+ currentPhase,
+ generatedCode,
+}) => {
+ const handleRetry = () => {
+ onClose();
+ if (onRetry) {
+ onRetry();
+ }
+ };
+
+ const mensagemExibida =
+ customMessage ??
+ "Ops! Parece que algo não funcionou como esperado. Tente novamente!";
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Revise o enunciado.
+ Verifique se os blocos estão corretamente conectados.
+
+ Certifique-se de que a lógica atende a todos os requisitos da
+ fase.
+
+
+
+
+
+
+ {/* 3. Rodapé (Botões de Ação) */}
+
+
+ Fechar
+
+
+
+
+ Tentar Novamente
+
+
+
+ );
+};
+
+FalhaModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ onRetry: PropTypes.func,
+ customMessage: PropTypes.string,
+ currentPhase: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ generatedCode: PropTypes.string,
+};
+
+export default FalhaModal;
diff --git a/app/src/components/game/GameArea.jsx b/app/src/components/game/GameArea.jsx
new file mode 100644
index 0000000..9271568
--- /dev/null
+++ b/app/src/components/game/GameArea.jsx
@@ -0,0 +1,138 @@
+/**
+ * @fileoverview React component for GameArea.jsx
+ *
+ * @module components.game.GameArea
+ */
+
+
+import { useEffect, useState, useRef } from "react";
+import { useGameState, GAME_STATES } from "../../contexts/GameStateContext";
+import { gameEventBus } from "../../utils/gameEvents";
+import ConfettiOverlay from "./ConfettiOverlay";
+import PropTypes from "prop-types";
+
+export default function GameArea({
+ children,
+ blocksRemaining = null,
+ phaseId = null,
+ remainingBlocksLabel = null,
+}) {
+ const {
+ executionState,
+ generatedCode,
+ finalizeWithSuccess,
+ finalizeWithFailure,
+ setFailureMessage,
+ } = useGameState();
+
+ const [showConfetti, setShowConfetti] = useState(false);
+ const [isTransitioning, setIsTransitioning] = useState(false);
+ const previousPhaseId = useRef(phaseId);
+ const effectivePhaseId = phaseId;
+ const remainingBlocks = blocksRemaining;
+
+ useEffect(() => {
+ if (effectivePhaseId !== null && effectivePhaseId !== previousPhaseId.current) {
+ setIsTransitioning(true);
+
+ const timer = setTimeout(() => {
+ setIsTransitioning(false);
+ previousPhaseId.current = effectivePhaseId;
+ }, 600);
+
+
+
+ return () => clearTimeout(timer);
+ }
+ }, [effectivePhaseId]);
+
+ useEffect(() => {
+ const handleGameSuccess = () => {
+ finalizeWithSuccess();
+ setShowConfetti(true);
+ };
+
+ const handleGameFailure = (e) => {
+ const reason = e.detail?.reason;
+ if (reason) setFailureMessage(reason);
+ finalizeWithFailure();
+ };
+
+ gameEventBus.addEventListener("gameSuccess", handleGameSuccess);
+ gameEventBus.addEventListener("gameFailure", handleGameFailure);
+
+ return () => {
+ gameEventBus.removeEventListener("gameSuccess", handleGameSuccess);
+ gameEventBus.removeEventListener("gameFailure", handleGameFailure);
+ };
+ }, [finalizeWithSuccess, finalizeWithFailure]);
+
+ useEffect(() => {
+ switch (executionState) {
+ case GAME_STATES.EXECUTANDO:
+ if (generatedCode) {
+ const codigo =
+ typeof generatedCode === "string"
+ ? generatedCode
+ : generatedCode.codigo;
+ const ws =
+ typeof generatedCode === "object" ? generatedCode.workspace : null;
+
+ gameEventBus.executeCode(codigo, ws);
+ }
+ break;
+ case GAME_STATES.PARADO:
+ gameEventBus.resetGame();
+ setShowConfetti(false);
+ break;
+ }
+ }, [executionState, generatedCode]);
+
+ const remainingLabel = remainingBlocksLabel ?? "Blocos restantes";
+
+ return (
+
+ {/* Confetti de sucesso de uma fase */}
+
+
+ {/* Overlay de transição */}
+
+
+ {/* Indicador de blocos restantes */}
+ {remainingBlocks !== null && !isNaN(remainingBlocks) && (
+
+
+ {remainingLabel}: {" "}
+ {remainingBlocks}
+
+
+ )}
+
+
+ {children}
+
+
+ );
+}
+
+GameArea.propTypes = {
+ children: PropTypes.node,
+ blocksRemaining: PropTypes.number,
+ phaseId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ remainingBlocksLabel: PropTypes.string,
+};
diff --git a/app/src/components/game/GameBase.jsx b/app/src/components/game/GameBase.jsx
new file mode 100644
index 0000000..0df393d
--- /dev/null
+++ b/app/src/components/game/GameBase.jsx
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview React component for GameBase.jsx
+ *
+ * @module components.game.GameBase
+ */
+
+
+import React from "react";
+import PropTypes from "prop-types";
+import { Panel, PanelGroup } from "react-resizable-panels";
+import GameNavBar from "./GameNavBar";
+import GameFaseInfo from "./GameFaseInfo";
+import GameArea from "./GameArea";
+import GameFooter from "./GameFooter";
+import SeletorDeFases from "./SeletorDeFases";
+import SucessoModal from "./SucessoModal";
+import FalhaModal from "./FalhaModal";
+import ResizeHandle from "./ResizeHandle";
+import { useIsMobile } from "../../hooks/useIsMobile";
+import { useGameState } from "../../contexts/GameStateContext";
+import { EditorProvider } from "../../contexts/EditorContext";
+import { usePhaser } from "../../hooks/usePhaser";
+import { useGameModals } from "../../hooks/useGameModals";
+
+function GameBaseContent({
+ gameFactory,
+ gameConfig,
+ children,
+ onHelpClick,
+ customFailureHandler,
+}) {
+ const isMobile = useIsMobile();
+
+ const {
+ currentPhase,
+ setCurrentPhase,
+ resetProgress,
+ executionState,
+ generatedCode,
+ failureMessage,
+ restart,
+ setOnWorkspaceChange,
+ } = useGameState();
+
+ const phaseConfig = gameConfig.fases[currentPhase - 1];
+ const usaModalFalha = !!customFailureHandler;
+
+ const { gameContainerRef } = usePhaser({
+ gameFactory,
+ phaseConfig,
+ currentPhase,
+ customFailureHandler,
+ gameConfig,
+ });
+
+ const {
+ modalFasesAberto,
+ setModalFasesAberto,
+ modalSucessoAberto,
+ modalFalhaAberto,
+ blocksRemainingCount,
+ handleProximaFase,
+ handleFecharModalSucesso,
+ handleFecharModalFalha,
+ handleTentarNovamente,
+ } = useGameModals({
+ executionState,
+ currentPhase,
+ phaseConfig,
+ gameConfig,
+ setCurrentPhase,
+ restart,
+ setOnWorkspaceChange,
+ usaModalFalha,
+ });
+
+ const handleResetProgresso = () => {
+ resetProgress();
+ window.dispatchEvent(
+ new CustomEvent("resetBlocklyWorkspace", {
+ detail: { gameId: gameConfig.gameId },
+ }),
+ );
+ };
+
+ const codeToShow = React.useMemo(() => {
+ if (!generatedCode) return "Nenhum código gerado";
+
+ let codigo = "";
+
+ if (typeof generatedCode === "string") {
+ codigo = generatedCode;
+ } else if (typeof generatedCode === "object" && generatedCode.codigo) {
+ codigo = generatedCode.codigo;
+ } else {
+ return "Código não disponível";
+ }
+
+ const codigoLimpo = codigo
+ .split("\n")
+ .filter((linha) => !linha.trim().startsWith("highlightBlock("))
+ .join("\n")
+ .trim();
+
+ return codigoLimpo || codigo;
+ }, [generatedCode]);
+
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+
+
+
setModalFasesAberto(true)}
+ onHelpClick={onHelpClick}
+ />
+
+ setModalFasesAberto(false)}
+ currentPhase={currentPhase}
+ gameConfig={gameConfig}
+ onChangePhase={(fase) => {
+ setCurrentPhase(fase);
+ setModalFasesAberto(false);
+ }}
+ onResetProgress={handleResetProgresso}
+ />
+
+
+
+
+
+ );
+}
+
+export default function GameBase({
+ gameFactory,
+ gameConfig,
+ children,
+ onHelpClick,
+ customFailureHandler,
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+GameBase.propTypes = {
+ gameFactory: PropTypes.func.isRequired,
+ gameConfig: PropTypes.shape({
+ gameId: PropTypes.string.isRequired,
+ gameName: PropTypes.string.isRequired,
+ fases: PropTypes.array.isRequired,
+ type: PropTypes.string,
+ thumbnail: PropTypes.string,
+ icon: PropTypes.string,
+ }).isRequired,
+ children: PropTypes.node.isRequired,
+ onHelpClick: PropTypes.func,
+ customFailureHandler: PropTypes.func,
+ helpHandler: PropTypes.func,
+ failureHandler: PropTypes.func,
+ onHelp: PropTypes.func,
+ title: PropTypes.string,
+};
diff --git a/app/src/components/game/GameEditor.jsx b/app/src/components/game/GameEditor.jsx
new file mode 100644
index 0000000..160b1cb
--- /dev/null
+++ b/app/src/components/game/GameEditor.jsx
@@ -0,0 +1,147 @@
+/**
+ * @fileoverview React component for GameEditor.jsx
+ *
+ * @module components.game.GameEditor
+ */
+
+import { Play, Loader, RotateCcw, CircleAlert } from "lucide-react";
+import PropTypes from "prop-types";
+import { useGameState, GAME_STATES } from "../../contexts/GameStateContext";
+import { useRef, useEffect } from "react";
+
+export default function GameEditor(props) {
+ const { children } = props;
+
+ const runText = props.runText ?? props.textoExecutar ?? props.runLabel ?? "Executar";
+ const restartText = props.restartText ?? props.textoReiniciar ?? props.restartLabel ?? "Reiniciar";
+
+ // useRef é síncrono: leitura/escrita imediata, sem batch do React
+ const isProcessingRef = useRef(false);
+ const processingTimeoutRef = useRef(null);
+
+ const {
+ executionState,
+ execute,
+ restart,
+ stop,
+ currentBlockCount,
+ editorType,
+ } = useGameState();
+
+ useEffect(() => {
+ return () => {
+ if (processingTimeoutRef.current) {
+ clearTimeout(processingTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ const isExecuting = executionState === GAME_STATES.EXECUTANDO;
+ const needsRestart =
+ executionState === GAME_STATES.SUCESSO ||
+ executionState === GAME_STATES.FALHA;
+ const noBlocks = currentBlockCount === 0;
+
+ const handleClick = () => {
+ // Leitura síncrona — funciona mesmo com cliques ultra-rápidos
+ if (isProcessingRef.current) {
+ return;
+ }
+
+ if (noBlocks && !isExecuting && !needsRestart) {
+ return;
+ }
+
+ // Escrita síncrona — bloqueia imediatamente
+ isProcessingRef.current = true;
+
+ if (processingTimeoutRef.current) {
+ clearTimeout(processingTimeoutRef.current);
+ }
+ processingTimeoutRef.current = setTimeout(() => {
+ isProcessingRef.current = false;
+ }, 200);
+
+ if (isExecuting) {
+ stop();
+ return;
+ }
+
+ if (needsRestart) {
+ restart();
+ } else {
+ execute();
+ }
+ };
+
+ const setStyle = () => {
+ const style =
+ "game-controls-custom flex items-center space-x-2 px-6 py-3 rounded-full font-medium transition-all duration-200 shadow-md";
+
+ if (noBlocks) {
+ return `${style} bg-gray-300 text-black cursor-not-allowed`;
+ }
+
+ if (isExecuting) {
+ return `${style} bg-[var(--action-green)] hover:bg-[rgb(235_119_63/1)]`;
+ }
+
+ if (needsRestart) {
+ return `${style} bg-[rgb(254_0_2/1)] hover:bg-[rgb(230_0_0/1)] text-white`;
+ }
+
+ return `${style} bg-green-100 hover:bg-green-200 text-black`;
+ };
+
+ const getEmptyStateText = () => {
+ return editorType === "code"
+ ? "Adicione código para execute"
+ : "Adicione blocos para execute";
+ };
+
+ return (
+
+
{children}
+
+
+ {isExecuting ? (
+ <>
+
+ Executando, clique para interromper...
+ >
+ ) : needsRestart ? (
+ <>
+
+ {restartText}
+ >
+ ) : noBlocks ? (
+ <>
+
+ {getEmptyStateText()}
+ >
+ ) : (
+ <>
+
+ {runText}
+ >
+ )}
+
+
+
+ );
+}
+
+GameEditor.propTypes = {
+ runText: PropTypes.string,
+ runLabel: PropTypes.string,
+ textoExecutar: PropTypes.string,
+ restartText: PropTypes.string,
+ restartLabel: PropTypes.string,
+ textoReiniciar: PropTypes.string,
+ children: PropTypes.node,
+};
\ No newline at end of file
diff --git a/app/src/components/game/GameFaseInfo.jsx b/app/src/components/game/GameFaseInfo.jsx
new file mode 100644
index 0000000..98c88aa
--- /dev/null
+++ b/app/src/components/game/GameFaseInfo.jsx
@@ -0,0 +1,85 @@
+
+import React from "react";
+import PropTypes from "prop-types";
+
+function obterDificuldade(dadosFase) {
+ // Usa a dificuldade da fase, se existir, senão calcula pelo número
+ if (dadosFase?.dificuldade) {
+ switch (dadosFase.dificuldade) {
+ case "Fácil":
+ return { nivel: "Fácil", cor: "bg-green-500", emoji: "😊" };
+ case "Médio":
+ return { nivel: "Médio", cor: "bg-yellow-500", emoji: "🤔" };
+ case "Difícil":
+ return { nivel: "Difícil", cor: "bg-orange-500", emoji: "😤" };
+ case "Extremo":
+ return { nivel: "Extremo", cor: "bg-red-500", emoji: "🔥" };
+ default:
+ return null;
+ }
+ }
+ return null;
+}
+
+// English alias for migration
+const getDifficulty = (phaseData) => obterDificuldade(phaseData);
+
+function GameFaseInfo({ phaseData = {}, phaseNumber }) {
+ const dificuldade = getDifficulty(phaseData);
+
+
+
+ return (
+
+ {phaseData && phaseData.nome ? (
+
+ {/* Número da fase */}
+
+ {phaseNumber}
+
+ {/* Título/Subtítulo */}
+
+
+ {phaseData.nome}
+
+ {phaseData.descricao && (
+
+ {phaseData.descricao}
+
+ )}
+
+ {/* Dificuldade */}
+
+ {dificuldade && (
+
+ {dificuldade.emoji}
+ {dificuldade.nivel}
+
+ )}
+
+
+ ) : (
+
+
+ ?
+
+
+
+ Selecione uma fase para começar
+
+
+
+
+ )}
+
+ );
+}
+
+export default GameFaseInfo;
+
+export { getDifficulty };
+
+GameFaseInfo.propTypes = {
+};
diff --git a/app/src/components/game/GameFooter.jsx b/app/src/components/game/GameFooter.jsx
new file mode 100644
index 0000000..1d045b3
--- /dev/null
+++ b/app/src/components/game/GameFooter.jsx
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview React component for GameFooter.jsx
+ *
+ * @module components.game.GameFooter
+ */
+
+
+import React from "react";
+import PropTypes from "prop-types";
+
+export default function GameFooter({
+ gameConfig,
+ currentPhase,
+ onHelpClick,
+ onOpenPhaseSelector,
+}) {
+ const totalPhases = gameConfig.fases.length;
+ const displayPhase = currentPhase ?? currentPhase;
+
+ const ajuda = () => {
+ if (onHelpClick) {
+ onHelpClick();
+ return;
+ }
+ alert("Recurso de ajuda será implementado em breve!");
+ };
+
+ return (
+
+
+ {/* Lado esquerdo - Botão de Ajuda (desativado temporariamente, tour será reimplementado) */}
+
+
+ Ajuda
+
+
+ {/* Centro - Indicador de Fase Atual/Total */}
+
+
+
+ {displayPhase}
+ /
+ {totalPhases}
+
+
+
+ {/* Lado direito - Botão do Seletor de Fases */}
+
+
+ Fases
+
+
+
+
+ );
+}
+
+GameFooter.propTypes = {
+ gameConfig: PropTypes.object.isRequired,
+ currentPhase: PropTypes.number,
+ onHelpClick: PropTypes.func,
+ onOpenPhaseSelector: PropTypes.func,
+};
diff --git a/app/src/components/game/GameNavBar.jsx b/app/src/components/game/GameNavBar.jsx
new file mode 100644
index 0000000..26bda44
--- /dev/null
+++ b/app/src/components/game/GameNavBar.jsx
@@ -0,0 +1,152 @@
+/**
+ * @fileoverview React component for GameNavBar.jsx
+ *
+ * @module components.game.GameNavBar
+ */
+
+
+import { useState } from "react";
+import PropTypes from "prop-types";
+import { useNavigate } from "react-router-dom";
+import logo from "../../assets/logo_decoda.svg";
+import { Code, Puzzle } from "lucide-react";
+import { ArrowLeft } from "lucide-react";
+
+export default function GameNavBar({ title, label, type = "blocks", thumbnail, icon }) {
+ const titleToShow = title ?? label;
+ const navigate = useNavigate();
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
+
+ const renderIcon = (size = "w-6 h-6 lg:w-8 lg:h-8") => {
+ if (type === "code") {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+
+
+ return (
+ <>
+ {/* Navbar normal - oculto em telas pequenas */}
+
+
+
+
navigate("/")}
+ title="Ir para Home"
+ className="header-logo group cursor-pointer flex items-center space-x-2"
+ >
+
+
+
+
+
+
+ {renderIcon("w-8 h-8")}
+
{titleToShow}
+ {thumbnail && (
+
{
+ e.target.style.display = 'none';
+ }}
+ />
+ )}
+
+
+
+
+
+ {/* Botão menu flutuante - só aparece em telas pequenas */}
+ setIsMenuOpen(true)}
+ >
+
+
+
+
+
+
+
+ {/* Overlay do menu mobile */}
+ {isMenuOpen && (
+
+
+
+
+ setIsMenuOpen(false)}
+ className="text-brand-500 text-3xl"
+ aria-label="Fechar menu"
+ >
+ ×
+
+
+
+ {type === "code" &&
}
+ {type === "blocks" && (
+
+ )}
+
{titleToShow}
+ {thumbnail && (
+
{
+ e.target.style.display = 'none';
+ }}
+ />
+ )}
+
+
{
+ setIsMenuOpen(false);
+ navigate("/");
+ }}
+ className="flex items-center gap-5 mt-10"
+ >
+
+ Voltar
+
+
+
{
+ setIsMenuOpen(false);
+ navigate("/");
+ }}
+ >
+ {
+ e.target.style.display = "none";
+ e.target.nextSibling.style.display = "inline";
+ }}
+ />
+
+
+
+ )}
+ >
+ );
+}
+
+GameNavBar.propTypes = {
+ title: PropTypes.string,
+ label: PropTypes.string,
+ type: PropTypes.string,
+ thumbnail: PropTypes.string,
+ icon: PropTypes.string,
+};
diff --git a/app/src/components/game/ResizeHandle.jsx b/app/src/components/game/ResizeHandle.jsx
new file mode 100644
index 0000000..9be226a
--- /dev/null
+++ b/app/src/components/game/ResizeHandle.jsx
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview React component for ResizeHandle.jsx
+ *
+ * @module components.game.ResizeHandle
+ */
+
+
+import { PanelResizeHandle } from "react-resizable-panels";
+import PropTypes from "prop-types";
+
+export default function ResizeHandle({
+ direction = "horizontal",
+ // English alias
+ orientation,
+ theme = "light",
+ disabled = false,
+}) {
+ const effectiveDirection = direction ?? orientation ?? "horizontal";
+ const horizontal = effectiveDirection === "horizontal";
+ const dark = theme === "dark";
+
+
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+ResizeHandle.propTypes = {
+ direction: PropTypes.string,
+ orientation: PropTypes.string,
+ theme: PropTypes.string,
+ disabled: PropTypes.bool,
+};
diff --git a/app/src/components/game/SeletorDeFases.jsx b/app/src/components/game/SeletorDeFases.jsx
new file mode 100644
index 0000000..3cf0c9a
--- /dev/null
+++ b/app/src/components/game/SeletorDeFases.jsx
@@ -0,0 +1,178 @@
+/**
+ * @fileoverview React component for SeletorDeFases.jsx
+ *
+ * @module components.game.SeletorDeFases
+ */
+
+
+import React, { useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import ConfirmacaoModal from "./ConfirmacaoModal";
+import { Lock, CheckCircle, Star, X } from "lucide-react";
+import { getUnlockedPhases } from "../../utils/phaseUtils";
+
+export default function SeletorDeFases({
+ isVisible,
+ onClose,
+ gameConfig,
+ currentPhase,
+ onChangePhase,
+ onResetProgress,
+}) {
+ const gameId = gameConfig.gameId;
+ const totalPhases = gameConfig.fases.length;
+ const storageKey = `${gameId}-completed-phases`;
+ const [completedPhases, setCompletedPhases] = useState([]);
+ const [showResetConfirmation, setShowResetConfirmation] = useState(false);
+
+ useEffect(() => {
+ if (!isVisible) return;
+ const salvo = localStorage.getItem(storageKey);
+ if (salvo) {
+ try {
+ const fases = JSON.parse(salvo);
+ setCompletedPhases(fases);
+ } catch {
+ setCompletedPhases([]);
+ }
+ } else {
+ setCompletedPhases([]);
+ }
+ }, [isVisible, storageKey]);
+
+ const unlockedPhases = getUnlockedPhases(completedPhases, totalPhases);
+
+ const selectPhase = (number) => {
+ if (!unlockedPhases.includes(number)) return;
+ if (onChangePhase) onChangePhase(number);
+ onClose();
+ };
+
+ if (!isVisible) return null;
+
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
+
+
+ Selecionar Fase - {gameConfig.gameName || "Jogo"}
+
+
+
+
+
+
+
+
+
+ {Array.from({ length: totalPhases }, (_, i) => {
+ const phaseNumber = i + 1;
+ const isUnlocked = unlockedPhases.includes(phaseNumber);
+ const wasCompleted = completedPhases.includes(phaseNumber);
+ const isCurrent = currentPhase === phaseNumber;
+ const phaseData = gameConfig.fases[i];
+
+ return (
+
isUnlocked && selectPhase(phaseNumber)}
+ >
+
+
+
+ {wasCompleted ? (
+
+ ) : !isUnlocked ? (
+
+ ) : (
+ phaseNumber
+ )}
+
+
+ {isCurrent && (
+
+ Atual
+
+ )}
+
+
+
+
+ Fase {phaseNumber}
+
+
+ {phaseData.nome}
+
+
+ {phaseData.descricao}
+
+
+ {phaseData.dificuldade}
+
+
+
+ );
+ })}
+
+
+
+
+ setShowResetConfirmation(true)}
+ >
+ Resetar TODO o progresso do jogo
+
+
+
+
setShowResetConfirmation(false)}
+ onConfirm={() => {
+ if (onResetProgress) onResetProgress();
+ setShowResetConfirmation(false);
+ onClose();
+ }}
+ title="Resetar progresso"
+ message="Tem certeza que deseja apagar TODO o progresso e blocos salvos deste jogo? Esta ação não pode ser desfeita."
+ />
+
+
+ );
+}
+
+SeletorDeFases.propTypes = {
+ isVisible: PropTypes.bool,
+ onClose: PropTypes.func,
+ gameConfig: PropTypes.object.isRequired,
+ onChangePhase: PropTypes.func,
+ onResetProgress: PropTypes.func,
+};
diff --git a/app/src/components/game/SucessoModal.jsx b/app/src/components/game/SucessoModal.jsx
new file mode 100644
index 0000000..150ee91
--- /dev/null
+++ b/app/src/components/game/SucessoModal.jsx
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview React component for SucessoModal.jsx
+ *
+ * @module components.game.SucessoModal
+ */
+
+
+import React from "react";
+import PropTypes from "prop-types";
+import { ChevronRight } from "lucide-react";
+
+import { ModalBase } from "./modal/ModalBase";
+import { ModalHeader } from "./modal/ModalHeader";
+import { CodeArea } from "./modal/CodeArea";
+import { FeedbackBox } from "./modal/FeedbackBox";
+
+const SucessoModal = ({
+ isOpen,
+ onClose,
+ onNextPhase,
+ generatedCode,
+ canGoNext,
+}) => {
+ const handleNextPhase = () => {
+ onClose();
+ if (onNextPhase) {
+ onNextPhase();
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ Os blocos que você conectou foram convertidos em código JavaScript
+ real. Este é o mesmo tipo de código que os programadores usam para
+ criar aplicações!
+
+
+
+
+
+ {/* 3. Rodapé (Botões de Ação) */}
+
+
+ Fechar
+
+
+ {canGoNext && (
+
+ Próxima Fase
+
+
+ )}
+
+
+ );
+};
+
+SucessoModal.propTypes = {
+ isOpen: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ onNextPhase: PropTypes.func,
+ generatedCode: PropTypes.any,
+ canGoNext: PropTypes.bool.isRequired,
+};
+
+export default SucessoModal;
diff --git a/app/src/components/game/editors/BlocklyEditor.jsx b/app/src/components/game/editors/BlocklyEditor.jsx
new file mode 100644
index 0000000..5114e03
--- /dev/null
+++ b/app/src/components/game/editors/BlocklyEditor.jsx
@@ -0,0 +1,332 @@
+/**
+ * @fileoverview React component for BlocklyEditor.jsx
+ *
+ * @module components.game.editors.BlocklyEditor
+ */
+
+
+import React, {
+ useEffect,
+ useRef,
+ forwardRef,
+ useImperativeHandle,
+ useState,
+ useCallback,
+ useMemo,
+} from "react";
+import PropTypes from "prop-types";
+import * as Blockly from "blockly/core";
+import Theme from "@blockly/theme-modern";
+import { javascriptGenerator } from "blockly/javascript";
+import { useGameState } from "../../../contexts/GameStateContext";
+import { validateBlocklyWorkspace } from "@/blockly/validation";
+import { useEditor } from "../../../contexts/EditorContext";
+import {
+ loadWorkspace,
+ createDebouncedSave,
+} from "../../../services/blockstorage";
+import { getCategoryIcon } from "./toolboxIcons";
+import { Hammer } from "lucide-react";
+import "./custom_category";
+import "./BlocklyEditor.mobile.css";
+
+const LoadingSpinner = () => (
+
+);
+
+const BlocklyEditor = forwardRef(function BlocklyEditor(
+ { toolboxGenerator, debugSolutions = null, starterBlocks = null, starter = null },
+ ref,
+) {
+ const starterBlocksProp = starterBlocks ?? starter;
+ const { registerExecutionFunction, onWorkspaceChange } = useGameState();
+ const { gameConfig: editorGameConfig, currentPhase, editorData, updateEditorData, gameNameKey } =
+ useEditor();
+
+ const hasSolution = debugSolutions && debugSolutions[currentPhase];
+
+ const handleLoadSolution = () => {
+ if (!workspaceRef.current) {
+ alert("Editor não está pronto");
+ return;
+ }
+
+ if (window.confirm("Deseja carregar a solução desta fase?")) {
+ try {
+ const solution = debugSolutions[currentPhase];
+ workspaceRef.current.clear();
+ Blockly.serialization.workspaces.load(solution, workspaceRef.current);
+ } catch (error) {
+ alert("Erro ao carregar a solução");
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (!editorGameConfig || !toolboxGenerator) return;
+
+ const phases = editorGameConfig.fases ?? editorGameConfig.phases; // support English alias
+ const phaseConfig = phases?.[currentPhase - 1];
+ const toolbox = toolboxGenerator(phaseConfig?.allowedBlocks);
+ const max = phaseConfig?.maxBlocks;
+
+ updateEditorData({
+ toolboxJson: toolbox,
+ maxBlocks: max,
+ });
+ }, [editorGameConfig, currentPhase, toolboxGenerator, updateEditorData]);
+
+ const { toolboxJson, maxBlocks } = editorData;
+
+ const blocklyDiv = useRef(null);
+ const workspaceRef = useRef(null);
+ const [initialJson, setInitialJson] = useState(undefined);
+ const [isLoading, setIsLoading] = useState(true);
+ const isInitializedRef = useRef(false);
+ const [currentBlockCount, setCurrentBlockCount] = useState(0);
+
+ const debouncedSave = useMemo(() => createDebouncedSave(1000), []);
+ const stableToolboxJson = useMemo(() => toolboxJson, [toolboxJson]);
+
+ const updateCategoriesState = useCallback((limitReached) => {
+ if (!workspaceRef.current) return;
+
+ const toolbox = workspaceRef.current.getToolbox();
+ if (!toolbox) return;
+
+ const categories = toolbox.getToolboxItems();
+
+ categories.forEach((category) => {
+ category.setDisabled(limitReached);
+ });
+ }, []);
+
+ const workspaceChange = useCallback(() => {
+ if (!workspaceRef.current) return;
+ const blockCount = workspaceRef.current.getAllBlocks().length;
+ setCurrentBlockCount(blockCount);
+
+ const limitReached =
+ maxBlocks !== undefined &&
+ maxBlocks !== Infinity &&
+ maxBlocks <= blockCount;
+ updateCategoriesState(limitReached);
+
+ if (onWorkspaceChange) {
+ onWorkspaceChange(blockCount);
+ }
+ }, [onWorkspaceChange, maxBlocks, updateCategoriesState]);
+
+ useEffect(() => {
+ updateCategoriesState();
+ }, [updateCategoriesState]);
+
+ useEffect(() => {
+ const generateAndValidateCode = () => {
+ if (!workspaceRef.current) {
+ return { codigo: null, workspace: null };
+ }
+ const validation = validateBlocklyWorkspace(workspaceRef.current, {
+ allowMultipleTopBlocks: editorGameConfig?.allowMultipleTopBlocks ?? false,
+ preferredStartBlocks: ["start", "when_run", "main"],
+ });
+ if (!validation.isValid) {
+ return { codigo: null, workspace: null };
+ }
+ const codigo = javascriptGenerator.workspaceToCode(workspaceRef.current);
+ return { codigo, workspace: workspaceRef.current };
+ };
+ registerExecutionFunction(generateAndValidateCode);
+
+
+ return () => {
+ registerExecutionFunction(null);
+ };
+ }, [registerExecutionFunction]);
+
+ useEffect(() => {
+ setIsLoading(true);
+ const loadedData = loadWorkspace(gameNameKey);
+ setInitialJson(loadedData);
+ setIsLoading(false);
+ }, [gameNameKey]);
+
+ useImperativeHandle(ref, () => workspaceRef.current, []);
+
+ useEffect(() => {
+ if (
+ isLoading ||
+ isInitializedRef.current ||
+ !blocklyDiv.current ||
+ !stableToolboxJson
+ ) {
+ return;
+ }
+ isInitializedRef.current = true;
+
+ const toolboxWithIcons = {
+ ...stableToolboxJson,
+ contents: stableToolboxJson.contents.map((cat) => ({
+ ...cat,
+ "css-icon": getCategoryIcon(cat.name),
+ })),
+ };
+
+ workspaceRef.current = Blockly.inject(blocklyDiv.current, {
+ toolbox: toolboxWithIcons,
+ trashcan: true,
+ scrollbars: true,
+ renderer: "zelos",
+ theme: Theme,
+ grid: { spacing: 25, length: 3, colour: "#ccc", snap: true },
+ zoom: { controls: false, wheel: true, startScale: 0.7 },
+ });
+
+ // Criar variáveis pré-definidas para o jogo Cripto
+ if (editorGameConfig?.gameId === "cripto") {
+ ["entrada", "saida", "pos"].forEach((varName) => {
+ const variableMap = workspaceRef.current.getVariableMap();
+ if (!variableMap.getVariable(varName)) {
+ workspaceRef.current.createVariable(varName);
+ }
+ });
+ }
+
+ // Carregar workspace: prioridade = localStorage > starterBlocks > vazio
+ if (initialJson) {
+ try {
+ Blockly.serialization.workspaces.load(
+ initialJson,
+ workspaceRef.current,
+ );
+ } catch (error) {
+ console.error(
+ "Falha ao carregar workspace do localStorage, limpando.",
+ error,
+ );
+ workspaceRef.current.clear();
+ }
+ } else if (starterBlocksProp && starterBlocksProp[currentPhase]) {
+ // Se não há nada salvo, carregar blocos iniciais da fase
+ try {
+ Blockly.serialization.workspaces.load(
+ starterBlocksProp[currentPhase],
+ workspaceRef.current,
+ );
+ } catch (error) {
+ console.error("Falha ao carregar blocos iniciais.", error);
+ }
+ }
+
+ workspaceChange();
+
+ const listener = (event) => {
+ if (!workspaceRef.current || event.isUiEvent) {
+ return;
+ }
+
+ if (event.type === Blockly.Events.BLOCK_CREATE) {
+ const currentCount = workspaceRef.current.getAllBlocks().length;
+ if (
+ maxBlocks !== undefined &&
+ maxBlocks !== Infinity &&
+ currentCount > maxBlocks
+ ) {
+ const blockToRemove = workspaceRef.current.getBlockById(
+ event.blockId,
+ );
+ if (blockToRemove) {
+ console.warn(
+ `Limite de blocos atingido (${maxBlocks}). Removendo bloco extra: ${event.blockId}`,
+ );
+
+ Blockly.Events.disable();
+ blockToRemove.dispose(false);
+ Blockly.Events.enable();
+ return;
+ }
+ }
+ }
+
+ workspaceChange();
+ const currentState = Blockly.serialization.workspaces.save(
+ workspaceRef.current,
+ );
+ debouncedSave(gameNameKey, currentState);
+ };
+
+ workspaceRef.current.addChangeListener(listener);
+
+ const observer = new ResizeObserver(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ });
+ observer.observe(blocklyDiv.current);
+
+ setTimeout(() => {
+ updateCategoriesState();
+ const bg = document.querySelector(".blocklyToolboxBackground");
+ if (bg) {
+ bg.setAttribute("fill", "none");
+ bg.setAttribute("stroke", "none");
+ }
+ }, 100);
+
+ return () => {
+ debouncedSave.cancel();
+ if (workspaceRef.current) {
+ workspaceRef.current.removeChangeListener(listener);
+ try {
+ workspaceRef.current.dispose();
+ } catch (error) {
+ console.warn("Erro ao fazer dispose do workspace:", error);
+ }
+ workspaceRef.current = null;
+ }
+ observer.disconnect();
+ isInitializedRef.current = false;
+ };
+ }, [
+ isLoading,
+ initialJson,
+ gameNameKey,
+ stableToolboxJson,
+ debouncedSave,
+ workspaceChange,
+ maxBlocks,
+ updateCategoriesState,
+ editorGameConfig,
+ currentPhase,
+ ]);
+
+ if (isLoading || !toolboxJson) {
+ return ;
+ }
+
+ return (
+
+ {hasSolution && (
+
+
+
+ )}
+
+
+ );
+});
+
+export default React.memo(BlocklyEditor);
+
+BlocklyEditor.propTypes = {
+ toolboxGenerator: PropTypes.func.isRequired,
+ debugSolutions: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+ starterBlocks: PropTypes.object,
+ starter: PropTypes.object,
+};
diff --git a/app/src/components/game/editors/BlocklyEditor.mobile.css b/app/src/components/game/editors/BlocklyEditor.mobile.css
new file mode 100644
index 0000000..76facf8
--- /dev/null
+++ b/app/src/components/game/editors/BlocklyEditor.mobile.css
@@ -0,0 +1,237 @@
+/* Loading Spinner Styles */
+.blockly-loading-spinner {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(255, 255, 255, 0.8);
+}
+
+.blockly-loading-text {
+ font-size: 18px;
+ color: #555;
+}
+/* Estilos para destaque de blocos durante execução */
+.blocklySelected > .blocklyPath {
+ stroke: #667eea !important;
+ stroke-width: 1px !important;
+ filter: drop-shadow(0 0 10px rgba(53, 2, 92, 1.7)) !important;
+}
+
+.blocklySelected {
+ z-index: 999 !important;
+}
+
+/* ===== BLOCKLY CUSTOMIZATIONS ===== */
+.blocklyScrollbarVertical,
+.blocklyScrollbarHorizontal,
+.blocklyToolboxDiv .blocklyScrollbarVertical,
+.blocklyToolboxDiv .blocklyScrollbarHorizontal,
+.blocklyFlyout .blocklyScrollbarVertical,
+.blocklyFlyout .blocklyScrollbarHorizontal,
+.blocklyWorkspace .blocklyScrollbarVertical,
+.blocklyWorkspace .blocklyScrollbarHorizontal,
+.blocklyDiv .blocklyScrollbarVertical,
+.blocklyDiv .blocklyScrollbarHorizontal,
+.blocklyDiv * .blocklyScrollbarVertical,
+.blocklyDiv * .blocklyScrollbarHorizontal,
+.blocklyScrollbarHandle,
+.blocklyScrollbarKnob,
+.blocklyScrollbarBackground,
+div[class*="blockly"] .blocklyScrollbarVertical,
+div[class*="blockly"] .blocklyScrollbarHorizontal,
+svg[class*="blockly"] .blocklyScrollbarVertical,
+svg[class*="blockly"] .blocklyScrollbarHorizontal {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
+ pointer-events: none !important;
+ width: 0 !important;
+ height: 0 !important;
+}
+
+.blocklyWorkspace,
+.blocklyWorkspace svg,
+.blocklyToolboxDiv,
+.blocklyToolboxDiv *,
+.blocklyFlyout,
+.blocklyFlyout *,
+.blocklyDiv,
+.blocklyDiv *,
+.blocklyMainBackground,
+.blocklyTreeRoot {
+ overflow: hidden !important;
+}
+
+.blocklyDiv {
+ background-color: #f8f9fa;
+ min-height: 200px;
+}
+
+.blocklyDiv .blocklyWorkspace {
+ background-color: #f8f9fa !important;
+}
+
+.blocklyDiv .blocklyFlyout,
+.blocklyDiv .blocklyToolboxDiv {
+ transition: opacity 0.2s ease-in-out;
+}
+
+.blocklyDiv:empty {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+ background-size: 200% 100%;
+ animation: loading-shimmer 1.5s infinite;
+}
+
+@media (max-width: 480px) {
+ .fixed.z-50 {
+ z-index: 1000 !important;
+ }
+
+ nav {
+ z-index: 100 !important;
+ }
+
+ .blocklyDiv,
+ .injectionDiv {
+ z-index: 10 !important;
+ position: relative !important;
+ }
+
+ .blocklyToolboxDiv {
+ z-index: 30 !important;
+ }
+}
+
+.blocklyTreeLabel svg {
+ vertical-align: middle;
+ margin-right: 4px;
+}
+
+/* ================================================================================================== */
+.blocklyToolboxCategoryLabel {
+ font-size: 1.2em;
+}
+
+.blocklyToolboxContents {
+ padding: 0.5em;
+}
+
+.blocklyToolboxCategory {
+ padding: 0.5em;
+ margin-top: 0.5em;
+ margin-bottom: 0.2em;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ border-radius: 10px;
+ border: 5px solid #ffffff;
+}
+
+.customIcon {
+ color: #fff;
+}
+
+.blocklyTreeRowContentContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.blocklyToolboxCategory {
+ height: initial;
+}
+
+@media (max-width: 480px) {
+ .blocklyToolboxCategory {
+ padding: 0px;
+ border: none;
+ border-radius: 0%;
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ font-size: 0;
+ width: 42px;
+ height: 40px;
+ /* Esconde o texto */
+ }
+
+ .blocklyToolboxCategoryLabel {
+ display: none;
+ /* Some completamente */
+ }
+
+ .blocklyTreeRowContentContainer {
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: center !important;
+ justify-content: center !important;
+ text-align: center !important;
+ width: 100% !important;
+ height: 100% !important;
+ }
+
+ .blocklyToolboxCategoryIcon {
+ /* font-size: 32px !important; */
+ margin: 0 !important;
+ color: white !important;
+ }
+
+ .blocklyText {
+ font-size: small !important;
+ }
+}
+
+.blockly-debug-solution-btn {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ z-index: 1000; /* Aumentado para ficar sobre tudo */
+ padding: 0.5rem;
+ background-color: white;
+ border: none;
+ border-radius: 9999px;
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+ transition: background-color 0.2s;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.blockly-debug-solution-btn:hover {
+ background-color: #f3e8ff;
+}
+
+.blockly-debug-solution-btn:active {
+ background-color: #e9d5ff;
+}
+
+@media (max-width: 768px) {
+ .blockly-debug-solution-btn {
+ top: 0.5rem;
+ right: 0.5rem;
+ padding: 0.5rem;
+ }
+
+ .blockly-debug-solution-btn svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .blockly-debug-solution-btn {
+ top: 0.375rem;
+ right: 0.375rem;
+ padding: 0.375rem;
+ }
+
+ .blockly-debug-solution-btn svg {
+ width: 1rem;
+ height: 1rem;
+ }
+}
diff --git a/app/src/components/game/editors/CodeEditor.jsx b/app/src/components/game/editors/CodeEditor.jsx
new file mode 100644
index 0000000..c9c00f4
--- /dev/null
+++ b/app/src/components/game/editors/CodeEditor.jsx
@@ -0,0 +1,201 @@
+/**
+ * @fileoverview React component for CodeEditor.jsx
+ *
+ * @module components.game.editors.CodeEditor
+ */
+
+
+import React, { useState, useEffect, useMemo } from "react";
+import PropTypes from "prop-types";
+import CodeMirror from "@uiw/react-codemirror";
+import { javascript } from "@codemirror/lang-javascript";
+import { autocompletion } from "@codemirror/autocomplete";
+import { indentWithTab, insertTab } from "@codemirror/commands";
+import { keymap } from "@codemirror/view";
+import { indentOnInput, indentUnit } from "@codemirror/language";
+import { useGameState } from "../../../contexts/GameStateContext";
+import { useEditor } from "../../../contexts/EditorContext";
+import {
+ loadCode,
+ createDebouncedCodeSave,
+} from "../../../services/codestorage";
+
+const createGameCompletion = (gameConfig) => {
+ return autocompletion({
+ override: [
+ (context) => {
+ const word = context.matchBefore(/\w*/);
+ if (!word || (word.from === word.to && !context.explicit)) return null;
+
+ const allowedFunctions = gameConfig?.allowedFunctions || [];
+ const functionDocs = gameConfig?.functionDocumentation || {};
+ const allowedStructures = gameConfig?.allowedControlStructures || [];
+
+ const functionOptions = allowedFunctions.map((funcName) => {
+ const doc = functionDocs[funcName] || {};
+ return {
+ label: funcName,
+ type: "function",
+ info: doc.description || `Função ${funcName}`,
+ detail: doc.syntax || `${funcName}()`,
+ apply: doc.example || `${funcName}()`,
+ };
+ });
+
+ const structureOptions = allowedStructures.map((structure) => {
+ const templates = {
+ if: "if (${condition}) {\n ${code}\n}",
+ else: "else {\n ${code}\n}",
+ while: "while (${condition}) {\n ${code}\n}",
+ for: "for (${init}; ${condition}; ${update}) {\n ${code}\n}",
+ var: "var ${name} = ${value};",
+ function: "function ${name}() {\n ${code}\n}",
+ };
+
+ return {
+ label: structure,
+ type: "keyword",
+ info: `Estrutura de controle ${structure}`,
+ detail: templates[structure] || structure,
+ apply: templates[structure] || structure,
+ };
+ });
+
+ return {
+ from: word.from,
+ options: [...functionOptions, ...structureOptions],
+ };
+ },
+ ],
+ });
+};
+
+export default function CodeEditor() {
+ const {
+ registerCodeEditorFunction,
+ onCodeEditorChange,
+ gameConfig,
+ currentPhase,
+ } = useGameState();
+ const { gameNameKey } = useEditor();
+
+ const phases = gameConfig?.fases ?? gameConfig?.phases; // support English alias
+ const phaseConfig = phases?.find((phase) => phase.id === currentPhase);
+ const initialCode = phaseConfig?.initialCode || "// Digite seu código aqui";
+
+ const [code, setCode] = useState(initialCode);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const debouncedCodeSave = useMemo(() => createDebouncedCodeSave(1000), []);
+ const codeStorageKey = `${gameNameKey}-code`;
+
+ useEffect(() => {
+ setIsLoading(true);
+
+ const savedCode = loadCode(codeStorageKey);
+ if (savedCode !== null) {
+ setCode(savedCode);
+ } else {
+ setCode(initialCode);
+ }
+
+ setIsLoading(false);
+ }, [codeStorageKey, initialCode]);
+
+ useEffect(() => {
+ const getCodeFromEditor = () => {
+ return code;
+ };
+
+ registerCodeEditorFunction(getCodeFromEditor);
+
+
+
+ return () => {
+ registerCodeEditorFunction(null);
+ };
+ }, [code, registerCodeEditorFunction]);
+
+ useEffect(() => {
+ if (!isLoading) {
+ onCodeEditorChange(code);
+ }
+ }, [code, onCodeEditorChange, isLoading]);
+
+ const handleChange = (value) => {
+ setCode(value);
+
+ if (!isLoading) {
+ debouncedCodeSave(codeStorageKey, value);
+ }
+ };
+
+ useEffect(() => {
+ return () => {
+ debouncedCodeSave.cancel();
+ };
+ }, [debouncedCodeSave]);
+
+ return (
+
+ {
+ const { from } = state.selection.main;
+ const line = state.doc.lineAt(from);
+ const lineText = line.text;
+ const indent = lineText.match(/^\s*/)[0];
+
+ let newIndent = indent;
+ if (lineText.trim().endsWith("{")) {
+ newIndent += " ";
+ }
+
+ dispatch(
+ state.update({
+ changes: {
+ from,
+ insert: "\n" + newIndent,
+ },
+ selection: { anchor: from + newIndent.length + 1 },
+ }),
+ );
+
+ return true;
+ },
+ },
+ ]),
+ ]}
+ onChange={handleChange}
+ basicSetup={{
+ lineNumbers: true,
+ highlightActiveLineGutter: true,
+ highlightSpecialChars: true,
+ history: true,
+ foldGutter: true,
+ drawSelection: true,
+ dropCursor: true,
+ allowMultipleSelections: true,
+ indentOnInput: true,
+ bracketMatching: true,
+ closeBrackets: true,
+ autocompletion: true,
+ highlightSelectionMatches: false,
+ }}
+ className="h-full"
+ />
+
+ );
+}
+
+CodeEditor.propTypes = {};
diff --git a/app/src/components/game/editors/custom_category.js b/app/src/components/game/editors/custom_category.js
new file mode 100644
index 0000000..b11adb7
--- /dev/null
+++ b/app/src/components/game/editors/custom_category.js
@@ -0,0 +1,79 @@
+/**
+ * @fileoverview Utility module for custom_category.js
+ *
+ * @module components.game.editors.custom_category
+ */
+
+import * as Blockly from "blockly/core";
+
+class CustomCategory extends Blockly.ToolboxCategory {
+ constructor(categoryDef, toolbox, opt_parent) {
+ super(categoryDef, toolbox, opt_parent);
+ }
+
+ init() {
+ super.init();
+
+ // Agora o DOM está criado, pode alterar cor do texto e ícone
+ const labelDom = this.rowDiv_.getElementsByClassName(
+ "blocklyToolboxCategoryLabel",
+ )[0];
+ if (labelDom) {
+ labelDom.style.color = "white";
+ }
+ if (this.iconDom_) {
+ this.iconDom_.style.color = "white";
+ }
+ }
+
+ addColourBorder_(colour) {
+ this.rowDiv_.style.backgroundColor = colour;
+ }
+
+ setSelected(isSelected) {
+ const labelDom = this.rowDiv_.getElementsByClassName(
+ "blocklyToolboxCategoryLabel",
+ )[0];
+ if (isSelected) {
+ this.rowDiv_.style.backgroundColor = "white";
+ labelDom.style.color = this.colour_;
+ this.iconDom_.style.color = this.colour_;
+ } else {
+ this.rowDiv_.style.backgroundColor = this.colour_;
+ labelDom.style.color = "white";
+ this.iconDom_.style.color = "white";
+ }
+ Blockly.utils.aria.setState(
+ this.htmlDiv_,
+ Blockly.utils.aria.State.SELECTED,
+ isSelected,
+ );
+ }
+
+ createIconDom_() {
+ const iconClass = this.toolboxItemDef_["css-icon"];
+ if (iconClass) {
+ const iconElement = document.createElement("i");
+ iconElement.className = iconClass;
+ iconElement.style.fontSize = "18px";
+ iconElement.style.marginRight = "8px";
+
+ return iconElement;
+ }
+
+ // fallback
+ const iconImg = document.createElement("img");
+ iconImg.src = "./logo_only.svg";
+ iconImg.alt = "Blockly Logo";
+ iconImg.width = 20;
+ iconImg.height = 20;
+ return iconImg;
+ }
+}
+
+Blockly.registry.register(
+ Blockly.registry.Type.TOOLBOX_ITEM,
+ Blockly.ToolboxCategory.registrationName,
+ CustomCategory,
+ true,
+);
diff --git a/app/src/components/game/editors/toolboxIcons.js b/app/src/components/game/editors/toolboxIcons.js
new file mode 100644
index 0000000..0180236
--- /dev/null
+++ b/app/src/components/game/editors/toolboxIcons.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Utility module for toolboxIcons.js
+ *
+ * @module components.game.editors.toolboxIcons
+ */
+
+export function getCategoryIcon(name) {
+ switch (name) {
+ case "Movimento":
+ return "fa fa-arrows-alt";
+ case "Repetição":
+ return "fa fa-refresh";
+ case "Lógica":
+ return "fa fa-code";
+ case "Sensores":
+ return "fa fa-eye";
+ case "Cor":
+ return "fa fa-paint-brush";
+ case "Tempo":
+ return "fa fa-clock";
+ case "Semáforo Carros":
+ return "fa fa-traffic-light";
+ case "Semáforo Pedestre":
+ return "fa fa-walking";
+ case "Multimídia":
+ return "fa fa-music";
+ case "Matemática":
+ return "fa fa-calculator";
+ case "Condicionais":
+ return "fa fa-check";
+ case "Funções":
+ return "fa fa-cogs";
+ case "Cena":
+ return "fa fa-flag";
+ case "Eventos":
+ return "fa fa-bell";
+ case "Texto":
+ return "fa fa-font";
+ case "Listas":
+ return "fa fa-list";
+ case "Variáveis":
+ return "fa fa-database";
+ case "Caneta":
+ return "fa fa-pencil-alt";
+ default:
+ return "fa fa-cube";
+ }
+}
diff --git a/app/src/components/game/modal/CodeArea.jsx b/app/src/components/game/modal/CodeArea.jsx
new file mode 100644
index 0000000..30d7e0a
--- /dev/null
+++ b/app/src/components/game/modal/CodeArea.jsx
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview React component for CodeArea.jsx
+ *
+ * @module components.game.modal.CodeArea
+ */
+
+import React from "react";
+import { Code } from "lucide-react";
+import PropTypes from "prop-types";
+
+export const CodeArea = ({
+ code,
+ title = "Código Gerado",
+ variant = "success",
+}) => {
+ const titleToShow = title;
+ const textColors = {
+ success: "text-green-400",
+ failure: "text-red-300",
+ };
+
+ return (
+
+
+
+
{titleToShow}
+
+
+
+
+ {code || "Nenhum código disponível"}
+
+
+
+ );
+};
+
+CodeArea.propTypes = {
+ code: PropTypes.any,
+ title: PropTypes.string,
+ variant: PropTypes.string,
+};
diff --git a/app/src/components/game/modal/FeedbackBox.jsx b/app/src/components/game/modal/FeedbackBox.jsx
new file mode 100644
index 0000000..636564e
--- /dev/null
+++ b/app/src/components/game/modal/FeedbackBox.jsx
@@ -0,0 +1,38 @@
+/**
+ * @fileoverview React component for FeedbackBox.jsx
+ *
+ * @module components.game.modal.FeedbackBox
+ */
+
+import React from "react";
+import PropTypes from "prop-types";
+
+export const FeedbackBox = ({ title, children, variant = "success" }) => {
+ const titleToShow = title;
+ const styles = {
+ success: "bg-green-50 border-green-200 text-green-800",
+ failure: "bg-amber-50 border-amber-200 text-amber-800",
+ };
+
+ const titleStyles = {
+ success: "text-green-900",
+ failure: "text-amber-900",
+ };
+
+ return (
+
+
+ {variant === "success" ? "💡" : "⚠️"} {titleToShow}
+
+
{children}
+
+ );
+};
+
+FeedbackBox.propTypes = {
+ title: PropTypes.string,
+ children: PropTypes.node,
+ variant: PropTypes.string,
+};
diff --git a/app/src/components/game/modal/ModalBase.jsx b/app/src/components/game/modal/ModalBase.jsx
new file mode 100644
index 0000000..427fabd
--- /dev/null
+++ b/app/src/components/game/modal/ModalBase.jsx
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview React component for ModalBase.jsx
+ *
+ * @module components.game.modal.ModalBase
+ */
+
+
+import React from "react";
+import { X } from "lucide-react";
+import PropTypes from "prop-types";
+
+export const ModalBase = ({ isOpen, onClose, children }) => {
+ if (!isOpen) return null;
+
+ return (
+ e.target === e.currentTarget && onClose && onClose()}
+ >
+
+ {children}
+
+
+ );
+};
+
+ModalBase.propTypes = {
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func,
+ children: PropTypes.node,
+};
diff --git a/app/src/components/game/modal/ModalHeader.jsx b/app/src/components/game/modal/ModalHeader.jsx
new file mode 100644
index 0000000..758acc2
--- /dev/null
+++ b/app/src/components/game/modal/ModalHeader.jsx
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview React component for ModalHeader.jsx
+ *
+ * @module components.game.modal.ModalHeader
+ */
+
+
+import React from "react";
+import { X, CheckCircle, AlertCircle } from "lucide-react";
+import PropTypes from "prop-types";
+
+export const ModalHeader = ({
+ title,
+ subTitle,
+ variant = "success",
+ onClose,
+ ariaCloseLabel,
+}) => {
+ const subtitleText = subTitle;
+
+ // Mapeamento de estilos por variante
+ const config = {
+ success: {
+ bgColor: "bg-green-100",
+ iconColor: "text-green-600",
+ Icon: CheckCircle,
+ },
+ failure: {
+ bgColor: "bg-red-100",
+ iconColor: "text-red-600",
+ Icon: AlertCircle,
+ },
+ };
+
+ const { bgColor, iconColor, Icon } = config[variant];
+
+
+
+ return (
+
+
+
+
+
+
+
{title}
+
{subtitleText}
+
+
+
+
+
+
+ );
+};
+
+ModalHeader.propTypes = {
+ title: PropTypes.string,
+ subTitle: PropTypes.string,
+ variant: PropTypes.string,
+ onClose: PropTypes.func,
+ ariaCloseLabel: PropTypes.string,
+};
diff --git a/app/src/components/letramento/AtividadeRuntimeFrame.jsx b/app/src/components/letramento/AtividadeRuntimeFrame.jsx
new file mode 100644
index 0000000..bfb93d4
--- /dev/null
+++ b/app/src/components/letramento/AtividadeRuntimeFrame.jsx
@@ -0,0 +1,111 @@
+import { useEffect, useMemo, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { letramentoEventBus, LETRAMENTO_EVENTS } from '../../utils/letramentoEvents';
+
+// Allowed event types from the iframe activity.
+// The HTML activity sends: window.parent.postMessage({ type: 'started' | 'running' | 'success' | 'failure' | 'completed', ...payload }, '*')
+const ALLOWED_TYPES = new Set(Object.values(LETRAMENTO_EVENTS));
+
+function generateChannelToken() {
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
+ return crypto.randomUUID();
+ }
+
+ return `ltr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
+}
+
+function resolveHtmlFile(htmlFile) {
+ if (/^https?:\/\//.test(htmlFile) || /^file:\/\//.test(htmlFile)) {
+ return htmlFile;
+ }
+
+ if (window.location.protocol === 'file:') {
+ const relativePath = htmlFile.startsWith('/') ? htmlFile.slice(1) : htmlFile;
+ return new URL(relativePath, window.location.href).toString();
+ }
+
+ return htmlFile.startsWith('/') ? htmlFile : `/${htmlFile}`;
+}
+
+function buildIframeSrc(htmlFile, channelToken) {
+ const [base, existingHash = ''] = htmlFile.split('#');
+ const resolvedBase = resolveHtmlFile(base);
+ const hashParams = new URLSearchParams(existingHash);
+ hashParams.set('channelToken', channelToken);
+ return `${resolvedBase}#${hashParams.toString()}`;
+}
+
+export default function AtividadeRuntimeFrame({ htmlFile, atividadeId, reloadToken = 0, className = '' }) {
+ const iframeRef = useRef(null);
+ const channelToken = useMemo(
+ () => generateChannelToken(),
+ [atividadeId, htmlFile, reloadToken],
+ );
+ const iframeSrc = useMemo(
+ () => buildIframeSrc(htmlFile, channelToken),
+ [htmlFile, channelToken],
+ );
+
+ const focusRuntime = () => {
+ const iframe = iframeRef.current;
+ if (!iframe) return;
+
+ iframe.focus();
+ iframe.contentWindow?.focus();
+ };
+
+ useEffect(() => {
+ const handleMessage = (event) => {
+ const iframeWindow = iframeRef.current?.contentWindow;
+ if (!iframeWindow || event.source !== iframeWindow) return;
+
+ // Accept only messages from the same origin or from sandboxed iframes.
+ // Sandboxed iframes (sandbox="allow-scripts" without allow-same-origin) always
+ // report event.origin as the string "null" — we allow that explicitly.
+ const allowedOrigin =
+ event.origin === window.location.origin || event.origin === 'null';
+ if (!allowedOrigin) return;
+
+ const { type, token, ...payload } = event.data ?? {};
+ if (token !== channelToken) return;
+ if (!type || !ALLOWED_TYPES.has(type)) return;
+
+ letramentoEventBus.dispatch(type, { atividadeId, ...payload });
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+ }, [atividadeId, channelToken]);
+
+ useEffect(() => {
+ // Focus right after mount/switch so keyboard-only users can start immediately.
+ const raf = window.requestAnimationFrame(() => {
+ focusRuntime();
+ window.setTimeout(focusRuntime, 50);
+ });
+
+ return () => window.cancelAnimationFrame(raf);
+ }, [atividadeId, htmlFile, reloadToken]);
+
+ return (
+
+ );
+}
+
+AtividadeRuntimeFrame.propTypes = {
+ htmlFile: PropTypes.string.isRequired,
+ atividadeId: PropTypes.string.isRequired,
+ reloadToken: PropTypes.number,
+ className: PropTypes.string,
+};
diff --git a/app/src/components/letramento/AtividadeStatusModal.jsx b/app/src/components/letramento/AtividadeStatusModal.jsx
new file mode 100644
index 0000000..05ed892
--- /dev/null
+++ b/app/src/components/letramento/AtividadeStatusModal.jsx
@@ -0,0 +1,98 @@
+import { useEffect } from 'react';
+import { CheckCircle, XCircle, RotateCcw, ArrowRight } from 'lucide-react';
+import PropTypes from 'prop-types';
+import { ATIVIDADE_STATES } from '../../contexts/LetramentoStateContext';
+
+export default function AtividadeStatusModal({ status, isOpen, onTryAgain, onDismiss, nextAtividadeId, onNextAtividade }) {
+ const isSuccess = status === ATIVIDADE_STATES.SUCCESS || status === ATIVIDADE_STATES.COMPLETED;
+ const isFailure = status === ATIVIDADE_STATES.FAILURE;
+
+ useEffect(() => {
+ if (!isOpen || (!isSuccess && !isFailure)) return undefined;
+
+ const handleKeyDown = (event) => {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ onDismiss();
+ return;
+ }
+
+ if (event.key === 'Enter' && isSuccess && nextAtividadeId && onNextAtividade) {
+ event.preventDefault();
+ onNextAtividade();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [isFailure, isOpen, isSuccess, nextAtividadeId, onDismiss, onNextAtividade]);
+
+ if (!isOpen || (!isSuccess && !isFailure)) return null;
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header — identical to SeletorDeFases */}
+
+
+
+ {isSuccess ? : }
+
+
+ {isSuccess ? 'Atividade concluída' : 'Tente novamente'}
+
+
+
+
+ {/* Body */}
+
+
+ {isSuccess
+ ? 'Você concluiu esta atividade com sucesso.'
+ : 'Não desanime. Você consegue tentar outra vez.'}
+
+
+
+ {/* Footer — identical to SeletorDeFases */}
+
+ {isFailure && (
+
+
+ Tentar Novamente
+
+ )}
+ {isSuccess && nextAtividadeId && onNextAtividade && (
+
+ Próxima Atividade
+
+
+ )}
+
+ Voltar à Lista
+
+
+
+
+ );
+}
+
+AtividadeStatusModal.propTypes = {
+ status: PropTypes.string.isRequired,
+ isOpen: PropTypes.bool.isRequired,
+ onTryAgain: PropTypes.func.isRequired,
+ onDismiss: PropTypes.func.isRequired,
+ nextAtividadeId: PropTypes.string,
+ onNextAtividade: PropTypes.func,
+};
diff --git a/app/src/components/letramento/AtividadesSidebar.jsx b/app/src/components/letramento/AtividadesSidebar.jsx
new file mode 100644
index 0000000..7a42dfc
--- /dev/null
+++ b/app/src/components/letramento/AtividadesSidebar.jsx
@@ -0,0 +1,204 @@
+import { useState } from 'react';
+import PropTypes from 'prop-types';
+import { listarAtividadesPorCategoria } from '../../atividades/letramento/letramentoRegistry';
+import { getCompletedPhases } from '../../services/letramentoStorage';
+import { getUnlockedAtividades } from '../../utils/phaseUtils';
+import { MousePointer, Keyboard, Lock, CheckCircle, ChevronLeft, ChevronRight } from 'lucide-react';
+
+const CATEGORIA_UI = {
+ mouse: {
+ Icon: MousePointer,
+ headerClass: 'bg-red-50',
+ iconWrapClass: 'bg-red-600',
+ currentClass: 'bg-red-50 border-red-500 ring-red-200',
+ currentBadgeClass: 'bg-red-600 text-white',
+ completedClass: 'bg-green-50 border-green-300 hover:border-green-400 hover:shadow',
+ completedBadgeClass: 'bg-green-100 text-green-600',
+ hoverBorderClass: 'hover:border-red-300',
+ },
+ teclado: {
+ Icon: Keyboard,
+ headerClass: 'bg-brand-50',
+ iconWrapClass: 'bg-brand-600',
+ currentClass: 'bg-brand-50 border-brand-500 ring-brand-200',
+ currentBadgeClass: 'bg-brand-600 text-white',
+ completedClass: 'bg-brand-50 border-brand-300 hover:border-brand-400 hover:shadow',
+ completedBadgeClass: 'bg-brand-100 text-brand-700',
+ hoverBorderClass: 'hover:border-brand-300',
+ },
+};
+
+/**
+ * Sidebar com lista de atividades da mesma categoria
+ * Mostra progresso e permite navegação
+ */
+export default function AtividadesSidebar({ atividadeAtual, categoria, onChangeAtividade, currentAtividadeCompleted = false }) {
+ const [isCollapsed, setIsCollapsed] = useState(false);
+ const atividades = listarAtividadesPorCategoria(categoria);
+ const categoriaUi = CATEGORIA_UI[categoria] ?? CATEGORIA_UI.mouse;
+
+ // Busca array de atividades completadas (similar ao SeletorDeFases)
+ const completedPhases = getCompletedPhases(categoria);
+ const optimisticCompleted = currentAtividadeCompleted
+ ? Array.from(new Set([...completedPhases, atividadeAtual]))
+ : completedPhases;
+
+ const unlockedPhases = getUnlockedAtividades(optimisticCompleted, atividades);
+
+ const atividadesDesbloqueadas = new Set(unlockedPhases);
+
+ const handleAtividadeClick = (atividadeId) => {
+ if (atividadesDesbloqueadas.has(atividadeId) && atividadeId !== atividadeAtual) {
+ onChangeAtividade(atividadeId);
+ }
+ };
+
+ const CategoriaIcon = categoriaUi.Icon;
+
+ return (
+
+ {/* Header da categoria */}
+
+ {!isCollapsed && (
+
+
+
+
+
+
{categoria}
+
{atividades.length} atividades
+
+
+ )}
+
+ {isCollapsed && (
+
+ )}
+
+
+ {/* Lista de atividades */}
+
+ {atividades.map((atividade, index) => {
+ const isAtual = atividade.id === atividadeAtual;
+ const isDesbloqueada = atividadesDesbloqueadas.has(atividade.id);
+ const isCompletada = optimisticCompleted.includes(atividade.id);
+
+ return (
+
handleAtividadeClick(atividade.id)}
+ disabled={!isDesbloqueada}
+ className={`
+ w-full mb-2 rounded-lg border-2 transition-all
+ ${isCollapsed ? 'p-2' : 'p-3'}
+ ${isAtual
+ ? `${categoriaUi.currentClass} ring-2 shadow-md`
+ : isCompletada
+ ? categoriaUi.completedClass
+ : isDesbloqueada
+ ? `bg-white border-gray-200 ${categoriaUi.hoverBorderClass} hover:shadow`
+ : 'bg-gray-50 border-gray-200 cursor-not-allowed opacity-60'
+ }
+ `}
+ title={isCollapsed ? `${atividade.titulo} - ${atividade.descricao}` : ''}
+ >
+ {isCollapsed ? (
+ // Versão colapsada - apenas ícone/número
+
+
+ {isCompletada ? (
+
+ ) : !isDesbloqueada ? (
+
+ ) : (
+ index + 1
+ )}
+
+
+ ) : (
+ // Versão expandida - conteúdo completo
+
+ {/* Número ou status icon */}
+
+ {isCompletada ? (
+
+ ) : !isDesbloqueada ? (
+
+ ) : (
+ index + 1
+ )}
+
+
+ {/* Título e descrição */}
+
+
+ {atividade.titulo}
+
+
+ {atividade.descricao}
+
+
+
+ )}
+
+ );
+ })}
+
+
+ {/* Footer com botão de colapsar */}
+
+ setIsCollapsed(!isCollapsed)}
+ className="w-full flex items-center justify-center gap-2 px-3 py-2 bg-white hover:bg-gray-100 border border-gray-300 rounded-lg transition-colors"
+ title={isCollapsed ? 'Expandir menu' : 'Recolher menu'}
+ >
+ {isCollapsed ? (
+
+ ) : (
+ <>
+
+ Recolher
+ >
+ )}
+
+
+
+ );
+}
+
+AtividadesSidebar.propTypes = {
+ atividadeAtual: PropTypes.string.isRequired,
+ categoria: PropTypes.string.isRequired,
+ onChangeAtividade: PropTypes.func.isRequired,
+ currentAtividadeCompleted: PropTypes.bool,
+};
diff --git a/app/src/components/letramento/LetramentoNavBar.jsx b/app/src/components/letramento/LetramentoNavBar.jsx
new file mode 100644
index 0000000..c35a590
--- /dev/null
+++ b/app/src/components/letramento/LetramentoNavBar.jsx
@@ -0,0 +1,29 @@
+import { Monitor, X } from 'lucide-react';
+import PropTypes from 'prop-types';
+
+export default function LetramentoNavBar({ titulo, onClose }) {
+ return (
+
+ );
+}
+
+LetramentoNavBar.propTypes = {
+ titulo: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired,
+};
diff --git a/app/src/components/letramento/TrilhaPassos.jsx b/app/src/components/letramento/TrilhaPassos.jsx
new file mode 100644
index 0000000..965b0ce
--- /dev/null
+++ b/app/src/components/letramento/TrilhaPassos.jsx
@@ -0,0 +1,63 @@
+import { Check } from 'lucide-react';
+import PropTypes from 'prop-types';
+
+// Displays a horizontal step trail — Windows wizard style.
+// currentStep is 1-based (matches the step numbers sent by the activity).
+// 0 = not started.
+export default function TrilhaPassos({ passos, currentStep, completed }) {
+ return (
+
+ {passos.map((passo, idx) => {
+ const stepNum = idx + 1;
+ const isDone = completed || stepNum < currentStep;
+ const isActive = !completed && stepNum === currentStep;
+
+ return (
+
+ {/* step circle */}
+
+
+ {isDone ? : stepNum}
+
+
+ {passo.label}
+
+
+
+ {/* connector */}
+ {idx < passos.length - 1 && (
+
+ )}
+
+ );
+ })}
+
+ );
+}
+
+TrilhaPassos.propTypes = {
+ passos: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number, label: PropTypes.string })).isRequired,
+ currentStep: PropTypes.number.isRequired,
+ completed: PropTypes.bool,
+};
+
+TrilhaPassos.defaultProps = { completed: false };
diff --git a/app/src/components/letramento/__tests__/AtividadeRuntimeFrame.test.jsx b/app/src/components/letramento/__tests__/AtividadeRuntimeFrame.test.jsx
new file mode 100644
index 0000000..21f9ee6
--- /dev/null
+++ b/app/src/components/letramento/__tests__/AtividadeRuntimeFrame.test.jsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import AtividadeRuntimeFrame from '../AtividadeRuntimeFrame';
+import { letramentoEventBus } from '../../../utils/letramentoEvents';
+
+function getRenderedIframe() {
+ return screen.getByTitle('Atividade de Letramento Digital');
+}
+
+function getChannelToken(iframe) {
+ const iframeUrl = new URL(iframe.getAttribute('src'), 'http://localhost');
+ return new URLSearchParams(iframeUrl.hash.slice(1)).get('channelToken');
+}
+
+describe('AtividadeRuntimeFrame', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('despacha eventos validos enviados pelo iframe com token correto', () => {
+ const dispatchSpy = vi.spyOn(letramentoEventBus, 'dispatch');
+
+ render(
+ ,
+ );
+
+ const iframe = getRenderedIframe();
+ const token = getChannelToken(iframe);
+
+ window.dispatchEvent(
+ new MessageEvent('message', {
+ data: { type: 'completed', token, score: 100 },
+ origin: 'null',
+ source: iframe.contentWindow,
+ }),
+ );
+
+ expect(dispatchSpy).toHaveBeenCalledWith('completed', {
+ atividadeId: 'atividade-segura',
+ score: 100,
+ });
+ });
+
+ it('ignora mensagens com token invalido', () => {
+ const dispatchSpy = vi.spyOn(letramentoEventBus, 'dispatch');
+
+ render(
+ ,
+ );
+
+ const iframe = getRenderedIframe();
+
+ window.dispatchEvent(
+ new MessageEvent('message', {
+ data: { type: 'completed', token: 'token-invalido', score: 100 },
+ origin: 'null',
+ source: iframe.contentWindow,
+ }),
+ );
+
+ expect(dispatchSpy).not.toHaveBeenCalled();
+ });
+
+ it('ignora mensagens vindas de outra source', () => {
+ const dispatchSpy = vi.spyOn(letramentoEventBus, 'dispatch');
+
+ render(
+ ,
+ );
+
+ const iframe = getRenderedIframe();
+ const token = getChannelToken(iframe);
+
+ window.dispatchEvent(
+ new MessageEvent('message', {
+ data: { type: 'completed', token, score: 100 },
+ origin: 'null',
+ source: window,
+ }),
+ );
+
+ expect(dispatchSpy).not.toHaveBeenCalled();
+ });
+});
\ No newline at end of file
diff --git a/app/src/config/__tests__/gameRegistry.test.js b/app/src/config/__tests__/gameRegistry.test.js
new file mode 100644
index 0000000..937abc7
--- /dev/null
+++ b/app/src/config/__tests__/gameRegistry.test.js
@@ -0,0 +1,197 @@
+import { describe, it, expect, afterEach } from "vitest";
+import {
+ GAMES_REGISTRY,
+ GAME_REGISTRY,
+ gameRegistryUtils,
+ getGameConfig,
+ getAllGames,
+ registerGame,
+} from "../gameRegistry";
+
+const EXPECTED_IDS = [
+ "aspirador",
+ "automato",
+ "cripto",
+ "molemash",
+ "puzzle",
+ "semaforo",
+ "turtle",
+];
+
+describe("GAMES_REGISTRY — estrutura", () => {
+ it("contem todos os 7 jogos esperados", () => {
+ expect(Object.keys(GAMES_REGISTRY)).toEqual(expect.arrayContaining(EXPECTED_IDS));
+ expect(Object.keys(GAMES_REGISTRY)).toHaveLength(EXPECTED_IDS.length);
+ });
+
+ it("cada jogo tem os campos obrigatorios", () => {
+ Object.entries(GAMES_REGISTRY).forEach(([id, config]) => {
+ expect(config.gameId, `${id} deve ter gameId`).toBeDefined();
+ expect(config.gameName, `${id} deve ter gameName`).toBeDefined();
+ expect(config.descricao, `${id} deve ter descricao`).toBeDefined();
+ expect(config.categoria, `${id} deve ter categoria`).toBeDefined();
+ expect(config.dificuldade, `${id} deve ter dificuldade`).toBeDefined();
+ });
+ });
+
+ it("gameId de cada entrada bate com a chave do registry", () => {
+ Object.entries(GAMES_REGISTRY).forEach(([key, config]) => {
+ expect(config.gameId).toBe(key);
+ });
+ });
+
+ it("GAME_REGISTRY e alias de GAMES_REGISTRY", () => {
+ expect(GAME_REGISTRY).toBe(GAMES_REGISTRY);
+ });
+
+ it("configs sao referencias diretas — sem loadGameConfig wrapper", () => {
+ expect(typeof GAMES_REGISTRY.aspirador).toBe("object");
+ expect(GAMES_REGISTRY.aspirador).not.toBeNull();
+ });
+});
+
+describe("getGameConfig()", () => {
+ it("retorna config de jogo existente", () => {
+ const config = getGameConfig("aspirador");
+ expect(config.gameId).toBe("aspirador");
+ });
+
+ it("lanca erro quando jogo nao existe", () => {
+ expect(() => getGameConfig("inexistente")).toThrow(/inexistente/);
+ });
+
+ it("lanca erro para ID vazio", () => {
+ expect(() => getGameConfig("")).toThrow();
+ });
+});
+
+describe("getAllGames()", () => {
+ it("retorna array com todos os 7 jogos", () => {
+ const games = getAllGames();
+ expect(games).toHaveLength(EXPECTED_IDS.length);
+ });
+
+ it("retorna array de objetos com gameId", () => {
+ getAllGames().forEach((g) => {
+ expect(g.gameId).toBeDefined();
+ });
+ });
+});
+
+describe("registerGame()", () => {
+ afterEach(() => {
+ delete GAMES_REGISTRY["test_game"];
+ });
+
+ it("registra um novo jogo e o torna acessivel via getGameConfig", () => {
+ const fakeGame = { gameId: "test_game", gameName: "Teste" };
+ const returned = registerGame(fakeGame);
+ expect(returned).toBe(fakeGame);
+ expect(getGameConfig("test_game")).toBe(fakeGame);
+ });
+
+ it("lanca erro quando gameId esta ausente", () => {
+ expect(() => registerGame({ gameName: "Sem ID" })).toThrow(/gameId/);
+ });
+});
+
+describe("gameRegistryUtils", () => {
+ describe("getActiveGames()", () => {
+ it("retorna todos os jogos", () => {
+ expect(gameRegistryUtils.getActiveGames()).toHaveLength(EXPECTED_IDS.length);
+ });
+ });
+
+ describe("getFeaturedGames()", () => {
+ it("retorna todos os jogos", () => {
+ expect(gameRegistryUtils.getFeaturedGames()).toHaveLength(EXPECTED_IDS.length);
+ });
+ });
+
+ describe("getGameById()", () => {
+ it("retorna config quando id existe", () => {
+ const g = gameRegistryUtils.getGameById("turtle");
+ expect(g.gameId).toBe("turtle");
+ });
+
+ it("retorna undefined quando id nao existe", () => {
+ expect(gameRegistryUtils.getGameById("xxx")).toBeUndefined();
+ });
+ });
+
+ describe("getGamesByCategory()", () => {
+ it("retorna apenas jogos da categoria pedida", () => {
+ const logicaGames = gameRegistryUtils.getGamesByCategory("Logica");
+ logicaGames.forEach((g) => expect(g.categoria).toBe("Logica"));
+ });
+
+ it("retorna array vazio para categoria inexistente", () => {
+ expect(gameRegistryUtils.getGamesByCategory("Categoria Inexistente")).toHaveLength(0);
+ });
+ });
+
+ describe("getGamesByDifficulty()", () => {
+ it("retorna apenas jogos com a dificuldade pedida", () => {
+ const games = gameRegistryUtils.getGamesByDifficulty("Iniciante");
+ games.forEach((g) => expect(g.dificuldade).toBe("Iniciante"));
+ });
+ });
+
+ describe("searchGames()", () => {
+ it("encontra jogo pelo nome (case-insensitive)", () => {
+ const results = gameRegistryUtils.searchGames("aspirador");
+ expect(results.some((g) => g.gameId === "aspirador")).toBe(true);
+ });
+
+ it("retorna array vazio para query sem match", () => {
+ expect(gameRegistryUtils.searchGames("zzzzz_inexistente_zzzzz")).toHaveLength(0);
+ });
+ });
+
+ describe("registerGame() / unregisterGame() / updateGame()", () => {
+ afterEach(() => {
+ delete GAMES_REGISTRY["util_test"];
+ });
+
+ it("registerGame adiciona jogo ao registry", () => {
+ gameRegistryUtils.registerGame({ gameId: "util_test", gameName: "X" });
+ expect(GAMES_REGISTRY["util_test"]).toBeDefined();
+ });
+
+ it("registerGame lanca erro sem id ou gameId", () => {
+ expect(() => gameRegistryUtils.registerGame({ gameName: "Sem" })).toThrow();
+ });
+
+ it("unregisterGame remove jogo do registry", () => {
+ GAMES_REGISTRY["util_test"] = { gameId: "util_test" };
+ gameRegistryUtils.unregisterGame("util_test");
+ expect(GAMES_REGISTRY["util_test"]).toBeUndefined();
+ });
+
+ it("updateGame mescla atualizacoes sobre config existente", () => {
+ GAMES_REGISTRY["util_test"] = { gameId: "util_test", gameName: "Old" };
+ gameRegistryUtils.updateGame("util_test", { gameName: "New" });
+ expect(GAMES_REGISTRY["util_test"].gameName).toBe("New");
+ expect(GAMES_REGISTRY["util_test"].gameId).toBe("util_test");
+ });
+
+ it("updateGame nao faz nada quando jogo nao existe", () => {
+ expect(() => gameRegistryUtils.updateGame("phantom", { x: 1 })).not.toThrow();
+ });
+ });
+
+ describe("getCategoriesWithCounts()", () => {
+ it("retorna objeto com pelo menos uma categoria", () => {
+ const cats = gameRegistryUtils.getCategoriesWithCounts();
+ expect(Object.keys(cats).length).toBeGreaterThan(0);
+ });
+
+ it("cada categoria tem array games com ao menos um jogo", () => {
+ const cats = gameRegistryUtils.getCategoriesWithCounts();
+ Object.values(cats).forEach((cat) => {
+ expect(Array.isArray(cat.games)).toBe(true);
+ expect(cat.games.length).toBeGreaterThan(0);
+ });
+ });
+ });
+});
diff --git a/app/src/config/categories.js b/app/src/config/categories.js
new file mode 100644
index 0000000..e3c4e65
--- /dev/null
+++ b/app/src/config/categories.js
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Categorias de jogos educacionais da plataforma.
+ *
+ * Define as categorias que organizam os jogos por tema pedagógico.
+ * Cada categoria possui: nome, descrição, cor de identificação e ícone unicode.
+ * Utilizado em filtros, navegação e metadados de jogos.
+ *
+ * @module config/categories
+ */
+
+/**
+ * Mapa de categorias educacionais disponíveis na plataforma.
+ * Organiza jogos por abordagem de ensino e competências desenvolvidas.
+ *
+ * @type {Object}
+ * @constant GAME_CATEGORIES
+ *
+ * @property {string} Lógica - Pensamento lógico e algoritmos
+ * @property {string} Variáveis - Armazenamento e manipulação de dados
+ * @property {string} Funções - Modularização e reutilização de código
+ * @property {string} Eventos - Programação orientada a eventos
+ *
+ * @example
+ * const categoryInfo = gameCategory['Lógica'];
+ * // { name: 'Lógica e Algoritmos', description: '...', color: '#3498db', icon: '🧠' }
+ */
+export const gameCategory = {
+ Lógica: {
+ name: "Lógica e Algoritmos",
+ description: "Jogos focados em pensamento lógico e estruturas algorítmicas",
+ color: "#3498db",
+ icon: "🧠",
+ },
+ Variáveis: {
+ name: "Variáveis e Dados",
+ description: "Jogos que ensinam sobre armazenamento e manipulação de dados",
+ color: "#e74c3c",
+ icon: "📊",
+ },
+ Funções: {
+ name: "Funções e Módulos",
+ description: "Jogos sobre criação e uso de funções reutilizáveis",
+ color: "#2ecc71",
+ icon: "⚙️",
+ },
+ Eventos: {
+ name: "Eventos e Interação",
+ description: "Jogos focados em programação orientada a eventos",
+ color: "#f39c12",
+ icon: "🎮",
+ },
+};
diff --git a/app/src/config/difficulty.js b/app/src/config/difficulty.js
new file mode 100644
index 0000000..3f242df
--- /dev/null
+++ b/app/src/config/difficulty.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Níveis de dificuldade dos jogos educacionais.
+ *
+ * Define qual é o público-alvo de cada jogo com base em conhecimento prévio.
+ * Cada nível possui: nome, descrição, cor de identificação e número de nível.
+ * Utilizado em seleção de jogos e filtros de dificuldade.
+ *
+ * @module config/difficulty
+ */
+
+/**
+ * Mapa de níveis de dificuldade para jogos educacionais.
+ * Organizados de Iniciante (1) até Avançado (3).
+ *
+ * @type {Object}
+ * @constant DIFFICULTY_LEVELS
+ *
+ * @property {string} Iniciante - Público principiante (nível 1)
+ * @property {string} Intermediário - Público com conhecimento básico (nível 2)
+ * @property {string} Avançado - Público experiente (nível 3)
+ *
+ * @example
+ * const level = difficultyLevels['Iniciante'];
+ * // { name: 'Iniciante', description: '...', color: '#27ae60', level: 1 }
+ */
+export const difficultyLevels = {
+ Iniciante: {
+ name: "Iniciante",
+ description: "Ideal para quem está começando",
+ color: "#27ae60",
+ level: 1,
+ },
+ Intermediário: {
+ name: "Intermediário",
+ description: "Para quem já tem conhecimento básico",
+ color: "#f39c12",
+ level: 2,
+ },
+ Avançado: {
+ name: "Avançado",
+ description: "Para usuários experientes",
+ color: "#e74c3c",
+ level: 3,
+ },
+};
diff --git a/app/src/config/gameRegistry.js b/app/src/config/gameRegistry.js
new file mode 100644
index 0000000..53734e6
--- /dev/null
+++ b/app/src/config/gameRegistry.js
@@ -0,0 +1,164 @@
+/**
+ * @fileoverview Registro centralizado de todos os jogos da plataforma.
+ *
+ * Carrega configurações de todos os 5 jogos educacionais (Autômato, Criptografia, Toupeira,
+ * Semáforo, Tartaruga) e fornece utilities para buscá-los, filtrá-los e gerenciá-los.
+ * Este é o ponto único de verdade para descoberta e acesso a jogos.
+ *
+ * @module config/gameRegistry
+ * @requires ../atividades/programacao/_/config/config.js
+ */
+
+import { gameConfig as ASPIRADOR_GAME_CONFIG } from "../atividades/programacao/aspirador/config/config.js";
+import { gameConfig as AUTOMATO_GAME_CONFIG } from "../atividades/programacao/automato/config/config.js";
+import { gameConfig as CRIPTO_GAME_CONFIG } from "../atividades/programacao/cripto/config/config.js";
+import { gameConfig as MOLE_MASH_GAME_CONFIG } from "../atividades/programacao/mole-mash/config/config.js";
+import { gameConfig as ORDERNACAO_GAME_CONFIG } from "../atividades/programacao/ordenacao/config/config.js";
+import { gameConfig as PUZZLE_GAME_CONFIG } from "../atividades/programacao/puzzle/config/config.js";
+import { gameConfig as SEMAFORO_GAME_CONFIG } from "../atividades/programacao/semaforo/config/config.js";
+import { gameConfig as TURTLE_GAME_CONFIG } from "../atividades/programacao/turtle/config/config.js";
+
+import { gameCategory as GAME_CATEGORIES } from "./categories.js";
+
+/**
+ * Registro centralizado de todos os 5 jogos da plataforma.
+ * Chave é o ID do jogo (usado em URLs e rotas).
+ *
+ * @type {Object}
+ * @constant GAMES_REGISTRY
+ *
+ * @property {GameConfig} aspirador - Jogo Aspirador (movimentação em grid)
+ * @property {GameConfig} automato - Jogo Autômato (movimentação em grid)
+ * @property {GameConfig} cripto - Jogo Criptografia (cifras)
+ * @property {GameConfig} molemash - Jogo Toupeira (reatividade)
+ * @property {GameConfig} ordenacao - Jogo Ordenação (algoritmos de ordenação)
+ * @property {GameConfig} puzzle - Jogo Quebra Cabeças (propriedades com combos)
+ * @property {GameConfig} semaforo - Jogo Semáforo (lógica condicional)
+ * @property {GameConfig} turtle - Jogo Tartaruga (desenho em canvas)
+ */
+export const GAMES_REGISTRY = {
+ aspirador: ASPIRADOR_GAME_CONFIG,
+ automato: AUTOMATO_GAME_CONFIG,
+ cripto: CRIPTO_GAME_CONFIG,
+ molemash: MOLE_MASH_GAME_CONFIG,
+ ordenacao: ORDERNACAO_GAME_CONFIG,
+ puzzle: PUZZLE_GAME_CONFIG,
+ semaforo: SEMAFORO_GAME_CONFIG,
+ turtle: TURTLE_GAME_CONFIG,
+};
+
+/**
+ * Utilitários para buscar, filtrar e gerenciar jogos do registry.
+ * Fornece interface única para acesso aos jogos com múltiplas estratégias de busca.
+ *
+ * @namespace gameRegistryUtils
+ * @type {Object}
+ */
+export const gameRegistryUtils = {
+ getActiveGames() {
+ return Object.values(GAMES_REGISTRY);
+ },
+
+ getFeaturedGames() {
+ return Object.values(GAMES_REGISTRY);
+ },
+
+ getGamesByCategory(categoria) {
+ return Object.values(GAMES_REGISTRY).filter(
+ (game) => game.categoria === categoria,
+ );
+ },
+
+ getGamesByDifficulty(dificuldade) {
+ return Object.values(GAMES_REGISTRY).filter(
+ (game) => game.dificuldade === dificuldade,
+ );
+ },
+
+ getGameById(id) {
+ return GAMES_REGISTRY[id];
+ },
+
+ searchGames(query) {
+ const searchTerm = query.toLowerCase();
+ return Object.values(GAMES_REGISTRY).filter(
+ (game) =>
+ game.gameName.toLowerCase().includes(searchTerm) ||
+ game.descricao.toLowerCase().includes(searchTerm) ||
+ game.conceitos.some((concept) =>
+ concept.toLowerCase().includes(searchTerm),
+ ),
+ );
+ },
+
+ getCategoriesWithCounts() {
+ const categories = {};
+ Object.values(GAMES_REGISTRY).forEach((game) => {
+ if (!categories[game.categoria]) {
+ categories[game.categoria] = {
+ ...GAME_CATEGORIES[game.categoria],
+ games: [],
+ };
+ }
+ categories[game.categoria].games.push(game);
+ });
+ return categories;
+ },
+
+ registerGame(gameConfig) {
+ if (!gameConfig.id && !gameConfig.gameId) {
+ throw new Error("Game must have an ID or gameId");
+ }
+ const id = gameConfig.gameId || gameConfig.id;
+ GAMES_REGISTRY[id] = gameConfig;
+ },
+
+ unregisterGame(gameId) {
+ delete GAMES_REGISTRY[gameId];
+ },
+
+ updateGame(gameId, updates) {
+ if (GAMES_REGISTRY[gameId]) {
+ GAMES_REGISTRY[gameId] = { ...GAMES_REGISTRY[gameId], ...updates };
+ }
+ },
+};
+
+export const getGameConfig = (gameId) => {
+ const config = GAMES_REGISTRY[gameId];
+ if (!config) {
+ throw new Error(`Jogo '${gameId}' não encontrado no registry`);
+ }
+ return config;
+};
+
+
+/**
+ * Retorna a lista de todas as configurações de jogos registradas.
+ * @returns {Array} Array com todas as configurações de jogos
+ */
+export const getAllGames = () => {
+ return Object.values(GAMES_REGISTRY);
+};
+
+
+/**
+ * Registra um novo jogo no registry global.
+ * Se `gameConfig.gameId` ou `gameConfig.id` não existir, lança erro.
+ * @param {GameConfig} gameConfig - Configuração do jogo a ser registrada
+ * @returns {GameConfig} A configuração registrada
+ * @throws {Error} Se `gameId` não for provido
+ */
+export const registerGame = (gameConfig) => {
+ if (!gameConfig.gameId) {
+ throw new Error("gameId é obrigatório para registrar um jogo");
+ }
+
+ const id = gameConfig.gameId || gameConfig.id;
+ GAMES_REGISTRY[id] = gameConfig;
+ return gameConfig;
+};
+
+export const GAME_REGISTRY = GAMES_REGISTRY;
+
+export default GAMES_REGISTRY;
diff --git a/app/src/config/type.js b/app/src/config/type.js
new file mode 100644
index 0000000..7ee20ef
--- /dev/null
+++ b/app/src/config/type.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Tipos de jogos da plataforma por abordagem de programação.
+ *
+ * Define os estilos de gameplay: blocos visuais, código texto ou seleção múltipla.
+ * Cada tipo possui: nome, descrição, cor e ícone uni code.
+ * Utilizado em metadados de jogos e filtros de descoberta.
+ *
+ * @module config/type
+ */
+
+/**
+ * Mapa de tipos de jogos disponíveis na plataforma.
+ * Representa diferentes abordagens de programação.
+ *
+ * @type {Object}
+ * @constant GAME_TYPES
+ *
+ * @property {string} blocks - Programação visual (Blockly)
+ * @property {string} code - Programação por texto (JavaScript)
+ * @property {string} options - Seleção múltipla (questões/escolhas)
+ *
+ * @example
+ * const blocklyGame = gameType['blocks'];
+ * // { name: 'Blocos', description: '...', color: '#8e44ad', icon: '🧩' }
+ */
+export const gameType = {
+ blocks: {
+ name: "Blocos",
+ description: "Jogos que utilizam programação visual baseada em blocos",
+ color: "#8e44ad",
+ icon: "🧩",
+ },
+ code: {
+ name: "Código",
+ description: "Jogos que envolvem escrita e compreensão de código",
+ color: "#C41E3A",
+ icon: "💻",
+ },
+ options: {
+ name: "Opções",
+ description: "Jogos que apresentam múltiplas escolhas ou caminhos",
+ color: "#e67e22",
+ icon: "🎲",
+ },
+};
diff --git a/app/src/contexts/EditorContext.jsx b/app/src/contexts/EditorContext.jsx
new file mode 100644
index 0000000..615612f
--- /dev/null
+++ b/app/src/contexts/EditorContext.jsx
@@ -0,0 +1,70 @@
+/**
+ * @fileoverview React component for EditorContext.jsx
+ *
+ * @module contexts.EditorContext
+ */
+
+
+import { createContext, useContext, useState, useCallback } from "react";
+import PropTypes from "prop-types";
+
+const EditorContext = createContext();
+
+/**
+ * Provider do contexto do editor que encapsula dados e handlers compartilhados
+ * entre componentes do editor (Blockly / Code Editor).
+ *
+ * @component
+ * @param {Object} props - Props do provider
+ * @param {React.ReactNode} props.children - Componentes filhos que consomem o contexto
+ * @param {Object} props.gameConfig - Configuração do jogo (gameId, fases, etc.)
+ * @param {number} props.currentPhase - Fase atualmente selecionada (1-indexed)
+ *
+ * @returns {JSX.Element} Provider com estado e métodos de interação do editor
+ */
+export function EditorProvider({ children, gameConfig, currentPhase }) {
+ const gameNameKey = `${gameConfig.gameId}-fase-${currentPhase}`;
+ const [editorData, setEditorData] = useState({});
+
+ const updateEditorData = useCallback((data) => {
+ setEditorData((prevData) => ({ ...prevData, ...data }));
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+EditorProvider.propTypes = {
+ children: PropTypes.node,
+ gameConfig: PropTypes.object,
+ currentPhase: PropTypes.number,
+};
+
+/**
+ * Hook para consumir o `EditorContext`.
+ * Lance um erro se usado fora de um `EditorProvider`.
+ *
+ * @hook
+ * @returns {Object} Context API do editor contendo: `gameNameKey`, `gameConfig`, `currentPhase`, `editorData`, `updateEditorData`
+ *
+ * @example
+ * const { gameNameKey, editorData, updateEditorData } = useEditor();
+ */
+export function useEditor() {
+ const ctx = useContext(EditorContext);
+ if (!ctx) {
+ throw new Error("useEditor deve ser usado dentro de EditorProvider");
+ }
+ return ctx;
+}
diff --git a/app/src/contexts/GameProgressContext.jsx b/app/src/contexts/GameProgressContext.jsx
new file mode 100644
index 0000000..5673b6a
--- /dev/null
+++ b/app/src/contexts/GameProgressContext.jsx
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Context isolado para progresso de fases do jogo.
+ *
+ * Gerencia: fase atual, fases completadas, persistência em localStorage e migração de dados.
+ *
+ * GameStateProvider compõe este provider internamente via Proxy Pattern,
+ * garantindo zero breaking changes: useGameState() continua expondo todos
+ * os campos de progresso no mesmo objeto de contexto.
+ *
+ * @module contexts.GameProgressContext
+ */
+
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import PropTypes from "prop-types";
+import { getUnlockedPhases } from "../utils/phaseUtils";
+
+const GameProgressContext = createContext(null);
+
+/**
+ * Provider que gerencia progresso de fases: fase atual, fases completadas
+ * e persistência bidirecional em localStorage (incluindo migração de chave legada).
+ *
+ * @component
+ * @param {Object} props
+ * @param {React.ReactNode} props.children
+ * @param {Object} props.gameConfig - Configuração do jogo
+ * @param {string} props.gameConfig.gameId - ID único do jogo
+ * @param {Array} props.gameConfig.fases - Array de fases
+ */
+export function GameProgressProvider({ children, gameConfig }) {
+ const [currentPhase, setCurrentPhase] = useState(1);
+ const [completedPhases, setCompletedPhases] = useState([]);
+
+ const storageKey = `${gameConfig.gameId}-completed-phases`;
+ const legacyStorageKey = `${gameConfig.gameId}-fases-concluidas`;
+ const initialized = useRef(false);
+
+ // Carrega progresso salvo na montagem; aplica debug mode se necessário.
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const debugKey = Array.from(urlParams.keys()).find(
+ (k) => k.toLowerCase() === "debug",
+ );
+ const isDebug = urlParams.get(debugKey)?.toLowerCase() === "true";
+
+ if (isDebug) {
+ const todas = Array.from(
+ { length: gameConfig.fases.length },
+ (_, i) => i + 1,
+ );
+ setCompletedPhases(todas);
+ localStorage.setItem(storageKey, JSON.stringify(todas));
+ setCurrentPhase(gameConfig.fases.length);
+ return;
+ }
+
+ if (initialized.current) return;
+
+ let fasesSalvas = null;
+
+ try {
+ const saved = localStorage.getItem(storageKey);
+ if (saved) {
+ fasesSalvas = JSON.parse(saved);
+ } else {
+ // Migração de chave legada
+ const legacySaved = localStorage.getItem(legacyStorageKey);
+ if (legacySaved) {
+ fasesSalvas = JSON.parse(legacySaved);
+ localStorage.setItem(storageKey, JSON.stringify(fasesSalvas));
+ console.info("[Migration] Data synced to new key", {
+ gameId: gameConfig.gameId,
+ migratedItems: fasesSalvas.length,
+ timestamp: new Date().toISOString(),
+ });
+ }
+ }
+ } catch (error) {
+ console.error(
+ "[Migration Error] Failed to restore phases from storage",
+ error,
+ );
+ fasesSalvas = null;
+ }
+
+ if (fasesSalvas && fasesSalvas.length > 0) {
+ setCompletedPhases(fasesSalvas);
+ const unlockedPhases = getUnlockedPhases(
+ fasesSalvas,
+ gameConfig.fases.length,
+ );
+ setCurrentPhase(Math.max(...unlockedPhases));
+ }
+
+ initialized.current = true;
+ }, [storageKey, legacyStorageKey, gameConfig.gameId, gameConfig.fases.length]);
+
+ // Persiste fases completadas sempre que mudam (após inicialização).
+ useEffect(() => {
+ if (!initialized.current) return;
+ try {
+ localStorage.setItem(storageKey, JSON.stringify(completedPhases));
+ } catch (error) {
+ console.error("[Storage Error] Failed to save completed phases", error);
+ }
+ }, [completedPhases, storageKey]);
+
+ /** Navega para uma fase específica (apenas progresso; execução fica em GameStateContext). */
+ const changePhase = useCallback((numeroFase) => {
+ setCurrentPhase(numeroFase);
+ }, []);
+
+ /** Remove todo o progresso e volta para a fase 1. */
+ const resetProgress = useCallback(() => {
+ setCompletedPhases([]);
+ setCurrentPhase(1);
+ localStorage.removeItem(storageKey);
+ }, [storageKey]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+GameProgressProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+ gameConfig: PropTypes.shape({
+ gameId: PropTypes.string.isRequired,
+ fases: PropTypes.array.isRequired,
+ }).isRequired,
+};
+
+/**
+ * Consome GameProgressContext diretamente.
+ * Consumidores que só precisam de dados de progresso podem migrar para este hook
+ * ao longo do tempo, sem depender do GameStateContext completo.
+ *
+ * @hook
+ * @returns {{ currentPhase, setCurrentPhase, completedPhases, setCompletedPhases, changePhase, resetProgress }}
+ */
+export function useGameProgress() {
+ const context = useContext(GameProgressContext);
+ if (!context) {
+ throw new Error(
+ "useGameProgress deve ser usado dentro de GameProgressProvider",
+ );
+ }
+ return context;
+}
diff --git a/app/src/contexts/GameStateContext.jsx b/app/src/contexts/GameStateContext.jsx
new file mode 100644
index 0000000..c984618
--- /dev/null
+++ b/app/src/contexts/GameStateContext.jsx
@@ -0,0 +1,372 @@
+/**
+ * @fileoverview React component for GameStateContext.jsx
+ *
+ * @module contexts.GameStateContext
+ */
+
+
+import React, {
+ createContext,
+ useContext,
+ useEffect,
+ useState,
+ useCallback,
+ useRef,
+} from "react";
+import PropTypes from "prop-types";
+import { gameEventBus } from "../utils/gameEvents";
+import { GameProgressProvider, useGameProgress } from "./GameProgressContext";
+
+export const GAME_STATES = {
+ PARADO: "parado",
+ EXECUTANDO: "executando",
+ SUCESSO: "sucesso",
+ FALHA: "falha",
+};
+
+const GameStateContext = createContext();
+
+/**
+ * Provedor de contexto global para o estado de execução do jogo.
+ * Gerencia: código gerado, estado de execução, fases completadas, mensagens de erro.
+ *
+ * @component
+ * @param {Object} props - Componente provider props
+ * @param {React.ReactNode} props.children - Componentes filhos
+ * @param {Object} props.gameConfig - Configuração do jogo (gameId, fases, etc)
+ * @param {string} props.gameConfig.gameId - ID único do jogo
+ * @param {Array} props.gameConfig.fases - Array de fases do jogo
+ *
+ * @returns {React.Context} BaseExecution context with executor methods and state
+ *
+ * @example
+ *
+ *
+ *
+ *
+ * @context
+ * - {@link useGameState} - Para consumir este contexto
+ */
+export function GameStateProvider({ children, gameConfig }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+/**
+ * Provider interno que consome GameProgressContext e gerencia estado de execução.
+ * Expõe via GameStateContext todos os campos (progresso + execução) para
+ * preservar compatibilidade com os consumidores existentes de useGameState().
+ *
+ * @private
+ */
+function GameStateInnerProvider({ children, gameConfig }) {
+ const {
+ currentPhase,
+ setCurrentPhase,
+ completedPhases,
+ setCompletedPhases,
+ changePhase: progressChangePhase,
+ resetProgress: progressResetProgress,
+ } = useGameProgress();
+
+ const [executionState, setExecutionState] = useState(GAME_STATES.PARADO);
+ const [generatedCode, setGeneratedCode] = useState("");
+ const [currentBlockCount, setCurrentBlockCount] = useState(0);
+ const [onWorkspaceChangeCallback, setOnWorkspaceChangeCallback] =
+ useState(null);
+ const [editorType, setEditorType] = useState("blockly"); // 'blockly' ou 'code'
+ const [codeEditorContent, setCodeEditorContent] = useState("");
+ const [failureMessage, setFailureMessage] = useState("");
+ const [isDebugMode, setIsDebugMode] = useState(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const debugKey = Array.from(urlParams.keys()).find(
+ (k) => k.toLowerCase() === "debug",
+ );
+ return urlParams.get(debugKey)?.toLowerCase() === "true";
+ });
+
+ const getCodeFromWorkspace = useRef(null);
+ const getCodeFromEditor = useRef(null);
+
+ /**
+ * Executa o código/workspace do editor de forma síncrona.
+ * Registra o código gerado e muda estado para EXECUTANDO.
+ * Valida se o editor registrou a função de execução antes de processar.
+ *
+ * @function execute
+ * @returns {void}
+ * @throws {console.error} Se editor não registrou a função de execução
+ */
+ const execute = () => {
+ if (editorType === "code") {
+ if (getCodeFromEditor.current) {
+ const codigo = getCodeFromEditor.current();
+
+ if (codigo && codigo.trim()) {
+ setGeneratedCode(codigo);
+ setExecutionState(GAME_STATES.EXECUTANDO);
+ } else {
+ console.error(
+ "CodeEditor ainda não registrou sua função de execução.",
+ );
+ }
+ }
+ } else {
+ if (getCodeFromWorkspace.current) {
+ const { codigo, workspace } = getCodeFromWorkspace.current();
+ if (codigo && workspace) {
+ setGeneratedCode({ codigo, workspace });
+ setExecutionState(GAME_STATES.EXECUTANDO);
+ }
+ } else {
+ console.error(
+ "BlocklyEditor ainda não registrou sua função de execução.",
+ );
+ }
+ }
+ };
+
+ /**
+ * Marca a execução como bem-sucedida e registra a fase como completada.
+ * Adiciona a fase ao array de fases concluídas se ainda não estiver incluída.
+ * Persiste as mudanças no localStorage.
+ *
+ * @function finalizeWithSuccess
+ * @returns {void}
+ */
+ const finalizeWithSuccess = () => {
+ setExecutionState(GAME_STATES.SUCESSO);
+
+ if (!completedPhases.includes(currentPhase)) {
+ setCompletedPhases([...completedPhases, currentPhase]);
+ }
+ };
+
+ /**
+ * Marca a execução como falhada.
+ * Atualiza o estado para FALHA sem limpar o código gerado.
+ * Permite que o jogador veja o código e os erros para corrigir.
+ *
+ * @function finalizeWithFailure
+ * @returns {void}
+ */
+ const finalizeWithFailure = () => {
+ setExecutionState(GAME_STATES.FALHA);
+ };
+
+ /**
+ * Reinicia o estado de execução e limpa o código gerado.
+ * Usado para permitir nova execução após sucesso/falha sem mudar de fase.
+ * Mantém a fase atual e fases completadas intactas.
+ *
+ * @function restart
+ * @returns {void}
+ */
+ const restart = () => {
+ setExecutionState(GAME_STATES.PARADO);
+ setGeneratedCode("");
+ };
+
+ /**
+ * Remove todo o progresso salvo e reseta fases para a primeira.
+ * Persiste a remoção no `localStorage` usando a chave do jogo.
+ *
+ * @function resetProgress
+ * @returns {void}
+ */
+ const resetProgress = () => {
+ progressResetProgress();
+ };
+
+ /**
+ * Navega para uma fase específica do jogo.
+ * Reseta o estado de execução, limpa o código gerado e conta de blocos.
+ * Usado quando o jogador seleciona uma nova fase no seletor.
+ *
+ * @function changePhase
+ * @param {number} numeroFase - Número da fase para navegar (1-indexed)
+ * @returns {void}
+ */
+ const changePhase = (numeroFase) => {
+ progressChangePhase(numeroFase);
+ setExecutionState(GAME_STATES.PARADO);
+ setGeneratedCode("");
+ setCurrentBlockCount(0);
+ setCodeEditorContent("");
+ };
+
+ /**
+ * Para a execução atual e limpa o código gerado sem alterar a fase.
+ * Usado para interromper execuções em andamento pelo usuário ou pela UI.
+ *
+ * @function stop
+ * @returns {void}
+ */
+ const stop = () => {
+ // Avisar Phaser para parar antes de mudar estado React
+ gameEventBus.stopExecution();
+ setExecutionState(GAME_STATES.PARADO);
+ setGeneratedCode("");
+ };
+
+ /**
+ * Registra a função que extrai código/workspace do editor Blockly.
+ * A função registrada deve retornar um objeto `{ codigo, workspace }`.
+ *
+ * @function registerExecutionFunction
+ * @param {Function} func - Função que retorna { codigo, workspace }
+ * @returns {void}
+ */
+ const registerExecutionFunction = useCallback((func) => {
+ getCodeFromWorkspace.current = func;
+ }, []);
+
+ /**
+ * Registra a função que retorna o código do editor de texto (code editor).
+ * Usado quando `editorType` é `code` para obter o código atual.
+ *
+ * @function registerCodeEditorFunction
+ * @param {Function} func - Função que retorna uma string com o código
+ * @returns {void}
+ */
+ const registerCodeEditorFunction = useCallback((func) => {
+ getCodeFromEditor.current = func;
+ }, []);
+
+ /**
+ * Callback disparado quando a workspace do Blockly sofre alterações.
+ * Atualiza contador de blocos e repassa para callback externo se fornecido.
+ *
+ * @function onWorkspaceChange
+ * @param {number} blockCount - Quantidade atual de blocos na workspace
+ * @returns {void}
+ */
+ const onWorkspaceChange = useCallback(
+ (blockCount) => {
+ setCurrentBlockCount(blockCount);
+ if (onWorkspaceChangeCallback) {
+ onWorkspaceChangeCallback(blockCount);
+ }
+ },
+ [onWorkspaceChangeCallback],
+ );
+
+ /**
+ * Callback para mudanças no editor de código (texto).
+ * Atualiza o conteúdo e ajusta o contador de blocos (1 se houver código, 0 caso contrário).
+ *
+ * @function onCodeEditorChange
+ * @param {string} content - Conteúdo atual do editor de código
+ * @returns {void}
+ */
+ const onCodeEditorChange = useCallback((content) => {
+ setCodeEditorContent(content);
+ setCurrentBlockCount(content.trim() ? 1 : 0);
+ }, []);
+
+ useEffect(() => {
+ if (editorType === "code" && getCodeFromEditor.current) {
+ setCurrentBlockCount(
+ codeEditorContent && codeEditorContent.trim() ? 1 : 0,
+ );
+ } else {
+ setCodeEditorContent(0);
+ }
+ }, [editorType, codeEditorContent]);
+
+
+
+ return (
+ setOnWorkspaceChangeCallback(() => fn),
+ editorType,
+ setEditorType,
+ codeEditorContent,
+ failureMessage,
+ setFailureMessage,
+ isDebugMode,
+ setIsDebugMode,
+ }}
+ >
+ {children}
+
+ );
+}
+
+/**
+ * Hook customizado para consumir o contexto global de estado do jogo.
+ * Fornece acesso aos estados de execução, progresso e métodos de controle do jogo.
+ *
+ * @hook
+ * @returns {Object} Objeto com estados e métodos do jogo:
+ * - Execução: executionState, execute(), finalizeWithSuccess(), finalizeWithFailure(), restart(), stop()
+ * - Progresso: currentPhase, changePhase(fase), completedPhases, resetProgress()
+ * - Código: generatedCode, codeEditorContent
+ * - Registro: registerExecutionFunction(), registerCodeEditorFunction()
+ * - Callbacks: onWorkspaceChange(), onCodeEditorChange()
+ * - Mensagens: failureMessage, setFailureMessage()
+ * - Debug: isDebugMode, setIsDebugMode()
+ * - Compatibilidade: aliases em português (estadoExecucao, codigoGerado, etc)
+ *
+ * @throws {Error} Se usado fora de GameStateProvider
+ *
+ * @example
+ * function MyComponent() {
+ * const { executionState, execute, currentPhase } = useGameState();
+ * return Executar ;
+ * }
+ */
+export function useGameState() {
+ const context = useContext(GameStateContext);
+ if (!context) {
+ throw new Error("useGameState deve ser usado dentro de GameStateProvider");
+ }
+ return context;
+}
+
+GameStateProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+ gameConfig: PropTypes.shape({
+ gameId: PropTypes.string.isRequired,
+ fases: PropTypes.array.isRequired,
+ }).isRequired,
+};
+
+GameStateInnerProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+ gameConfig: PropTypes.shape({
+ gameId: PropTypes.string.isRequired,
+ fases: PropTypes.array.isRequired,
+ }).isRequired,
+};
diff --git a/app/src/contexts/LetramentoStateContext.jsx b/app/src/contexts/LetramentoStateContext.jsx
new file mode 100644
index 0000000..22ee9a8
--- /dev/null
+++ b/app/src/contexts/LetramentoStateContext.jsx
@@ -0,0 +1,97 @@
+import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { letramentoEventBus, LETRAMENTO_EVENTS } from '../utils/letramentoEvents';
+import { saveProgress } from '../services/letramentoStorage';
+
+export const ATIVIDADE_STATES = {
+ IDLE: 'idle',
+ STARTED: 'started',
+ RUNNING: 'running',
+ SUCCESS: 'success',
+ FAILURE: 'failure',
+ COMPLETED: 'completed',
+};
+
+const LetramentoStateContext = createContext(null);
+
+export function LetramentoStateProvider({ children, atividadeId }) {
+ const [status, setStatus] = useState(ATIVIDADE_STATES.IDLE);
+ const [attempts, setAttempts] = useState(0);
+ const [score, setScore] = useState(null);
+ const [currentStep, setCurrentStep] = useState(0);
+
+ const persist = useCallback((update) => {
+ if (!atividadeId) return;
+ saveProgress(atividadeId, update);
+ }, [atividadeId]);
+
+ useEffect(() => {
+ const onStarted = ({ detail }) => {
+ setStatus(ATIVIDADE_STATES.STARTED);
+ persist({ status: ATIVIDADE_STATES.STARTED, startedAt: new Date().toISOString(), ...detail });
+ };
+
+ const onRunning = ({ detail }) => {
+ setStatus(ATIVIDADE_STATES.RUNNING);
+ if (detail?.step) setCurrentStep(detail.step);
+ persist({ status: ATIVIDADE_STATES.RUNNING, ...detail });
+ };
+
+ const onSuccess = ({ detail }) => {
+ setStatus(ATIVIDADE_STATES.SUCCESS);
+ setScore(detail?.score ?? null);
+ persist({ status: ATIVIDADE_STATES.SUCCESS, score: detail?.score ?? null, ...detail });
+ };
+
+ const onFailure = ({ detail }) => {
+ setAttempts((a) => {
+ const next = a + 1;
+ persist({ status: ATIVIDADE_STATES.FAILURE, attempts: next, ...detail });
+ return next;
+ });
+ setStatus(ATIVIDADE_STATES.FAILURE);
+ };
+
+ const onCompleted = ({ detail }) => {
+ setStatus(ATIVIDADE_STATES.COMPLETED);
+ persist({ status: ATIVIDADE_STATES.COMPLETED, completedAt: new Date().toISOString(), ...detail });
+ };
+
+ letramentoEventBus.addEventListener(LETRAMENTO_EVENTS.STARTED, onStarted);
+ letramentoEventBus.addEventListener(LETRAMENTO_EVENTS.RUNNING, onRunning);
+ letramentoEventBus.addEventListener(LETRAMENTO_EVENTS.SUCCESS, onSuccess);
+ letramentoEventBus.addEventListener(LETRAMENTO_EVENTS.FAILURE, onFailure);
+ letramentoEventBus.addEventListener(LETRAMENTO_EVENTS.COMPLETED, onCompleted);
+
+ return () => {
+ letramentoEventBus.removeEventListener(LETRAMENTO_EVENTS.STARTED, onStarted);
+ letramentoEventBus.removeEventListener(LETRAMENTO_EVENTS.RUNNING, onRunning);
+ letramentoEventBus.removeEventListener(LETRAMENTO_EVENTS.SUCCESS, onSuccess);
+ letramentoEventBus.removeEventListener(LETRAMENTO_EVENTS.FAILURE, onFailure);
+ letramentoEventBus.removeEventListener(LETRAMENTO_EVENTS.COMPLETED, onCompleted);
+ };
+ }, [atividadeId, persist]);
+
+ const reset = useCallback(() => {
+ setStatus(ATIVIDADE_STATES.IDLE);
+ setScore(null);
+ setCurrentStep(0);
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+LetramentoStateProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+ atividadeId: PropTypes.string.isRequired,
+};
+
+export function useLetramentoState() {
+ const ctx = useContext(LetramentoStateContext);
+ if (!ctx) throw new Error('useLetramentoState deve ser usado dentro de LetramentoStateProvider');
+ return ctx;
+}
diff --git a/app/src/contexts/__tests__/EditorContext.test.jsx b/app/src/contexts/__tests__/EditorContext.test.jsx
new file mode 100644
index 0000000..56d0270
--- /dev/null
+++ b/app/src/contexts/__tests__/EditorContext.test.jsx
@@ -0,0 +1,151 @@
+import { describe, it, expect } from "vitest";
+import { render, screen, act } from "@testing-library/react";
+import React from "react";
+import { EditorProvider, useEditor } from "../EditorContext";
+
+const mockGameConfig = {
+ gameId: "test-game",
+ gameName: "Test Game",
+ fases: [{ id: 1 }, { id: 2 }],
+};
+
+function ConsumerComponent() {
+ const { gameNameKey, gameConfig, currentPhase, editorData, updateEditorData } =
+ useEditor();
+ return (
+
+ {gameNameKey}
+ {gameConfig.gameId}
+ {currentPhase}
+ {editorData.toolboxJson ?? "none"}
+ updateEditorData({ toolboxJson: "updated-toolbox" })}
+ >
+ Update
+
+
+ );
+}
+
+function renderWithProvider(phase = 1) {
+ return render(
+
+
+
+ );
+}
+
+describe("EditorProvider", () => {
+ it("provides gameNameKey derived from gameId and currentPhase", () => {
+ renderWithProvider(1);
+ expect(screen.getByTestId("gameNameKey").textContent).toBe(
+ "test-game-fase-1"
+ );
+ });
+
+ it("updates gameNameKey when phase changes", () => {
+ renderWithProvider(3);
+ expect(screen.getByTestId("gameNameKey").textContent).toBe(
+ "test-game-fase-3"
+ );
+ });
+
+ it("provides gameConfig to consumers", () => {
+ renderWithProvider();
+ expect(screen.getByTestId("gameId").textContent).toBe("test-game");
+ });
+
+ it("provides currentPhase to consumers", () => {
+ renderWithProvider(2);
+ expect(screen.getByTestId("currentPhase").textContent).toBe("2");
+ });
+
+ it("initialises editorData as empty object", () => {
+ renderWithProvider();
+ expect(screen.getByTestId("toolboxJson").textContent).toBe("none");
+ });
+
+ it("updateEditorData merges new data into editorData", async () => {
+ renderWithProvider();
+ await act(async () => {
+ screen.getByRole("button").click();
+ });
+ expect(screen.getByTestId("toolboxJson").textContent).toBe(
+ "updated-toolbox"
+ );
+ });
+
+ it("updateEditorData preserves existing keys when adding new ones", async () => {
+ function MultiUpdateConsumer() {
+ const { editorData, updateEditorData } = useEditor();
+ return (
+
+ {editorData.key1 ?? "none"}
+ {editorData.key2 ?? "none"}
+ updateEditorData({ key1: "value1" })}
+ />
+ updateEditorData({ key2: "value2" })}
+ />
+
+ );
+ }
+ render(
+
+
+
+ );
+ await act(async () => {
+ screen.getByTestId("add-key1").click();
+ });
+ await act(async () => {
+ screen.getByTestId("add-key2").click();
+ });
+ expect(screen.getByTestId("key1").textContent).toBe("value1");
+ expect(screen.getByTestId("key2").textContent).toBe("value2");
+ });
+
+ it("does not expose phase or setEditorData in the context value", () => {
+ let contextValue;
+ function Inspector() {
+ contextValue = useEditor();
+ return null;
+ }
+ render(
+
+
+
+ );
+ expect(contextValue).not.toHaveProperty("phase");
+ expect(contextValue).not.toHaveProperty("setEditorData");
+ });
+
+ it("exposes exactly the expected keys", () => {
+ let contextValue;
+ function Inspector() {
+ contextValue = useEditor();
+ return null;
+ }
+ render(
+
+
+
+ );
+ expect(Object.keys(contextValue).sort()).toEqual(
+ ["currentPhase", "editorData", "gameConfig", "gameNameKey", "updateEditorData"].sort()
+ );
+ });
+});
+
+describe("useEditor", () => {
+ it("throws when used outside EditorProvider", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ expect(() => render( )).toThrow(
+ "useEditor deve ser usado dentro de EditorProvider"
+ );
+ spy.mockRestore();
+ });
+});
diff --git a/app/src/contexts/__tests__/GameProgressContext.test.jsx b/app/src/contexts/__tests__/GameProgressContext.test.jsx
new file mode 100644
index 0000000..500ad54
--- /dev/null
+++ b/app/src/contexts/__tests__/GameProgressContext.test.jsx
@@ -0,0 +1,221 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { render, screen, act, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+import {
+ GameProgressProvider,
+ useGameProgress,
+} from "../GameProgressContext";
+
+// ── helpers ──────────────────────────────────────────────────────────────────
+
+const GAME_CONFIG = {
+ gameId: "test-game",
+ fases: [{ id: 1 }, { id: 2 }, { id: 3 }],
+};
+
+function Consumer() {
+ const ctx = useGameProgress();
+ return (
+
+ {ctx.currentPhase}
+ {JSON.stringify(ctx.completedPhases)}
+ ctx.changePhase(2)}>change
+ ctx.resetProgress()}>reset
+ ctx.setCompletedPhases([1, 2])}>setCompleted
+ ctx.setCurrentPhase(3)}>setPhase
+
+ );
+}
+
+function renderProvider(gameConfig = GAME_CONFIG) {
+ return render(
+
+
+
+ );
+}
+
+// ── useGameProgress outside provider ─────────────────────────────────────────
+
+describe("useGameProgress", () => {
+ it("throws quando usado fora do provider", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ expect(() => render( )).toThrow(
+ "useGameProgress deve ser usado dentro de GameProgressProvider"
+ );
+ spy.mockRestore();
+ });
+});
+
+// ── initial state ─────────────────────────────────────────────────────────────
+
+describe("GameProgressProvider — estado inicial", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("fase inicial é 1 e completedPhases é []", () => {
+ renderProvider();
+ expect(screen.getByTestId("phase").textContent).toBe("1");
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ });
+});
+
+// ── localStorage load ──────────────────────────────────────────────────────────
+
+describe("GameProgressProvider — carregamento do localStorage", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("carrega fases completadas do localStorage", async () => {
+ localStorage.setItem("test-game-completed-phases", JSON.stringify([1, 2]));
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[1,2]");
+ });
+ });
+
+ it("define currentPhase como a maior fase desbloqueada", async () => {
+ // completed=[1,2] → unlocked=[1,2,3] → max=3
+ localStorage.setItem("test-game-completed-phases", JSON.stringify([1, 2]));
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("phase").textContent).toBe("3");
+ });
+ });
+
+ it("migra da chave legada quando storageKey não existe", async () => {
+ localStorage.setItem("test-game-fases-concluidas", JSON.stringify([1]));
+ const spy = vi.spyOn(console, "info").mockImplementation(() => {});
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[1]");
+ });
+ // Novo key deve ter sido gravado
+ expect(localStorage.getItem("test-game-completed-phases")).toBe("[1]");
+ spy.mockRestore();
+ });
+
+ it("trata erro de JSON inválido no localStorage sem quebrar", async () => {
+ localStorage.setItem("test-game-completed-phases", "INVALID JSON{{");
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ });
+ expect(spy).toHaveBeenCalledWith(
+ expect.stringContaining("Migration Error"),
+ expect.any(Error)
+ );
+ spy.mockRestore();
+ });
+});
+
+// ── debug mode ────────────────────────────────────────────────────────────────
+
+describe("GameProgressProvider — debug mode", () => {
+ afterEach(() => {
+ localStorage.clear();
+ });
+
+ it("em debug=true desbloqueia todas as fases", async () => {
+ delete window.location;
+ window.location = { search: "?debug=true" };
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[1,2,3]");
+ });
+ expect(screen.getByTestId("phase").textContent).toBe("3");
+ });
+
+ it("debug é case-insensitive (DEBUG=True)", async () => {
+ delete window.location;
+ window.location = { search: "?DEBUG=True" };
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[1,2,3]");
+ });
+ });
+
+ it("debug=false não ativa o modo debug", async () => {
+ delete window.location;
+ window.location = { search: "?debug=false" };
+ localStorage.clear();
+ renderProvider();
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ });
+ });
+});
+
+// ── changePhase ───────────────────────────────────────────────────────────────
+
+describe("GameProgressProvider — changePhase", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("atualiza currentPhase", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("change"));
+ expect(screen.getByTestId("phase").textContent).toBe("2");
+ });
+});
+
+// ── resetProgress ──────────────────────────────────────────────────────────────
+
+describe("GameProgressProvider — resetProgress", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("volta fase para 1 e limpa completedPhases", async () => {
+ const user = userEvent.setup();
+ localStorage.setItem("test-game-completed-phases", JSON.stringify([1]));
+ renderProvider();
+ // aguarda carga
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[1]");
+ });
+ await user.click(screen.getByText("reset"));
+ expect(screen.getByTestId("phase").textContent).toBe("1");
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ expect(localStorage.getItem("test-game-completed-phases")).toBe("[]");
+ });
+});
+
+// ── setCompletedPhases persiste ───────────────────────────────────────────────
+
+describe("GameProgressProvider — persistência ao mudar completedPhases", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("persiste no localStorage quando completedPhases muda (após init)", async () => {
+ const user = userEvent.setup();
+ // Primeiro carregamento: init é falso, efeito de persistência não dispara
+ localStorage.setItem("test-game-completed-phases", JSON.stringify([1]));
+ renderProvider();
+ await waitFor(() =>
+ expect(screen.getByTestId("completed").textContent).toBe("[1]")
+ );
+ // Trigger setCompletedPhases → 2º efeito persiste
+ await user.click(screen.getByText("setCompleted"));
+ await waitFor(() => {
+ expect(localStorage.getItem("test-game-completed-phases")).toBe("[1,2]");
+ });
+ });
+});
diff --git a/app/src/contexts/__tests__/GameStateContext.test.jsx b/app/src/contexts/__tests__/GameStateContext.test.jsx
new file mode 100644
index 0000000..780ea0d
--- /dev/null
+++ b/app/src/contexts/__tests__/GameStateContext.test.jsx
@@ -0,0 +1,452 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { render, screen, act, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+import { GameStateProvider, useGameState, GAME_STATES } from "../GameStateContext";
+
+// ── mock gameEventBus ─────────────────────────────────────────────────────────
+
+const { mockStopExecution } = vi.hoisted(() => ({
+ mockStopExecution: vi.fn(),
+}));
+vi.mock("../../utils/gameEvents", () => ({
+ gameEventBus: { stopExecution: mockStopExecution },
+}));
+
+// ── helpers ───────────────────────────────────────────────────────────────────
+
+const GAME_CONFIG = {
+ gameId: "test-game",
+ fases: [{ id: 1 }, { id: 2 }, { id: 3 }],
+};
+
+function Consumer() {
+ const ctx = useGameState();
+ return (
+
+ {ctx.executionState}
+ {ctx.currentPhase}
+ {JSON.stringify(ctx.completedPhases)}
+ {typeof ctx.generatedCode === "object"
+ ? JSON.stringify(ctx.generatedCode)
+ : ctx.generatedCode}
+
+ {ctx.currentBlockCount}
+ {ctx.editorType}
+ {ctx.codeEditorContent}
+ {ctx.failureMessage}
+ {String(ctx.isDebugMode)}
+
+ ctx.execute()}>execute
+ ctx.finalizeWithSuccess()}>success
+ ctx.finalizeWithFailure()}>failure
+ ctx.restart()}>restart
+ ctx.stop()}>stop
+ ctx.resetProgress()}>reset
+ ctx.setCurrentPhase(2)}>phase2
+ ctx.setEditorType("code")}>setCode
+ ctx.setFailureMessage("erro!")}>setFail
+ ctx.onWorkspaceChange(5)}>wsChange
+ ctx.onCodeEditorChange("x = 1")}>codeChange
+ ctx.onCodeEditorChange("")}>codeChangeClear
+ ctx.setOnWorkspaceChange((n) => {})}>setWsCb
+ {
+ ctx.registerExecutionFunction(() => ({ codigo: "js code", workspace: {} }));
+ }}>regBlockly
+ {
+ ctx.registerCodeEditorFunction(() => "my code");
+ }}>regCode
+ {
+ ctx.registerExecutionFunction(null);
+ ctx.execute();
+ }}>execNoWs
+
+ );
+}
+
+function renderProvider(gameConfig = GAME_CONFIG) {
+ return render(
+
+
+
+ );
+}
+
+// ── useGameState outside provider ─────────────────────────────────────────────
+
+describe("useGameState", () => {
+ it("throws quando usado fora de GameStateProvider", () => {
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ expect(() => render( )).toThrow(
+ "useGameState deve ser usado dentro de GameStateProvider"
+ );
+ spy.mockRestore();
+ });
+});
+
+// ── initial state ──────────────────────────────────────────────────────────────
+
+describe("GameStateProvider — estado inicial", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("executionState inicial é PARADO", () => {
+ renderProvider();
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.PARADO);
+ });
+
+ it("currentPhase inicial é 1", () => {
+ renderProvider();
+ expect(screen.getByTestId("phase").textContent).toBe("1");
+ });
+
+ it("completedPhases inicial é []", () => {
+ renderProvider();
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ });
+
+ it("editorType padrão é blockly", () => {
+ renderProvider();
+ expect(screen.getByTestId("editorType").textContent).toBe("blockly");
+ });
+
+ it("GAME_STATES exporta os 4 valores esperados", () => {
+ expect(GAME_STATES.PARADO).toBe("parado");
+ expect(GAME_STATES.EXECUTANDO).toBe("executando");
+ expect(GAME_STATES.SUCESSO).toBe("sucesso");
+ expect(GAME_STATES.FALHA).toBe("falha");
+ });
+});
+
+// ── isDebugMode via URL ────────────────────────────────────────────────────────
+
+describe("GameStateProvider — isDebugMode", () => {
+ afterEach(() => { localStorage.clear(); });
+
+ it("isDebugMode=true quando URL tem debug=true", () => {
+ delete window.location;
+ window.location = { search: "?debug=true" };
+ renderProvider();
+ expect(screen.getByTestId("debug").textContent).toBe("true");
+ });
+
+ it("isDebugMode=false quando sem parâmetro debug", () => {
+ delete window.location;
+ window.location = { search: "" };
+ renderProvider();
+ expect(screen.getByTestId("debug").textContent).toBe("false");
+ });
+});
+
+// ── execute — blockly ──────────────────────────────────────────────────────────
+
+describe("execute — modo blockly", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("muda para EXECUTANDO quando workspace registrada retorna código", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("regBlockly"));
+ await user.click(screen.getByText("execute"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.EXECUTANDO);
+ });
+
+ it("loga erro quando nenhuma função blockly foi registrada", async () => {
+ const user = userEvent.setup();
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ renderProvider();
+ await user.click(screen.getByText("execute"));
+ expect(spy).toHaveBeenCalledWith(
+ expect.stringContaining("BlocklyEditor")
+ );
+ spy.mockRestore();
+ });
+});
+
+// ── execute — code editor ──────────────────────────────────────────────────────
+
+describe("execute — modo code editor", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("muda para EXECUTANDO com editorType=code e função registrada", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("setCode"));
+ await user.click(screen.getByText("regCode"));
+ await user.click(screen.getByText("execute"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.EXECUTANDO);
+ });
+
+ it("não muda estado quando editorType=code mas nenhuma função registrada", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("setCode"));
+ await user.click(screen.getByText("execute"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.PARADO);
+ });
+});
+
+// ── finalizeWithSuccess ────────────────────────────────────────────────────────
+
+describe("finalizeWithSuccess", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("muda estado para SUCESSO", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("success"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.SUCESSO);
+ });
+
+ it("adiciona fase atual às completedPhases", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("success"));
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toContain("1");
+ });
+ });
+
+ it("não duplica a fase se já completada", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("success"));
+ await user.click(screen.getByText("success"));
+ await waitFor(() => {
+ const completed = JSON.parse(screen.getByTestId("completed").textContent);
+ expect(completed.filter((x) => x === 1)).toHaveLength(1);
+ });
+ });
+});
+
+// ── finalizeWithFailure ────────────────────────────────────────────────────────
+
+describe("finalizeWithFailure", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("muda estado para FALHA", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("failure"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.FALHA);
+ });
+});
+
+// ── restart ────────────────────────────────────────────────────────────────────
+
+describe("restart", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("volta para PARADO e limpa generatedCode", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("regBlockly"));
+ await user.click(screen.getByText("execute"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.EXECUTANDO);
+ await user.click(screen.getByText("restart"));
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.PARADO);
+ expect(screen.getByTestId("code").textContent.trim()).toBe("");
+ });
+});
+
+// ── stop ───────────────────────────────────────────────────────────────────────
+
+describe("stop", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ mockStopExecution.mockClear();
+ });
+
+ it("chama gameEventBus.stopExecution() e volta para PARADO", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("stop"));
+ expect(mockStopExecution).toHaveBeenCalled();
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.PARADO);
+ });
+});
+
+// ── resetProgress ──────────────────────────────────────────────────────────────
+
+describe("resetProgress", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("limpa progresso e volta para fase 1", async () => {
+ const user = userEvent.setup();
+ localStorage.setItem("test-game-completed-phases", JSON.stringify([1]));
+ renderProvider();
+ await waitFor(() =>
+ expect(screen.getByTestId("completed").textContent).toContain("1")
+ );
+ await user.click(screen.getByText("reset"));
+ await waitFor(() => {
+ expect(screen.getByTestId("completed").textContent).toBe("[]");
+ expect(screen.getByTestId("phase").textContent).toBe("1");
+ });
+ });
+});
+
+// ── changePhase (setCurrentPhase alias) ───────────────────────────────────────
+
+describe("changePhase via setCurrentPhase", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("muda fase, reseta estado e limpa código e contador", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("regBlockly"));
+ await user.click(screen.getByText("execute"));
+ await user.click(screen.getByText("wsChange")); // blockCount=5
+ await user.click(screen.getByText("phase2"));
+ expect(screen.getByTestId("phase").textContent).toBe("2");
+ expect(screen.getByTestId("state").textContent).toBe(GAME_STATES.PARADO);
+ expect(screen.getByTestId("code").textContent.trim()).toBe("");
+ expect(screen.getByTestId("blockCount").textContent).toBe("0");
+ });
+});
+
+// ── onWorkspaceChange ──────────────────────────────────────────────────────────
+
+describe("onWorkspaceChange", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("atualiza currentBlockCount", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("wsChange"));
+ expect(screen.getByTestId("blockCount").textContent).toBe("5");
+ });
+
+ it("chama o callback externo se registrado via setOnWorkspaceChange", async () => {
+ const user = userEvent.setup();
+ const externalCb = vi.fn();
+
+ function ConsumerWithCb() {
+ const ctx = useGameState();
+ return (
+
+ {ctx.currentBlockCount}
+ ctx.setOnWorkspaceChange(externalCb)}>setCb
+ ctx.onWorkspaceChange(7)}>trigger
+
+ );
+ }
+ render(
+
+
+
+ );
+ await user.click(screen.getByText("setCb"));
+ await user.click(screen.getByText("trigger"));
+ expect(externalCb).toHaveBeenCalledWith(7);
+ });
+});
+
+// ── onCodeEditorChange ─────────────────────────────────────────────────────────
+
+describe("onCodeEditorChange", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("atualiza codeEditorContent e blockCount=1 quando há conteúdo", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ // Precisamos estar em modo code com função registrada para o effect não sobrescrever
+ await user.click(screen.getByText("setCode"));
+ await user.click(screen.getByText("regCode"));
+ await user.click(screen.getByText("codeChange"));
+ await waitFor(() => {
+ expect(screen.getByTestId("codeContent").textContent).toBe("x = 1");
+ expect(screen.getByTestId("blockCount").textContent).toBe("1");
+ });
+ });
+
+ it("blockCount=0 quando conteúdo está vazio", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("codeChange"));
+ await user.click(screen.getByText("codeChangeClear"));
+ expect(screen.getByTestId("blockCount").textContent).toBe("0");
+ });
+});
+
+// ── setFailureMessage ──────────────────────────────────────────────────────────
+
+describe("setFailureMessage", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("atualiza failureMessage", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ await user.click(screen.getByText("setFail"));
+ expect(screen.getByTestId("failMsg").textContent).toBe("erro!");
+ });
+});
+
+// ── editorType effect ──────────────────────────────────────────────────────────
+
+describe("editorType effect", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ delete window.location;
+ window.location = { search: "" };
+ });
+
+ it("em modo code com função registrada, blockCount reflecte codeEditorContent vazio", async () => {
+ const user = userEvent.setup();
+ renderProvider();
+ // Registra função e entra em modo code com conteúdo
+ await user.click(screen.getByText("setCode"));
+ await user.click(screen.getByText("regCode"));
+ await user.click(screen.getByText("codeChange")); // codeEditorContent="x = 1" → blockCount=1
+ // Limpa conteúdo → effect roda: codeEditorContent.trim()="" → blockCount=0
+ await user.click(screen.getByText("codeChangeClear"));
+ await waitFor(() => {
+ expect(screen.getByTestId("blockCount").textContent).toBe("0");
+ });
+ });
+});
diff --git a/app/src/games/automato_code_editor/config.js b/app/src/games/automato_code_editor/config.js
new file mode 100644
index 0000000..2c8c42c
--- /dev/null
+++ b/app/src/games/automato_code_editor/config.js
@@ -0,0 +1,375 @@
+export const gameConfig = {
+ gameId: "automato_code_editor",
+ type: "code",
+ icon: "🤖",
+ gameName: "Autômato com Código",
+ descricao:
+ "Aprenda programação navegando por labirintos com código Javascript",
+ categoria: "Lógica",
+ tempoEstimado: "15-30 min",
+ dificuldade: "Intermediário",
+ conceitos: [
+ "Sequências",
+ "Loops/Repetição",
+ "Condicionais",
+ "Estruturas de controle",
+ ],
+ route: "/games/automato_code_editor",
+ component: "AutomatoGame",
+ ativo: true,
+ destaque: true,
+ prdReady: false,
+ obetivos: [
+ "Entender sequências de comandos",
+ "Usar loops para otimizar código",
+ "Aplicar condicionais para tomada de decisão",
+ "Resolver problemas de navegação",
+ ],
+ metadata: {
+ ultimaAtualizacao: "2025-09-07",
+ versao: "1.0.0",
+ },
+ allowedFunctions: [
+ "moverParaFrente",
+ "virarEsquerda",
+ "virarDireita",
+ "haCaminho",
+ "chegouNoAlvo",
+ ],
+ allowedControlStructures: ["if", "else", "while", "var"],
+ functionDocumentation: {
+ moverParaFrente: {
+ description: "Move o autômato uma posição para frente",
+ syntax: "moverParaFrente()",
+ returns: "void",
+ example: "moverParaFrente();",
+ },
+ virarEsquerda: {
+ description: "Vira o autômato 90° para a esquerda",
+ syntax: "virarEsquerda()",
+ returns: "void",
+ example: "virarEsquerda();",
+ },
+ virarDireita: {
+ description: "Vira o autômato 90° para a direita",
+ syntax: "virarDireita()",
+ returns: "void",
+ example: "virarDireita();",
+ },
+ haCaminho: {
+ description: "Verifica se há um caminho livre na direção especificada",
+ syntax: "haCaminho(direcao)",
+ parameters: ['direcao: "frente" | "esquerda" | "direita"'],
+ returns: "boolean",
+ example:
+ 'if (haCaminho("frente")) {\n //informe o código que será executado aqui \n }',
+ },
+ chegouNoAlvo: {
+ description: "Verifica se o autômato chegou ao objetivo",
+ syntax: "chegouNoAlvo()",
+ returns: "boolean",
+ example:
+ "while (!chegouNoAlvo()) {\n //informe o código que será executado aqui;\n }",
+ },
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Primeiro Passo",
+ descricao: "Aprenda a mover para frente",
+ maxBlocks: Infinity,
+ startPosition: { x: 3, y: 4 },
+ allowedBlocks: ["moveForward"],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 2, 1, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use a função moverParaFrente() para chegar ao alvo`,
+ },
+ {
+ id: 2,
+ nome: "Primeira Curva",
+ descricao: "Aprenda a virar à direita",
+ maxBlocks: Infinity,
+ startPosition: { x: 2, y: 4 },
+ allowedBlocks: ["moveForward", "turnRight"],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 1, 0, 0, 0],
+ [0, 0, 0, 0, 1, 0, 0, 0],
+ [0, 0, 0, 0, 3, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use moverParaFrente() e virarDireita() para chegar ao alvo`,
+ },
+ {
+ id: 3,
+ nome: "Vire para todos os lados",
+ descricao: "Aprenda a virar em todas as direções",
+ maxBlocks: Infinity,
+ startPosition: { x: 2, y: 4 },
+ allowedBlocks: ["moveForward", "turnLeft", "turnRight"],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 2, 1, 0, 0, 0],
+ [0, 0, 0, 0, 1, 0, 0, 0],
+ [0, 0, 0, 0, 1, 1, 3, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Aprenda um movimento novo: virarEsquerda()`,
+ },
+ {
+ id: 4,
+ nome: "Linha Reta",
+ descricao: "Use repetição para economizar blocos",
+ maxBlocks: 2,
+ startPosition: { x: 1, y: 4 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 2, 1, 1, 1, 1, 3, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Aplique a repetição "while" e use a condicional chegouNoAlvo() para repetir os comandos.`,
+ },
+ {
+ id: 5,
+ nome: "Escadaria",
+ descricao: "Navegue pela escadaria diagonal",
+ maxBlocks: 5,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 1],
+ [0, 0, 0, 0, 0, 0, 1, 1],
+ [0, 0, 0, 0, 0, 3, 1, 0],
+ [0, 0, 0, 0, 1, 1, 0, 0],
+ [0, 0, 0, 1, 1, 0, 0, 0],
+ [0, 0, 1, 1, 0, 0, 0, 0],
+ [0, 2, 1, 0, 0, 0, 0, 0],
+ [1, 1, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use repetição e todas as funções que aprendeu até agora para economizar blocos.`,
+ },
+ {
+ id: 6,
+ nome: "Torre",
+ descricao: "Suba a torre",
+ maxBlocks: 5,
+ startPosition: { x: 3, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 3, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 0, 0, 1, 1, 1, 0, 0],
+ [0, 0, 2, 1, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use comandos avulsos e repetição para superar esse desafio.`,
+ },
+ {
+ id: 7,
+ nome: "Caminho em Bloco",
+ descricao: "Use condicionais - verifique se há caminho à frente",
+ maxBlocks: 5,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 1, 0, 0],
+ [0, 1, 0, 0, 0, 1, 0, 0],
+ [0, 1, 1, 3, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0, 1, 0, 0],
+ [0, 2, 1, 1, 1, 1, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use a condicional "if" e a função haCaminho() para verificar se existe o caminho a frente:
+// exemplo:
+//
+//if (haCaminho("frente")) {
+// moverParaFrente();
+//}
+//
+// Use haCaminho("esquerda") e haCaminho("direita") para verificar os outros lados`,
+ },
+ {
+ id: 8,
+ nome: "Labirinto Ramificado",
+ descricao:
+ "Navegue por caminhos que se ramificam - use condicionais gerais",
+ maxBlocks: 10,
+ startPosition: { x: 1, y: 2 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 1, 1, 1, 1, 1, 0],
+ [0, 2, 1, 0, 0, 1, 0, 0],
+ [0, 0, 0, 1, 1, 1, 1, 0],
+ [0, 1, 1, 3, 0, 1, 0, 0],
+ [0, 0, 0, 1, 0, 1, 0, 0],
+ [0, 1, 1, 1, 1, 1, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Use a condicional "if" e a função haCaminho() para verificar se existe o caminho a frente:
+// exemplo:
+//
+//if (haCaminho("frente")) {
+// moverParaFrente();
+//}
+//
+// Use haCaminho("esquerda") e haCaminho("direita") para verificar os outros lados`,
+ },
+ {
+ id: 9,
+ nome: "Caminho Complexo",
+ descricao: "Um labirinto mais desafiador",
+ maxBlocks: 7,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 0, 0, 0],
+ [0, 1, 0, 0, 1, 1, 0, 0],
+ [0, 1, 1, 1, 0, 1, 0, 0],
+ [0, 0, 0, 1, 0, 1, 0, 0],
+ [0, 2, 1, 1, 0, 3, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ },
+ {
+ id: 10,
+ nome: "Labirinto Avançado",
+ descricao: "Use todas suas habilidades - agora com condicionais if/else",
+ maxBlocks: 10,
+ startPosition: { x: 5, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ "automato_ifElse",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 1, 1, 0, 0],
+ [0, 0, 1, 0, 0, 0, 0, 0],
+ [3, 1, 1, 1, 1, 1, 1, 0],
+ [0, 1, 0, 1, 0, 1, 1, 0],
+ [1, 1, 1, 1, 1, 0, 1, 0],
+ [0, 1, 0, 1, 0, 2, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Você pode ter estruturas de if/else mais complexas:
+//exemplo:
+//if (haCaminho("frente")) {
+// moverParaFrente();
+//} else if (haCaminho("esquerda")) {
+// virarEsquerda();
+// } else {
+// virarDireita();
+// }
+`,
+ },
+ {
+ id: 11,
+ nome: "Desafio Final",
+ descricao: "O último desafio - use tudo que aprendeu!",
+ maxBlocks: 10,
+ startPosition: { x: 1, y: 6 },
+ allowedBlocks: [
+ "moveForward",
+ "turnLeft",
+ "turnRight",
+ "automato_repeat_until_goal",
+ "automato_if",
+ "automato_ifElse",
+ ],
+ mapa: [
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 1, 1, 0, 3, 0, 1, 0],
+ [0, 1, 1, 0, 1, 1, 1, 0],
+ [0, 1, 0, 1, 0, 1, 0, 0],
+ [0, 1, 1, 1, 1, 1, 1, 0],
+ [0, 0, 0, 1, 0, 0, 1, 0],
+ [0, 2, 1, 1, 1, 0, 1, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ ],
+ initialCode: `// Este é o desafio final! Use tudo que aprendeu para chegar ao objetivo. Boa sorte!`,
+ },
+ ],
+
+ SquareType: {
+ WALL: 0,
+ OPEN: 1,
+ START: 2,
+ FINISH: 3,
+ },
+
+ DirectionType: {
+ NORTH: 0,
+ EAST: 1,
+ SOUTH: 2,
+ WEST: 3,
+ },
+
+ BlockColors: {
+ MOVEMENT: 290, // Roxo para movimento
+ LOOPS: 120, // Verde para loops
+ LOGIC: 210, // Azul para lógica
+ },
+};
diff --git a/app/src/games/bird/config/config.js b/app/src/games/bird/config/config.js
new file mode 100644
index 0000000..93da8dd
--- /dev/null
+++ b/app/src/games/bird/config/config.js
@@ -0,0 +1,239 @@
+export const gameConfig = {
+ gameId: "bird",
+ gameName: "Passáro",
+ type: "blocks",
+ icon: "🐦",
+ descricao:
+ "Ajude o Passáro a coletar comida e voltar ao ninho usando blocos Blockly.",
+ dificuldade: "Iniciante",
+ categoria: "Lógica",
+ tempoEstimado: "30-60 min",
+ conceitos: ["Loops/Repetição", "Condicionais", "Eventos"],
+ route: "/games/bird",
+ component: "BirdGame",
+ ativo: true,
+ destaque: true,
+ prdReady: false,
+ objectives: [
+ "Controlar o movimento do pássaro",
+ "Coletar comida",
+ "Retornar ao ninho com segurança",
+ ],
+ metadata: {
+ ultimaAtualizacao: "2025-09-07",
+ versao: "1.0.0",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Coletando Comida",
+ descricao: "Passe pela minhoca antes de ir para o ninho.",
+ start: { x: 20, y: 20 },
+ startAngle: 90,
+ worm: { x: 50, y: 50 },
+ nest: { x: 80, y: 80 },
+ obstacles: [],
+ axis: [],
+ allowedBlocks: ["mover_direcao", "enquanto_nao_chegou_ninho"],
+ },
+ {
+ id: 2,
+ nome: "Use Lógica",
+ descricao: "Use lógica para resolver o desafio.",
+ start: { x: 20, y: 20 },
+ startAngle: 0,
+ worm: { x: 80, y: 20 },
+ nest: { x: 80, y: 80 },
+ obstacles: [{ x0: 0, y0: 50, x1: 60, y1: 50 }],
+ axis: [],
+ allowedBlocks: [
+ "mover_direcao",
+ "nao_tem_minhoca",
+ "se_senao",
+ "enquanto_nao_chegou_ninho",
+ ],
+ },
+ {
+ id: 3,
+ nome: "Desviando Obstáculos",
+ descricao: "Desvie dos obstáculos para chegar ao ninho.",
+ start: { x: 20, y: 70 },
+ startAngle: 270,
+ worm: { x: 50, y: 20 },
+ nest: { x: 80, y: 70 },
+ obstacles: [{ x0: 40, y0: 60, x1: 40, y1: 100 }],
+ axis: [],
+ allowedBlocks: [
+ "mover_direcao",
+ "nao_tem_minhoca",
+ "se_senao",
+ "enquanto_nao_chegou_ninho",
+ ],
+ },
+ {
+ id: 4,
+ nome: "Movimentação com condicionais",
+ descricao: "Chegue ao ninho usando lógica, condicionais e variáveis.",
+ start: { x: 20, y: 80 },
+ startAngle: 0,
+ worm: null,
+ nest: { x: 80, y: 20 },
+ obstacles: [{ x0: 0, y0: 0, x1: 65, y1: 65 }],
+ axis: { x: true, y: false },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_senao",
+ "posicao",
+ "numero",
+ "comparador",
+ ],
+ },
+ {
+ id: 5,
+ nome: "Movimentação no eixo vertical",
+ descricao:
+ "Chegue ao ninho usando lógica, condicionais e variáveis, mas agora no eixo vertical (Y).",
+ start: { x: 80, y: 80 },
+ startAngle: 270,
+ worm: null,
+ nest: { x: 20, y: 20 },
+ obstacles: [{ x0: 0, y0: 100, x1: 65, y1: 35 }],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_senao",
+ "posicao",
+ "numero",
+ "comparador",
+ ],
+ },
+ {
+ id: 6,
+ nome: "Caminho Bloqueado",
+ descricao: "Desvie do obstáculo central para chegar ao ninho.",
+ start: { x: 20, y: 40 },
+ startAngle: 0,
+ worm: { x: 80, y: 20 },
+ nest: { x: 20, y: 80 },
+ obstacles: [{ x0: 0, y0: 59, x1: 50, y1: 59 }],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_mutavel",
+ "posicao",
+ "numero",
+ "comparador",
+ "logica_e",
+ ],
+ },
+ {
+ id: 7,
+ nome: "Desafio dos Muros",
+ descricao: "Passe por vários muros para chegar ao ninho.",
+ start: { x: 80, y: 80 },
+ startAngle: 180,
+ worm: { x: 80, y: 20 },
+ nest: { x: 20, y: 20 },
+ obstacles: [
+ { x0: 0, y0: 70, x1: 40, y1: 70 },
+ { x0: 70, y0: 50, x1: 100, y1: 50 },
+ ],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_mutavel",
+ "posicao",
+ "numero",
+ "comparador",
+ "logica_e",
+ ],
+ },
+ {
+ id: 8,
+ nome: "Labirinto",
+ descricao:
+ "Colete a comida e chegue ao ninho passando por vários obstáculos.",
+ start: { x: 20, y: 25 },
+ startAngle: 90,
+ worm: { x: 80, y: 25 },
+ nest: { x: 80, y: 75 },
+ obstacles: [
+ { x0: 40, y0: 0, x1: 40, y1: 20 },
+ { x0: 75, y0: 50, x1: 100, y1: 50 },
+ { x0: 50, y0: 100, x1: 50, y1: 75 },
+ { x0: 0, y0: 50, x1: 25, y1: 50 },
+ ],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_mutavel",
+ "posicao",
+ "numero",
+ "comparador",
+ "logica_e",
+ ],
+ },
+ {
+ id: 9,
+ nome: "Desafio Final",
+ descricao:
+ "Use tudo que aprendeu para coletar a comida e chegar ao ninho.",
+ start: { x: 80, y: 70 },
+ startAngle: 180,
+ worm: { x: 20, y: 20 },
+ nest: { x: 80, y: 20 },
+ obstacles: [
+ { x0: 0, y0: 72, x1: 31, y1: 100 },
+ { x0: 40, y0: 50, x1: 71, y1: 0 },
+ { x0: 80, y0: 50, x1: 100, y1: 50 },
+ ],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_mutavel",
+ "posicao",
+ "numero",
+ "comparador",
+ "logica_e",
+ ],
+ },
+ {
+ id: 10,
+ nome: "Labirinto Supremo",
+ descricao:
+ "O último desafio! Passe por todos os obstáculos, colete a comida e chegue ao ninho.",
+ start: { x: 20, y: 20 },
+ startAngle: 90,
+ worm: { x: 80, y: 50 },
+ nest: { x: 20, y: 20 },
+ obstacles: [
+ { x0: 40, y0: 60, x1: 60, y1: 60 },
+ { x0: 40, y0: 60, x1: 60, y1: 30 },
+ { x0: 60, y0: 30, x1: 100, y1: 30 },
+ ],
+ axis: { x: true, y: true },
+ allowedBlocks: [
+ "mover_direcao",
+ "enquanto_nao_chegou_ninho",
+ "nao_tem_minhoca",
+ "se_mutavel",
+ "posicao",
+ "numero",
+ "comparador",
+ "logica_e",
+ ],
+ },
+ ],
+};
diff --git a/app/src/games/detector-de-sentimentos/config.js b/app/src/games/detector-de-sentimentos/config.js
new file mode 100644
index 0000000..27c99b1
--- /dev/null
+++ b/app/src/games/detector-de-sentimentos/config.js
@@ -0,0 +1,97 @@
+// Detector de Sentimentos
+// - Conceito de IA: Análise de Sentimento (Sentiment Analysis)
+// •Tecnologia: Processamento de linguagem natural para classificar emoções em texto
+// •Biblioteca JavaScript: sentiment (npm) ou implementação própria com dicionário
+// •Cenário Lúdico: Um laboratório de sentimentos onde um cientista emoji usa uma máquina fantástica para analisar as frases da criança.
+// • Debate Ético: A IA consegue realmente entender sentimentos ou apenas reconhecer palavras? O que acontece quando ela interpreta errado uma piada ou sarcasmo?
+
+export const gameConfig = {
+ gameId: "detector-de-sentimentos",
+ gameName: "Detector de Sentimentos",
+ type: "blocks",
+ icon: "🔬",
+ descricao:
+ "Utilize blocos de programação para criar um laboratório que analisa sentimentos em textos usando inteligência artificial.",
+ dificuldade: "Intermediário",
+ categoria: "IA/ML",
+ tempoEstimado: "30-45 min",
+ conceitos: [
+ "Sequência",
+ "Eventos",
+ "Condicionais",
+ "Entrada de dados",
+ "Análise de texto",
+ "Inteligência Artificial",
+ "Interface do usuário",
+ "Lógica condicional",
+ ],
+ route: "/games/detector-de-sentimentos",
+ component: "DetectorSentimentosGame",
+ ativo: false,
+ destaque: false,
+ prdReady: false,
+ objectives: [
+ "Configure o laboratório de análise de sentimentos",
+ "Implemente a captura de entrada do usuário",
+ "Integre a análise de sentimentos com IA",
+ "Crie respostas visuais baseadas nos resultados",
+ "Adicione interações do cientista virtual",
+ ],
+ metadata: {
+ lastUpdated: "2025-09-20",
+ version: "1.0.0",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Configurando o Laboratório",
+ descricao:
+ "Configure o cenário do laboratório e implemente a primeira interação: capturar o texto do usuário e exibir uma resposta básica.",
+ exibeLoadingValidacao: true,
+ allowedBlocks: [
+ // EVENTOS
+ "configurar_laboratorio",
+ // FUNÇÕES
+ "inicializar_analisador",
+ "configurar_analisador",
+ "preparar_analise_sentimento",
+ ],
+ hint: "💡 Comece configurando o laboratório e o analisador. \n Ele pode trabalhar em Português, Espanhol ou Inglês!",
+ },
+ {
+ id: 2,
+ nome: "Analisando Sentimentos",
+ descricao:
+ "Integre a análise de sentimentos com IA e crie respostas condicionais baseadas no resultado (positivo, negativo ou neutro).",
+ exibeLoadingValidacao: true,
+ allowedBlocks: [
+ // CONDICIONAIS
+ "controls_if",
+ // EVENTOS
+ "ao_iniciar_jogo",
+ "quando_botao_clicado",
+ // ENTRADA/SAÍDA
+ "perguntar_e_aguardar",
+ "mostrar_mensagem",
+ "mostrar_emoji_gigante",
+ // FUNÇÕES
+ "configurar_laboratorio",
+ "cientista_diz",
+ // LÓGICA
+ "logic_boolean",
+ "logic_compare",
+ "logic_operation",
+ // IA/ANÁLISE
+ "sentimento_do_texto",
+ "analisar_sentimento",
+ // VARIÁVEIS
+ "criar_variavel",
+ "definir_variavel",
+ "obter_variavel",
+ // TEXTO
+ "texto_string",
+ ],
+ hint: '🤖 Use "sentimento do texto" para analisar o sentimento!\n😊 Combine com condicionais para mostrar emojis diferentes!\n🔬 Faça o cientista reagir aos resultados!',
+ },
+ ],
+};
diff --git a/app/src/games/emoji-match/config/config.js b/app/src/games/emoji-match/config/config.js
new file mode 100644
index 0000000..da80dc1
--- /dev/null
+++ b/app/src/games/emoji-match/config/config.js
@@ -0,0 +1,226 @@
+export const gameConfig = {
+ gameId: "emoji_match",
+ gameName: "Emoji Match",
+ type: "blocks",
+ icon: "😎",
+ descricao:
+ "Utilize blocos de programação para criar um jogo de memória, encontrando pares de emojis iguais.",
+ dificuldade: "Intermediário",
+ categoria: "Lógica",
+ tempoEstimado: "45-60 min",
+ conceitos: [
+ "Sequência",
+ "Eventos",
+ "Condicionais",
+ "Variáveis",
+ "Funções",
+ "Loops/Repetição",
+ "Matemática",
+ "Texto",
+ ],
+ route: "/games/emojimatch",
+ component: "EmojiMatchGame",
+ ativo: true,
+ destaque: true,
+ prdReady: false,
+ objectives: [
+ "Identificar emojis correspondentes",
+ "Usar lógica condicional para verificar pares",
+ "Implementar feedback visual para acertos e erros",
+ ],
+ metadata: {
+ lastUpdated: "2025-08-31",
+ version: "1.0.0",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Adicionando os Primeiros Emojis",
+ descricao:
+ "Aprenda sobre coordenadas X e Y adicionando um fundo e um emoji à tela usando blocos básicos.",
+ exibeLoadingValidacao: true,
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // MULTIMÍDIA
+ "selecionar_imagem_fundo",
+ // FUNÇÕES
+ "criar_fundo",
+ "adicionar_emoji",
+ ],
+ hint: "💡 Nossa tela tem 800 pixels de largura e 600 pixels de altura.\n Qual seria o ponto central? 🤔",
+ },
+ {
+ id: 2,
+ nome: "Criando o Grid de Emojis",
+ descricao:
+ "Preencha a tela com emojis usando laços de repetição e aprenda a trabalhar com variáveis e operações matemáticas.",
+ exibeLoadingValidacao: true,
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "adicionar_emoji_aleatorio",
+ // REPETIÇÃO
+ "loop_grid",
+ "obter_variavel_loop",
+ // MATEMÁTICA
+ "math_number",
+ "math_arithmetic",
+ // VARIÁVEIS
+ "definir_coordenada",
+ "obter_coordenada",
+ ],
+ hint: "1️⃣ Utilize loops para preencher o grid com emojis. \n 2️⃣ Cada emoji tem aproximadamente 70x70 pixels. \n 3️⃣ Os Emojis ficam melhor centralizados.",
+ },
+ {
+ id: 3,
+ nome: "Detectando Cliques",
+ descricao:
+ "Implemente a interação do jogador detectando cliques nos emojis e destacando o selecionado.",
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "criar_grid_emojis",
+ "configurar_destaque_emoji",
+ "definir_cor_destaque",
+ "definir_tamanho_destaque",
+ // EVENTOS
+ "quando_emoji_clicado",
+ // VARIÁVEIS
+ "definir_primeiro_emoji",
+ "obter_emoji_clicado",
+ // DESTAQUE
+ "destacar_emoji_selecionado",
+ ],
+ textoInstrucao:
+ "Clique em um emoji para validar o funcionamento do evento.",
+ hint: "💡 Lembre-se de que você pode usar variáveis \npara armazenar o emoji clicado.",
+ },
+ {
+ id: 4,
+ nome: "Lógica do Jogo: Encontrando o Par",
+ descricao:
+ "Utilize blocos condicionais para comparar emojis e definir o que acontece ao encontrar um par ou não.",
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "criar_grid_emojis",
+ "aplicar_destaque_salvo",
+ // EVENTOS
+ "quando_emoji_clicado",
+ // CONDICIONAIS
+ "se_primeiro_emoji_selecionado",
+ "controls_if",
+ // VARIÁVEIS
+ "definir_primeiro_emoji",
+ "limpar_primeiro_emoji",
+ "obter_emoji_clicado",
+ "obter_primeiro_emoji",
+ // LÓGICA
+ "comparar_emojis",
+ // DESTAQUE
+ "destacar_emoji_selecionado",
+ // FUNÇÕES
+ "vencer_o_jogo",
+ ],
+ textoInstrucao: "Encontre o único par de emojis idênticos!",
+ hint: "💡 Utilize blocos condicionais para\ncomparar os emojis selecionados.",
+ },
+ {
+ id: 5,
+ nome: "Adicionando Pontuação",
+ descricao:
+ "Adicione um placar ao jogo,utilizando variáveis, \nblocos de texto e operações matemáticas para atualizar a pontuação.",
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // EVENTOS
+ "quando_emoji_clicado",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "criar_grid_emojis",
+ "aplicar_destaque_salvo",
+ "definir_pontuacao_inicial",
+ // CONDICIONAIS / LÓGICA
+ "se_primeiro_emoji_selecionado",
+ "controls_if",
+ "comparar_emojis",
+ // VARIÁVEIS
+ "definir_primeiro_emoji",
+ "limpar_primeiro_emoji",
+ "obter_emoji_clicado",
+ "obter_primeiro_emoji",
+ "obter_pontuacao",
+ // TEXTO
+ "adicionar_texto",
+ "atualizar_texto",
+ "text",
+ "text_join",
+ // MATEMÁTICA
+ "math_number",
+ "math_arithmetic",
+ // DESTAQUE
+ "destacar_emoji_selecionado",
+ // FUNÇÕES
+ "vencer_o_jogo",
+ ],
+ hint: "💡 Replique a solução anterior,\nmas agora você poderá adicionar\npontos a cada par encontrado!",
+ },
+ {
+ id: 6,
+ nome: "Adicionando tempo do Jogo",
+ descricao:
+ "Inclua um cronômetro para limitar o tempo da partida e exiba o tempo restante na tela.",
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "criar_grid_emojis",
+ "aplicar_destaque_salvo",
+ // EVENTOS
+ "ao_clicar_no_emoji",
+ "configura_pontuacao",
+ // TEMPO
+ "definir_tempo_jogo",
+ "a_cada_segundo",
+ "tempo_restante",
+ // TEXTO
+ "adicionar_texto",
+ "atualizar_texto",
+ "text",
+ "text_join",
+ // MATEMÁTICA
+ "math_number",
+ ],
+ hint: '💡 Use o bloco "A cada segundo" para atualizar o \ntempo restante, mas sem iniciar a variável,\nnada funciona!',
+ },
+ {
+ id: 7,
+ nome: "Jogo Livre",
+ descricao:
+ "Explore livremente todos os blocos e crie suas próprias regras e desafios com emojis.",
+ allowedBlocks: [
+ // CENA
+ "ao_iniciar_jogo",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "criar_grid_emojis",
+ "aplicar_destaque_salvo",
+ // EVENTOS
+ "ao_clicar_no_emoji",
+ "configura_pontuacao",
+ // TEMPO
+ "definir_tempo_jogo",
+ "configurar_tempo",
+ ],
+ hint: "💡 Sinta-se à vontade para\n experimentar o jogo que você criou completo!",
+ },
+ ],
+};
diff --git a/app/src/games/motoca/config/config.js b/app/src/games/motoca/config/config.js
new file mode 100644
index 0000000..f136ba2
--- /dev/null
+++ b/app/src/games/motoca/config/config.js
@@ -0,0 +1,132 @@
+export const gameConfig = {
+ gameId: "motoca",
+ gameName: "Saindo do Buraco",
+ type: "blocks",
+ icon: "🏍️",
+ descricao:
+ "Utilize blocos de programação para criar recriar o jogo Saindo do Buraco.",
+ dificuldade: "Intermediário",
+ categoria: "Lógica",
+ tempoEstimado: "45-60 min",
+ conceitos: [
+ "Sequência",
+ "Eventos",
+ "Condicionais",
+ "Variáveis",
+ "Funções",
+ "Loops/Repetição",
+ "Matemática",
+ "Texto",
+ ],
+ route: "/games/motoca",
+ component: "MotocaGame",
+ ativo: true,
+ destaque: true,
+ prdReady: false,
+ objectives: [
+ "Configure o cenário do jogo",
+ "Implemente a lógica para mover a motoca",
+ "Implemente a lógica para evitar os buracos",
+ "Adicione pontuação e condições de vitória",
+ "Adicione um cronômetro para aumentar o desafio",
+ ],
+ metadata: {
+ lastUpdated: "2025-09-11",
+ version: "1.0.0",
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Adicionando os Primeiros Elementos",
+ descricao:
+ "Aprenda sobre coordenadas X e Y adicionando um fundo e o Motoca à tela usando blocos básicos.",
+ exibeLoadingValidacao: true,
+ allowedBlocks: [
+ // EVENTOS
+ "ao_iniciar_jogo",
+ // MULTIMÍDIA
+ "selecionar_imagem_fundo",
+ "selecionar_imagem_motoca",
+ // FUNÇÕES
+ "criar_fundo",
+ "criar_motoca",
+ ],
+ hint: "💡 Nossa tela tem 800 pixels de largura e 600 pixels de altura.\n Qual seria o ponto central? 🤔",
+ },
+ {
+ id: 2,
+ nome: "Movendo a Motoca",
+ descricao:
+ "Use eventos e blocos de controle para mover a motoca para a esquerda e para a direita.",
+ exibeLoadingValidacao: true,
+ hint: " Para ter sucesso, ao executar o código\n tente mover a moto para esquerda e para direita.",
+ allowedBlocks: [
+ // CONTROLES
+ "controls_if",
+ // EVENTOS
+ "ao_iniciar_jogo",
+ "ao_toca_na_tela",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "adicionar_motoca",
+ "mover",
+ // LÓGICA
+ "clicou_do_lado",
+ // MATEMÁTICA
+ "angulo",
+ ],
+ },
+ {
+ id: 3,
+ nome: "Evitando os Buracos",
+ descricao:
+ "Adicione buracos que aparecem aleatoriamente e implemente a lógica para evitar que a motoca caia neles. Tente sobreviver por pelo menos 10 segundos!",
+ exibeLoadingValidacao: false,
+ tempoMinimoSobrevivencia: 10000,
+ allowedBlocks: [
+ // EVENTOS
+ "ao_iniciar_jogo",
+ "ao_tocar_buraco",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "adicionar_motoca",
+ "adicionar_buracos",
+ "configura_movimentacao_motoca",
+ "parar_jogo",
+ ],
+ },
+ {
+ id: 4,
+ nome: "Movimentação automática da Moto",
+ descricao:
+ "Implemente a movimentação automática da motoca para frente e para trás, além de evitar os buracos. Tente sobreviver por pelo menos 20 segundos!",
+ exibeLoadingValidacao: false,
+ tempoMinimoSobrevivencia: 30000,
+ allowedBlocks: [
+ // CONTROLES
+ "controls_if",
+ // EVENTOS
+ "ao_iniciar_jogo",
+ "ao_mover_automaticamente",
+ "ao_tocar_buraco",
+ // FUNÇÕES
+ "adicionar_fundo",
+ "adicionar_motoca",
+ "adicionar_buracos",
+ "mover",
+ "parar_jogo",
+ // LÓGICA
+ "logic_boolean",
+ "logic_compare",
+ "logic_operation",
+ "logic_negate",
+ // MATEMÁTICA
+ "angulo",
+ // SENSORES
+ "ha_obstaculo_na_direcao",
+ "caminho_seguro",
+ "perto_do_limite",
+ ],
+ },
+ ],
+};
diff --git a/app/src/games/semaforo_code_editor/config.js b/app/src/games/semaforo_code_editor/config.js
new file mode 100644
index 0000000..6e4f759
--- /dev/null
+++ b/app/src/games/semaforo_code_editor/config.js
@@ -0,0 +1,214 @@
+export const gameConfig = {
+ gameId: "semaforo_code_editor",
+ gameName: "Semáforo com Código",
+ type: "code",
+ icon: "🚦",
+ descricao: "Controle o semáforo de carros e pedestres com código Javascript.",
+ dificuldade: "Iniciante",
+ categoria: "Lógica",
+ tempoEstimado: "30-45 min",
+ conceitos: ["Sequência", "Eventos", "Condicionais"],
+ route: "/games/semaforo_code_editor",
+ component: "SemaforoGame",
+ ativo: true,
+ destaque: false,
+ prdReady: false,
+ objectives: [
+ "Controlar o semáforo de carros",
+ "Usar eventos para interação",
+ "Aplicar lógica condicional para segurança",
+ ],
+ metadata: {
+ lastUpdated: "2025-08-17",
+ version: "1.0.0",
+ },
+ allowedFunctions: [
+ "mudarSemaforo",
+ "mudarSemaforoPedestre",
+ "aguardarSegundos",
+ "piscarLuzPedestre",
+ "tocarSom",
+ "pararSom",
+ "while",
+ ],
+ functionDocumentation: {
+ mudarSemaforo: {
+ description: "Muda a cor do semáforo dos carros.",
+ syntax: "mudarSemaforo(cor)",
+ parameters: ["cor: 'verde' | 'amarelo' | 'vermelho'"],
+ returns: "void",
+ example: "mudarSemaforo('verde');",
+ },
+ mudarSemaforoPedestre: {
+ description: "Muda a cor do semáforo de pedestres.",
+ syntax: "mudarSemaforoPedestre(cor)",
+ parameters: ["cor: 'verde' | 'vermelho'"],
+ returns: "void",
+ example: "mudarSemaforoPedestre('verde');",
+ },
+ aguardarSegundos: {
+ description:
+ "Faz o programa esperar por um número específico de segundos.",
+ syntax: "aguardarSegundos(segundos)",
+ parameters: ["segundos: number"],
+ returns: "void",
+ example: "aguardarSegundos(5);",
+ },
+ piscarLuzPedestre: {
+ description:
+ "Faz a luz do semáforo de pedestres piscar por um determinado número de segundos.",
+ syntax: "piscarLuzPedestre(cor, segundos)",
+ parameters: ["cor: 'vermelho' | 'verde'", "segundos: number"],
+ returns: "void",
+ example: "piscarLuzPedestre('vermelho', 3);",
+ },
+ tocarSom: {
+ description: "Toca um som específico.",
+ syntax: "tocarSom(nomeDoSom)",
+ parameters: ["nomeDoSom: 'beep_semaforo' | 'barulho_cidade'"],
+ returns: "void",
+ example: "tocarSom('barulho_cidade');",
+ },
+ while: {
+ description:
+ "Cria um loop que repete o bloco de código enquanto a condição for verdadeira.",
+ syntax: "while (condição) { // código a ser repetido }",
+ parameters: ["condição: boolean"],
+ returns: "void",
+ example: "while (true) {\n // código a ser repetido \n}",
+ },
+ },
+ fases: [
+ {
+ id: 1,
+ nome: "Controle básico do semáforo de carros",
+ descricao:
+ "Programe o semáforo dos carros para alternar entre verde, amarelo e vermelho.",
+ allowedBlocks: ["mudar_semaforo"],
+ temPedestre: false,
+ timeout: 15,
+ validationRegex: /mudarSemaforo\s*\(\s*'([^']+)'\s*\)\s*;/g,
+ expectedSequence: ["verde", "amarelo", "vermelho"],
+ initialCode: `// Use o bloco "mudar semáforo" para controlar as cores do semáforo dos carros.`,
+ },
+ {
+ id: 2,
+ nome: "Controle básico do semáforo de carros",
+ descricao:
+ "Programe o semáforo dos carros para alternar entre verde, amarelo e vermelho com espera (5 segundos para cada cor).",
+ allowedBlocks: ["mudar_semaforo", "aguardar_segundos", "repetir"],
+ temPedestre: false,
+ timeout: 15,
+ validationRegex:
+ /(mudarSemaforo\s*\(\s*'([^']+)'\s*\)\s*;)|(aguardarSegundos\s*\(\s*5\s*\)\s*;)/g,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "aguardar", seg: 5 },
+ ],
+ initialCode: `// Use o bloco "mudar semáforo" para controlar as cores do semáforo dos carros.
+// Use o bloco "aguardar segundos" para definir o tempo que cada cor deve durar.
+// Use o bloco "while(true) {}" para criar um ciclo infinito.`,
+ },
+ {
+ id: 3,
+ nome: "Controle do semáforo de pedestres",
+ descricao:
+ "Programe o semáforo dos carros e dos pedestres para alternar corretamente. Use os blocos de pedestre.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "repetir",
+ "mudar_semaforo_pedestre",
+ ],
+ temPedestre: false,
+ timeout: 15,
+ validationRegex:
+ /(mudarSemaforo\s*\(\s*'([^']+)\'\s*\)\s*;)|(mudarSemaforoPedestre\s*\(\s*'([^']+)\'\s*\)\s*;)|(aguardarSegundos\s*\(\s*([0-9]+)\s*\)\s*;)/g,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 5 },
+ ],
+ initialCode: `// Use o bloco "mudar semáforo" para controlar as cores do semáforo dos carros.
+// Use o bloco "mudar semáforo de pedestre" para controlar as cores do semáforo dos pedestres.
+// Use o bloco "aguardar segundos" para definir o tempo que cada cor deve durar.
+// Use o bloco "while(true) {}" para criar um ciclo infinito.`,
+ },
+ {
+ id: 4,
+ nome: "Semáforo de pedestres com luz piscante",
+ descricao:
+ "Programe o semáforo dos carros e dos pedestres para alternar corretamente. Após liberar o pedestre, faça a luz de pedestre piscar por 3 segundos antes de fechar.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "repetir",
+ "mudar_semaforo_pedestre",
+ "piscar_luz_pedestre",
+ ],
+ temPedestre: true,
+ timeout: 20,
+ validationRegex:
+ /(mudarSemaforo\s*\(\s*'([^']+)'\s*\)\s*;)|(mudarSemaforoPedestre\s*\(\s*'([^']+)'\s*\)\s*;)|(aguardarSegundos\s*\(\s*([0-9]+)\s*\)\s*;)|(piscarLuzPedestre\s*\(\s*'([^']+)'\s*,\s*([0-9]+)\s*\)\s*;)/g,
+ expectedCommands: [
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "piscar", cor: "vermelho", seg: 5 },
+ ],
+ initialCode: `// Use o bloco "piscar luz do pedestre" para fazer a luz do pedestre piscar.`,
+ },
+ {
+ id: 5,
+ nome: "Semáforo com sons",
+ descricao:
+ "Programe o semáforo e controle os sons do jogo usando os blocos de tocar e parar som.",
+ allowedBlocks: [
+ "mudar_semaforo",
+ "aguardar_segundos",
+ "repetir",
+ "mudar_semaforo_pedestre",
+ "piscar_luz_pedestre",
+ "tocar_som",
+ "parar_som",
+ ],
+ temPedestre: true,
+ timeout: 25,
+ validationRegex:
+ /(mudarSemaforo\s*\(\s*'([^']+)'\s*\)\s*;)|(mudarSemaforoPedestre\s*\(\s*'([^']+)'\s*\)\s*;)|(aguardarSegundos\s*\(\s*([0-9]+)\s*\)\s*;)|(piscarLuzPedestre\s*\(\s*'([^']+)'\s*,\s*([0-9]+)\s*\)\s*;)|(tocarSom\s*\(\s*'([^']+)'\s*\)\s*;)|(pararSom\s*\(\s*'([^']+)'\s*\)\s*;)/g,
+ expectedCommands: [
+ { tipo: "tocar", som: "barulho_cidade" },
+ { tipo: "semaforo", cor: "verde" },
+ { tipo: "pedestre", cor: "vermelho" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "amarelo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "semaforo", cor: "vermelho" },
+ { tipo: "pedestre", cor: "verde" },
+ { tipo: "tocar", som: "beep_semaforo" },
+ { tipo: "aguardar", seg: 5 },
+ { tipo: "piscar", cor: "vermelho", seg: 5 },
+ { tipo: "parar", som: "beep_semaforo" },
+ ],
+ initialCode: `//Use tudo que aprendeu e agora adicione sons ao nosso jogo.
+//Adicione sons ao semáforo usando os blocos "tocar som" e "parar som". Exemplo: \n
+//tocarSom('barulho_cidade');
+//tocarSom('beep_semaforo');
+//pararSom('beep_semaforo');`,
+ },
+ ],
+};
diff --git a/app/src/hooks/__tests__/useGameModals.test.js b/app/src/hooks/__tests__/useGameModals.test.js
new file mode 100644
index 0000000..198f345
--- /dev/null
+++ b/app/src/hooks/__tests__/useGameModals.test.js
@@ -0,0 +1,278 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { renderHook, act } from "@testing-library/react";
+import { useGameModals } from "../useGameModals";
+import { GAME_STATES } from "../../contexts/GameStateContext";
+
+// Parâmetros padrão reutilizados nos testes
+const DEFAULT_PHASE_CONFIG = { maxBlocks: 10 };
+const DEFAULT_GAME_CONFIG = { fases: [1, 2, 3] }; // 3 fases
+
+function buildOptions(overrides = {}) {
+ return {
+ executionState: GAME_STATES.PARADO,
+ currentPhase: 1,
+ phaseConfig: DEFAULT_PHASE_CONFIG,
+ gameConfig: DEFAULT_GAME_CONFIG,
+ setCurrentPhase: vi.fn(),
+ restart: vi.fn(),
+ setOnWorkspaceChange: vi.fn(),
+ usaModalFalha: false,
+ ...overrides,
+ };
+}
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — estado inicial", () => {
+ it("todos os modais começam fechados", () => {
+ const { result } = renderHook(() => useGameModals(buildOptions()));
+
+ expect(result.current.modalFasesAberto).toBe(false);
+ expect(result.current.modalSucessoAberto).toBe(false);
+ expect(result.current.modalFalhaAberto).toBe(false);
+ });
+
+ it("contagens de blocos começam como null", () => {
+ const { result } = renderHook(() => useGameModals(buildOptions()));
+
+ expect(result.current.blocksRemainingCount).toBeNull();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — abertura automática de modais", () => {
+ it("abre modal de sucesso quando executionState muda para SUCESSO", () => {
+ const options = buildOptions({ executionState: GAME_STATES.SUCESSO });
+ const { result } = renderHook(() => useGameModals(options));
+
+ expect(result.current.modalSucessoAberto).toBe(true);
+ });
+
+ it("abre modal de falha quando executionState é FALHA e usaModalFalha=true", () => {
+ const options = buildOptions({
+ executionState: GAME_STATES.FALHA,
+ usaModalFalha: true,
+ });
+ const { result } = renderHook(() => useGameModals(options));
+
+ expect(result.current.modalFalhaAberto).toBe(true);
+ });
+
+ it("NÃO abre modal de falha quando usaModalFalha=false", () => {
+ const options = buildOptions({
+ executionState: GAME_STATES.FALHA,
+ usaModalFalha: false,
+ });
+ const { result } = renderHook(() => useGameModals(options));
+
+ expect(result.current.modalFalhaAberto).toBe(false);
+ });
+
+ it("não abre nenhum modal quando executionState é PARADO", () => {
+ const options = buildOptions({ executionState: GAME_STATES.PARADO });
+ const { result } = renderHook(() => useGameModals(options));
+
+ expect(result.current.modalSucessoAberto).toBe(false);
+ expect(result.current.modalFalhaAberto).toBe(false);
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — handlers de fechar modais", () => {
+ it("handleFecharModalSucesso fecha o modal de sucesso", () => {
+ const { result } = renderHook(() =>
+ useGameModals(buildOptions({ executionState: GAME_STATES.SUCESSO })),
+ );
+
+ expect(result.current.modalSucessoAberto).toBe(true);
+
+ act(() => {
+ result.current.handleFecharModalSucesso();
+ });
+
+ expect(result.current.modalSucessoAberto).toBe(false);
+ });
+
+ it("handleFecharModalFalha fecha o modal de falha", () => {
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({ executionState: GAME_STATES.FALHA, usaModalFalha: true }),
+ ),
+ );
+
+ expect(result.current.modalFalhaAberto).toBe(true);
+
+ act(() => {
+ result.current.handleFecharModalFalha();
+ });
+
+ expect(result.current.modalFalhaAberto).toBe(false);
+ });
+
+ it("setModalFasesAberto abre e fecha o modal de fases", () => {
+ const { result } = renderHook(() => useGameModals(buildOptions()));
+
+ act(() => {
+ result.current.setModalFasesAberto(true);
+ });
+ expect(result.current.modalFasesAberto).toBe(true);
+
+ act(() => {
+ result.current.setModalFasesAberto(false);
+ });
+ expect(result.current.modalFasesAberto).toBe(false);
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — handleTentarNovamente", () => {
+ it("fecha o modal de falha e chama restart()", () => {
+ const restart = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ executionState: GAME_STATES.FALHA,
+ usaModalFalha: true,
+ restart,
+ }),
+ ),
+ );
+
+ act(() => {
+ result.current.handleTentarNovamente();
+ });
+
+ expect(result.current.modalFalhaAberto).toBe(false);
+ expect(restart).toHaveBeenCalledOnce();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — handleProximaFase", () => {
+ it("avança para a próxima fase, fecha o modal de sucesso e chama restart()", () => {
+ const setCurrentPhase = vi.fn();
+ const restart = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ executionState: GAME_STATES.SUCESSO,
+ currentPhase: 1,
+ setCurrentPhase,
+ restart,
+ }),
+ ),
+ );
+
+ act(() => {
+ result.current.handleProximaFase();
+ });
+
+ expect(setCurrentPhase).toHaveBeenCalledWith(2);
+ expect(result.current.modalSucessoAberto).toBe(false);
+ expect(restart).toHaveBeenCalledOnce();
+ });
+
+ it("não avança além da última fase", () => {
+ const setCurrentPhase = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ currentPhase: 3, // última fase (gameConfig tem 3 fases)
+ setCurrentPhase,
+ }),
+ ),
+ );
+
+ act(() => {
+ result.current.handleProximaFase();
+ });
+
+ expect(setCurrentPhase).not.toHaveBeenCalled();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("useGameModals — workspace change callback", () => {
+ it("registra o callback de workspace na montagem", () => {
+ const setOnWorkspaceChange = vi.fn();
+ renderHook(() => useGameModals(buildOptions({ setOnWorkspaceChange })));
+
+ expect(setOnWorkspaceChange).toHaveBeenCalledWith(expect.any(Function));
+ });
+
+ it("remove o callback de workspace na desmontagem", () => {
+ const setOnWorkspaceChange = vi.fn();
+ const { unmount } = renderHook(() =>
+ useGameModals(buildOptions({ setOnWorkspaceChange })),
+ );
+
+ unmount();
+
+ expect(setOnWorkspaceChange).toHaveBeenLastCalledWith(null);
+ });
+
+ it("calcula blocos restantes corretamente", () => {
+ const setOnWorkspaceChange = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ phaseConfig: { maxBlocks: 10 },
+ setOnWorkspaceChange,
+ }),
+ ),
+ );
+
+ // Obtém o callback registrado e invoca-o
+ const registeredCallback = setOnWorkspaceChange.mock.calls[0][0];
+ act(() => {
+ registeredCallback(4);
+ });
+
+ expect(result.current.blocksRemainingCount).toBe(6);
+ });
+
+ it("mantém blocos null quando maxBlocks é Infinity", () => {
+ const setOnWorkspaceChange = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ phaseConfig: { maxBlocks: Infinity },
+ setOnWorkspaceChange,
+ }),
+ ),
+ );
+
+ const registeredCallback = setOnWorkspaceChange.mock.calls[0][0];
+ act(() => {
+ registeredCallback(5);
+ });
+
+ expect(result.current.blocksRemainingCount).toBeNull();
+ });
+
+ it("trata blockCount não-numérico como 0", () => {
+ const setOnWorkspaceChange = vi.fn();
+ const { result } = renderHook(() =>
+ useGameModals(
+ buildOptions({
+ phaseConfig: { maxBlocks: 8 },
+ setOnWorkspaceChange,
+ }),
+ ),
+ );
+
+ const registeredCallback = setOnWorkspaceChange.mock.calls[0][0];
+ act(() => {
+ registeredCallback("não-número");
+ });
+
+ expect(result.current.blocksRemainingCount).toBe(8);
+ });
+
+ it("não lança erro quando setOnWorkspaceChange é undefined", () => {
+ expect(() =>
+ renderHook(() =>
+ useGameModals(buildOptions({ setOnWorkspaceChange: undefined })),
+ ),
+ ).not.toThrow();
+ });
+});
diff --git a/app/src/hooks/__tests__/usePhaser.test.js b/app/src/hooks/__tests__/usePhaser.test.js
new file mode 100644
index 0000000..ed50c19
--- /dev/null
+++ b/app/src/hooks/__tests__/usePhaser.test.js
@@ -0,0 +1,329 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { renderHook, act } from "@testing-library/react";
+import { usePhaser } from "../usePhaser";
+
+// ---------------------------------------------------------------------------
+// Mocks
+// ---------------------------------------------------------------------------
+
+// vi.hoisted garante que as funções existem antes do hoisting do vi.mock.
+const { mockDestroy, mockResize, mockPhaserGame } = vi.hoisted(() => {
+ const mockDestroy = vi.fn();
+ const mockResize = vi.fn();
+ // Precisa ser uma função construtora (não arrow) para suportar `new Phaser.Game()`
+ const mockPhaserGame = vi.fn(function () {
+ this.destroy = mockDestroy;
+ this.scale = { resize: mockResize };
+ });
+ return { mockDestroy, mockResize, mockPhaserGame };
+});
+
+vi.mock("phaser", () => ({
+ default: { Game: mockPhaserGame },
+}));
+
+// useIsMobile controla se estamos em modo mobile.
+let mockIsMobile = false;
+vi.mock("../useIsMobile", () => ({
+ useIsMobile: () => mockIsMobile,
+}));
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+const PHASE_CONFIG = { maxBlocks: 5 };
+const GAME_CONFIG = { gameId: "test-game", fases: [PHASE_CONFIG] };
+
+function buildOptions(overrides = {}) {
+ return {
+ gameFactory: vi.fn(() => ({ type: Phaser.AUTO })),
+ phaseConfig: PHASE_CONFIG,
+ currentPhase: 1,
+ customFailureHandler: undefined,
+ gameConfig: GAME_CONFIG,
+ ...overrides,
+ };
+}
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — retorno", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockDestroy.mockClear();
+ mockResize.mockClear();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("retorna gameContainerRef como objeto ref", () => {
+ const { result } = renderHook(() => usePhaser(buildOptions()));
+
+ expect(result.current).toHaveProperty("gameContainerRef");
+ expect(result.current.gameContainerRef).toHaveProperty("current");
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — inicialização do Phaser", () => {
+ let container;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockDestroy.mockClear();
+ // Cria um elemento DOM real para annexar ao ref
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ vi.useRealTimers();
+ });
+
+ it("chama gameFactory com os parâmetros corretos após o delay", () => {
+ const gameFactory = vi.fn(() => ({}));
+ const options = buildOptions({ gameFactory, customFailureHandler: vi.fn() });
+
+ const { result } = renderHook(() => usePhaser(options));
+
+ // Injeta o elemento DOM no ref antes do timer disparar
+ result.current.gameContainerRef.current = container;
+
+ // gameFactory NÃO deve ser chamado antes do setTimeout(10ms)
+ expect(gameFactory).not.toHaveBeenCalled();
+
+ act(() => {
+ vi.advanceTimersByTime(10);
+ });
+
+ expect(gameFactory).toHaveBeenCalledWith(
+ container,
+ options.phaseConfig,
+ options.customFailureHandler,
+ options.currentPhase,
+ options.gameConfig,
+ );
+ });
+
+ it("não instancia Phaser.Game quando gameContainerRef.current é null", () => {
+ renderHook(() => usePhaser(buildOptions()));
+
+ act(() => {
+ vi.advanceTimersByTime(10);
+ });
+
+ // Container null → not called
+ expect(mockPhaserGame).not.toHaveBeenCalled();
+ });
+
+ it("reinicializa quando currentPhase muda", () => {
+ const gameFactory = vi.fn(() => ({}));
+ const { result, rerender } = renderHook(
+ (props) => usePhaser(props),
+ { initialProps: buildOptions({ gameFactory, currentPhase: 1 }) },
+ );
+
+ result.current.gameContainerRef.current = container;
+ act(() => vi.advanceTimersByTime(10));
+ const callsAfterFirst = gameFactory.mock.calls.length;
+
+ rerender(buildOptions({ gameFactory, currentPhase: 2 }));
+ act(() => vi.advanceTimersByTime(10));
+
+ expect(gameFactory.mock.calls.length).toBeGreaterThan(callsAfterFirst);
+ });
+
+ it("reinicializa quando gameFactory muda", () => {
+ const factory1 = vi.fn(() => ({}));
+ const factory2 = vi.fn(() => ({}));
+
+ const { result, rerender } = renderHook(
+ (props) => usePhaser(props),
+ { initialProps: buildOptions({ gameFactory: factory1 }) },
+ );
+
+ result.current.gameContainerRef.current = container;
+ act(() => vi.advanceTimersByTime(10));
+
+ rerender(buildOptions({ gameFactory: factory2 }));
+ act(() => vi.advanceTimersByTime(10));
+
+ expect(factory2).toHaveBeenCalled();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — cleanup", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockDestroy.mockClear();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("cancela o timeout pendente ao desmontar antes dos 10ms", () => {
+ const gameFactory = vi.fn(() => ({}));
+ const { unmount } = renderHook(() =>
+ usePhaser(buildOptions({ gameFactory })),
+ );
+
+ // Desmonta antes do timeout disparar
+ unmount();
+ act(() => vi.advanceTimersByTime(10));
+
+ // gameFactory não deve ter sido chamado porque o timer foi cancelado
+ expect(gameFactory).not.toHaveBeenCalled();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — resize ao alternar mobile/desktop", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockResize.mockClear();
+ mockPhaserGame.mockClear();
+ mockIsMobile = false;
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ mockIsMobile = false;
+ });
+
+ it("não chama scale.resize antes de existir instância Phaser", () => {
+ renderHook(() => usePhaser(buildOptions()));
+ // Sem instância Phaser (container null), resize não é chamado
+ expect(mockResize).not.toHaveBeenCalled();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — resize ao alternar mobile/desktop COM instância", () => {
+ let container;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockResize.mockClear();
+ mockIsMobile = false;
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ vi.useRealTimers();
+ mockIsMobile = false;
+ });
+
+ it("chama scale.resize quando isMobile muda e instância Phaser existe", () => {
+ // Usa factory estável para não mudar deps do segundo effect no rerender
+ const stableFactory = vi.fn(() => ({}));
+ const baseOptions = buildOptions({ gameFactory: stableFactory });
+
+ const { result, rerender } = renderHook(
+ ({ isMobile: _isMobile, ...opts }) => {
+ mockIsMobile = _isMobile;
+ return usePhaser(opts);
+ },
+ { initialProps: { ...baseOptions, isMobile: false } },
+ );
+
+ // Anexa container e inicializa Phaser
+ result.current.gameContainerRef.current = container;
+ act(() => vi.advanceTimersByTime(10));
+ expect(mockPhaserGame).toHaveBeenCalled();
+
+ // Alterna para mobile → deve chamar scale.resize
+ mockResize.mockClear();
+ act(() => {
+ rerender({ ...baseOptions, isMobile: true });
+ });
+
+ expect(mockResize).toHaveBeenCalled();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — erros na inicialização", () => {
+ let container;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockDestroy.mockClear();
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ vi.useRealTimers();
+ });
+
+ it("loga console.error quando gameFactory lança exceção no timeout", () => {
+ const throwingFactory = vi.fn(() => { throw new Error("factory error"); });
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+
+ const { result } = renderHook(() =>
+ usePhaser(buildOptions({ gameFactory: throwingFactory })),
+ );
+ result.current.gameContainerRef.current = container;
+
+ act(() => vi.advanceTimersByTime(10));
+
+ expect(spy).toHaveBeenCalledWith(
+ expect.stringContaining("Erro ao inicializar"),
+ expect.any(Error),
+ );
+ spy.mockRestore();
+ });
+});
+
+// ---------------------------------------------------------------------------
+describe("usePhaser — cleanup com destroy lançando erro", () => {
+ let container;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ mockPhaserGame.mockClear();
+ mockDestroy.mockClear();
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ vi.useRealTimers();
+ });
+
+ it("loga console.warn quando destroy() lança exceção durante cleanup", async () => {
+ mockDestroy.mockImplementation(() => { throw new Error("destroy error"); });
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const stableFactory = vi.fn(() => ({}));
+
+ const { result, unmount } = renderHook(() =>
+ usePhaser(buildOptions({ gameFactory: stableFactory })),
+ );
+ result.current.gameContainerRef.current = container;
+ await act(async () => vi.advanceTimersByTime(10));
+ expect(mockPhaserGame).toHaveBeenCalled();
+
+ // Desmonta → cleanup roda → destroy lança → console.warn chamado
+ await act(async () => unmount());
+
+ expect(spy).toHaveBeenCalledWith(
+ expect.stringContaining("Erro durante cleanup"),
+ expect.any(Error),
+ );
+ spy.mockRestore();
+ });
+});
diff --git a/app/src/hooks/useGameModals.js b/app/src/hooks/useGameModals.js
new file mode 100644
index 0000000..c9fad47
--- /dev/null
+++ b/app/src/hooks/useGameModals.js
@@ -0,0 +1,122 @@
+/**
+ * @fileoverview Hook para gerenciar o estado e handlers dos modais do jogo.
+ *
+ * Encapsula: abertura/fechamento dos modais de fases, sucesso e falha;
+ * contagem de blocos restantes; callback de mudança de workspace.
+ *
+ * @module hooks.useGameModals
+ */
+
+import { useState, useCallback, useEffect } from "react";
+import { GAME_STATES } from "../contexts/GameStateContext";
+
+/**
+ * Hook que gerencia estado e handlers dos modais do jogo.
+ *
+ * @param {Object} options
+ * @param {string} options.executionState - Estado atual de execução (GAME_STATES)
+ * @param {number} options.currentPhase - Número da fase atual
+ * @param {Object} options.phaseConfig - Configuração da fase atual
+ * @param {Object} options.gameConfig - Configuração completa do jogo
+ * @param {Function} options.setCurrentPhase - Setter da fase atual
+ * @param {Function} options.restart - Reinicia estado de execução
+ * @param {Function} options.setOnWorkspaceChange - Registra callback de workspace
+ * @param {boolean} options.usaModalFalha - Se deve abrir modal ao falhar
+ *
+ * @returns {{
+ * modalFasesAberto: boolean,
+ * setModalFasesAberto: Function,
+ * modalSucessoAberto: boolean,
+ * modalFalhaAberto: boolean,
+ * blocksRemainingCount: number|null,
+ * handleProximaFase: Function,
+ * handleFecharModalSucesso: Function,
+ * handleFecharModalFalha: Function,
+ * handleTentarNovamente: Function,
+ * }}
+ */
+export function useGameModals({
+ executionState,
+ currentPhase,
+ phaseConfig,
+ gameConfig,
+ setCurrentPhase,
+ restart,
+ setOnWorkspaceChange,
+ usaModalFalha,
+}) {
+ const [modalFasesAberto, setModalFasesAberto] = useState(false);
+ const [modalSucessoAberto, setModalSucessoAberto] = useState(false);
+ const [modalFalhaAberto, setModalFalhaAberto] = useState(false);
+ const [blocksRemainingCount, setBlocksRemainingCount] = useState(null);
+
+ // Atualiza contagem de blocos restantes ao mudar a workspace.
+ const handleWorkspaceChange = useCallback(
+ (blockCount) => {
+ if (phaseConfig.maxBlocks === Infinity) {
+ setBlocksRemainingCount(null);
+ return;
+ }
+ const blocosUsados = typeof blockCount === "number" ? blockCount : 0;
+ const blocosRestantes = phaseConfig.maxBlocks - blocosUsados;
+ setBlocksRemainingCount(blocosRestantes);
+ },
+ [phaseConfig.maxBlocks],
+ );
+
+ // Registra e limpa o callback de mudança de workspace.
+ useEffect(() => {
+ if (setOnWorkspaceChange) {
+ setOnWorkspaceChange(handleWorkspaceChange);
+ }
+ return () => {
+ if (setOnWorkspaceChange) {
+ setOnWorkspaceChange(null);
+ }
+ };
+ }, [setOnWorkspaceChange, handleWorkspaceChange]);
+
+ // Abre modal de sucesso ou falha conforme o estado de execução.
+ useEffect(() => {
+ if (executionState === GAME_STATES.SUCESSO) {
+ setModalSucessoAberto(true);
+ }
+ if (executionState === GAME_STATES.FALHA && usaModalFalha) {
+ setModalFalhaAberto(true);
+ }
+ }, [executionState, usaModalFalha]);
+
+ const handleProximaFase = useCallback(() => {
+ const proximaFase = currentPhase + 1;
+ if (proximaFase <= gameConfig.fases.length) {
+ setCurrentPhase(proximaFase);
+ }
+ setModalSucessoAberto(false);
+ restart();
+ }, [currentPhase, gameConfig.fases.length, setCurrentPhase, restart]);
+
+ const handleFecharModalSucesso = useCallback(() => {
+ setModalSucessoAberto(false);
+ }, []);
+
+ const handleFecharModalFalha = useCallback(() => {
+ setModalFalhaAberto(false);
+ }, []);
+
+ const handleTentarNovamente = useCallback(() => {
+ setModalFalhaAberto(false);
+ restart();
+ }, [restart]);
+
+ return {
+ modalFasesAberto,
+ setModalFasesAberto,
+ modalSucessoAberto,
+ modalFalhaAberto,
+ blocksRemainingCount,
+ handleProximaFase,
+ handleFecharModalSucesso,
+ handleFecharModalFalha,
+ handleTentarNovamente,
+ };
+}
diff --git a/app/src/hooks/useGameTour.js b/app/src/hooks/useGameTour.js
new file mode 100644
index 0000000..64584e6
--- /dev/null
+++ b/app/src/hooks/useGameTour.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Utility module for useGameTour.js
+ *
+ * @module hooks.useGameTour
+ */
+
+import { useTour } from "./useTour";
+
+/**
+ * Hook wrapper de useTour específico para tours de jogos.
+ * Abstrai gerenciamento de chave localStorage usando nome do jogo + sufixo padrão.
+ * Simplifica integração de tours com games mantendo histórico por jogo.
+ *
+ * @hook useGameTour
+ * @param {string} gameName - Nome único do jogo (ex: 'semaforo', 'turtle')
+ * @param {Array} steps - Array de passos do tour Shepherd
+ * @param {Object} [options={}] - Opções customizadas para Shepherd (merged com defaults)
+ * @param {number} [startDelay=1500] - Delay (ms) antes de auto-iniciar tour
+ *
+ * @returns {Object} Methodos do tour:
+ * - startTour() - Inicia o tour manualmente
+ * - completeTour() - Marca como concluído e salva em localStorage
+ * - resetTour() - Reseta o tour (remove localStorage, permite replay)
+ *
+ * @example
+ * const { startTour, completeTour } = useGameTour(
+ * 'semaforo',
+ * semaphoreSteps,
+ * { theme: 'dark' },
+ * 1000
+ * );
+ * // Salva em localStorage como: 'semaforo-tour-completed'
+ */
+export const useGameTour = (
+ gameName,
+ steps,
+ options = {},
+ startDelay = 1500,
+) => {
+ const storageKey = `${gameName}-tour-completed`;
+
+ const { startTour, completeTour, resetTour } = useTour({
+ steps,
+ options,
+ storageKey,
+ autoStart: true,
+ startDelay,
+ });
+ return {
+ startTour,
+ completeTour,
+ resetTour,
+ };
+};
diff --git a/app/src/hooks/useIsMobile.js b/app/src/hooks/useIsMobile.js
new file mode 100644
index 0000000..0e98b20
--- /dev/null
+++ b/app/src/hooks/useIsMobile.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Utility module for useIsMobile.js
+ *
+ * @module hooks.useIsMobile
+ */
+
+import { useState, useEffect } from "react";
+import { isMobileViewport } from "../utils/isMobile";
+
+/**
+ * Hook para detectar se a viewport está em modo mobile.
+ * Rastreia redimensionamento de janela e atualiza estado automaticamente.
+ * Útil para renderização condicional e ajustes de layout responsivo.
+ *
+ * @hook useIsMobile
+ * @param {number} [breakpoint=768] - Largura em pixels abaixo da qual é considerado mobile
+ *
+ * @returns {boolean} true se window.innerWidth < breakpoint, false caso contrário
+ *
+ * @example
+ * const isMobile = useIsMobile();
+ * return isMobile ? : ;
+ *
+ * @example
+ * const isSmall = useIsMobile(640); // breakpoint customizado
+ */
+export function useIsMobile(breakpoint = 768) {
+ const [isMobile, setIsMobile] = useState(isMobileViewport(breakpoint));
+
+ useEffect(() => {
+ const handleResize = () => setIsMobile(isMobileViewport(breakpoint));
+ window.addEventListener("resize", handleResize);
+ return () => window.removeEventListener("resize", handleResize);
+ }, [breakpoint]);
+
+ return isMobile;
+}
diff --git a/app/src/hooks/usePhaser.js b/app/src/hooks/usePhaser.js
new file mode 100644
index 0000000..16d7cb0
--- /dev/null
+++ b/app/src/hooks/usePhaser.js
@@ -0,0 +1,107 @@
+/**
+ * @fileoverview Hook para gerenciar o ciclo de vida de uma instância Phaser.
+ *
+ * Encapsula: criação, destruição e resize da instância Phaser em resposta
+ * a mudanças de fase, fábrica de jogo e modo mobile.
+ *
+ * @module hooks.usePhaser
+ */
+
+import { useRef, useEffect } from "react";
+import Phaser from "phaser";
+import { useIsMobile } from "./useIsMobile";
+
+/**
+ * Hook que gerencia o ciclo de vida de uma instância Phaser.
+ *
+ * @param {Object} options
+ * @param {Function} options.gameFactory - Fábrica que cria a config Phaser
+ * @param {Object} options.phaseConfig - Configuração da fase atual
+ * @param {number} options.currentPhase - Número da fase atual
+ * @param {Function} [options.customFailureHandler] - Handler customizado de falha
+ * @param {Object} options.gameConfig - Configuração completa do jogo
+ *
+ * @returns {{ gameContainerRef: React.RefObject }}
+ */
+export function usePhaser({
+ gameFactory,
+ phaseConfig,
+ currentPhase,
+ customFailureHandler,
+ gameConfig,
+}) {
+ const gameContainerRef = useRef(null);
+ const gameInstanceRef = useRef(null);
+ const isInitializingRef = useRef(false);
+ const isMobile = useIsMobile();
+
+ // Redimensiona Phaser ao alternar entre mobile e desktop.
+ useEffect(() => {
+ if (gameInstanceRef.current && gameContainerRef.current) {
+ const phaserScale = gameInstanceRef.current.scale;
+ phaserScale.resize(
+ gameContainerRef.current.clientWidth,
+ gameContainerRef.current.clientHeight,
+ );
+ }
+ }, [isMobile]);
+
+ // (Re)inicializa Phaser quando a fase ou a fábrica mudam.
+ useEffect(() => {
+ if (isInitializingRef.current) {
+ return;
+ }
+
+ isInitializingRef.current = true;
+
+ if (gameInstanceRef.current) {
+ try {
+ gameInstanceRef.current.destroy(true);
+ } catch (error) {
+ console.warn("Erro ao destruir Phaser:", error);
+ }
+ gameInstanceRef.current = null;
+ }
+
+ // Aguardar um frame para garantir que o cleanup foi concluído.
+ const timeoutId = setTimeout(() => {
+ try {
+ if (!gameContainerRef.current) {
+ console.warn("Container do jogo não disponível");
+ isInitializingRef.current = false;
+ return;
+ }
+
+ const config = gameFactory(
+ gameContainerRef.current,
+ phaseConfig,
+ customFailureHandler,
+ currentPhase,
+ gameConfig,
+ );
+ gameInstanceRef.current = new Phaser.Game(config);
+ } catch (error) {
+ console.error("Erro ao inicializar Phaser:", error);
+ } finally {
+ isInitializingRef.current = false;
+ }
+ }, 10);
+
+ return () => {
+ clearTimeout(timeoutId);
+
+ if (gameInstanceRef.current) {
+ try {
+ gameInstanceRef.current.destroy(true);
+ } catch (error) {
+ console.warn("Erro durante cleanup do Phaser:", error);
+ }
+ gameInstanceRef.current = null;
+ }
+
+ isInitializingRef.current = false;
+ };
+ }, [gameFactory, currentPhase, customFailureHandler]); // eslint-disable-line react-hooks/exhaustive-deps
+
+ return { gameContainerRef };
+}
diff --git a/app/src/hooks/useTour.js b/app/src/hooks/useTour.js
new file mode 100644
index 0000000..155803e
--- /dev/null
+++ b/app/src/hooks/useTour.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Utility module for useTour.js
+ *
+ * @module hooks.useTour
+ */
+
+// TODO: Tour desativado temporariamente. Shepherd.js será removido quando o sistema
+// de onboarding for reimplementado. Ver TODO de remoção do Shepherd.
+// import Shepherd from "shepherd.js";
+
+/**
+ * Hook para gerenciar tours interativos usando Shepherd.js.
+ * Controla ciclo de vida do tour: criar, mostrar, salvar conclusão em localStorage.
+ *
+ * Características:
+ * - Executa uma única vez (verifica localStorage)
+ * - Delay configurável antes de iniciar
+ * - Overlay modal com highlight de elementos
+ * - Integração com localStorage para rastrear conclusão
+ *
+ * @hook useTour
+ * @param {Object} [options={}] - Configuração do hook
+ * @param {Array} [options.steps=[]] - Array de passos do tour (Shepherd steps)
+ * @param {Object} [options.options={}] - Opções do tour Shepherd (mergeadas com defaults)
+ * @param {string} [options.storageKey='tour-completed'] - Chave localStorage para rastrear conclusão
+ * @param {boolean} [options.autoStart=true] - Inicia tour automaticamente se não completado
+ * @param {number} [options.startDelay=1000] - Delay (ms) antes de auto-iniciar o tour
+ *
+ * @returns {Object} Objeto com métodos do tour:
+ * - start() - Inicia o tour
+ * - end() - Encerra o tour
+ * - cancel() - Cancela o tour
+ * - markCompleted() - Marca como concluído e salva em localStorage
+ * - getCurrentStep() - Retorna passo atual
+ * - next() - Próximo passo
+ * - back() - Passo anterior
+ *
+ * @example
+ * const tour = useTour({
+ * steps: [
+ * { element: '.button-start', title: '...', text: '...' },
+ * { element: '.result-area', title: '...', text: '...' }
+ * ],
+ * storageKey: 'game-intro-tour',
+ * startDelay: 500
+ * });
+ *
+ * return Tour iniciará automaticamente após 500ms
;
+ */
+export const useTour = ({
+ // eslint-disable-next-line no-unused-vars
+ steps = [],
+ // eslint-disable-next-line no-unused-vars
+ options = {},
+ // eslint-disable-next-line no-unused-vars
+ storageKey = "tour-completed",
+ // eslint-disable-next-line no-unused-vars
+ autoStart = true,
+ // eslint-disable-next-line no-unused-vars
+ startDelay = 1000,
+} = {}) => {
+ const startTour = () => {};
+ const completeTour = () => {};
+ const resetTour = () => {};
+
+ return {
+ startTour,
+ completeTour,
+ resetTour,
+ tour: null,
+ };
+};
diff --git a/app/src/interpreters/ApiHelpers.js b/app/src/interpreters/ApiHelpers.js
new file mode 100644
index 0000000..8489626
--- /dev/null
+++ b/app/src/interpreters/ApiHelpers.js
@@ -0,0 +1,123 @@
+/**
+ * Utilitários para registrar funções e wrappers no `js-interpreter`.
+ * Centraliza criação de wrappers para ações assíncronas, condições e highlight
+ * e facilita a exposição dessas funções no escopo seguro do interpretador.
+ *
+ * @class ApiHelpers
+ */
+export class ApiHelpers {
+ /**
+ * Cria wrapper para ações do jogo (funções assíncronas com delay)
+ * @param {object} scene - Cena do jogo que contém o método
+ * @param {string} methodName - Nome do método a ser chamado
+ * @param {number} animationDelay - Delay após execução (ms)
+ * @returns {function} - Wrapper para o js-interpreter
+ */
+ static createActionWrapper(scene, methodName, animationDelay = 100) {
+ return function (arg1, callback) {
+ const realArg =
+ typeof arg1 === "object" && arg1.data !== undefined ? arg1.data : arg1;
+
+ if (scene[methodName] && typeof scene[methodName] === "function") {
+ try {
+ const result = scene[methodName](realArg);
+
+ if (result && typeof result.then === "function") {
+ result
+ .then(() => {
+ setTimeout(callback, animationDelay);
+ })
+ .catch((error) => {
+ console.error(`Error in ${methodName}:`, error);
+ setTimeout(callback, animationDelay);
+ });
+ } else {
+ setTimeout(callback, animationDelay);
+ }
+ } catch (error) {
+ console.error(`Error executing ${methodName}:`, error);
+ setTimeout(callback, animationDelay);
+ }
+ } else {
+ console.warn(`Method '${methodName}' not found in scene`);
+ setTimeout(callback, animationDelay);
+ }
+ };
+ }
+
+ /**
+ * Cria wrapper para condições do jogo (funções síncronas)
+ * @param {object} scene - Cena do jogo que contém o método
+ * @param {string} methodName - Nome do método a ser chamado
+ * @returns {function} - Wrapper para o js-interpreter
+ */
+ static createConditionWrapper(scene, methodName) {
+ return (...args) => {
+ if (scene[methodName] && typeof scene[methodName] === "function") {
+ return scene[methodName](...args);
+ } else {
+ console.warn(`Method '${methodName}' not found in scene`);
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Cria wrapper para função de highlight de blocos
+ * @param {object} scene - Cena do jogo
+ * @returns {function} - Wrapper para highlight
+ */
+ static createHighlightWrapper(scene) {
+ return (id) => {
+ const blockId = String(id || "");
+
+ // Chamar método de highlight se disponível
+ if (scene.highlightBlock && typeof scene.highlightBlock === "function") {
+ scene.highlightBlock(blockId);
+ } else if (scene.workspace?.highlightBlock) {
+ scene.workspace.highlightBlock(blockId);
+ }
+
+ // Marcar pausa para highlight se suportado
+ if (scene.highlightPause !== undefined) {
+ scene.highlightPause = true;
+ }
+ };
+ }
+
+ /**
+ * Registra uma função no escopo global do interpreter
+ * @param {object} interpreter - Instância do js-interpreter
+ * @param {object} globalScope - Escopo global do interpreter
+ * @param {string} name - Nome da função
+ * @param {function} wrapper - Função wrapper
+ * @param {boolean} isAsync - Se a função é assíncrona
+ * @returns {void}
+ */
+ static registerFunction(
+ interpreter,
+ globalScope,
+ name,
+ wrapper,
+ isAsync = false,
+ ) {
+ const func = isAsync
+ ? interpreter.createAsyncFunction(wrapper)
+ : interpreter.createNativeFunction(wrapper);
+
+ interpreter.setProperty(globalScope, name, func);
+ }
+
+ /**
+ * Registra múltiplas funções de uma vez
+ * @param {object} interpreter - Instância do js-interpreter
+ * @param {object} globalScope - Escopo global do interpreter
+ * @param {Array} functions - Array de objetos {name, wrapper, isAsync}
+ * @returns {void}
+ */
+ static registerMultipleFunctions(interpreter, globalScope, functions) {
+ functions.forEach(({ name, wrapper, isAsync = false }) => {
+ this.registerFunction(interpreter, globalScope, name, wrapper, isAsync);
+ });
+ }
+}
diff --git a/app/src/interpreters/CodeSanitizer.js b/app/src/interpreters/CodeSanitizer.js
new file mode 100644
index 0000000..239e8f5
--- /dev/null
+++ b/app/src/interpreters/CodeSanitizer.js
@@ -0,0 +1,97 @@
+/**
+ * @fileoverview Code sanitizer for GameInterpreter.
+ *
+ * Validates user-submitted code before handing it to js-interpreter,
+ * defending against common DoS patterns (infinite loops, excessive input, etc.)
+ *
+ * @module interpreters.CodeSanitizer
+ */
+
+/**
+ * Error thrown when code fails sanitization.
+ * The `rule` property identifies which rule triggered the rejection.
+ */
+export class CodeSanitizationError extends Error {
+ /**
+ * @param {string} message - Human-readable reason.
+ * @param {string} rule - Rule identifier (e.g. "max_length").
+ */
+ constructor(message, rule) {
+ super(message);
+ this.name = "CodeSanitizationError";
+ this.rule = rule;
+ }
+}
+
+/**
+ * Default sanitization rules applied to every code submission.
+ *
+ * @type {Array<{id: string, check: (code: string) => boolean, message: string}>}
+ */
+export const DEFAULT_RULES = [
+ {
+ id: "max_length",
+ check: (code) => code.length > 50_000,
+ message:
+ "O código é demasiado longo para ser executado (máx. 50 000 caracteres).",
+ },
+ {
+ id: "infinite_while",
+ check: (code) =>
+ /while\s*\(\s*(true|!0|1)\s*\)/i.test(code) &&
+ !/break\b/.test(code) &&
+ !/return\b/.test(code),
+ message:
+ "Encontrado loop infinito (while(true) sem break/return). Adiciona uma condição de saída.",
+ },
+ {
+ id: "infinite_for",
+ check: (code) =>
+ /for\s*\(\s*;?\s*;?\s*;?\s*\)/.test(code) &&
+ !/break\b/.test(code) &&
+ !/return\b/.test(code),
+ message:
+ "Encontrado loop infinito (for(;;) sem break/return). Adiciona uma condição de saída.",
+ },
+ {
+ id: "excessive_nesting",
+ check: (code) => {
+ let depth = 0;
+ let maxDepth = 0;
+ for (const ch of code) {
+ if (ch === "{") {
+ maxDepth = Math.max(maxDepth, ++depth);
+ } else if (ch === "}") {
+ depth = Math.max(0, depth - 1);
+ }
+ }
+ return maxDepth > 15;
+ },
+ message:
+ "O código tem demasiados níveis de aninhamento (máx. 15 blocos {}).",
+ },
+];
+
+/**
+ * Validates code against the default rules plus any extra rules provided.
+ * Throws {@link CodeSanitizationError} on the first rule that fails.
+ *
+ * @param {string} code - Source code to validate.
+ * @param {Array} [extraRules=[]] - Additional rules to apply after the defaults.
+ * @throws {CodeSanitizationError}
+ */
+export function sanitizeCode(code, extraRules = []) {
+ if (typeof code !== "string") {
+ throw new CodeSanitizationError(
+ "O código deve ser uma string.",
+ "type_check",
+ );
+ }
+
+ const rules = [...DEFAULT_RULES, ...extraRules];
+ for (const rule of rules) {
+ if (rule.check(code)) {
+ throw new CodeSanitizationError(rule.message, rule.id);
+ }
+ }
+}
diff --git a/app/src/interpreters/GameInterpreter.js b/app/src/interpreters/GameInterpreter.js
new file mode 100644
index 0000000..87a2c2c
--- /dev/null
+++ b/app/src/interpreters/GameInterpreter.js
@@ -0,0 +1,207 @@
+/**
+ * @fileoverview Utility module for GameInterpreter.js
+ *
+ * @module interpreters.GameInterpreter
+ */
+
+import Interpreter from "js-interpreter";
+import { sanitizeCode, CodeSanitizationError } from "./CodeSanitizer";
+
+/**
+ * Interpreta e executa código JavaScript dentro de um sandbox isolado usando js-interpreter.
+ * Oferece controle de fluxo: execução passo-a-passo, pausa, parada e stepping para debug visual.
+ * Utilizado para executar código do jogador de forma segura com animações sincronizadas.
+ *
+ * @class GameInterpreter
+ * @example
+ * const interpreter = new GameInterpreter({ stepDelay: 20 });
+ * const result = await interpreter.executeCode(userCode, apiSetupFn);
+ * // result: 'completed' | 'stopped' | 'stopped_by_user'
+ */
+export class GameInterpreter {
+ /**
+ * Inicializa um novo interpretador de código do jogo.
+ *
+ * @constructor
+ * @param {Object} [config={}] - Configuração do interpretador
+ * @param {number} [config.stepDelay=20] - Delay (ms) entre steps de execução para visualização
+ */
+ constructor(config = {}) {
+ this.config = {
+ stepDelay: 20,
+ ...config,
+ };
+ this.interpreter = null;
+ this.isRunning = false;
+ this.shouldStop = false;
+ this.stoppedByUser = false;
+ this.pause = 0;
+ }
+
+ async executeCode(code, apiSetupFunction) {
+ if (this.isRunning) {
+ // Aguardar até 200ms como segurança (BaseGameScene já previne este cenário)
+ const start = Date.now();
+ while (this.isRunning && (Date.now() - start) < 200) {
+ await new Promise(resolve => setTimeout(resolve, 20));
+ }
+ }
+
+ this.isRunning = true;
+ this.shouldStop = false;
+ this.stoppedByUser = false;
+ this.pause = 0;
+
+ try {
+ sanitizeCode(code);
+ this.interpreter = new Interpreter(code, apiSetupFunction);
+
+ if (
+ !this.interpreter.ast ||
+ !this.interpreter.ast.body ||
+ this.interpreter.ast.body.length === 0
+ ) {
+ console.warn("No code to execute - empty AST");
+ return "completed";
+ }
+
+ const result = await this._runSteps();
+ return result;
+ } catch (error) {
+ if (error instanceof CodeSanitizationError) {
+ console.warn("[GameInterpreter] Código rejeitado pelo sanitizador:", error.rule, error.message);
+ } else {
+ console.error("Error in executeCode:", error);
+ }
+ throw error;
+ } finally {
+ this.isRunning = false;
+ this.interpreter = null;
+ }
+ }
+
+ async executeFunction(func, ...args) {
+ if (!this.interpreter || !this.globalScope) {
+ return;
+ }
+
+ try {
+ const interpreterArgs = args.map((arg) =>
+ this.interpreter.nativeToPseudo(arg),
+ );
+ const funcObject =
+ typeof func === "string"
+ ? this.interpreter.getValueFromScope(func)
+ : func;
+
+ if (funcObject && funcObject.isFunc) {
+ this.interpreter.queueFunction(funcObject, interpreterArgs);
+ await this._runSteps();
+ } else {
+ console.warn(
+ `Função '${typeof func === "string" ? func : "desconhecida"}' não encontrada no escopo do interpretador.`,
+ );
+ }
+ } catch (error) {
+ console.error("Erro ao execute a função no interpretador:", error);
+ }
+ }
+
+ /**
+ * Para a execução do código de forma segura.
+ * Sinaliza para parar e marca como parado pelo usuário.
+ *
+ * @function stop
+ * @returns {void}
+ */
+ stop() {
+ this.shouldStop = true;
+ this.stoppedByUser = true;
+ }
+
+ /**
+ * Para a execução internamente (ex: jogo detectou condição de fim).
+ * Não marca como parado pelo utilizador — o fluxo de validação continua normal.
+ *
+ * @function stopInternal
+ * @returns {void}
+ */
+ stopInternal() {
+ this.shouldStop = true;
+ this.stoppedByUser = false;
+ }
+
+ /**
+ * Define pausa para sincronizar com animações do jogo.
+ * Interno: controla velocidade de stepping para visualização adequada.
+ *
+ * @function setPause
+ * @param {number} pauseValue - Tempo de pausa em ms (0 = sem pausa)
+ * @returns {void}
+ */
+ setPause(pauseValue) {
+ this.pause = pauseValue;
+ }
+
+ _runSteps() {
+ return new Promise((resolve, reject) => {
+ let stepCount = 0;
+
+ const executeChunk = () => {
+ this.pause = 0;
+ let ticks = 1000;
+ let go;
+
+ do {
+ if (this.shouldStop) {
+ resolve(this.stoppedByUser ? "stopped_by_user" : "stopped");
+ return;
+ }
+ stepCount++;
+
+ try {
+ go = this.interpreter.step();
+ } catch (error) {
+ console.error(`Error in step ${stepCount}:`, error);
+ reject(error);
+ return;
+ }
+
+ if (!go) {
+ resolve("completed");
+ return;
+ }
+
+ // Forçar pausa para evitar travamento
+ if (!ticks--) {
+ this.pause = 1;
+ }
+
+ if (go && this.pause) {
+ go = false;
+ setTimeout(executeChunk, this.pause);
+ }
+ } while (go);
+
+ // Se não há pausa, continuar imediatamente
+ if (!this.pause) {
+ resolve("completed");
+ }
+ };
+
+ executeChunk();
+ });
+ }
+
+ /**
+ * Retorna se o interpretador está em execução no momento.
+ * Útil para UI/controles que precisam habilitar/desabilitar botões.
+ *
+ * @name running
+ * @type {boolean}
+ * @memberof GameInterpreter#
+ */
+ get running() {
+ return this.isRunning;
+ }
+}
diff --git a/app/src/interpreters/__tests__/ApiHelpers.test.js b/app/src/interpreters/__tests__/ApiHelpers.test.js
new file mode 100644
index 0000000..f4d2d97
--- /dev/null
+++ b/app/src/interpreters/__tests__/ApiHelpers.test.js
@@ -0,0 +1,238 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { ApiHelpers } from "../ApiHelpers";
+
+// ── createActionWrapper ──────────────────────────────────────────────────────
+describe("ApiHelpers.createActionWrapper", () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("calls the scene method with the primitive arg", () => {
+ const scene = { move: vi.fn() };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 0);
+ const cb = vi.fn();
+ wrapper(5, cb);
+ expect(scene.move).toHaveBeenCalledWith(5);
+ });
+
+ it("unwraps interpreter object (arg.data) before passing to method", () => {
+ const scene = { move: vi.fn() };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 0);
+ const cb = vi.fn();
+ wrapper({ data: 42 }, cb);
+ expect(scene.move).toHaveBeenCalledWith(42);
+ });
+
+ it("calls callback after animationDelay for sync method", () => {
+ const scene = { move: vi.fn(() => undefined) };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 50);
+ const cb = vi.fn();
+ wrapper(1, cb);
+ expect(cb).not.toHaveBeenCalled();
+ vi.advanceTimersByTime(50);
+ expect(cb).toHaveBeenCalled();
+ });
+
+ it("calls callback after delay when method returns a resolved promise", async () => {
+ const scene = { move: vi.fn(() => Promise.resolve()) };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 30);
+ const cb = vi.fn();
+ wrapper(1, cb);
+ await Promise.resolve(); // let microtask (then) run
+ vi.advanceTimersByTime(30);
+ expect(cb).toHaveBeenCalled();
+ });
+
+ it("calls callback after delay when method returns a rejected promise", async () => {
+ const scene = { move: vi.fn(() => Promise.reject(new Error("fail"))) };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 30);
+ const cb = vi.fn();
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ wrapper(1, cb);
+ await Promise.resolve();
+ await Promise.resolve();
+ vi.advanceTimersByTime(30);
+ expect(cb).toHaveBeenCalled();
+ spy.mockRestore();
+ });
+
+ it("warns and calls callback when method throws synchronously", () => {
+ const scene = { move: vi.fn(() => { throw new Error("boom"); }) };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move", 10);
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ const cb = vi.fn();
+ wrapper(1, cb);
+ vi.advanceTimersByTime(10);
+ expect(cb).toHaveBeenCalled();
+ spy.mockRestore();
+ });
+
+ it("warns and calls callback when method is not found on scene", () => {
+ const scene = {};
+ const wrapper = ApiHelpers.createActionWrapper(scene, "nonexistent", 10);
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const cb = vi.fn();
+ wrapper(1, cb);
+ vi.advanceTimersByTime(10);
+ expect(cb).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("nonexistent"));
+ spy.mockRestore();
+ });
+
+ it("uses default delay of 100ms", () => {
+ const scene = { move: vi.fn() };
+ const wrapper = ApiHelpers.createActionWrapper(scene, "move");
+ const cb = vi.fn();
+ wrapper(1, cb);
+ vi.advanceTimersByTime(99);
+ expect(cb).not.toHaveBeenCalled();
+ vi.advanceTimersByTime(1);
+ expect(cb).toHaveBeenCalled();
+ });
+});
+
+// ── createConditionWrapper ───────────────────────────────────────────────────
+describe("ApiHelpers.createConditionWrapper", () => {
+ it("calls scene method and returns its value", () => {
+ const scene = { isWall: vi.fn(() => true) };
+ const wrapper = ApiHelpers.createConditionWrapper(scene, "isWall");
+ expect(wrapper()).toBe(true);
+ expect(scene.isWall).toHaveBeenCalled();
+ });
+
+ it("passes args through to the scene method", () => {
+ const scene = { check: vi.fn((x, y) => x + y) };
+ const wrapper = ApiHelpers.createConditionWrapper(scene, "check");
+ expect(wrapper(2, 3)).toBe(5);
+ });
+
+ it("warns and returns false when method not found", () => {
+ const scene = {};
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const wrapper = ApiHelpers.createConditionWrapper(scene, "missing");
+ expect(wrapper()).toBe(false);
+ expect(spy).toHaveBeenCalledWith(expect.stringContaining("missing"));
+ spy.mockRestore();
+ });
+});
+
+// ── createHighlightWrapper ───────────────────────────────────────────────────
+describe("ApiHelpers.createHighlightWrapper", () => {
+ it("calls scene.highlightBlock with string id", () => {
+ const scene = { highlightBlock: vi.fn() };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper("block-1");
+ expect(scene.highlightBlock).toHaveBeenCalledWith("block-1");
+ });
+
+ it("converts non-string id to string", () => {
+ const scene = { highlightBlock: vi.fn() };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper(42);
+ expect(scene.highlightBlock).toHaveBeenCalledWith("42");
+ });
+
+ it("uses empty string when id is falsy", () => {
+ const scene = { highlightBlock: vi.fn() };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper(null);
+ expect(scene.highlightBlock).toHaveBeenCalledWith("");
+ });
+
+ it("falls back to scene.workspace.highlightBlock when scene.highlightBlock absent", () => {
+ const workspaceHighlight = vi.fn();
+ const scene = { workspace: { highlightBlock: workspaceHighlight } };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper("b");
+ expect(workspaceHighlight).toHaveBeenCalledWith("b");
+ });
+
+ it("sets scene.highlightPause to true when property exists", () => {
+ const scene = { highlightBlock: vi.fn(), highlightPause: false };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper("b");
+ expect(scene.highlightPause).toBe(true);
+ });
+
+ it("does not set highlightPause when property is undefined", () => {
+ const scene = { highlightBlock: vi.fn() };
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ wrapper("b");
+ expect(scene.highlightPause).toBeUndefined();
+ });
+
+ it("does nothing when neither highlightBlock nor workspace.highlightBlock exists", () => {
+ const scene = {};
+ const wrapper = ApiHelpers.createHighlightWrapper(scene);
+ expect(() => wrapper("b")).not.toThrow();
+ });
+});
+
+// ── registerFunction ─────────────────────────────────────────────────────────
+describe("ApiHelpers.registerFunction", () => {
+ function makeInterpreter() {
+ return {
+ createAsyncFunction: vi.fn((fn) => ({ async: true, fn })),
+ createNativeFunction: vi.fn((fn) => ({ native: true, fn })),
+ setProperty: vi.fn(),
+ };
+ }
+
+ it("registers a sync (native) function when isAsync=false", () => {
+ const interp = makeInterpreter();
+ const scope = {};
+ const fn = vi.fn();
+ ApiHelpers.registerFunction(interp, scope, "move", fn, false);
+ expect(interp.createNativeFunction).toHaveBeenCalledWith(fn);
+ expect(interp.setProperty).toHaveBeenCalledWith(scope, "move", expect.anything());
+ });
+
+ it("registers an async function when isAsync=true", () => {
+ const interp = makeInterpreter();
+ const scope = {};
+ const fn = vi.fn();
+ ApiHelpers.registerFunction(interp, scope, "move", fn, true);
+ expect(interp.createAsyncFunction).toHaveBeenCalledWith(fn);
+ });
+
+ it("defaults isAsync to false", () => {
+ const interp = makeInterpreter();
+ ApiHelpers.registerFunction(interp, {}, "fn", vi.fn());
+ expect(interp.createNativeFunction).toHaveBeenCalled();
+ });
+});
+
+// ── registerMultipleFunctions ────────────────────────────────────────────────
+describe("ApiHelpers.registerMultipleFunctions", () => {
+ function makeInterpreter() {
+ return {
+ createAsyncFunction: vi.fn((fn) => fn),
+ createNativeFunction: vi.fn((fn) => fn),
+ setProperty: vi.fn(),
+ };
+ }
+
+ it("registers each function in the array", () => {
+ const interp = makeInterpreter();
+ const scope = {};
+ const fn1 = vi.fn();
+ const fn2 = vi.fn();
+ ApiHelpers.registerMultipleFunctions(interp, scope, [
+ { name: "move", wrapper: fn1 },
+ { name: "turn", wrapper: fn2, isAsync: true },
+ ]);
+ expect(interp.setProperty).toHaveBeenCalledTimes(2);
+ expect(interp.createNativeFunction).toHaveBeenCalledWith(fn1);
+ expect(interp.createAsyncFunction).toHaveBeenCalledWith(fn2);
+ });
+
+ it("does nothing for empty array", () => {
+ const interp = makeInterpreter();
+ ApiHelpers.registerMultipleFunctions(interp, {}, []);
+ expect(interp.setProperty).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/src/interpreters/__tests__/CodeSanitizer.test.js b/app/src/interpreters/__tests__/CodeSanitizer.test.js
new file mode 100644
index 0000000..f171807
--- /dev/null
+++ b/app/src/interpreters/__tests__/CodeSanitizer.test.js
@@ -0,0 +1,169 @@
+import { describe, it, expect } from "vitest";
+import {
+ sanitizeCode,
+ CodeSanitizationError,
+ DEFAULT_RULES,
+} from "../CodeSanitizer";
+
+describe("CodeSanitizationError", () => {
+ it("tem name e rule corretos", () => {
+ const err = new CodeSanitizationError("mensagem de teste", "alguma_regra");
+ expect(err).toBeInstanceOf(Error);
+ expect(err.name).toBe("CodeSanitizationError");
+ expect(err.rule).toBe("alguma_regra");
+ expect(err.message).toBe("mensagem de teste");
+ });
+});
+
+describe("sanitizeCode — verificação de tipo", () => {
+ it("lança type_check para entrada não-string", () => {
+ expect(() => sanitizeCode(42)).toThrow(CodeSanitizationError);
+ expect(() => sanitizeCode(42)).toThrow(
+ expect.objectContaining({ rule: "type_check" }),
+ );
+ });
+
+ it("lança type_check para entrada null", () => {
+ expect(() => sanitizeCode(null)).toThrow(
+ expect.objectContaining({ rule: "type_check" }),
+ );
+ });
+});
+
+describe("sanitizeCode — regra max_length", () => {
+ it("lança max_length quando o código excede 50 000 caracteres", () => {
+ const codigoLongo = "a".repeat(50_001);
+ expect(() => sanitizeCode(codigoLongo)).toThrow(
+ expect.objectContaining({ rule: "max_length" }),
+ );
+ });
+
+ it("não lança quando o código tem exatamente 50 000 caracteres", () => {
+ const codigo = "a".repeat(50_000);
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+});
+
+describe("sanitizeCode — regra infinite_while", () => {
+ it("lança para while(true) sem break ou return", () => {
+ const codigo = "while(true) { doSomething(); }";
+ expect(() => sanitizeCode(codigo)).toThrow(
+ expect.objectContaining({ rule: "infinite_while" }),
+ );
+ });
+
+ it("lança para while(!0) sem break ou return", () => {
+ const codigo = "while(!0) { doSomething(); }";
+ expect(() => sanitizeCode(codigo)).toThrow(
+ expect.objectContaining({ rule: "infinite_while" }),
+ );
+ });
+
+ it("lança para while(1) sem break ou return", () => {
+ const codigo = "while(1) { doSomething(); }";
+ expect(() => sanitizeCode(codigo)).toThrow(
+ expect.objectContaining({ rule: "infinite_while" }),
+ );
+ });
+
+ it("não lança para while(true) com break", () => {
+ const codigo = "while(true) { if (x > 0) break; }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+
+ it("não lança para while(true) com return", () => {
+ const codigo = "function f() { while(true) { return 1; } }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+
+ it("não lança para while(condição) sem break", () => {
+ const codigo = "while(i < 10) { i++; }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+});
+
+describe("sanitizeCode — regra infinite_for", () => {
+ it("lança para for(;;) sem break ou return", () => {
+ const codigo = "for(;;) { doSomething(); }";
+ expect(() => sanitizeCode(codigo)).toThrow(
+ expect.objectContaining({ rule: "infinite_for" }),
+ );
+ });
+
+ it("não lança para for(;;) com break", () => {
+ const codigo = "for(;;) { if (done) break; }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+
+ it("não lança para for(;;) com return", () => {
+ const codigo = "function f() { for(;;) { return; } }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+
+ it("não lança para um for loop normal", () => {
+ const codigo = "for(let i = 0; i < 10; i++) { doSomething(); }";
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+});
+
+describe("sanitizeCode — regra excessive_nesting", () => {
+ it("lança quando a profundidade de chavetas excede 15", () => {
+ const codigo = "{".repeat(16) + "}".repeat(16);
+ expect(() => sanitizeCode(codigo)).toThrow(
+ expect.objectContaining({ rule: "excessive_nesting" }),
+ );
+ });
+
+ it("não lança com exatamente 15 níveis de profundidade", () => {
+ const codigo = "{".repeat(15) + "}".repeat(15);
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+});
+
+describe("sanitizeCode — código válido", () => {
+ it("não lança para uma função normal", () => {
+ const codigo = `
+ function fatorial(n) {
+ if (n <= 1) return 1;
+ return n * fatorial(n - 1);
+ }
+ fatorial(5);
+ `;
+ expect(() => sanitizeCode(codigo)).not.toThrow();
+ });
+});
+
+describe("sanitizeCode — regras extra", () => {
+ it("aplica regras extra depois das padrão", () => {
+ const regraPersonalizada = {
+ id: "sem_alert",
+ check: (codigo) => /\balert\s*\(/.test(codigo),
+ message: "alert() não é permitido.",
+ };
+ expect(() => sanitizeCode("alert('oi');", [regraPersonalizada])).toThrow(
+ expect.objectContaining({ rule: "sem_alert" }),
+ );
+ });
+
+ it("não aciona a regra extra quando a condição não se verifica", () => {
+ const regraPersonalizada = {
+ id: "sem_alert",
+ check: (codigo) => /\balert\s*\(/.test(codigo),
+ message: "alert() não é permitido.",
+ };
+ expect(() =>
+ sanitizeCode("console.log('oi');", [regraPersonalizada]),
+ ).not.toThrow();
+ });
+});
+
+describe("DEFAULT_RULES", () => {
+ it("exporta um array de regras com id, check e message", () => {
+ expect(Array.isArray(DEFAULT_RULES)).toBe(true);
+ for (const regra of DEFAULT_RULES) {
+ expect(typeof regra.id).toBe("string");
+ expect(typeof regra.check).toBe("function");
+ expect(typeof regra.message).toBe("string");
+ }
+ });
+});
diff --git a/app/src/interpreters/__tests__/GameInterpreter.test.js b/app/src/interpreters/__tests__/GameInterpreter.test.js
new file mode 100644
index 0000000..56c520c
--- /dev/null
+++ b/app/src/interpreters/__tests__/GameInterpreter.test.js
@@ -0,0 +1,322 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { GameInterpreter } from "../GameInterpreter";
+
+// js-interpreter executa código real — usamos mock para controlar o comportamento de forma determinista
+vi.mock("js-interpreter", () => {
+ // Cada teste pode substituir o comportamento de mockStep através de mockStepFn
+ const MockInterpreter = vi.fn(function (code, setup) {
+ if (setup) setup(this, {});
+ this.ast = { body: [{}] }; // AST não vazia por defeito
+ this.step = mockStepFn;
+ this.nativeToPseudo = vi.fn((v) => v);
+ this.getValueFromScope = vi.fn(() => null);
+ this.queueFunction = vi.fn();
+ });
+ return { default: MockInterpreter };
+});
+
+// Função step mutável — os testes podem substituí-la
+let mockStepFn = vi.fn(() => false); // false = termina imediatamente
+
+beforeEach(() => {
+ mockStepFn = vi.fn(() => false); // repõe para "termina imediatamente"
+ vi.useFakeTimers();
+});
+
+afterEach(() => {
+ vi.useRealTimers();
+});
+
+// ── construtor ───────────────────────────────────────────────────────────────
+describe("construtor", () => {
+ it("usa stepDelay padrão de 20", () => {
+ const gi = new GameInterpreter();
+ expect(gi.config.stepDelay).toBe(20);
+ });
+
+ it("combina configuração personalizada", () => {
+ const gi = new GameInterpreter({ stepDelay: 50 });
+ expect(gi.config.stepDelay).toBe(50);
+ });
+
+ it("inicia com isRunning=false", () => {
+ expect(new GameInterpreter().isRunning).toBe(false);
+ });
+
+ it("getter running reflete isRunning", () => {
+ const gi = new GameInterpreter();
+ expect(gi.running).toBe(false);
+ gi.isRunning = true;
+ expect(gi.running).toBe(true);
+ });
+});
+
+// ── stop / stopInternal / setPause ───────────────────────────────────────────
+describe("stop()", () => {
+ it("define shouldStop e stoppedByUser como true", () => {
+ const gi = new GameInterpreter();
+ gi.stop();
+ expect(gi.shouldStop).toBe(true);
+ expect(gi.stoppedByUser).toBe(true);
+ });
+});
+
+describe("stopInternal()", () => {
+ it("define shouldStop=true mas stoppedByUser=false", () => {
+ const gi = new GameInterpreter();
+ gi.stopInternal();
+ expect(gi.shouldStop).toBe(true);
+ expect(gi.stoppedByUser).toBe(false);
+ });
+});
+
+describe("setPause()", () => {
+ it("atualiza o valor de pause", () => {
+ const gi = new GameInterpreter();
+ gi.setPause(100);
+ expect(gi.pause).toBe(100);
+ });
+});
+
+// ── executeCode — caminhos básicos ───────────────────────────────────────────
+describe("executeCode", () => {
+ it("devolve 'completed' quando o interpretador termina imediatamente", async () => {
+ mockStepFn = vi.fn(() => false); // step devolve false = terminado
+ const gi = new GameInterpreter();
+ const p = gi.executeCode("x=1;", vi.fn());
+ vi.runAllTimers();
+ expect(await p).toBe("completed");
+ });
+
+ it("devolve 'completed' para código vazio (AST body vazio)", async () => {
+ const Interpreter = (await import("js-interpreter")).default;
+ Interpreter.mockImplementationOnce(function (code, setup) {
+ this.ast = { body: [] }; // body vazio
+ this.step = vi.fn(() => false);
+ });
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ const result = await gi.executeCode("", vi.fn());
+ expect(result).toBe("completed");
+ spy.mockRestore();
+ });
+
+ it("define isRunning=false após a conclusão", async () => {
+ const gi = new GameInterpreter();
+ const p = gi.executeCode("x=1;", vi.fn());
+ vi.runAllTimers();
+ await p;
+ expect(gi.isRunning).toBe(false);
+ });
+
+ it("lança exceção e limpa quando o construtor do Interpreter lança", async () => {
+ const Interpreter = (await import("js-interpreter")).default;
+ Interpreter.mockImplementationOnce(() => {
+ throw new Error("parse error");
+ });
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ await expect(gi.executeCode("bad code", vi.fn())).rejects.toThrow(
+ "parse error",
+ );
+ expect(gi.isRunning).toBe(false);
+ spy.mockRestore();
+ });
+
+ it("aguarda se já em execução e depois executa", async () => {
+ // A primeira execução usa um step que demora mais ciclos
+ let callCount = 0;
+ mockStepFn = vi.fn(() => {
+ callCount++;
+ return callCount < 2; // executa 2 steps e termina
+ });
+
+ const gi = new GameInterpreter();
+ gi.isRunning = true; // simula já em execução
+
+ // Inicia a segunda chamada — deve aguardar
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ gi.isRunning = false; // permite prosseguir após a verificação
+ const p = gi.executeCode("x=1;", vi.fn());
+ vi.runAllTimers();
+ const result = await p;
+ expect(result).toBeDefined();
+ spy.mockRestore();
+ });
+});
+
+// ── executeCode — caminhos de paragem ────────────────────────────────────────
+describe("executeCode — caminhos de paragem", () => {
+ it("devolve 'stopped_by_user' quando stop() é chamado durante a execução", async () => {
+ const gi = new GameInterpreter();
+ let stepped = false;
+ mockStepFn = vi.fn(() => {
+ if (!stepped) {
+ stepped = true;
+ gi.stop(); // aciona paragem no primeiro step
+ return true; // simula que há mais código para executar
+ }
+ return false;
+ });
+ const p = gi.executeCode("x=1;", vi.fn());
+ vi.runAllTimers();
+ expect(await p).toBe("stopped_by_user");
+ });
+
+ it("devolve 'stopped' quando stopInternal() é chamado durante a execução", async () => {
+ const gi = new GameInterpreter();
+ let stepped = false;
+ mockStepFn = vi.fn(() => {
+ if (!stepped) {
+ stepped = true;
+ gi.stopInternal();
+ return true;
+ }
+ return false;
+ });
+ const p = gi.executeCode("x=1;", vi.fn());
+ vi.runAllTimers();
+ expect(await p).toBe("stopped");
+ });
+
+ it("rejeita quando step() lança exceção", async () => {
+ mockStepFn = vi.fn(() => {
+ throw new Error("step error");
+ });
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ await expect(gi.executeCode("x=1;", vi.fn())).rejects.toThrow("step error");
+ spy.mockRestore();
+ });
+});
+
+// ── executeFunction ───────────────────────────────────────────────────────────
+describe("executeFunction", () => {
+ it("devolve imediatamente quando o interpretador é null", async () => {
+ const gi = new GameInterpreter();
+ // interpretador é null por defeito
+ const result = await gi.executeFunction("myFunc");
+ expect(result).toBeUndefined();
+ });
+
+ it("emite aviso quando a função não é encontrada no scope", async () => {
+ const gi = new GameInterpreter();
+ // Define o interpretador manualmente sem globalScope
+ gi.interpreter = {
+ nativeToPseudo: vi.fn((v) => v),
+ getValueFromScope: vi.fn(() => null), // devolve null — não é uma função
+ queueFunction: vi.fn(),
+ };
+ gi.globalScope = {}; // define para que a verificação de null passe
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ await gi.executeFunction("missing");
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+ });
+});
+
+// ── executeFunction — bloco catch ─────────────────────────────────────────────
+describe("executeFunction — bloco catch", () => {
+ it("regista console.error quando nativeToPseudo lança exceção", async () => {
+ const gi = new GameInterpreter();
+ gi.interpreter = {
+ nativeToPseudo: vi.fn(() => {
+ throw new Error("pseudo error");
+ }),
+ getValueFromScope: vi.fn(() => null),
+ queueFunction: vi.fn(),
+ };
+ gi.globalScope = {};
+ const spy = vi.spyOn(console, "error").mockImplementation(() => {});
+ await gi.executeFunction("myFunc", 42);
+ expect(spy).toHaveBeenCalled();
+ spy.mockRestore();
+ });
+});
+
+// ── _runSteps — mecanismo de divisão em blocos ────────────────────────────────
+describe("_runSteps — mecanismo de divisão em blocos", () => {
+ it("agenda o próximo bloco após 1000 steps e resolve 'completed'", async () => {
+ let callCount = 0;
+ // Devolve true para 1001 chamadas (atingindo o limite de 1000 ciclos) e depois false
+ mockStepFn = vi.fn(() => {
+ callCount++;
+ return callCount <= 1001; // primeiros 1001 → true, 1002º → false
+ });
+
+ const gi = new GameInterpreter();
+ const p = gi.executeCode("loop", vi.fn());
+
+ // O primeiro bloco executa 1001 steps, atinge o limite de ciclos e agenda o próximo bloco
+ // Avança os timers para acionar o callback de executeChunk
+ vi.runAllTimers();
+
+ expect(await p).toBe("completed");
+ });
+});
+
+// ── executeFunction — caminho de sucesso (funcObject.isFunc = true) ───────────
+describe("executeFunction — caminho de sucesso", () => {
+ it("chama queueFunction e executa steps quando a função é encontrada", async () => {
+ const gi = new GameInterpreter();
+ const mockQueueFunction = vi.fn();
+ gi.interpreter = {
+ nativeToPseudo: vi.fn((v) => v),
+ getValueFromScope: vi.fn(() => ({ isFunc: true })),
+ queueFunction: mockQueueFunction,
+ step: vi.fn(() => false), // step termina imediatamente
+ };
+ gi.globalScope = {};
+ await gi.executeFunction("myFunc", 1, 2);
+ expect(mockQueueFunction).toHaveBeenCalled();
+ });
+});
+
+// ── executeCode — sanitização ─────────────────────────────────────────────────
+describe("executeCode — sanitização", () => {
+ it("rejeita com CodeSanitizationError para while(true) infinito sem break", async () => {
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ await expect(
+ gi.executeCode("while(true) { doSomething(); }", vi.fn()),
+ ).rejects.toMatchObject({
+ name: "CodeSanitizationError",
+ rule: "infinite_while",
+ });
+ expect(spy).toHaveBeenCalledWith(
+ "[GameInterpreter] Código rejeitado pelo sanitizador:",
+ "infinite_while",
+ expect.any(String),
+ );
+ spy.mockRestore();
+ });
+
+ it("rejeita com CodeSanitizationError quando o código excede max_length", async () => {
+ const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ await expect(
+ gi.executeCode("a".repeat(50_001), vi.fn()),
+ ).rejects.toMatchObject({ name: "CodeSanitizationError", rule: "max_length" });
+ spy.mockRestore();
+ });
+
+ it("não chama console.error para erros de sanitização (apenas console.warn)", async () => {
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ const errSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ const gi = new GameInterpreter();
+ await expect(
+ gi.executeCode("while(true) { doSomething(); }", vi.fn()),
+ ).rejects.toMatchObject({ name: "CodeSanitizationError" });
+ expect(errSpy).not.toHaveBeenCalled();
+ warnSpy.mockRestore();
+ errSpy.mockRestore();
+ });
+
+ it("permite código válido passar pela sanitização", async () => {
+ mockStepFn = vi.fn(() => false);
+ const gi = new GameInterpreter();
+ const p = gi.executeCode("var x = 1;", vi.fn());
+ vi.runAllTimers();
+ expect(await p).toBe("completed");
+ });
+});
diff --git a/app/src/main.jsx b/app/src/main.jsx
new file mode 100644
index 0000000..e291142
--- /dev/null
+++ b/app/src/main.jsx
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview React component for main.jsx
+ *
+ * @module main
+ */
+
+import React from "react";
+import ReactDOM from "react-dom/client";
+import "./blockly/blocklyConfig.js"; // Configuração global do Blockly (locale PT-BR)
+import App from "./App.jsx";
+
+// Log de versão para rastreabilidade em produção
+if (import.meta.env.PROD) {
+ console.log(
+ `%cDecoda v${import.meta.env.VITE_APP_VERSION} (${import.meta.env.VITE_GIT_HASH})`,
+ "color: #2563eb; font-weight: bold; font-size: 12px;"
+ );
+}
+
+ReactDOM.createRoot(document.getElementById("root")).render( );
diff --git a/app/src/pages/About/About.jsx b/app/src/pages/About/About.jsx
new file mode 100644
index 0000000..67d7b59
--- /dev/null
+++ b/app/src/pages/About/About.jsx
@@ -0,0 +1,242 @@
+/**
+ * @fileoverview React component for About.jsx
+ *
+ * @module pages.About.About
+ */
+
+import { ArrowRight, Users, Heart, Lightbulb, Globe } from "lucide-react";
+import Navbar from "../../components/Navbar";
+import Footer from "../HomePage/Footer";
+import Baner from "../../assets/baner_quemsomos.png"
+import BanerMobile from "../../assets/banner_quemsomos_mobile.png"
+import { BookOpen } from 'lucide-react'
+import iconlivro from "../../assets/icon_livro.svg"
+import iconmao from "../../assets/icon_mao.svg"
+import iconcabeca from "../../assets/icon_cabeca.svg"
+import iconelo from "../../assets/icon_elo.svg"
+import iconpasta from "../../assets/icon_pasta.svg"
+import iconcirculo from "../../assets/icon_circulo.svg"
+
+export default function About() {
+ return (
+ <>
+ {/* Navegação */}
+
+
+
+
+
+
+ {/* Container principal */}
+
+
+ {/* Conteúdo textual */}
+
+
+
+
+
+
+
+
+
+
+
+
+ Quem somos
+
+
+ Educadores do Núcleo de Tecnologia do MTST
+
+
+
+
+
+
+
+
+
+ Por uma educação tecnológica para todos
+
+
+
+
+
+ {/*CARD 1*/}
+
+
+
+
+
+
+
+ Educação popular
+
+
+ Criamos métodos de ensino contextualizados, críticos e adaptados à realidade brasileira.
+
+
+
+ {/*CARD 2*/}
+
+
+
+
+
+
+ Compromisso social
+
+
+ Lutamos por uma tecnologia inclusiva que reduza desigualdades e promova justiça social.
+
+
+
+ {/*CARD 3*/}
+
+
+
+
+
+
+ Pedagogia crítica
+
+
+ Criamos métodos de ensino contextualizados, críticos e adaptados à realidade brasileira.
+
+
+
+
+
+
+
+
+
+
+
+ Nosso trabalho
+
+
+ {/*linha*/}
+
+
+
+
+
+ Somos parte do Núcleo de Tecnologia do MTST, e somos comprometidos com a democratização do ensino de programação e tecnologia. Acreditamos que a educação digital deve ser acessível, gratuita e transformadora para todas as pessoas.
+
+
+
+ Nossa missão é empoderar estudantes e professores através de ferramentas pedagógicas abertas, baseadas em metodologias críticas e participativas. Trabalhamos para que a tecnologia seja um instrumento de inclusão social e desenvolvimento comunitário.
+
+
+
+
+ O Decoda nasceu dessa visão: uma plataforma educacional que valoriza o pensamento computacional como ferramenta de transformação social, respeitando a diversidade de saberes e promovendo a autonomia pedagógica.
+
+
+
+ {/*card nosso trabalho*/}
+
+
+
+
+
+
+
+
+ Educação Popular
+
+
+
+ Construção coletiva do saber
+
+
+
+
+
+
+
+
+
+
+ Inclusão Digital
+
+
+
+ Tecnologia para todos
+
+
+
+
+
+
+
+
+
+
+ Pensamento Crítico
+
+
+
+ Autonomia e reflexão
+
+
+
+
+
+
+
+
+
+
+ Programação Visual
+
+
+
+ Aprender fazendo
+
+
+
+
+
+
+
+
+
+
+ Transformação Social
+
+
+
+ Tecnologia para mudar
+
+
+
+
+
+
+
+ {/* botao conheca nossa historia */}
+
+
+
+
+
+
+
+ {/* Footer */}
+
+ >
+ );
+}
diff --git a/app/src/pages/Atividades/Atividades.jsx b/app/src/pages/Atividades/Atividades.jsx
new file mode 100644
index 0000000..e6c2431
--- /dev/null
+++ b/app/src/pages/Atividades/Atividades.jsx
@@ -0,0 +1,472 @@
+import React, { useState, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import Navbar from "../../components/Navbar";
+import Footer from "../HomePage/Footer";
+import { gameRegistryUtils } from "../../config/gameRegistry";
+import { difficultyLevels } from "../../config/difficulty";
+import { gameCategory } from "../../config/categories";
+import { gameType } from "../../config/type";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { useAtividadesTour } from "./hooks/useAtividadesTour";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../styles/shepherd-theme.css";
+
+const Atividades = () => {
+ const navigate = useNavigate();
+
+ // Inicializa o tour guiado
+ useAtividadesTour();
+ const [selectedCategory, setSelectedCategory] = useState("all");
+ const [selectedDifficulty, setSelectedDifficulty] = useState("all");
+ const [selectedType, setSelectedType] = useState("all");
+ const [searchQuery, setSearchQuery] = useState("");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPage, setItemsPerPage] = useState(8);
+
+ // Obter jogos filtrados
+ const filteredGames = useMemo(() => {
+ let games = gameRegistryUtils.getActiveGames();
+
+ // Filtrar por categoria
+ if (selectedCategory !== "all") {
+ games = games.filter((game) => game.categoria === selectedCategory);
+ }
+
+ // Filtrar por dificuldade
+ if (selectedDifficulty !== "all") {
+ games = games.filter((game) => game.dificuldade === selectedDifficulty);
+ }
+
+ // Filtrar por tipo
+ if (selectedType !== "all") {
+ games = games.filter((game) => game.type === selectedType);
+ }
+
+ // Filtrar por busca
+ if (searchQuery.trim()) {
+ const query = searchQuery.toLowerCase();
+ games = games.filter(
+ (game) =>
+ game.gameName.toLowerCase().includes(query) ||
+ game.descricao.toLowerCase().includes(query) ||
+ (game.conceitos &&
+ game.conceitos.some((concept) =>
+ concept.toLowerCase().includes(query)
+ ))
+ );
+ }
+
+ return games;
+ }, [selectedCategory, selectedDifficulty, selectedType, searchQuery]);
+
+ // Paginação
+ const totalPages = Math.ceil(filteredGames.length / itemsPerPage);
+ const startIndex = (currentPage - 1) * itemsPerPage;
+ const endIndex = startIndex + itemsPerPage;
+ const currentGames = filteredGames.slice(startIndex, endIndex);
+
+ // Reset para página 1 quando filtros mudam
+ React.useEffect(() => {
+ setCurrentPage(1);
+ }, [
+ selectedCategory,
+ selectedDifficulty,
+ selectedType,
+ searchQuery,
+ itemsPerPage,
+ ]);
+
+ const getDifficultyColor = (difficulty) => {
+ return difficultyLevels[difficulty]?.color || "#6c757d";
+ };
+
+ const getCategoryColor = (category) => {
+ return gameCategory[category]?.color || "#6c757d";
+ };
+
+ const getTypeColor = (type) => {
+ return gameType[type]?.color || "#6c757d";
+ };
+
+ const getNameFromType = (type) => {
+ return gameType[type]?.name || type;
+ };
+
+ const handleGameSelect = (game) => {
+ if (game.route) {
+ navigate(game.route);
+ } else {
+ navigate(`/atividades/programacao/${game.gameId || game.id}`);
+ }
+ };
+
+ const handleClearFilters = () => {
+ setSelectedCategory("all");
+ setSelectedDifficulty("all");
+ setSelectedType("all");
+ setSearchQuery("");
+ };
+
+ return (
+ <>
+
+
+ {/* Background Gradient */}
+
+
+
+ {/* Container principal */}
+
+ {/* Header */}
+
+
+
+ Atividades de Programação
+
+
+ Explore nossos jogos educativos para aprender programação.
+
+
+
+
+ {/* Filtros */}
+
+
+ {/* Busca */}
+
+
+ Buscar atividades
+
+
+
setSearchQuery(e.target.value)}
+ />
+
+
+
+
+ {/* Categoria */}
+
+
+ Categoria
+
+ setSelectedCategory(e.target.value)}
+ >
+ Todas as categorias
+ {Object.entries(gameCategory).map(([key, category]) => (
+
+ {category.name}
+
+ ))}
+
+
+
+ {/* Tipo */}
+
+
+ Tipo
+
+ setSelectedType(e.target.value)}
+ >
+ Todos os tipos
+ {Object.entries(gameType).map(([key, type]) => (
+
+ {type.name}
+
+ ))}
+
+
+
+ {/* Dificuldade */}
+
+
+ Dificuldade
+
+ setSelectedDifficulty(e.target.value)}
+ >
+ Todas as dificuldades
+ {Object.entries(difficultyLevels).map(([key, level]) => (
+
+ {level.name}
+
+ ))}
+
+
+
+ {/* Itens por página */}
+
+
+ Itens por página
+
+ setItemsPerPage(Number(e.target.value))}
+ >
+ 8 atividades
+ 16 atividades
+ 32 atividades
+
+
+
+
+ {/* Botão limpar filtros */}
+ {(selectedCategory !== "all" ||
+ selectedDifficulty !== "all" ||
+ selectedType !== "all" ||
+ searchQuery) && (
+
+
+ Limpar todos os filtros
+
+
+ )}
+
+
+ {/* Resultado e contador */}
+
+
+ {searchQuery
+ ? `Resultados para "${searchQuery}"`
+ : "Todas as Atividades"}
+
+
+ {filteredGames.length}{" "}
+ {filteredGames.length === 1 ? "atividade" : "atividades"}
+
+
+
+ {/* Grid de Cards */}
+ {currentGames.length > 0 ? (
+ <>
+
+ {currentGames.map((game) => (
+
handleGameSelect(game)}
+ data-prd-ready={game.prdReady}
+ className="relative bg-white/50 backdrop-blur-lg shadow-md hover:shadow-xl rounded-xl border border-gray-200 p-6 flex flex-col md:flex-row gap-6 cursor-pointer transition-all duration-300 hover:scale-[1.02] group data-[prd-ready=false]:opacity-50"
+ >
+ {/* Thumbnail */}
+
+
+
{
+ e.target.style.display = 'none';
+ e.target.parentElement.innerHTML = `
${game.icon}
`;
+ }}
+ />
+
+
+
+ {/* Conteúdo */}
+
+
+
+
+ {game.gameName}
+
+
+ {/* Badges */}
+
+
+ {gameCategory[game.categoria]?.name}
+
+ {!game.prdReady && (
+
+ Em desenvolvimento
+
+ )}
+
+
+
+
+ {game.descricao}
+
+
+ {/* Informações adicionais */}
+
+
+ ⏱️ {game.tempoEstimado}
+
+
+ 🎯 {game.faixaEtaria}
+
+
+ 📊 {game.fases?.length || 0} fases
+
+
+
+ {/* Conceitos */}
+ {game.conceitos && game.conceitos.length > 0 && (
+
+ {game.conceitos.slice(0, 3).map((concept) => (
+
+ {concept}
+
+ ))}
+ {game.conceitos.length > 3 && (
+
+
+ +{game.conceitos.length - 3}
+
+
+
+ {game.conceitos.slice(3).join(", ")}
+
+
+ )}
+
+ )}
+
+
+ {/* Badges de tipo e dificuldade */}
+
+
+
+ {getNameFromType(game.type)}
+
+
+ {difficultyLevels[game.dificuldade]?.name}
+
+
+
+
+
+ ))}
+
+
+ {/* Paginação */}
+ {totalPages > 1 && (
+
+
+ setCurrentPage((prev) => Math.max(1, prev - 1))
+ }
+ disabled={currentPage === 1}
+ className="p-2 rounded-lg bg-white border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
+ >
+
+
+
+
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map(
+ (page) => (
+ setCurrentPage(page)}
+ className={`px-4 py-2 rounded-lg font-semibold transition-all ${
+ currentPage === page
+ ? "bg-gradient-to-r from-red-600 via-pink-600 to-purple-600 text-white shadow-lg"
+ : "bg-white border border-gray-200 text-gray-900 hover:bg-gray-50"
+ }`}
+ >
+ {page}
+
+ )
+ )}
+
+
+
+ setCurrentPage((prev) => Math.min(totalPages, prev + 1))
+ }
+ disabled={currentPage === totalPages}
+ className="p-2 rounded-lg bg-white border border-gray-200 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
+ >
+
+
+
+ )}
+ >
+ ) : (
+ // Estado vazio
+
+
🎮
+
+ Nenhuma atividade encontrada
+
+
+ Não encontramos atividades que correspondam aos seus critérios.
+ Que tal tentar uma busca diferente?
+
+
+ Ver todas as atividades
+
+
+ )}
+
+
+
+ {/* Footer */}
+
+ >
+ );
+};
+
+export default Atividades;
diff --git a/app/src/pages/Atividades/config/tourSteps.js b/app/src/pages/Atividades/config/tourSteps.js
new file mode 100644
index 0000000..79bde9b
--- /dev/null
+++ b/app/src/pages/Atividades/config/tourSteps.js
@@ -0,0 +1,358 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module pages.Atividades.config.tourSteps
+ */
+
+// Helper para detectar mobile e ajustar posicionamento
+const isMobile = () => window.innerWidth < 768;
+
+const getAttachPosition = (
+ desktopPosition = "right",
+ mobilePosition = "bottom",
+) => {
+ return isMobile() ? mobilePosition : desktopPosition;
+};
+
+export const tourSteps = [
+ {
+ id: "welcome",
+ text: `
+
+ Aqui você encontra todos os jogos educativos disponíveis para aprender programação de forma divertida.
+ Vamos fazer um tour rápido para conhecer os recursos!
+ `,
+ buttons: [
+ {
+ text: "Pular Tour",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.complete();
+ },
+ },
+ {
+ text: "Iniciar Tour",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "search-bar",
+ attachTo: {
+ element: () =>
+ document.querySelector('input[placeholder*="Digite o nome"]'),
+ on: getAttachPosition("bottom", "bottom"),
+ },
+ text: `
+
+ Use a barra de busca para encontrar atividades rapidamente!
+ Você pode buscar por:
+
+ Nome do jogo: "Semáforo", "Motoca", etc.
+ Conceitos: "loops", "condicionais", "funções"
+ Descrição: palavras-chave do conteúdo
+
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "filters-category",
+ attachTo: {
+ element: () => document.querySelector("select[value]"),
+ on: getAttachPosition("bottom", "bottom"),
+ },
+ text: `
+
+ Refine sua busca usando os filtros disponíveis:
+
+ Categoria: Jogos, Desafios, Projetos
+ Tipo: Guiado ou Livre
+ Dificuldade: Iniciante, Intermediário, Avançado
+
+ Combine múltiplos filtros para encontrar exatamente o que precisa!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "items-per-page",
+ attachTo: {
+ element: () => {
+ const selects = Array.from(document.querySelectorAll("select"));
+ return selects.find(
+ (select) =>
+ select.querySelector('option[value="8"]') &&
+ select.textContent.includes("atividades"),
+ );
+ },
+ on: getAttachPosition("bottom", "bottom"),
+ },
+ text: `
+
+ Escolha quantas atividades deseja ver por página:
+
+ 8 atividades: Ideal para navegar com calma
+ 16 atividades: Visão geral equilibrada
+ 32 atividades: Ver todas de uma vez
+
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "clear-filters",
+ attachTo: {
+ element: () => document.querySelector('button[class*="gradient"]'),
+ on: getAttachPosition("top", "bottom"),
+ },
+ text: `
+
+ Quando você aplicar filtros, este botão aparecerá automaticamente.
+ Use-o para remover todos os filtros de uma vez e ver todas as atividades disponíveis novamente.
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ when: {
+ show: function () {
+ // Se o botão não estiver visível, pula este passo
+ const button = document.querySelector('button[class*="gradient"]');
+ if (!button || button.closest(".mt-6")?.style.display === "none") {
+ this.next();
+ }
+ },
+ },
+ },
+ {
+ id: "activity-cards",
+ attachTo: {
+ element: () => document.querySelector(".my-3.grid"),
+ on: getAttachPosition("top", "top"),
+ },
+ text: `
+
+ Cada card mostra informações importantes:
+
+ Ícone e Nome: Identidade visual da atividade
+ Descrição: Resumo do que você vai aprender
+ Badges: Categoria, tipo e dificuldade
+ Informações: Tempo estimado, faixa etária e número de fases
+ Conceitos: Tópicos de programação abordados
+
+ Clique em qualquer card para iniciar a atividade!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "pagination",
+ attachTo: {
+ element: () => document.querySelector(".mt-12.flex.items-center"),
+ on: "top",
+ },
+ text: `
+
+ Quando houver muitas atividades, use a paginação para navegar:
+
+ Use as setas para ir para a página anterior ou próxima
+ Clique nos números para ir direto para uma página específica
+
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ when: {
+ show: function () {
+ // Se não houver paginação, pula este passo
+ const pagination = document.querySelector(".mt-12.flex.items-center");
+ if (!pagination) {
+ this.next();
+ }
+ },
+ },
+ },
+ {
+ id: "finish",
+ text: `
+
+ Agora você conhece todos os recursos da página de atividades.
+ Dicas finais:
+
+ Comece com atividades de nível Iniciante se você é novo em programação
+ Use os filtros para encontrar atividades por conceito específico
+ Explore diferentes categorias para variar seu aprendizado
+ Divirta-se aprendendo! 🎮
+
+
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Começar a Explorar!",
+ action() {
+ this.complete();
+ },
+ },
+ ],
+ },
+];
+
+export const tourOptions = {
+ defaultStepOptions: {
+ cancelIcon: {
+ enabled: true,
+ },
+ classes: "custom-tour-step",
+ scrollTo: { behavior: "smooth", block: "center" },
+ },
+ useModalOverlay: true,
+};
diff --git a/app/src/pages/Atividades/hooks/useAtividadesTour.js b/app/src/pages/Atividades/hooks/useAtividadesTour.js
new file mode 100644
index 0000000..149a798
--- /dev/null
+++ b/app/src/pages/Atividades/hooks/useAtividadesTour.js
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview Utility module for useAtividadesTour.js
+ *
+ * @module pages.Atividades.hooks.useAtividadesTour
+ */
+
+import { useTour } from "../../../hooks/useTour";
+import { tourSteps, tourOptions } from "../config/tourSteps";
+
+const TOUR_STORAGE_KEY = "atividades-tour-completed";
+
+/**
+ * Hook específico da página de Atividades que usa o useTour genérico
+ * Mantém a mesma interface para compatibilidade
+ */
+export const useAtividadesTour = () => {
+ const { startTour, completeTour, resetTour } = useTour({
+ steps: tourSteps,
+ options: tourOptions,
+ storageKey: TOUR_STORAGE_KEY,
+ autoStart: true,
+ startDelay: 1500,
+ });
+
+ return {
+ startTour,
+ completeTour,
+ resetTour,
+ };
+};
diff --git a/app/src/pages/Atividades/utils/progress.js b/app/src/pages/Atividades/utils/progress.js
new file mode 100644
index 0000000..8eebb2d
--- /dev/null
+++ b/app/src/pages/Atividades/utils/progress.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Utility module for progress.js
+ *
+ * @module pages.Atividades.utils.progress
+ */
+
+/*
+ MIGRATION: Wave 2 — Portuguese -> English identifier migration
+ All storage keys and function names use English identifiers.
+*/
+
+/**
+ * Calcula a porcentagem de conclusão de um jogo (0-100%).
+ * Recupera as fases completadas do localStorage usando a chave padrão de migração.
+ *
+ * A chave de armazenamento segue o padrão: `{gameId}-completed-phases`
+ * Mantém compatibilidade com a chave anterior durante transição.
+ *
+ * @function calculateProgress
+ * @param {string} gameId - ID único do jogo (ex: 'molemash', 'semaforo')
+ * @param {Array} phases - Array de fases do jogo (ex: [1, 2, 3, 4, 5])
+ * @returns {number} Percentual de conclusão do jogo (0-100, sempre arredondado)
+ *
+ * @example
+ * const percent = calculateProgress('semaforo', [1, 2, 3]);
+ * // Se completedPhases=[1], retorna 33
+ *
+ * @throws {console.error} Se localStorage estiver corrompido (retorna 0)
+ */
+export const calculateProgress = (gameId, phases) => {
+ if (!phases || phases.length === 0) return 0;
+
+ try {
+ const storageKey = `${gameId}-completed-phases`;
+ const completedPhases = JSON.parse(
+ localStorage.getItem(storageKey) || "[]",
+ );
+
+ // Calculamos a porcentagem baseada no tamanho do array de concluídos
+ const percentage = (completedPhases.length / phases.length) * 100;
+
+ // Retorna no máximo 100 e arredondado
+ return Math.min(Math.round(percentage), 100);
+ } catch (error) {
+ console.error("Erro ao ler localStorage:", error);
+ return 0;
+ }
+};
diff --git a/app/src/pages/Educadores/Educadores.jsx b/app/src/pages/Educadores/Educadores.jsx
new file mode 100644
index 0000000..eac2c28
--- /dev/null
+++ b/app/src/pages/Educadores/Educadores.jsx
@@ -0,0 +1,188 @@
+/**
+ * @fileoverview React component for Educadores.jsx
+ *
+ * @module pages.Educadores.Educadores
+ */
+
+import Navbar from "../../components/Navbar";
+import Footer from "../HomePage/Footer";
+import educadoresImg from "./assets/educadores.png";
+import { Workflow, BookOpen, Gift, Users } from "lucide-react";
+
+const stepsData = [
+ {
+ id: 1,
+ title: "Explore a Plataforma",
+ description:
+ "Navegue pelas atividades. Teste você mesmo para entender a experiência dos alunos.",
+ },
+ {
+ id: 2,
+ title: "Acesse a Documentação",
+ description:
+ "Consulte nossos guias pedagógicos completos com planos de aula, estratégias de ensino e atividades desplugadas.",
+ },
+ {
+ id: 3,
+ title: "Prepare sua Aula",
+ description:
+ "Escolha as atividades adequadas ao nível da turma e planeje a sequência didática usando nossos recursos.",
+ },
+ {
+ id: 4,
+ title: "Ensine e Inspire",
+ description:
+ "Aplique em sala de aula, acompanhe o progresso dos alunos e ajuste conforme necessário. Sua turma vai adorar!",
+ },
+];
+
+export default function Educadores() {
+ return (
+ <>
+ {/* Navegação */}
+
+
+
+
+
+ {/* Título Principal */}
+
+
+ Seja um Educador Popular de Tecnologia
+
+
+ Saiba como utilizar o Decoda para transformar suas aulas de
+ programação em experiências envolventes e eficazes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {stepsData.map((step) => (
+
+
+ {step.id}
+
+
+ {step.title}
+
+
+ {step.description}
+
+
+ ))}
+
+
+
+ {/* CTA Button */}
+
+
+ {/* Seção de Benefícios Extras */}
+
+
+ Por que escolher o Decoda?
+
+
+
+
+
+
+
+
+ Atividades pensadas para o Contexto
+
+
+
+ Aprenda programação através de desafios divertidos e
+ contextualizados que mantêm os alunos motivados.
+
+
+
+
+
+
+
+
+ Material Pedagógico Completo
+
+
+
+ Guias detalhados, planos de aula prontos e atividades
+ desplugadas para complementar suas aulas.
+
+
+
+
+
+
+
+
+ 100% Gratuito
+
+
+
+ Sem cadastro, sem instalação, sem custos. Acesse direto do
+ navegador e comece a ensinar hoje mesmo.
+
+
+
+
+
+
+
+
+ Aprendizagem Colaborativa
+
+
+
+ Promova discussões em grupo e desenvolvimento do pensamento
+ crítico através de desafios compartilhados.
+
+
+
+
+
+
+
+
+ {/* Footer */}
+
+ >
+ );
+}
diff --git a/app/src/pages/Educadores/assets/educadores.png b/app/src/pages/Educadores/assets/educadores.png
new file mode 100644
index 0000000..3193848
Binary files /dev/null and b/app/src/pages/Educadores/assets/educadores.png differ
diff --git a/app/src/pages/Faq/Faq.jsx b/app/src/pages/Faq/Faq.jsx
new file mode 100644
index 0000000..28d6475
--- /dev/null
+++ b/app/src/pages/Faq/Faq.jsx
@@ -0,0 +1,188 @@
+/**
+ * @fileoverview React component for Faq.jsx
+ *
+ * @module pages.Faq.Faq
+ */
+
+import Navbar from "../../components/Navbar";
+import Footer from "../HomePage/Footer";
+
+const faqData = [
+ {
+ id: 1,
+ question: "O que é o Decoda?",
+ answer:
+ "O Decoda é uma plataforma educacional de programação visual desenvolvida especialmente para o ensino de lógica de programação. Utilizamos blocos visuais arrastar-e-soltar baseados no Google Blockly, eliminando a barreira da sintaxe complexa e focando no raciocínio lógico através de jogos educativos contextualizados.",
+ },
+ {
+ id: 2,
+ question: "Por que usar programação com blocos ao invés de código texto?",
+ answer:
+ 'Os blocos visuais eliminam erros de sintaxe (não há como "escrever errado"), permitem foco total na lógica de programação, são intuitivos e acessíveis para todas as idades, e oferecem feedback visual imediato. Os blocos só encaixam onde fazem sentido logicamente, permitindo que estudantes se concentrem em resolver problemas ao invés de decorar sintaxe.',
+ },
+ {
+ id: 3,
+ question: "A plataforma é gratuita?",
+ answer:
+ "Sim! O Decoda é 100% gratuito. Não requer cadastro, instalação ou pagamento. Todos as atividades, recursos pedagógicos e documentação estão disponíveis gratuitamente para educadores e alunos. Acreditamos que a educação em programação deve ser acessível a todos.",
+ },
+ {
+ id: 4,
+ question: "O que é Computação Desplugada?",
+ answer:
+ "É uma metodologia que ensina conceitos de programação e pensamento computacional sem usar computadores, através de atividades práticas, jogos e brincadeiras. Nossa documentação oferece diversas atividades desplugadas que educadores podem aplicar antes de usar a plataforma digital, facilitando o entendimento dos conceitos e reduzindo a ansiedade tecnológica.",
+ },
+ {
+ id: 5,
+ question: "Quais conceitos de programação são ensinados?",
+ answer:
+ "Baseamos nosso ensino nos 4 fundamentos da programação e nos pilares do pensamento computacional:",
+ list: [
+ { title: "Sequências", description: "Ordem lógica de comandos" },
+ { title: "Condicionais", description: "Decisões SE/ENTÃO" },
+ { title: "Repetição", description: "Estruturas de repetição" },
+ { title: "Funções", description: "Agrupamento e reutilização" },
+ { title: "Variáveis", description: "Armazenamento de dados" },
+ { title: "Eventos", description: "Interações do usuário" },
+ ],
+ },
+ {
+ id: 6,
+ question: "Preciso instalar algum programa?",
+ answer:
+ "Não! O Decoda é uma plataforma web que funciona diretamente no navegador. Basta acessar o site e começar a programar. Não requer instalação, downloads ou configurações complexas. Funciona em computadores, tablets e smartphones com qualquer navegador moderno.",
+ },
+ {
+ id: 7,
+ question: "A plataforma oferece recursos para educadores?",
+ answer: "Sim! Oferecemos documentação completa para educadores com:",
+ list: [
+ "Guias pedagógicos detalhados",
+ "Estratégias de ensino para sala de aula",
+ "Planos de aula prontos",
+ "Atividades de computação desplugada",
+ "Ferramentas de avaliação de aprendizado",
+ "Dicas de gestão e organização da sala",
+ "Resolução de problemas comuns",
+ ],
+ },
+ {
+ id: 8,
+ question: "Meu progresso é salvo automaticamente?",
+ answer:
+ "Sim! O Decoda salva automaticamente seu progresso no navegador. Você pode fechar a aba e voltar depois que seu trabalho estará preservado. Isso funciona sem necessidade de cadastro ou login, mantendo a privacidade e simplicidade da plataforma.",
+ },
+ {
+ id: 9,
+ question: "Posso usar o Decoda em sala de aula?",
+ answer:
+ "Absolutamente! O Decoda foi desenvolvido especificamente para uso educacional em sala de aula. A plataforma promove aprendizagem colaborativa, discussão coletiva de estratégias e desenvolvimento do pensamento crítico. Não requer cadastro de alunos, facilitando a adoção em ambientes escolares.",
+ },
+ {
+ id: 10,
+ question: "A plataforma funciona em dispositivos móveis?",
+ answer:
+ "Sim! O Decoda possui design responsivo e funciona em tablets e smartphones. A interface se adapta automaticamente ao tamanho da tela, permitindo programação por toque em dispositivos móveis. Recomendamos tablets para uma experiência mais confortável, mas smartphones também são suportados.",
+ },
+];
+
+export default function Faq() {
+ return (
+ <>
+ {/* Navegação */}
+
+
+
+ {/* Container principal */}
+
+
+
+ Perguntas Frequentes
+
+
+
+ {faqData.map((faq, index) => (
+
+
+
+
+ {faq.question}
+
+
+
+
+
+
+
+
+
+
+
+
+ {faq.answer}
+
+ {faq.list && (
+
+ {faq.list.map((item, idx) => (
+
+ {typeof item === "string" ? (
+ item
+ ) : (
+ <>
+ {item.title}: {" "}
+ {item.description}
+ >
+ )}
+
+ ))}
+
+ )}
+
+
+ ))}
+
+
+
+
+
+ {/* Footer */}
+
+ >
+ );
+}
diff --git a/app/src/pages/HomePage/About.jsx b/app/src/pages/HomePage/About.jsx
new file mode 100644
index 0000000..fddf317
--- /dev/null
+++ b/app/src/pages/HomePage/About.jsx
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview React component for About.jsx
+ *
+ * @module pages.HomePage.About
+ */
+
+import { Link } from "react-router-dom";
+import GroupImage from "./assets/group.png";
+import SectionTitle from "./SectionTitle";
+const About = () => {
+ return (
+
+
+
+
+
+
+
+
+ DECODA é a plataforma do
+ NT-MTST que facilita o processo de educação popular em programação
+
+
+ Voltada tanto para alunos, quanto para professores com interesse em
+ aprender e ensinar programação. Inspirada na pedagogia freiriana,
+ tem como origem ser material de apoio as aulas correntes do Núcleo
+ de Tecnologia do MTST . Desde 2020, o Núcleo de Tecnologia do
+ MTST promove mutirões e formações nas periferias, ensinando
+ programação, inclusão digital e acesso a direitos.
+
+
+
+
+
+ );
+};
+
+export default About;
diff --git a/app/src/pages/HomePage/CtaPlayground.jsx b/app/src/pages/HomePage/CtaPlayground.jsx
new file mode 100644
index 0000000..c3680d0
--- /dev/null
+++ b/app/src/pages/HomePage/CtaPlayground.jsx
@@ -0,0 +1,471 @@
+import React, { useState, useEffect, useRef } from "react";
+import fatorialSvg from "./assets/fatorial.svg";
+
+const CtaPlayground = () => {
+ const [activeTab, setActiveTab] = useState("blocos");
+ const [displayedCode, setDisplayedCode] = useState("");
+ const [isTyping, setIsTyping] = useState(true);
+ const typingTimeoutRef = useRef(null);
+ const terminalRef = useRef(null);
+
+ /**
+ * Gera elementos React para destacar sintaxe de `code` na `language` especificada.
+ * Suporta 'javascript', 'python' e 'console'. Retorna array de nós React.
+ *
+ * @function highlightCode
+ * @param {string} code - Código-fonte a ser destacado
+ * @param {string} language - Linguagem do código ('javascript'|'python'|'console')
+ * @returns {React.ReactNode[]} Elementos React representando o código com destaque
+ */
+ const highlightCode = (code, language) => {
+ if (language === "console") {
+ // Highlight para console
+ return code.split("\n").map((line, i) => {
+ if (line.startsWith(">")) {
+ return (
+
+ >
+ {line.substring(1)}
+
+ );
+ }
+ return (
+
+ {line}
+
+ );
+ });
+ }
+
+ // Palavras-chave por linguagem
+ const keywords = {
+ javascript: [
+ "var",
+ "for",
+ "count",
+ "if",
+ "else",
+ "function",
+ "return",
+ "const",
+ "let",
+ "typeof",
+ "window",
+ "alert",
+ ],
+ python: [
+ "from",
+ "import",
+ "for",
+ "in",
+ "range",
+ "int",
+ "if",
+ "else",
+ "def",
+ "return",
+ "print",
+ "isinstance",
+ "str",
+ ],
+ };
+
+ const types = {
+ javascript: ["Number"],
+ python: ["Number", "numbers"],
+ };
+
+ const currentKeywords = keywords[language] || [];
+ const currentTypes = types[language] || [];
+
+ // Tokenizar o código
+ const tokens = [];
+ let currentToken = "";
+ let inString = false;
+ let stringChar = "";
+ let _inComment = false;
+
+ for (let i = 0; i < code.length; i++) {
+ const char = code[i];
+ const nextChar = code[i + 1];
+
+ // Detectar início/fim de string
+ if ((char === '"' || char === "'") && code[i - 1] !== "\\") {
+ if (!inString) {
+ if (currentToken) {
+ tokens.push({ type: "text", value: currentToken });
+ currentToken = "";
+ }
+ inString = true;
+ stringChar = char;
+ currentToken = char;
+ } else if (char === stringChar) {
+ currentToken += char;
+ tokens.push({ type: "string", value: currentToken });
+ currentToken = "";
+ inString = false;
+ stringChar = "";
+ } else {
+ currentToken += char;
+ }
+ continue;
+ }
+
+ if (inString) {
+ currentToken += char;
+ continue;
+ }
+
+ // Detectar comentários
+ if (char === "/" && nextChar === "/" && language === "javascript") {
+ if (currentToken) {
+ tokens.push({ type: "text", value: currentToken });
+ currentToken = "";
+ }
+ // Pegar resto da linha
+ const endOfLine = code.indexOf("\n", i);
+ const comment = code.substring(
+ i,
+ endOfLine !== -1 ? endOfLine : code.length,
+ );
+ tokens.push({ type: "comment", value: comment });
+ i = endOfLine !== -1 ? endOfLine - 1 : code.length;
+ continue;
+ }
+
+ if (char === "#" && language === "python") {
+ if (currentToken) {
+ tokens.push({ type: "text", value: currentToken });
+ currentToken = "";
+ }
+ const endOfLine = code.indexOf("\n", i);
+ const comment = code.substring(
+ i,
+ endOfLine !== -1 ? endOfLine : code.length,
+ );
+ tokens.push({ type: "comment", value: comment });
+ i = endOfLine !== -1 ? endOfLine - 1 : code.length;
+ continue;
+ }
+
+ // Separadores
+ if (/\s|[(){}.,;]/.test(char) || char === "[" || char === "]") {
+ if (currentToken) {
+ tokens.push({ type: "text", value: currentToken });
+ currentToken = "";
+ }
+ tokens.push({ type: "separator", value: char });
+ continue;
+ }
+
+ // Operadores
+ if (/[+\-*/<>=!:]/.test(char)) {
+ if (currentToken) {
+ tokens.push({ type: "text", value: currentToken });
+ currentToken = "";
+ }
+ tokens.push({ type: "operator", value: char });
+ continue;
+ }
+
+ currentToken += char;
+ }
+
+ if (currentToken) {
+ if (inString) {
+ tokens.push({ type: "string", value: currentToken });
+ } else {
+ tokens.push({ type: "text", value: currentToken });
+ }
+ }
+
+ // Renderizar tokens com cores
+ return tokens.map((token, i) => {
+ if (token.type === "string") {
+ return (
+
+ {token.value}
+
+ );
+ }
+ if (token.type === "comment") {
+ return (
+
+ {token.value}
+
+ );
+ }
+ if (token.type === "operator") {
+ return (
+
+ {token.value}
+
+ );
+ }
+ if (token.type === "separator") {
+ return (
+
+ {token.value}
+
+ );
+ }
+ if (token.type === "text") {
+ // Verificar se é palavra-chave
+ if (currentKeywords.includes(token.value)) {
+ return (
+
+ {token.value}
+
+ );
+ }
+ // Verificar se é tipo
+ if (currentTypes.includes(token.value)) {
+ return (
+
+ {token.value}
+
+ );
+ }
+ // Verificar se é número
+ if (/^\d+$/.test(token.value)) {
+ return (
+
+ {token.value}
+
+ );
+ }
+ // Verificar se é booleano ou null
+ if (
+ [
+ "True",
+ "False",
+ "None",
+ "null",
+ "undefined",
+ "true",
+ "false",
+ ].includes(token.value)
+ ) {
+ return (
+
+ {token.value}
+
+ );
+ }
+ return (
+
+ {token.value}
+
+ );
+ }
+ return {token.value} ;
+ });
+ };
+
+ // Código do exemplo de fatorial
+ const codes = {
+ javascript: `var numero, fatorial, i;
+
+numero = 5;
+fatorial = 1;
+i = 1;
+for (var count = 0; count < numero; count++) {
+ fatorial = fatorial * i;
+ i = (typeof i === 'number' ? i : 0) + 1;
+}
+window.alert([numero,'! = ',fatorial].join(''));`,
+
+ python: `from numbers import Number
+
+numero = None
+fatorial = None
+i = None
+
+numero = 5
+fatorial = 1
+i = 1
+for count in range(int(numero)):
+ fatorial = fatorial * i
+ i = (i if isinstance(i, Number) else 0) + 1
+print(''.join([str(x) for x in [numero, '! = ', fatorial]]))`,
+
+ console: `> Executando programa...
+>
+> Calculando 5!
+> 5! = 120
+>
+> Programa finalizado com sucesso.`,
+ };
+
+ // Efeito de digitação (typing effect) - apenas para abas de código
+ useEffect(() => {
+ if (activeTab === "blocos") {
+ setIsTyping(false);
+ setDisplayedCode("");
+ return;
+ }
+
+ setDisplayedCode("");
+ setIsTyping(true);
+
+ const currentCode = codes[activeTab];
+ let currentIndex = 0;
+
+ /**
+ * Função interna que simula digitação de `codes[activeTab]`.
+ * Atualiza `displayedCode` caractere a caractere e controla `isTyping`.
+ *
+ * @private
+ * @returns {void}
+ */
+ const typeCharacter = () => {
+ if (currentIndex < currentCode.length) {
+ setDisplayedCode(currentCode.substring(0, currentIndex + 1));
+ currentIndex++;
+
+ // Auto-scroll para acompanhar o texto
+ if (terminalRef.current) {
+ terminalRef.current.scrollTop = terminalRef.current.scrollHeight;
+ }
+
+ // Velocidade variável para simular digitação humana
+ const delay = Math.random() * 30 + 10; // 10-40ms entre caracteres
+ typingTimeoutRef.current = setTimeout(typeCharacter, delay);
+ } else {
+ setIsTyping(false);
+ }
+ };
+
+ // Pequeno delay antes de começar a digitar
+ const startDelay = setTimeout(() => {
+ typeCharacter();
+ }, 300);
+
+ // Cleanup
+ return () => {
+ clearTimeout(startDelay);
+ if (typingTimeoutRef.current) {
+ clearTimeout(typingTimeoutRef.current);
+ }
+ };
+ }, [activeTab]);
+
+ return (
+
+ {/* Keyframe animation for cursor blink */}
+
+
+ {/* Background com gradiente igual ao Hero */}
+
+
+ {/* Container principal */}
+
+
+ {/* Terminal de Código (Esquerda) */}
+
+
+
+ {/* Header do Terminal */}
+
+
+
+ {/* Tabs */}
+
+ setActiveTab("blocos")}
+ className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${
+ activeTab === "blocos"
+ ? "bg-blue-600 text-white"
+ : "text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400"
+ }`}
+ >
+ Blocos
+
+ setActiveTab("javascript")}
+ className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${
+ activeTab === "javascript"
+ ? "bg-blue-600 text-white"
+ : "text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400"
+ }`}
+ >
+ JavaScript
+
+ setActiveTab("python")}
+ className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${
+ activeTab === "python"
+ ? "bg-blue-600 text-white"
+ : "text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400"
+ }`}
+ >
+ Python
+
+ setActiveTab("console")}
+ className={`px-4 py-1 rounded-md transition-colors font-medium text-sm ${
+ activeTab === "console"
+ ? "bg-blue-600 text-white"
+ : "text-gray-600 text-gray-600 hover:bg-blue-600/20 hover:text-blue-600 dark:hover:text-blue-400"
+ }`}
+ >
+ Console
+
+
+
+
+ {/* Conteúdo do Terminal */}
+
+ {activeTab === "blocos" ? (
+
+
+
+ ) : (
+
+
+ {highlightCode(displayedCode, activeTab)}
+ {isTyping && (
+
+ )}
+
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default CtaPlayground;
diff --git a/app/src/pages/HomePage/CtaVideoPreview.jsx b/app/src/pages/HomePage/CtaVideoPreview.jsx
new file mode 100644
index 0000000..1aeae08
--- /dev/null
+++ b/app/src/pages/HomePage/CtaVideoPreview.jsx
@@ -0,0 +1,59 @@
+/**
+ * @fileoverview React component for CtaVideoPreview.jsx
+ *
+ * @module pages.HomePage.CtaVideoPreview
+ */
+
+import React from "react";
+import atividadesVideo from "./assets/atividades.mp4";
+
+const CtaVideoPreview = () => {
+ return (
+
+ {/* Background com gradiente igual ao CtaPlayground */}
+
+
+ {/* Container principal */}
+
+
+ {/* Terminal de Vídeo */}
+
+
+
+ {/* Header do Terminal */}
+
+
+ {/* Conteúdo do Vídeo */}
+
+
+
+ Seu navegador não suporta a reprodução de vídeos.
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CtaVideoPreview;
diff --git a/app/src/pages/HomePage/Features.jsx b/app/src/pages/HomePage/Features.jsx
new file mode 100644
index 0000000..7a76b9b
--- /dev/null
+++ b/app/src/pages/HomePage/Features.jsx
@@ -0,0 +1,213 @@
+/**
+ * @fileoverview React component for Features.jsx
+ *
+ * @module pages.HomePage.Features
+ */
+
+import { Check, Sparkles, ArrowRight } from "lucide-react";
+
+export default function Features() {
+ return (
+
+ {/* Background com gradiente escuro */}
+
+
+ {/* Container principal */}
+
+
+ {/* Header da seção */}
+
+
+ Tudo que você precisa para ensinar e aprender programação
+
+
+ Nossa plataforma oferece uma abordagem completa para educadores e
+ alunos, combinando programação visual (usando blocos) e jogos
+ educativos contextualizados.
+
+
+
+ {/* Container das features */}
+
+ {/* SEÇÃO 1: Para Alunos */}
+
+
+
+
+ Para Alunos
+
+
+
+
+
+ Programação visual com blocos intuitivos
+
+
+
+ Jogos educativos contextualizados
+
+
+
+ Feedback imediato e visual
+
+
+
+ Sem erros de sintaxe ou barreiras técnicas
+
+
+
+ Aprendizado por tentativa e experimentação
+
+
+
+ Desenvolvimento da criatividade e autonomia
+
+
+
+ Interface amigável e responsiva
+
+
+
+ Progressão natural de dificuldade
+
+
+
+ Facilidade de uso por meio de Blocos
+
+
+
+
+
+ {/* SEÇÃO 2: Para Educadores */}
+
+
+
+
+ Para Educadores
+
+
+
+
+
+ Guias pedagógicos completos e práticos
+
+
+
+ Estratégias de ensino para sala de aula
+
+
+
+ Atividades de computação desplugada
+
+
+
+ Planos de aula e preparação facilitada
+
+
+
+ Ferramentas de avaliação de aprendizado
+
+
+
+ Gestão e organização da sala de aula
+
+
+
+ Resolução de problemas e desafios comuns
+
+
+
+ Desenvolvimento do pensamento crítico
+
+
+
+ Aprendizagem colaborativa e inclusiva
+
+
+
+
+
+
+ {/* Divider */}
+
+
+ {/* FAQ Call to Action */}
+
+
+
+
+ );
+}
diff --git a/app/src/pages/HomePage/Footer.jsx b/app/src/pages/HomePage/Footer.jsx
new file mode 100644
index 0000000..0bc4d31
--- /dev/null
+++ b/app/src/pages/HomePage/Footer.jsx
@@ -0,0 +1,106 @@
+/**
+ * @fileoverview React component for Footer.jsx
+ *
+ * @module pages.HomePage.Footer
+ */
+
+import { Link } from "react-router-dom";
+import { Github, BookOpen, Mail, Heart } from "lucide-react";
+import logo from "../../assets/logo_decoda.svg";
+
+const Footer = () => {
+ const currentYear = new Date().getFullYear();
+
+ return (
+
+ );
+};
+
+export default Footer;
diff --git a/app/src/pages/HomePage/ForWhom.jsx b/app/src/pages/HomePage/ForWhom.jsx
new file mode 100644
index 0000000..75618a8
--- /dev/null
+++ b/app/src/pages/HomePage/ForWhom.jsx
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview React component for ForWhom.jsx
+ *
+ * @module pages.HomePage.ForWhom
+ */
+
+import FirstPersonImage from "./assets/first-person.png";
+import SecondPersonImage from "./assets/second-person.png";
+import ThirdPersonImage from "./assets/third-person.png";
+import SectionTitle from "./SectionTitle";
+
+const ForWhom = () => {
+ return (
+
+
+
+
+
+
+ Estudantes iniciantes
+
+
+ Aprender a lógica da programação da base com programação com códigos
+
+
+
+
+
+ Estudantes avançados
+
+
+ Conseguem prototipar seus primeiros apps já com código.
+
+
+
+
+
Educadores
+
+ Materiais para desenvolver suas aulas de programação
+
+
+
+
+ );
+};
+
+export default ForWhom;
diff --git a/app/src/pages/HomePage/Hero.jsx b/app/src/pages/HomePage/Hero.jsx
new file mode 100644
index 0000000..8019bde
--- /dev/null
+++ b/app/src/pages/HomePage/Hero.jsx
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview React component for Hero.jsx
+ *
+ * @module pages.HomePage.Hero
+ */
+
+import { Link } from "react-router-dom";
+import { ArrowRight, Gamepad2, Trophy } from "lucide-react";
+import heroImage from "./assets/puzzled-image.png";
+
+const Hero = () => {
+ const scrollToFeatures = (e) => {
+ e.preventDefault();
+ const featuresSection = document.getElementById("features-section");
+ if (featuresSection) {
+ featuresSection.scrollIntoView({ behavior: "smooth" });
+ }
+ };
+
+ return (
+
+ {/* Background Gradient */}
+
+ {/* Container Principal */}
+
+ {/* Coluna Esquerda - Texto */}
+
+ {/* Título Principal */}
+
+ Programe com desafios
+ em contexto
+
+
+ {/* Subtítulo */}
+
+ Descubra o mundo da programação através de atividades interativas.
+
+ Desenvolva habilidades lógicas enquanto se diverte!
+
+
+ {/* Botões de Ação */}
+
+
+
+ Comece as atividades
+
+
+
+
+
+ {/* Coluna Direita - Imagem Hero (oculta no mobile) */}
+
+
+
+
+
+
+ Comece as atividades
+
+
+
+
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/app/src/pages/HomePage/HomePage.jsx b/app/src/pages/HomePage/HomePage.jsx
new file mode 100644
index 0000000..1127c38
--- /dev/null
+++ b/app/src/pages/HomePage/HomePage.jsx
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview React component for HomePage.jsx
+ *
+ * @module pages.HomePage.HomePage
+ */
+
+import Navbar from "../../components/Navbar";
+import PropTypes from "prop-types";
+import Hero from "./Hero";
+import Features from "./Features";
+import CtaPlayground from "./CtaPlayground";
+import Footer from "./Footer";
+import About from "./About";
+import ForWhom from "./ForWhom";
+import StudentsMaterials from "./StudentsMaterials";
+import TeachersMaterials from "./TeachersMaterials";
+
+const HomePage = () => {
+ return (
+
+ {/* Navegação */}
+
+
+ {/* Hero Section */}
+
+
+ {/* Quem Somos */}
+
+
+ {/* Para quem */}
+
+
+ {/* Materiais para estudantes */}
+
+
+ {/* Materiais para educadores */}
+
+
+
+
+ );
+};
+
+export default HomePage;
+
+HomePage.propTypes = {};
diff --git a/app/src/pages/HomePage/SectionTitle.jsx b/app/src/pages/HomePage/SectionTitle.jsx
new file mode 100644
index 0000000..052466f
--- /dev/null
+++ b/app/src/pages/HomePage/SectionTitle.jsx
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview React component for SectionTitle.jsx
+ *
+ * @module pages.HomePage.SectionTitle
+ */
+
+const SectionTitle = ({
+ title,
+ squaresColor = "brand-400",
+ titleColor = "black",
+ textShadowColor = "white",
+}) => {
+ const bgColorClass = `bg-${squaresColor}`;
+ const solidColorClass = `text-${titleColor}`;
+
+ const strokeClass =
+ textShadowColor === "white"
+ ? "[-webkit-text-stroke:2px_white]"
+ : "[-webkit-text-stroke:2px_black]";
+
+ return (
+
+
+
+ {title}
+
+
+ {title}
+
+
+ );
+};
+
+export default SectionTitle;
diff --git a/app/src/pages/HomePage/StudentsMaterials.jsx b/app/src/pages/HomePage/StudentsMaterials.jsx
new file mode 100644
index 0000000..234d4ff
--- /dev/null
+++ b/app/src/pages/HomePage/StudentsMaterials.jsx
@@ -0,0 +1,77 @@
+/**
+ * @fileoverview React component for StudentsMaterials.jsx
+ *
+ * @module pages.HomePage.StudentsMaterials
+ */
+
+import { ArrowRight } from "lucide-react";
+import { Link } from "react-router-dom";
+import CtaPlayground from "./CtaPlayground";
+import CtaVideoPreview from "./CtaVideoPreview";
+import SectionTitle from "./SectionTitle";
+
+const StudentsMaterials = () => {
+ return (
+
+
+
PARA ESTUDANTES
+
+
+
+
+ Está iniciando em programação e quer entender melhor?
+
+
+ Acesse nossas atividades e dê os primeiros passos no mundo da
+ programação! Começe a programar de forma divertida e interativa com
+ nossos materiais feitos especialmente para iniciantes.
+
+
+
+
+ Acesse nossas Atividades
+
+
+
+
+
+
+
+
+
+ Acesse nossas Atividades
+
+
+
+
+
+
+
+
+
+
+
+ Já tem a base de programação e busca algo mais avançado?
+
+
+ Acesse nosso Laboratório e comece a criar seus próprios programas
+ usando blocos visuais. Converta para JavaScript ou Python e aprenda
+ programação de verdade enquanto se diverte.
+
+
+
+
+
+
+
+ Acesse nosso Laboratório
+
+
+
+
+
+
+ );
+};
+
+export default StudentsMaterials;
diff --git a/app/src/pages/HomePage/TeachersMaterials.jsx b/app/src/pages/HomePage/TeachersMaterials.jsx
new file mode 100644
index 0000000..4c32c02
--- /dev/null
+++ b/app/src/pages/HomePage/TeachersMaterials.jsx
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview React component for TeachersMaterials.jsx
+ *
+ * @module pages.HomePage.TeachersMaterials
+ */
+
+import { ArrowRight } from "lucide-react";
+import { Link } from "react-router-dom";
+import CtaPlayground from "./CtaPlayground";
+import SectionTitle from "./SectionTitle";
+
+const TeachersMaterials = () => {
+ return (
+
+
+
+ PARA EDUCADORES
+
+
+
+
+
+ Quer entender como você pode usar nossa plataforma para ensinar?
+
+
+ Acesse nosso material de documentação e comece a usar o Decoda para
+ potencializar suas aulas e engajar seus alunos com atividades
+ interativas.
+
+
+
+
+
+
+
+ Acesse o material para Educadores
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default TeachersMaterials;
diff --git a/app/src/pages/HomePage/assets/atividades.mp4 b/app/src/pages/HomePage/assets/atividades.mp4
new file mode 100644
index 0000000..8881469
Binary files /dev/null and b/app/src/pages/HomePage/assets/atividades.mp4 differ
diff --git a/app/src/pages/HomePage/assets/fatorial.svg b/app/src/pages/HomePage/assets/fatorial.svg
new file mode 100644
index 0000000..d933f23
--- /dev/null
+++ b/app/src/pages/HomePage/assets/fatorial.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/src/pages/HomePage/assets/first-person.png b/app/src/pages/HomePage/assets/first-person.png
new file mode 100644
index 0000000..1eb93bf
Binary files /dev/null and b/app/src/pages/HomePage/assets/first-person.png differ
diff --git a/app/src/pages/HomePage/assets/group.png b/app/src/pages/HomePage/assets/group.png
new file mode 100644
index 0000000..5987b99
Binary files /dev/null and b/app/src/pages/HomePage/assets/group.png differ
diff --git a/app/src/pages/HomePage/assets/hero.png b/app/src/pages/HomePage/assets/hero.png
new file mode 100644
index 0000000..a3825df
Binary files /dev/null and b/app/src/pages/HomePage/assets/hero.png differ
diff --git a/app/src/pages/HomePage/assets/puzzled-image.png b/app/src/pages/HomePage/assets/puzzled-image.png
new file mode 100644
index 0000000..3936c30
Binary files /dev/null and b/app/src/pages/HomePage/assets/puzzled-image.png differ
diff --git a/app/src/pages/HomePage/assets/second-person.png b/app/src/pages/HomePage/assets/second-person.png
new file mode 100644
index 0000000..407a1ff
Binary files /dev/null and b/app/src/pages/HomePage/assets/second-person.png differ
diff --git a/app/src/pages/HomePage/assets/third-person.png b/app/src/pages/HomePage/assets/third-person.png
new file mode 100644
index 0000000..32fde8c
Binary files /dev/null and b/app/src/pages/HomePage/assets/third-person.png differ
diff --git a/app/src/pages/Iniciativas/Hero.jsx b/app/src/pages/Iniciativas/Hero.jsx
new file mode 100644
index 0000000..aa553b2
--- /dev/null
+++ b/app/src/pages/Iniciativas/Hero.jsx
@@ -0,0 +1,48 @@
+import { Link } from "react-router-dom";
+import heroImage from "./assets/hero-image.png";
+import bannerMobile from "../../assets/iniciativas-banner-mobile.png";
+
+const Hero = () => {
+ return (
+
+ {/* background removed per request - no colored gradient */}
+ {/* empty for now */}
+ {/* Container Principal */}
+
+ {/* Coluna Esquerda - Imagem Hero (oculta no mobile) */}
+
+
+
+
+
+
+ {/* Coluna Direita - Texto */}
+
+ {/* Título Principal */}
+
+ Iniciativas
+ em educação
+ do NT MTST
+
+
+ {/* Subtítulo */}
+
+ Conheça nossas iniciativas que colocam
+ o NT MTST como pioneiro em educação
+ popular tecnológica no Brasil
+
+
+
+
+ )
+};
+
+export default Hero;
\ No newline at end of file
diff --git a/app/src/pages/Iniciativas/IniciativaDetalhe.jsx b/app/src/pages/Iniciativas/IniciativaDetalhe.jsx
new file mode 100644
index 0000000..18f4b42
--- /dev/null
+++ b/app/src/pages/Iniciativas/IniciativaDetalhe.jsx
@@ -0,0 +1,137 @@
+import React, { useEffect } from 'react';
+import { useParams, Link } from 'react-router-dom';
+import Navbar from "../../components/Navbar";
+import Footer from '../HomePage/Footer'; // Verifique se o caminho do import está correto
+import { iniciativas } from './iniciativasData';
+
+const IniciativaDetalhe = () => {
+ const { id } = useParams();
+
+ // Scroll para o topo ao carregar a página
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, [id]);
+
+ const iniciativa = iniciativas.find((item) => item.id === id);
+
+ // Tratamento caso a iniciativa não exista
+ if (!iniciativa) {
+ return (
+
+
Iniciativa não encontrada.
+
+ Voltar para Iniciativas
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {/* Header / Hero da Iniciativa com o Cover */}
+ {/* pt-[220px] compensa a altura do Navbar (181px) + 40px de respiro */}
+
+
+
+ {iniciativa.titulo}
+
+
+ {iniciativa.subtitulo}
+
+
+ {iniciativa.descricaoResumida}
+
+
+
+ {/* Imagem Cover no Hero */}
+
+
+
+
+
+ {/* Conteúdo Dinâmico Intercalado */}
+
+
+ {/* Bloco 1 */}
+
+
+
+
+
+
+
+
+
+ {iniciativa.texto1}
+
+
+
+
+ {/* Bloco 2 */}
+
+
+
+
+
+
+
+
+
+ {iniciativa.texto2}
+
+
+
+
+ {/* Bloco 3 */}
+
+
+
+
+
+
+
+
+
+ {iniciativa.texto3}
+
+
+
+
+
+
+ {/* Botão de Voltar para Iniciativas */}
+
+
+ Voltar para Iniciativas
+
+
+
+
+
+
+ );
+};
+
+export default IniciativaDetalhe;
\ No newline at end of file
diff --git a/app/src/pages/Iniciativas/Iniciativas.jsx b/app/src/pages/Iniciativas/Iniciativas.jsx
new file mode 100644
index 0000000..3a327f1
--- /dev/null
+++ b/app/src/pages/Iniciativas/Iniciativas.jsx
@@ -0,0 +1,37 @@
+import React, { useEffect } from 'react';
+import Navbar from "../../components/Navbar";
+import PropTypes from "prop-types";
+import Hero from "./Hero";
+import IniciativasDestaque from "./IniciativasDestaque";
+import IniciativasList from "./IniciativasList";
+import { iniciativas } from './iniciativasData';
+import Footer from "../HomePage/Footer";
+
+const IniciativasPage = () => {
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+
+ return (
+
+ {/* Navegação */}
+
+
+ {/* Hero Section */}
+
+
+ {/* Destaque */}
+
+
+ {/* Lista de Iniciativas */}
+
+
+
+
+ );
+};
+
+export default IniciativasPage;
+
+IniciativasPage.propTypes = {};
\ No newline at end of file
diff --git a/app/src/pages/Iniciativas/IniciativasDestaque.jsx b/app/src/pages/Iniciativas/IniciativasDestaque.jsx
new file mode 100644
index 0000000..f2aa426
--- /dev/null
+++ b/app/src/pages/Iniciativas/IniciativasDestaque.jsx
@@ -0,0 +1,109 @@
+// import React from 'react';
+
+// const IniciativasDestaque = () => {
+// const cards = [
+// {
+// titulo: "Aulas nas Escolas",
+// imagem: "/src/pages/Iniciativas/assets/destaque1.png",
+// },
+// {
+// titulo: "Formações de Educadores",
+// imagem: "/src/pages/Iniciativas/assets/destaque2.png",
+// },
+// {
+// titulo: "Aulas no OcupaLab",
+// imagem: "/src/pages/Iniciativas/assets/destaque3.png",
+// }
+// ];
+
+// return (
+//
+
+// {/* Título da Seção */}
+//
+// Principais Iniciativas
+//
+
+// {/* Container dos Cards */}
+//
+// {cards.map((card, index) => (
+// // Cada Card (Frame 42 / 46 do Figma)
+//
+
+// {/* Caixa da Imagem (Rectangle 204/205) */}
+//
+// {/* Opcional: Se a imagem no Figma tiver aquele filtro vermelho por cima */}
+//
+//
+
+// {/* Título do Card */}
+//
+// {card.titulo}
+//
+
+// {/* Botão (Frame 47) */}
+//
+// Saiba mais
+//
+
+//
+// ))}
+//
+//
+// );
+// };
+
+// export default IniciativasDestaque;
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+const IniciativasDestaque = ({ iniciativas }) => {
+ // Pega apenas as 3 primeiras iniciativas do JSON para o destaque
+ const destaques = iniciativas.slice(0, 3);
+
+ return (
+
+
+ {/* Título da Seção */}
+
+ Principais Iniciativas
+
+
+ {/* Container dos Cards */}
+
+ {destaques.map((iniciativa) => (
+
+
+ {/* Caixa da Imagem */}
+
+ {/* Overlay vermelho para dar um efeito visual por cima da imagem */}
+
+
+
+ {/* Título do Card */}
+
+ {iniciativa.titulo}
+
+
+ {/* Link apontando para a página de Detalhes da Iniciativa específica */}
+
+ Saiba mais
+
+
+
+ ))}
+
+
+ );
+};
+
+export default IniciativasDestaque;
\ No newline at end of file
diff --git a/app/src/pages/Iniciativas/IniciativasList.jsx b/app/src/pages/Iniciativas/IniciativasList.jsx
new file mode 100644
index 0000000..38ffe09
--- /dev/null
+++ b/app/src/pages/Iniciativas/IniciativasList.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+const IniciativaRow = ({ iniciativa, reverse }) => {
+ return (
+
+
+
+
+ {iniciativa.titulo}
+
+
+
+
+
+ {iniciativa.descricaoResumida}
+
+
+ {/* Link para a página dedicada */}
+
+ Conheça a iniciativa
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const IniciativasList = ({ iniciativas }) => {
+ return (
+
+ {iniciativas.map((iniciativa, index) => (
+
+ ))}
+
+ );
+};
+
+export default IniciativasList;
\ No newline at end of file
diff --git a/app/src/pages/Iniciativas/assets/destaque1.png b/app/src/pages/Iniciativas/assets/destaque1.png
new file mode 100644
index 0000000..89ccc12
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/destaque1.png differ
diff --git a/app/src/pages/Iniciativas/assets/destaque2.png b/app/src/pages/Iniciativas/assets/destaque2.png
new file mode 100644
index 0000000..58eed5a
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/destaque2.png differ
diff --git a/app/src/pages/Iniciativas/assets/destaque3.png b/app/src/pages/Iniciativas/assets/destaque3.png
new file mode 100644
index 0000000..783b6be
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/destaque3.png differ
diff --git a/app/src/pages/Iniciativas/assets/escolas.png b/app/src/pages/Iniciativas/assets/escolas.png
new file mode 100644
index 0000000..44d2c12
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/escolas.png differ
diff --git a/app/src/pages/Iniciativas/assets/escolas1.jpg b/app/src/pages/Iniciativas/assets/escolas1.jpg
new file mode 100644
index 0000000..ffe40c9
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/escolas1.jpg differ
diff --git a/app/src/pages/Iniciativas/assets/escolas2.png b/app/src/pages/Iniciativas/assets/escolas2.png
new file mode 100644
index 0000000..3193848
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/escolas2.png differ
diff --git a/app/src/pages/Iniciativas/assets/escolas3.jpg b/app/src/pages/Iniciativas/assets/escolas3.jpg
new file mode 100644
index 0000000..f516826
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/escolas3.jpg differ
diff --git a/app/src/pages/Iniciativas/assets/formacao.png b/app/src/pages/Iniciativas/assets/formacao.png
new file mode 100644
index 0000000..ac9d535
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/formacao.png differ
diff --git a/app/src/pages/Iniciativas/assets/formacao1.jpg b/app/src/pages/Iniciativas/assets/formacao1.jpg
new file mode 100644
index 0000000..42eaba0
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/formacao1.jpg differ
diff --git a/app/src/pages/Iniciativas/assets/formacao2.jpg b/app/src/pages/Iniciativas/assets/formacao2.jpg
new file mode 100644
index 0000000..6ae1fa0
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/formacao2.jpg differ
diff --git a/app/src/pages/Iniciativas/assets/formacao3.jpg b/app/src/pages/Iniciativas/assets/formacao3.jpg
new file mode 100644
index 0000000..2674d12
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/formacao3.jpg differ
diff --git a/app/src/pages/Iniciativas/assets/hero-image.png b/app/src/pages/Iniciativas/assets/hero-image.png
new file mode 100644
index 0000000..c34fb6b
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/hero-image.png differ
diff --git a/app/src/pages/Iniciativas/assets/ocupaLab.svg b/app/src/pages/Iniciativas/assets/ocupaLab.svg
new file mode 100644
index 0000000..e01a4f0
--- /dev/null
+++ b/app/src/pages/Iniciativas/assets/ocupaLab.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/pages/Iniciativas/assets/ocupalab1.jpeg b/app/src/pages/Iniciativas/assets/ocupalab1.jpeg
new file mode 100644
index 0000000..ca991fc
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/ocupalab1.jpeg differ
diff --git a/app/src/pages/Iniciativas/assets/ocupalab2.jpeg b/app/src/pages/Iniciativas/assets/ocupalab2.jpeg
new file mode 100644
index 0000000..cfb580e
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/ocupalab2.jpeg differ
diff --git a/app/src/pages/Iniciativas/assets/ocupalab3.jpeg b/app/src/pages/Iniciativas/assets/ocupalab3.jpeg
new file mode 100644
index 0000000..e9bc50e
Binary files /dev/null and b/app/src/pages/Iniciativas/assets/ocupalab3.jpeg differ
diff --git a/app/src/pages/Iniciativas/iniciativasData.js b/app/src/pages/Iniciativas/iniciativasData.js
new file mode 100644
index 0000000..d12f1b4
--- /dev/null
+++ b/app/src/pages/Iniciativas/iniciativasData.js
@@ -0,0 +1,63 @@
+import coverEscolas from "./assets/escolas.png";
+import coverFormacoes from "./assets/formacao.png";
+import coverOcupalab from "./assets/ocupaLab.svg";
+import escolas1 from "./assets/escolas1.jpg";
+import escolas2 from "./assets/escolas2.png";
+import escolas3 from "./assets/escolas3.jpg";
+import formacao1 from "./assets/formacao1.jpg";
+import formacao2 from "./assets/formacao2.jpg";
+import formacao3 from "./assets/formacao3.jpg";
+import ocupalab1 from "./assets/ocupalab1.jpeg";
+import ocupalab2 from "./assets/ocupalab2.jpeg";
+import ocupalab3 from "./assets/ocupalab3.jpeg";
+import destaque1 from "./assets/destaque1.png";
+import destaque2 from "./assets/destaque2.png";
+import destaque3 from "./assets/destaque3.png";
+
+export const iniciativas = [
+ {
+ id: "escolas",
+ titulo: "Aulas nas Escolas",
+ subtitulo: "Levando tecnologia para as salas de aula",
+ descricaoResumida: "Uma iniciativa focada em inserir a educação popular tecnológica diretamente no ambiente escolar.",
+ cover: coverEscolas,
+ destaque: destaque1,
+ texto1: "Na nossa trajetória, reconhecemos em Paulo Freire muito mais que uma inspiração, encontramos a metodologia que dá vida para além do código. Por aqui o ensino é feito de forma popular e acessível. No Núcleo de Tecnologia do MTST a lógica de programação não começa com um Hello World abstrato, mas com o mundo das nossas turmas.",
+ foto1: escolas1,
+ texto2: "Partimos da realidade do educando. Antes de variáveis e loops, conversamos sobre aplicativos de delivery, redes sociais, automação no trabalho. Identificamos, juntos, como a tecnologia já está (ou não) em nossas vidas. A partir daí, codificamos e decodificamos problemas reais, Um algoritimo pode organizar uma fila de merenda? Pode ajudar na divulgação do sarau da comunidade? Criamos projetos que tenham significado, onde o código vira uma ferramenta de intervenção no mundo.",
+ foto2: escolas2,
+ texto3: "O diálogo é a melhor sintaxe. Nossa sala de aula é um espaço aberto, onde quem ensina também aprende, e quem aprende também ensina. A tecnologia é o meio, mas a transformação é o fim. E é nessa troca constante que construímos uma educação popular tecnológica que é, acima de tudo, humana.",
+ foto3: escolas3,
+ link: "/iniciativas/escolas"
+ },
+ {
+ id: "formacoes",
+ titulo: "Formações de Educadores",
+ subtitulo: "Capacitando quem transforma",
+ descricaoResumida: "Treinamentos intensivos para formar educadores populares de tecnologia.",
+ cover: coverFormacoes,
+ destaque: destaque2,
+ texto1: "O grande desafio que enfrentamos hoje é levar o ensino de tecnologia para os territórios de forma que ele realmente faça sentido para quem vive lá. Acreditamos que um curso não pode ser um pacote fechado, imposto de fora para dentro, mas deve nascer e ser fruto direto da 'leitura do mundo' de cada realidade. É preciso compreender as vivências, as lutas e as necessidades locais para que o aprendizado técnico dialogue com a realidade e seja uma resposta verdadeira aos desafios do dia a dia, não apenas um conhecimento distante.",
+ foto1: formacao1,
+ texto2: "Para transformar essa visão em prática, nós, do Núcleo de Tecnologia do MTST, nos baseamos nos princípios de Paulo Freire para formar nossos educadores populares em tecnologia. Nosso objetivo vai muito além do mero repasse de conceitos ou de linhas de código; trata-se de criar um espaço onde o saber é construído em conjunto com a comunidade. A tecnologia passa a ser uma ferramenta de emancipação, garantindo que o universo digital seja utilizado para entender, questionar e transformar a sociedade de forma crítica.",
+ foto2: formacao2,
+ texto3: "Para construir essa ponte de saberes, desenhamos a formação para abraçar dois públicos que se complementam perfeitamente: professores que ainda não conhecem programação e programadores que não têm experiência com pedagogia ou ensino. Ao promover essa troca entre quem sabe ensinar e quem domina a técnica, formamos educadores completos, capazes de traduzir a lógica da computação em uma linguagem acolhedora, humana e conectada com a realidade popular. Acreditamos que, ao capacitar esses educadores, estamos plantando sementes para uma educação tecnológica que é verdadeiramente transformadora e inclusiva.",
+ foto3: formacao3,
+ link: "/iniciativas/formacoes"
+ },
+ {
+ id: "ocupalab",
+ titulo: "Aulas no OcupaLab",
+ subtitulo: "Nosso laboratório de inovação",
+ descricaoResumida: "Espaço dedicado à experimentação e inovação tecnológica.",
+ cover: coverOcupalab,
+ destaque: destaque3,
+ texto1: "Desde 2020, o Núcleo de Tecnologia do MTST atua nas periferias construindo o acesso aos direitos digitais de forma coletiva. Dessa caminhada contínua, nasceu o OcupaLab, um laboratório de inovação popular erguido dentro de uma ocupação. Diferente de iniciativas que vêm de cima para baixo, do Estado ou do mercado corporativo, este é um território onde a comunidade toma as rédeas do próprio aprendizado. É um ambiente feito para que as pessoas possam estudar, criar e programar, sempre guiadas pela autonomia e por uma forte consciência de classe.",
+ foto1: ocupalab1,
+ texto2: "A própria construção desse espaço reflete a nossa crença de que a emancipação tecnológica exige mobilização. Foram três meses de mutirões intensos, nos quais o trabalho lado a lado com os moradores ergueu o alicerce da nossa soberania digital. A força, a fé e o esforço de cada pessoa envolvida não apenas levantaram paredes e passaram cabos de rede, mas geraram uma potência coletiva transformadora, muito mais vital do que a própria eletricidade que hoje alimenta os nossos equipamentos.",
+ foto2: ocupalab2,
+ texto3: "Na prática, o laboratório de portas abertas se consolida como uma ferramenta essencial para intervir na realidade local. Muito além de disponibilizar máquinas para acessar serviços públicos e promover a informática básica, o espaço é um celeiro de invenções focado em resolver problemas concretos do cotidiano. Por meio de oficinas de eletrônica, robótica, uso de impressoras 3D e até inteligência artificial, garantimos que as ferramentas mais avançadas sejam dominadas pelo povo e aplicadas a favor das necessidades da nossa comunidade.",
+ foto3: ocupalab3,
+ link: "/iniciativas/ocupalab"
+ }
+];
\ No newline at end of file
diff --git a/app/src/pages/LabPython/LabPython.jsx b/app/src/pages/LabPython/LabPython.jsx
new file mode 100644
index 0000000..5e167e9
--- /dev/null
+++ b/app/src/pages/LabPython/LabPython.jsx
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview React component for LabPython.jsx
+ *
+ * @module pages.LabPython.LabPython
+ */
+
+import GameNavBar from "../../components/game/GameNavBar";
+import { useIsMobile } from "../../hooks/useIsMobile";
+import DesktopOnlyTapume from "../../components/DesktopOnlyTapume";
+
+export default function LabPython() {
+ const isSmallScreen = useIsMobile(1024);
+
+ if (isSmallScreen) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/app/src/pages/Playground/Playground.css b/app/src/pages/Playground/Playground.css
new file mode 100644
index 0000000..b5630b6
--- /dev/null
+++ b/app/src/pages/Playground/Playground.css
@@ -0,0 +1,13 @@
+/**
+ * Estilos principais do Playground - convertido para Tailwind CSS
+ * Mantidos apenas estilos específicos que não podem ser feitos inline
+ */
+
+/* Garantir que PanelGroup ocupe toda altura */
+[data-panel-group-direction="horizontal"] {
+ height: 100%;
+}
+
+[data-panel] {
+ height: 100%;
+}
diff --git a/app/src/pages/Playground/Playground.jsx b/app/src/pages/Playground/Playground.jsx
new file mode 100644
index 0000000..113f81a
--- /dev/null
+++ b/app/src/pages/Playground/Playground.jsx
@@ -0,0 +1,305 @@
+/**
+ * @fileoverview React component for Playground.jsx
+ *
+ * @module pages.Playground.Playground
+ */
+
+import React, { useState, useCallback } from "react";
+import { PanelGroup, Panel } from "react-resizable-panels";
+import { Moon, Sun, HelpCircle } from "lucide-react";
+import * as Blockly from "blockly/core";
+import "blockly/blocks";
+import GameNavBar from "../../components/game/GameNavBar";
+import ResizeHandle from "../../components/game/ResizeHandle";
+import PlaygroundEditor from "./components/PlaygroundEditor";
+import PlaygroundViewer from "./components/PlaygroundViewer";
+import PlaygroundModal from "./components/PlaygroundModal";
+import { useCodeGeneration } from "./hooks/useCodeGeneration";
+import { useCodeExecution } from "./hooks/useCodeExecution";
+import { usePlaygroundTour } from "./hooks/usePlaygroundTour";
+import { useIsMobile } from "../../hooks/useIsMobile";
+import "shepherd.js/dist/css/shepherd.css";
+import "../../styles/shepherd-theme.css";
+import "./Playground.css";
+
+const Playground = () => {
+ const [workspace, setWorkspace] = useState(null);
+ const [workspaceVersion, setWorkspaceVersion] = useState(0);
+ const [theme, setTheme] = useState("light");
+ const [modal, setModal] = useState({
+ isOpen: false,
+ type: "success",
+ title: "",
+ message: "",
+ details: "",
+ });
+ const isMobile = useIsMobile();
+ const generatedCode = useCodeGeneration(workspace, workspaceVersion);
+ const { output, isRunning, runCode, clearOutput } = useCodeExecution();
+ const { startTour } = usePlaygroundTour();
+
+ const blockCount = generatedCode.javascript
+ ? generatedCode.javascript.split("\n").filter((line) => line.trim()).length
+ : 0;
+
+ const handleWorkspaceChange = useCallback((newWorkspace) => {
+ setWorkspace(newWorkspace);
+ setWorkspaceVersion((v) => v + 1);
+ }, []);
+
+ const handleRunCode = useCallback(() => {
+ if (generatedCode.javascript) {
+ runCode(generatedCode.javascript);
+ }
+ }, [generatedCode.javascript, runCode]);
+
+ const toggleTheme = useCallback(() => {
+ setTheme((prev) => (prev === "light" ? "dark" : "light"));
+ }, []);
+
+ const handleHelp = useCallback(() => {
+ startTour();
+ }, [startTour]);
+
+ const closeModal = useCallback(() => {
+ setModal({
+ isOpen: false,
+ type: "success",
+ title: "",
+ message: "",
+ details: "",
+ });
+ }, []);
+
+ const handleDownloadWorkspace = useCallback(() => {
+ if (!workspace) {
+ setModal({
+ isOpen: true,
+ type: "error",
+ title: "Erro ao Salvar",
+ message: "Nenhum workspace disponível para salvar.",
+ details: "",
+ });
+ return;
+ }
+
+ try {
+ const json = Blockly.serialization.workspaces.save(workspace);
+ const dataStr = JSON.stringify(json, null, 2);
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
+ const url = URL.createObjectURL(dataBlob);
+ const link = document.createElement("a");
+ link.href = url;
+ const fileName = `playground-${new Date().toISOString().slice(0, 10)}.json`;
+ link.download = fileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+
+ setModal({
+ isOpen: true,
+ type: "success",
+ title: "Blocos Salvos com Sucesso!",
+ message: "Seu workspace foi exportado com sucesso.",
+ details: `Arquivo: ${fileName}\nLocalização: Pasta de Downloads`,
+ });
+ } catch (error) {
+ console.error("Erro ao baixar workspace:", error);
+ setModal({
+ isOpen: true,
+ type: "error",
+ title: "Erro ao Salvar",
+ message: "Ocorreu um erro ao exportar o workspace.",
+ details: error.message,
+ });
+ }
+ }, [workspace]);
+
+ const handleUploadWorkspace = useCallback(() => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".json";
+
+ input.onchange = (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const json = JSON.parse(event.target.result);
+
+ if (!workspace) {
+ setModal({
+ isOpen: true,
+ type: "error",
+ title: "Erro ao Carregar",
+ message: "O workspace não está pronto para receber dados.",
+ details: "",
+ });
+ return;
+ }
+
+ // Limpar workspace atual
+ workspace.clear();
+
+ // Carregar novo workspace
+ Blockly.serialization.workspaces.load(json, workspace);
+
+ // Atualizar versão para forçar regeneração do código
+ setWorkspaceVersion((v) => v + 1);
+
+ setModal({
+ isOpen: true,
+ type: "success",
+ title: "Blocos Carregados com Sucesso!",
+ message: "O workspace foi carregado e está pronto para uso.",
+ details: `Arquivo: ${file.name}`,
+ });
+ } catch (error) {
+ console.error("Erro ao carregar workspace:", error);
+ setModal({
+ isOpen: true,
+ type: "error",
+ title: "Erro ao Carregar",
+ message:
+ "Não foi possível carregar o arquivo. Verifique se é um arquivo JSON válido.",
+ details: error.message,
+ });
+ }
+ };
+
+ reader.readAsText(file);
+ };
+
+ input.click();
+ }, [workspace]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ Editor de Blocos
+
+ {!isMobile && (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ Código
+
+
+ {theme === "light" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Playground;
diff --git a/app/src/pages/Playground/components/CodeViewer.css b/app/src/pages/Playground/components/CodeViewer.css
new file mode 100644
index 0000000..2f0723f
--- /dev/null
+++ b/app/src/pages/Playground/components/CodeViewer.css
@@ -0,0 +1,118 @@
+/**
+ * Estilos para o CodeViewer com CodeMirror
+ */
+
+.code-viewer-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ border-radius: 8px;
+ overflow: hidden;
+ transition:
+ background-color 0.3s ease,
+ color 0.3s ease;
+ position: relative;
+}
+
+/* Tema Dark (padrão) */
+.code-viewer-theme-dark {
+ background-color: #1e1e1e;
+ color: #d4d4d4;
+}
+
+/* Tema Light */
+.code-viewer-theme-light {
+ background-color: #ffffff;
+ color: #333333;
+}
+
+/* Botão copiar flutuante */
+.code-viewer-copy-btn {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ z-index: 10;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ background: var(--action-green);
+ color: black;
+ border: none;
+ padding: 8px 14px;
+ border-radius: 9999px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.code-viewer-copy-btn:hover {
+ transform: scale(1.05);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+}
+
+.code-viewer-copy-btn.copied {
+ background: linear-gradient(135deg, #22c55e, #16a34a);
+}
+
+.code-viewer-copy-btn svg {
+ width: 16px;
+ height: 16px;
+}
+
+.code-viewer-copy-btn span {
+ font-size: 13px;
+ font-weight: 500;
+}
+
+/* Container do CodeMirror */
+.code-viewer-editor {
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+}
+
+.code-viewer-editor .cm-editor {
+ height: 100% !important;
+ font-family: "Consolas", "Monaco", "Courier New", monospace !important;
+ font-size: 13px !important;
+}
+
+.code-viewer-editor .cm-scroller {
+ overflow: auto !important;
+ font-family: "Consolas", "Monaco", "Courier New", monospace !important;
+}
+
+.code-viewer-editor .cm-content {
+ font-family: "Consolas", "Monaco", "Courier New", monospace !important;
+ padding: 8px 0 !important;
+}
+
+.code-viewer-editor .cm-line {
+ padding: 0 8px !important;
+ font-family: "Consolas", "Monaco", "Courier New", monospace !important;
+}
+
+/* Ocultar scrollbars do CodeMirror - Padrão da aplicação */
+.code-viewer-editor .cm-scroller::-webkit-scrollbar {
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+}
+
+.code-viewer-editor .cm-scroller {
+ scrollbar-width: none !important;
+ -ms-overflow-style: none !important;
+}
+
+/* Cursor não deve aparecer (read-only) */
+.code-viewer-editor .cm-cursor {
+ display: none !important;
+}
+
+/* Ajuste de seleção para read-only */
+.code-viewer-editor .cm-selectionBackground {
+ background-color: rgba(100, 149, 237, 0.3) !important;
+}
diff --git a/app/src/pages/Playground/components/CodeViewer.jsx b/app/src/pages/Playground/components/CodeViewer.jsx
new file mode 100644
index 0000000..56d93e2
--- /dev/null
+++ b/app/src/pages/Playground/components/CodeViewer.jsx
@@ -0,0 +1,79 @@
+/**
+ * @fileoverview React component for CodeViewer.jsx
+ *
+ * @module pages.Playground.components.CodeViewer
+ */
+
+import React, { useState } from "react";
+import { Copy, Check } from "lucide-react";
+import CodeMirror from "@uiw/react-codemirror";
+import { javascript } from "@codemirror/lang-javascript";
+import "./CodeViewer.css";
+
+const CodeViewer = ({
+ code,
+ language = "javascript",
+ title = "Código",
+ theme = "light",
+}) => {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(code);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (error) {
+ console.error("Erro ao copiar:", error);
+ }
+ };
+
+ return (
+
+
+ {copied ? (
+ <>
+
+ Copiado!
+ >
+ ) : (
+ <>
+
+ Copiar
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default CodeViewer;
diff --git a/app/src/pages/Playground/components/ExamplesModal.jsx b/app/src/pages/Playground/components/ExamplesModal.jsx
new file mode 100644
index 0000000..f2d07b3
--- /dev/null
+++ b/app/src/pages/Playground/components/ExamplesModal.jsx
@@ -0,0 +1,168 @@
+/**
+ * @fileoverview React component for ExamplesModal.jsx
+ *
+ * @module pages.Playground.components.ExamplesModal
+ */
+
+import React from "react";
+import {
+ X,
+ BookOpen,
+ Play,
+ Hash,
+ User,
+ RotateCw,
+ Plus,
+ Dices,
+ BarChart3,
+ Thermometer,
+ Calculator,
+} from "lucide-react";
+
+const ExamplesModal = ({ isOpen, onClose, onSelectExample }) => {
+ if (!isOpen) return null;
+
+ const examples = [
+ {
+ id: "contador",
+ name: "Contador de 1 a 10",
+ description: "Um loop que conta de 1 a 10 e imprime cada valor",
+ file: "/examples/contador.json",
+ icon: Hash,
+ },
+ {
+ id: "nome",
+ name: "Imprimir Texto",
+ description: "Um programa simples que imprime um texto",
+ file: "/examples/nome.json",
+ icon: User,
+ },
+ {
+ id: "fibonacci",
+ name: "Fibonacci",
+ description: "Um exemplo de sequência de Fibonacci em blocos",
+ file: "/examples/fibonacci.json",
+ icon: RotateCw,
+ },
+ {
+ id: "soma-lista",
+ name: "Soma de Lista",
+ description: "Percorre uma lista de números e calcula a soma total",
+ file: "/examples/soma-lista.json",
+ icon: Plus,
+ },
+ {
+ id: "par-impar",
+ name: "Par ou Ímpar",
+ description:
+ "Verifica se um número é par ou ímpar usando operador módulo",
+ file: "/examples/par-impar.json",
+ icon: Dices,
+ },
+ {
+ id: "maior-numero",
+ name: "Maior Número",
+ description: "Encontra o maior número em uma lista usando comparação",
+ file: "/examples/maior-numero.json",
+ icon: BarChart3,
+ },
+ {
+ id: "temperatura",
+ name: "Conversor de Temperatura",
+ description: "Converte temperatura de Celsius para Fahrenheit",
+ file: "/examples/temperatura.json",
+ icon: Thermometer,
+ },
+ {
+ id: "fatorial",
+ name: "Cálculo Fatorial",
+ description: "Calcula o fatorial de um número usando loop",
+ file: "/examples/fatorial.json",
+ icon: Calculator,
+ },
+ ];
+
+ const handleExampleClick = async (example) => {
+ try {
+ const response = await fetch(example.file);
+ if (!response.ok) {
+ throw new Error("Erro ao carregar exemplo");
+ }
+ const data = await response.json();
+
+ // Extrair apenas os dados do workspace (blocks e variables)
+ const workspaceData = {
+ blocks: data.blocks,
+ ...(data.variables && { variables: data.variables }),
+ };
+
+ onSelectExample(workspaceData);
+ onClose();
+ } catch (error) {
+ console.error("Erro ao carregar exemplo:", error);
+ alert("Erro ao carregar exemplo. Tente novamente.");
+ }
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+ {/* Content */}
+
+
+ {examples.map((example) => {
+ const IconComponent = example.icon;
+ return (
+
handleExampleClick(example)}
+ className="group relative flex items-start gap-4 p-5 bg-white/80 backdrop-blur-sm border border-white/30 rounded-xl shadow-lg hover:border-green-300 transition-all duration-200 text-left"
+ >
+ {/* Icon */}
+
+
+
+
+ {/* Content */}
+
+
+ {example.name}
+
+
+ {example.description}
+
+
+
+ {/* Arrow Icon */}
+
+
+ );
+ })}
+
+
+
+
+ );
+};
+
+export default ExamplesModal;
diff --git a/app/src/pages/Playground/components/PlaygroundConsole.css b/app/src/pages/Playground/components/PlaygroundConsole.css
new file mode 100644
index 0000000..623b9b4
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundConsole.css
@@ -0,0 +1,276 @@
+/**
+ * Estilos para o PlaygroundConsole
+ */
+
+.playground-console-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ border-radius: 8px;
+ overflow: hidden;
+ transition:
+ background-color 0.3s ease,
+ color 0.3s ease;
+}
+
+/* Tema Dark (padrão) */
+.playground-console-theme-dark {
+ background-color: #1e1e1e;
+ color: #d4d4d4;
+}
+
+.playground-console-theme-dark .playground-console-header {
+ background-color: #252526;
+ border-bottom: 1px solid #333;
+}
+
+.playground-console-theme-dark .playground-console-title {
+ color: #cccccc;
+}
+
+.playground-console-theme-dark .playground-console-content {
+ background-color: #1e1e1e;
+}
+
+.playground-console-theme-dark .playground-console-empty {
+ color: #888;
+}
+
+/* Tema Light */
+.playground-console-theme-light {
+ background-color: #ffffff;
+ color: #333333;
+}
+
+.playground-console-theme-light .playground-console-header {
+ background-color: #f5f5f5;
+ border-bottom: 1px solid #ddd;
+}
+
+.playground-console-theme-light .playground-console-title {
+ color: #333333;
+}
+
+.playground-console-theme-light .playground-console-content {
+ background-color: #ffffff;
+}
+
+.playground-console-theme-light .playground-console-empty {
+ color: #666;
+}
+
+.playground-console-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ transition: background-color 0.3s ease;
+}
+
+.playground-console-title {
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.playground-console-clear-btn {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ background: var(--action-green); /* hero/action button color */
+ color: black;
+ border: none;
+ padding: 6px 12px;
+ border-radius: 9999px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: all 0.2s;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.playground-console-clear-btn:hover:not(:disabled) {
+ transform: scale(1.05);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+}
+
+.playground-console-clear-btn:hover:not(:disabled) {
+ transform: scale(1.05);
+ box-shadow: 0 4px 8px rgba(237, 27, 47, 0.3);
+}
+
+.playground-console-clear-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ box-shadow: none;
+}
+
+.playground-console-clear-btn svg {
+ width: 16px;
+ height: 16px;
+}
+
+.playground-console-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+ font-family: "Consolas", "Monaco", "Courier New", monospace;
+ font-size: 13px;
+ line-height: 1.6;
+ transition: background-color 0.3s ease;
+}
+
+.playground-console-empty {
+ padding: 16px;
+ text-align: center;
+ font-style: italic;
+ transition: color 0.3s ease;
+}
+
+.playground-console-entry {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ padding: 6px 8px;
+ margin-bottom: 2px;
+ border-radius: 4px;
+ transition: background-color 0.1s;
+}
+
+.playground-console-entry:hover {
+ background-color: #2a2a2a;
+}
+
+.playground-console-theme-light .playground-console-entry:hover {
+ background-color: #f0f0f0;
+}
+
+.playground-console-icon {
+ flex-shrink: 0;
+ font-size: 14px;
+}
+
+.playground-console-timestamp {
+ flex-shrink: 0;
+ color: #858585;
+ font-size: 11px;
+ margin-top: 2px;
+}
+
+.playground-console-message {
+ flex: 1;
+ word-break: break-word;
+ white-space: pre-wrap;
+}
+
+/* Tipos de mensagem - Dark theme */
+.playground-console-theme-dark
+ .playground-console-log
+ .playground-console-message {
+ color: #d4d4d4;
+}
+
+.playground-console-theme-dark .playground-console-error {
+ background-color: #3d1f1f;
+ border-left: 3px solid #f48771;
+}
+
+.playground-console-theme-dark
+ .playground-console-error
+ .playground-console-message {
+ color: #f48771;
+}
+
+.playground-console-theme-dark .playground-console-success {
+ background-color: #1f3d1f;
+ border-left: 3px solid #73c991;
+}
+
+.playground-console-theme-dark
+ .playground-console-success
+ .playground-console-message {
+ color: #73c991;
+}
+
+.playground-console-theme-dark .playground-console-alert {
+ background-color: #3d3d1f;
+ border-left: 3px solid #dcdcaa;
+}
+
+.playground-console-theme-dark
+ .playground-console-alert
+ .playground-console-message {
+ color: #dcdcaa;
+}
+
+/* Tipos de mensagem - Light theme */
+.playground-console-theme-light
+ .playground-console-log
+ .playground-console-message {
+ color: #333333;
+}
+
+.playground-console-theme-light .playground-console-error {
+ background-color: #ffe6e6;
+ border-left: 3px solid #d32f2f;
+}
+
+.playground-console-theme-light
+ .playground-console-error
+ .playground-console-message {
+ color: #d32f2f;
+}
+
+.playground-console-theme-light .playground-console-success {
+ background-color: #e6ffe6;
+ border-left: 3px solid #388e3c;
+}
+
+.playground-console-theme-light
+ .playground-console-success
+ .playground-console-message {
+ color: #388e3c;
+}
+
+.playground-console-theme-light .playground-console-alert {
+ background-color: #fff8e6;
+ border-left: 3px solid #f57c00;
+}
+
+.playground-console-theme-light
+ .playground-console-alert
+ .playground-console-message {
+ color: #f57c00;
+}
+
+/* Scrollbar styling - Seguindo padrão do globals.css */
+.playground-console-content::-webkit-scrollbar {
+ width: 8px;
+}
+
+.playground-console-content::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.playground-console-content::-webkit-scrollbar-thumb {
+ background: linear-gradient(
+ 45deg,
+ var(--brand-primary) 0%,
+ var(--brand-secondary) 50%,
+ var(--brand-accent) 100%
+ );
+ border-radius: 4px;
+}
+
+.playground-console-content::-webkit-scrollbar-thumb:hover {
+ background: linear-gradient(
+ 45deg,
+ var(--brand-dark) 0%,
+ var(--brand-secondary) 50%,
+ var(--brand-purple-dark) 100%
+ );
+}
+
+.playground-console-content {
+ scrollbar-width: thin;
+ scrollbar-color: var(--brand-primary) transparent;
+}
diff --git a/app/src/pages/Playground/components/PlaygroundConsole.jsx b/app/src/pages/Playground/components/PlaygroundConsole.jsx
new file mode 100644
index 0000000..81bf507
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundConsole.jsx
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview React component for PlaygroundConsole.jsx
+ *
+ * @module pages.Playground.components.PlaygroundConsole
+ */
+
+import React, { useEffect, useRef } from "react";
+import { Trash2 } from "lucide-react";
+import "./PlaygroundConsole.css";
+
+const PlaygroundConsole = ({ output, onClear, theme = "light" }) => {
+ const consoleRef = useRef(null);
+
+ useEffect(() => {
+ if (consoleRef.current) {
+ consoleRef.current.scrollTop = consoleRef.current.scrollHeight;
+ }
+ }, [output]);
+
+ const formatTimestamp = (date) => {
+ return date.toLocaleTimeString("pt-BR", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ fractionalSecondDigits: 3,
+ });
+ };
+
+ const getIcon = (type) => {
+ switch (type) {
+ case "error":
+ return "❌";
+ case "success":
+ return "✅";
+ case "alert":
+ return "⚠️";
+ default:
+ return "";
+ }
+ };
+
+ return (
+
+
+
+ Console ({output.length})
+
+
+
+ Limpar
+
+
+
+ {output.length === 0 ? (
+
+ Nenhuma saída ainda. Execute o código para ver os resultados.
+
+ ) : (
+ output.map((entry) => (
+
+
+ {formatTimestamp(entry.timestamp)}
+
+
+ {getIcon(entry.type)}
+
+
+ {entry.message}
+
+
+ ))
+ )}
+
+
+ );
+};
+
+export default PlaygroundConsole;
diff --git a/app/src/pages/Playground/components/PlaygroundEditor.css b/app/src/pages/Playground/components/PlaygroundEditor.css
new file mode 100644
index 0000000..b2bf13e
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundEditor.css
@@ -0,0 +1,43 @@
+/**
+ * Estilos para o editor Blockly do Playground
+ */
+
+.playground-editor-container {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.playground-editor {
+ width: 100%;
+ flex: 1;
+ position: relative;
+}
+
+.playground-editor .blocklyToolboxDiv {
+ background-color: #1e1e1e;
+ border-right: 1px solid #333;
+}
+
+.playground-editor .blocklyFlyout {
+ background-color: #2d2d2d;
+}
+
+.playground-editor .blocklyMainBackground {
+ stroke: #333;
+}
+
+.playground-editor .blocklyScrollbarBackground {
+ fill: #1e1e1e;
+}
+
+.playground-editor .blocklyScrollbarHandle {
+ fill: #666;
+}
+
+.playground-editor .blocklyScrollbarHandle:hover {
+ fill: #888;
+}
diff --git a/app/src/pages/Playground/components/PlaygroundEditor.jsx b/app/src/pages/Playground/components/PlaygroundEditor.jsx
new file mode 100644
index 0000000..c343adf
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundEditor.jsx
@@ -0,0 +1,446 @@
+/**
+ * @fileoverview React component for PlaygroundEditor.jsx
+ *
+ * @module pages.Playground.components.PlaygroundEditor
+ */
+
+import React, {
+ useEffect,
+ useRef,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
+import * as Blockly from "blockly/core";
+import {
+ Download,
+ Upload,
+ Play,
+ Loader,
+ CircleAlert,
+ BookOpen,
+ HelpCircle,
+} from "lucide-react";
+import { playgroundToolbox } from "../config/playgroundToolbox";
+import { defaultWorkspace } from "../config/defaultWorkspace";
+import {
+ loadWorkspace,
+ createDebouncedSave,
+} from "../services/playgroundStorage";
+import { useIsMobile } from "../../../hooks/useIsMobile";
+import ExamplesModal from "./ExamplesModal";
+import "./PlaygroundEditor.css";
+
+// Tema claro personalizado
+const lightTheme = Blockly.Theme.defineTheme("light", {
+ base: Blockly.Themes.Classic,
+ componentStyles: {
+ workspaceBackgroundColour: "#f9fafb",
+ toolboxBackgroundColour: "#e5e7eb",
+ toolboxForegroundColour: "#374151",
+ flyoutBackgroundColour: "#f3f4f6",
+ flyoutForegroundColour: "#111827",
+ flyoutOpacity: 0.95,
+ scrollbarColour: "#9ca3af",
+ scrollbarOpacity: 0.5,
+ },
+});
+
+// Tema escuro personalizado
+const darkTheme = Blockly.Theme.defineTheme("dark", {
+ base: Blockly.Themes.Classic,
+ componentStyles: {
+ workspaceBackgroundColour: "#1e1e1e",
+ toolboxBackgroundColour: "#252526",
+ toolboxForegroundColour: "#cccccc",
+ flyoutBackgroundColour: "#2d2d30",
+ flyoutForegroundColour: "#ffffff",
+ flyoutOpacity: 0.95,
+ scrollbarColour: "#6b7280",
+ scrollbarOpacity: 0.5,
+ },
+});
+
+const PlaygroundEditor = ({
+ onWorkspaceChange,
+ onDownloadWorkspace,
+ onUploadWorkspace,
+ onRunCode,
+ onHelp,
+ isRunning,
+ blockCount = 0,
+ theme = "light",
+}) => {
+ const blocklyDiv = useRef(null);
+ const workspaceRef = useRef(null);
+ const isInitializedRef = useRef(false);
+ const isMobile = useIsMobile();
+ const debouncedSave = useMemo(() => createDebouncedSave(1000), []);
+ const [showExamples, setShowExamples] = useState(false);
+ const [isVisible, setIsVisible] = useState(true);
+
+ // Monitorar visibilidade da página
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ const visible = !document.hidden;
+ setIsVisible(visible);
+
+ if (visible && workspaceRef.current) {
+ // Forçar resize quando a página voltar a ficar visível
+ requestAnimationFrame(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ });
+ }
+ };
+
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+ return () =>
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ }, []);
+
+ // Forçar resize quando isVisible mudar
+ useEffect(() => {
+ if (isVisible && workspaceRef.current) {
+ const timer = setTimeout(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ }, 100);
+ return () => clearTimeout(timer);
+ }
+ }, [isVisible]);
+
+ /**
+ * Debounced save e notificação externa quando a workspace muda.
+ * Persiste a workspace localmente e chama `onWorkspaceChange` se fornecido.
+ *
+ * @function handleWorkspaceChange
+ * @returns {void}
+ */
+ const handleWorkspaceChange = useCallback(() => {
+ if (!workspaceRef.current) return;
+
+ debouncedSave(workspaceRef.current);
+
+ if (onWorkspaceChange) {
+ onWorkspaceChange(workspaceRef.current);
+ }
+ }, [onWorkspaceChange, debouncedSave]);
+
+ useEffect(() => {
+ if (isInitializedRef.current || !blocklyDiv.current) return;
+
+ isInitializedRef.current = true;
+
+ const toolboxWithIcons = {
+ ...playgroundToolbox,
+ contents: playgroundToolbox.contents.map((cat) => ({
+ ...cat,
+ "css-icon": cat.cssIcon || "fa fa-cube",
+ })),
+ };
+
+ workspaceRef.current = Blockly.inject(blocklyDiv.current, {
+ toolbox: toolboxWithIcons,
+ theme: theme === "light" ? lightTheme : darkTheme,
+ grid: {
+ spacing: 25,
+ length: 3,
+ colour: theme === "light" ? "#ccc" : "#3a3a3a",
+ snap: true,
+ },
+ zoom: {
+ controls: false,
+ wheel: true,
+ startScale: 1.0,
+ maxScale: 3,
+ minScale: 0.3,
+ scaleSpeed: 1.2,
+ },
+ trashcan: true,
+ scrollbars: true,
+ renderer: "zelos",
+ });
+
+ const savedData = loadWorkspace();
+ const workspaceToLoad = savedData || defaultWorkspace;
+
+ try {
+ Blockly.serialization.workspaces.load(
+ workspaceToLoad,
+ workspaceRef.current,
+ );
+ } catch (error) {
+ console.error("Erro ao carregar workspace:", error);
+ // Se falhar, tenta carregar o workspace padrão
+ try {
+ Blockly.serialization.workspaces.load(
+ defaultWorkspace,
+ workspaceRef.current,
+ );
+ } catch (fallbackError) {
+ console.error("Erro ao carregar workspace padrão:", fallbackError);
+ }
+ }
+
+ handleWorkspaceChange();
+
+ const listener = (event) => {
+ if (event.isUiEvent) return;
+ handleWorkspaceChange();
+ };
+
+ workspaceRef.current.addChangeListener(listener);
+
+ // Fazer zoom para ajustar todos os blocos na tela (especialmente útil em mobile)
+ setTimeout(() => {
+ if (workspaceRef.current && isMobile) {
+ const metrics = workspaceRef.current.getMetrics();
+ if (metrics) {
+ // Calcular escala para caber todos os blocos
+ workspaceRef.current.zoomToFit();
+ }
+ }
+ }, 200);
+
+ // Adicionar ResizeObserver para garantir que o workspace se redimensione corretamente
+ const resizeObserver = new ResizeObserver(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ });
+
+ if (blocklyDiv.current) {
+ resizeObserver.observe(blocklyDiv.current);
+ }
+
+ // Listener para quando a página voltar a ficar visível (alt+tab, mudança de aba)
+ const handleVisibilityChange = () => {
+ if (!document.hidden && workspaceRef.current) {
+ // Pequeno delay para garantir que o layout está estável
+ setTimeout(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ }, 50);
+ }
+ };
+
+ // Listener para quando a janela receber foco novamente
+ const handleWindowFocus = () => {
+ if (workspaceRef.current) {
+ setTimeout(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ }, 50);
+ }
+ };
+
+ // Listener para resize da janela
+ const handleWindowResize = () => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ };
+
+ document.addEventListener("visibilitychange", handleVisibilityChange);
+ window.addEventListener("focus", handleWindowFocus);
+ window.addEventListener("resize", handleWindowResize);
+
+ // Forçar um resize inicial após um pequeno delay
+ setTimeout(() => {
+ if (workspaceRef.current) {
+ Blockly.svgResize(workspaceRef.current);
+ }
+ }, 100);
+
+ return () => {
+ resizeObserver.disconnect();
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
+ window.removeEventListener("focus", handleWindowFocus);
+ window.removeEventListener("resize", handleWindowResize);
+ if (workspaceRef.current) {
+ workspaceRef.current.dispose();
+ workspaceRef.current = null;
+ }
+ };
+ }, [handleWorkspaceChange]);
+
+ // Efeito para trocar o tema do Blockly dinamicamente
+ useEffect(() => {
+ if (!workspaceRef.current) return;
+
+ const newTheme = theme === "light" ? lightTheme : darkTheme;
+ workspaceRef.current.setTheme(newTheme);
+
+ // Atualizar a cor do grid
+ const gridColour = theme === "light" ? "#ccc" : "#3a3a3a";
+ workspaceRef.current.getTheme().componentStyles.gridColour = gridColour;
+
+ // Forçar atualização visual
+ workspaceRef.current.refreshTheme();
+ }, [theme]);
+
+ const naoTemBlocos = blockCount === 0;
+
+ /**
+ * Carrega um exemplo de workspace no editor.
+ * Substitui o workspace atual pelo exemplo fornecido e ajusta o zoom.
+ *
+ * @function handleLoadExample
+ * @param {Object} exampleData - Dados serializados do exemplo (workspace)
+ * @returns {void}
+ */
+ const handleLoadExample = useCallback(
+ (exampleData) => {
+ if (!workspaceRef.current) return;
+
+ try {
+ // Limpar workspace atual
+ workspaceRef.current.clear();
+
+ // Carregar o exemplo
+ Blockly.serialization.workspaces.load(
+ exampleData,
+ workspaceRef.current,
+ );
+
+ // Fazer zoom para ajustar todos os blocos na tela (especialmente útil em mobile)
+ setTimeout(() => {
+ if (workspaceRef.current && isMobile) {
+ workspaceRef.current.zoomToFit();
+ }
+ }, 150);
+
+ // Notificar mudança
+ handleWorkspaceChange();
+ } catch (error) {
+ console.error("Erro ao carregar exemplo:", error);
+ alert("Erro ao carregar exemplo. Tente novamente.");
+ }
+ },
+ [handleWorkspaceChange, isMobile],
+ );
+
+ /**
+ * Retorna as classes CSS para o botão de executar dependendo do estado.
+ * Usa `isMobile`, `isRunning` e `blockCount` para decidir estilos/estado.
+ *
+ * @function getRunButtonClasses
+ * @returns {string} Classes CSS para aplicação ao botão
+ */
+ const getRunButtonClasses = () => {
+ const baseClasses = `flex items-center gap-2 ${isMobile ? "px-3 py-3" : "px-6 py-3"} rounded-full text-sm font-medium transition-all duration-200 shadow-md hover:shadow-lg`;
+
+ if (naoTemBlocos) {
+ return `${baseClasses} bg-gray-300 text-black cursor-not-allowed`;
+ }
+
+ if (isRunning) {
+ return `${baseClasses} bg-[var(--action-green)] hover:bg-[rgb(235_119_63/1)] text-white`;
+ }
+
+ return `${baseClasses} bg-green-100 hover:bg-green-200 text-black`;
+ };
+
+ return (
+
+
+
+ {isRunning ? (
+ <>
+
+ {!isMobile && Executando... }
+ >
+ ) : naoTemBlocos ? (
+ <>
+
+ {!isMobile && Adicione blocos para executar }
+ >
+ ) : (
+ <>
+
+ {!isMobile && Executar }
+ >
+ )}
+
+
+
+
+ {!isMobile && Salvar Blocos }
+
+
+
+
+ {!isMobile && Carregar Blocos }
+
+
+
setShowExamples(true)}
+ className={`flex items-center gap-2 ${isMobile ? "px-3 py-3" : "px-6 py-3"} bg-green-100 text-black rounded-full text-sm font-medium transition-all duration-200 shadow-md hover:bg-green-200`}
+ data-tour="examples-button"
+ title="Ver exemplos de código"
+ >
+
+ {!isMobile && Exemplos }
+
+
+ {isMobile && onHelp && (
+
+
+
+ )}
+
+
+
+
setShowExamples(false)}
+ onSelectExample={handleLoadExample}
+ />
+
+ );
+};
+
+export default PlaygroundEditor;
diff --git a/app/src/pages/Playground/components/PlaygroundModal.jsx b/app/src/pages/Playground/components/PlaygroundModal.jsx
new file mode 100644
index 0000000..b0d4887
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundModal.jsx
@@ -0,0 +1,108 @@
+/**
+ * @fileoverview React component for PlaygroundModal.jsx
+ *
+ * @module pages.Playground.components.PlaygroundModal
+ */
+
+import React from "react";
+import { X, CheckCircle, AlertCircle } from "lucide-react";
+
+const PlaygroundModal = ({
+ isOpen,
+ onClose,
+ type = "success",
+ title,
+ message,
+ details,
+}) => {
+ if (!isOpen) return null;
+
+ const getIcon = () => {
+ switch (type) {
+ case "success":
+ return ;
+ case "error":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ const getStyles = () => {
+ switch (type) {
+ case "success":
+ return {
+ border: "border-green-500",
+ bg: "bg-green-50",
+ titleColor: "text-green-900",
+ messageColor: "text-green-700",
+ };
+ case "error":
+ return {
+ border: "border-red-500",
+ bg: "bg-red-50",
+ titleColor: "text-red-900",
+ messageColor: "text-red-700",
+ };
+ default:
+ return {
+ border: "border-gray-500",
+ bg: "bg-gray-50",
+ titleColor: "text-gray-900",
+ messageColor: "text-gray-700",
+ };
+ }
+ };
+
+ const styles = getStyles();
+
+ return (
+
+
+ {/* Header com gradiente */}
+
+
+ {getIcon()}
+
+ {title}
+
+
+
+
+
+
+
+ {/* Conteúdo */}
+
+
{message}
+
+ {details && (
+
+ )}
+
+
+ {/* Footer */}
+
+
+ Fechar
+
+
+
+
+ );
+};
+
+export default PlaygroundModal;
diff --git a/app/src/pages/Playground/components/PlaygroundViewer.css b/app/src/pages/Playground/components/PlaygroundViewer.css
new file mode 100644
index 0000000..315c393
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundViewer.css
@@ -0,0 +1,5 @@
+/**
+ * Estilos para o PlaygroundViewer - apenas estilos que não podem ser feitos com Tailwind
+ */
+
+/* Mantido apenas para referência, mas todo o resto foi convertido para Tailwind */
diff --git a/app/src/pages/Playground/components/PlaygroundViewer.jsx b/app/src/pages/Playground/components/PlaygroundViewer.jsx
new file mode 100644
index 0000000..f2693e9
--- /dev/null
+++ b/app/src/pages/Playground/components/PlaygroundViewer.jsx
@@ -0,0 +1,137 @@
+/**
+ * @fileoverview React component for PlaygroundViewer.jsx
+ *
+ * @module pages.Playground.components.PlaygroundViewer
+ */
+
+import React, { useState } from "react";
+import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
+import CodeViewer from "./CodeViewer";
+import PlaygroundConsole from "./PlaygroundConsole";
+
+const ResizeHandle = ({ theme }) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+const PlaygroundViewer = ({
+ generatedCode,
+ output,
+ onClearConsole,
+ theme = "light",
+}) => {
+ const [activeTab, setActiveTab] = useState("javascript");
+
+ const tabs = [
+ { id: "javascript", label: "JavaScript", icon: "JS" },
+ { id: "python", label: "Python", icon: "PY" },
+ ];
+
+ const renderContent = () => {
+ switch (activeTab) {
+ case "javascript":
+ return (
+
+ );
+
+ case "python":
+ return (
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {tabs.map((tab) => (
+ setActiveTab(tab.id)}
+ title={tab.label}
+ >
+ {tab.icon}
+ {tab.label}
+
+ ))}
+
+
+
+
+
+
+ {renderContent()}
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PlaygroundViewer;
diff --git a/app/src/pages/Playground/config/defaultWorkspace.js b/app/src/pages/Playground/config/defaultWorkspace.js
new file mode 100644
index 0000000..74620f5
--- /dev/null
+++ b/app/src/pages/Playground/config/defaultWorkspace.js
@@ -0,0 +1,163 @@
+/**
+ * Workspace padrão para o Playground
+ * Contém um exemplo simples de soma de dois números e exibição do resultado
+ */
+export const defaultWorkspace = {
+ blocks: {
+ languageVersion: 0,
+ blocks: [
+ {
+ type: "variables_set",
+ id: "default_var1",
+ x: 50,
+ y: 50,
+ fields: {
+ VAR: {
+ id: "var_numero1",
+ },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "default_num1",
+ fields: {
+ NUM: 10,
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "default_var2",
+ fields: {
+ VAR: {
+ id: "var_numero2",
+ },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_number",
+ id: "default_num2",
+ fields: {
+ NUM: 5,
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "variables_set",
+ id: "default_var3",
+ fields: {
+ VAR: {
+ id: "var_resultado",
+ },
+ },
+ inputs: {
+ VALUE: {
+ block: {
+ type: "math_arithmetic",
+ id: "default_soma",
+ fields: {
+ OP: "ADD",
+ },
+ inputs: {
+ A: {
+ block: {
+ type: "variables_get",
+ id: "default_get1",
+ fields: {
+ VAR: {
+ id: "var_numero1",
+ },
+ },
+ },
+ },
+ B: {
+ block: {
+ type: "variables_get",
+ id: "default_get2",
+ fields: {
+ VAR: {
+ id: "var_numero2",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ next: {
+ block: {
+ type: "text_print",
+ id: "default_print",
+ inputs: {
+ TEXT: {
+ block: {
+ type: "text_join",
+ id: "default_join",
+ extraState: {
+ itemCount: 3,
+ },
+ inputs: {
+ ADD0: {
+ block: {
+ type: "text",
+ id: "default_text1",
+ fields: {
+ TEXT: "O resultado da soma é: ",
+ },
+ },
+ },
+ ADD1: {
+ block: {
+ type: "variables_get",
+ id: "default_get3",
+ fields: {
+ VAR: {
+ id: "var_resultado",
+ },
+ },
+ },
+ },
+ ADD2: {
+ block: {
+ type: "text",
+ id: "default_text2",
+ fields: {
+ TEXT: " ✓",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ variables: [
+ {
+ name: "numero1",
+ id: "var_numero1",
+ },
+ {
+ name: "numero2",
+ id: "var_numero2",
+ },
+ {
+ name: "resultado",
+ id: "var_resultado",
+ },
+ ],
+};
diff --git a/app/src/pages/Playground/config/playgroundToolbox.js b/app/src/pages/Playground/config/playgroundToolbox.js
new file mode 100644
index 0000000..61774e8
--- /dev/null
+++ b/app/src/pages/Playground/config/playgroundToolbox.js
@@ -0,0 +1,260 @@
+/**
+ * @fileoverview Utility module for playgroundToolbox.js
+ *
+ * @module pages.Playground.config.playgroundToolbox
+ */
+
+import { getCategoryIcon } from "../../../components/game/editors/toolboxIcons";
+
+export const playgroundToolbox = {
+ kind: "categoryToolbox",
+ contents: [
+ // Lógica
+ {
+ kind: "category",
+ name: "Lógica",
+ colour: "#5C81A6",
+ cssIcon: getCategoryIcon("Lógica"),
+ contents: [
+ { kind: "block", type: "controls_if" },
+ { kind: "block", type: "logic_compare" },
+ { kind: "block", type: "logic_operation" },
+ { kind: "block", type: "logic_negate" },
+ { kind: "block", type: "logic_boolean" },
+ { kind: "block", type: "logic_null" },
+ { kind: "block", type: "logic_ternary" },
+ ],
+ },
+
+ // Loops
+ {
+ kind: "category",
+ name: "Repetição",
+ colour: "#5CA65C",
+ cssIcon: getCategoryIcon("Repetição"),
+ contents: [
+ {
+ kind: "block",
+ type: "controls_repeat_ext",
+ inputs: {
+ TIMES: {
+ shadow: { type: "math_number", fields: { NUM: 10 } },
+ },
+ },
+ },
+ { kind: "block", type: "controls_whileUntil" },
+ {
+ kind: "block",
+ type: "controls_for",
+ inputs: {
+ FROM: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ TO: { shadow: { type: "math_number", fields: { NUM: 10 } } },
+ BY: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ { kind: "block", type: "controls_forEach" },
+ { kind: "block", type: "controls_flow_statements" },
+ ],
+ },
+
+ // Matemática
+ {
+ kind: "category",
+ name: "Matemática",
+ colour: "#5C68A6",
+ cssIcon: getCategoryIcon("Matemática"),
+ contents: [
+ { kind: "block", type: "math_number", fields: { NUM: 123 } },
+ {
+ kind: "block",
+ type: "math_arithmetic",
+ inputs: {
+ A: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ B: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "math_single",
+ inputs: {
+ NUM: { shadow: { type: "math_number", fields: { NUM: 9 } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "math_trig",
+ inputs: {
+ NUM: { shadow: { type: "math_number", fields: { NUM: 45 } } },
+ },
+ },
+ { kind: "block", type: "math_constant" },
+ {
+ kind: "block",
+ type: "math_number_property",
+ inputs: {
+ NUMBER_TO_CHECK: {
+ shadow: { type: "math_number", fields: { NUM: 0 } },
+ },
+ },
+ },
+ {
+ kind: "block",
+ type: "math_round",
+ inputs: {
+ NUM: { shadow: { type: "math_number", fields: { NUM: 3.1 } } },
+ },
+ },
+ { kind: "block", type: "math_on_list" },
+ {
+ kind: "block",
+ type: "math_modulo",
+ inputs: {
+ DIVIDEND: { shadow: { type: "math_number", fields: { NUM: 64 } } },
+ DIVISOR: { shadow: { type: "math_number", fields: { NUM: 10 } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "math_constrain",
+ inputs: {
+ VALUE: { shadow: { type: "math_number", fields: { NUM: 50 } } },
+ LOW: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ HIGH: { shadow: { type: "math_number", fields: { NUM: 100 } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "math_random_int",
+ inputs: {
+ FROM: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ TO: { shadow: { type: "math_number", fields: { NUM: 100 } } },
+ },
+ },
+ { kind: "block", type: "math_random_float" },
+ {
+ kind: "block",
+ type: "math_atan2",
+ inputs: {
+ X: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ Y: { shadow: { type: "math_number", fields: { NUM: 1 } } },
+ },
+ },
+ ],
+ },
+
+ // Texto
+ {
+ kind: "category",
+ name: "Texto",
+ colour: "#5CA68D",
+ cssIcon: getCategoryIcon("Texto"),
+ contents: [
+ { kind: "block", type: "text", fields: { TEXT: "" } },
+ { kind: "block", type: "text_join" },
+ {
+ kind: "block",
+ type: "text_append",
+ inputs: {
+ TEXT: { shadow: { type: "text", fields: { TEXT: "" } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "text_length",
+ inputs: {
+ VALUE: { shadow: { type: "text", fields: { TEXT: "abc" } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "text_isEmpty",
+ inputs: {
+ VALUE: { shadow: { type: "text", fields: { TEXT: "" } } },
+ },
+ },
+ { kind: "block", type: "text_indexOf" },
+ { kind: "block", type: "text_charAt" },
+ { kind: "block", type: "text_getSubstring" },
+ {
+ kind: "block",
+ type: "text_changeCase",
+ inputs: {
+ TEXT: { shadow: { type: "text", fields: { TEXT: "abc" } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "text_trim",
+ inputs: {
+ TEXT: { shadow: { type: "text", fields: { TEXT: "abc" } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "text_print",
+ inputs: {
+ TEXT: { shadow: { type: "text", fields: { TEXT: "abc" } } },
+ },
+ },
+ {
+ kind: "block",
+ type: "text_prompt_ext",
+ inputs: {
+ TEXT: { shadow: { type: "text", fields: { TEXT: "abc" } } },
+ },
+ },
+ ],
+ },
+
+ // Listas
+ {
+ kind: "category",
+ name: "Listas",
+ colour: "#745CA6",
+ cssIcon: getCategoryIcon("Listas"),
+ contents: [
+ { kind: "block", type: "lists_create_with" },
+ { kind: "block", type: "lists_create_empty" },
+ {
+ kind: "block",
+ type: "lists_repeat",
+ inputs: {
+ NUM: { shadow: { type: "math_number", fields: { NUM: 5 } } },
+ },
+ },
+ { kind: "block", type: "lists_length" },
+ { kind: "block", type: "lists_isEmpty" },
+ { kind: "block", type: "lists_indexOf" },
+ { kind: "block", type: "lists_getIndex" },
+ { kind: "block", type: "lists_setIndex" },
+ { kind: "block", type: "lists_getSublist" },
+ {
+ kind: "block",
+ type: "lists_split",
+ inputs: {
+ DELIM: { shadow: { type: "text", fields: { TEXT: "," } } },
+ },
+ },
+ { kind: "block", type: "lists_sort" },
+ ],
+ },
+
+ // Variáveis
+ {
+ kind: "category",
+ name: "Variáveis",
+ colour: "#A65C81",
+ custom: "VARIABLE",
+ cssIcon: getCategoryIcon("Variáveis"),
+ },
+
+ // Funções
+ {
+ kind: "category",
+ name: "Funções",
+ colour: "#9A5CA6",
+ custom: "PROCEDURE",
+ cssIcon: getCategoryIcon("Funções"),
+ },
+ ],
+};
diff --git a/app/src/pages/Playground/config/tourSteps.js b/app/src/pages/Playground/config/tourSteps.js
new file mode 100644
index 0000000..414b433
--- /dev/null
+++ b/app/src/pages/Playground/config/tourSteps.js
@@ -0,0 +1,578 @@
+/**
+ * @fileoverview Utility module for tourSteps.js
+ *
+ * @module pages.Playground.config.tourSteps
+ */
+
+// Helper para detectar mobile e ajustar posicionamento
+const isMobile = () => window.innerWidth < 768;
+
+const getAttachPosition = (
+ desktopPosition = "right",
+ mobilePosition = "bottom",
+) => {
+ return isMobile() ? mobilePosition : desktopPosition;
+};
+
+export const tourSteps = [
+ {
+ id: "welcome",
+ text: `
+
+ Este é um ambiente interativo onde você pode aprender programação usando blocos visuais.
+ Vamos fazer um tour rápido para conhecer as funcionalidades!
+ `,
+ buttons: [
+ {
+ text: "Pular Tour",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.complete();
+ },
+ },
+ {
+ text: "Iniciar Tour",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "blockly-toolbox",
+ attachTo: {
+ element: () => {
+ return (
+ document.querySelector(".blocklyToolbox") ||
+ document.querySelector(".blocklyToolboxDiv") ||
+ document.querySelector(".blocklyFlyout") ||
+ document.querySelector('[class*="blocklyToolbox"]')
+ );
+ },
+ on: "right",
+ },
+ text: `
+
+ Aqui estão organizadas as categorias de blocos por tipo de função:
+
+ Lógica: Condições e comparações
+ Loops: Estruturas de repetição
+ Matemática: Operações numéricas
+ Texto: Manipulação de strings
+ Listas: Arrays e coleções
+ Variáveis: Armazenamento de dados
+
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "blockly-workspace",
+ attachTo: {
+ element: ".blocklyWorkspace",
+ on: getAttachPosition("right", "top"),
+ },
+ text: `
+
+ Arrastar: Clique em um bloco da categoria à esquerda e arraste para esta área
+ Conectar: Aproxime blocos compatíveis - eles se encaixam automaticamente com um "clique"
+ Dica: Blocos com formatos diferentes indicam tipos de dados e conexões compatíveis
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "blockly-trashcan",
+ attachTo: {
+ element: ".blocklyTrash",
+ on: getAttachPosition("left", "top"),
+ },
+ text: `
+
+ Excluir blocos: Arraste qualquer bloco para esta lixeira para removê-lo da área de trabalho
+ Dica: Você também pode clicar com o botão direito em um bloco e selecionar "Excluir"
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "button-run",
+ attachTo: {
+ element: '[data-tour="run-button"]',
+ on: "bottom",
+ },
+ text: `
+
+ Execute seu código: Clique aqui para rodar o programa que você criou com os blocos.
+ Os resultados aparecerão no console abaixo. Se houver erros, eles também serão mostrados lá.
+ Dica: Você precisa ter pelo menos um bloco na área de trabalho para execute.
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "button-download",
+ attachTo: {
+ element: '[data-tour="download-button"]',
+ on: "bottom",
+ },
+ text: `
+
+ Salve seu trabalho: Exporta seu workspace para um arquivo no seu computador.
+ Use isso para fazer backup dos seus projetos ou compartilhar com outras pessoas.
+ Importante: Seus blocos também são salvos automaticamente no navegador!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "button-upload",
+ attachTo: {
+ element: '[data-tour="upload-button"]',
+ on: "bottom",
+ },
+ text: `
+
+ Carregue um workspace salvo: Importa um arquivo que você salvou anteriormente.
+ Clique aqui e selecione o arquivo do seu computador para restaurar seus blocos.
+ Atenção: Isso substituirá os blocos atuais na área de trabalho!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "button-examples",
+ attachTo: {
+ element: '[data-tour="examples-button"]',
+ on: "bottom",
+ },
+ text: `
+
+ Explore exemplos prontos: Abre uma galeria com vários exemplos de programas.
+ Ótimo para aprender novos conceitos e se inspirar para criar seus próprios projetos!
+ Clique no botão agora para ver os exemplos disponíveis!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Ver Exemplos",
+ action() {
+ // Abrir o modal de exemplos
+ const examplesButton = document.querySelector(
+ '[data-tour="examples-button"]',
+ );
+ if (examplesButton) {
+ examplesButton.click();
+ }
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "examples-modal",
+ text: `
+
+ Aqui você encontra exemplos organizados por categoria:
+
+ Básicos: Conceitos fundamentais
+ Matemática: Operações e cálculos
+ Loops: Estruturas de repetição
+ Listas: Trabalho com arrays
+ Lógica: Condicionais e decisões
+
+ Clique em qualquer exemplo para carregar na área de trabalho!
+ `,
+ buttons: [
+ {
+ text: "Fechar Exemplos",
+ classes: "shepherd-button-secondary",
+ action() {
+ // Fechar o modal
+ const closeButton = document.querySelector("[data-modal-close]");
+ if (closeButton) {
+ closeButton.click();
+ }
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ // Fechar o modal
+ const closeButton = document.querySelector("[data-modal-close]");
+ if (closeButton) {
+ closeButton.click();
+ }
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "code-viewer",
+ attachTo: {
+ element: '[data-tour="code-viewer"]',
+ on: getAttachPosition("left", "top"),
+ },
+ text: `
+
+ Aqui você vê o código JavaScript e Python gerado automaticamente a partir dos seus blocos.
+ É uma ótima forma de aprender como os blocos se traduzem em código real!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "console",
+ attachTo: {
+ element: '[data-tour="console"]',
+ on: getAttachPosition("left", "top"),
+ },
+ text: `
+
+ Quando você executa seu código, os resultados aparecem aqui.
+ Mensagens de erro também serão exibidas para ajudar na depuração.
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "theme-toggle",
+ attachTo: {
+ element: '[data-tour="theme-toggle"]',
+ on: "bottom",
+ },
+ text: `
+
+ Alterne entre tema claro e escuro para sua preferência de visualização.
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "resize-panels",
+ when: {
+ show() {
+ // Só mostrar este step se estiver em mobile
+ if (!isMobile()) {
+ this.next();
+ }
+ },
+ },
+ text: `
+
+ Barra divisória: Arraste a barra entre "Editor de Blocos" e "Código" para ajustar o tamanho de cada área conforme sua necessidade.
+ 💻 No computador as áreas ficam fixas em 50%.
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Próximo",
+ action() {
+ this.next();
+ },
+ },
+ ],
+ },
+ {
+ id: "help-button",
+ attachTo: {
+ element: '[data-tour="help-button"]',
+ on: "bottom",
+ },
+ text: `
+
+ Clique aqui sempre que quiser rever este tour!
+ Agora você está pronto para começar a programar!
+ `,
+ buttons: [
+ {
+ text: "Anterior",
+ classes: "shepherd-button-secondary",
+ action() {
+ this.back();
+ },
+ },
+ {
+ text: "Finalizar",
+ action() {
+ this.complete();
+ },
+ },
+ ],
+ },
+];
+
+export const tourOptions = {
+ defaultStepOptions: {
+ cancelIcon: {
+ enabled: true,
+ },
+ classes: "playground-tour-step",
+ scrollTo: { behavior: "smooth", block: "center" },
+ },
+ useModalOverlay: true,
+};
diff --git a/app/src/pages/Playground/hooks/useCodeExecution.js b/app/src/pages/Playground/hooks/useCodeExecution.js
new file mode 100644
index 0000000..56f9949
--- /dev/null
+++ b/app/src/pages/Playground/hooks/useCodeExecution.js
@@ -0,0 +1,97 @@
+/**
+ * @fileoverview Utility module for useCodeExecution.js
+ *
+ * @module pages.Playground.hooks.useCodeExecution
+ */
+
+import { useState, useCallback, useRef } from "react";
+import { GameInterpreter } from "../../../interpreters/GameInterpreter";
+
+export const useCodeExecution = () => {
+ const [output, setOutput] = useState([]);
+ const [isRunning, setIsRunning] = useState(false);
+ const interpreterRef = useRef(null);
+
+ const addOutput = useCallback((message, type = "log") => {
+ setOutput((prev) => [
+ ...prev,
+ {
+ id: Date.now() + Math.random(),
+ timestamp: new Date(),
+ message,
+ type,
+ },
+ ]);
+ }, []);
+
+ const clearOutput = useCallback(() => {
+ setOutput([]);
+ }, []);
+
+ const runCode = useCallback(
+ async (code) => {
+ if (!code || code.trim() === "") {
+ addOutput("Nenhum código para executar", "error");
+ return;
+ }
+
+ setIsRunning(true);
+ clearOutput();
+
+ // Adicionar linha de início da execução
+ addOutput("▶️ Iniciando execução...", "log");
+
+ try {
+ interpreterRef.current = new GameInterpreter();
+
+ const apiSetup = (interpreter, globalObject) => {
+ const consoleLogWrapper = (text) => {
+ addOutput(String(text), "log");
+ };
+ interpreter.setProperty(
+ globalObject,
+ "console",
+ interpreter.nativeToPseudo({
+ log: consoleLogWrapper,
+ }),
+ );
+
+ const alertWrapper = (text) => {
+ addOutput(String(text), "log");
+ };
+ interpreter.setProperty(
+ globalObject,
+ "alert",
+ interpreter.createNativeFunction(alertWrapper),
+ );
+
+ const promptWrapper = (text) => {
+ addOutput(`Prompt: ${text} (não suportado)`, "alert");
+ return null;
+ };
+ interpreter.setProperty(
+ globalObject,
+ "prompt",
+ interpreter.createNativeFunction(promptWrapper),
+ );
+ };
+
+ await interpreterRef.current.executeCode(code, apiSetup);
+
+ addOutput("Execução concluída com sucesso", "success");
+ } catch (error) {
+ addOutput(`Erro: ${error.message}`, "error");
+ } finally {
+ setIsRunning(false);
+ }
+ },
+ [addOutput, clearOutput],
+ );
+
+ return {
+ output,
+ isRunning,
+ runCode,
+ clearOutput,
+ };
+};
diff --git a/app/src/pages/Playground/hooks/useCodeGeneration.js b/app/src/pages/Playground/hooks/useCodeGeneration.js
new file mode 100644
index 0000000..d0ed1bb
--- /dev/null
+++ b/app/src/pages/Playground/hooks/useCodeGeneration.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Utility module for useCodeGeneration.js
+ *
+ * @module pages.Playground.hooks.useCodeGeneration
+ */
+
+import { useMemo } from "react";
+import * as Blockly from "blockly/core";
+import { javascriptGenerator } from "blockly/javascript";
+import { pythonGenerator } from "blockly/python";
+
+export const useCodeGeneration = (workspace, version = 0) => {
+ return useMemo(() => {
+ if (!workspace) {
+ return {
+ javascript: "",
+ python: "",
+ };
+ }
+
+ try {
+ javascriptGenerator.STATEMENT_PREFIX = null;
+ javascriptGenerator.STATEMENT_SUFFIX = null;
+ javascriptGenerator.INFINITE_LOOP_TRAP = null;
+
+ pythonGenerator.STATEMENT_PREFIX = null;
+ pythonGenerator.STATEMENT_SUFFIX = null;
+ pythonGenerator.INFINITE_LOOP_TRAP = null;
+
+ const javascript = javascriptGenerator.workspaceToCode(workspace);
+ const python = pythonGenerator.workspaceToCode(workspace);
+
+ return {
+ javascript,
+ python,
+ };
+ } catch (error) {
+ console.error("Erro ao gerar código:", error);
+ return {
+ javascript: `// Erro ao gerar código: ${error.message}`,
+ python: `# Erro ao gerar código: ${error.message}`,
+ };
+ }
+ }, [workspace, version]);
+};
diff --git a/app/src/pages/Playground/hooks/usePlaygroundTour.js b/app/src/pages/Playground/hooks/usePlaygroundTour.js
new file mode 100644
index 0000000..387ce0d
--- /dev/null
+++ b/app/src/pages/Playground/hooks/usePlaygroundTour.js
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview Utility module for usePlaygroundTour.js
+ *
+ * @module pages.Playground.hooks.usePlaygroundTour
+ */
+
+import { useTour } from "../../../hooks/useTour";
+import { tourSteps, tourOptions } from "../config/tourSteps";
+
+const TOUR_STORAGE_KEY = "playground-tour-completed";
+
+/**
+ * Hook específico do Playground que usa o useTour genérico
+ * Mantém a mesma interface para compatibilidade
+ */
+export const usePlaygroundTour = () => {
+ const { startTour, completeTour, resetTour } = useTour({
+ steps: tourSteps,
+ options: tourOptions,
+ storageKey: TOUR_STORAGE_KEY,
+ autoStart: true,
+ startDelay: 1000,
+ });
+
+ return {
+ startTour,
+ completeTour,
+ resetTour,
+ };
+};
diff --git a/app/src/pages/Playground/services/playgroundStorage.js b/app/src/pages/Playground/services/playgroundStorage.js
new file mode 100644
index 0000000..35c500e
--- /dev/null
+++ b/app/src/pages/Playground/services/playgroundStorage.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Utility module for playgroundStorage.js
+ *
+ * @module pages.Playground.services.playgroundStorage
+ */
+
+import * as Blockly from "blockly/core";
+
+const STORAGE_KEY = "playground-workspace";
+
+export const saveWorkspace = (workspace) => {
+ if (!workspace) {
+ console.warn("Workspace não fornecido para salvar");
+ return false;
+ }
+
+ try {
+ const json = Blockly.serialization.workspaces.save(workspace);
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(json));
+ return true;
+ } catch (error) {
+ console.error("Erro ao salvar workspace:", error);
+ return false;
+ }
+};
+
+export const loadWorkspace = () => {
+ try {
+ const saved = localStorage.getItem(STORAGE_KEY);
+ return saved ? JSON.parse(saved) : null;
+ } catch (error) {
+ console.error("Erro ao carregar workspace:", error);
+ return null;
+ }
+};
+
+export const createDebouncedSave = (delay = 1000) => {
+ let timeoutId = null;
+
+ return (workspace) => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+
+ timeoutId = setTimeout(() => {
+ saveWorkspace(workspace);
+ }, delay);
+ };
+};
diff --git a/app/src/pages/PrimeirosPassos/AtividadeLetramentoView.jsx b/app/src/pages/PrimeirosPassos/AtividadeLetramentoView.jsx
new file mode 100644
index 0000000..acf6ab5
--- /dev/null
+++ b/app/src/pages/PrimeirosPassos/AtividadeLetramentoView.jsx
@@ -0,0 +1,100 @@
+import { useParams, Navigate, useNavigate } from 'react-router-dom';
+import { getAtividade } from '../../atividades/letramento/letramentoRegistry';
+import { LetramentoStateProvider, useLetramentoState, ATIVIDADE_STATES } from '../../contexts/LetramentoStateContext';
+import LetramentoNavBar from '../../components/letramento/LetramentoNavBar';
+import AtividadeRuntimeFrame from '../../components/letramento/AtividadeRuntimeFrame';
+import AtividadeStatusModal from '../../components/letramento/AtividadeStatusModal';
+import TrilhaPassos from '../../components/letramento/TrilhaPassos';
+import AtividadesSidebar from '../../components/letramento/AtividadesSidebar';
+
+function AtividadeContent({ atividade }) {
+ const { status, currentStep, reset } = useLetramentoState();
+ const navigate = useNavigate();
+ const isCompleted = status === ATIVIDADE_STATES.SUCCESS || status === ATIVIDADE_STATES.COMPLETED;
+ const startLabel = atividade.categoria === 'mouse'
+ ? 'Mova o mouse para começar'
+ : 'Pressione uma tecla para começar';
+
+ const handleTryAgain = () => {
+ reset();
+ };
+
+ const handleClose = () => navigate(-1);
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
+ {/* Main content area with sidebar */}
+
+ {/* Sidebar navegação de atividades */}
+
+
+ {/* Content area */}
+
+ {/* Step trail */}
+ {atividade.passos?.length > 0 && (
+
+ )}
+
+ {/* Activity iframe */}
+
+
+ {/* Footer status */}
+
+
+ {isCompleted
+ ? 'Atividade concluída'
+ : currentStep > 0
+ ? `Passo ${currentStep} de ${atividade.passos?.length ?? '?'}`
+ : startLabel}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function AtividadeLetramentoView() {
+ const { atividadeId } = useParams();
+ const atividade = getAtividade(atividadeId);
+
+ if (!atividade) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/app/src/pages/PrimeirosPassos/CategoriaLetramentoView.jsx b/app/src/pages/PrimeirosPassos/CategoriaLetramentoView.jsx
new file mode 100644
index 0000000..f8befa1
--- /dev/null
+++ b/app/src/pages/PrimeirosPassos/CategoriaLetramentoView.jsx
@@ -0,0 +1,148 @@
+import { useEffect, useState } from 'react';
+import { useParams, Navigate, useNavigate } from 'react-router-dom';
+import { listarAtividadesPorCategoria } from '../../atividades/letramento/letramentoRegistry';
+import { LetramentoStateProvider, useLetramentoState, ATIVIDADE_STATES } from '../../contexts/LetramentoStateContext';
+import LetramentoNavBar from '../../components/letramento/LetramentoNavBar';
+import AtividadeRuntimeFrame from '../../components/letramento/AtividadeRuntimeFrame';
+import AtividadeStatusModal from '../../components/letramento/AtividadeStatusModal';
+import TrilhaPassos from '../../components/letramento/TrilhaPassos';
+import AtividadesSidebar from '../../components/letramento/AtividadesSidebar';
+
+function AtividadeContent({ atividade, onChangeAtividade }) {
+ const navigate = useNavigate();
+ const { status, currentStep, reset } = useLetramentoState();
+ const isCompleted = status === ATIVIDADE_STATES.SUCCESS || status === ATIVIDADE_STATES.COMPLETED;
+ const isFailure = status === ATIVIDADE_STATES.FAILURE;
+ const [isStatusModalDismissed, setIsStatusModalDismissed] = useState(false);
+ const [reloadToken, setReloadToken] = useState(0);
+ const startLabel = atividade.categoria === 'mouse'
+ ? 'Mova o mouse para começar'
+ : 'Pressione uma tecla para começar';
+
+ useEffect(() => {
+ setIsStatusModalDismissed(false);
+ setReloadToken(0);
+ }, [atividade.id]);
+
+ useEffect(() => {
+ if (isCompleted || isFailure) {
+ setIsStatusModalDismissed(false);
+ }
+ }, [isCompleted, isFailure]);
+
+ const handleTryAgain = () => {
+ setIsStatusModalDismissed(true);
+ reset();
+ setReloadToken((value) => value + 1);
+ };
+
+ const handleDismissStatusModal = () => {
+ setIsStatusModalDismissed(true);
+ };
+
+ const handleNextAtividade = () => {
+ if (!atividade.proxima) return;
+
+ setIsStatusModalDismissed(true);
+ onChangeAtividade(atividade.proxima);
+ };
+
+ const handleClose = () => {
+ navigate(-1);
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+ {/* Sidebar navegação de atividades */}
+
+
+ {/* Content area */}
+
+ {/* Step trail */}
+ {atividade.passos?.length > 0 && (
+
+ )}
+
+ {/* Activity iframe */}
+
+
+ {/* Footer status */}
+
+
+ {isCompleted
+ ? 'Atividade concluída'
+ : currentStep > 0
+ ? `Passo ${currentStep} de ${atividade.passos?.length ?? '?'}`
+ : startLabel}
+
+
+
+
+
+
+
+
+ );
+}
+
+export default function CategoriaLetramentoView() {
+ const { categoria } = useParams();
+
+ const atividades = listarAtividadesPorCategoria(categoria);
+
+ // Validação inicial
+ if (!atividades || atividades.length === 0) {
+ return ;
+ }
+
+ // Estado local para controlar qual atividade está sendo exibida (similar ao currentPhase dos games)
+ const [atividadeAtualId, setAtividadeAtualId] = useState(() => atividades[0].id);
+
+ const atividadeAtual = atividades.find(a => a.id === atividadeAtualId);
+
+ // Validação de segurança - não retorna null, apenas ajusta o estado
+ if (!atividadeAtual) {
+ return ;
+ }
+
+ const handleChangeAtividade = (novoId) => {
+ setAtividadeAtualId(novoId);
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/app/src/pages/PrimeirosPassos/PrimeirosPassos.jsx b/app/src/pages/PrimeirosPassos/PrimeirosPassos.jsx
new file mode 100644
index 0000000..5fc9e95
--- /dev/null
+++ b/app/src/pages/PrimeirosPassos/PrimeirosPassos.jsx
@@ -0,0 +1,28 @@
+import Navbar from "../../components/Navbar";
+import Footer from "../HomePage/Footer";
+import PrimeirosPassosList from "./PrimeirosPassosList";
+import { useIsMobile } from "../../hooks/useIsMobile";
+import DesktopOnlyTapume from "../../components/DesktopOnlyTapume";
+
+export default function PrimeirosPassos() {
+ const isSmallScreen = useIsMobile(1024);
+
+ return (
+
+
+ {isSmallScreen ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/app/src/pages/PrimeirosPassos/PrimeirosPassosList.jsx b/app/src/pages/PrimeirosPassos/PrimeirosPassosList.jsx
new file mode 100644
index 0000000..8553d8d
--- /dev/null
+++ b/app/src/pages/PrimeirosPassos/PrimeirosPassosList.jsx
@@ -0,0 +1,95 @@
+import React from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { MousePointer2, Keyboard, AppWindow, KeyRound } from 'lucide-react';
+
+const trilhas = [
+ {
+ id: 'mouse',
+ Icone: MousePointer2,
+ titulo: 'Usar o Mouse',
+ descricao: 'Aprenda a mover, clicar e usar o mouse no computador.',
+ bgClass: 'bg-brand-500',
+ rota: '/primeiros-passos/mouse',
+ isModal: true,
+ },
+ {
+ id: 'teclado',
+ Icone: Keyboard,
+ titulo: 'Usar o Teclado',
+ descricao: 'Aprenda a localizar teclas, digitar, editar textos e usar atalhos básicos.',
+ bgClass: 'bg-brand-400',
+ rota: '/primeiros-passos/teclado',
+ isModal: true,
+ },
+ // TODO: atividades em elaboração
+ // {
+ // id: 'janelas',
+ // Icone: AppWindow,
+ // titulo: 'Usar Janelas',
+ // descricao: 'Aprenda a abrir, fechar e organizar janelas na tela.',
+ // bgClass: 'bg-green-500',
+ // rota: null,
+ // },
+ // {
+ // id: 'login',
+ // Icone: KeyRound,
+ // titulo: 'O que é um Login',
+ // descricao: 'Entenda o que são usuário e senha e como entrar em um site.',
+ // bgClass: 'bg-gray-900',
+ // rota: null,
+ // },
+];
+
+const PrimeirosPassosList = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const handleClick = (trilha) => {
+ if (!trilha.rota) return;
+ if (trilha.isModal) {
+ navigate(trilha.rota, { state: { backgroundLocation: location } });
+ } else {
+ navigate(trilha.rota);
+ }
+ };
+
+ return (
+
+
+ {trilhas.map((trilha) => {
+ const { id, Icone, titulo, descricao, bgClass, rota } = trilha;
+ return (
+
handleClick(trilha)}
+ disabled={!rota}
+ className={[
+ bgClass,
+ 'rounded-[32px] p-10 flex flex-col items-center text-center gap-6 text-white shadow-lg',
+ rota
+ ? 'hover:scale-[1.03] transition-transform duration-200 cursor-pointer'
+ : 'opacity-50 cursor-not-allowed',
+ ].join(' ')}
+ >
+
+
+ {titulo}
+
+
+ {descricao}
+
+ {!rota && (
+
+ Em breve
+
+ )}
+
+ );
+ })}
+
+
+ );
+};
+
+export default PrimeirosPassosList;
+
diff --git a/app/src/services/__tests__/blockstorage.test.js b/app/src/services/__tests__/blockstorage.test.js
new file mode 100644
index 0000000..56b5ab4
--- /dev/null
+++ b/app/src/services/__tests__/blockstorage.test.js
@@ -0,0 +1,82 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { loadWorkspace, createDebouncedSave } from "../blockstorage";
+
+// ---------------------------------------------------------------------------
+// localStorage mock
+// ---------------------------------------------------------------------------
+const store = {};
+const localStorageMock = {
+ getItem: vi.fn((k) => store[k] ?? null),
+ setItem: vi.fn((k, v) => { store[k] = v; }),
+ removeItem: vi.fn((k) => { delete store[k]; }),
+};
+vi.stubGlobal("localStorage", localStorageMock);
+
+beforeEach(() => {
+ Object.keys(store).forEach((k) => delete store[k]);
+ vi.clearAllMocks();
+});
+
+// ---------------------------------------------------------------------------
+const VALID_WORKSPACE = { blocks: { blocks: [{ type: "mover" }] } };
+const EMPTY_BLOCKS = { blocks: { blocks: [] } };
+
+describe("loadWorkspace()", () => {
+ it("devolve null quando a chave não existe", () => {
+ expect(loadWorkspace("absent")).toBeNull();
+ });
+
+ it("devolve o workspace quando válido", () => {
+ store["ws"] = JSON.stringify(VALID_WORKSPACE);
+ expect(loadWorkspace("ws")).toEqual(VALID_WORKSPACE);
+ });
+
+ it("devolve workspace com blocks vazio (array)", () => {
+ store["ws"] = JSON.stringify(EMPTY_BLOCKS);
+ expect(loadWorkspace("ws")).toEqual(EMPTY_BLOCKS);
+ });
+
+ it("devolve null quando blocks não é array", () => {
+ store["ws"] = JSON.stringify({ blocks: { blocks: "não-array" } });
+ expect(loadWorkspace("ws")).toBeNull();
+ });
+
+ it("devolve null quando blocks.blocks está ausente", () => {
+ store["ws"] = JSON.stringify({ blocks: {} });
+ expect(loadWorkspace("ws")).toBeNull();
+ });
+
+ it("devolve null quando o JSON está corrompido", () => {
+ store["ws"] = "{ broken";
+ expect(loadWorkspace("ws")).toBeNull();
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("ws");
+ });
+});
+
+describe("createDebouncedSave()", () => {
+ it("retorna uma função de save", () => {
+ expect(typeof createDebouncedSave()).toBe("function");
+ });
+
+ it("instâncias diferentes são independentes", () => {
+ expect(createDebouncedSave()).not.toBe(createDebouncedSave());
+ });
+
+ it("quando invocada com delay=0 persiste workspace válido", async () => {
+ const save = createDebouncedSave(0);
+ save("ws", VALID_WORKSPACE);
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
+ "ws",
+ JSON.stringify(VALID_WORKSPACE),
+ );
+ });
+
+ it("quando invocada com delay=0 remove chave para workspace inválido", async () => {
+ const save = createDebouncedSave(0);
+ save("ws", null);
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("ws");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/src/services/__tests__/codestorage.test.js b/app/src/services/__tests__/codestorage.test.js
new file mode 100644
index 0000000..d9f1288
--- /dev/null
+++ b/app/src/services/__tests__/codestorage.test.js
@@ -0,0 +1,93 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { loadCode, createDebouncedCodeSave, clearCodeStorage } from "../codestorage";
+
+// ---------------------------------------------------------------------------
+// localStorage mock
+// ---------------------------------------------------------------------------
+const store = {};
+const localStorageMock = {
+ getItem: vi.fn((k) => store[k] ?? null),
+ setItem: vi.fn((k, v) => { store[k] = v; }),
+ removeItem: vi.fn((k) => { delete store[k]; }),
+};
+vi.stubGlobal("localStorage", localStorageMock);
+
+beforeEach(() => {
+ Object.keys(store).forEach((k) => delete store[k]);
+ vi.clearAllMocks();
+});
+
+// ---------------------------------------------------------------------------
+describe("loadCode()", () => {
+ it("devolve null quando a chave não existe", () => {
+ expect(loadCode("absent")).toBeNull();
+ });
+
+ it("devolve o código quando válido", () => {
+ store["k"] = JSON.stringify("console.log('oi')");
+ expect(loadCode("k")).toBe("console.log('oi')");
+ });
+
+ it("devolve null quando o valor não é string", () => {
+ store["k"] = JSON.stringify({ not: "a string" });
+ expect(loadCode("k")).toBeNull();
+ });
+
+ it("devolve null e remove chave quando o JSON está corrompido", () => {
+ store["k"] = "{ broken";
+ expect(loadCode("k")).toBeNull();
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ });
+});
+
+describe("clearCodeStorage()", () => {
+ it("remove a chave do localStorage", () => {
+ store["k"] = JSON.stringify("código");
+ clearCodeStorage("k");
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ });
+});
+
+describe("createDebouncedCodeSave()", () => {
+ it("retorna uma função de save", () => {
+ expect(typeof createDebouncedCodeSave()).toBe("function");
+ });
+
+ it("instâncias diferentes são independentes", () => {
+ expect(createDebouncedCodeSave()).not.toBe(createDebouncedCodeSave());
+ });
+
+ it("quando invocada com delay=0 persiste código válido", async () => {
+ const save = createDebouncedCodeSave(0);
+ save("k", "let x = 1;");
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.setItem).toHaveBeenCalledWith(
+ "k",
+ JSON.stringify("let x = 1;"),
+ );
+ });
+
+ it("quando invocada com delay=0 remove chave para string vazia", async () => {
+ const save = createDebouncedCodeSave(0);
+ save("k", "");
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+
+ it("quando invocada com delay=0 remove chave para string em branco", async () => {
+ const save = createDebouncedCodeSave(0);
+ save("k", " ");
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+
+ it("quando invocada com delay=0 remove chave para valor não-string", async () => {
+ const save = createDebouncedCodeSave(0);
+ save("k", null);
+ await new Promise((r) => setTimeout(r, 10));
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/src/services/__tests__/createStorageService.test.js b/app/src/services/__tests__/createStorageService.test.js
new file mode 100644
index 0000000..b2ccf41
--- /dev/null
+++ b/app/src/services/__tests__/createStorageService.test.js
@@ -0,0 +1,108 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { createStorageService } from "../createStorageService";
+
+// ---------------------------------------------------------------------------
+// localStorage mock
+// ---------------------------------------------------------------------------
+const store = {};
+const localStorageMock = {
+ getItem: vi.fn((k) => store[k] ?? null),
+ setItem: vi.fn((k, v) => { store[k] = v; }),
+ removeItem: vi.fn((k) => { delete store[k]; }),
+};
+vi.stubGlobal("localStorage", localStorageMock);
+
+beforeEach(() => {
+ Object.keys(store).forEach((k) => delete store[k]);
+ vi.clearAllMocks();
+});
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+const isString = (v) => typeof v === "string";
+const isBlank = (v) => v.trim() === "";
+
+// ---------------------------------------------------------------------------
+describe("createStorageService — load()", () => {
+ it("devolve null quando a chave não existe", () => {
+ const svc = createStorageService({ validator: isString });
+ expect(svc.load("missing")).toBeNull();
+ });
+
+ it("devolve o valor quando válido", () => {
+ store["k"] = JSON.stringify("hello");
+ const svc = createStorageService({ validator: isString });
+ expect(svc.load("k")).toBe("hello");
+ });
+
+ it("devolve null e remove a chave quando o valor não passa no validator", () => {
+ store["k"] = JSON.stringify(42); // não é string
+ const svc = createStorageService({ validator: isString });
+ expect(svc.load("k")).toBeNull();
+ });
+
+ it("devolve null e remove a chave quando o JSON está corrompido", () => {
+ store["k"] = "{ invalid json";
+ const svc = createStorageService({ validator: isString });
+ expect(svc.load("k")).toBeNull();
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ });
+});
+
+describe("createStorageService — save()", () => {
+ it("persiste um valor válido via JSON.stringify", () => {
+ const svc = createStorageService({ validator: isString });
+ svc.save("k", "world");
+ expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify("world"));
+ });
+
+ it("remove a chave quando o validator rejeita o valor", () => {
+ const svc = createStorageService({ validator: isString });
+ svc.save("k", 99); // não é string
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+
+ it("remove a chave quando isEmpty retorna true", () => {
+ const svc = createStorageService({ validator: isString, isEmpty: isBlank });
+ svc.save("k", " ");
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ expect(localStorageMock.setItem).not.toHaveBeenCalled();
+ });
+
+ it("persiste quando valor é válido e não está vazio", () => {
+ const svc = createStorageService({ validator: isString, isEmpty: isBlank });
+ svc.save("k", "código");
+ expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify("código"));
+ });
+
+ it("usa isEmpty=()=>false por padrão (não remove valores válidos)", () => {
+ const svc = createStorageService({ validator: isString }); // sem isEmpty
+ svc.save("k", ""); // string vazia mas sem isEmpty configurado
+ expect(localStorageMock.setItem).toHaveBeenCalledWith("k", JSON.stringify(""));
+ });
+});
+
+describe("createStorageService — clear()", () => {
+ it("remove a chave do localStorage", () => {
+ store["k"] = "qualquer";
+ const svc = createStorageService({ validator: isString });
+ svc.clear("k");
+ expect(localStorageMock.removeItem).toHaveBeenCalledWith("k");
+ });
+});
+
+describe("createStorageService — createDebouncedSave()", () => {
+ it("retorna uma função", () => {
+ const svc = createStorageService({ validator: isString });
+ expect(typeof svc.createDebouncedSave()).toBe("function");
+ });
+
+ it("instâncias diferentes são funções independentes", () => {
+ const svc = createStorageService({ validator: isString });
+ const fn1 = svc.createDebouncedSave(500);
+ const fn2 = svc.createDebouncedSave(500);
+ expect(fn1).not.toBe(fn2);
+ });
+});
diff --git a/app/src/services/blockstorage.js b/app/src/services/blockstorage.js
new file mode 100644
index 0000000..adc5633
--- /dev/null
+++ b/app/src/services/blockstorage.js
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Persistência do workspace Blockly no `localStorage`.
+ *
+ * Utiliza `createStorageService` para centralizar o padrão load/save/debounce.
+ * O workspace é considerado válido apenas quando possui `blocks.blocks` como
+ * array não nulo; estados inválidos (workspace limpo) removem a chave.
+ *
+ * @module services.blockstorage
+ */
+
+import { createStorageService } from "./createStorageService";
+
+/**
+ * Valida a estrutura do workspace Blockly serializado.
+ * Emite avisos no console para facilitar diagnóstico em desenvolvimento.
+ *
+ * @param {unknown} workspaceJson - Valor lido do localStorage
+ * @returns {boolean}
+ */
+function isWorkspaceStateValid(workspaceJson) {
+ if (!workspaceJson) {
+ console.warn("Validação falhou: JSON do workspace é nulo.");
+ return false;
+ }
+ if (typeof workspaceJson.blocks === "undefined") {
+ console.warn("Validação falhou: a propriedade 'blocks' não existe.");
+ return false;
+ }
+ if (!Array.isArray(workspaceJson.blocks.blocks)) {
+ console.warn("Validação falhou: 'blocks.blocks' não é um array.");
+ return false;
+ }
+ return true;
+}
+
+const workspaceService = createStorageService({ validator: isWorkspaceStateValid });
+
+/**
+ * Carrega o workspace serializado associado a `key`.
+ *
+ * @param {string} key - Chave do localStorage
+ * @returns {Object|null} Workspace Blockly ou `null` se ausente/inválido
+ */
+export function loadWorkspace(key) {
+ return workspaceService.load(key);
+}
+
+/**
+ * Cria uma função de salvamento com debounce para evitar múltiplas escritas
+ * consecutivas durante edições rápidas.
+ *
+ * @param {number} [delay=1000] - Tempo de debounce em milissegundos
+ * @returns {Function} Função debounced `(key, workspaceJson) => void`
+ */
+export function createDebouncedSave(delay = 1000) {
+ return workspaceService.createDebouncedSave(delay);
+}
diff --git a/app/src/services/codestorage.js b/app/src/services/codestorage.js
new file mode 100644
index 0000000..bd88810
--- /dev/null
+++ b/app/src/services/codestorage.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Utilitários para persistência de código no `localStorage`.
+ *
+ * Utiliza `createStorageService` para centralizar o padrão load/save/debounce.
+ * Strings vazias são tratadas como "sem conteúdo" e removem a chave em vez
+ * de salvar um valor vazio.
+ *
+ * Convenções:
+ * - Valores válidos são strings não vazias; strings em branco removem a chave.
+ * - As chaves são fornecidas pelo chamador, por exemplo: `playground:code`.
+ *
+ * Exemplo de uso:
+ * ```js
+ * import { loadCode, createDebouncedCodeSave, clearCodeStorage } from '../services/codestorage';
+ *
+ * const key = 'playground:code';
+ * const code = loadCode(key); // string | null
+ * const debouncedSave = createDebouncedCodeSave(1000);
+ * debouncedSave(key, code);
+ * // Para limpar:
+ * clearCodeStorage(key);
+ * ```
+ *
+ * @module services.codestorage
+ */
+
+import { createStorageService } from "./createStorageService";
+
+const codeService = createStorageService({
+ validator: (v) => typeof v === "string",
+ isEmpty: (v) => v.trim() === "",
+});
+
+/**
+ * Carrega o conteúdo de código associado a `key` no `localStorage`.
+ *
+ * @param {string} key - Chave usada no `localStorage`
+ * @returns {string|null} O conteúdo salvo ou `null` se ausente/inválido
+ */
+export function loadCode(key) {
+ return codeService.load(key);
+}
+
+/**
+ * Cria uma função de salvamento com debounce para evitar múltiplas escritas
+ * consecutivas durante edições rápidas.
+ *
+ * A função retornada tem a assinatura `(key: string, codeContent: string) => void`.
+ *
+ * @param {number} [delay=1000] - Tempo de debounce em milissegundos
+ * @returns {Function} Função debounced que salva o código fornecido
+ *
+ * @example
+ * const debouncedSave = createDebouncedCodeSave(500);
+ * debouncedSave('playground:code', 'console.log("oi")');
+ */
+export function createDebouncedCodeSave(delay = 1000) {
+ return codeService.createDebouncedSave(delay);
+}
+
+/**
+ * Remove a entrada de código associada à `key` do `localStorage`.
+ *
+ * @param {string} key - Chave a ser removida
+ * @returns {void}
+ */
+export function clearCodeStorage(key) {
+ codeService.clear(key);
+}
diff --git a/app/src/services/createStorageService.js b/app/src/services/createStorageService.js
new file mode 100644
index 0000000..12b1f86
--- /dev/null
+++ b/app/src/services/createStorageService.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Factory para serviços de persistência no `localStorage`.
+ *
+ * Elimina a duplicação entre `blockstorage` e `codestorage` centralizando o
+ * padrão: `safeLocalGet(validator)` + `debounce(save)`.
+ *
+ * @module services.createStorageService
+ */
+
+import debounce from "lodash.debounce";
+import { safeLocalGet } from "../utils/localStorage";
+
+/**
+ * @typedef {Object} StorageService
+ * @property {(key: string) => T | null} load - Carrega e valida valor do localStorage
+ * @property {(key: string, value: T) => void} save - Persiste valor ou remove chave se inválido
+ * @property {(delay?: number) => Function} createDebouncedSave - Retorna função de save com debounce
+ * @property {(key: string) => void} clear - Remove a chave do localStorage
+ * @template T
+ */
+
+/**
+ * Cria um serviço de armazenamento parametrizado por validador.
+ *
+ * @template T
+ * @param {object} options
+ * @param {(value: unknown) => boolean} options.validator
+ * Função que valida o valor lido do localStorage antes de o devolver.
+ * @param {(value: T) => boolean} [options.isEmpty]
+ * Predicado que determina se um valor válido deve ser tratado como "vazio"
+ * e portanto removido em vez de salvo. Por padrão nenhum valor é considerado vazio.
+ * @returns {StorageService}
+ *
+ * @example
+ * // Serviço para strings de código (remove se vazio)
+ * const codeService = createStorageService({
+ * validator: (v) => typeof v === "string",
+ * isEmpty: (v) => v.trim() === "",
+ * });
+ *
+ * @example
+ * // Serviço para objectos workspace Blockly
+ * const workspaceService = createStorageService({
+ * validator: (v) => v != null && Array.isArray(v?.blocks?.blocks),
+ * });
+ */
+export function createStorageService({ validator, isEmpty = () => false }) {
+ function load(key) {
+ return safeLocalGet(key, validator);
+ }
+
+ function save(key, value) {
+ if (!validator(value) || isEmpty(value)) {
+ localStorage.removeItem(key);
+ return;
+ }
+ localStorage.setItem(key, JSON.stringify(value));
+ }
+
+ function createDebouncedSave(delay = 1000) {
+ return debounce(save, delay, { leading: false, trailing: true });
+ }
+
+ function clear(key) {
+ localStorage.removeItem(key);
+ }
+
+ return { load, save, createDebouncedSave, clear };
+}
diff --git a/app/src/services/letramentoStorage.js b/app/src/services/letramentoStorage.js
new file mode 100644
index 0000000..70df299
--- /dev/null
+++ b/app/src/services/letramentoStorage.js
@@ -0,0 +1,256 @@
+/**
+ * @fileoverview Utilitários para persistência de progresso de atividades de letramento no `localStorage`.
+ *
+ * Este módulo fornece helpers seguros e convenientes para carregar, salvar
+ * e gerenciar o progresso das atividades de letramento digital, incluindo
+ * controle de fases completadas por categoria.
+ *
+ * Convenções:
+ * - Padrão de chave para progresso: `letramento:progress:{atividadeId}`
+ * - Padrão de chave para fases completadas: `letramento:completed-phases:{categoria}`
+ *
+ * Schema de progresso por atividade:
+ * ```
+ * {
+ * atividadeId: string,
+ * status: 'not-started' | 'started' | 'running' | 'success' | 'failure' | 'completed',
+ * attempts: number,
+ * score: number | null,
+ * startedAt: string | null, // ISO 8601
+ * completedAt: string | null, // ISO 8601
+ * lastAccess: string, // ISO 8601
+ * }
+ * ```
+ *
+ * Exemplo de uso:
+ * ```js
+ * import { getProgress, saveProgress, getCompletedPhases } from '../services/letramentoStorage';
+ *
+ * const progress = getProgress('mouse-velocidade');
+ * saveProgress('mouse-velocidade', { status: 'completed', score: 100 });
+ * const completed = getCompletedPhases('mouse');
+ * ```
+ *
+ * @module services.letramentoStorage
+ */
+
+import { getAtividade } from '../atividades/letramento/letramentoRegistry';
+import { safeLocalGet } from '../utils/localStorage';
+
+const PREFIX = 'letramento:progress:';
+const COMPLETED_PREFIX = 'letramento:completed-phases:';
+
+/**
+ * Gera a chave de storage para o progresso de uma atividade específica.
+ *
+ * @private
+ * @param {string} atividadeId - Identificador da atividade
+ * @returns {string} Chave formatada para uso no `localStorage`
+ */
+function storageKey(atividadeId) {
+ return `${PREFIX}${atividadeId}`;
+}
+
+/**
+ * Gera a chave de storage para as fases completadas de uma categoria.
+ *
+ * @private
+ * @param {string} categoria - Nome da categoria de atividades
+ * @returns {string} Chave formatada para uso no `localStorage`
+ */
+function completedPhasesKey(categoria) {
+ return `${COMPLETED_PREFIX}${categoria}`;
+}
+
+/**
+ * Obtém a lista de IDs de atividades completadas para uma categoria específica.
+ *
+ * Retorna um array de strings contendo os IDs das atividades que foram
+ * marcadas como completadas nesta categoria. Em caso de erro ou ausência
+ * de dados, retorna um array vazio.
+ *
+ * @param {string} categoria - Nome da categoria (ex: 'mouse', 'teclado')
+ * @returns {string[]} Array com IDs das atividades completadas
+ *
+ * @example
+ * const completed = getCompletedPhases('mouse');
+ * // => ['mouse-velocidade', 'mouse-precisao']
+ */
+export function getCompletedPhases(categoria) {
+ return safeLocalGet(completedPhasesKey(categoria), Array.isArray) ?? [];
+}
+
+/**
+ * Adiciona uma atividade à lista de fases completadas de uma categoria.
+ *
+ * Se a atividade já estiver na lista, não duplica a entrada. A função
+ * sempre retorna a lista atualizada de atividades completadas.
+ *
+ * @param {string} categoria - Nome da categoria da atividade
+ * @param {string} atividadeId - ID da atividade a marcar como completada
+ * @returns {string[]} Array atualizado com IDs das atividades completadas
+ *
+ * @example
+ * addCompletedPhase('mouse', 'mouse-velocidade');
+ * // => ['mouse-velocidade']
+ */
+export function addCompletedPhase(categoria, atividadeId) {
+ const completed = getCompletedPhases(categoria);
+ if (!completed.includes(atividadeId)) {
+ completed.push(atividadeId);
+ try {
+ localStorage.setItem(completedPhasesKey(categoria), JSON.stringify(completed));
+ } catch (e) {
+ console.error('[letramentoStorage] Falha ao salvar fase completada', e);
+ }
+ }
+ return completed;
+}
+
+/**
+ * Remove todas as fases completadas de uma categoria do `localStorage`.
+ *
+ * Útil para resetar o progresso de uma categoria específica ou para
+ * testes/depuração.
+ *
+ * @param {string} categoria - Nome da categoria a limpar
+ * @returns {void}
+ */
+export function clearCompletedPhases(categoria) {
+ localStorage.removeItem(completedPhasesKey(categoria));
+}
+
+/**
+ * Carrega o progresso salvo de uma atividade específica.
+ *
+ * A função tenta ler e fazer parse do progresso salvo. Em caso de valor
+ * ausente, dados corrompidos ou erro de parsing, retorna `null`.
+ *
+ * @param {string} atividadeId - ID da atividade
+ * @returns {Object|null} Objeto com o progresso da atividade ou `null` se ausente/inválido
+ * @returns {string} returns.atividadeId - ID da atividade
+ * @returns {string} returns.status - Status atual ('not-started' | 'started' | 'running' | 'success' | 'failure' | 'completed')
+ * @returns {number} returns.attempts - Número de tentativas realizadas
+ * @returns {number|null} returns.score - Pontuação obtida
+ * @returns {string|null} returns.startedAt - Timestamp ISO 8601 do início
+ * @returns {string|null} returns.completedAt - Timestamp ISO 8601 da conclusão
+ * @returns {string} returns.lastAccess - Timestamp ISO 8601 do último acesso
+ *
+ * @example
+ * const progress = getProgress('mouse-velocidade');
+ * if (progress?.status === 'completed') {
+ * console.log(`Score: ${progress.score}`);
+ * }
+ */
+export function getProgress(atividadeId) {
+ return safeLocalGet(storageKey(atividadeId));
+}
+
+/**
+ * Salva ou atualiza o progresso de uma atividade no `localStorage`.
+ *
+ * Faz merge do progresso existente com as atualizações fornecidas,
+ * automaticamente atualiza o timestamp de `lastAccess`, e quando o status
+ * muda para 'completed' ou 'success', adiciona a atividade à lista de
+ * fases completadas da categoria.
+ *
+ * Se a atividade ainda não tem progresso salvo, inicializa com valores
+ * padrão antes de aplicar as atualizações.
+ *
+ * @param {string} atividadeId - ID da atividade
+ * @param {Object} update - Objeto com campos a atualizar
+ * @param {string} [update.status] - Novo status da atividade
+ * @param {number} [update.attempts] - Atualizar contagem de tentativas
+ * @param {number|null} [update.score] - Pontuação obtida
+ * @param {string|null} [update.startedAt] - Timestamp ISO 8601 do início
+ * @param {string|null} [update.completedAt] - Timestamp ISO 8601 da conclusão
+ * @returns {Object} O objeto de progresso completo após a atualização
+ *
+ * @example
+ * // Marcar como iniciada
+ * saveProgress('mouse-velocidade', {
+ * status: 'started',
+ * startedAt: new Date().toISOString()
+ * });
+ *
+ * // Completar com pontuação
+ * saveProgress('mouse-velocidade', {
+ * status: 'completed',
+ * score: 85,
+ * completedAt: new Date().toISOString()
+ * });
+ */
+export function saveProgress(atividadeId, update) {
+ const existing = getProgress(atividadeId) ?? {
+ atividadeId,
+ status: 'not-started',
+ attempts: 0,
+ score: null,
+ startedAt: null,
+ completedAt: null,
+ };
+
+ const next = {
+ ...existing,
+ ...update,
+ lastAccess: new Date().toISOString(),
+ };
+
+ try {
+ localStorage.setItem(storageKey(atividadeId), JSON.stringify(next));
+
+ // Se a atividade foi completada, adiciona ao array de completadas
+ if ((next.status === 'completed' || next.status === 'success') && existing.status !== next.status) {
+ const atividade = getAtividade(atividadeId);
+ if (atividade?.categoria) {
+ addCompletedPhase(atividade.categoria, atividadeId);
+ }
+ }
+ } catch (e) {
+ console.error('[letramentoStorage] Falha ao salvar progresso', e);
+ }
+
+ return next;
+}
+
+/**
+ * Obtém o progresso de todas as atividades salvas no `localStorage`.
+ *
+ * Itera por todas as chaves do `localStorage` que seguem o padrão
+ * `letramento:progress:*` e retorna um objeto mapeando IDs de atividades
+ * para seus respectivos progressos.
+ *
+ * @returns {Object.} Objeto com progressos indexados por atividadeId
+ *
+ * @example
+ * const allProgress = getAllProgress();
+ * // => {
+ * // 'mouse-velocidade': { status: 'completed', score: 100, ... },
+ * // 'mouse-precisao': { status: 'started', score: null, ... }
+ * // }
+ */
+export function getAllProgress() {
+ const result = {};
+ for (let i = 0; i < localStorage.length; i++) {
+ const k = localStorage.key(i);
+ if (k && k.startsWith(PREFIX)) {
+ const id = k.slice(PREFIX.length);
+ result[id] = getProgress(id);
+ }
+ }
+ return result;
+}
+
+/**
+ * Remove o progresso salvo de uma atividade específica do `localStorage`.
+ *
+ * Útil para resetar o progresso individual de uma atividade ou para
+ * testes/depuração. Não remove a atividade da lista de fases completadas
+ * da categoria.
+ *
+ * @param {string} atividadeId - ID da atividade a limpar
+ * @returns {void}
+ */
+export function clearProgress(atividadeId) {
+ localStorage.removeItem(storageKey(atividadeId));
+}
diff --git a/app/src/shared/BaseGameScene.js b/app/src/shared/BaseGameScene.js
new file mode 100644
index 0000000..e986729
--- /dev/null
+++ b/app/src/shared/BaseGameScene.js
@@ -0,0 +1,311 @@
+/**
+ * @fileoverview Classe base para cenas de jogos Phaser na plataforma.
+ *
+ * `BaseGameScene` provê utilitários comuns: gerenciamento do `GameInterpreter`,
+ * controle de execução, reprodução de áudio global, highlight de blocos e
+ * integração com o `gameEventBus`.
+ *
+ * Subclasses devem implementar handlers específicos e sobrescrever hooks como
+ * `onInit`, `onBeforeRun`, `onSuccess`, `onFailure` quando necessário.
+ *
+ * @module shared.BaseGameScene
+ */
+
+import Phaser from "phaser";
+import { setupGameController } from "./gameController";
+import { gameEventBus } from "../utils/gameEvents";
+import { GameInterpreter } from "../interpreters/GameInterpreter";
+
+// IMPORTAÇÃO DOS SONS GLOBAIS
+import globalWinSound from "./../assets/win.mp3";
+import globalFailSound from "./../assets/fail.mp3";
+
+const GLOBAL_AUDIO = {
+ WIN: "global_win",
+ FAIL: "global_fail",
+};
+
+export class BaseGameScene extends Phaser.Scene {
+ constructor(key) {
+ super(key);
+ this.historico = [];
+ this.isRunning = false;
+ this.workspace = null;
+ this._validationTimer = null; // Referência ao timer de validação pendente
+ }
+
+ /**
+ * Inicializa as configurações da cena.
+ *
+ * @param {Object} data - Dados passados à cena via `this.scene.start(key, data)`
+ * @returns {void}
+ */
+ init(data) {
+ const registry = this.registry.values;
+
+ // Prioridade: Dados passados direto > Dados do Registry > Objeto Vazio
+ this.configFase = data.configFase || registry.configFase || {};
+ this.gameConfig = data.gameConfig || registry.gameConfig || {};
+ this.customFailureHandler =
+ data.customFailureHandler || registry.customFailureHandler;
+
+ const delay = data.stepDelay || registry.stepDelay || 50;
+
+ this.gameInterpreter = new GameInterpreter({
+ stepDelay: delay,
+ pauseExec: true,
+ });
+
+ this.historico = [];
+ this.isRunning = false;
+
+ if (this.onInit) this.onInit(data);
+ }
+
+ /**
+ * Preload de assets globais (sons compartilhados entre jogos).
+ * @returns {void}
+ */
+ preloadGlobalAssets() {
+ this.load.audio(GLOBAL_AUDIO.WIN, globalWinSound);
+ this.load.audio(GLOBAL_AUDIO.FAIL, globalFailSound);
+ }
+
+ /**
+ * Pausa assíncrona em segundos, útil para fluxos de demonstração.
+ * @param {number} segundos - Tempo em segundos para aguardar
+ * @returns {Promise}
+ */
+ async aguardar(segundos) {
+ return new Promise((resolve) => {
+ this.time.delayedCall(segundos * 1000, resolve);
+ });
+ }
+
+ /**
+ * Solicita highlight de um bloco no workspace (se existir).
+ * @param {string|null} id - ID do bloco a ser destacado ou `null` para limpar
+ * @returns {Promise}
+ */
+ highlightBlock(id) {
+ if (this.workspace) {
+ this.workspace.highlightBlock(id);
+ }
+ return Promise.resolve();
+ }
+
+ /**
+ * Toca um som global identificado pela chave.
+ * @param {string} key - Chave do som (ex: GLOBAL_AUDIO.WIN)
+ * @param {Object} [config] - Configurações de reprodução (volume, loop, etc.)
+ * @returns {void}
+ */
+ playAudio(key, config = {}) {
+ try {
+ this.sound.play(key, config);
+ } catch (error) {
+ console.warn(`Falha ao tocar audio '${key}':`, error);
+ }
+ }
+
+ /**
+ * Para reprodução do som associado à `key`.
+ * @param {string} key - Chave do som
+ * @returns {void}
+ */
+ stopAudio(key) {
+ try {
+ this.sound.stopByKey(key);
+ } catch (error) {
+ console.warn(`Falha ao parar audio '${key}':`, error);
+ }
+ }
+
+ /**
+ * Configura handlers padrão de execução/reset/validação usando `setupGameController`.
+ *
+ * @param {Function} apiFactory - Factory que recebe `this` e retorna API usada pelo interpreter
+ * @param {Function} validatorFunc - Função de validação que recebe (historico, configFase, gameConfig)
+ * @returns {void}
+ */
+ setupStandardController(apiFactory, validatorFunc) {
+ const executeHandler = async (codigo, workspace) => {
+ if (this.sound.context.state === "suspended") {
+ await this.sound.context.resume();
+ }
+
+ // PROTEÇÃO: Forçar cleanup se execução anterior ainda ativa
+ if (this.isRunning || this.gameInterpreter.running) {
+ console.warn('[BaseGameScene] Nova execução durante execução ativa, forçando cleanup');
+ this.cleanupExecution();
+ await new Promise(resolve => setTimeout(resolve, 50));
+ }
+
+ // Armazena o código para uso posterior pelos validators (ex: Aspirador Fase 5)
+ this.currentCode = codigo;
+
+ this.handleExecutionStart(workspace);
+
+ // Validação de Estrutura (Regex)
+ if (this.configFase.validationRegex) {
+ if (!this.checkRegex(codigo)) {
+ this.handleFailure(
+ this.gameConfig.mensagens?.erroEstrutura ||
+ "Erro estrutural no código.",
+ );
+ return;
+ }
+ }
+
+ try {
+ if (this.onBeforeRun) await this.onBeforeRun();
+
+ const timeoutMs = (this.configFase.timeout || 15) * 1000;
+
+ const executionResult = await Promise.race([
+ this.gameInterpreter.executeCode(codigo, apiFactory(this)),
+ new Promise((_, reject) =>
+ this.time.delayedCall(timeoutMs, () =>
+ reject(new Error("TIMEOUT")),
+ ),
+ ),
+ ]);
+
+ // BUG FIX: Não validar se o usuário interrompeu a execução.
+ // Antes, a validação era agendada mesmo quando parada, e disparava
+ // 1s depois sobre uma nova execução já em andamento — causando falha falsa.
+ // Only abort validation when the *user* explicitly stopped execution.
+ // 'stopped' means the game itself stopped the interpreter (e.g. Aspirador
+ // calling stopInternal() after cleaning all dirt) — validation must still run.
+ if (executionResult === 'stopped_by_user') {
+ this.isRunning = false;
+ return;
+ }
+
+ // Agendar validação com delay configurável (1000ms por padrão, 0 em testes)
+ const validationDelay =
+ this.configFase && this.configFase.delayValidacao !== undefined
+ ? this.configFase.delayValidacao
+ : 1000;
+
+ if (validationDelay === 0) {
+ // Executar imediatamente para testes
+ this.handleValidation(validatorFunc);
+ } else {
+ // Agendar com delay para produção, guardando referência para cancelamento
+ this._validationTimer = this.time.delayedCall(validationDelay, () => {
+ this._validationTimer = null;
+ this.handleValidation(validatorFunc);
+ });
+ }
+ } catch (error) {
+ console.error("Erro durante a execução:", error);
+ const msgErro =
+ error.message === "TIMEOUT"
+ ? "Tempo limite de execução excedido."
+ : "Erro inesperado na execução do código.";
+ this.handleFailure(msgErro);
+ }
+ };
+
+ const resetHandler = () => {
+ this.cleanupExecution();
+ if (this.onReset) this.onReset();
+ };
+
+ setupGameController(this, {
+ onExecuteCode: executeHandler,
+ onReset: resetHandler,
+ onCheckSuccess: () => {},
+ });
+ }
+
+ /**
+ * Verifica o código com a regex configurada na fase (se houver).
+ * @param {string} codigo - Código fonte a validar
+ * @returns {boolean}
+ */
+ checkRegex(codigo) {
+ try {
+ const pattern = this.configFase.validationRegex;
+ if (!pattern) return true;
+ const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
+ return regex.test(codigo);
+ } catch (e) {
+ console.warn("Expressão regular inválida:", e);
+ return true;
+ }
+ }
+
+ /**
+ * Inicializa estado antes de executar código: reseta histórico e marca execução ativa.
+ * @param {*} workspace - Referência ao workspace Blockly/Editor usado na cena
+ * @returns {void}
+ */
+ handleExecutionStart(workspace) {
+ this.workspace = workspace;
+ this.historico = [];
+ this.isRunning = true;
+ this.highlightBlock(null);
+ }
+
+ /**
+ * Executa a validação usando o validatorFunc e dispara eventos/áudio conforme resultado.
+ * Os hooks `onSuccess` e `onFailure` podem ser async: o evento React só é emitido
+ * após a Promise resolver, permitindo animações antes do modal aparecer.
+ * @param {Function} validatorFunc - Função que retorna { success: boolean, reason?: string }
+ * @returns {Promise}
+ */
+ async handleValidation(validatorFunc) {
+ if (!this.isRunning) return;
+
+ const resultado = validatorFunc(
+ this.historico,
+ this.configFase,
+ this.gameConfig,
+ );
+
+ this.highlightBlock(null);
+
+ if (resultado.success) {
+ this.playAudio(GLOBAL_AUDIO.WIN);
+ if (this.onSuccess) await this.onSuccess();
+ gameEventBus.gameSuccess();
+ } else {
+ this.handleFailure(resultado.reason);
+ }
+ this.isRunning = false;
+ }
+
+ /**
+ * Tratamento padrão de falha: reproduz áudio, aguarda onFailure (pode ser async)
+ * e emite evento global após a animação de falha terminar.
+ * @param {string} reason - Mensagem/razão da falha
+ * @returns {Promise}
+ */
+ async handleFailure(reason) {
+ this.playAudio(GLOBAL_AUDIO.FAIL);
+
+ if (this.onFailure) await this.onFailure();
+ if (this.customFailureHandler) this.customFailureHandler(reason);
+
+ gameEventBus.gameFailure(reason);
+ this.isRunning = false;
+ }
+
+ /**
+ * Limpa estado de execução (stop interpreter, limpa highlights, reseta flags)
+ * @returns {void}
+ */
+ cleanupExecution() {
+ // Cancelar timer de validação pendente para evitar validação stale
+ if (this._validationTimer) {
+ this._validationTimer.remove();
+ this._validationTimer = null;
+ }
+ this.gameInterpreter.stop();
+ this.highlightBlock(null);
+ this.isRunning = false;
+ this.historico = [];
+ }
+}
diff --git a/app/src/shared/BaseGameValidator.js b/app/src/shared/BaseGameValidator.js
new file mode 100644
index 0000000..f9577c7
--- /dev/null
+++ b/app/src/shared/BaseGameValidator.js
@@ -0,0 +1,123 @@
+/**
+ * @fileoverview Validações base para as várias fases dos jogos.
+ *
+ * `BaseGameValidator` implementa checks genéricos (sanity, sequência) e um
+ * contrato para validações específicas de fase via `validatePhase` que deve
+ * ser implementado por validadores concretos de cada jogo.
+ *
+ * @module shared.BaseGameValidator
+ */
+
+export class BaseGameValidator {
+ /**
+ * Valida a solução do aluno aplicando checks genéricos e delegando para
+ * `validatePhase` para validações específicas do jogo.
+ *
+ * @param {Array} history - Histórico de ações do aluno
+ * @param {Object} configFase - Configuração da fase atual
+ * @param {Object} gameConfig - Configuração global do jogo
+ * @param {Object} [sceneRef] - Referência à cena (opcional)
+ * @returns {{success: boolean, reason?: string}}
+ */
+ validate(history, configFase, gameConfig, sceneRef) {
+ // 1. Fail-Safe Técnico: A config da fase chegou?
+ if (!configFase || Object.keys(configFase).length === 0) {
+ console.error("Validação falhou: Configuração da fase não encontrada.");
+ return this.failure("Erro Técnico: Fase não configurada.");
+ }
+
+ // 2. Sanity Check: O histórico está vazio? (Genérico para qualquer jogo)
+ const sanityCheck = this.checkSanity(history, gameConfig);
+ if (!sanityCheck.success) return sanityCheck;
+
+ // 3. Validação de Sequência (Opcional, ativado via config)
+ // Muitos jogos usam "siga estes passos exatos", então vale manter na base.
+ if (configFase.expectedSequence) {
+ const sequenceCheck = this.checkSequence(
+ history,
+ configFase.expectedSequence,
+ gameConfig,
+ );
+ if (!sequenceCheck.success) return sequenceCheck;
+ }
+
+ // 4. Validação Específica (Polimorfismo)
+ // Aqui entra a lógica de Negócio do Jogo (Limites, Regras, etc)
+ return this.validatePhase(history, configFase, gameConfig, sceneRef);
+ }
+
+ /**
+ * Validação específica de fase a ser implementada por cada jogo.
+ * Deve retornar um objeto `{ success: boolean, reason?: string }`.
+ *
+ * @abstract
+ * @param {Array} history
+ * @param {Object} configFase
+ * @param {Object} gameConfig
+ * @param {Object} [sceneRef] - Referência à cena (opcional)
+ * @returns {{success: boolean, reason?: string}}
+ */
+ validatePhase(history, configFase, gameConfig, sceneRef) {
+ console.warn("BaseGameValidator: validatePhase não implementado.");
+ return this.success();
+ }
+
+ // --- Verificações Genéricas ---
+
+ /**
+ * Verifica se o histórico contém movimentos.
+ * @param {Array} history
+ * @param {Object} gameConfig
+ * @returns {{success: boolean, reason?: string}}
+ */
+ checkSanity(history, gameConfig) {
+ if (!history || history.length === 0) {
+ return this.failure(
+ gameConfig?.mensagens?.semMovimento || "Nenhum movimento detectado.",
+ );
+ }
+ return this.success();
+ }
+
+ /**
+ * Compara o `history` com a sequência esperada definida na config.
+ * @param {Array} history
+ * @param {Array} expected
+ * @param {Object} gameConfig
+ * @returns {{success: boolean, reason?: string}}
+ */
+ checkSequence(history, expected, gameConfig) {
+ // Compara se o histórico bate com a sequência esperada, propriedade por propriedade
+ const match = expected.every((step, i) => {
+ const action = history[i];
+ if (!action) return false;
+ // Verifica se todas as chaves do passo esperado estão presentes e iguais na ação
+ return Object.keys(step).every((key) => step[key] === action[key]);
+ });
+
+ return match
+ ? this.success()
+ : this.failure(
+ gameConfig?.mensagens?.caminhoErrado || "Sequência incorreta.",
+ );
+ }
+
+ // --- Helpers de Retorno (Syntax Sugar) ---
+
+ /**
+ * Helper que representa sucesso de validação.
+ * @returns {{success: boolean}}
+ */
+ success() {
+ return { success: true };
+ }
+
+ /**
+ * Helper que representa falha de validação.
+ * @param {string} reason - Mensagem explicando a falha
+ * @returns {{success: boolean, reason: string}}
+ */
+ failure(reason) {
+ return { success: false, reason };
+ }
+}
diff --git a/app/src/shared/__tests__/BaseGameScene.test.js b/app/src/shared/__tests__/BaseGameScene.test.js
new file mode 100644
index 0000000..f76b42e
--- /dev/null
+++ b/app/src/shared/__tests__/BaseGameScene.test.js
@@ -0,0 +1,709 @@
+/**
+ * @fileoverview Utility module for BaseGameScene.test.js
+ *
+ * @module shared.__tests__.BaseGameScene.test
+ */
+
+import { describe, it, expect, beforeEach, vi } from "vitest";
+import { BaseGameScene } from "../BaseGameScene";
+import { gameEventBus } from "../../utils/gameEvents";
+import { GameInterpreter } from "../../interpreters/GameInterpreter";
+
+// Mock dos módulos externos
+vi.mock("../../utils/gameEvents", () => ({
+ gameEventBus: {
+ gameSuccess: vi.fn(),
+ gameFailure: vi.fn(),
+ },
+}));
+
+vi.mock("../../interpreters/GameInterpreter", () => ({
+ GameInterpreter: class {
+ constructor(config) {
+ this.config = config;
+ this.executeCode = vi.fn();
+ this.stop = vi.fn();
+ }
+ },
+}));
+
+vi.mock("../gameController", () => ({
+ setupGameController: vi.fn(),
+}));
+
+// Mock do Phaser
+vi.mock("phaser", () => ({
+ default: {
+ Scene: class {
+ constructor(key) {
+ this.key = key;
+ this.time = {
+ delayedCall: vi.fn((delay, callback) => callback()),
+ };
+ this.sound = {
+ get: vi.fn(),
+ play: vi.fn(),
+ stop: vi.fn(),
+ stopByKey: vi.fn(),
+ context: {
+ state: "running",
+ resume: vi.fn().mockResolvedValue(),
+ },
+ };
+ this.registry = {
+ values: {},
+ };
+ }
+ },
+ },
+}));
+
+describe("BaseGameScene", () => {
+ let scene;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ scene = new BaseGameScene("test-scene");
+ });
+
+ describe("Constructor", () => {
+ it("deve inicializar com valores padrão", () => {
+ expect(scene.historico).toEqual([]);
+ expect(scene.isRunning).toBe(false);
+ expect(scene.workspace).toBeNull();
+ });
+
+ it("deve armazenar a chave da cena", () => {
+ expect(scene.key).toBe("test-scene");
+ });
+ });
+
+ describe("init()", () => {
+ it("deve inicializar com configurações vazias", () => {
+ scene.init({});
+
+ expect(scene.configFase).toEqual({});
+ expect(scene.gameConfig).toEqual({});
+ expect(scene.historico).toEqual([]);
+ expect(scene.isRunning).toBe(false);
+ });
+
+ it("deve armazenar configurações fornecidas", () => {
+ const data = {
+ configFase: { nivel: 1 },
+ gameConfig: { pontos: 100 },
+ customFailureHandler: function () {},
+ stepDelay: 100,
+ };
+
+ scene.init(data);
+
+ expect(scene.configFase).toEqual({ nivel: 1 });
+ expect(scene.gameConfig).toEqual({ pontos: 100 });
+ expect(scene.customFailureHandler).toBe(data.customFailureHandler);
+ });
+
+ it("deve usar dados do registry quando não passados diretamente", () => {
+ scene.registry.values = {
+ configFase: { nivel: 2 },
+ gameConfig: { pontos: 200 },
+ stepDelay: 75,
+ };
+
+ scene.init({});
+
+ expect(scene.configFase).toEqual({ nivel: 2 });
+ expect(scene.gameConfig).toEqual({ pontos: 200 });
+ expect(scene.gameInterpreter.config.stepDelay).toBe(75);
+ });
+
+ it("deve priorizar dados passados direto sobre registry", () => {
+ scene.registry.values = {
+ configFase: { nivel: 2 },
+ gameConfig: { pontos: 200 },
+ stepDelay: 75,
+ };
+
+ const data = {
+ configFase: { nivel: 1 },
+ gameConfig: { pontos: 100 },
+ stepDelay: 100,
+ };
+
+ scene.init(data);
+
+ expect(scene.configFase).toEqual({ nivel: 1 });
+ expect(scene.gameConfig).toEqual({ pontos: 100 });
+ expect(scene.gameInterpreter.config.stepDelay).toBe(100);
+ });
+
+ it("deve criar uma instância de GameInterpreter", () => {
+ scene.init({ stepDelay: 200 });
+
+ expect(scene.gameInterpreter).toBeDefined();
+ expect(scene.gameInterpreter.config).toEqual({
+ stepDelay: 200,
+ pauseExec: true,
+ });
+ });
+
+ it("deve usar stepDelay padrão de 50", () => {
+ scene.init({});
+
+ expect(scene.gameInterpreter.config).toEqual({
+ stepDelay: 50,
+ pauseExec: true,
+ });
+ });
+
+ it("deve chamar onInit se definido", () => {
+ scene.onInit = function (data) {};
+ scene.onInit = vi.fn(scene.onInit);
+ const data = { configFase: {} };
+
+ scene.init(data);
+
+ expect(scene.onInit).toHaveBeenCalledWith(data);
+ });
+ });
+
+ describe("aguardar()", () => {
+ it("deve retornar uma Promise", () => {
+ const result = scene.aguardar(1);
+ expect(result).toBeInstanceOf(Promise);
+ });
+
+ it("deve chamar time.delayedCall com tempo correto", async () => {
+ scene.aguardar(2);
+
+ expect(scene.time.delayedCall).toHaveBeenCalledWith(
+ 2000,
+ expect.any(Function),
+ );
+ });
+ });
+
+ describe("highlightBlock()", () => {
+ it("deve chamar workspace.highlightBlock quando workspace existe", () => {
+ scene.workspace = {
+ highlightBlock: vi.fn(),
+ };
+
+ scene.highlightBlock("block-123");
+
+ expect(scene.workspace.highlightBlock).toHaveBeenCalledWith("block-123");
+ });
+
+ it("não deve lançar erro quando workspace é null", () => {
+ scene.workspace = null;
+
+ expect(() => scene.highlightBlock("block-123")).not.toThrow();
+ });
+ });
+
+ describe("playAudio()", () => {
+ it("deve reproduzir áudio", () => {
+ scene.playAudio("som-teste", { volume: 0.5 });
+
+ expect(scene.sound.play).toHaveBeenCalledWith("som-teste", {
+ volume: 0.5,
+ });
+ });
+
+ it("deve aceitar config vazia", () => {
+ scene.playAudio("som-teste");
+
+ expect(scene.sound.play).toHaveBeenCalledWith("som-teste", {});
+ });
+
+ it("não deve lançar erro se falhar", () => {
+ scene.sound.play.mockImplementation(() => {
+ throw new Error("Audio error");
+ });
+
+ expect(() => scene.playAudio("som-teste")).not.toThrow();
+ });
+ });
+
+ describe("stopAudio()", () => {
+ it("deve chamar stopByKey", () => {
+ scene.stopAudio("loop");
+ expect(scene.sound.stopByKey).toHaveBeenCalledWith("loop");
+ });
+
+ it("não deve lançar erro se falhar", () => {
+ scene.sound.stopByKey.mockImplementation(() => {
+ throw new Error("Stop audio error");
+ });
+
+ expect(() => scene.stopAudio("ghost")).not.toThrow();
+ });
+ });
+
+ describe("checkRegex()", () => {
+ it("deve retornar true quando não há regex configurada", () => {
+ scene.configFase = {};
+
+ const result = scene.checkRegex("qualquer codigo");
+
+ expect(result).toBe(true);
+ });
+
+ it("deve validar código com regex string", () => {
+ scene.configFase = {
+ validationRegex: "function\\s+\\w+",
+ };
+
+ expect(scene.checkRegex("function teste() {}")).toBe(true);
+ expect(scene.checkRegex("const x = 5")).toBe(false);
+ });
+
+ it("deve validar código com objeto RegExp", () => {
+ scene.configFase = {
+ validationRegex: /function\s+\w+/,
+ };
+
+ expect(scene.checkRegex("function teste() {}")).toBe(true);
+ expect(scene.checkRegex("const x = 5")).toBe(false);
+ });
+
+ it("deve retornar true em caso de regex inválida", () => {
+ scene.configFase = {
+ validationRegex: "[invalid regex(",
+ };
+
+ const result = scene.checkRegex("codigo qualquer");
+
+ expect(result).toBe(true);
+ });
+ });
+
+ describe("handleExecutionStart()", () => {
+ it("deve resetar estado de execução", () => {
+ const workspace = { highlightBlock: vi.fn() };
+ scene.historico = ["item1", "item2"];
+ scene.isRunning = false;
+
+ scene.handleExecutionStart(workspace);
+
+ expect(scene.workspace).toBe(workspace);
+ expect(scene.historico).toEqual([]);
+ expect(scene.isRunning).toBe(true);
+ });
+
+ it("deve limpar highlight", () => {
+ const workspace = { highlightBlock: vi.fn() };
+
+ scene.handleExecutionStart(workspace);
+
+ expect(workspace.highlightBlock).toHaveBeenCalledWith(null);
+ });
+ });
+
+ describe("BaseGameScene (Assets Globais)", () => {
+ let scene;
+
+ beforeEach(() => {
+ vi.useFakeTimers();
+ scene = new BaseGameScene("TestScene");
+
+ // Mock do Loader do Phaser
+ scene.load = {
+ audio: vi.fn(),
+ };
+
+ // Mock do Sound Manager
+ scene.sound = {
+ get: vi.fn().mockReturnValue(true),
+ play: vi.fn(),
+ stop: vi.fn(),
+ };
+
+ scene.init({});
+ });
+
+ it("preloadGlobalAssets: deve carregar os audios padrão de win/fail", () => {
+ scene.preloadGlobalAssets();
+
+ // Verifica se carregou com as chaves reservadas
+ expect(scene.load.audio).toHaveBeenCalledWith(
+ "global_win",
+ expect.stringContaining("win.mp3"),
+ );
+ expect(scene.load.audio).toHaveBeenCalledWith(
+ "global_fail",
+ expect.stringContaining("fail.mp3"),
+ );
+ });
+ });
+
+ describe("handleValidation()", () => {
+ beforeEach(() => {
+ scene.init({});
+ scene.workspace = { highlightBlock: vi.fn() };
+ scene.isRunning = true;
+ });
+
+ it("não deve validar se não está executando", () => {
+ scene.isRunning = false;
+ const validator = function () {};
+ const validatorSpy = vi.fn(validator);
+
+ scene.handleValidation(validatorSpy);
+
+ expect(validatorSpy).not.toHaveBeenCalled();
+ });
+
+ it("deve chamar gameSuccess quando validação passa", async () => {
+ scene.isRunning = true; // Necessário para handleValidation executar
+ scene.onSuccess = function () {};
+ scene.onSuccess = vi.fn(scene.onSuccess);
+ const validator = function () {
+ return { success: true };
+ };
+ const validatorSpy = vi.fn(validator);
+
+ await scene.handleValidation(validatorSpy);
+
+ expect(validatorSpy).toHaveBeenCalledWith(
+ scene.historico,
+ scene.configFase,
+ scene.gameConfig,
+ );
+ expect(scene.onSuccess).toHaveBeenCalled();
+ expect(gameEventBus.gameSuccess).toHaveBeenCalled();
+ expect(scene.isRunning).toBe(false);
+ });
+
+ it("deve chamar handleFailure quando validação falha", () => {
+ scene.handleFailure = function () {};
+ scene.handleFailure = vi.fn(scene.handleFailure);
+ const validator = function () {
+ return {
+ success: false,
+ reason: "Teste falhou",
+ };
+ };
+ const validatorSpy = vi.fn(validator);
+
+ scene.handleValidation(validatorSpy);
+
+ expect(scene.handleFailure).toHaveBeenCalledWith("Teste falhou");
+ });
+
+ it("deve limpar highlight após validação", () => {
+ const validator = function () {
+ return { success: true };
+ };
+ const validatorSpy = vi.fn(validator);
+
+ scene.handleValidation(validatorSpy);
+
+ expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);
+ });
+
+ it("deve tocar som global_win automaticamente no sucesso", () => {
+ // Setup
+ scene.init({});
+ scene.isRunning = true;
+ scene.playAudio = vi.fn(); // Espião no método playAudio
+
+ const validator = () => ({ success: true });
+
+ scene.handleValidation(validator);
+
+ expect(scene.playAudio).toHaveBeenCalledWith("global_win");
+ expect(gameEventBus.gameSuccess).toHaveBeenCalled();
+ });
+
+ it("deve tocar som global_fail automaticamente na falha", () => {
+ // Setup
+ scene.init({});
+ scene.isRunning = true;
+ scene.playAudio = vi.fn(); // Espião no método playAudio
+
+ const validator = () => ({ success: false, reason: "Erro" });
+
+ scene.handleValidation(validator);
+
+ expect(scene.playAudio).toHaveBeenCalledWith("global_fail");
+ // Pequeno avanço de tempo pois o failure tem delay
+ vi.advanceTimersByTime(100);
+ expect(gameEventBus.gameFailure).toHaveBeenCalled();
+ });
+ });
+
+ describe("handleFailure()", () => {
+ beforeEach(() => {
+ scene.init({});
+ scene.isRunning = true;
+ });
+
+ it("deve chamar onFailure se definido", () => {
+ scene.onFailure = function () {};
+ scene.onFailure = vi.fn(scene.onFailure);
+
+ scene.handleFailure("Erro teste");
+
+ expect(scene.onFailure).toHaveBeenCalled();
+ });
+
+ it("deve chamar customFailureHandler se definido", () => {
+ const customHandler = function (reason) {};
+ scene.init({ customFailureHandler: vi.fn(customHandler) });
+
+ scene.handleFailure("Erro teste");
+
+ expect(scene.customFailureHandler).toHaveBeenCalledWith("Erro teste");
+ });
+
+ it("deve chamar gameEventBus.gameFailure", () => {
+ scene.handleFailure("Erro teste");
+
+ expect(gameEventBus.gameFailure).toHaveBeenCalled();
+ });
+
+ it("deve definir isRunning como false", () => {
+ scene.handleFailure("Erro teste");
+
+ expect(scene.isRunning).toBe(false);
+ });
+ });
+
+ describe("cleanupExecution()", () => {
+ beforeEach(() => {
+ scene.init({});
+ scene.workspace = { highlightBlock: vi.fn() };
+ scene.historico = ["item1", "item2"];
+ scene.isRunning = true;
+ });
+
+ it("deve parar o interpretador", () => {
+ scene.cleanupExecution();
+
+ expect(scene.gameInterpreter.stop).toHaveBeenCalled();
+ });
+
+ it("deve limpar highlight", () => {
+ scene.cleanupExecution();
+
+ expect(scene.workspace.highlightBlock).toHaveBeenCalledWith(null);
+ });
+
+ it("deve resetar estado", () => {
+ scene.cleanupExecution();
+
+ expect(scene.isRunning).toBe(false);
+ expect(scene.historico).toEqual([]);
+ });
+ });
+
+ describe("setupStandardController()", () => {
+ let apiFactory;
+ let validatorFunc;
+ let setupGameController;
+
+ beforeEach(async () => {
+ const { setupGameController: mockSetup } =
+ await import("../gameController");
+ setupGameController = mockSetup;
+
+ scene.init({
+ configFase: { timeout: 5 },
+ });
+
+ apiFactory = function () {
+ return {
+ funcao1: vi.fn(),
+ funcao2: vi.fn(),
+ };
+ };
+ apiFactory = vi.fn(apiFactory);
+
+ validatorFunc = function () {
+ return { success: true };
+ };
+ validatorFunc = vi.fn(validatorFunc);
+
+ scene.setupStandardController(apiFactory, validatorFunc);
+ });
+
+ it("deve chamar setupGameController", () => {
+ expect(setupGameController).toHaveBeenCalledWith(
+ scene,
+ expect.objectContaining({
+ onExecuteCode: expect.any(Function),
+ onReset: expect.any(Function),
+ onCheckSuccess: expect.any(Function),
+ }),
+ );
+ });
+
+ it("handler de reset deve chamar cleanupExecution", () => {
+ scene.cleanupExecution = function () {};
+ scene.cleanupExecution = vi.fn(scene.cleanupExecution);
+
+ const callArgs = setupGameController.mock.calls[0][1];
+ callArgs.onReset();
+
+ expect(scene.cleanupExecution).toHaveBeenCalled();
+ });
+
+ it("handler de reset deve chamar onReset se definido", () => {
+ scene.onReset = function () {};
+ scene.onReset = vi.fn(scene.onReset);
+
+ const callArgs = setupGameController.mock.calls[0][1];
+ callArgs.onReset();
+
+ expect(scene.onReset).toHaveBeenCalled();
+ });
+ });
+
+ describe("setupStandardController - executeHandler", () => {
+ let apiFactory;
+ let validatorFunc;
+ let executeHandler;
+
+ beforeEach(async () => {
+ const { setupGameController } = await import("../gameController");
+
+ scene.init({
+ configFase: {
+ timeout: 5,
+ delayValidacao: 500,
+ },
+ });
+
+ apiFactory = function () {
+ return {};
+ };
+ validatorFunc = function () {
+ return { success: true };
+ };
+
+ scene.setupStandardController(apiFactory, validatorFunc);
+
+ executeHandler = setupGameController.mock.calls[0][1].onExecuteCode;
+ });
+
+ it("deve executar código sem validação regex", async () => {
+ const codigo = 'console.log("teste")';
+ const workspace = { highlightBlock: vi.fn() };
+
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+
+ await executeHandler(codigo, workspace);
+
+ expect(scene.gameInterpreter.executeCode).toHaveBeenCalledWith(
+ codigo,
+ expect.any(Object),
+ );
+ });
+
+ it("deve validar regex antes de executar", async () => {
+ scene.configFase.validationRegex = /function/;
+ scene.handleFailure = function () {};
+ scene.handleFailure = vi.fn(scene.handleFailure);
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("const x = 5", workspace);
+
+ expect(scene.handleFailure).toHaveBeenCalledWith(
+ expect.stringContaining("estrutural"),
+ );
+ expect(scene.gameInterpreter.executeCode).not.toHaveBeenCalled();
+ });
+
+ it("deve chamar onBeforeRun se definido", async () => {
+ scene.onBeforeRun = function () {
+ return Promise.resolve();
+ };
+ scene.onBeforeRun = vi.fn(scene.onBeforeRun);
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ expect(scene.onBeforeRun).toHaveBeenCalled();
+ });
+
+ it("deve tratar timeout de execução", async () => {
+ scene.configFase.timeout = 0.001; // 1ms
+ scene.handleFailure = function () {};
+ scene.handleFailure = vi.fn(scene.handleFailure);
+ scene.gameInterpreter.executeCode.mockImplementation(
+ () => new Promise((resolve) => setTimeout(resolve, 1000)),
+ );
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ expect(scene.handleFailure).toHaveBeenCalledWith(
+ expect.stringContaining("Tempo limite"),
+ );
+ });
+
+ it("deve usar timeout padrão de 15 segundos", async () => {
+ delete scene.configFase.timeout;
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ // Verifica que não deu timeout imediato
+ expect(scene.gameInterpreter.executeCode).toHaveBeenCalled();
+ });
+
+ it("deve tratar erros inesperados", async () => {
+ scene.handleFailure = function () {};
+ scene.handleFailure = vi.fn(scene.handleFailure);
+ scene.gameInterpreter.executeCode.mockRejectedValue(
+ new Error("Erro customizado"),
+ );
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ expect(scene.handleFailure).toHaveBeenCalledWith(
+ expect.stringContaining("inesperado"),
+ );
+ });
+
+ it("deve aguardar delay antes de validar", async () => {
+ scene.configFase.delayValidacao = 1000;
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ expect(scene.time.delayedCall).toHaveBeenCalledWith(
+ 1000,
+ expect.any(Function),
+ );
+ });
+
+ it("deve usar delay padrão de 1000ms", async () => {
+ delete scene.configFase.delayValidacao;
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+ const workspace = { highlightBlock: vi.fn() };
+
+ await executeHandler("codigo", workspace);
+
+ expect(scene.time.delayedCall).toHaveBeenCalledWith(
+ 1000,
+ expect.any(Function),
+ );
+ });
+
+ it("deve armazenar o código em currentCode", async () => {
+ scene.gameInterpreter.executeCode.mockResolvedValue();
+ const workspace = { highlightBlock: vi.fn() };
+ const codigo = "var passos = 1;\nmover();";
+
+ await executeHandler(codigo, workspace);
+
+ expect(scene.currentCode).toBe(codigo);
+ });
+ });
+});
diff --git a/app/src/shared/__tests__/BaseGameValidator.test.js b/app/src/shared/__tests__/BaseGameValidator.test.js
new file mode 100644
index 0000000..69579fc
--- /dev/null
+++ b/app/src/shared/__tests__/BaseGameValidator.test.js
@@ -0,0 +1,120 @@
+/**
+ * @fileoverview Utility module for BaseGameValidator.test.js
+ *
+ * @module shared.__tests__.BaseGameValidator.test
+ */
+
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { BaseGameValidator } from "../BaseGameValidator";
+
+// Subclasse Fake apenas para testar a Base sem depender do MoleMash
+class TestValidator extends BaseGameValidator {
+ validatePhase(_history, _config, _gameConfig) {
+ // Simula uma validação específica que sempre passa se chegar aqui
+ return this.success();
+ }
+}
+
+describe("BaseGameValidator", () => {
+ let validator;
+ const mockGameConfig = {
+ mensagens: {
+ semMovimento: "Nada feito",
+ caminhoErrado: "Caminho ruim",
+ foraTabuleiro: "Saiu da borda",
+ },
+ };
+
+ beforeEach(() => {
+ validator = new TestValidator();
+ });
+
+ describe("1. Fail-Safe (Configuração)", () => {
+ it("deve falhar se a config da fase for undefined", () => {
+ const result = validator.validate([], undefined, mockGameConfig);
+ expect(result.success).toBe(false);
+ expect(result.reason).toContain("Erro Técnico");
+ });
+
+ it("deve falhar se a config da fase for vazia", () => {
+ const result = validator.validate([], {}, mockGameConfig);
+ expect(result.success).toBe(false);
+ expect(result.reason).toContain("Erro Técnico");
+ });
+ });
+
+ describe("2. Sanity Check (Histórico Vazio)", () => {
+ it("deve falhar se o histórico for vazio", () => {
+ const result = validator.validate([], { id: 1 }, mockGameConfig);
+ expect(result.success).toBe(false);
+ expect(result.reason).toBe("Nada feito");
+ });
+
+ it("deve falhar se o histórico for null", () => {
+ const result = validator.validate(null, { id: 1 }, mockGameConfig);
+ expect(result.success).toBe(false);
+ });
+ });
+
+ describe("3. Validação de Sequência (ExpectedSequence)", () => {
+ const configSequencia = {
+ id: 1,
+ expectedSequence: [
+ { l: 1, c: 1 },
+ { l: 1, c: 2 },
+ ],
+ };
+
+ it("deve passar se o histórico bater com a sequência", () => {
+ const history = [
+ { l: 1, c: 1, t: 100 }, // t (tempo) deve ser ignorado na comparação
+ { l: 1, c: 2, t: 200 },
+ ];
+ const result = validator.validate(
+ history,
+ configSequencia,
+ mockGameConfig,
+ );
+ expect(result.success).toBe(true);
+ });
+
+ it("deve falhar se um passo for diferente", () => {
+ const history = [
+ { l: 1, c: 1 },
+ { l: 1, c: 3 }, // Errado
+ ];
+ const result = validator.validate(
+ history,
+ configSequencia,
+ mockGameConfig,
+ );
+ expect(result.success).toBe(false);
+ expect(result.reason).toBe("Caminho ruim");
+ });
+
+ it("deve falhar se o tamanho for menor que a sequência", () => {
+ const history = [{ l: 1, c: 1 }];
+ const result = validator.validate(
+ history,
+ configSequencia,
+ mockGameConfig,
+ );
+ expect(result.success).toBe(false);
+ });
+ });
+
+ describe("4. Delegação (Template Method)", () => {
+ it("deve chamar validatePhase se passar nas validações básicas", () => {
+ // Espiona o método da subclasse
+ const spy = vi.spyOn(validator, "validatePhase");
+
+ const history = [{ x: 1 }];
+ const config = { id: 99 }; // Sem sequence, deve ir pro validatePhase
+
+ const result = validator.validate(history, config, mockGameConfig);
+
+ expect(result.success).toBe(true);
+ expect(spy).toHaveBeenCalledWith(history, config, mockGameConfig, undefined);
+ });
+ });
+});
diff --git a/app/src/shared/gameController.js b/app/src/shared/gameController.js
new file mode 100644
index 0000000..8d3b93e
--- /dev/null
+++ b/app/src/shared/gameController.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Integra eventos globais de UI com handlers de execução do jogo.
+ *
+ * O `gameController` encapsula a associação entre o `gameEventBus` (eventos
+ * globais disparados pela UI/editor) e callbacks da cena (execute, reset,
+ * checkSuccess). Garante também limpeza dos listeners quando a cena é
+ * destruída.
+ *
+ * @module shared.gameController
+ */
+
+import { gameEventBus } from "../utils/gameEvents";
+
+/**
+ * Registra handlers da cena nos eventos globais do editor/jogos.
+ *
+ * Associa os callbacks fornecidos ao `gameEventBus` e garante limpeza
+ * automática quando a cena é destruída.
+ *
+ * @param {Phaser.Scene} scene - Instância da cena do Phaser
+ * @param {Object} handlers - Handlers da cena
+ * @param {Function} handlers.onExecuteCode - Recebe evento com `{ detail: { code, workspace } }`
+ * @param {Function} handlers.onReset - Callback para resetar a cena
+ * @param {Function} handlers.onCheckSuccess - Verifica sucesso da execução
+ * @returns {void}
+ */
+export function setupGameController(
+ scene,
+ { onExecuteCode, onReset, onCheckSuccess },
+) {
+ if (scene.events.listenerCount("destroy") > 0 && scene._cleanupController) {
+ scene.events.removeListener("destroy", scene._cleanupController);
+ }
+
+ const executeCodeHandler = (event) => {
+ onExecuteCode.call(scene, event.detail.code, event.detail.workspace);
+ };
+
+ const resetGameHandler = () => {
+ onReset.call(scene);
+ };
+
+ const stopExecutionHandler = () => {
+ if (scene.gameInterpreter) {
+ scene.gameInterpreter.stop();
+ }
+ if (scene.workspace) {
+ scene.workspace.highlightBlock(null);
+ }
+ };
+
+ gameEventBus.addEventListener("executeCode", executeCodeHandler);
+ gameEventBus.addEventListener("resetGame", resetGameHandler);
+ gameEventBus.addEventListener("stopExecution", stopExecutionHandler);
+
+ const cleanup = () => {
+ gameEventBus.removeEventListener("executeCode", executeCodeHandler);
+ gameEventBus.removeEventListener("resetGame", resetGameHandler);
+ gameEventBus.removeEventListener("stopExecution", stopExecutionHandler);
+ };
+
+ scene._cleanupController = cleanup;
+ scene.events.on("destroy", cleanup);
+
+ gameEventBus.gameReady();
+}
diff --git a/app/src/styles/globals.css b/app/src/styles/globals.css
new file mode 100644
index 0000000..a0bdcb2
--- /dev/null
+++ b/app/src/styles/globals.css
@@ -0,0 +1,313 @@
+@import url("https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Barlow+Semi+Condensed:wght@400;700;900&display=swap");
+
+@import "@fortawesome/fontawesome-free/css/all.css";
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+@tailwind typography;
+
+:root {
+ --brand-primary: #ed1b2f;
+ --brand-secondary: #ed0973;
+ --brand-accent: #b624c0;
+ --brand-light: #ff6b9d;
+ --brand-dark: #c41e3a;
+ --brand-purple-light: #d946ef;
+ --brand-purple-dark: #9333ea;
+
+ /* primary gradient colors used across the app */
+ --primary-gradient-start: #F90527;
+ --primary-gradient-end: #F50C52;
+
+ /* action button green */
+ --action-green: rgb(213 223 80 / 1);
+
+ /* font families for easy global tweaks */
+ --font-sans: "Lato", system-ui, sans-serif;
+ --font-title: "Barlow Semi Condensed", system-ui, sans-serif;
+ --font-mono: "JetBrains Mono", monospace;
+
+ --vh: 1vh;
+ --full-height: 100vh;
+}
+
+/* font helpers using the new variables */
+.font-sans {
+ font-family: var(--font-sans) !important;
+}
+.font-title {
+ font-family: var(--font-title) !important;
+}
+.font-mono {
+ font-family: var(--font-mono) !important;
+}
+
+@supports (height: 100dvh) {
+ :root {
+ --full-height: 100dvh;
+ }
+}
+
+.h-screen-safe {
+ height: var(--full-height);
+ min-height: var(--full-height);
+}
+
+.min-h-screen-safe {
+ min-height: var(--full-height);
+}
+
+@media (max-width: 768px) {
+ .mobile-viewport-fix {
+ height: var(--full-height);
+ max-height: var(--full-height);
+ overflow: hidden;
+ }
+ .mobile-viewport-fix .flex-col {
+ height: 100%;
+ }
+}
+
+@layer components {
+ .glass-panel {
+ @apply bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl shadow-lg;
+ }
+ .game-gradient {
+ @apply bg-gradient-to-br from-red-600 via-pink-600 to-purple-600;
+ }
+ /* Igual ao gradiente do Hero da homepage */
+ .primary-gradient {
+ background: linear-gradient(135deg, var(--primary-gradient-start) 0%, var(--primary-gradient-end) 100%);
+ }
+
+ /* utility for a solid highlight border (bottom) */
+ .border-highlight {
+ border-bottom: 2px solid var(--action-green);
+ }
+
+ /* button using the action green shade */
+ .btn-action-green {
+ @apply bg-green-100 text-black font-medium py-2 px-4 rounded-lg transition-colors duration-200;
+ }
+ .btn-primary {
+ @apply bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200;
+ }
+ .tab-active {
+ @apply bg-gradient-to-r from-red-600 to-pink-600 text-white shadow-inner shadow-black/30;
+ }
+ .tab-inactive {
+ @apply bg-white text-gray-800 hover:bg-gray-100 shadow-md hover:shadow-lg transition-all duration-200;
+ }
+ .header-logo {
+ @apply flex items-center space-x-2 cursor-pointer hover:bg-white/10 px-3 py-2 rounded-lg transition-all duration-200;
+ }
+ .header-logo-icon {
+ @apply text-2xl transition-transform duration-200;
+ }
+ .header-logo-text {
+ @apply text-white font-bold text-lg hidden sm:block;
+ }
+ .game-title-badge {
+ @apply flex items-center space-x-2 bg-white/10 px-4 py-2 rounded-lg;
+ }
+ .phase-indicator {
+ @apply bg-white/20 px-3 py-1 rounded-full;
+ }
+ .phase-current {
+ @apply text-yellow-300 font-bold;
+ }
+ .phase-separator {
+ @apply text-white/60 mx-1;
+ }
+ .phase-total {
+ @apply text-white/80;
+ }
+ .header-glass {
+ @apply bg-white/10 backdrop-blur-sm border-b border-white/20 shadow-lg;
+ }
+ .fade-in {
+ animation: fade-in 300ms ease both;
+ }
+ .slide-in-bottom {
+ animation: slide-in-bottom 300ms ease both;
+ }
+ @keyframes fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+ @keyframes slide-in-bottom {
+ from { opacity: 0; transform: translateY(1rem); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ /* shared cancel icon used in modals, selector, tour */
+ .cancel-icon,
+ .shepherd-cancel-icon {
+ width: 32px !important;
+ height: 32px !important;
+ border-radius: 8px !important;
+ background: #f3f4f6 !important;
+ transition: all 0.2s ease !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ color: #6b7280 !important;
+ }
+ .cancel-icon:hover,
+ .shepherd-cancel-icon:hover {
+ background: #e5e7eb !important;
+ color: #374151 !important;
+ transform: rotate(90deg) !important;
+ }
+}
+
+@layer utilities {
+ .line-clamp-2 {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ }
+ .line-clamp-3 {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ }
+}
+
+/* ===== BLOCKLY CUSTOMIZATIONS ===== */
+.blocklyScrollbarVertical,
+.blocklyScrollbarHorizontal,
+.blocklyToolboxDiv .blocklyScrollbarVertical,
+.blocklyToolboxDiv .blocklyScrollbarHorizontal,
+.blocklyFlyout .blocklyScrollbarVertical,
+.blocklyFlyout .blocklyScrollbarHorizontal,
+.blocklyWorkspace .blocklyScrollbarVertical,
+.blocklyWorkspace .blocklyScrollbarHorizontal,
+.blocklyDiv .blocklyScrollbarVertical,
+.blocklyDiv .blocklyScrollbarHorizontal,
+.blocklyDiv * .blocklyScrollbarVertical,
+.blocklyDiv * .blocklyScrollbarHorizontal,
+.blocklyScrollbarHandle,
+.blocklyScrollbarKnob,
+.blocklyScrollbarBackground,
+div[class*="blockly"] .blocklyScrollbarVertical,
+div[class*="blockly"] .blocklyScrollbarHorizontal,
+svg[class*="blockly"] .blocklyScrollbarVertical,
+svg[class*="blockly"] .blocklyScrollbarHorizontal {
+ display: none !important;
+ visibility: hidden !important;
+ opacity: 0 !important;
+ pointer-events: none !important;
+ width: 0 !important;
+ height: 0 !important;
+}
+
+.blocklyWorkspace,
+.blocklyWorkspace svg,
+.blocklyToolboxDiv,
+.blocklyToolboxDiv *,
+.blocklyFlyout,
+.blocklyFlyout *,
+.blocklyDiv,
+.blocklyDiv *,
+.blocklyMainBackground,
+.blocklyTreeRoot {
+ overflow: hidden !important;
+}
+
+.blocklyDiv {
+ background-color: #f8f9fa;
+ min-height: 200px;
+}
+
+.blocklyDiv .blocklyWorkspace {
+ background-color: #f8f9fa !important;
+}
+
+.blocklyDiv .blocklyFlyout,
+.blocklyDiv .blocklyToolboxDiv {
+ transition: opacity 0.2s ease-in-out;
+}
+
+.blocklyDiv:empty {
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
+ background-size: 200% 100%;
+ animation: loading-shimmer 1.5s infinite;
+}
+
+@keyframes loading-shimmer {
+ 0% {
+ background-position: -200% 0;
+ }
+ 100% {
+ background-position: 200% 0;
+ }
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-fade-in {
+ animation: fade-in 0.5s ease-out;
+}
+
+.animate-fadeIn {
+ animation: fadeIn 0.2s ease-out forwards;
+}
+
+#confetti {
+ z-index: 99999 !important;
+ pointer-events: none !important;
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+}
+
+#visualization {
+ position: relative;
+}
+
+#capacityBubble {
+ position: absolute;
+ margin-top: -0.5em;
+ left: 10px;
+ bottom: 10px;
+ z-index: 10;
+}
+
+#capacity {
+ display: none;
+ color: #fff;
+ padding: 5px 1em;
+ border-radius: 15px;
+ background-color: rgba(64, 64, 64, 0.7);
+ font-size: large;
+}
+.capacityNumber {
+ font-weight: bold;
+}
diff --git a/app/src/styles/shepherd-theme.css b/app/src/styles/shepherd-theme.css
new file mode 100644
index 0000000..5eed65f
--- /dev/null
+++ b/app/src/styles/shepherd-theme.css
@@ -0,0 +1,271 @@
+/* Overlay */
+.shepherd-modal-overlay-container {
+ opacity: 0.5 !important;
+ z-index: 9998 !important;
+}
+
+/* Tour Step Container - Base */
+.shepherd-element {
+ z-index: 9999 !important;
+ max-width: 500px !important;
+}
+
+/* Centralizar modal quando não tem attachTo (modal inicial) */
+.shepherd-element.shepherd-enabled {
+ position: fixed !important;
+}
+
+/* Centralizar modal quando não tem attachTo (modal inicial) */
+.shepherd-element:not([data-popper-placement]) {
+ position: fixed !important;
+ top: 50% !important;
+ left: 50% !important;
+ transform: translate(-50%, -50%) !important;
+ margin: 0 !important;
+ width: auto !important;
+ height: auto !important;
+}
+
+/* Garantir que o conteúdo seja visível */
+.shepherd-element .shepherd-content {
+ position: relative !important;
+}
+
+/* Classes genéricas para steps customizados */
+.custom-tour-step,
+.playground-tour-step,
+.game-tour-step {
+ background: white !important;
+ border-radius: 16px !important;
+ box-shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06),
+ 0 20px 60px rgba(0, 0, 0, 0.15) !important;
+ border: 1px solid rgba(0, 0, 0, 0.05) !important;
+ position: relative !important;
+}
+
+/* Tour Header com Ícone */
+.tour-header {
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+ margin-bottom: 16px !important;
+}
+
+.tour-icon-wrapper {
+ width: 48px !important;
+ height: 48px !important;
+ border-radius: 12px !important;
+ background: var(--action-green) !important; /* action button color */
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ flex-shrink: 0 !important;
+}
+
+.tour-icon {
+ color: white !important;
+ stroke-width: 2 !important;
+}
+
+/* Header */
+.shepherd-header {
+ padding: 24px 24px 0 24px !important;
+ background: transparent !important;
+}
+
+/* Content */
+.shepherd-text {
+ padding: 24px !important;
+ color: #333 !important;
+ font-size: 15px !important;
+ line-height: 1.6 !important;
+ font-family: 'Lato', system-ui, sans-serif !important;
+}
+
+.shepherd-text h3 {
+ margin: 0 !important;
+ font-size: 22px !important;
+ font-weight: 700 !important;
+ font-family: 'Barlow Semi Condensed', sans-serif !important;
+ color: #1f2937 !important; /* same as GameFaseInfo title */
+}
+
+.shepherd-text ul {
+ margin: 12px 0 !important;
+ padding-left: 20px !important;
+ list-style: none !important;
+}
+
+.shepherd-text ul li {
+ margin: 8px 0 !important;
+ color: #555 !important;
+ position: relative !important;
+ padding-left: 20px !important;
+}
+
+.shepherd-text ul li::before {
+ content: "●" !important;
+ position: absolute !important;
+ left: 0 !important;
+ color: var(--brand-primary) !important;
+ font-weight: bold !important;
+}
+
+.shepherd-text strong {
+ color: #111 !important;
+ font-weight: 600 !important;
+}
+
+.shepherd-text em {
+ color: #666 !important;
+ font-style: italic !important;
+}
+
+/* Footer */
+.shepherd-footer {
+ padding: 0 24px 24px 24px !important;
+ background: transparent !important;
+ display: flex !important;
+ gap: 12px !important;
+ justify-content: flex-end !important;
+ border-top: 1px solid #e5e7eb !important;
+ padding-top: 16px !important;
+ margin-top: 8px !important;
+}
+
+.shepherd-button {
+ border: none !important;
+ padding: 12px 28px !important;
+ border-radius: 9999px !important;
+ font-size: 14px !important;
+ font-weight: 700 !important;
+ cursor: pointer !important;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
+ font-family:
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", sans-serif !important;
+}
+
+.shepherd-button:not(.shepherd-button-secondary) {
+ background: var(--action-green) !important; /* action button color (green-100) */
+ color: black !important;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
+ font-family: 'Lato', system-ui, sans-serif !important;
+}
+
+.shepherd-button:not(.shepherd-button-secondary):hover {
+ background: #B4E34E !important; /* approximate green-200 */
+ transform: scale(1.05) !important;
+ box-shadow:
+ 0 8px 40px 0 rgba(44, 44, 84, 0.35),
+ 0 2px 16px 0 rgba(44, 44, 84, 0.25) !important;
+}
+
+.shepherd-button-secondary {
+ background: rgba(255, 255, 255, 0.8) !important;
+ backdrop-filter: blur(4px) !important;
+ color: #1f2937 !important;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
+ font-family: 'Lato', system-ui, sans-serif !important;
+}
+.shepherd-button-secondary:hover {
+ background: rgba(255, 255, 255, 0.95) !important;
+ color: #111827 !important;
+ transform: scale(1.05) !important;
+ box-shadow: 0 8px 20px 0 rgba(44, 44, 84, 0.2) !important;
+}
+
+/* Cancel Icon (X) */
+.shepherd-cancel-icon {
+ /* Tour-specific positioning */
+ position: absolute !important;
+ top: 16px !important;
+ right: 16px !important;
+}
+
+/* Arrow - Ocultar */
+.shepherd-arrow {
+ display: none !important;
+}
+
+/* Animations */
+@keyframes shepherd-element-fade-in {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.shepherd-element {
+ animation: shepherd-element-fade-in 0.3s ease-out !important;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .shepherd-element {
+ max-width: 90vw !important;
+ margin: 10px !important;
+ }
+
+ .shepherd-text {
+ font-size: 14px !important;
+ padding: 16px !important;
+ }
+
+ .shepherd-text h3 {
+ font-size: 18px !important;
+ }
+
+ .shepherd-button {
+ padding: 8px 16px !important;
+ font-size: 13px !important;
+ }
+
+ .tour-icon-wrapper {
+ width: 40px !important;
+ height: 40px !important;
+ }
+
+ .shepherd-element:not([data-popper-placement]) {
+ max-width: calc(100vw - 32px) !important;
+ left: 50% !important;
+ top: 50% !important;
+ }
+
+ /* Posicionamento específico para áreas destacadas em mobile */
+ /* Quando anexado à esquerda ou direita, posicionar acima em mobile */
+ .shepherd-element[data-popper-placement^="right"],
+ .shepherd-element[data-popper-placement^="left"] {
+ position: fixed !important;
+ top: auto !important;
+ bottom: 16px !important;
+ left: 50% !important;
+ transform: translateX(-50%) !important;
+ right: auto !important;
+ max-width: calc(100vw - 32px) !important;
+ }
+
+ /* Para workspace, lixeira, code viewer e console */
+ /* Posicionar na parte inferior quando anexado */
+ .shepherd-element[data-popper-placement="left"],
+ .shepherd-element[data-popper-placement="right"] {
+ bottom: 80px !important;
+ }
+}
+
+/* Garantir visibilidade total do modal inicial */
+.shepherd-element:not([data-popper-placement]) .shepherd-content {
+ max-height: 90vh !important;
+ overflow-y: auto !important;
+}
+
+/* Scroll suave no conteúdo se necessário */
+.shepherd-element:not([data-popper-placement]) .shepherd-text {
+ max-height: calc(90vh - 120px) !important;
+ overflow-y: auto !important;
+}
diff --git a/app/src/utils/__tests__/drawAxes.test.js b/app/src/utils/__tests__/drawAxes.test.js
new file mode 100644
index 0000000..fa2d80f
--- /dev/null
+++ b/app/src/utils/__tests__/drawAxes.test.js
@@ -0,0 +1,103 @@
+import { describe, it, expect, vi } from "vitest";
+import { drawAxes } from "../drawAxes";
+
+function makeScene() {
+ const textMock = { setOrigin: vi.fn() };
+ const g = {
+ fillStyle: vi.fn(),
+ fillRect: vi.fn(),
+ };
+ const scene = {
+ add: {
+ graphics: vi.fn(() => g),
+ text: vi.fn(() => textMock),
+ },
+ _g: g,
+ _text: textMock,
+ };
+ return { scene, g, textMock };
+}
+
+describe("drawAxes", () => {
+ it("creates a graphics object", () => {
+ const { scene } = makeScene();
+ drawAxes(scene, { x: false, y: false });
+ expect(scene.add.graphics).toHaveBeenCalledOnce();
+ });
+
+ describe("axis.x = true", () => {
+ it("draws major and mid tick rects on the x axis", () => {
+ const { scene, g } = makeScene();
+ drawAxes(scene, { x: true, y: false });
+ expect(g.fillRect).toHaveBeenCalled();
+ });
+
+ it("adds text labels for labeled ticks", () => {
+ const { scene } = makeScene();
+ drawAxes(scene, { x: true, y: false });
+ // labeledTicks = [20, 40, 60, 80] → 4 calls
+ const xCalls = scene.add.text.mock.calls;
+ expect(xCalls.length).toBeGreaterThanOrEqual(4);
+ });
+
+ it("does not add text for y axis when only x is enabled", () => {
+ const { scene } = makeScene();
+ drawAxes(scene, { x: true, y: false }, 800, 600);
+ // All text calls should use bottomY (alturaTela - 8 = 592) as y coordinate
+ const yCoordsOfText = scene.add.text.mock.calls.map((c) => c[1]);
+ // bottomY + 2 = 594 for labeledTicks that produce text at bottomY+2
+ yCoordsOfText.forEach((y) => {
+ expect(typeof y).toBe("number");
+ });
+ });
+ });
+
+ describe("axis.y = true", () => {
+ it("draws major and mid tick rects on the y axis", () => {
+ const { scene, g } = makeScene();
+ drawAxes(scene, { x: false, y: true });
+ expect(g.fillRect).toHaveBeenCalled();
+ });
+
+ it("adds text labels for labeled ticks on y axis", () => {
+ const { scene } = makeScene();
+ drawAxes(scene, { x: false, y: true });
+ expect(scene.add.text).toHaveBeenCalled();
+ });
+ });
+
+ describe("axis.x = false, axis.y = false", () => {
+ it("draws no rects and no text", () => {
+ const { scene, g } = makeScene();
+ drawAxes(scene, { x: false, y: false });
+ expect(g.fillRect).not.toHaveBeenCalled();
+ expect(scene.add.text).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("axis.x = true, axis.y = true", () => {
+ it("draws both axes", () => {
+ const { scene, g } = makeScene();
+ drawAxes(scene, { x: true, y: true });
+ // majorTicks (4) + midTicks (5) for each axis = 18 fillRect calls
+ expect(g.fillRect.mock.calls.length).toBe(18);
+ // labeledTicks (4) for x + 4 for y = 8 text calls
+ expect(scene.add.text.mock.calls.length).toBe(8);
+ });
+ });
+
+ describe("default dimensions", () => {
+ it("uses 800x600 by default", () => {
+ const { scene, g } = makeScene();
+ // Should not throw without explicit dimensions
+ expect(() => drawAxes(scene, { x: true, y: true })).not.toThrow();
+ });
+ });
+
+ describe("custom dimensions", () => {
+ it("accepts custom width and height", () => {
+ const { scene } = makeScene();
+ expect(() => drawAxes(scene, { x: true, y: true }, 1024, 768)).not.toThrow();
+ });
+ });
+});
diff --git a/app/src/utils/__tests__/gameEvents.test.js b/app/src/utils/__tests__/gameEvents.test.js
new file mode 100644
index 0000000..b8cab60
--- /dev/null
+++ b/app/src/utils/__tests__/gameEvents.test.js
@@ -0,0 +1,92 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { gameEventBus } from "../gameEvents";
+
+function listenOnce(eventName) {
+ return new Promise((resolve) => {
+ gameEventBus.addEventListener(eventName, (e) => resolve(e), { once: true });
+ });
+}
+
+describe("gameEventBus", () => {
+ describe("executeCode", () => {
+ it("dispatches executeCode event with code and workspace in detail", async () => {
+ const p = listenOnce("executeCode");
+ gameEventBus.executeCode("console.log(1)", { blocks: [] });
+ const e = await p;
+ expect(e.detail).toEqual({ code: "console.log(1)", workspace: { blocks: [] } });
+ });
+
+ it("dispatches executeCode with undefined workspace when omitted", async () => {
+ const p = listenOnce("executeCode");
+ gameEventBus.executeCode("x = 1");
+ const e = await p;
+ expect(e.detail.code).toBe("x = 1");
+ expect(e.detail.workspace).toBeUndefined();
+ });
+ });
+
+ describe("resetGame", () => {
+ it("dispatches resetGame event", async () => {
+ const p = listenOnce("resetGame");
+ gameEventBus.resetGame();
+ const e = await p;
+ expect(e.type).toBe("resetGame");
+ });
+ });
+
+ describe("pauseGame", () => {
+ it("dispatches pauseGame event", async () => {
+ const p = listenOnce("pauseGame");
+ gameEventBus.pauseGame();
+ const e = await p;
+ expect(e.type).toBe("pauseGame");
+ });
+ });
+
+ describe("gameSuccess", () => {
+ it("dispatches gameSuccess event", async () => {
+ const p = listenOnce("gameSuccess");
+ gameEventBus.gameSuccess();
+ const e = await p;
+ expect(e.type).toBe("gameSuccess");
+ });
+ });
+
+ describe("gameFailure", () => {
+ it("dispatches gameFailure with reason in detail", async () => {
+ const p = listenOnce("gameFailure");
+ gameEventBus.gameFailure("colisão detectada");
+ const e = await p;
+ expect(e.detail).toEqual({ reason: "colisão detectada" });
+ });
+
+ it("dispatches gameFailure with undefined reason when omitted", async () => {
+ const p = listenOnce("gameFailure");
+ gameEventBus.gameFailure();
+ const e = await p;
+ expect(e.detail).toEqual({ reason: undefined });
+ });
+ });
+
+ describe("gameReady", () => {
+ it("dispatches gameReady event", async () => {
+ const p = listenOnce("gameReady");
+ gameEventBus.gameReady();
+ const e = await p;
+ expect(e.type).toBe("gameReady");
+ });
+ });
+
+ describe("stopExecution", () => {
+ it("dispatches stopExecution event", async () => {
+ const p = listenOnce("stopExecution");
+ gameEventBus.stopExecution();
+ const e = await p;
+ expect(e.type).toBe("stopExecution");
+ });
+ });
+
+ it("is an EventTarget", () => {
+ expect(gameEventBus).toBeInstanceOf(EventTarget);
+ });
+});
diff --git a/app/src/utils/__tests__/isMobile.test.js b/app/src/utils/__tests__/isMobile.test.js
new file mode 100644
index 0000000..60338b6
--- /dev/null
+++ b/app/src/utils/__tests__/isMobile.test.js
@@ -0,0 +1,63 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { isMobileViewport, MOBILE_BREAKPOINT } from "../isMobile";
+
+describe("MOBILE_BREAKPOINT", () => {
+ it("é 768", () => {
+ expect(MOBILE_BREAKPOINT).toBe(768);
+ });
+});
+
+describe("isMobileViewport", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ describe("breakpoint padrão (768px)", () => {
+ it("retorna true quando width é menor que 768", () => {
+ vi.stubGlobal("innerWidth", 375);
+ expect(isMobileViewport()).toBe(true);
+ });
+
+ it("retorna true na resolução exatamente 767px", () => {
+ vi.stubGlobal("innerWidth", 767);
+ expect(isMobileViewport()).toBe(true);
+ });
+
+ it("retorna false quando width é exatamente 768", () => {
+ vi.stubGlobal("innerWidth", 768);
+ expect(isMobileViewport()).toBe(false);
+ });
+
+ it("retorna false em resolução desktop comum", () => {
+ vi.stubGlobal("innerWidth", 1440);
+ expect(isMobileViewport()).toBe(false);
+ });
+ });
+
+ describe("breakpoint customizado", () => {
+ it("retorna true quando width é menor que o breakpoint fornecido", () => {
+ vi.stubGlobal("innerWidth", 639);
+ expect(isMobileViewport(640)).toBe(true);
+ });
+
+ it("retorna false quando width é igual ao breakpoint fornecido", () => {
+ vi.stubGlobal("innerWidth", 640);
+ expect(isMobileViewport(640)).toBe(false);
+ });
+
+ it("retorna false quando width é maior que o breakpoint fornecido", () => {
+ vi.stubGlobal("innerWidth", 1024);
+ expect(isMobileViewport(640)).toBe(false);
+ });
+ });
+
+ describe("consistência com MOBILE_BREAKPOINT", () => {
+ it("isMobileViewport() usa MOBILE_BREAKPOINT como padrão", () => {
+ vi.stubGlobal("innerWidth", MOBILE_BREAKPOINT - 1);
+ expect(isMobileViewport()).toBe(true);
+
+ vi.stubGlobal("innerWidth", MOBILE_BREAKPOINT);
+ expect(isMobileViewport()).toBe(false);
+ });
+ });
+});
diff --git a/app/src/utils/__tests__/letramentoEvents.test.js b/app/src/utils/__tests__/letramentoEvents.test.js
new file mode 100644
index 0000000..00f4670
--- /dev/null
+++ b/app/src/utils/__tests__/letramentoEvents.test.js
@@ -0,0 +1,63 @@
+import { describe, it, expect } from "vitest";
+import { letramentoEventBus, LETRAMENTO_EVENTS } from "../letramentoEvents";
+
+function listenOnce(bus, eventName) {
+ return new Promise((resolve) => {
+ bus.addEventListener(eventName, (e) => resolve(e), { once: true });
+ });
+}
+
+describe("LETRAMENTO_EVENTS", () => {
+ it("has the expected constant values", () => {
+ expect(LETRAMENTO_EVENTS).toEqual({
+ STARTED: "started",
+ RUNNING: "running",
+ SUCCESS: "success",
+ FAILURE: "failure",
+ COMPLETED: "completed",
+ });
+ });
+});
+
+describe("letramentoEventBus", () => {
+ it("is an EventTarget", () => {
+ expect(letramentoEventBus).toBeInstanceOf(EventTarget);
+ });
+
+ describe("dispatch", () => {
+ it("fires a CustomEvent with the given name and detail", async () => {
+ const p = listenOnce(letramentoEventBus, "started");
+ letramentoEventBus.dispatch("started", { score: 10 });
+ const e = await p;
+ expect(e.type).toBe("started");
+ expect(e.detail).toEqual({ score: 10 });
+ });
+
+ it("uses empty object as default detail", async () => {
+ const p = listenOnce(letramentoEventBus, "running");
+ letramentoEventBus.dispatch("running");
+ const e = await p;
+ expect(e.detail).toEqual({});
+ });
+ });
+
+ describe("named helpers", () => {
+ const cases = [
+ ["started", () => letramentoEventBus.started({ x: 1 }), { x: 1 }],
+ ["running", () => letramentoEventBus.running({ y: 2 }), { y: 2 }],
+ ["success", () => letramentoEventBus.success({ ok: true }),{ ok: true }],
+ ["failure", () => letramentoEventBus.failure({ err: "e" }),{ err: "e" }],
+ ["completed", () => letramentoEventBus.completed({ n: 3 }), { n: 3 }],
+ ];
+
+ for (const [eventName, trigger, expectedDetail] of cases) {
+ it(`${eventName}() dispatches "${eventName}" with detail`, async () => {
+ const p = listenOnce(letramentoEventBus, eventName);
+ trigger();
+ const e = await p;
+ expect(e.type).toBe(eventName);
+ expect(e.detail).toEqual(expectedDetail);
+ });
+ }
+ });
+});
diff --git a/app/src/utils/__tests__/loadFont.test.js b/app/src/utils/__tests__/loadFont.test.js
new file mode 100644
index 0000000..8604313
--- /dev/null
+++ b/app/src/utils/__tests__/loadFont.test.js
@@ -0,0 +1,85 @@
+import { describe, it, expect, beforeEach, vi } from "vitest";
+
+// Each test imports registerFont fresh so the module-level Set resets.
+
+describe("registerFont", () => {
+ beforeEach(() => {
+ // Remove any
+
+
+
+
+
+
+