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.