- TypeScript 71%
- HTML 19.6%
- Python 7.4%
- JavaScript 1.5%
- CSS 0.5%
|
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m48s
|
||
|---|---|---|
| .forgejo/workflows | ||
| kanji-sets | ||
| scripts | ||
| src | ||
| .gitignore | ||
| .kanjivg.url | ||
| .sqlite-wasm.url | ||
| apple-touch-icon.png | ||
| build_db.ts | ||
| favicon.ico | ||
| favicon.svg | ||
| index.html | ||
| input.css | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tailwind.config.js | ||
| tsconfig.json | ||
Kanji Grader (FSRS)
Um aplicativo de prática manuscrita de japonês e revisão por repetição espaçada, totalmente offline e rodando no navegador. O usuário desenha kanji (ou kana) numa canvas HTML e é pontuado em ordem dos traços, direção dos traços e precisão da forma. O progresso de aprendizado é gerenciado pelo algoritmo FSRS, com todos os dados persistidos localmente no navegador via SQLite compilado para WebAssembly e armazenado no Origin Private File System (OPFS).
Em produção: https://kaku.ggjz.org
Funcionalidades
Modos de operação
| Modo | Descrição |
|---|---|
| Modo Revisão (padrão) | Fila diária guiada pelo FSRS. Os caracteres são apresentados automaticamente conforme o agendamento de repetição espaçada. Uma nota de aprovação avança o card; reprovação reagenda o card para a mesma sessão. |
| Modo Prática | Seleção livre. Escolha qualquer hiragana, katakana ou kanji a partir de uma grade de caracteres e desenhe livremente. As pontuações são exibidas mas não são registradas. |
| Modo Quiz | Testa recall ativo: o app apresenta uma dica e você desenha o caractere correspondente de memória. Cada quiz concluído é salvo no Histórico de Quizzes. |
O Modo Prática suporta os seguintes conjuntos de caracteres:
- Hiragana / Katakana com botões de modificador Dakuten / Handakuten (Normal, Dakuten, Handakuten). Selecionar um modificador substitui o kana base no lugar (ex.: は → ば ou ぱ) em vez de mostrar caracteres separados.
- Kanji por série Jouyou (1–6 e ginásio), com uma visão unificada "Jouyou (Todos)" ordenada por contagem de traços.
- Ensino médio (kanji Jinmeiyō).
- Níveis Kanken 4, 3, 2.5 e 2.
O Modo Quiz oferece dois tipos de teste:
- Kana — a dica é o romaji (ou o kana equivalente) e você desenha o hiragana/katakana. Avança automaticamente.
- Kanji (Jōyō) — agrupado como no Modo Prática (1º–6º ano, Chūgakkō, Jōyō=todos os 2136). A dica é uma combinação alternável de significado / on'yomi / kun'yomi (ligue/desligue cada uma ao vivo durante o quiz) que você usa para desenhar o kanji de memória. Após avaliar, o kanji correto e sua ordem de traços são revelados para conferência, e um botão "Próximo →" avança. As dicas vêm de
src/quiz-data.json.
Desenho e pontuação
- Canvas 300 × 300 com suporte a mouse e toque (compatível com iPad).
- Sobreposição de guia de referência (alternável) renderiza os traços corretos com indicadores numerados de ordem por trás da camada de desenho, com 30 % de opacidade.
- Sobreposição de grade com cruz central e linhas tracejadas opcionais nos quadrantes, alternável independentemente.
- Desfazer (por traço, também via Ctrl+Z / Cmd+Z) e Limpar.
- A pontuação avalia três métricas independentes, cada uma com nota 0–100 %:
- Ordem dos Traços — Se cada traço desenhado corresponde geometricamente ao traço de referência sequencial correto. Usa centroide + comparação ponto a ponto normalizada com um portão de distância.
- Direção dos Traços — Se cada traço foi desenhado na direção correta (início-para-fim vs fim-para-início).
- Precisão da Forma — Erro ponto a ponto do traço do usuário reamostrado e normalizado contra a referência, com penalidade por razão de comprimento para traços muito curtos ou muito longos.
- Uma pontuação total ponderada combina as três métricas. Os pesos são ajustáveis via um slider de dois polegares personalizado no modal de Configurações (padrão: Ordem 30 %, Direção 30 %, Forma 40 %).
- Cada métrica tem também um slider de tolerância independente (0–1) que suaviza ou endurece a curva de penalidade.
- Uma nota de aprovação configurável (padrão 90 %) determina se o card FSRS recebe Bom ou De Novo.
Presets de pontuação e regras avançadas
Três presets de pontuação dão perfis rápidos de dificuldade:
| Preset | Pesos | Tolerâncias | Regras |
|---|---|---|---|
| Mais Fácil | Forma 100 % | Todas 1.0 | Casamento 1-para-1, sem reforço de primeiro/último, direção relaxada |
| Normal (padrão) | Ord 30 / Dir 30 / Forma 40 | Todas 1.0 | Casamento 1-para-1, reforço do primeiro e último traço |
| Mais Difícil | Ord 30 / Dir 30 / Forma 40 | Todas 0.5 | Casamento 1-para-1, primeiro e último, direção estrita |
Regras avançadas de pontuação (checkboxes em Configurações):
- Forçar Casamento 1-para-1 de Traços — Cada traço do usuário só pode casar com um traço de referência; previne que um único traço bem desenhado infle as pontuações.
- Forçar Ordem do Primeiro e Último Traço — Penaliza fortemente ordem/forma se o primeiro ou último traço não casar. Crítico para forma adequada do kanji.
- Direção Estrita dos Traços — A pontuação de direção fica binária (0 ou 100) em vez de crédito parcial (50/100).
Repetição espaçada (FSRS)
- Usa
ts-fsrs(v3.5+) com fuzzing habilitado e retenção desejada configurável (padrão 90 %). - Fila diária construída a partir de três níveis de prioridade:
- Cards em Aprendizado / Reaprendizado vencidos agora.
- Cards de Revisão vencidos a qualquer hora hoje.
- Cards Novos (até um limite diário configurável, padrão 20).
- Cards avaliados como "De Novo" que ainda estão vencidos hoje são reenfileirados no final da sessão atual.
- O contador de cards novos persiste através de recarregamentos da página dentro do mesmo dia.
- Navegador de Cards lista todos os cards com rótulos de tempo de vencimento relativos e ações por card: Resetar (apaga o histórico FSRS) e Mover para o Topo (força um card para a frente da fila de cards novos).
Estatísticas
- Painel de estatísticas da sessão (coluna esquerda) mostra contagens restantes de Novos / Aprendendo / Revisando para a sessão atual.
- Gráfico de pizza total (gradiente cônico) com legenda mostra a distribuição global em todos os caracteres do banco.
Configurações e ferramentas de depuração
- Modal de Configurações: cards novos/dia, retenção desejada do FSRS, nota de aprovação, slider de pesos, sliders de tolerância por métrica, presets de pontuação, regras avançadas.
- Modal de Debug: viagem no tempo (+1 dia para testar FSRS), resetar progresso, resetar banco e configurações, apagar o arquivo OPFS do banco.
- Alternância Tema Escuro / Claro (padrão escuro), persistida em
localStorage.
Arquitetura
100 % cliente, offline-first
A aplicação inteira roda no navegador, com zero backend. Não há lógica do lado servidor, chamadas de API ou contas de usuário. Uma vez cacheado o banco SQLite no OPFS, o aplicativo funciona totalmente offline.
Arquitetura TypeScript modular
O código-fonte está dividido em módulos focados:
| Arquivo | Função |
|---|---|
src/app.ts |
Classe principal KanjiGrader: lógica de UI, desenho no canvas, eventos, fluxo de revisão FSRS, configurações, gerenciamento de modos, lógica de modificadores de kana |
src/scoring.ts |
Classe estática GeometryUtil (amostragem de path, reamostragem, normalização, centroide, comprimento de arco) e função calculateGeometricScore() |
src/grading.ts |
Integração com FSRS: gradeKanji() calcula o próximo estado do card, updateFsrsParams() configura a retenção |
src/types.ts |
Interfaces TypeScript compartilhadas: KanjiRow, FsrsCardRow, WorkerMessage, WorkerResponse |
src/worker.ts |
Web Worker dedicado: gerenciamento de SQLite/OPFS, download do banco, montagem da fila diária, CRUD de cards, estatísticas |
src/kanji-data.json |
Dados de caracteres agrupados por série e ordenados por traços (gerado pelos scripts do pipeline) |
Web Worker + SQLite WASM + OPFS
Todas as operações de banco rodam num Web Worker dedicado (worker.ts) para manter a thread de UI responsiva. O worker:
- No primeiro lançamento, baixa o banco pré-compilado
kanjivg.sqlite3.gzviafetch(), descompacta e salva no OPFS. - Em lançamentos seguintes, detecta o arquivo OPFS existente e pula o download.
- Lida com todas as leituras/escritas: buscar dados SVG, salvar estado do card FSRS, montar a fila diária, computar estatísticas e executar resets.
O SQLite é carregado via importScripts('sqlite3.js') (o build oficial em WASM) usando o VFS OpfsDb para armazenamento durável. As escritas OPFS usam createSyncAccessHandle() quando disponível (Safari/iOS), com createWritable() como fallback.
Por que HTTPS é obrigatório (cabeçalhos COOP / COEP)
createSyncAccessHandle() do OPFS e SharedArrayBuffer exigem isolamento de origem cruzada. Em produção, o CloudFront serve cada resposta com:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Esses cabeçalhos são injetados por uma política customizada de Response Headers anexada à distribuição CloudFront — sem eles a página carrega, mas o OPFS falha silenciosamente e o worker não consegue persistir o banco.
Algoritmo de pontuação geométrica
A pontuação é puramente geométrica (sem modelo ML), implementada em scoring.ts:
- Cada traço do usuário é reamostrado para 50 pontos igualmente espaçados.
- Os pontos são normalizados pela bounding box num espaço de coordenadas centrado de 100 unidades.
- Cada traço do usuário é casado com seu melhor traço de referência minimizando uma métrica combinada de erro de ponto normalizado + distância de centroide.
- Reforço 1-para-1 (quando habilitado) impede que múltiplos traços do usuário reivindiquem o mesmo traço de referência.
- Ordem dos traços está correta apenas se o traço do usuário i casa melhor com o traço de referência i e passa por um portão de forma/distância. O reforço de primeiro/último traço (quando habilitado) aplica penalidade de 50 % na ordem e 80 % na forma em caso de erro.
- Direção é avaliada por traço: o ponto inicial do usuário é comparado com ambos os extremos do traço de referência. Em modo estrito a direção é binária (0/100); em modo leniente, traços invertidos recebem 50 %.
- Forma é o erro médio normalizado de ponto, escalado por uma penalidade quadrática de razão de comprimento e uma penalidade por diferença na quantidade de traços (25 % por traço extra/faltando, limitada a 100 %).
- As curvas de tolerância por métrica suavizam as pontuações:
nota = 100 − (100 − bruta) / tolerância. - Total ponderado final:
total = (ordem × pOrdem) + (direção × pDir) + (forma × pForma).
Estrutura do repositório
.forgejo/workflows/deploy.yml # CI: build + deploy para S3 + CloudFront
.kanjivg.url # URL do release do KanjiVG (editar para atualizar)
.sqlite-wasm.url # URL do build oficial do SQLite WASM
src/ # Código-fonte TypeScript
scripts/ # Pipeline de dados (Python + test_scoring.ts)
kanji-sets/ # Listas de caracteres (com atribuição por subpasta)
app.kanjialive.com/ # japanese-radicals.csv + LICENSE
kanji.jitenon.jp/ # shougakkou, jouyou_new, jinmeiyou, kanken*
index.html # Página única servida pela CDN
input.css # Entrada do Tailwind
tailwind.config.js
tsconfig.json
package.json / package-lock.json
build_db.ts # Constrói kanjivg.sqlite3.gz a partir do zip do KanjiVG
README.md
Desenvolvimento local
Pré-requisitos: Node 20+, Python 3.10+ (apenas para os scripts em scripts/), python3/make/g++ (para a compilação nativa do better-sqlite3).
# Instalar dependências
npm install
# Baixar os arquivos vendored uma única vez (o CI faz isso automaticamente)
curl -fsSL "$(cat .kanjivg.url)" -o "$(basename $(cat .kanjivg.url))"
curl -fsSL "$(cat .sqlite-wasm.url)" -o /tmp/sqlite-wasm.zip
DIR=$(basename $(cat .sqlite-wasm.url) .zip)
unzip -j /tmp/sqlite-wasm.zip "${DIR}/jswasm/sqlite3.js" "${DIR}/jswasm/sqlite3.wasm" "${DIR}/jswasm/sqlite3-opfs-async-proxy.js" -d .
# Construir o banco SQLite a partir do zip do KanjiVG
npx tsx build_db.ts
# Build da aplicação (esbuild + Tailwind)
npm run build
# Rodar testes da pontuação
npm test
Testar localmente (com OPFS funcionando)
npm run build # gera dist/bundle.js, dist/worker.js, dist/output.css
npm run serve # → http://localhost:8000
npm run serve roda scripts/serve_local.py (só usa a biblioteca padrão do Python). Abra http://localhost:8000 — não use 127.0.0.1 por outro hostname nem outra porta sem ajustar.
Após o build, todos os arquivos servidos em produção ficam no diretório raiz (index.html, sqlite3.js, sqlite3.wasm, sqlite3-opfs-async-proxy.js, kanjivg.sqlite3.gz) mais dist/ (bundle.js, worker.js, output.css).
Pipeline de dados
src/kanji-data.json (a lista mestre de caracteres agrupada por série e ordenada por traços) é mantida pelos scripts em scripts/. Esses scripts só precisam rodar quando você quer regenerar o JSON a partir das listas em kanji-sets/.
# Reconstruir séries 1–6 a partir de shougakkou.txt
python scripts/rebuild_grades_1to6.py
# Reconstruir kanji do ginásio (Jouyou menos séries 1–6)
python scripts/rebuild_middle_school.py
# Adicionar kanji Jinmeiyou (ensino médio)
python scripts/add_high_school_kanji.py
# Adicionar níveis Kanken
python scripts/add_kanken_kanji.py
# Cross-referência da cobertura de SVG vs kanji-data.json
python scripts/analyze_kanji.py
O analyze_kanji.py e o recover_missing_kanji.py dependem dos diretórios kanji/ e others/ (ambos não versionados — disponíveis localmente). Para baixar os SVGs originais do KanjiVG, basta extrair o zip apontado por .kanjivg.url.
src/quiz-data.json alimenta o quiz de kanji (desenhar de memória a partir de significados / on'yomi / kun'yomi, agrupado por série como no Modo Prática). Ele é versionado e empacotado pelo esbuild dentro de dist/bundle.js (assim como src/kanji-data.json), então só precisa ser regerado quando os dados de origem mudam.
Atribuição dos conjuntos de caracteres
shougakkou.txt— Kanji do ensino fundamental por série (学年別漢字配当表), definidos pelo Ministério da Educação do Japão (MEXT).jouyou_new.txt— Lista Jouyou (常用漢字表), fixada por diretriz do Gabinete japonês (内閣告示).jinmeiyou.txt— Kanji Jinmeiyō (人名用漢字), fixados pelo Ministério da Justiça do Japão.kanken1.txt–kanken4.txt,kanken1.5.txt,kanken2.5.txt— Kanji por nível Kanken (漢検), definidos pela Fundação Kanken (日本漢字能力検定協会).
A página kanji.jitenon.jp foi a fonte de conveniência da qual essas listas foram copiadas com Ctrl+c/Ctrl+v
kanji-sets/app.kanjialive.com/japanese-radicals.csv vem da tabela de radicais original do projeto Kanji Alive redistribuído sob a licença Creative Commons indicada em kanji-sets/app.kanjialive.com/LICENSE.md.
Os SVGs de ordem dos traços (não versionados; baixados pelo CI) vêm do projeto KanjiVG.
Dependências externas
| Dependência | Função |
|---|---|
| ts-fsrs | Algoritmo de agendamento por repetição espaçada |
| KanjiVG | Dados SVG de ordem dos traços para kanji, hiragana, katakana |
| SQLite WASM | SQLite no cliente via sqlite3.js + sqlite3.wasm + proxy assíncrono OPFS |
| esbuild | Bundler TypeScript |
| Tailwind CSS v3 | Framework CSS utility-first |
| tsx | Execução de TypeScript (usada pelo npm test e build_db.ts) |