fix: carregador do mural à prova de loop no celular

build-atlas.js gera páginas leves de 3072 px para celular (--so-mobile
regera só elas) e registra o campo mobile no atlas-map.json.

wall.html usa essas páginas em toque ou GPU/aparelho fraco, detecta pela
marca em sessionStorage que a tentativa anterior morreu e força o modo leve,
e trata falha de download, de decodificação e perda de contexto WebGL com
aviso e botão, sem recarregar sozinho. Detalhes e testes no achado 3.5.
This commit is contained in:
rodado
2026-09-14 12:37:59 +02:00
parent 10b7cb0cb9
commit fc45ccd9d0
4 changed files with 193 additions and 7 deletions
+31 -1
View File
@@ -19,6 +19,7 @@
*
* Uso:
* node web/build-atlas.js [--n=99999] [--out=atlas] [--thumbs=DIR]
* node web/build-atlas.js --so-mobile (só as páginas leves, a partir do atlas pronto)
* node web/build-atlas.js [--n=99999] [--out=atlas] [--downloads=~/Downloads]
*/
@@ -48,6 +49,12 @@ const ATLAS_SIZE = 8192; // dentro do limite de textura de qualquer GPU moderna
const TILES_PER_ROW = ATLAS_SIZE / TILE_SIZE; // 64 -> capacidade 4096 peças por atlas
const TILES_PER_ATLAS = TILES_PER_ROW * TILES_PER_ROW;
const CATALOGO = path.resolve(__dirname, '..', 'pipeline', 'catalogo.jsonl');
// light pages for phones: the same layout scaled down, so every tile keeps its
// atlas/col/row and only the page size changes. 3072² stays under maxTextureSize
// everywhere and under the ~16.7 M-pixel image decode ceiling of iOS Safari — a
// phone never has to decode an 8192² page, which is what killed the tab.
const MOBILE_TILE = 48;
const MOBILE_SIZE = TILES_PER_ROW * MOBILE_TILE; // 3072
// ── acha os arquivos já baixados pela Trilha B em ~/Downloads ────────────────
// nomes: BRAS__20.500.12156.1_<id>__THUMB__thumb_2.jpg (preferido, leve)
@@ -201,6 +208,27 @@ async function realTile(filePath) {
.toBuffer();
}
// ── light pages for phones, from the desktop pages already on disk ────────────
async function buildMobile(outDir) {
const mapPath = path.join(outDir, 'atlas-map.json');
const mapData = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
const atlases = [];
for (let a = 0; a < mapData.atlases.length; a++) {
const name = `atlas-m-${a}.webp`;
await sharp(path.join(outDir, mapData.atlases[a]))
.resize(MOBILE_SIZE, MOBILE_SIZE, { kernel: 'lanczos3' })
.webp({ quality: 82, effort: 4 })
.toFile(path.join(outDir, name));
const mb = (fs.statSync(path.join(outDir, name)).size / 1024 / 1024).toFixed(2);
console.log(`Salvo ${name} (${mb} MB, ${MOBILE_SIZE}px)`);
atlases.push(name);
}
mapData.mobile = { size: MOBILE_SIZE, tile: MOBILE_TILE, atlases };
fs.writeFileSync(mapPath, JSON.stringify(mapData));
console.log(`atlas-map.json: variante leve registrada (${atlases.length} páginas de ${MOBILE_SIZE}px)`);
}
// ── main ────────────────────────────────────────────────────────────────────
async function main() {
@@ -284,6 +312,8 @@ async function main() {
const mapPath = path.join(OUT_DIR, 'atlas-map.json');
fs.writeFileSync(mapPath, JSON.stringify(mapData));
console.log(`Salvo atlas-map.json (${(fs.statSync(mapPath).size / 1024).toFixed(0)} KB)`);
await buildMobile(OUT_DIR);
}
main().catch(e => { console.error(e); process.exit(1); });
// --so-mobile: only regenerate the light pages from the desktop atlas on disk
(args['so-mobile'] ? buildMobile(OUT_DIR) : main()).catch(e => { console.error(e); process.exit(1); });