# GERADO por scripts/tema_login_escolinha.py — nao edite na mao.
# Fonte: keycloak-theme/escolinha/*.ftl (login.ftl, login-update-password.ftl, estilo.ftl, template.ftl, error.ftl, info.ftl, logout-confirm.ftl, login-page-expired.ftl, login-reset-password.ftl, login-config-totp.ftl, login-otp.ftl, webauthn-register.ftl, webauthn-authenticate.ftl, select-authenticator.ftl, login-username.ftl, login-password.ftl)
apiVersion: v1
kind: ConfigMap
metadata:
name: kc-theme-athleticmapv2
namespace: plataforma-prod
annotations:
# Sem isto o ArgoCD aplica pelo lado do cliente e guarda o manifesto
# inteiro numa anotacao limitada a 256 KiB — que este tema estoura.
argocd.argoproj.io/sync-options: ServerSideApply=true
data:
theme.properties: |-
parent=keycloak
login.ftl: |-
<#--
Athletic Map — tela de login do tenant escolinha (tema athleticmap-v2).
Este arquivo e a FONTE legivel. O que vai para o cluster e o ConfigMap
36-kc-theme.yaml, gerado por scripts/tema_login_escolinha.py, que substitui os
marcadores abaixo pelas imagens em data URI:
As imagens vivem no `estilo.ftl`, em data URI. Os marcadores que o
gerador substitui estao LA, e nao aqui — escrever a sintaxe deles
neste comentario fazia o gerador inchar este arquivo em 200 KB.
Nao edite o YAML na mao: ele e derivado deste arquivo.
-->
<#-- Passkeys (Keycloak 26.4+): o `passkeys.ftl` vem do tema BASE. Sem a politica
"Passkeys" ligada no realm, a macro nao desenha nada. -->
<#import "passkeys.ftl" as passkeys>
Athletic Map — Entrar
<#-- Ver estilo.ftl: o
<#-- O realm `clientes` e o do PAINEL DO COMPRADOR, e a conta dele e NOVA:
separada da conta da escolinha, e sem senha ate ele usar o convite.
"Bem-vindo de volta" fazia o comprador digitar a senha da escolinha e
levar "senha invalida" sem entender por que (2026-09-25). -->
<#if (realm.name!"") == "clientes">
Painel da sua conta
Aqui voce ve tudo o que contratou. Esta conta e separada
da conta da sua escolinha.
Primeiro acesso? Use o link que enviamos para o e-mail do
pagamento. Nao chegou? Clique em “Esqueceu sua senha?”.
<#-- O realm `athleticmap` e o da PLATAFORMA (07/10/2026): o Painel do dono e o sistema
interno da empresa. So o dono tem conta aqui, com 2FA. -->
<#elseif (realm.name!"") == "athleticmap">
#if>
<#if realm.password>
<#-- FORA do form de senha: a macro traz os proprios forms (o do
WebAuthn e o de escolher credencial). `webauthn` no autocomplete do
e-mail e o que faz o navegador sugerir a passkey no proprio campo. -->
<@passkeys.conditionalUIData />
#if>
login-update-password.ftl: |-
<#--
A tela de DEFINIR SENHA — a primeira que o cliente que acabou de pagar ve
depois de clicar no convite.
Ate 2026-09-25 ela caia no tema de fabrica do Keycloak: fundo cinza de
poligonos, caixa branca e botao azul. O tema tinha `login.ftl` e mais nada, e
`parent=keycloak` cuidava do resto — que e o resto errado.
A MOLDURA E A MESMA do login, por `<#include "estilo.ftl">`. As imagens
pesam 400 KB: duplica-las aqui levaria o ConfigMap a 860 KB, perto do limite
de 1 MiB. Com o include elas existem uma vez.
O QUE O KEYCLOAK ENTREGA NESTA TELA
url.loginAction o destino do POST
username quem esta definindo (so leitura)
isAppInitiatedAction veio de dentro do app, e nao de um convite
messagesPerField erros por campo
Athletic Map — tela de login do tenant escolinha (tema athleticmap-v2).
Este arquivo e a FONTE legivel. O que vai para o cluster e o ConfigMap
36-kc-theme.yaml, gerado por scripts/tema_login_escolinha.py, que substitui os
marcadores abaixo pelas imagens em data URI:
As imagens vivem no `estilo.ftl`, em data URI. Os marcadores que o
gerador substitui estao LA, e nao aqui — escrever a sintaxe deles
neste comentario fazia o gerador inchar este arquivo em 200 KB.
Nao edite o YAML na mao: ele e derivado deste arquivo.
-->
Athletic Map — Definir senha
<#-- Ver estilo.ftl: o
template.ftl: |-
<#--
A MOLDURA DE TODAS AS OUTRAS TELAS do fluxo de login.
── POR QUE ELA EXISTE ───────────────────────────────────────────────────────
O tema tinha `login.ftl` e `login-update-password.ftl`, escritas a mao. As
OUTRAS quarenta paginas do Keycloak — "execute estas acoes", link expirado,
erro, esqueci a senha, termos — caiam no `parent=keycloak` e apareciam com o
fundo cinza de poligonos e o botao azul de fabrica.
Escrever quarenta paginas a mao seria trocar um problema por quarenta. Esta
aqui e a que todas elas usam: sobrescrevendo-a, TODAS ganham a marca de uma
vez, e o conteudo de cada uma continua vindo do tema base.
── O CONTRATO NAO E MEU, E NAO PODE MUDAR ───────────────────────────────────
A assinatura do macro e as quatro secoes `<#nested>` sao do Keycloak. Cada
pagina do tema base chama `<@layout.registrationLayout ...>` passando
`displayInfo`, `displayMessage`, `displayRequiredFields` e preenchendo
`header`, `form`, `info` e `socialProviders`. Mudar um nome aqui nao quebra
uma pagina: quebra as quarenta.
header o titulo da tela
form o conteudo principal
info o rodape informativo, so quando displayInfo
socialProviders os provedores externos, quando houver
E DUAS SECOES QUE SAO DESTE TEMA (2026-10-08), usadas so pelas paginas
escritas aqui (error, info, logout-confirm, login-otp...):
selo o icone redondo acima do titulo
sub a linha cinza abaixo do titulo, como a do login
Uma pagina do tema BASE nunca as preenche (as dela sao if/elseif sobre os
quatro nomes acima), entao para ela as duas saem vazias e nao desenham nada.
── O QUE ELA DELIBERADAMENTE NAO FAZ ────────────────────────────────────────
Nao carrega `properties.styles` nem `properties.scripts` do tema base: o
visual e o do `estilo.ftl`, e trazer o CSS do PatternFly junto so devolveria
o cinza que se esta tirando.
Nao mostra o seletor de idioma. O realm suporta so pt-BR — e com um idioma
so, o seletor e um botao que nao faz nada.
── AS IMAGENS VEM DO `estilo.ftl` ───────────────────────────────────────────
Mesmo include do `login.ftl`: elas pesam ~400 KB em data URI e existem uma
vez no ConfigMap.
-->
<#--
As chaves pt-BR de link do Keycloak vem com seta de texto: "« Voltar para o
aplicativo", "» Clique aqui para continuar". Num botao a seta sobra. Escrita
com escape (\xAB, \xBB) para nao depender da codificacao de quem le o arquivo.
-->
<#function semSeta texto>
<#return texto?replace("\xAB", "")?replace("\xBB", "")?trim>
#function>
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
${msg("loginTitle",(realm.displayName!''))}
<#include "estilo.ftl">
<#-- O mesmo importmap do template.ftl de fabrica: as paginas de passkey do tema
base (cadastrar passkey, escolher credencial) importam `rfc4648` pelo nome.
Ver o comentario no login.ftl. -->
<#assign selo><#nested "selo">#assign>
<#if selo?markup_string?trim?has_content>
<#-- A pagina traz o proprio ``: a cor
depende do que ela diz. -->
${selo}
#if>
#if>
<#-- A mesma regra do tema base: uma acao iniciada pelo proprio app nao
mostra o aviso de "voce precisa completar isto", porque quem a
iniciou foi o usuario, agora. -->
<#if displayMessage && message?has_content
&& (message.type != 'warning' || !isAppInitiatedAction??)>
#macro>
error.ftl: |-
<#--
A tela de ERRO do Keycloak ("Sentimos muito..."): redirect_uri invalido,
client inexistente, link de acao invalido ou ja usado, sessao perdida.
Ate 2026-10-08 era a `error.ftl` do tema BASE dentro do `template.ftl` deste:
ja tinha a marca, mas o titulo "Sentimos muito..." e o texto tecnico solto
("Parametro invalido: redirect_uri") sem dizer o que a pessoa faz agora. Quem
chega aqui quase sempre so precisa voltar e tentar de novo.
Textos: as chaves pt-BR do Keycloak quando existem (`errorTitle`,
`backToApplication`). O `traceIdSupportMessage` NAO tem traducao pt-BR no
26.7.5 — sairia em ingles —, entao a frase do codigo de suporte e fixa aqui.
O id `kc-error-message` e o mesmo do base (o validador procura por ele).
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=false; section>
<#if section = "selo">
<#elseif section = "header">
Algo deu errado
<#elseif section = "sub">
${kcSanitize(msg("errorTitle"))?no_esc} Não foi possível concluir o seu pedido.
<#elseif section = "form">
<#if message?? && message.summary?has_content>
${kcSanitize(message.summary)?no_esc}
#if>
Volte para o aplicativo e tente de novo. Se continuar acontecendo, fale com o suporte.
<#if traceId??>
Se falar com o suporte, informe este código: ${traceId}
#if>
@layout.registrationLayout>
info.ftl: |-
<#--
As telas de INFORMACAO do Keycloak: "execute estas acoes" (o link do e-mail de
convite/troca de senha), "sua conta foi atualizada", "voce saiu da sessao",
e-mail verificado, acao expirada com sessao, etc.
Ate 2026-10-08 era a `info.ftl` do tema BASE na moldura deste. Dois defeitos
que motivaram esta: o titulo "Account updated" saia em INGLES (a chave
`accountUpdatedTitle` nao tem traducao pt-BR no 26.7.5), e o titulo repetia
palavra por palavra a frase de baixo.
TITULO: `messageHeader` quando o Keycloak manda um (traduzido aqui quando o
pt-BR de fabrica nao tem); senao, a propria mensagem — e entao ela NAO se
repete embaixo.
Os ids `kc-info-message` e os links sao os do base; o validador procura o id.
-->
<#import "template.ftl" as layout>
<#-- Chaves de titulo que o pt-BR do Keycloak 26.7.5 nao traduz. -->
<#assign titulosPtBr = {"accountUpdatedTitle": "Conta atualizada"}>
<#-- Nomes de acao obrigatoria que em pt-BR de fabrica sao tecnicos demais. -->
<#assign acoesPtBr = {
"webauthn-register-passwordless": "Cadastrar uma chave de seguran\xE7a (passkey)",
"webauthn-register": "Cadastrar uma chave de seguran\xE7a para o segundo fator"}>
<#assign sucesso = ((message.type)!'') == 'success' || (messageHeader!'') == 'accountUpdatedTitle'>
<@layout.registrationLayout displayMessage=false; section>
<#if section = "selo">
<#if requiredActions??>
<#elseif sucesso>
<#else>
#if>
<#elseif section = "header">
<#if messageHeader??>
${kcSanitize(titulosPtBr[messageHeader]!msg("${messageHeader}"))?no_esc}
<#elseif requiredActions??>
Antes de continuar
<#else>
${(message.summary)!''}
#if>
<#elseif section = "sub">
<#if messageHeader?? || requiredActions??>${(message.summary)!''}#if>
<#elseif section = "form">
#if>
@layout.registrationLayout>
logout-confirm.ftl: |-
<#--
CONFIRMAR A SAIDA — o que o Keycloak mostra quando o logout chega sem
`id_token_hint` (link antigo, app que perdeu o token, alguem digitando a URL).
O "Sair" do Painel do dono ja manda o id_token e nao passa por aqui
(2026-10-07); esta tela e para todo o resto. Ate 2026-10-08 era a do tema
BASE na moldura deste: "Saindo" / "Voce realmente deseja sair?" e o botao.
O botao precisa de `name="confirmLogout"` e o form do `session_code`: e o que
o Keycloak le no POST. O base desabilitava o botao no onsubmit, e um botao
desabilitado nao vai no POST — aqui nao se desabilita nada.
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "selo">
<#elseif section = "header">
Sair da conta
<#elseif section = "sub">
${msg("logoutConfirmHeader")}
<#elseif section = "form">
#if>
@layout.registrationLayout>
login-page-expired.ftl: |-
<#--
PAGINA EXPIRADA — o Keycloak mostra quando chega um formulario de uma etapa
que o login ja passou (voltar do navegador e reenviar, aba antiga).
O base dizia "Para reiniciar o processo de login [Clique aqui] . Para
continuar o processo de login [Clique aqui] ." — dois links soltos com ponto
depois. Aqui sao dois botoes: o principal recomeca (o que quase todo mundo
quer), o secundario continua de onde parou.
Os ids `loginRestartLink` e `loginContinueLink` sao os do base.
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "selo">
<#elseif section = "header">
A página expirou
<#elseif section = "sub">
Esta etapa do login não vale mais. Escolha como seguir:
<#elseif section = "form">
Recomeçar o loginContinuar de onde parei
#if>
@layout.registrationLayout>
login-reset-password.ftl: |-
<#--
ESQUECEU SUA SENHA — o link "Esqueceu sua senha?" do login cai aqui.
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: campo sem o icone e
a altura do login, "« Voltar ao Login" ACIMA do botao e a instrucao no rodape.
Aqui a instrucao vira o subtitulo, o campo e o mesmo do login e o "voltar"
fica embaixo, onde se procura.
O Keycloak le `username` no POST. O id do form (`kc-reset-password-form`) e o
do base; o validador procura por ele. Depois de enviar, o Keycloak volta ao
LOGIN com o aviso "Voce devera receber um e-mail..." (mesmo para e-mail que
nao existe, para nao revelar quem tem conta) — essa tela e a login.ftl.
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username'); section>
<#if section = "selo">
<#elseif section = "header">
Esqueceu sua senha?
<#elseif section = "sub">
<#if realm.duplicateEmailsAllowed>${msg("emailInstructionUsername")}<#else>${msg("emailInstruction")}#if>
<#elseif section = "form">
${layout.semSeta(msg("backToLogin"))}
#if>
@layout.registrationLayout>
login-config-totp.ftl: |-
<#--
CONFIGURAR O 2FA (TOTP) — o dono (`atm_admin`) cai aqui no primeiro login
depois do 2FA obrigatorio (fluxo `browser-2fa-dono`, 2026-10-07).
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: funcionava, mas com
o "*" de obrigatorio numa linha sozinha, o passo 3 mandando "clicar em Ok"
num botao que se chama "Enviar", e o QR solto no texto.
NAO MUDE OS IDS: o robo do e2e (`frontend/e2e/apoio.ts`, passarPeloKeycloak)
acha a tela por `kc-totp-settings-form`, troca para a chave em texto por
`mode-manual`, le `kc-totp-secret-key` e preenche `totp`, `userLabel` e
`saveTOTPBtn`. O Keycloak le no POST: totp, totpSecret, userLabel, mode,
logout-sessions e cancel-aia.
`totp.policy.algorithmKey` e o getAlgorithmKey() do base, pela propriedade.
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout displayRequiredFields=false displayMessage=!messagesPerField.existsError('totp','userLabel'); section>
<#if section = "selo">
<#elseif section = "header">
Verificação em duas etapas
<#elseif section = "sub">
Além da senha, cada login vai pedir um código do aplicativo autenticador do seu celular.
<#elseif section = "form">
Digite o código que o aplicativo mostra e dê um nome a este celular.
#if>
@layout.registrationLayout>
login-otp.ftl: |-
<#--
DIGITAR O CODIGO DO 2FA — o dono, a cada login com senha (o login por
passkey dispensa; ver `browser-2fa-dono` no molde).
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: titulo "Entrar",
como se fosse o login de novo, e um campo sem dizer de onde vem o codigo.
NAO MUDE OS IDS: o e2e (`frontend/e2e/apoio.ts`) acha a tela por
`kc-otp-login-form`, preenche `otp`, clica `kc-login` e detecta codigo
recusado por `input-error-otp-code`. O Keycloak le `otp` e, com mais de um
celular cadastrado, `selectedCredentialId`.
-->
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError('totp'); section>
<#if section = "selo">
<#elseif section = "header">
Código de verificação
<#elseif section = "sub">
Abra o aplicativo autenticador do seu celular e digite o código que ele mostra agora.
<#elseif section = "form">
#if>
@layout.registrationLayout>
webauthn-register.ftl: |-
<#--
CADASTRAR PASSKEY / CHAVE DE SEGURANCA — acoes obrigatorias
`webauthn-register-passwordless` (passkey, que dispensa a senha) e
`webauthn-register` (chave como segundo fator). As duas usam esta pagina.
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: "Registrar Chave de
Seguranca" e um botao "Cadastre-se" — que parece criar CONTA, nao cadastrar a
chave.
O
<#if !isSetRetry?has_content && isAppInitiatedAction?has_content>
#if>
#if>
@layout.registrationLayout>
webauthn-authenticate.ftl: |-
<#--
ENTRAR COM PASSKEY / CHAVE DE SEGURANCA — a etapa WebAuthn de um fluxo
(quando o Keycloak pede a chave numa tela propria, e nao pela sugestao no
campo de e-mail do login.ftl).
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: a lista de chaves
vinha com classes do PatternFly que o tema nao carrega, e saia como texto
corrido.
O
<#elseif section = "info">
<#if realm.registrationAllowed && !registrationDisabled??>
#if>
#if>
@layout.registrationLayout>
select-authenticator.ftl: |-
<#--
ESCOLHER COMO ENTRAR — o "Tente outra forma" de um fluxo com alternativas
(senha, passkey, codigo do celular...).
Ate 2026-10-08 era a pagina do tema BASE na moldura deste: cada opcao e um