Especificação do Componente: Renderizador de Cenário¶
Esta seção detalha a arquitetura lógica do componente de visualização (src/ui/cenario.ts). Diferente de image maps estáticos, este componente opera como uma Máquina de Estado, gerenciando geometria complexa (polígonos em perspectiva), transições animadas (Zoom/Modais) e a integração de segurança com o Moodle.
1. Ciclo de Vida da Renderização (Pipeline)¶
O componente segue um fluxo estrito de "Busca Paralela, Renderização Unificada" para evitar layout shifts (mudanças bruscas na tela) e garantir que o usuário só veja links que realmente funcionam.
flowchart TD
A[Gatilho Detectado] --> B{Inicia Carregamento}
B -->|Fetch 1| C["Download JSON Visual (CDN)"]
B -->|Fetch 2| D["Consulta API Moodle (AJAX)"]
C --> E[Validação de Schema]
D --> F["Parse de Permissões (cmid)"]
E & F --> G{Merge de Dados}
G --> H[Cálculo de Geometria]
H --> I[Renderização DOM Final]
I --> J[Anexar Eventos e Tooltips]
1.1 Fases do Processo¶
- Mounting: O script injeta um loader (spinner) dentro do container alvo e prepara a estrutura HTML básica.
- Fetching:
- Baixa o JSON de configuração (ex:
cenario-01.json) para obter o layout visual. - Chama
core_courseformat_get_statepara resolver URLs dinâmicas e checar permissões.
- Baixa o JSON de configuração (ex:
- Merging (Fail Open):
- O sistema cruza os dados do JSON com a resposta do Moodle pelo ID.
- Se a API do Moodle falhar: O sistema assume que o acesso está Liberado (Fail Open) e tenta gerar links genéricos para não bloquear o aluno indevidamente.
- Se a API responder:
uservisible === false: Hotspot marcado como Bloqueado (Visual + Comportamento).uservisible === true: Hotspot recebe a URL final validada pelo LMS.
- Painting: O HTML é inserido na página. Se estiver em modo
DEV, os auxiliares visuais (bordas piscantes) são ativados.
2. Lógica de Geometria e Responsividade¶
Para suportar objetos em perspectiva (como portas laterais e corredores) e garantir a adaptação a qualquer tela, o sistema utiliza dois modelos geométricos baseados em porcentagem.
2.1 Modelo Retangular (Box Model)¶
Usado para botões simples, TVs frontais e cartazes.
- Input:
x, y, width, height(Pixels originais). - Output:
top: %; left: %; width: %; height: %;. - Fórmula: $$ \text{left} = (\frac{x}{W_{ref}}) \times 100\% $$ $$ \text{top} = (\frac{y}{H_{ref}}) \times 100\% $$
2.2 Modelo Poligonal (Clip-Path)¶
Usado para formas irregulares (trapézios, losangos) que simulam perspectiva.
- Input:
points: [[x1,y1], [x2,y2], [x3,y3], ...]. - Cálculo: Cada par de coordenadas é convertido para porcentagem relativa ao tamanho total da imagem.
- CSS Resultante:
3. Estados do Hotspot (Comportamento)¶
O componente visual reflete o estado funcional da atividade no Moodle.
3.1 Habilitado (Enabled)¶
- Critério:
uservisible: trueou link externo. - Comportamento: Cursor pointer, escala (zoom) ao passar o mouse, clique executa a ação.
- CSS:
.middag-app-cn-hotspot
3.2 Bloqueado (Locked)¶
- Critério:
uservisible: false(retornado explicitamente pelo Moodle). - Comportamento:
- Cursor not-allowed.
- Ícone de cadeado visível (opcional via CSS).
- Clique prevenido (
e.preventDefault()). - Tooltip exibe: "Atividade restrita".
- CSS:
.middag-app-cn-hotspot--locked
3.3 Carregando (Loading)¶
- Critério: Enquanto as promessas do
fetchestão pendentes. - Comportamento: Exibe skeleton ou spinner. Interação desabilitada.
4. Estilização Visual e Efeitos¶
O JSON define a semântica visual através da propriedade variant, que é mapeada para classes CSS prefixadas para isolamento total.
4.1 Variantes Pré-definidas¶
| Variante JSON | Classe CSS | Aplicação Típica |
|---|---|---|
glass-door |
.middag-app-cn-hotspot--glass-door |
Portas de vidro. Aplica opacidade leve e brilho no hover. |
poster |
.middag-app-cn-hotspot--poster |
Cartazes na parede. Aplica box-shadow leve. |
tv-screen |
.middag-app-cn-hotspot--tv-screen |
Telas de TV. Pode ter brilho interno ou scanlines. |
btn-exit |
.middag-app-cn-hotspot--btn-exit |
Botão de sair/voltar para home. |
4.2 Efeito de Hover em Polígonos¶
Como a propriedade border não acompanha o recorte do clip-path, utilizamos filtros CSS para criar o efeito de destaque ("glow") ao redor da forma irregular.
.middag-app-cn-hotspot:hover {
/* Cria uma "luz" externa ao redor do recorte exato do polígono */
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.8)) brightness(1.1);
cursor: pointer;
}
5. Sistema de Modais (TV Expandida)¶
O modal permite navegação aprofundada (ex: ver a tela da TV em tela cheia) sem sair do contexto da página.
5.1 Transição de Entrada (Zoom In)¶
Para criar continuidade visual:
- Captura: O JS captura o
getBoundingClientRect()do hotspot clicado. - Animação: O modal inicia com as dimensões e posição da origem e transiciona para
top: 0; left: 0; width: 100%usando CSS Transitions. - Backdrop: Um fundo escuro (
opacity: 0.8) aparece suavemente atrás do conteúdo.
5.2 Estratégia de Fechamento¶
Sistema híbrido de segurança para evitar que o usuário fique preso:
- Botão "X" Nativo: Renderizado automaticamente no canto superior direito (z-index máximo).
- Hotspot Interno: O conteudista pode desenhar um botão "Voltar" dentro da imagem da TV.
- Tecla ESC: Fecha o modal e devolve o foco para o elemento de origem.
6. Developer Experience (DX)¶
Para facilitar a configuração das coordenadas JSON sem "chutar" valores, o ambiente de desenvolvimento possui auxiliares visuais.
6.1 Modo de Debug¶
O script detecta automaticamente localhost ou import.meta.env.DEV. Quando ativo, adiciona a classe .middag-app-cn-debug-mode ao container.
/* Apenas em Dev/Localhost: Pisca os hotspots para facilitar a localização visual */
.middag-app-cn-debug-mode .middag-app-cn-hotspot {
background-color: rgba(255, 0, 0, 0.2) !important; /* Vermelho translúcido */
border: 1px dashed red;
animation: middag-app-cn-debug-pulse 2s infinite;
}
@keyframes middag-app-cn-debug-pulse {
0% {
opacity: 0.3;
}
50% {
opacity: 0.7;
}
100% {
opacity: 0.3;
}
}
Isso permite que o desenvolvedor veja exatamente onde as áreas clicáveis estão, mesmo que sejam transparentes em produção.
7. Estrutura DOM Renderizada¶
Abaixo, a hierarquia final injetada no DOM. O gerenciamento de z-index é crítico para que o Modal cubra o cenário, mas não o menu do Moodle (se desejado).
<div class="middag-app-cn-container middag-app-cn-debug-mode">
<div class="middag-app-cn-scene-layer">
<img src="..." class="middag-app-cn-image-base" alt="" aria-hidden="true"/>
<button
class="middag-app-cn-hotspot middag-app-cn-hotspot--glass-door"
style="clip-path: polygon(10% 20%, ...);"
aria-label="Acessar Triagem"
></button>
<button
class="middag-app-cn-hotspot middag-app-cn-hotspot--tv-screen"
data-action="modal"
style="top: 40%; left: 40%; ..."
></button>
</div>
<div class="middag-app-cn-modal-layer" aria-hidden="true" style="display: none;">
<div class="middag-app-cn-modal-backdrop"></div>
<div class="middag-app-cn-modal-content">
<img src=".../tv-expandida.jpg" class="middag-app-cn-image-modal"/>
<a href="..." class="middag-app-cn-hotspot ...">Link Videoaula</a>
<button class="middag-app-cn-modal-close-btn" aria-label="Fechar">✕</button>
</div>
</div>
</div>
8. Acessibilidade (a11y)¶
O componente adere às diretrizes WCAG 2.1 AA.
8.1 Navegação e Foco¶
- Ordem de Tabulação: Segue a ordem do array
hotspotsno JSON. Elementos bloqueados usam<button disabled>, que nativamente são removidos da ordem de tabulação em alguns navegadores, ou mantidos comaria-disabled="true"se quisermos que o usuário saiba que eles existem (configurável). - Indicador de Foco: O CSS implementa
:focus-visiblecom outline de alto contraste para navegação via teclado.
8.2 Leitores de Tela¶
- Rótulos: Usa
aria-labelconcatenado com o estado (ex: "Avisos (Bloqueado)"). - Imagens: Imagens de fundo possuem
aria-hidden="true"para reduzir ruído.
8.3 Gestão de Modal (Focus Trap)¶
Quando o modal abre:
- O foco é movido para o botão "Fechar" ou primeiro elemento interativo.
- Um Focus Trap impede que o
Tabsaia do modal e vá para o fundo da página. - A tecla
Escfecha o modal imediatamente.
9. CSS Isolado (Scoped)¶
Para evitar conflitos com temas do Moodle (Boost, Classic), todas as classes usam o prefixo middag-app-cn-. Não utilizamos IDs para estilização.
/* Exemplo de CSS injetado */
.middag-app-cn-container {
position: relative;
width: 100%;
line-height: 0;
}
.middag-app-cn-hotspot {
position: absolute;
z-index: 10;
display: block;
/* Reset de estilos do botão do tema */
background: transparent;
border: 0;
padding: 0;
cursor: pointer;
transition: transform 0.2s ease;
}
.middag-app-cn-hotspot:hover, .middag-app-cn-hotspot:focus {
transform: scale(1.05);
box-shadow: 0 0 15px rgba(255, 255, 0, 0.5);
}
.middag-app-cn-hotspot--locked {
cursor: not-allowed;
background-color: rgba(0, 0, 0, 0.3);
border: 1px dashed #999;
}
Próximo passo na leitura: Verifique as regras de integridade do JSON na seção Validação de Campos.