coach-core
Version:
Core package for the Coach.
1 lines • 52.6 kB
JavaScript
(function(){if("undefined"!=typeof window){const e={getAbsoluteURL:function(e){const t=globalThis.document.createElement("a");return t.href=e,t.href},getHostname:function(e){const t=globalThis.document.createElement("a");return t.href=e,t.hostname},exists:function(e,t){return t.includes(e)},caseInsensitiveAttributeValueFilter:function(e,t){return function(n){if((n.getAttribute(e)||"").toLowerCase()===t.toLowerCase())return n}},isHTTP2:function(){return"h2"===e.getConnectionType().toLowerCase()},isHTTP3:function(){return e.getConnectionType().toLowerCase().startsWith("h3")},getConnectionType:function(){if(globalThis.performance.getEntriesByType("navigation")&&globalThis.performance.getEntriesByType("navigation")[0]&&globalThis.performance.getEntriesByType("navigation")[0].nextHopProtocol)return globalThis.performance.getEntriesByType("navigation")[0].nextHopProtocol;if(globalThis.performance&&globalThis.performance.getEntriesByType&&globalThis.performance.getEntriesByType("resource")){const t=globalThis.performance.getEntriesByType("resource");if(t.length>1&&t[0].nextHopProtocol){const n=document.domain;for(let r=0,o=t.length;r<o;r++)if(n===e.getHostname(t[r].name))return t[r].nextHopProtocol}}return"unknown"},getSynchJSFiles:function(t){return Array.prototype.slice.call(t.querySelectorAll("script")).filter(function(e){return!e.async&&e.src&&!e.defer}).map(function(t){return e.getAbsoluteURL(t.src)})},getAsynchJSFiles:function(t){return Array.prototype.slice.call(t.querySelectorAll("script")).filter(function(e){return e.async&&e.src}).map(function(t){return e.getAbsoluteURL(t.src)})},getResourceHintsHrefs:function(e){return Array.prototype.slice.call(globalThis.document.head.querySelectorAll("link")).filter(function(t){return t.rel===e}).map(function(e){return e.href})},getCSSFiles:function(t){return Array.prototype.slice.call(t.querySelectorAll("link")).filter(function(e){return"stylesheet"===e.rel&&!e.href.startsWith("data:")}).map(function(t){return e.getAbsoluteURL(t.href)})},plural:function(e,t){return e>1&&(t+="s"),`${e} ${t}`},getTransferSize:function(e){const t=globalThis.performance.getEntriesByName(e,"resource");return 1===t.length&&"number"==typeof t[0].transferSize?t[0].transferSize:0},ms:e=>e<1e3?e+" ms":Number(e/1e3).toFixed(3)+" s"};return(function(e){var t={},n={},r={},o={};try{r.amp=(function(){"use strict";const e=document.querySelectorAll("html")[0];let t=100;return(e&&e.getAttribute("amp-version")||globalThis.AMP)&&(t=0),{id:"amp",title:"Avoid using AMP",description:"AMP was one of Google attempts to strengthen its monopoly in the Interente advertising market. You can read more about it here: https://storage.courtlistener.com/recap/gov.uscourts.nysd.564903/gov.uscourts.nysd.564903.152.0_1.pdf Using AMP you also share private user information with Google that your user hasn't agreed on sharing.",advice:0===t?"The page is using AMP, that makes you share private user information with Google.":"",score:t,weight:1,severity:"info",offending:[],tags:["bestpractice"]}})()}catch(e){o.amp=e.message}try{r.charset=(function(){"use strict";let e=100,t="";const n=document.characterSet;return null===n?(t="The page is missing a character set. If you use Chrome/Firefox we know you are missing it, if you use another browser, it could be an implementation problem.",e=0):"UTF-8"!==n&&(t="You are not using charset UTF-8?",e=50),{id:"charset",title:"Declare a charset in your document",description:"The Unicode Standard (UTF-8) covers (almost) all the characters, punctuations, and symbols in the world. Please use that.",advice:t,score:e,weight:2,severity:"info",offending:[],tags:["bestpractice"]}})()}catch(e){o.charset=e.message}try{r.cumulativeLayoutShift=(function(){"use strict";let e="There is no Layout Shift on the page.",t=0,n=0;const r=PerformanceObserver.supportedEntryTypes;if(r&&r.includes("layout-shift")){let r=0,o=Number.NEGATIVE_INFINITY,i=Number.NEGATIVE_INFINITY;const s=new PerformanceObserver(()=>{});s.observe({type:"layout-shift",buffered:!0});const a=s.takeRecords();for(let e of a)e.hadRecentInput||((e.startTime-o>5e3||e.startTime-i>1e3)&&(o=e.startTime,r=0),i=e.startTime,r+=e.value,n=Math.max(n,r));n<=.1?t=100:n>.25?(t=0,e=`You have a poor cumulative layout shift score (${n.toFixed(4)}). It is in the Google Web Vitals poor range, with a shift higher than 0.25. You should manually check the filmstrip or video and check if it will affect the user.`):(t=50,e=`You have a cumulative layout shift score (${n.toFixed(4)}) that needs improvements. It is in the Google Web Vitals needs improvement range, shift higher than 0.1. You should manually check the filmstrip or video and check if it will affect the user.`)}else e="Layout Shift is not supported in this browser";return{id:"cumulativeLayoutShift",title:"Cumulative Layout Shift",description:"Cumulative Layout Shift measures the sum total of all individual layout shift scores for unexpected layout shift that occur. The metric is measuring visual stability by quantify how often users experience unexpected layout shifts. It is one of Google Web Vitals.",advice:e,score:t,weight:8,severity:"error",offending:[],tags:["bestpractice"]}})()}catch(e){o.cumulativeLayoutShift=e.message}try{r.doctype=(function(){"use strict";let e=100,t="";const n=document.doctype;return null===n?(t="The page is missing a doctype. Please use <!DOCTYPE html>.",e=0):("html"!==n.name.toLowerCase()||""!==n.systemId&&"about:legacy-compat"!==n.systemId.toLowerCase())&&(t="Just do yourself a favor and use the HTML5 doctype declaration: <!DOCTYPE html>",e=25),{id:"doctype",title:"Declare a doctype in your document",description:"The <!DOCTYPE> declaration is not an HTML tag; it is an instruction to the web browser about what version of HTML the page is written in.",advice:t,score:e,weight:2,severity:"warn",offending:[],tags:["bestpractice"]}})()}catch(e){o.doctype=e.message}try{r.imageAltText=(function(e){"use strict";const t=[],n=document.querySelectorAll("img");for(let r=0,o=n.length;r<o;r++){const o=n[r],i=o.hasAttribute("alt"),s=o.getAttribute("aria-hidden"),a=o.getAttribute("role"),c=s&&"true"===s.toLowerCase()||a&&"presentation"===a.toLowerCase()||a&&"none"===a.toLowerCase();i||c||t.push(e.getAbsoluteURL(o.currentSrc||o.src||""))}const r=0===t.length?100:Math.max(0,100-10*t.length);return{id:"imageAltText",title:"Give every image a textual alternative",description:'Every <img> needs an alt attribute. Use alt="meaningful description" for content images so assistive technologies can announce them, or alt="" (or role="presentation" / aria-hidden="true") for purely decorative images so they are skipped. A missing alt attribute leaves screen reader users with no information at all. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#alt',advice:t.length>0?"The page has "+e.plural(t.length,"image")+' without an alt attribute. Add alt="..." with a description, or alt="" if the image is purely decorative.':"",score:r,weight:4,severity:"info",offending:t,tags:["bestpractice","accessibility"]}})(e)}catch(e){o.imageAltText=e.message}try{r.language=(function(){"use strict";const e=document.querySelectorAll("html"),t=e[0].getAttribute("lang");let n=100,r="";return e.length>0?null===t&&(n=0,r='The page is missing a language definition in the HTML tag. Define it with <html lang="YOUR_LANGUAGE_CODE">'):(n=0,r="What! The page is missing the HTML tag!"),{id:"language",title:"Declare the language code for your document",description:"According to the W3C recommendation you should declare the primary language for each Web page with the lang attribute inside the <html> tag https://www.w3.org/International/questions/qa-html-language-declarations#basics.",advice:r,score:n,weight:3,severity:"warn",offending:[],tags:["bestpractice"]}})()}catch(e){o.language=e.message}try{r.metaDescription=(function(e){"use strict";let t=100,n="",r=Array.prototype.slice.call(document.querySelectorAll("meta[name][content]"));r=r.filter(e.caseInsensitiveAttributeValueFilter("name","description"));const o=r.length>0?r[0].getAttribute("content"):"";return 0===o.length?(n="The page is missing a meta description.",t=0):o.length>160&&(n="The meta description is too long. It has "+o.length+" characters, the recommended max is 160",t=50),{id:"metaDescription",title:"Meta description",description:"Use a page description to make the page more relevant to search engines.",advice:n,score:t,weight:5,severity:"info",offending:[],tags:["bestpractice"]}})(e)}catch(e){o.metaDescription=e.message}try{r.optimizely=(function(e){"use strict";const t=e.getSynchJSFiles(document.head),n=[];let r=100,o="";for(const i of t)"cdn.optimizely.com"===e.getHostname(i)&&(n.push(i),r=0,o="The page is using Optimizely. Use it with care because it hurts your performance. Only turn it on (= load the JavaScript) when you run your A/B tests. Then when you are finished make sure to turn it off.");return{id:"optimizely",title:"Only use Optimizely when you need it",description:"Use Optimizely with care because it hurts your performance since JavaScript is loaded synchronously inside of the head tag, making the first paint happen later. Only turn on Optimzely (= load the javascript) when you run your A/B tests.",advice:o,score:r,weight:2,severity:"info",offending:n,tags:["bestpractice"]}})(e)}catch(e){o.optimizely=e.message}try{r.pageTitle=(function(){"use strict";const e=document.title;let t=100,n="";return 0===e.length?(n="The page is missing a title.",t=0):e.length>60&&(n="The title is too long by "+(e.length-60)+" characters. The recommended max is 60",t=50),{id:"pageTitle",title:"Page title",description:"Use a title to make the page more relevant to search engines.",advice:n,score:t,weight:5,severity:"warn",offending:[],tags:["bestpractice"]}})()}catch(e){o.pageTitle=e.message}try{r.url=(function(){"use strict";const e=document.URL;let t=100,n="";return e.includes("?")&&e.indexOf("jsessionid")>e.indexOf("?")&&(t=0,n="The page has the session id for the user as a parameter, please change so the session handling is done only with cookies. "),(e.match(/&/g)||[]).length>1&&(t-=50,n+="The page is using more than two request parameters. You should really rethink and try to minimize the number of parameters. "),e.length>100&&(t-=10,n+="The URL is "+e.length+" characters long. Try to make it less than 100 characters. "),(e.includes(" ")||e.includes("%20"))&&(t-=10,n+="Could the developer or the CMS be on Windows? Avoid using spaces in the URLs, use hyphens or underscores. "),{id:"url",title:"Have a good URL format",description:"A clean URL is good for the user and for SEO. Make them human readable, avoid too long URLs, spaces in the URL, too many request parameters, and never ever have the session id in your URL.",advice:n,score:Math.max(t,0),weight:2,severity:"info",offending:[],tags:["bestpractice"]}})()}catch(e){o.url=e.message}try{r.viewport=(function(){"use strict";let e="";const t=document.querySelectorAll("meta");for(let n=0,r=t.length;n<r;n++){const r=t[n].getAttribute("name");if(r&&"viewport"===r.toLowerCase()){e=(t[n].getAttribute("content")||"").trim();break}}let n=100,r="";const o=e.toLowerCase();return 0===e.length?(n=0,r='The page is missing a viewport meta tag. Add <meta name="viewport" content="width=device-width, initial-scale=1"> so the browser lays the page out at the device width.'):(o.includes("width=device-width")||(n-=50,r+="The viewport meta tag does not contain width=device-width, the browser may use a desktop-width fallback. "),(o.includes("user-scalable=no")||o.includes("user-scalable=0"))&&(n-=30,r+="The viewport meta tag disables user-scalable, which breaks pinch-to-zoom and is an accessibility problem. "),/maximum-scale\s*=\s*(0?\.\d+|1(\.0+)?)\b/.test(o)&&(n-=20,r+="The viewport meta tag sets maximum-scale to 1 or less, which prevents the user from zooming in. Remove it. ")),{id:"viewport",title:"Set a sensible viewport meta tag",description:"The viewport meta tag tells the browser how to lay out the page on small screens. Without it (or without width=device-width) the page is rendered at a desktop fallback width and scaled down, which makes text unreadable on mobile. Disabling zoom (user-scalable=no, maximum-scale<=1) is also an accessibility regression. https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag",advice:r.trim(),score:Math.max(0,n),weight:5,severity:"warn",offending:[],tags:["bestpractice"]}})()}catch(e){o.viewport=e.message}t.bestpractice={adviceList:r},Object.keys(o).length>0&&(n.bestpractice=o);var i={},s={};try{i.amp=(function(){"use strict";const e=document.querySelectorAll("html")[0];return!!(e&&e.getAttribute("amp-version")||globalThis.AMP)&&(e.getAttribute("amp-version")||!0)})()}catch(e){s.amp=e.message}try{i.browser=(function(){"use strict";const{userAgent:e}=navigator;return e.includes("Firefox/")?`Firefox ${e.split("Firefox/")[1]}`:e.includes("Edg/")?`Edge ${e.split("Edg/")[1]}`:e.includes("Chrome/")?`Chrome ${e.match(/(Chrome)\/(\S+)/)[2]}`:e.includes("Safari/")?`Safari ${e.match(/(Version)\/(\S+)/)[2]}`:"Unknown"})()}catch(e){s.browser=e.message}try{i.connectionType=(function(e){"use strict";return e.getConnectionType()})(e)}catch(e){s.connectionType=e.message}try{i.documentHeight=(function(){"use strict";return Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)})()}catch(e){s.documentHeight=e.message}try{i.documentTitle=(function(){"use strict";return document.title})()}catch(e){s.documentTitle=e.message}try{i.documentWidth=(function(){"use strict";return Math.max(document.body.scrollWidth,document.body.offsetWidth,document.documentElement.clientWidth,document.documentElement.scrollWidth,document.documentElement.offsetWidth)})()}catch(e){s.documentWidth=e.message}try{i.domDepth=(function(){"use strict";function e(e){let t=0;if(e.parentNode)for(;e=e.parentNode;)t++;return t}const t=(function(t){const n=t.querySelectorAll("*");let r=n.length,o=0,i=0;for(;r--;){let t=e(n[r]);t>i&&(i=t),o+=t}return{avg:o/n.length,max:i}})(document);return{avg:Math.round(t.avg),max:t.max}})()}catch(e){s.domDepth=e.message}try{i.domElements=(function(){"use strict";return document.querySelectorAll("*").length})()}catch(e){s.domElements=e.message}try{i.generator=(function(){"use strict";const e=document.querySelector('meta[name="generator"]');if(e)return e.getAttribute("content")})()}catch(e){s.generator=e.message}try{i.head=(function(e){"use strict";return{jssync:e.getSynchJSFiles(document.head),jsasync:e.getAsynchJSFiles(document.head),css:e.getCSSFiles(document.head)}})(e)}catch(e){s.head=e.message}try{i.iframes=(function(){"use strict";return document.querySelectorAll("iframe").length})()}catch(e){s.iframes=e.message}try{i.localStorageSize=(function(){"use strict";try{return(function(e){if(e){const t=e.length||Object.keys(e).length;let n=0;for(let r=0;r<t;r++){const t=e.key(r),o=e.getItem(t);n+=t.length+o.length}return n}return 0})(globalThis.localStorage)}catch{return"Could not access localStorage."}})()}catch(e){s.localStorageSize=e.message}try{i.metaDescription=(function(){"use strict";const e=document.querySelector('meta[name="description"]'),t=document.querySelector('meta[property="og:description"]');return e?e.getAttribute("content"):t?t.getAttribute("content"):""})()}catch(e){s.metaDescription=e.message}try{i.networkConnectionType=(function(){"use strict";return globalThis.navigator.connection?globalThis.navigator.connection.effectiveType:"unknown"})()}catch(e){s.networkConnectionType=e.message}try{i.resourceHints=(function(e){"use strict";return{"dns-prefetch":e.getResourceHintsHrefs("dns-prefetch"),preconnect:e.getResourceHintsHrefs("preconnect"),prefetch:e.getResourceHintsHrefs("prefetch"),prerender:e.getResourceHintsHrefs("prerender")}})(e)}catch(e){s.resourceHints=e.message}try{i.responsive=(function(){"use strict";let e=!0;const t=document.body.scrollWidth,n=window.innerWidth,r=document.body.children;for(var o in t>n&&(e=!1),r)r[o].scrollWidth>n&&(e=!1);return e})()}catch(e){s.responsive=e.message}try{i.scripts=(function(){"use strict";return document.querySelectorAll("script").length})()}catch(e){s.scripts=e.message}try{i.serializedDomSize=(function(){"use strict";return document.body.innerHTML.length})()}catch(e){s.serializedDomSize=e.message}try{i.serviceWorker=(function(){"use strict";return"serviceWorker"in navigator&&(!!navigator.serviceWorker.controller&&("activated"===navigator.serviceWorker.controller.state&&navigator.serviceWorker.controller.scriptURL))})()}catch(e){s.serviceWorker=e.message}try{i.sessionStorageSize=(function(){"use strict";try{return(function(e){const t=e.length||Object.keys(e).length;let n=0;for(let r=0;r<t;r++){const t=e.key(r),o=e.getItem(t);n+=t.length+o.length}return n})(globalThis.sessionStorage)}catch{return"Could not access sessionStorage"}})()}catch(e){s.sessionStorageSize=e.message}try{i.userTiming=(function(){"use strict";let e=0,t=0;return globalThis.performance&&globalThis.performance.getEntriesByType&&(t=globalThis.performance.getEntriesByType("measure").length,e=globalThis.performance.getEntriesByType("mark").length),{marks:e,measures:t}})()}catch(e){s.userTiming=e.message}try{i.windowSize=(function(){"use strict";return(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+"x"+(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)})()}catch(e){s.windowSize=e.message}t.info=i,Object.keys(s).length>0&&(n.info=s);var a={},c={};try{a.avoidRenderBlocking=(function(e){"use strict";const t=[],n=e.getCSSFiles(document.head),r=e.getSynchJSFiles(document.head),o=document.domain,i=[],s=e.getResourceHintsHrefs("preconnect").map(function(t){return e.getHostname(t)});let a=0,c=0,l="",u=0;function h(n){const r=e.getHostname(n);r===o?(t.push(n),u+=5):(t.push(n),e.exists(r,i)||(u+=e.exists(r,s)?5:10,i.push(r)),u+=5)}if(e.isHTTP2()){if(n.length>0){l="";for(const r of n)e.getTransferSize(r)>14500&&(t.push(r),u+=5,a++,l+="The style "+r+" is larger than the magic number TCP window size 14.5 kB. Make the file smaller and the page will render faster. ")}if(r.length>0){u+=10*r.length;for(const e of r)t.push(e),c++;l+="Avoid loading synchronously JavaScript inside of head, you shouldn't need JavaScript to render your page! "}}else if(e.isHTTP3());else{for(const e of n)h(e);a=n.length;for(const e of r)h(e);c=r.length}return t.length>0&&(l+=`The page has ${e.plural(a,"render blocking CSS request")} and ${e.plural(c,"blocking JavaScript request")} inside of head.`),{id:"avoidRenderBlocking",title:"Avoid slowing down the critical rendering path",description:"The critical rendering path is what the browser needs to do to start rendering the page. Every file requested inside of the head element will postpone the rendering of the page, because the browser need to do the request. Avoid loading JavaScript synchronously inside of the head (you should not need JavaScript to render the page), request files from the same domain as the main document (to avoid DNS lookups) and inline CSS for really fast rendering and a short rendering path.",advice:l,score:Math.max(0,100-u),weight:10,severity:"warn",offending:t,tags:["performance"]}})(e)}catch(e){c.avoidRenderBlocking=e.message}try{a.avoidScalingImages=(function(e){"use strict";const t=[],n=Array.prototype.slice.call(document.querySelectorAll("img"));let r=0,o="";for(let o=0,i=n.length;o<i;o++){const i=n[o];i.clientWidth+100<i.naturalWidth&&i.clientWidth>0&&(t.push(e.getAbsoluteURL(i.currentSrc)),r+=10)}return r>0&&(o=`The page has ${e.plural(r/10,"image")} that are scaled more than 100 pixels. It would be better if those images are sent so the browser don't need to scale them.`),{id:"avoidScalingImages",title:"Don't scale images in the browser",description:"It's easy to scale images in the browser and make sure they look good in different devices, however that is bad for performance! Scaling images in the browser takes extra CPU time and will hurt performance on mobile. And the user will download extra kilobytes (sometimes megabytes) of data that could be avoided. Don't do that, make sure you create multiple version of the same image server-side and serve the appropriate one.",advice:o,score:Math.max(0,100-r),weight:5,severity:"warn",offending:t,tags:["performance","image"]}})(e)}catch(e){c.avoidScalingImages=e.message}try{a.cssPrint=(function(e){"use strict";const t=[],n=document.querySelectorAll("link");for(let r=0,o=n.length;r<o;r++)"print"===n[r].media&&t.push(e.getAbsoluteURL(n[r].href));const r=10*t.length;return{id:"cssPrint",title:"Do not load specific print stylesheets.",description:"Loading a specific stylesheet for printing slows down the page, even though it is not used. You can include the print styles inside your other CSS file(s) just by using an @media query targeting type print.",advice:t.length>0?`The page has ${e.plural(t.length,"print stylesheet")}. You should include that stylesheet using @media type print instead.`:"",score:Math.max(0,100-r),weight:1,severity:"info",offending:t,tags:["performance","css"]}})(e)}catch(e){c.cssPrint=e.message}try{a.decodingAsync=(function(e){"use strict";const t=[],n=document.querySelectorAll("img");let r=0,o=0;for(let i=0,s=n.length;i<s;i++){const s=n[i];(s.src||s.currentSrc)&&(o++,s.hasAttribute("decoding")||(r++,t.push(e.getAbsoluteURL(s.currentSrc||s.src))))}let i=100,s="";if(o>0&&r>0){const t=r/o;i=Math.round(100*(1-t)),s="The page has "+e.plural(r,"image")+" (out of "+o+') without a decoding hint. Add decoding="async" to non-critical images so the browser can decode them off the main thread.'}return{id:"decodingAsync",title:'Add decoding="async" to non-critical images',description:'Setting decoding="async" on an <img> tells the browser it can decode the image off the main thread, which keeps the page responsive to user interactions while images are being processed. The default ("auto") leaves the choice to the browser. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#decoding',advice:s,score:i,weight:2,severity:"info",offending:t,tags:["performance","image"]}})(e)}catch(e){c.decodingAsync=e.message}try{a.firstContentfulPaint=(function(e){"use strict";let t,n=100;const r=performance.getEntriesByName("first-contentful-paint");if(r.length>0){const o=r[0].startTime;if(o<=1800)t=`First contentful paint is good ${e.ms(o)}.`;else if(o<=3e3)n=50,t=`First contentful paint can be improved (${e.ms(o)}). It is in the Google Web Vitals needs improvement range, slower than 1.8 seconds.`;else{n=0,t=`First contentful paint is poor (${e.ms(o)}). It is in the Google Web Vitals poor range, slower than 3 seconds.`;let r=globalThis.performance.getEntriesByType("navigation")[0];r&&Number(r.responseStart.toFixed(0))>1e3&&(t+=`The page has a high time to first byte (TTFB) ${e.ms(r.responseStart)} that you should look into to improve first contentful paint.`)}}else t="There is no first contentful paint for this paint.";return{id:"firstContentfulPaint",title:"Have a fast first contentful paint",description:'The First Contentful Paint (FCP) metric measures the time from when the page starts loading to when any part of the page content is rendered on the screen. For this metric, "content" refers to text, images (including background images), <svg> elements, or non-white <canvas> elements.',advice:t,score:Math.max(0,n),weight:7,severity:"error",offending:[],tags:["performance"]}})(e)}catch(e){c.firstContentfulPaint=e.message}try{a.googleTagManager=(function(){"use strict";var e=100;return globalThis.google_tag_manager&&(e=0),{id:"googleTagManager",title:"Avoid using Google Tag Manager.",description:"Google Tag Manager makes it possible for non tech users to add scripts to your page that will downgrade performance.",advice:0===e?"The page is using Google Tag Manager, this is a performance risk since non-tech users can add JavaScript to your page.":"",score:e,weight:5,severity:"warn",offending:[],tags:["performance","js"]}})()}catch(e){c.googleTagManager=e.message}try{a.inlineCss=(function(e){"use strict";const t=[],n=e.getCSSFiles(document.head),r=Array.prototype.slice.call(globalThis.document.head.querySelectorAll("style"));let o="",i=0;return e.isHTTP2()&&n.length>0&&r.length>0?(i+=5,o="The page has both inline CSS and CSS requests even though it uses a HTTP/2-ish connection. If you have many users on slow connections, it can be better to only inline the CSS. Run your own tests and check the waterfall graph to see what happens."):e.isHTTP2()&&r.length>0&&0===n.length?o+="The page has inline CSS and uses HTTP/2. Do you have a lot of users with slow connections on the site? It is good to inline CSS when using HTTP/2.":e.isHTTP2()&&n.length>0&&(o+="It is always faster for the user if you inline CSS instead of making a CSS request."),e.isHTTP3()?n.length>0&&0===r.length?(i+=5,o="The page uses HTTP/3 and loads "+e.plural(n.length,"CSS request")+" in head. Inline the critical CSS to avoid render-blocking on the first byte and lazy load the rest.",t.push.apply(t,n)):n.length>0&&r.length>0&&(o="The page uses HTTP/3 with both inline CSS and CSS requests in head. That is fine if the inline CSS is the critical path; otherwise drop the external request."):e.isHTTP2()||(n.length>0&&0===r.length&&(i+=10*n.length,o="The page loads "+e.plural(n.length,"CSS request")+" inside of head, try to inline the CSS for the first render and lazy load the rest.",t.push.apply(t,n)),r.length>0&&n.length>0&&(i+=10,o+="The page has both inline styles as well as it is requesting "+e.plural(n.length,"CSS file")+" inside of the head. Let's only inline CSS for really fast render.",t.push.apply(t,n))),{id:"inlineCss",title:"Inline CSS for faster first render",description:"In the early days of the Internet, inlining CSS was one of the ugliest things you can do. That has changed if you want your page to start rendering fast for your user. Always inline the critical CSS when you use HTTP/1 and HTTP/2 (avoid doing CSS requests that block rendering) and lazy load and cache the rest of the CSS. It is a little more complicated when using HTTP/2. Does your server support HTTP push? Then maybe that can help. Do you have a lot of users on a slow connection and are serving large chunks of HTML? Then it could be better to use the inline technique, becasue some servers always prioritize HTML content over CSS so the user needs to download the HTML first, before the CSS is downloaded.",advice:o,score:Math.max(0,100-i),weight:7,severity:"warn",offending:t,tags:["performance","css"]}})(e)}catch(e){c.inlineCss=e.message}try{a.interactionToNextPaint=(function(e){"use strict";let t,n=100;const r=[],o=PerformanceObserver.supportedEntryTypes;if(o&&o.includes("event")){const o=new PerformanceObserver(()=>{});try{o.observe({type:"event",buffered:!0,durationThreshold:40})}catch{o.observe({type:"event",buffered:!0})}const i=o.takeRecords();if(0===i.length)t="No interactions were observed during this page load, so Interaction to Next Paint cannot be assessed in this snapshot. INP is best measured with real-user monitoring.";else{const o={};for(const e of i){const t=e.interactionId;t&&((!o[t]||e.duration>o[t].duration)&&(o[t]=e))}const s=Object.values(o);if(0===s.length)t="Events were observed but none were part of a real interaction (interactionId 0), so Interaction to Next Paint cannot be assessed.";else{let o=s[0];for(let e=1;e<s.length;e++)s[e].duration>o.duration&&(o=s[e]);const i=Math.round(o.duration),a=o.target?o.target.cloneNode(!1).outerHTML:o.name;i<=200?t=`Interaction to Next Paint is good ${e.ms(i)}.`:i<=500?(n=80,t=`Interaction to Next Paint can be improved ${e.ms(i)}. It is in the Google Web Vitals needs-improvement range (slower than 200 ms). The slowest interaction was on ${o.name}.`,r.push(a)):(n=0,t=`Interaction to Next Paint is poor ${e.ms(i)}. It is in the Google Web Vitals poor range (slower than 500 ms). The slowest interaction was on ${o.name}. Look for long tasks on the main thread, expensive event handlers, or layout work triggered by the interaction.`,r.push(a))}}}else t="Interaction to Next Paint is not supported in this browser.";return{id:"interactionToNextPaint",title:"Have a fast Interaction to Next Paint",description:"Interaction to Next Paint (INP) is one of the Google Core Web Vitals — it replaced First Input Delay in March 2024 — and measures the latency of the slowest interaction with the page. To be fast according to Google, the p75 across all interactions should be under 200 ms, and over 500 ms is poor performance. INP is best measured with real-user monitoring; this rule reports what it can observe from the events that happened during the test.",advice:t,score:Math.max(0,n),weight:7,severity:"error",offending:r,tags:["performance"]}})(e)}catch(e){c.interactionToNextPaint=e.message}try{a.jquery=(function(e){"use strict";const t=[];if("function"==typeof globalThis.jQuery){let e=globalThis.$;t.push(globalThis.jQuery.fn.jquery);let n=globalThis.jQuery;for(;n.fn&&n.fn.jquery&&(n=globalThis.jQuery.noConflict(!0),globalThis.jQuery&&globalThis.jQuery.fn&&globalThis.jQuery.fn.jquery)&&n.fn.jquery!==globalThis.jQuery.fn.jquery;)t.push(globalThis.jQuery.fn.jquery);globalThis.jQuery=globalThis.$=e}return{id:"jquery",title:"Avoid using more than one jQuery version per page",description:"There are sites out there that use multiple versions of jQuery on the same page. You shouldn't do that because the user will then unnecessarily download extra data. Cleanup the code and make sure you only use one version.",advice:t.length>1?"The page uses "+e.plural(t.length,"version")+" of jQuery! You only need one version, please remove the unnecessary version(s).":"",score:t.length>1?0:100,weight:1,severity:"info",offending:t,tags:["jQuery","performance"]}})(e)}catch(e){c.jquery=e.message}try{a.largestContentfulPaint=(function(e){"use strict";let t=100,n="";const r=[],o=PerformanceObserver.supportedEntryTypes;if(o&&o.includes("largest-contentful-paint")){const o=new PerformanceObserver(()=>{});o.observe({type:"largest-contentful-paint",buffered:!0});const i=o.takeRecords();if(i.length>0){const o=i.at(-1),s=2500,a=4e3,c={url:o.url,renderTime:Number(Math.max(o.renderTime,o.loadTime).toFixed(0)),tagName:o.element?o.element.tagName:"",tag:o.element?o.element.cloneNode(!1).outerHTML:""};c.renderTime<=s?n=`Largest contentful paint is good ${e.ms(c.renderTime)}.`:c.renderTime<=a?(t=80,n=`Largest contentful paint can be improved ${e.ms(c.renderTime)}. It is in the Google Web Vitals needs improvement range, slower than 2.5 seconds.`,r.push(c.url||c.tag)):c.renderTime>a&&(t=0,n=`Largest contentful paint is poor ${e.ms(c.renderTime)}. It is in the Google Web Vitals poor range, slower than 4 seconds.`,r.push(c.url||c.tag))}}else n="Largest contentful paint is not supported in this browser";return{id:"largestContentfulPaint",title:"Have a fast largest contentful paint",description:"Largest contentful paint is one of Google Web Vitals and reports the render time of the largest image or text block visible within the viewport, relative to when the page first started loading. To be fast according to Google, it needs to render before 2.5 seconds and results over 4 seconds is poor performance.",advice:n,score:Math.max(0,t),weight:7,severity:"error",offending:r,tags:["performance"]}})(e)}catch(e){c.largestContentfulPaint=e.message}try{a.lazyLoadingImages=(function(e){"use strict";const t=2*(window.innerHeight||document.documentElement.clientHeight||0),n=[];let r=0;const o=document.querySelectorAll("img");for(let i=0,s=o.length;i<s;i++){const s=o[i];if(!s.src&&!s.currentSrc)continue;let a;try{a=s.getBoundingClientRect()}catch{continue}0===a.height&&0===a.width||(a.top<t||(r++,"lazy"!==(s.loading||"").toLowerCase()&&n.push(e.getAbsoluteURL(s.currentSrc||s.src))))}let i=100,s="";if(n.length>0){const t=n.length/Math.max(r,1);i=Math.round(100*(1-t)),s="The page has "+e.plural(n.length,"below-the-fold image")+' without loading="lazy". Add loading="lazy" so the browser defers downloading and decoding them until the user scrolls them into view.'}return{id:"lazyLoadingImages",title:"Lazy-load below-the-fold images",description:'Adding loading="lazy" to an <img> tells the browser not to download or decode it until it is close to the viewport. For images that the user may never see (deep in the page, behind a tab, in a footer carousel), this saves bandwidth and main-thread time during initial render. The LCP image and any image in the initial viewport should NOT be lazy-loaded — that delays the first paint. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#loading',advice:s,score:i,weight:4,severity:"warn",offending:n,tags:["performance","image"]}})(e)}catch(e){c.lazyLoadingImages=e.message}try{a.lcpImageHints=(function(){"use strict";let e=100,t="";const n=[],r=PerformanceObserver.supportedEntryTypes;if(r&&r.includes("largest-contentful-paint")){const r=new PerformanceObserver(()=>{});r.observe({type:"largest-contentful-paint",buffered:!0});const o=r.takeRecords();if(0===o.length)t="No LCP entries were observed during this snapshot.";else{const r=o.at(-1).element;if(r&&"IMG"===r.tagName){const o=r.cloneNode(!1).outerHTML,i=(r.fetchPriority||"").toLowerCase();"lazy"===(r.loading||"").toLowerCase()&&(e-=60,t+='The LCP image has loading="lazy", which delays its download until the browser thinks it is near the viewport — and so directly delays the LCP metric. Remove loading="lazy" from the LCP image. ',n.push(o)),"high"!==i&&(e-=30,t+='The LCP image is missing fetchpriority="high". Adding it tells the browser to fetch the image with high priority instead of the default heuristic (which often deprioritises hero images that are loaded after the HTML has been parsed). ',n.push(o))}else t="The LCP element is not an image, so this rule does not apply."}}else t="Largest Contentful Paint is not supported in this browser, so the LCP image hints cannot be assessed.";return{id:"lcpImageHints",title:"Apply the right priority hints to the LCP image",description:'When the Largest Contentful Paint element is an image, the browser priority hints applied to that element directly affect the LCP metric. The image must NOT be loading="lazy" (that defers the fetch until near-viewport, which is the opposite of what an LCP image needs) and SHOULD be fetchpriority="high" (so the browser fetches it with high priority instead of guessing). https://web.dev/articles/fetch-priority',advice:t.trim(),score:Math.max(0,e),weight:5,severity:"warn",offending:n,tags:["performance","image"]}})()}catch(e){c.lcpImageHints=e.message}try{a.longTasks=(function(e){"use strict";const t=[];let n=0,r=0,o=0,i=0,s=0,a="The page do not have any CPU Long Tasks.";const c=PerformanceObserver.supportedEntryTypes;if(c&&c.includes("longtask")){const e=new PerformanceObserver(()=>{});e.observe({type:"longtask",buffered:!0});const a=performance.getEntriesByName("first-contentful-paint").length>0?performance.getEntriesByName("first-contentful-paint")[0].startTime:void 0;for(let c of e.takeRecords())n+=20,r+=c.duration,a&&c.startTime<a?(i++,s+=c.duration):a&&c.startTime>a&&(o+=c.duration-50),t.push(c.name)}else a="The Long Task API is not supported in this browser.";return{id:"longTasks",title:"Avoid CPU Long Tasks",description:'Long CPU tasks locks the thread. To the user this is commonly visible as a "locked up" page where the browser is unable to respond to user input; this is a major source of bad user experience on the web today. However the CPU Long Task is depending on the computer/phones actual CPU speed, so you should measure this on the same type of the device that your user is using. To debug you should use the Chrome timeline log and drag/drop it into devtools or use Firefox Geckoprofiler.',advice:t.length>0?`The page has ${e.plural(t.length,"CPU long task")} with the total of ${e.ms(r.toFixed(0))}. The total blocking time is ${e.ms(o)} ${i>0?` and ${e.plural(i,"long task")} before first contentful paint with total time of ${e.ms(s)}.`:"."} However the CPU Long Task is depending on the computer/phones actual CPU speed, so you should measure this on the same type of the device that your user is using. Use Geckoprofiler for Firefox or Chromes tracelog to debug your long tasks.`:a,score:Math.max(0,100-n),weight:8,severity:"warn",offending:t,tags:["performance","js"]}})(e)}catch(e){c.longTasks=e.message}try{a.modernImageFormats=(function(e){"use strict";const t=/\.(jpe?g|png|gif|bmp)(?:[?#]|$)/i,n=/\.(avif|webp|jxl)(?:[?#]|$)/i;function r(e){let t=e.parentElement;if(!t||"PICTURE"!==t.tagName)return!1;const r=t.querySelectorAll("source");for(const e of r){const t=(e.getAttribute("type")||"").toLowerCase();if("image/avif"===t||"image/webp"===t||"image/jxl"===t)return!0;const r=e.getAttribute("srcset")||"";if(n.test(r))return!0}return!1}function o(e){const t=e.getAttribute("srcset")||"";return n.test(t)}const i=[];let s=0,a=0;const c=document.querySelectorAll("img");for(let l=0,u=c.length;l<u;l++){const u=c[l],h=u.currentSrc||u.src||"";h&&0!==h.indexOf("data:")&&(n.test(h)?a++:t.test(h)&&(a++,r(u)||o(u)||(s++,i.push(e.getAbsoluteURL(h)))))}let l=100,u="";if(a>0&&s>0){const t=s/a;l=Math.round(100*(1-t)),u="The page ships "+e.plural(s,"image")+" (out of "+a+') in JPEG/PNG/GIF without a modern alternative. Wrap them in a <picture> with a <source type="image/avif"> or "image/webp" before the legacy <img>, or serve modern formats from your image pipeline directly. AVIF and WebP usually deliver 25–50% smaller files at the same quality.'}return{id:"modernImageFormats",title:"Serve images in modern formats (AVIF, WebP)",description:'AVIF and WebP routinely deliver 25–50% smaller files than JPEG and PNG at the same perceived quality, and every browser version still under support understands at least one of them. Ship modern formats either through a <picture> element with <source type="image/avif"> / "image/webp" entries in front of the legacy <img>, or directly from a content-negotiating image pipeline that returns AVIF / WebP when the client accepts it. https://web.dev/articles/serve-images-webp',advice:u,score:l,weight:4,severity:"warn",offending:i,tags:["performance","image"]}})(e)}catch(e){c.modernImageFormats=e.message}try{a.spof=(function(e){"use strict";const t=[],n=[],r=document.domain,o=e.getCSSFiles(document.head);let i=0;for(const s of o){const o=e.getHostname(s);o!==r&&(t.push(s),n.includes(o)||(n.push(o),i+=10))}const s=e.getSynchJSFiles(document.head);for(const o of s){const s=e.getHostname(o);s!==r&&(t.push(o),n.includes(s)||(n.push(s),i+=10))}return{id:"spof",title:"Avoid Frontend single point of failures",description:"A page can be stopped from loading in the browser if a single JavaScript, CSS, and in some cases a font, couldn't be fetched or is loading really slowly (the white screen of death). That is a scenario you really want to avoid. Never load 3rd-party components synchronously inside of the head tag.",advice:t.length>0?"The page has "+e.plural(t.length,"request")+" inside of the head that can cause a SPOF (single point of failure). Load them asynchronously or move them outside of the document head.":"",score:Math.max(0,100-i),weight:7,severity:"error",offending:t,tags:["performance","css","js"]}})(e)}catch(e){c.spof=e.message}t.performance={adviceList:a},Object.keys(c).length>0&&(n.performance=c);var l={},u={};try{l.facebook=(function(){"use strict";let e=100;return globalThis.FB&&(e=0),{id:"facebook",title:"Avoid including Facebook",description:"You share share private user information with Facebook that your user hasn't agreed on sharing.",advice:0===e?"The page gets content from Facebook. That means you share your users private information with Facebook.":"",score:e,weight:8,severity:"warn",offending:[],tags:["privacy"]}})()}catch(e){u.facebook=e.message}try{l.fingerprint=(function(){"use strict";let e=100;return(globalThis.FingerprintJS||globalThis.Fingerprint2)&&(e=0),{id:"fingerprint",title:"Do not fingerprint your user.",description:'Fingerprinting consists of collecting different kinds of information about the user with the goal of building a unique "fingerprint" for them. Different types of fingerprinting are used on the web by trackers. Browser fingerprinting use characteristics specific to the browser of the user, relying on the fact that the chance of another user having the exact same browser set-up is fairly small if there are a large enough number of variables to track',advice:0===e?"The page uses https://fingerprintjs.com to fingerprint the user.":"",score:e,weight:8,severity:"warn",offending:[],tags:["privacy"]}})()}catch(e){u.fingerprint=e.message}try{l.ga=(function(){"use strict";let e=100;const t=!(!globalThis.ga||!globalThis.ga.create),n="function"==typeof globalThis.gtag&&Array.isArray(globalThis.dataLayer)&&globalThis.dataLayer.some(function(e){return e&&"config"===e[0]&&"string"==typeof e[1]&&0===e[1].indexOf("G-")});return(t||n)&&(e=0),{id:"ga",title:"Avoid using Google Analytics",description:"Google Analytics share private user information with Google that your user hasn't agreed on sharing.",advice:0===e?"The page is using Google Analytics meaning you share your users private information with Google. You should use analytics that care about user privacy, something like https://matomo.org.":"",score:e,weight:8,severity:"warn",offending:[],tags:["privacy"]}})()}catch(e){u.ga=e.message}try{l.https=(function(){"use strict";let e=100,t="";return document.URL.includes("https://")||(e=0,t="What!! The page is not using HTTPS. Every unencrypted HTTP request reveals information about user’s behavior, read more about it at https://https.cio.gov/everything/. You can get a totally free SSL/TLS certificate from https://letsencrypt.org/."),{id:"https",title:"Serve your content securely",description:"A page should always use HTTPS (https://https.cio.gov/everything/). You also need that for HTTP/2. You can get your free SSL/TLC certificate from https://letsencrypt.org/.",advice:t,score:e,weight:10,severity:"error",offending:[],tags:["privacy"]}})()}catch(e){u.https=e.message}try{l.iframeSandbox=(function(e){"use strict";const t=e.getHostname(document.URL).toLowerCase(),n=[],r=document.querySelectorAll("iframe");for(let o=0,i=r.length;o<i;o++){const i=r[o];if(!i.src)continue;if(!/^https?:/i.test(i.src))continue;const s=e.getHostname(i.src).toLowerCase();s&&s!==t&&(i.hasAttribute("sandbox")||n.push(e.getAbsoluteURL(i.src)))}const o=n.length>0?0:100;return{id:"iframeSandbox",title:"Sandbox cross-origin iframes",description:"Adding a sandbox attribute to a cross-origin iframe restricts what the embedded page can do (script execution, form submission, top-level navigation, popups, etc.) and is one of the cheapest ways to limit the blast radius of a third-party embed. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox",advice:0===o?"The page embeds "+e.plural(n.length,"cross-origin iframe")+' without a sandbox attribute. Add sandbox="" with the minimum set of allow-* tokens the embed actually needs.':"",score:o,weight:4,severity:"info",offending:n,tags:["privacy"]}})(e)}catch(e){u.iframeSandbox=e.message}try{l.referrerPolicy=(function(){"use strict";let e="";const t=document.querySelectorAll("meta");for(let n=0,r=t.length;n<r;n++){const r=t[n].getAttribute("name");if(r&&"referrer"===r.toLowerCase()){e=(t[n].getAttribute("content")||"").trim();break}}const n=e.length>0?100:0;return{id:"referrerPolicy",title:"Declare a referrer policy on the document",description:'Without an explicit referrer policy the browser falls back to the user-agent default and may leak the full URL of the previous page (including query strings) to every cross-origin request. Set a Referrer-Policy response header (preferred) or a <meta name="referrer"> tag in the document. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy',advice:0===n?'No <meta name="referrer"> tag was found on the page. Set a Referrer-Policy response header (preferred) or add a meta tag, for example <meta name="referrer" content="strict-origin-when-cross-origin">.':"",score:n,weight:3,severity:"warn",offending:[],tags:["privacy"]}})()}catch(e){u.referrerPolicy=e.message}try{l.sessionReplay=(function(e){"use strict";const t=["hotjar.com","fullstory.com","clarity.ms","smartlook.com","smartlook.cloud","logrocket.com","logrocket.io","lr-ingest.io","lr-in.com","lrkt-in.com","mouseflow.com","quantummetric.com","inspectlet.com","luckyorange.com","luckyorange.net","crazyegg.com"];function n(n){if(!n)return!1;const r=e.getHostname(n).toLowerCase();if(!r)return!1;for(const e of t)if(r===e||r.endsWith("."+e))return!0;return!1}const r=[],o=document.querySelectorAll("script");for(let t=0,i=o.length;t<i;t++)o[t].src&&n(o[t].src)&&r.push(e.getAbsoluteURL(o[t].src));const i=document.querySelectorAll("iframe");for(let t=0,o=i.length;t<o;t++)i[t].src&&n(i[t].src)&&r.push(e.getAbsoluteURL(i[t].src));const s=r.length>0?0:100;return{id:"sessionReplay",title:"Configure session-replay tools carefully if you use them",description:"Session-replay tools (Hotjar, FullStory, Microsoft Clarity, LogRocket, Smartlook, Mouseflow and similar) record user behaviour on the page in detail. They have legitimate uses but they have also been shown to capture personally-identifiable information from form fields when not configured with redaction. If you use one of these, make sure input redaction is on, that you have explicit user consent under the relevant regulation (GDPR, CCPA, etc.) before recording starts, and that you know where the recordings are stored and for how long. https://www.princeton.edu/~jmayer/papers/Acar2018.pdf",advice:0===s?"The page loads "+e.plural(r.length,"session-replay script")+". Confirm input redaction is enabled, that you have explicit user consent before recording starts, and that the data-retention and storage location of the recordings match your privacy policy.":"",score:s,weight:5,severity:"warn",offending:r,tags:["privacy"]}})(e)}catch(e){u.sessionReplay=e.message}try{l.surveillance=(function(e){"use strict";const t=["google-analytics.com","googletagmanager.com","doubleclick.net","googlesyndication.com","googleadservices.com","googletagservices.com","facebook.com","facebook.net","fbcdn.net","youtube.com","twitter.com","twimg.com","t.co","ads-twitter.com","linkedin.com","licdn.com","tiktok.com","tiktokcdn.com","tiktokv.com","bytedance.com","snapchat.com","snap.com","sc-static.net","pinterest.com","pinimg.com","reddit.com","redditstatic.com","bat.bing.com","mc.yandex.ru","mc.yandex.com","hm.baidu.com"];function n(n){if(!n)return!1;const r=e.getHostname(n).toLowerCase();if(!r)return!1;for(const e of t)if(r===e||r.endsWith("."+e))return!0;return!1}const r=[],o=document.querySelectorAll("script");for(let t=0,i=o.length;t<i;t++)o[t].src&&n(o[t].src)&&r.push(e.getAbsoluteURL(o[t].src));const i=document.querySelectorAll("iframe");for(let t=0,o=i.length;t<o;t++)i[t].src&&n(i[t].src)&&r.push(e.getAbsoluteURL(i[t].src));const s=r.length>0?0:100;return{id:"surveillance",title:"Avoid embedding services from surveillance capitalist companies",description:"Embedding scripts or iframes from companies whose business model is surveillance capitalism (Google, Facebook, etc.) leaks detailed user data on every page view, often before the user has had a chance to consent. See https://en.wikipedia.org/wiki/Surveillance_capitalism for background. Prefer privacy-respecting alternatives where possible.",advice:0===s?"The page embeds "+e.plural(r.length,"resource")+" from companies that profit from user surveillance. Consider privacy-respecting alternatives.":"",score:s,weight:10,severity:"warn",offending:r,tags:["privacy"]}})(e)}catch(e){u.surveillance=e.message}try{l.youtube=(function(){"use strict";let e=100;return globalThis.YT&&(e=0),{id:"youtube",title:"Avoid including Youtube videos",description:"If you include Youtube videos on your page, you are sharing private user information with Google.",advice:0===e?"The page is including code from Youtube. You share user private information with Google. Instead you can host a video screenshot and let the user choose to go to Youtube or not, by clicking on the screenshot. You can look at http://labnol.org/?p=27941 and make sure you host your screenshot yourself. Or choose another video service.":"",score:e,weight:6,severity:"info",offending:[],tags:["privacy"]}})()}catch(e){u.youtube=e.message}t.privacy={adviceList:l},Object.keys(u).length>0&&(n.privacy=u);var h={},d={};try{h.elementTimings=(function(){"use strict";const e=PerformanceObserver.supportedEntryTypes;if(!e||!e.includes("element"))return;const t=new PerformanceObserver(()=>{});t.observe({type:"element",buffered:!0});const n=t.takeRecords(),r={};for(let e of n)r[e.identifier]={duration:e.duration,url:e.url,loadTime:Number(e.loadTime.toFixed(0)),renderTime:Number(e.renderTime.toFixed(0)),startTime:Number(e.startTime.toFixed(0)),naturalHeight:e.naturalHeight,naturalWidth:e.naturalWidth,tagName:e.element?e.element.tagName:""};return r})()}catch(e){d.elementTimings=e.message}try{h.fullyLoaded=(function(){"use strict";if(globalThis.performance&&globalThis.performance.getEntriesByType){const e=globalThis.performance.getEntriesByType("resource");let t=0;for(let n=0,r=e.length;n<r;n++)e[n].responseEnd>t&&(t=e[n].responseEnd);return t}return-1})()}catch(e){d.fullyLoaded=e.message}try{h.largestContentfulPaint=(function(){"use strict";const e=PerformanceObserver.supportedEntryTypes;if(!e||!e.includes("largest-contentful-paint"))return;const t=new PerformanceObserver(()=>{});t.observe({type:"largest-contentful-paint",buffered:!0});const n=t.takeRecords();if(n.length>0){const e=n.at(-1);return{duration:e.duration,id:e.id,url:e.url,loadTime:Number(e.loadTime.toFixed(0)),renderTime:Number(Math.max(e.renderTime,e.loadTime).toFixed(0)),size:e.size,startTime:Number(e.startTime.toFixed(0)),tagN