feat(vida): ideias do dia com inspiração da internet e "olha o que eu aprendi"

Todo dia, a partir das 9h, o Carinha cria 3 atividades novas (inspiracao.py):
- sinais de fontes abertas: tempo da cidade (Open-Meteo), datas
  comemorativas da semana (lista fixa + feriados da BrasilAPI), "neste dia"
  da Wikipédia e assuntos em alta no Google Trends Brasil;
- o Gemini escolhe ideias leves (descarta tragédia, política e afins) e
  cada uma vira atividade pelo estúdio (Criacao.gerar), com motivo e uma
  curiosidade;
- até 3 tentativas por dia, 30 min entre elas; botão "Criar mais agora";
- as atividades criadas entram na rotina do modo vida (as de hoje com 40%
  de chance) e, com alguém perto, ele conta a curiosidade: legenda e voz
  de robô (ordem "aprendi", até 2x por dia cada);
- painel: cartão "Ideias do dia" no estúdio (Mostrar, Contar, o que ele
  viu na internet, ajustes: quantas, horário, cidade, fontes) e tópico no
  guia.

Correções achadas no caminho:
- WebView de fábrica do S10 (Chrome 102) não tem AbortSignal.timeout: o
  celular nunca mandava o status e a placa o via offline;
- arquivos estáticos com Cache-Control no-cache e o app com LOAD_NO_CACHE:
  o WebView seguia com uma carinha.html velha depois de deploy;
- status do celular recusado vai para o journal (1 linha por minuto);
- legenda mais estreita para não cobrir a bateria.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-08 08:13:03 -03:00
co-authored by Claude Opus 5.5
parent 43770c6d67
commit 4777ceb023
19 changed files with 1179 additions and 30 deletions
+22 -1
View File
@@ -80,6 +80,11 @@
<script src="rosto-motor.js"></script>
<script src="voz-robo.js"></script>
<script>
// WebView de fábrica do S10 (Chrome 102) não tem AbortSignal.timeout (só no 103): sem isto o celular
// nunca mandava o status e a placa achava que ele estava offline.
if (window.AbortSignal && !AbortSignal.timeout) {
AbortSignal.timeout = (ms) => { const c = new AbortController(); setTimeout(() => c.abort(), ms); return c.signal; };
}
/* Token do servidor (o app Android abre a página com ?t=...): vai em todo pedido ao
próprio servidor, inclusive os de reacoes-render.js. Sem ?t= nada muda. */
(function () {
@@ -729,8 +734,24 @@ function trocarRosto(nome) {
return true;
}
/* "Olha o que eu aprendi hoje!": a curiosidade de uma ideia do dia, em legenda e na voz de robô. */
function contarAprendi(texto) {
if (window.carinhaSemServidor || !texto) return;
if (r.dormindo) r.dormindo = false;
atividade(null);
animar("surpreso");
setTimeout(() => {
const frase = "Olha o que eu aprendi hoje! " + texto;
if (window.MotorRosto) window.MotorRosto.faixa("legenda", { texto: frase, segundos: Math.max(6, Math.min(16, frase.length / 10)) });
falar(frase);
animar("feliz");
}, 900);
}
function trata(ordem) {
ordem = (ordem || "").trim().toLowerCase();
const bruta = (ordem || "").trim();
if (/^aprendi /i.test(bruta)) { contarAprendi(bruta.slice(8).trim()); return; } // sem minúsculas: é uma frase
ordem = bruta.toLowerCase();
if (!ordem) return;
if(ordem==='recarregar') {
if(new URLSearchParams(location.search).get('aparelho')==='1') {
+1 -1
View File
@@ -487,7 +487,7 @@
// efeito de digitação; se não couber, mostra o final do que já foi "dito"
let txt = faixa.texto.slice(0, Math.floor((o.t - faixa.t0) * 30));
c.font = '500 ' + fonte + 'px system-ui,sans-serif'; c.textAlign = 'center'; c.textBaseline = 'middle';
const max = W * 0.8;
const max = W * 0.7; // deixa livre o canto da bateria
while (txt.length > 1 && c.measureText(txt).width > max) txt = txt.slice(1);
c.fillText(txt, W / 2, y);
} else if (faixa.modo === 'digitando') {