PageSourceSearch

https://originsurname.com/_kit/js/ads.js?v=20261001-221937

js originsurname.com collected 2026-10-06 14:11:18 UTC 50,406 bytes, 912 lines download raw bytes

1/**
2 * Cargador de anuncios de la red. Antes vivia EN LINEA en el pie de
3 * apellidos.de: 31.418 B en CADA pagina vista, sin cachear nunca.
4 *
5 * POR QUE SALE DE AHI
6 * Medido el 8-ago-2026 sobre la ficha de apellidos.de: el JS en linea eran
7 * 49.109 B en bruto y 16.195 B comprimidos, o sea EL 50,2 % del peso de la
8 * pagina, y este bloque solo era 31.418 de esos 49.109. Es el mismo argumento
9 * que saco el CSS a un fichero externo: la hoja son 20 KB que se descargan UNA
10 * vez y valen para toda la navegacion; incrustada costaba ese peso en cada
11 * pagina y no se cacheaba jamas.
12 *
13 * Va con la version en la URL y Cache-Control de un ano (lo pone el .htaccess
14 * del kit), asi que al cambiarlo hay que pasar `php _kit/build.php` ANTES de
15 * `sync.php`, o los navegadores siguen con el fichero viejo.
16 *
17 * LA CONFIGURACION LLEGA EN `window.KIT_ADS`, que emite `kit_ads_js()` en
18 * `_kit/php/ads.php`. Son tres valores --la cuenta, el endpoint de INP y el
19 * idioma-- y eran las tres unicas interpolaciones de PHP que habia en 600
20 * lineas: por eso este bloque se podia sacar entero y el de la interfaz no.
21 *
22 * NO SE HA TOCADO NI UNA LINEA DE LA LOGICA. Lo que sigue es literalmente lo
23 * que habia, con esas tres sustituciones. Los comentarios de dentro explican
24 * decisiones medidas (INP, slots eager, requestIdleCallback) y valen mas que
25 * el codigo: no se resumen.
26 */
27/* ===========================================================================
28 * EL HUECO SE LLAMA `.kit-hueco`, Y `.ad-slot` SIGUE ACEPTANDOSE. 25-ago-2026.
29 * ===========================================================================
30 * La clase se llamaba `.ad-slot`. EasyList la esconde con una regla GENERICA
31 * --linea 6770, `##.ad-slot`, sin dominio delante-- asi que en cualquier
32 * navegador con bloqueador el hueco salia `display:none` y todas las unidades
33 * median 0x0 en el pixel 0. La pagina PARECIA no tener anuncios: cualquier
34 * medida de posicion hecha asi valia cero y no lo parecia. Comprobado tambien
35 * contra EasyList Spanish y los filtros de uBO: `kit-hueco` no aparece en
36 * ninguna de las tres.
37 *
38 * LOS SELECTORES ACEPTAN LOS DOS NOMBRES A PROPOSITO, y esto no es pereza:
39 * el kit y el marcado de los veintidos docroots se despliegan por FTP EN
40 * SERIE, uno detras de otro. Con un solo nombre, cualquier ventana en la que
41 * un sitio tenga el kit nuevo y el marcado viejo --o al reves-- deja sus
42 * unidades sin observador, y una unidad que nadie observa no es perezosa, es
43 * MUERTA: no da error, simplemente no hay anuncio.
44 *
45 * SE QUITA `.ad-slot` de aqui cuando `grep ad-slot` sobre el marcado de los
46 * veintidos docroots devuelva cero EN PRODUCCION, no en el arbol local.
47 * =========================================================================== */
48(function () {
49    'use strict';
50    var KIT_ADS = window.KIT_ADS || {};
51    /* SE SALE SI NO HAY CUENTA **NI** UNIDADES, no solo si falta la cuenta.
52     *
53     * Aqui ponia `if (!KIT_ADS.client) { return; }`, y eso anulaba el respaldo
54     * que este mismo fichero documenta treinta lineas mas abajo: sacar la
55     * cuenta del `data-ad-client` de la primera unidad. En un sitio cuyo
56     * publisher no viva en `$adsenseid` --colorearnombre lo tiene en una
57     * constante, globalsurnames en un include suyo-- `kit_ads_js()` emite la
58     * cuenta vacia, y con la guarda vieja el cargador se iba antes de mirar el
59     * DOM: las unidades puestas y NADIE que pida adsbygoogle.js.
60     *
61     * El script va con `defer`, asi que cuando esto corre el HTML ya esta
62     * analizado y las unidades se pueden consultar. */
63    if (!KIT_ADS.client
64        && !document.querySelector('.kit-hueco ins.adsbygoogle[data-ad-client],.ad-slot ins.adsbygoogle[data-ad-client]')) {
65        return;                        // ni cuenta ni unidades: nada que cargar
66    }
67(function(){
68    // Configuración
69    // LA CUENTA SALE DE LAS PROPIAS UNIDADES SI NO LA MANDAN.
70    //
71    // Cada sitio guarda su publisher donde le da la gana: unos en $adsenseid,
72    // colorearnombre en una constante COLOR_ADS_CLIENTE, globalsurnames en un
73    // include que ni siquiera esta en common/. Perseguir esa variable sitio por
74    // sitio es lo que dejo a dos de ellos con `client=` vacio y CERO cargas del
75    // script -- las unidades estaban, y no las pedia nadie.
76    //
77    // El <ins> ya lleva data-ad-client y es la fuente de verdad: si el bloque no
78    // pertenece a esa cuenta no se rellena, asi que aqui no puede haber otra.
79    function clienteDelDom(){
80        var i=document.querySelector('.kit-hueco ins.adsbygoogle[data-ad-client],.ad-slot ins.adsbygoogle[data-ad-client]');
81        return i?i.getAttribute('data-ad-client'):'';
82    }
83    // SI LAS DOS NO COINCIDEN, GANA EL DOM.
84    //
85    // `KIT_ADS.client` sale de una variable de PHP y el `<ins>` sale de quien
86    // pinto la unidad. Cuando difieren, el equivocado es SIEMPRE el primero: un
87    // bloque solo se rellena si se pide con la cuenta a la que pertenece, asi
88    // que la cuenta del `<ins>` es la unica que puede funcionar.
89    //
90    // Pasaba en apellidosmapa.com el 13-ago-2026: unidades de
91    // pub-0680240524407204 y cargador pidiendo pub-8178564232918506. Cero
92    // relleno, ningun error, y desde fuera parecia falta de inventario.
93    // EN ESCRITORIO, EL FORMATO DE SIEMPRE. 26-sep-2026.
94    //
95    // Las unidades display de apellidos.de van a tamaño fijo (sin data-ad-format; el
96    // tamaño lo pone kit.css) para que en movil no crezcan mas que su hueco: era el CLS
97    // de campo. Pero en escritorio el hueco mide 952 px y un 336x280 fijo queda como un
98    // cuadrado en medio de la columna, tambien en los huecos HORIZONTALES, que antes
99    // servian bandas a lo ancho. El propietario lo vio el mismo dia: «veo todos los
100    // anuncios cuadrados en lugar de horizontales». En escritorio el CLS de campo ya era
101    // bueno, asi que alli se devuelve a cada <ins> el formato que tenia --lo guarda en
102    // data-kit-formato / data-kit-fwr-- ANTES de pedir ningun anuncio: identico a antes.
103    // matchMedia con el mismo corte que la hoja (1024 px): no lee layout.
104    //
105    // 1-oct-2026: desde hoy `kit_ad()` emite asi TODAS las unidades display de la red
106    // (antes solo apellidos.de), y se repite justo antes de cada push(): push({}) rellena
107    // la PRIMERA <ins> sin procesar del DOM, no la que se le diga, asi que una unidad
108    // anadida despues de arrancar (un listado que carga mas) se pediria sin formato.
109    var ESCRITORIO = !!(window.matchMedia && window.matchMedia('(min-width: 1024px)').matches);
110    function formatoDeEscritorio(){
111        if (!ESCRITORIO) { return; }
112        var fijas = document.querySelectorAll('ins.adsbygoogle[data-kit-formato]:not([data-ad-format])');
113        for (var kf = 0; kf < fijas.length; kf++) {
114            fijas[kf].setAttribute('data-ad-format', fijas[kf].getAttribute('data-kit-formato'));
115            var fwr = fijas[kf].getAttribute('data-kit-fwr');
116            if (fwr) { fijas[kf].setAttribute('data-full-width-responsive', fwr); }
117        }
118    }
119    formatoDeEscritorio();
120    var AD_DOM=clienteDelDom();
121    var AD_CLIENT=AD_DOM||KIT_ADS.client;
122    if(AD_DOM&&KIT_ADS.client&&AD_DOM!==KIT_ADS.client&&window.console&&console.warn){
123        console.warn('[kit-ads] la cuenta del cargador ('+KIT_ADS.client+') no es la de las unidades ('+AD_DOM+'); se usa la de las unidades');
124    }
125    // 250px, no 1200px.
126    //
127    // 1200 px es pedir el anuncio cuando falta pantalla y media para verlo: en
128    // una ficha con cinco huecos separados eso los pedia casi todos de una,
129    // que es justo lo que se quiere evitar. Con 250 px el anuncio se pide poco
130    // antes de asomar --lo justo para que no se vea el hueco vacio al bajar--
131    // y el que nunca se mira no se pide nunca.
132    //
133    // No se baja a 0: a 0 el anuncio empieza a cargarse cuando YA se esta
134    // mirando el hueco, y se ve entrar. El margen es el precio de que no se
135    // note, no una excepcion a la regla.
136    var ROOT_MARGIN='250px 0px';
137    // Slots que se piden siempre, sin esperar al viewport.
138    //
139    // 2 en movil y 3 en escritorio, porque el problema esta solo en movil:
140    // INP p75 de 424 ms frente a 128 ms en escritorio, sobre un umbral de 200.
141    // Cada push({}) dispara la maquinaria de AdSense en el hilo principal, y
142    // medido en una ficha son 26 tareas largas con 6.251 ms de bloqueo total.
143    //
144    // Bajarlo en los dos habria costado impresiones en escritorio sin ninguna
145    // ganancia: alli el INP va sobrado. Las unidades que no entran aqui siguen
146    // pidiendose al hacer scroll, no se pierden salvo para quien no baja nada.
147    // UNO, y solo si de verdad esta arriba. Antes eran 2 en movil y 3 en
148    // escritorio "esten donde esten", y ese "esten donde esten" es lo que se
149    // retira: en una pagina larga forzaba unidades que el visitante no llegaba
150    // a ver nunca.
151    //
152    // Se deja en 1 porque la primera unidad de estas plantillas cae dentro de
153    // la primera pantalla en la practica totalidad de las vistas, y el
154    // observador la pediria igual; tenerlo explicito evita que un layout raro
155    // retrase la unica impresion segura de la pagina.
156    //
157    // LO QUE ESTO CUESTA, para no descubrirlo por sorpresa en el informe: el
158    // comentario anterior dejo medido que pasar de "siempre" a "solo cerca del
159    // viewport" hundio el ratio impresiones/pagina de 4,0 a 1,5. Aquello era
160    // con EAGER_ADS=1 y sin margen ninguno; aqui quedan los 250 px de
161    // ROOT_MARGIN. Si el ratio vuelve a caer, la palanca es ROOT_MARGIN.
162    var EAGER_ADS = 1;
163    var INP_ENDPOINT=KIT_ADS.inp;
164    var INP_LANG=KIT_ADS.lang;
165
166    var adsenseLoaded=false;
167    var pendingAds=[];
168
169    // 1 — Cargar adsbygoogle.js
170    //
171    // OJO: el comentario anterior justificaba cargar el script de inmediato
172    // diciendo que el INP p75 era de 36 ms sobre un umbral de 200, "5,5x de
173    // margen". Esa cifra era FALSA: salia de un monitor que medía solo
174    // processingEnd - processingStart en vez de la latencia completa.
175    //
176    // Search Console (CrUX, 30/7/2026) reporta INP > 200 ms en 22.283 URLs de
177    // movil. O sea que no habia margen ninguno: el INP es, junto al LCP, el
178    // problema real de esta red.
179    //
180    // Se deja la carga inmediata de momento porque el script es async y no
181    // bloquea el render, pero hay que reevaluarlo con el monitor ya corregido
182    // antes de dar por buena esta decision.
183    // 1a — NO SE CARGA adsbygoogle.js HASTA QUE EL AVISO DE CONSENTIMIENTO ESTA RESUELTO.
184    // 26-sep-2026.
185    //
186    // Medido con un Chrome de laboratorio (movil, CPU x4, primera visita desde la UE):
187    // el toque en «Consentir» del aviso de Funding Choices era la PEOR interaccion de la
188    // pagina, 440-890 ms. La traza lo explica: el `click` duraba 510 ms, de los que el
189    // manejador del propio aviso eran 45 y los otros 459 eran MICROTAREAS DE ADSENSE.
190    // adsbygoogle.js ya estaba cargado y esperando la senal TCF; en cuanto llega, arranca
191    // toda su maquinaria DENTRO del mismo clic --incluido un recalculo de estilo de 1.105
192    // elementos--, y el primer toque del lector carga con el.
193    //
194    // Mientras el aviso esta abierto no se puede ver ningun anuncio (lo tapa todo y AdSense
195    // espera la senal para pedir), asi que esperar no cuesta impresiones. Se escucha la API
196    // TCF de la IAB: `tcloaded` es quien ya habia consentido (la senal llega en seguida),
197    // `useractioncomplete` quien acaba de contestar, y `gdprApplies===false` quien esta fuera
198    // del RGPD. Y se arranca DESPUES del siguiente pintado (rAF + setTimeout), para que el
199    // toque en «Consentir» pinte antes de que AdSense se despierte.
200    //
201    // Sin CMP --bloqueado, o una pagina que no lo lleve-- no se espera mas de 4 s.
202    var consentimientoResuelto=false, trasConsentir=[];
203    function trasConsentimiento(fn){
204        if(consentimientoResuelto){fn();return;}
205        trasConsentir.push(fn);
206    }
207    (function vigilarConsentimiento(){
208        var resuelto=function(){
209            if(consentimientoResuelto)return;
210            consentimientoResuelto=true;
211            var ya=false, lista=trasConsentir;
212            trasConsentir=[];
213            var correr=function(){
214                if(ya)return; ya=true;
215                for(var i=0;i<lista.length;i++){try{lista[i]();}catch(e){}}
216            };
217            // Despues del siguiente pintado Y en un hueco libre del hilo. Solo con
218            // «despues del pintado» (primera version, 26-sep-2026) el clic ya pasaba de
219            // 510 a 101 ms, pero adsbygoogle.js se evaluaba 134 ms despues y detras venia
220            // show_ads_impl: 304 ms de script y 269 de microtareas. Quien toca algo justo
221            // despues de consentir se lo encontraba de frente. requestIdleCallback lo
222            // aparca hasta que el hilo este libre; el tope de 3 s y el setTimeout son la
223            // red para cuando no hay reposo o la pestana esta oculta.
224            requestAnimationFrame(function(){
225                setTimeout(function(){
226                    if('requestIdleCallback' in window){requestIdleCallback(correr,{timeout:3000});}
227                    else{setTimeout(correr,300);}
228                },0);
229            });
230            setTimeout(correr,4000);
231        };
232        var intentos=0, fcSinTcf=0;
233        var fcTag=document.querySelector('script[src*="fundingchoicesmessages.google.com"]');
234        // Cuantos ms hace que termino de bajar el script de Funding Choices (-1 = aun no,
235        // o no se sabe). Resource Timing y no el evento load: este fichero va con defer y
236        // el script de FC con async, asi que muchas veces ya ha cargado al llegar aqui.
237        var fcBajadoHace=function(){
238            try{
239                var e=performance.getEntriesByName(fcTag.src);
240                if(e.length&&e[0].responseEnd>0){return performance.now()-e[0].responseEnd;}
241            }catch(x){}
242            return -1;
243        };
244        (function espera(){
245            if(typeof window.__tcfapi==='function'){
246                try{
247                    window.__tcfapi('addEventListener',2,function(tc,ok){
248                        if(!ok||!tc){resuelto();return;}
249                        if(tc.gdprApplies===false||tc.eventStatus==='tcloaded'||tc.eventStatus==='useractioncomplete'){
250                            try{window.__tcfapi('removeEventListener',2,function(){},tc.listenerId);}catch(e){}
251                            resuelto();
252                        }
253                    });
254                }catch(e){resuelto();}
255                return;
256            }
257            // Cuanto esperar a que aparezca __tcfapi. Con 4 s fijos, en un movil lento
258            // el script de Funding Choices llegaba DESPUES y AdSense se cargaba antes
259            // del aviso: vuelta al problema de arriba (laboratorio a CPU x4: el toque en
260            // «Consentir» seguia en 430-540 ms en esas pasadas). Si la pagina lleva el
261            // script del CMP, se le esperan 15 s; si no lo lleva, 1 s basta.
262            //
263            // SALVO QUE FUNDING CHOICES YA HAYA DICHO QUE NO HAY MENSAJE. 1-oct-2026.
264            // Si la consola no tiene un mensaje del EEE publicado para el dominio, el script
265            // de FC carga y __tcfapi NO APARECE NUNCA: esos 15 s eran 15 s sin pedir un solo
266            // anuncio, y por eso lasembajadas, wikiapellidos y apellidosespaña llevaban el
267            // CMP apagado. Medido en el laboratorio (movil, CPU x4), las dos formas:
268            //   con mensaje (7 sitios)  FC hace 7 peticiones; `window.googlefc` y __tcfapi
269            //                           aparecen EN EL MISMO instante, 440-941 ms despues
270            //                           de bajar el script.
271            //   sin mensaje (3 sitios)  FC hace 1 peticion; `window.googlefc` aparece con
272            //                           solo `__fci` dentro, 450-1.200 ms despues, y
273            //                           __tcfapi nunca.
274            // O sea que `googlefc.__fci` sin __tcfapi es FC ya decidido y sin mensaje: se
275            // le dan 500 ms de gracia y se sigue. NO se usa «1,5 s desde que cargo FC»
276            // como unica regla: con mensaje __tcfapi ya tarda hasta 0,94 s en este equipo,
277            // y en un movil lento pasaria de 1,5 y AdSense volveria a arrancar dentro del
278            // toque en «Consentir». Esa regla queda de red, a 3 s, por si Google cambia
279            // `__fci` de nombre (entonces se espera mas, nunca menos).
280            if(fcTag){
281                var gfc=window.googlefc;
282                if(gfc&&typeof gfc.__fci!=='undefined'){
283                    if(!fcSinTcf){fcSinTcf=1;}
284                    else if(++fcSinTcf>5){resuelto();return;}
285                }
286                var hace=fcBajadoHace();
287                if(hace>=3000){resuelto();return;}
288            }
289            var tope=fcTag?150:10;
290            if(++intentos>=tope){resuelto();return;}
291            setTimeout(espera,100);
292        })();
293    })();
294
295    function cargarScript(){
296        if(adsenseLoaded)return;
297        if(!consentimientoResuelto){trasConsentimiento(cargarScript);return;}
298        adsenseLoaded=true;
299        var s=document.createElement('script');
300        s.src='https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client='+AD_CLIENT;
301        // ============================================================
302        // FRENO A LOS ANUNCIOS AUTOMATICOS. 22-ago-2026.
303        // ============================================================
304        // Medido ese dia en la ficha de apellidos.de con el navegador:
305        //   - 17 `ins.adsbygoogle` en la pagina, de las que **6 son
306        //     `.google-auto-placed`**: unidades que inyecta Google por su cuenta
307        //     ADEMAS de las que pone la plantilla;
308        //   - y un **vignette**: un intersticial que se lleva la navegacion.
309        //     La URL acababa en `apellidos.de/#google_vignette` sin que nadie
310        //     hubiera pulsado nada. Paso tres veces seguidas.
311        //
312        // Cada unidad inyectada es DOM nuevo, layout y un `push` mas en el hilo
313        // principal, y todo eso cae en la ventana de carga, que es donde se
314        // miden las peores interacciones. El intersticial es peor todavia:
315        // aparece encima y responde al primer toque del visitante.
316        //
317        // `data-ad-frequency-hint` es la palanca DOCUMENTADA para esto y se lee
318        // del propio <script>. Se pone a 60s: es una sugerencia, no una orden,
319        // y no apaga el inventario -- solo lo espacia.
320        //
321        // LO QUE ESTO **NO** ARREGLA, para no venderlo de mas: apagar los
322        // Anuncios Automaticos o quitar el formato "vignette" se hace en la
323        // consola de AdSense, no desde aqui. Si tras esto siguen saliendo seis
324        // unidades inyectadas, la palanca es esa y no hay otra.
325        s.setAttribute('data-ad-frequency-hint','60s');
326        s.async=true;
327        s.crossOrigin='anonymous';
328        s.onload=function(){
329            for(var i=0;i<pendingAds.length;i++){pushAd(pendingAds[i]);}
330            pendingAds=[];
331        };
332        document.head.appendChild(s);
333    }
334
335    // Arrancar en cuanto el hilo principal este libre, no en plena carga.
336    //
337    // INP mide la latencia de las interacciones, y las peores son las primeras:
338    // las que el usuario hace mientras el navegador todavia esta ejecutando
339    // scripts. adsbygoogle.js es, con diferencia, el JS mas pesa
339do de la
340    // pagina. Lanzarlo con requestIdleCallback lo mete en un hueco libre del
341    // hilo en vez de ponerlo a competir con el primer toque del usuario.
342    //
343    // El timeout de 2000 ms es el compromiso: si el hilo no se libera antes,
344    // se arranca igual para no retrasar el relleno y perder impresiones.
345    //
346    // A proposito NO se arranca "a la primera interaccion": eso cargaria el
347    // script justo durante esa interaccion, que es precisamente lo que
348    // empeoraria el INP que queremos bajar.
349    if('requestIdleCallback' in window){
350        requestIdleCallback(cargarScript,{timeout:2000});
351    }else{
352        setTimeout(cargarScript,1200);
353    }
354
355    // 2 — Push individual con guarda contra doble push.
356    //
357    // Los push se escalonan en lugar de encadenarse. Cada push({}) dispara
358    // medicion, insercion de iframe, layout y pintado; tres seguidos en el
359    // mismo tick forman una tarea larga y cualquier toque del usuario durante
360    // esa tarea se lleva el retraso entero al INP. Cediendo el hilo entre uno
361    // y otro, el navegador puede atender la interaccion en medio.
362    var colaPush=[], vaciando=false;
363
364    function vaciarCola(){
365        if(!colaPush.length){vaciando=false;return;}
366        vaciando=true;
367        var el=colaPush.shift();
368        formatoDeEscritorio();
369        try{(adsbygoogle=window.adsbygoogle||[]).push({});}catch(e){}
370        // ceder el hilo antes del siguiente
371        if('requestIdleCallback' in window){
372            requestIdleCallback(vaciarCola,{timeout:300});
373        }else{
374            setTimeout(vaciarCola,50);
375        }
376    }
377
378    function pushAd(el){
379        if(el.dataset.adPushed)return;
380        el.dataset.adPushed='1';
381        colaPush.push(el);
382        if(!vaciando)vaciarCola();
383    }
384
385    // 3 — Cuando un ad entra en viewport
386    function onAdVisible(el){
387        if(adsenseLoaded){pushAd(el);}
388        else{pendingAds.push(el);cargarScript();}
389    }
390
391    // 4 — Observer con IntersectionObserver
392    function initObserver(){
393        if(!('IntersectionObserver' in window)){
394            var fn=function(){
395                cargarScript();
396                document.querySelectorAll('.kit-hueco .adsbygoogle,.ad-slot .adsbygoogle').forEach(function(ad){pendingAds.push(ad);});
397            };
398            'requestIdleCallback' in window?requestIdleCallback(fn,{timeout:3000}):setTimeout(fn,2000);
399            return;
400        }
401        var observer=new IntersectionObserver(function(entries){
402            for(var i=0;i<entries.length;i++){
403                if(entries[i].isIntersecting){
404                    observer.unobserve(entries[i].target);
405                    onAdVisible(entries[i].target);
406                }
407            }
408        },{rootMargin:ROOT_MARGIN,threshold:0.01});
409
410        function observarAds(){
411            // Solo NUESTRAS unidades, que son las que van dentro de .kit-hueco.
412            //
413            // Antes el selector era '.adsbygoogle' a secas, y en los 5 dominios
414            // con Auto ads eso tambien coge los <ins> que inyecta Google.
415            // Medido el 3-ago en nachnamen.net/nachname-mueller: 16 <ins> en la
416            // pagina, 9 nuestros y 7 inyectados, ninguno de esos 7 dentro de un
417            // .kit-hueco. Los inyectados los gestiona el propio Auto ads; que
418            // entren aqui tiene dos efectos malos:
419            //
420            //   1. Consumen presupuesto de EAGER_ADS. El idx se cuenta en orden
421            //      de DOM sobre todos los <ins>, asi que un auto ad inyectado
422            //      por encima de nuestra primera unidad deja a esta fuera de los
423            //      2-3 huecos que se piden siempre.
424            //   2. Les poniamos data-ad-pushed y les haciamos push({}) por
425            //      nuestra cuenta. Verificado en esa misma carga: uno de los
426            //      elementos inyectados salio con data-ad-pushed nuestro.
427            //
428            // Con .kit-hueco el selector es exacto y en los 11 dominios sin Auto
429            // ads no cambia nada: alli todos los .adsbygoogle son nuestros.
430            var ads=document.querySelectorAll('.kit-hueco .adsbygoogle,.ad-slot .adsbygoogle');
431            var vh=window.innerHeight||800;
432
433            // DOS FASES: primero se lee TODO el layout, despues se escribe.
434            //
435            // Antes se hacia todo en un mismo bucle: getBoundingClientRect()
436            // (lectura de layout) y acto seguido onAdVisible(), que empuja el
437            // anuncio y muta el DOM. En la vuelta siguiente el navegador tenia
438            // que recalcular el layout para poder responder al siguiente
439            // getBoundingClientRect(). Eso es layout thrashing y Chrome lo
440            // reportaba como "Forced reflow while executing JavaScript took
441            // 62ms".
442            //
443            // Tambien cuenta escribir dataset.adObserved: el CSS de los huecos
444            // usa el selector [data-ad-status="filled"], asi que tocar un
445            // atributo data-* puede invalidar estilo. Por eso las escrituras
446            // van todas en la fase 2.
447            // 26-sep-2026: YA NO SE LEE LA GEOMETRIA AQUI. Este bucle hacia
448            // getBoundingClientRect() de cada unidad, y como `observarAds()` corria
449            // en cuanto se evaluaba el script, ese era el PRIMER calculo de estilo y
450            // layout de la pagina, forzado dentro de nuestra tarea: 381 ms en la
451            // traza de laboratorio (movil, CPU x4), 539 ms todo el script. Lo que
452            // sabia `p.top` --si la unidad esta en pantalla-- lo sabe igual el
453            // IntersectionObserver, que lo calcula despues del layout sin forzar
454            // nada: las unidades de la primera pantalla le llegan en su primera
455            // notificacion. `top` queda a Infinity para que la condicion de abajo
456            // solo mire el orden.
457            var pendientes=[];
458            var idx=0;
459            for(var i=0;i<ads.length;i++){
460                var a=ads[i];
461                if(a.dataset.adPushed||a.dataset.adObserved)continue;
462                idx++;
463                pendientes.push({el:a,idx:idx,top:Infinity});
464            }
465
466            pendientes.forEach(function(p){
467                var ad=p.el;
468                ad.dataset.adObserved='1';
469                // Los primeros EAGER_ADS se empujan siempre, esten donde esten.
470                //
471                // Antes la unica condicion era que el anuncio estuviera cerca del
472                // viewport, y en paginas largas (apellido.php y pais.php llevan
473                // 5-6 unidades muy separadas) eso dejaba fuera a todas menos la
474                // primera o la segunda para quien no hacia scroll. El ratio
475                // impresiones/pagina vista de la red cayo de 4,0 a 1,5: la mitad
476                // del agujero de ingresos eran slots que nunca se pedian.
477                //
478                // Se usa p.top, medido en la fase de lectura: volver a llamar
479                // aqui a getBoundingClientRect() es justo lo que provocaba el
480                // reflow forzado.
481                // `vh*1.5` era pantalla y media por debajo del pliegue: se
482                // pedia el anuncio de quien no habia bajado todavia. Ahora el
483                // arranque inmediato es solo para lo que esta EN pantalla, y
484                // del resto se encarga el observador con sus 250 px de margen.
485                if(p.idx<=EAGER_ADS || p.top<vh){ onAdVisible(ad); }
486                else { observer.observe(ad); }
487            });
488        }
489        // Despues del primer pintado, no al evaluar el script (26-sep-2026).
490        requestAnimationFrame(function(){setTimeout(observarAds,0);});
491        setTimeout(observarAds,800);
492
493        // 4b — Barrido de rescate al hacer scroll
494        //
495        // EL PROBLEMA, MEDIDO. El 3-ago en produccion, sobre
496        // nachnamen.net/nachname-mueller en escritorio: recorrida la pagina
497        // entera (13.720 px) y esperados 8 s, de nuestras 9 unidades solo 3
498        // habian llegado a pedirse. Las otras 6 tenian data-ad-observed="1"
499        // pero NO data-ad-pushed: el observer las registro y su callback no
500        // llego a dispararse nunca. En esa misma carga los Auto ads rellenaron
501        // 6 de 7. O sea que dos tercios del inventario propio no se pedian ni
502        // con la pagina leida de arriba abajo.
503        //
504        // Sospecha razonable del porque: los listados usan .vl, que es
505        // content-visibility:auto, y un elemento dentro de un subarbol saltado
506        // no tiene caja, asi que nunca alcanza el threshold de 0,01 que se le
507        // pide al observer. Pero NO se ha confirmado, y este arreglo no depende
508        // de que esa explicacion sea la buena: comprueba la geometria a mano,
509        // que es exactamente lo que el observer tenia que haber hecho.
510        //
511        // POR QUE SOLO CON SCROLL. Si el usuario no hace scroll, el
512        // comportamiento es identico al de antes: no se pide ni una unidad de
513        // mas. Esto repara el camino que estaba roto, no sube la agresividad.
514        // Importa porque el 82 % del trafico no interactua y ahi solo se
515        // resuelve el 7,5 % de los huecos: pedir mas anuncios a quien se va sin
516        // mirar solo empeoraria la visibilidad y el iRPM.
517        //
518        // COSTE EN INP: ninguno nuevo. Se lee toda la geometria antes de
519        // escribir nada (si no, es reflujo forzado), y los push siguen saliendo
520        // por colaPush, que cede el hilo entre uno y otro.
521        var rescatePendiente=false;
522
523        function barridoRescate(){
524            rescatePendiente=false;
525            var ads=document.querySelectorAll('.kit-hueco .adsbygoogle,.ad-slot .adsbygoogle');
526            if(!ads.length)return;
527            var vh=window.innerHeight||800;
528            var margen=1200; // el mismo margen que se le pide al observer
529
530            // FASE 1 — leer. Nada de mutar el DOM en este bucle.
531            var aEmpujar=[];
532            for(var i=0;i<ads.length;i++){
533                var a=ads[i];
534                if(a.dataset.adPushed)continue;
535                var r=a.getBoundingClientRect();
536                // Rect a cero = sigue dentro de un subarbol sin renderizar.
537                // No se puede decidir todavia; ya entrara en otro barrido.
538                if(r.height===0&&r.width===0)continue;
539                if(r.top<vh+margen&&r.bottom>-margen)aEmpujar.push(a);
540            }
541
542            // FASE 2 — escribir.
543            for(var j=0;j<aEmpujar.length;j++)onAdVisible(aEmpujar[j]);
544        }
545
546        // Estrangulado a un barrido por fotograma con requestAnimationFrame.
547        //
548        // La primera version usaba setTimeout de 400 ms y se le escapaban
549        // unidades: medido en local, con un recorrido rapido de la pagina
550        // pasaban de 2 a 4 unidades pedidas de 9, porque entre barrido y
551        // barrido una unidad entraba y salia de la ventana de 1200 px sin que
552        // ningun barrido la viera. Una unidad que se pasa de largo ya no vuelve
553        // a ser elegible (r.bottom > -margen), y eso es a proposito: no se
554        // piden anuncios que el usuario ha dejado atras.
555        //
556        // Con rAF el barrido corre como mucho una vez por fotograma, que es lo
557        // mismo que hace cualquier efecto ligado al scroll. Son 9 lecturas de
558        // getBoundingClientRect por fotograma mientras se desplaza, todas
559        // seguidas y sin escrituras en medio, asi que no fuerzan reflujo extra:
560        // el navegador ya recalcula el layout en cada fotograma de scroll.
561        addEventListener('scroll',function(){
562            if(rescatePendiente)return;
563            rescatePendiente=true;
564            requestAnimationFrame(barridoRescate);
565        },{passive:true});
566    }
567
568    // 5 — Monitor de Core Web Vitals
569    //
570    // Antes solo medía INP. Pero INP nunca ha sido el problema: el p75 real es
571    // de 34-45 ms sobre un umbral de 200. Lo que no se estaba midiendo es
572    // justo lo que penaliza en movil — LCP y CLS — y sin datos no se puede
573    // saber si añadir unidades de anuncio ha perjudicado o no.
574    //
575    // Se manda todo en el mismo beacon al ocultarse la pestaña, marcando si la
576    // visita era movil: los umbrales se incumplen en movil, no en escritorio, y
577    // mezclarlos esconde el problema.
578    function initCWV(){
579        // SIN DESTINO NO SE MIDE NADA (12-ago-2026). La baliza va apagada en la
580        // red: kit_ads_js() ya no emite KIT_ADS.inp. La guarda va la PRIMERA, y
581        // no solo delante del envio, por dos motivos:
582        //   1. Los PerformanceObserver de aqui abajo cuestan trabajo en el hilo
583        //      principal; instrumentar para luego tirar la medicion es empeorar
584        //      el INP a cambio de nada.
585        //   2. `navigator.sendBeacon('')` no es inofensivo: postea contra la URL
586        //      ACTUAL. Sin esta linea, apagar la baliza convert
586iria un 404 en un
587        //      POST a cada pagina del sitio.
588        if(!INP_ENDPOINT)return;
589        if(!('PerformanceObserver' in window))return;
590        var worst=0, cls=0, lcp=0;
591
592        // Momento en que la pestaña se oculta por primera vez.
593        //
594        // Si la pagina se abre en una pestaña de fondo (clic con rueda, "abrir
595        // en pestaña nueva"), el navegador no registra el LCP hasta que esa
596        // pestaña se hace visible, y el startTime acumula TODO el tiempo que
597        // estuvo oculta. Eso produce LCP de 10-11 segundos que no tienen nada
598        // que ver con lo que vivio el usuario.
599        //
600        // Se veia clarisimo en los datos: distribucion bimodal con 848 visitas
601        // por debajo de 2,5 s y 377 agrupadas en ~11 s, casi nada en medio. Un
602        // LCP lento de verdad se reparte; ese se amontonaba.
603        //
604        // La libreria oficial web-vitals de Google descarta el LCP en este
605        // caso. Aqui se hace igual: si el LCP ocurrio despues de que la pestaña
606        // se ocultara, no se reporta.
607        var primerOculto = (document.visibilityState === 'hidden') ? 0 : Infinity;
608        document.addEventListener('visibilitychange', function(){
609            primerOculto = Math.min(primerOculto, performance.now());
610        }, {once:true});
611
612        // PRERENDER. Chrome precarga en segundo plano paginas que cree que el
613        // usuario va a pulsar (desde la busqueda de Google, sobre todo). En esas
614        // paginas el reloj de performance arranca al EMPEZAR el prerender, no al
615        // activarse, asi que un LCP "de 10 s" puede ser en realidad de 300 ms mas
616        // 10 s de espera a que el usuario pulse el enlace.
617        //
618        // Esto no es teorico: el 01/08/2026, en la ventana limpia, 181 muestras
619        // moviles daban LCP de 10,3-11,0 s en un racimo cerradisimo (apenas 3
620        // muestras entre 4 s y 10 s: la distribucion era bimodal, no una cola).
621        // De esas 181, 180 no tenian interaccion y traian 1,89 huecos empujados
622        // con 0,01 resueltos — o sea, ningun anuncio pintando tarde. Eran fichas
623        // de apellido, que son justo las que se clican desde Google.
624        //
625        // activationStart es 0 en una navegacion normal, asi que restarlo no
626        // cambia nada fuera del caso prerender. Es lo que hace la libreria
627        // oficial web-vitals.
628        // Altura de partida de lo que hay encima de <main>, tomada nada mas
629        // cargar el DOM. La diferencia con la altura final identifica al que
630        // empuja. Las claves cortas son para que quepa en el CSV.
631        var alturaIni = {};
632        var anchoIni = document.documentElement.clientWidth;
633        try{
634            [['nav','nav'],['header','hdr'],['section.hero','hero']].forEach(function(par){
635                var e2 = document.querySelector(par[0]);
636                alturaIni[par[0]] = {n: par[1], h: e2 ? Math.round(e2.getBoundingClientRect().height) : 0};
637            });
638        }catch(e){}
639
640        var nav0 = performance.getEntriesByType('navigation')[0];
641        var arranque = (nav0 && nav0.activationStart) ? nav0.activationStart : 0;
642        var fuePrerender = (document.prerendering || arranque > 0) ? 1 : 0;
643        document.addEventListener('prerenderingchange', function(){
644            fuePrerender = 1;
645            var n = performance.getEntriesByType('navigation')[0];
646            if(n && n.activationStart) arranque = n.activationStart;
647        }, {once:true});
648
649        function observar(tipo, fn, extra){
650            try{
651                var opt={type:tipo,buffered:true};
652                if(extra){for(var k in extra){opt[k]=extra[k];}}
653                new PerformanceObserver(fn).observe(opt);
654            }catch(e){}
655        }
656
657        // INP: hay que usar e.duration, que es la LATENCIA COMPLETA de la
658        // interaccion (retardo de entrada + procesado + presentacion).
659        //
660        // Antes se calculaba processingEnd - processingStart, que es solo el
661        // tramo de procesado: una fraccion del total. Por eso este monitor
662        // daba un p75 de 34-45 ms mientras Search Console reportaba mas de
663        // 200 ms en 22.283 URLs de movil. Sobre esa cifra falsa se dio por
664        // bueno que "sobraba margen" de INP.
665        // SIGUE SIN CUADRAR CON EL CAMPO. Medido el 3-ago-2026: este monitor
666        // reporta un p75 de 34-45 ms y CrUX da 189 ms para el origen. 4-5x.
667        //
668        // Faltaban dos cosas para que esto sea INP de verdad:
669        //
670        // 1. FILTRAR POR interactionId. Las entradas de tipo 'event' incluyen
671        //    eventos que NO son interacciones del usuario. INP solo cuenta las
672        //    que traen interactionId > 0. Sin el filtro se mide otra cosa.
673        //
674        // 2. AGRUPAR POR INTERACCION. Un solo toque genera varias entradas
675        //    (pointerdown, pointerup, click) que comparten interactionId. La
676        //    latencia de esa interaccion es el MAXIMO del grupo, no cada
677        //    entrada suelta. Quedarse con el max global de entradas sueltas
678        //    subestima cuando la parte lenta se reparte entre eventos del
679        //    mismo grupo.
680        //
681        // El sesgo que queda es de MUESTRA, no de calculo, y es el que explica
682        // el grueso de la diferencia: el beacon solo sale en visibilitychange y
683        // descarta la visita si las tres metricas son cero, asi que la visita
684        // que no interactua nunca entra. CrUX si la cuenta. Por eso este numero
685        // seguira siendo optimista aunque el calculo ya sea correcto: sirve
686        // para comparar ANTES/DESPUES de un cambio propio, no para decidir si
687        // se aprueba el umbral. Para eso, CrUX.
688        var interacciones = {};   // interactionId -> latencia maxima del grupo
689        observar('event', function(list){
690            list.getEntries().forEach(function(e){
691                if(!e.interactionId) return;              // 1
692                var id = e.interactionId;                 // 2
693                if(!(id in interacciones) || e.duration > interacciones[id]){
694                    interacciones[id] = e.duration;
695                }
696                if(interacciones[id] > worst) worst = interacciones[id];
697            });
698        }, {durationThreshold:16});
699
700        // CLS por ventana de sesion, que es como lo define Google desde 2020.
701        //
702        // NO es la suma de todos los desplazamientos de la pagina: es el MAXIMO
703        // de una ventana de sesion, entendiendo por sesion una rafaga de
704        // desplazamientos separados entre si por menos de 1 s y que en total no
705        // superan los 5 s. Sumarlo todo infla la cifra cuanto mas dura la
706        // visita y mas unidades cargan al hacer scroll, y da valores que no se
707        // parecen en nada a los de PageSpeed o DevTools.
708        // Ademas se guarda QUE ELEMENTO provoca el mayor desplazamiento.
709        //
710        // Hace falta porque el CLS de esta red es un caso raro: 1.274 de 1.285
711        // visitas malas son de ESCRITORIO, con valores agrupados en 0,65-0,70
712        // (deterministas) y repartidos por igual entre fichas, paises y mapas.
713        // O sea, algo del layout comun que solo se renderiza en escritorio.
714        //
715        // No se puede reproducir desde una sesion de pruebas porque depende de
716        // que los anuncios rellenen, y eso es no determinista. Ya se han hecho
717        // dos hipotesis erroneas al respecto; en vez de una tercera, se anota el
718        // culpable real y se decide con el dato.
719        var sesion = 0, sesionIni = 0, sesionFin = 0, culpable = '', peorShift = 0;
720
721        function describir(n){
722            if(!n || n.nodeType !== 1) return '?';
723            var d = n.tagName.toLowerCase();
724            if(n.id) return d + '#' + n.id;
725            var c = (typeof n.className === 'string' ? n.className : '').trim().split(/\s+/)[0];
726            return c ? d + '.' + c : d;
727        }
728
729        observar('layout-shift', function(list){
730            list.getEntries().forEach(function(e){
731                if(e.hadRecentInput) return;
732                if(e.value > peorShift){
733                    peorShift = e.value;
734                    var s = e.sources && e.sources[0];
735                    // No basta con QUE elemento se movio. En movil el culpable
736                    // sale "main" en 47 de 76 visitas malas, y que se mueva el
737                    // bloque principal entero solo dice que algo lo empuja desde
738                    // arriba, no QUE lo empuja.
739                    //
740                    // El instante y la distancia si lo identifican: un empujon de
741                    // 180 px a los 2.400 ms es un anuncio inyectado; uno de 20 px
742                    // a los 150 ms es reflujo de maquetacion. Se guarda como
743                    // "main@2400+180" — para agrupar por elemento, cortar por '@'.
744                    var quien = s ? describir(s.node) : '';
745                    if(s && s.previousRect && s.currentRect){
746                        var dy = Math.round(s.currentRect.top - s.previousRect.top);
747                        quien += '@' + Math.round(e.startTime) + (dy >= 0 ? '+' : '') + dy;
748                    }
749                    culpable = quien;
750                }
751                if(sesion && (e.startTime - sesionFin) < 1000 && (e.startTime - sesionIni) < 5000){
752                    sesion += e.value;
753                }else{
754                    sesion = e.value;
755                    sesionIni = e.startTime;
756                }
757                sesionFin = e.startTime;
758                if(sesion > cls) cls = sesion;
759            });
760        });
761
762        // Se guarda QUE elemento marca el LCP y en que estado estaba la pestaña.
763        //
764        // Motivo: el LCP movil tiene una distribucion con hueco — nada entre 4 s
765        // y 10 s, y luego un grupo repartido de 10 s a 19 s. Sobre eso ya se han
766        // hecho DOS diagnosticos equivocados: que lo causaban los anuncios
767        // (refutado: esas visitas traian 0,01 huecos resueltos, o sea ninguno
768        // pintando) y que era prerender de Chrome (refutado: prerender=1 salio
769        // en 0 de 1.313 muestras).
770        //
771        // En vez de un tercero, se anota el dato y se decide con el: el elemento
772        // culpable, y si la pestaña estaba oculta al medirlo, que es la via que
773        // queda por descartar.
774        var lcpQuien = '', lcpOculta = 0;
775        observar('largest-contentful-paint', function(list){
776            var es=list.getEntries();
777            if(!es.length) return;
778            var u=es[es.length-1];
779            lcp=u.startTime;
780            lcpQuien = u.element ? describir(u.element) : (u.url ? 'url' : 'sin-elemento');
781            lcpOculta = (document.visibilityState === 'hidden') ? 1 : 0;
782        });
783
784        addEventListener('visibilitychange',function(){
785            if(document.visibilityState!=='hidden')return;
786            if(worst<=0 && lcp<=0 && cls<=0)return;
787            var fd=new FormData();
788            fd.append('inp',Math.round(worst));
789            // LCP posterior a que la pestaña se ocultara = pestaña de fondo.
790            // Se manda 0 (el endpoint lo trata como "sin dato") en vez de un
791            // valor inflado que envenena los percentiles.
792            // Se resta activationStart antes de comparar y de reportar: en una
793            // navegacion normal vale 0 y no cambia nada; en una prerenderizada
794            // convierte "10,4 s" en el LCP real desde que el usuario vio la
795            // pagina. Ver el bloque de PRERENDER mas arriba.
796            var lcpReal = (lcp > 0) ? Math.max(lcp - arranque, 0) : 0;
797            fd.append('lcp', (lcpReal > 0 && lcp <= primerOculto) ? Math.round(lcpReal) : 0);
798            fd.append('prerender', fuePrerender);
799            fd.append('lcpquien', lcpQuien);
800            fd.append('lcpoculta', lcpOculta);
801            // Cuantos elementos de AdSense han acabado POR ENCIMA de <main>.
802            //
803            // El culpable del CLS movil es "main" en 24 de 33 visitas malas, con
804            // un desplazamiento medio de 53 px a los 6.226 ms. Que se mueva main
805            // entero, tarde y esa distancia, apunta a un anuncio inyectado
806            // encima. Pero apuntar no es demostrar: ya van tres hipotesis
807            // refutadas en este monitor, asi que se cuenta en vez de suponer.
808            //
809            // Si las visitas con desplazamiento de main traen anuncios arriba y
810            // las limpias no, es eso. Si no correlaciona, hay que buscar en otro
811            // sitio (el <header> del HTML va vacio, es candidato a que algo lo
812            // rellene).
813            var adsArriba = 0;
814            try{
815                var m = document.querySelector('main');
816                if(m){
817                    var topMain = m.getBoundingClientRect().top + (window.scrollY || 0);
818                    var anuncios = document.querySelectorAll('ins.adsbygoogle,iframe[id^="aswift"]');
819                    for(var q=0;q<anuncios.length;q++){
820                        var r = anuncios[q].getBoundingClientRect();
821                        if(r.height > 0 && (r.top + (window.scrollY || 0)) < topMain) adsArriba++;
822                    }
823                }
824            }catch(err2){}
825            fd.append('adsarriba', adsArriba);
826            // Cual de los tres bloques que hay ENCIMA de <main> ha crecido.
827            //
828            // Descartado ya que sean anuncios inyectados: con 1.291 visitas,
829            // adsarriba=0 en el 98%, y de las que tienen a main como culpable
830            // solo el 7% traia algun anuncio arriba, frente al 5% de las demas.
831            // Indistinguible.
832            //
833            // Encima de main solo quedan tres cosas: nav, un <header> que se
834            // sirve VACIO, y section.hero. Se mide la altura de cada uno al
835            // cargar y al irse el usuario; el que haya crecido es el que empuja.
836            var crecio = '';
837            try{
838                var d = [];
839                for(var s2 in alturaIni){
840                    var el2 = document.querySelector(s2);
841                    var ahora = el2 ? Math.round(el2.getBoundingClientRect().height) : 0;
842                    d.push(alturaIni[s2].n + (ahora - alturaIni[s2].h));
843                }
844                // Ancho del viewport al principio y al final.
845                //
846                // El hero crece 60-62 px en las visitas malas, y 60 px es justo
847                // la altura de UNA fila de .hero-stats (numero + etiqueta). O sea
848                // que .hero-stats pasa de N filas a N+1 al reajustarse el
849                // flex-wrap. Como .hero h1 y .hero-stat-num usan clamp() con
850                // unidades vw, un cambio de ancho del viewport cambia el tamano
851                // de letra y con el, donde rompen las filas.
852                // Si w cambia, la causa es el ancho; si no, es el contenido.
853                d.push('w' + Math.round(anchoIni) + 'w' + Math.round(document.documentElement.clientWidth));
854                // Altura ABSOLUTA del hero al empezar, no solo el delta.
855                //
856                // Se han refutado SIETE hipotesis sobre por que crece
857                // section.hero (la ultima, el font boosting de Chrome: se anadio
858                // text-size-adjust:100% y el CLS no se movio, 0,135 -> 0,130, y
859                // las visitas con hero>=40px pasaron del 11,2% al 12,6%).
860                //
861                // Asi que se deja de buscar la causa y se prepara un arreglo que
862                // no depende de ella: si se reserva por CSS la altura maxima del
863                // hero, crezca por lo que crezca no puede empujar a <main>. Para
864                // eso hace falta la altura de partida, que es lo que falta.
865                var hIni = alturaIni['section.hero'] ? alturaIni['section.hero'].h : 0;
866                d.push('h' + hIni);
867                crecio = d.join('-');
868            }catch(err3){}
869            fd.append('crecio', crecio);
870            fd.append('cls',Math.round(cls*1000)/1000);
871            fd.append('movil', (innerWidth<768 || /Mobi|Android/i.test(navigator.userAgent)) ? 1 : 0);
872            fd.append('culpable', culpable);
873            fd.append('page',location.pathname);
874            fd.append('lang',INP_LANG);
875            // Tasa de relleno. AdSense escribe data-ad-status en el <ins> cuando
876            // ya ha decidido: "filled" si hay anuncio, "unfilled" si no lo ha
877            // vendido. Los huecos que nunca se empujaron (lazy load que no llego
878            // a entrar en pantalla) no tienen el atributo y quedan fuera, que es
879            // lo correcto: aqui se mide cuanto rellena Google de lo que le
880            // pedimos, no cuanto pedimos.
881            // Tres numeros, no dos, porque "sin anuncio" y "Google no contesto
882            // a tiempo" no son lo mismo y se arreglan de forma distinta:
883            //   empujados = huecos que hemos mandado a AdSense
884            //   pedidos   = de esos, los que ya tenian veredicto al irse el usuario
885            //   llenos    = de esos, los que traian anuncio
886            // empujados-pedidos mide latencia de Google; pedidos-llenos mide
887            // falta de demanda.
888            var pedidos=0, llenos=0, empujados=0;
889            try{
890                var ins=document.querySelectorAll('ins.adsbygoogle');
891                for(var j=0;j<ins.length;j++){
892                    var est=ins[j].getAttribute('data-ad-status');
893                    if(ins[j].dataset.adPushed || est) empujados++;
894                    if(est){ pedidos++; if(est==='filled') llenos++; }
895                }
896            }catch(err){}
897            fd.append('pedidos',pedidos);
898            fd.append('llenos',llenos);
899            fd.append('empujados',empujados);
900            navigator.sendBeacon(INP_ENDPOINT,fd);
901        },{once:true});
902    }
903
904    // Init
905    if(document.readyState==='loading'){
906        document.addEventListener('DOMContentLoaded',function(){initObserver();initCWV();});
907    }else{
908        initObserver();
909        initCWV();
910    }
911})();
912})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.