hiparquia.dog — sitio F0-lite: Astro estático, cero JS, candado del protocolo en código
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import pw from '/home/claude/.npm-global/lib/node_modules/playwright/index.js';
|
||||
import http from 'http'; import fs from 'fs'; import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
const { chromium } = pw;
|
||||
|
||||
/* ioon-verificar · los seis escenarios adversos + lo que se mide con número.
|
||||
Se sirve dist/ en 127.0.0.1 y se mide ahí, no en el disco. */
|
||||
|
||||
const RAIZ = 'dist';
|
||||
const TIPO = { '.html':'text/html;charset=utf-8', '.css':'text/css', '.js':'text/javascript',
|
||||
'.woff2':'font/woff2', '.png':'image/png', '.json':'application/json' };
|
||||
const srv = http.createServer((req,res)=>{
|
||||
let p = decodeURIComponent(req.url.split('?')[0]);
|
||||
if (p.endsWith('/')) p += 'index.html';
|
||||
const f = path.join(RAIZ, p);
|
||||
if (!fs.existsSync(f) || fs.statSync(f).isDirectory()) { res.writeHead(404); return res.end('404'); }
|
||||
res.writeHead(200, { 'content-type': TIPO[path.extname(f)] ?? 'application/octet-stream' });
|
||||
res.end(fs.readFileSync(f));
|
||||
});
|
||||
await new Promise(r => srv.listen(4321, '127.0.0.1', r));
|
||||
const BASE = 'http://127.0.0.1:4321';
|
||||
const RUTAS = ['/', '/ficha/hombres-necios/', '/ficha/nada-te-turbe/'];
|
||||
|
||||
const b = await chromium.launch({ executablePath:'/opt/pw-browsers/chromium' });
|
||||
const R = {};
|
||||
|
||||
/* ── 1 · sin JavaScript ─────────────────────────────────────────────────── */
|
||||
{
|
||||
const c = await b.newContext({ javaScriptEnabled:false, viewport:{width:1280,height:900} });
|
||||
const p = await c.newPage();
|
||||
R.sinJS = [];
|
||||
for (const r of RUTAS) {
|
||||
await p.goto(BASE+r, { waitUntil:'load' });
|
||||
R.sinJS.push({ ruta:r,
|
||||
textoVisible: await p.evaluate(()=>document.body.innerText.trim().length),
|
||||
h1: await p.evaluate(()=>document.querySelector('h1')?.innerText ?? null),
|
||||
enlaces: await p.evaluate(()=>document.querySelectorAll('a').length) });
|
||||
}
|
||||
await c.close();
|
||||
}
|
||||
|
||||
/* ── 2 · movimiento reducido ────────────────────────────────────────────── */
|
||||
{
|
||||
const c = await b.newContext({ reducedMotion:'reduce', viewport:{width:1280,height:900} });
|
||||
const p = await c.newPage(); await p.goto(BASE+RUTAS[1]);
|
||||
R.movReducido = await p.evaluate(()=>{
|
||||
const a = document.querySelector('main a') ?? document.querySelector('a');
|
||||
return { duracion: getComputedStyle(a).transitionDuration,
|
||||
textoVisible: document.body.innerText.trim().length };
|
||||
});
|
||||
await c.close();
|
||||
}
|
||||
|
||||
/* ── 3 · pestaña oculta ─────────────────────────────────────────────────── */
|
||||
{
|
||||
const c = await b.newContext({ viewport:{width:1280,height:900} });
|
||||
const p = await c.newPage();
|
||||
await p.addInitScript(() => {
|
||||
Object.defineProperty(document,'visibilityState',{get:()=>'hidden'});
|
||||
Object.defineProperty(document,'hidden',{get:()=>true});
|
||||
window.requestAnimationFrame = () => 0; // rAF congelado
|
||||
});
|
||||
await p.goto(BASE+RUTAS[1]);
|
||||
R.oculta = await p.evaluate(()=>{
|
||||
const inv = [...document.querySelectorAll('h1,h2,p,li')]
|
||||
.filter(e=>e.innerText.trim() && getComputedStyle(e).opacity !== '1').length;
|
||||
return { textoVisible: document.body.innerText.trim().length, elementosInvisibles: inv };
|
||||
});
|
||||
await c.close();
|
||||
}
|
||||
|
||||
/* ── 4 · 360 px · 5 · táctil · 6 · objetivos de toque ───────────────────── */
|
||||
{
|
||||
for (const [nom, opc] of [
|
||||
['ancho360', { viewport:{width:360,height:740} }],
|
||||
['tactil', { viewport:{width:390,height:844}, hasTouch:true, isMobile:true,
|
||||
userAgent:'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)' }]]) {
|
||||
const c = await b.newContext(opc); const p = await c.newPage();
|
||||
R[nom] = [];
|
||||
for (const r of RUTAS) {
|
||||
await p.goto(BASE+r);
|
||||
R[nom].push(await p.evaluate(ruta => {
|
||||
const d = document.documentElement;
|
||||
const toques = [...document.querySelectorAll('a')].map(a=>{
|
||||
const b = a.getBoundingClientRect();
|
||||
return { texto:a.innerText.trim().slice(0,22), alto:+b.height.toFixed(1), ancho:+b.width.toFixed(1) };
|
||||
}).filter(t=>t.alto>0);
|
||||
return { ruta,
|
||||
desbordeH: d.scrollWidth - d.clientWidth,
|
||||
peorToque: Math.min(...toques.map(t=>t.alto)),
|
||||
bajo24: toques.filter(t=>t.alto < 24).map(t=>t.texto),
|
||||
bajo44: toques.filter(t=>t.alto < 44).map(t=>t.texto) };
|
||||
}, r));
|
||||
}
|
||||
await c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/* ── lo que se mide con número ──────────────────────────────────────────── */
|
||||
{
|
||||
const c = await b.newContext({ viewport:{width:1280,height:900} });
|
||||
const p = await c.newPage();
|
||||
const red = [];
|
||||
p.on('response', r => red.push({ url:r.url(), tipo:r.request().resourceType() }));
|
||||
await p.goto(BASE+RUTAS[1], { waitUntil:'networkidle' });
|
||||
R.recursos = red.map(r=>({ tipo:r.tipo, url:r.url.replace(BASE,'') }));
|
||||
R.js = red.filter(r=>r.tipo==='script').length;
|
||||
/* anchos durante el hover: un enlace que ensancha empuja a sus vecinos */
|
||||
R.hover = await p.evaluate(async () => {
|
||||
const a = document.querySelector('main a') ?? document.querySelector('header a');
|
||||
if (!a) return null;
|
||||
const antes = a.getBoundingClientRect().width;
|
||||
a.dispatchEvent(new MouseEvent('mouseover',{bubbles:true}));
|
||||
a.classList.add('__h');
|
||||
await new Promise(r=>setTimeout(r,200));
|
||||
return { antes:+antes.toFixed(2), despues:+a.getBoundingClientRect().width.toFixed(2) };
|
||||
});
|
||||
R.meta = await p.evaluate(()=>({
|
||||
lang: document.documentElement.lang,
|
||||
titulo: document.title,
|
||||
descripcion: document.querySelector('meta[name=description]')?.content?.length ?? 0,
|
||||
ogImage: document.querySelector('meta[property="og:image"]')?.content ?? null,
|
||||
canonica: document.querySelector('link[rel=canonical]')?.href ?? null,
|
||||
jsonld: !!document.querySelector('script[type="application/ld+json"]'),
|
||||
saltar: !!document.querySelector('.saltar'),
|
||||
h1: document.querySelectorAll('h1').length,
|
||||
}));
|
||||
await c.close();
|
||||
}
|
||||
await b.close(); srv.close();
|
||||
|
||||
/* bytes en el cable, comprimidos */
|
||||
const gz = (f) => +(execSync(`gzip -c9 ${f} | wc -c`).toString().trim());
|
||||
R.bytes = {
|
||||
indexHTML: gz('dist/index.html'),
|
||||
fichaHTML: gz('dist/ficha/hombres-necios/index.html'),
|
||||
fuenteWOFF2: fs.statSync('dist/Commissioner-wght.woff2').size,
|
||||
ogPNG: fs.statSync('dist/og/hombres-necios.png').size,
|
||||
};
|
||||
console.log(JSON.stringify(R, null, 1));
|
||||
Reference in New Issue
Block a user