diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ce32b387d7..3633ba349c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -24,6 +24,11 @@ jobs: sudo apt-get update sudo apt-get --only-upgrade install google-chrome-stable google-chrome --version + - name: Install dependencies + run: | + python -m pip install --upgrade --user pip + python -m pip install --user scipy + python -m pip show scipy - name: Install Firefox uses: browser-actions/setup-firefox@latest #with: @@ -69,4 +74,6 @@ jobs: - name: Run test with Influx 2.6.1 run: bin/sitespeed.js http://127.0.0.1:3001/simple/ -n 1 --influxdb.host 127.0.0.1 --influxdb.port 8087 --influxdb.version 2 --influxdb.organisation sitespeed --influxdb.token sitespeed --xvfb - name: Run Chrome test with config - run: node bin/sitespeed.js --config test/exampleConfig.json http://127.0.0.1:3001/simple/ --xvfb \ No newline at end of file + run: node bin/sitespeed.js --config test/exampleConfig.json http://127.0.0.1:3001/simple/ --xvfb + - name: Run Chrome test using compare plugin + run: node bin/sitespeed.js --compare.id compare --compare.saveBaseline --compare.baselinePath test/ http://127.0.0.1:3001/simple/ --xvfb \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5500817034..fb90e4c4c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,4 +44,6 @@ RUN echo 'ALL ALL=NOPASSWD: /usr/sbin/tc, /usr/sbin/route, /usr/sbin/ip' > /etc/ ENTRYPOINT ["/start.sh"] VOLUME /sitespeed.io +VOLUME /baseline + WORKDIR /sitespeed.io diff --git a/docs/_includes/version/browsertime.txt b/docs/_includes/version/browsertime.txt index 2941b81991..5fc5a7bfee 100644 --- a/docs/_includes/version/browsertime.txt +++ b/docs/_includes/version/browsertime.txt @@ -1 +1 @@ -19.0.0 \ No newline at end of file +19.1.0 \ No newline at end of file diff --git a/lib/cli/cli.js b/lib/cli/cli.js index c20f457f96..c4d2444e3f 100644 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -1870,6 +1870,68 @@ export async function parseCommandLine() { group: 'API' }); + parsed + .option('compare.id', { + type: 'string', + describe: + 'The id of the test. Will be used to find the baseline test, that is using the id as a part of the name.', + group: 'compare' + }) + .option('compare.baselinePath', { + type: 'string', + describe: + 'Specifies the path to the baseline data file. This file is used as a reference for comparison against the current test data.', + group: 'compare' + }) + .option('compare.saveBaseline', { + type: 'boolean', + default: false, + describe: + 'Determines whether to save the current test data as the new baseline. Set to true to save the current data as baseline for future comparisons.', + group: 'compare' + }) + .option('compare.testType', { + describe: + 'Selects the statistical test type to be used for comparison. Options are mannwhitneyu for the Mann-Whitney U test and wilcoxon for the Wilcoxon signed-rank test.', + choices: ['mannwhitneyu', ' wilcoxon'], + default: 'mannwhitneyu', + group: 'compare' + }) + .option('compare.alternative', { + choices: ['less', ' greater', 'two-sided'], + default: 'less', + describe: + 'Specifies the alternative hypothesis to be tested. Options are less for one-sided test where the first group is expected to be less than the second, greater for one-sided test with the first group expected to be greater, or two-sided for a two-sided test.', + group: 'compare' + }) + .option('compare.wilcoxon.correction', { + type: 'boolean', + describe: + 'Enables or disables the continuity correction in the Wilcoxon signed-rank test. Set to true to enable the correction.', + default: false, + group: 'compare' + }) + .option('compare.wilcoxon.zeroMethod', { + choices: ['wilcox', ' pratt', 'zsplit'], + describe: + 'Specifies the method for handling zero differences in the Wilcoxon test. wilcox discards all zero-difference pairs, pratt includes all, and zsplit splits them evenly among positive and negative ranks.', + default: 'zsplit', + group: 'compare' + }) + .option('compare.mannwhitneyu.useContinuity', { + type: 'boolean', + default: false, + describe: + 'Determines whether to use continuity correction in the Mann-Whitney U test. Set to true to apply the correction.', + group: 'compare' + }) + .option('compare.mannwhitneyu.method', { + choices: ['auto', ' exact', 'symptotic'], + escribe: + 'Selects the method for calculating the Mann-Whitney U test. auto automatically selects between exact and asymptotic based on sample size, exact uses the exact distribution of U, and symptotic uses a normal approximation.', + default: 'auto', + group: 'compare' + }); parsed .option('mobile', { describe: diff --git a/lib/plugins/compare/baseline.js b/lib/plugins/compare/baseline.js new file mode 100644 index 0000000000..a945caee8e --- /dev/null +++ b/lib/plugins/compare/baseline.js @@ -0,0 +1,35 @@ +import fs from 'node:fs/promises'; +import { join, resolve } from 'node:path'; + +export async function getBaseline(id, compareOptions) { + try { + return JSON.parse( + await fs.readFile( + resolve( + join(compareOptions.baselinePath || process.cwd(), `${id}.json`) + ) + ) + ); + } catch { + return; + } +} +/* +async function getBaselineFromInternet(url) { + try { + const response = await fetch(url); + return response.json(); + } catch (error) { + log.error('Could not fetch', error); + } +} + +async function getBaselineFromFile(path) {} + +/* +export async function saveBaseline(json, options) { + +}*/ +export async function saveBaseline(json, name) { + return fs.writeFile(resolve(name), JSON.stringify(json)); +} diff --git a/lib/plugins/compare/helper.js b/lib/plugins/compare/helper.js new file mode 100644 index 0000000000..22df67a32a --- /dev/null +++ b/lib/plugins/compare/helper.js @@ -0,0 +1,347 @@ +import { fileURLToPath } from 'node:url'; +import { join } from 'node:path'; +import path from 'node:path'; + +import { execa } from 'execa'; +import { Stats } from 'fast-stats'; +import intel from 'intel'; +import { decimals } from '../../support/helpers/index.js'; +const log = intel.getLogger('sitespeedio.plugin.compare'); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +class Metric { + constructor(name, values) { + this.name = name; + this.stats = new Stats().push(values); + } + + getName() { + return this.name; + } + getValues() { + return this.stats.data; + } + getStats() { + return this.stats; + } +} + +export async function runStatisticalTests(data) { + let extras = ''; + try { + const { stdout } = await execa( + process.env.PYTHON || 'python', + [join(__dirname, 'statistical.py')], + { + input: JSON.stringify(data) + } + ); + extras = stdout; + const results = JSON.parse(stdout); + log.verbose('Result from the python script %j'.results); + return results; + } catch (error) { + log.error(error); + log.error(extras); + } +} + +export function getStatistics(arrayOfValues) { + return new Stats().push(arrayOfValues); +} + +function getExtras(data) { + const metrics = {}; + const results = {}; + + for (const run of data.extras) { + for (const name of Object.keys(run)) { + if (!metrics[name]) { + metrics[name] = []; + } + metrics[name].push(run[name]); + } + } + + for (const [metricName, values] of Object.entries(metrics)) { + results[metricName] = new Metric(metricName, values); + } + + return results; +} + +function getTimings(data) { + const timingMetrics = { + ttfb: [], + loadEventEnd: [], + firstContentfulPaint: [], + fullyLoaded: [] + }; + + for (const run of data.browserScripts) { + timingMetrics['ttfb'].push(run.timings.ttfb); + timingMetrics['loadEventEnd'].push(run.timings.loadEventEnd); + timingMetrics['firstContentfulPaint'].push( + run.timings.paintTiming['first-contentful-paint'] + ); + } + + for (const run of data.fullyLoaded) { + timingMetrics['fullyLoaded'].push(run); + } + + const results = {}; + for (const [metricName, values] of Object.entries(timingMetrics)) { + if (!results.timings) { + results.timings = {}; + } + results.timings[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getUserTimings(data) { + const userTimingMetrics = {}; + for (const run of data.browserScripts) { + if (run.timings.userTimings) { + const { marks, measures } = run.timings.userTimings; + + for (const mark of marks) { + if (!userTimingMetrics[mark.name]) { + userTimingMetrics[mark.name] = []; + } + userTimingMetrics[mark.name].push(decimals(mark.startTime)); + } + + for (const measure of measures) { + if (!userTimingMetrics[measure.name]) { + userTimingMetrics[measure.name] = []; + } + userTimingMetrics[measure.name].push(decimals(measure.startTime)); + } + } + } + + const results = {}; + for (const [metricName, values] of Object.entries(userTimingMetrics)) { + if (!results.userTimings) { + results.userTimings = {}; + } + results.userTimings[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getElementTimings(data) { + const elementTimingMetrics = {}; + for (const run of data.browserScripts) { + if (run.timings.elementTimings) { + for (const [name, timing] of Object.entries(run.timings.elementTimings)) { + if (!elementTimingMetrics[name]) { + elementTimingMetrics[name] = []; + } + elementTimingMetrics[name].push(timing.renderTime); + } + } + } + + const results = {}; + for (const [metricName, values] of Object.entries(elementTimingMetrics)) { + if (!results.elementTimings) { + results.elementTimings = {}; + } + results.elementTimings[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getGoogleWebVitals(data) { + const googleWebVitalsMetrics = {}; + for (const run of data.googleWebVitals) { + for (const [name, value] of Object.entries(run)) { + if (!googleWebVitalsMetrics[name]) { + googleWebVitalsMetrics[name] = []; + } + googleWebVitalsMetrics[name].push(value); + } + } + + const results = {}; + for (const [metricName, values] of Object.entries(googleWebVitalsMetrics)) { + if (!results.googleWebVitals) { + results.googleWebVitals = {}; + } + results.googleWebVitals[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getVisualMetrics(data) { + const DO_NOT_USE = new Set([ + 'VisualProgress', + 'videoRecordingStart', + 'VisualComplete85', + 'VisualComplete95', + 'VisualComplete99' + ]); + + const visualMetrics = {}; + for (const run of data.visualMetrics) { + for (const [name, value] of Object.entries(run)) { + if (!DO_NOT_USE.has(name)) { + if (!visualMetrics[name]) { + visualMetrics[name] = []; + } + visualMetrics[name].push(value); + } + } + } + + const results = {}; + for (const [metricName, values] of Object.entries(visualMetrics)) { + if (!results.visualMetrics) { + results.visualMetrics = {}; + } + results.visualMetrics[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getCDPPerformance(data) { + const metricsToKeep = new Set([ + 'JSEventListeners', + 'LayoutCount', + 'RecalcStyleCount', + 'LayoutDuration', + 'RecalcStyleDuration', + 'ScriptDuration', + 'V8CompileDuration', + 'TaskDuration', + 'TaskOtherDuration', + 'JSHeapUsedSize' + ]); + const cdpPerformance = {}; + for (const run of data.cdp.performance) { + for (const name of Object.keys(run)) { + if (metricsToKeep.has(name)) { + if (!cdpPerformance[name]) { + cdpPerformance[name] = []; + } + cdpPerformance[name].push(decimals(run[name])); + } + } + } + + // Convert to Metric objects + const results = {}; + for (const [metricName, values] of Object.entries(cdpPerformance)) { + if (!results.cdp) { + results.cdp = {}; + } + results.cdp[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getCPU(data) { + const cpuMetrics = { + tasks: [], + totalDuration: [], + lastLongTask: [], + beforeFirstContentfulPaint: [], + beforeLargestContentfulPaint: [] + }; + + for (const run of data.cpu) { + const longTasks = run.longTasks; + cpuMetrics['tasks'].push(longTasks['tasks']); + cpuMetrics['totalDuration'].push(longTasks['totalDuration']); + cpuMetrics['lastLongTask'].push(longTasks['lastLongTask']); + cpuMetrics['beforeFirstContentfulPaint'].push( + longTasks['beforeFirstContentfulPaint'].totalDuration + ); + cpuMetrics['beforeLargestContentfulPaint'].push( + longTasks['beforeLargestContentfulPaint'].totalDuration + ); + } + + const isEmpty = Object.values(cpuMetrics).every(arr => arr.length === 0); + if (isEmpty) { + return {}; // Return an empty object if no data + } + + const results = {}; + for (const [metricName, values] of Object.entries(cpuMetrics)) { + if (!results.cpu) { + results.cpu = {}; + } + results.cpu[metricName] = new Metric(`${metricName}`, values); + } + return results; +} + +function getRenderBlocking(data) { + const renderBlockingMetrics = { + beforeFCPms: [], + beforeLCPms: [], + beforeFCPelements: [], + beforeLCPelements: [] + }; + + for (const run of data.renderBlocking) { + renderBlockingMetrics['beforeFCPms'].push( + run.recalculateStyle.beforeFCP.durationInMillis + ); + renderBlockingMetrics['beforeFCPelements'].push( + run.recalculateStyle.beforeFCP.elements + ); + renderBlockingMetrics['beforeLCPms'].push( + run.recalculateStyle.beforeLCP.durationInMillis + ); + renderBlockingMetrics['beforeLCPelements'].push( + run.recalculateStyle.beforeLCP.elements + ); + } + + // Check if all arrays in renderBlockingMetrics are empty + const isEmpty = Object.values(renderBlockingMetrics).every( + arr => arr.length === 0 + ); + if (isEmpty) { + return {}; // Return an empty object if no data + } + + const results = {}; + for (const [metricName, values] of Object.entries(renderBlockingMetrics)) { + if (!results.renderBlocking) { + results.renderBlocking = {}; + } + results.renderBlocking[metricName] = new Metric(`${metricName}`, values); + } + + return results; +} + +export function getMetrics(data) { + const userTimings = getUserTimings(data); + const elementTimings = getElementTimings(data); + const visualMetrics = getVisualMetrics(data); + const rb = getRenderBlocking(data); + const gWV = getGoogleWebVitals(data); + const cdp = getCDPPerformance(data); + const cpu = getCPU(data); + const timings = getTimings(data); + const extras = getExtras(data); + return { + ...extras, + ...timings, + ...cpu, + ...cdp, + ...visualMetrics, + ...gWV, + ...rb, + ...elementTimings, + ...userTimings + }; +} diff --git a/lib/plugins/compare/index.js b/lib/plugins/compare/index.js new file mode 100644 index 0000000000..b0958b07c8 --- /dev/null +++ b/lib/plugins/compare/index.js @@ -0,0 +1,229 @@ +import { fileURLToPath } from 'node:url'; +import { readFileSync } from 'node:fs'; +import { resolve, join } from 'node:path'; + +import { SitespeedioPlugin } from '@sitespeed.io/plugin'; +import intel from 'intel'; +import merge from 'lodash.merge'; +import dayjs from 'dayjs'; + +import { throwIfMissing } from '../../support/util.js'; +import { getStatistics, runStatisticalTests, getMetrics } from './helper.js'; +import { getBaseline, saveBaseline } from './baseline.js'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); + +const log = intel.getLogger('sitespeedio.plugin.compare'); +const defaultConfig = {}; + +const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; + +const DEFAULT_METRICS_PAGESUMMARY = [ + 'metrics.timings.*.statisticalTestU', + 'metrics.cpu.*.statisticalTestU', + 'metrics.cdp.*.statisticalTestU', + 'metrics.visualMetrics.*.statisticalTestU', + 'metrics.googleWebVitals.*.statisticalTestU', + 'metrics.renderBlocking.*.statisticalTestU', + 'metrics.elementTimings.*.statisticalTestU', + 'metrics.userTimings.*.statisticalTestU', + 'metrics.extras.*.statisticalTestU' +]; + +export default class ComparePlugin extends SitespeedioPlugin { + constructor(options, context, queue) { + super({ name: 'compare', options, context, queue }); + } + + async open(context, options) { + this.page = 0; + this.make = context.messageMaker('compare').make; + this.compareOptions = merge({}, defaultConfig, options.compare); + this.options = options; + throwIfMissing(options.compare, ['id'], 'compare'); + this.pug = readFileSync(resolve(__dirname, 'pug', 'index.pug'), 'utf8'); + log.info( + 'Starting the compare plugin.' + + (this.compareOptions.saveBaseline + ? ' Will save this test as the baseline' + : '') + ); + if (options.browsertime.iterations < 20) { + log.warning( + 'You should use 20+ iterations to get statistical significant data' + ); + } + context.filterRegistry.registerFilterForType( + DEFAULT_METRICS_PAGESUMMARY, + 'compare.pageSummary' + ); + } + async processMessage(message) { + switch (message.type) { + case 'sitespeedio.setup': { + super.sendMessage('compare.setup'); + // Add the HTML pugs + super.sendMessage('html.pug', { + id: 'compare', + name: 'Compare', + pug: this.pug, + type: 'pageSummary' + }); + break; + } + case 'browsertime.pageSummary': { + this.page++; + const baseline = await getBaseline( + this.options.compare.id + '-' + this.page, + this.compareOptions + ); + + if (baseline) { + if ( + baseline && + this.options.browsertime.iterations !== baseline.timestamps.length + ) + log.warning( + 'The baseline test has %s runs and you current have %s. You should make sure you test the same amount of runs', + baseline.timestamps.length, + this.options.browsertime.iterations + ); + log.info( + 'Got a baseline:' + this.options.compare.id + '-' + this.page + ); + const newMetrics = getMetrics(message.data); + const baselineMetrics = getMetrics(baseline); + const metricsInputData = { + options: { + test_type: this.compareOptions.testType, + alternative: this.compareOptions.alternative + }, + metrics: {} + }; + + if (this.compareOptions.testType === 'mannwhitneyu') { + metricsInputData.options.use_continuity = + this.compareOptions.mannwhitneyu.useContinuity; + metricsInputData.options.method = + this.compareOptions.mannwhitneyu.method; + metricsInputData.options.nan_policy = 'omit'; + } else if (this.compareOptions.testType === 'wilcoxon') { + metricsInputData.options.correction = + this.compareOptions.wilcoxon.correction; + metricsInputData.options.zero_method = + this.compareOptions.wilcoxon.zeroMethod; + } + + for (let group in newMetrics) { + if (baselineMetrics[group]) { + metricsInputData.metrics[group] = {}; + for (let metricName in newMetrics[group]) { + // Ensure both current and baseline metrics are available + if ( + baselineMetrics[group][metricName] && + newMetrics[group][metricName] + ) { + // Directly access the Metric instance + const currentMetric = newMetrics[group][metricName]; + const baselineMetric = baselineMetrics[group][metricName]; + + // Ensure these are indeed Metric instances + const currentStats = getStatistics(currentMetric.getValues()); + const baselineStats = getStatistics( + baselineMetric.getValues() + ); + metricsInputData.metrics[group][metricName] = { + sample1: baselineStats.data, + sample2: currentStats.data + }; + } else { + log.info( + `Skipping ${group}.${metricName} as it's not present in both current and baseline metrics.` + ); + } + } + } + } + + const results = await runStatisticalTests(metricsInputData); + const finalResult = {}; + for (let group in results) { + finalResult[group] = {}; + for (let metricName in results[group]) { + const result = results[group][metricName]; + // Again, accessing the metricName within the group + const currentStats = getStatistics( + newMetrics[group][metricName].getValues() + ); + const baselineStats = getStatistics( + baselineMetrics[group][metricName].getValues() + ); + + finalResult[group][metricName] = { + current: { + stdev: currentStats.stddev(), + mean: currentStats.amean(), + median: currentStats.median(), + values: currentStats.data + }, + baseline: { + stdev: baselineStats.stddev(), + mean: baselineStats.amean(), + median: baselineStats.median(), + values: baselineStats.data + }, + statisticalTestU: result['p-value'] + }; + } + } + const meta = { + baseline: { + timestamp: dayjs(baseline.info.timestamp).format(TIME_FORMAT), + url: baseline.info.url, + alias: baseline.info.alias + }, + current: { + timestamp: dayjs(message.data.info.timestamp).format(TIME_FORMAT), + url: message.data.info.url, + alias: message.data.info.alias + }, + testOptions: this.compareOptions, + iterations: this.options.browsertime.iterations + }; + + if (this.compareOptions.saveBaseline) { + await saveBaseline( + message.data, + join( + this.compareOptions.baselinePath || process.cwd(), + `${this.options.compare.id}-${this.page}.json` + ) + ); + } + + super.sendMessage( + 'compare.pageSummary', + { metrics: finalResult, meta }, + { + url: message.url, + group: message.group, + runTime: message.runTime + } + ); + } else { + if (this.compareOptions.saveBaseline) { + await saveBaseline( + message.data, + join( + this.compareOptions.baselinePath || process.cwd(), + `${this.options.compare.id}-${this.page}.json` + ) + ); + } + } + + break; + } + } + } +} diff --git a/lib/plugins/compare/pug/index.pug b/lib/plugins/compare/pug/index.pug new file mode 100644 index 0000000000..c92698f6f5 --- /dev/null +++ b/lib/plugins/compare/pug/index.pug @@ -0,0 +1,135 @@ +- const compare = pageInfo.data.compare.pageSummary; + +h1 Compare + +if compare.meta.iterations < 21 + p.error + | Warning: The number of iterations (#{compare.meta.iterations}) is less than the + a(href='https://en.wikipedia.org/wiki/Mann–Whitney_U_test#Calculations') recommended minimum of 21. + | This may impact the reliability of the statistical comparison. + +p + | In this web performance comparison, statistical tests are employed to analyze the significance of performance changes between baseline and current measurements. The Mann-Whitney U test, ideal for comparing independent samples, is used when analyzing different web pages or different conditions (e.g., comparing load times of a page with and without a new optimization). The Wilcoxon signed-rank test is applied to related or paired samples, such as comparing the response times of the same website before and after applying a specific optimization technique. + +h2 Settings +p + | The test conducted in this comparison is the #{compare.meta.testOptions.testType} test. The alternative hypothesis used for this test is "#{compare.meta.testOptions.alternative}". + if compare.meta.testOptions.testType === 'mannwhitneyu' + | For more information on the settings of the Mann-Whitney U test, please refer to the + a(href='https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mannwhitneyu.html') official documentation. + | The test was configured to use continuity (set to #{compare.meta.testOptions.mannwhitneyu.useContinuity}) and the method chosen was "#{compare.meta.testOptions.mannwhitneyu.method}". + else if compare.meta.testOptions.testType === 'wilcoxon' + | For more information on the settings of the Wilcoxon test, please refer to the + a(href='https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.wilcoxon.html') official documentation. + | For this test, a correction parameter (#{compare.meta.testOptions.wilcoxon.correction ? 'enabled' : 'disabled'}) was applied to adjust for small sample sizes. + + +p + | The baseline test + if compare.meta.baseline.alias + a(href=compare.meta.baseline.url) #{compare.meta.baseline.alias} + else + a(href=compare.meta.baseline.url) #{compare.meta.baseline.url} + | was conducted at #{compare.meta.baseline.timestamp} and the current test + if compare.meta.current.alias + a(href=compare.meta.current.url) #{compare.meta.current.alias} + else + a(href=compare.meta.current.url) #{compare.meta.current.url} + | was conducted at #{compare.meta.current.timestamp}. + + +h2 Comparison Data + +table + thead + tr + th Metric Name + th Score #{compare.meta.testOptions.testType} + th Baseline mean + th Current mean + th Baseline median + th Current median + th Baseline Std Dev + th Current Std Dev + th Significant Change? + + tbody + each metricGroup, groupName in compare.metrics + each values, metricName in metricGroup + tr + td + b #{groupName + '.' + metricName} + if values.statisticalTestU === "N/A" + td N/A + else + td #{h.decimals(values.statisticalTestU)} + td #{h.decimals(values.baseline.mean)} + td #{h.decimals(values.current.mean)} + td #{h.decimals(values.baseline.median)} + td #{h.decimals(values.current.median)} + td #{h.decimals(values.baseline.stdev)} + td #{h.decimals(values.current.stdev)} + if values.statisticalTestU === "N/A" + td No Test Conducted + else + td #{values.statisticalTestU < 0.05 ? 'Yes' : 'No'} + +h2 Graphs + +each metricGroup, groupName in compare.metrics + each values, metricName in metricGroup + - var fullMetricName = groupName + '.' + metricName + - var metricId = fullMetricName.replace(/\./g, '_') + h3 #{fullMetricName} + .ct-chart(id=`chart-${metricId}`) + .ct-legend + span.ct-legend-item + i(style='background-color: #468847') + | Baseline: [#{values.baseline.values.join(', ')}] + .ct-legend + span.ct-legend-item + i(style='background-color: #c09853;') + | Current: [#{values.current.values.join(', ')}] + script(type='text/javascript'). + document.addEventListener("DOMContentLoaded", function() { + var baselineData = !{JSON.stringify(values.baseline.values)}; + var currentData = !{JSON.stringify(values.current.values)}; + var metricId = '#{metricId}'; + var overlapSeriesName = 'Overlap'; + + var chartData = { + series: [ + { + name: 'Baseline', + data: baselineData.map((value, index) => ({ x: index + 1, y: value })), + className: 'baseline-series' + }, + { + name: 'Current', + data: currentData.map((value, index) => ({ x: index + 1, y: value })), + className: 'current-series' + }, + { + name: overlapSeriesName, + data: baselineData.map((value, index) => ({ x: index + 1, y: currentData[index] === value ? value : null })) + } + ] + }; + var optionsApa = { + showLine: false, + axisX: { + + }, + axisY: { + + } + }; + var chart = new Chartist.Line(`#chart-${metricId}`, chartData, optionsApa); + + // Take care of series that has the same value + chart.on('draw', function(data) { + if(data.type === 'point' && data.series.name === overlapSeriesName && data.value.y !== null) { + data.element._node.setAttribute('style', 'stroke: #59922b; stroke-width: 20px;'); + } + }); + }); diff --git a/lib/plugins/compare/statistical.py b/lib/plugins/compare/statistical.py new file mode 100644 index 0000000000..97a53fd4fd --- /dev/null +++ b/lib/plugins/compare/statistical.py @@ -0,0 +1,37 @@ +import sys +import json +from scipy.stats import wilcoxon, mannwhitneyu + +def has_variability(sample): + """Check if the sample has more than one unique value.""" + return len(set(sample)) > 1 + +def perform_test(test_type, sample1, sample2, **kwargs): + """Perform the statistical test based on the test type.""" + if not has_variability(sample1) or not has_variability(sample2): + return None, "No variability" + + if test_type == 'wilcoxon': + return wilcoxon(sample1, sample2, **kwargs) + elif test_type == 'mannwhitneyu': + return mannwhitneyu(sample1, sample2, **kwargs) + else: + raise ValueError("Invalid test type. Choose 'wilcoxon' or 'mannwhitneyu'.") + +input_data = json.loads(sys.stdin.read()) +options = input_data['options'] +test_type = options.pop('test_type') +final_results = {} + +# Iterate over each metric group in the metrics dictionary +for group_name, metrics in input_data['metrics'].items(): + group_results = {} + for metric_name, metric_data in metrics.items(): + stat, p = perform_test(test_type, metric_data['sample1'], metric_data['sample2'], **options) + if p == "No variability": + group_results[metric_name] = {'statistic': "N/A", 'p-value': "N/A"} + else: + group_results[metric_name] = {'statistic': stat, 'p-value': p} + final_results[group_name] = group_results + +print(json.dumps(final_results)) diff --git a/lib/plugins/graphite/data-generator.js b/lib/plugins/graphite/data-generator.js index 08007a82b2..a5dd7436fd 100644 --- a/lib/plugins/graphite/data-generator.js +++ b/lib/plugins/graphite/data-generator.js @@ -18,7 +18,7 @@ function keyPathFromMessage(message, options, includeQueryParameters, alias) { // always have browser and connectivity in Browsertime and related tools if ( - /(^pagexray|^coach|^browsertime|^largestassets|^slowestassets|^aggregateassets|^domains|^thirdparty|^axe|^sustainable)/.test( + /(^pagexray|^coach|^browsertime|^largestassets|^slowestassets|^aggregateassets|^domains|^thirdparty|^axe|^compare|^sustainable)/.test( message.type ) ) { diff --git a/lib/plugins/html/assets/css/index.min.css b/lib/plugins/html/assets/css/index.min.css index 38d77fa459..aa9bf14c3d 100644 --- a/lib/plugins/html/assets/css/index.min.css +++ b/lib/plugins/html/assets/css/index.min.css @@ -1 +1 @@ -/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}*,:after,:before{box-sizing:border-box}ol,ul{margin-bottom:2.5rem}ul{margin-top:0;padding-left:0;list-style:circle inside}ul ol,ul ul{margin:1.5rem 0 1.5rem 3rem;font-size:.9rem}ol{margin-top:0;padding-left:0;list-style:decimal inside}ol ol,ol ul{margin:1.5rem 0 1.5rem 3rem;font-size:.9rem}li{margin-bottom:0}figure{margin-bottom:2.5rem}footer{text-align:center}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after{content:"";display:table;clear:both}.container{position:relative;width:100%;max-width:1140px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column,.columns{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column,.columns{margin-left:4%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.6666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.6666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}@media (min-width:1550px){.container{max-width:1400px;font-size:1.2em}}@media (min-width:1900px){.container{max-width:1800px;font-size:1.4em}}table{width:100%;margin-bottom:2.5rem;border-collapse:separate;border-spacing:1px;background-color:#e1e1e1}td,th{padding:4px 1rem;vertical-align:top;text-align:left}td:first-child,th:first-child{padding-left:1rem}td:last-child,th:last-child{padding-right:1rem}tr:nth-child(odd){background:#fafafa}tr:nth-child(2n){background:#fff}tr.odd{background:#fafafa}tr.even{background:#fff}th{background:#f1fbff}td.number,th.number{text-align:right}td.right{text-align:right}td.url{overflow-wrap:break-word;word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}td.assetsurl{max-width:400px}td.pagesurl{max-width:350px}td.offendingurl{max-width:900px}td.break{word-break:break-all}td.extraheader{font-weight:700;background:#f5f5f5}@media only screen and (max-width:800px){.responsive table,.responsive tbody,.responsive td,.responsive th,.responsive thead,.responsive tr{display:block}.responsive tr.u-hideable{display:none}.responsive thead tr{position:absolute;top:-9999px;left:-9999px}.responsive tr{border:2px solid #e1e1e1}.responsive td{border:none;border-bottom:1px solid #e1e1e1;position:relative;padding-left:50%;white-space:normal;text-align:left;max-width:none}.responsive td:before{position:absolute;top:6px;left:6px;width:30%;padding-right:10px;white-space:nowrap;text-align:left;font-weight:700}.responsive td:before{content:attr(data-title)}.responsive td.url.offendingurl{word-break:break-all;padding-left:2px}.hidden-small{display:none}}html{font-size:100%}body{font-size:1em;line-height:1.6;font-weight:400;font-family:-apple-system,BlinkMacSystemFont,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h1,h2,h3,h4,h5,h6{margin-top:1rem;margin-bottom:2rem;font-weight:300}h1{font-size:3rem;line-height:1.2}h2{font-size:2.8rem;line-height:1.25}h3{font-size:2.6rem;line-height:1.3}h4{font-size:2.4rem;line-height:1.35}h5{font-size:1.8rem;line-height:1.5}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}p{margin-top:0}a{color:#0095d2}a:hover{color:#00719f}blockquote,dl,p,pre{margin-bottom:2.5rem}code{padding:.2rem .5rem;margin:0 .2rem;font-size:.9rem;white-space:nowrap;background:#fafafa;border:1px solid #e1e1e1;border-radius:4px}pre>code{display:block;padding:1rem 1.5rem;white-space:pre;overflow:auto}.u-full-width{width:100%}.u-max-full-width{max-width:100%}.u-pull-right{float:right}.u-pull-left{float:left}.u-cf{content:"";display:table;clear:both}.u-hideable{display:none}.button{display:inline-block;height:38px;padding:0 30px;margin-bottom:1rem;color:#222;text-align:center;font-size:80%;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #e1e1e1;cursor:pointer}.button:active,.button:focus,.button:hover{color:#222;border-color:#aeaeae;outline:0}.button--primary{color:#fff;background-color:#0095d2;border-color:#0095d2}.button--primary:active,.button--primary:focus,.button--primary:hover{color:#fff;background-color:#0087be;border-color:#0087be}.button-download{color:#fff;background-color:#ec971f;border-color:#eb9316;padding:0 10px;margin-right:1rem}.button-download:active,.button-download:focus,.button-download:hover{color:#fff;background-color:#e38d13;border-color:#da8813}.navgrid{width:100%;min-width:0;margin-left:0;margin-right:0;padding-left:0;padding-right:10px}.nav{background:#0095d2}.nav ul{list-style:none;text-align:center;padding:0;margin:0;background-color:#0095d2}.nav li{line-height:40px;height:40px;border-bottom:none;margin-bottom:0}.nav a{text-decoration:none;color:#fff;display:block}.nav a:hover{background-color:#0073b0}.nav a.active{background-color:#0073b0;color:#fff;cursor:default}.logo{text-align:center;background-color:#0095d2}.navbar-brand{padding:0;font-size:18px;max-width:250px}@media screen and (min-width:820px){body{padding-top:50px}.navgrid{width:100%;max-width:1140px;min-width:755px;margin:0 auto;overflow:hidden}.nav{height:50px;width:100%;z-index:1000;position:fixed;top:0}.navbar-brand{padding:0;font-size:18px;float:left;max-width:250px}.nav{z-index:10;top:0;background-color:#0095d2}.nav a{padding-left:20px;padding-right:20px}.nav li{border-bottom:none;height:50px;line-height:50px;float:left;display:inline-block;margin-right:0}.nav a{text-decoration:none;color:#fff;display:block}.nav ul{list-style:none;text-align:center;padding:0;margin:0;background-color:#0095d2}:target:before{content:"";display:block;height:50px;margin:-50px 0 0}}@media (min-width:1550px){.nav li{font-size:1.2em}}@media (min-width:1900px){.nav li{font-size:1.4em}}table[data-sortable] th[data-sorted=true]{color:#3a87ad;background:#d9edf7;border-bottom-color:#bce8f1}table[data-sortable] th:not([data-sortable=false]){cursor:pointer;color:#222;text-decoration:underline}.summarybox{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.summarybox.ok{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.summarybox.warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.summarybox.error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.summarybox.info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}a.summaries{text-decoration:none}.summarynumber{font-size:2rem;line-height:1;font-weight:700}.summarysmall{font-size:1rem;line-height:1}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.normal{font-size:100%}.ok{background-color:#468847}.warning{background-color:#f0ad4e}.error{background-color:#d9534f}.info{background-color:#0095d2}ul.menu{list-style:none;font-size:125%;text-transform:uppercase}.errors{margin-bottom:1.333em;background:#ffb6c1;padding-left:1em}.subtableheader{background:#ffe3eb}.large{font-size:1.333rem;line-height:1.8rem}.hidden-small{display:inline}.url{overflow-wrap:break-word;word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}/*! github.com/micmro/PerfCascade Version:2.11.0 (24/11/2021) */.water-fall-chart{width:100%;overflow:visible;font-size:12px;line-height:1em}.water-fall-chart *{box-sizing:border-box}.water-fall-chart button{cursor:pointer}.water-fall-holder{fill:#ccc}.water-fall-chart .left-fixed-holder{overflow:visible}.water-fall-chart .marker-holder{width:100%}.water-fall-chart .line-label-holder{cursor:pointer}.water-fall-chart .line-holder{stroke-width:1;stroke:#ccc;stroke-opacity:0.5;transition:all 60ms}.water-fall-chart .line-holder .line-mark{fill:#69009e;opacity:.01;stroke-width:0;transition:all 60ms}.water-fall-chart .line-holder.active{stroke:#69009e;stroke-width:2;stroke-opacity:1}.water-fall-chart .line-holder.active .line-mark{opacity:.4}.water-fall-chart .type-onload .line-holder{stroke:#c0c0ff}.water-fall-chart .type-oncontentload .line-holder{stroke:#d888df}.water-fall-chart .labels{width:100%}.water-fall-chart .labels .inner-label{pointer-events:none}.water-fall-chart .time-block.active{opacity:.8}.water-fall-chart .line-end,.water-fall-chart .line-start{display:none;stroke-width:1;stroke-opacity:0.5;stroke:#000}.water-fall-chart .line-end.active,.water-fall-chart .line-start.active{display:block}.left-fixed-holder .label-full-bg{fill:#fff;opacity:.9}.time-scale line{stroke:#0cc;stroke-width:1}.time-scale line.sub-second-line{stroke:#ccc;opacity:.75;stroke-width:.5}.time-scale text{font-weight:700}.row-item{cursor:pointer}.row-item .even{fill:#ccc;opacity:.05}.row-item .odd{fill:#000;opacity:.05}.row-item:hover .even,.row-item:hover .odd{fill:#000;opacity:.1}.row-item:focus{outline:solid 1.5px #aaa;outline-offset:-1.5px}.row-item:focus .even,.row-item:focus .odd{fill:#000;opacity:.2}.row-item .rect-holder text{fill:#aaa}.row-item.status0 .even,.row-item.status5xx .even{fill:#f66}.row-item.status0 .odd,.row-item.status5xx .odd{fill:#f00}.row-item.status4xx .even{fill:#c33}.row-item.status4xx .odd{fill:#c00}.row-item.status3xx .even{fill:#ff6}.row-item.status3xx .odd{fill:#ff0}.row-item.potentiallyRenderBlocking .even,.row-item.potentiallyRenderBlocking .odd{fill:#e5a331}.row-item.renderBlocking .even,.row-item.renderBlocking .odd{fill:#e57231;stroke-dasharray:5;stroke:black;stroke-width:2px}.row-item.largestContentfulPaint .even,.row-item.largestContentfulPaint .odd{fill:#c9e531;stroke-dasharray:8;stroke-width:2px;stroke:black}.row-item.largestContentfulPaint .even,.row-item.largestContentfulPaint .odd,.row-item.potentiallyRenderBlocking .even,.row-item.potentiallyRenderBlocking .odd,.row-item.renderBlocking .even,.row-item.renderBlocking .odd,.row-item.status0 .even,.row-item.status0 .odd,.row-item.status3xx .even,.row-item.status3xx .odd,.row-item.status4xx .even,.row-item.status4xx .odd,.row-item.status5xx .even,.row-item.status5xx .odd{opacity:.3}.row-item.largestContentfulPaint:hover .even,.row-item.largestContentfulPaint:hover .odd,.row-item.potentiallyRenderBlocking:hover .even,.row-item.potentiallyRenderBlocking:hover .odd,.row-item.renderBlocking:hover .even,.row-item.renderBlocking:hover .odd,.row-item.status0:hover .even,.row-item.status0:hover .odd,.row-item.status3xx:hover .even,.row-item.status3xx:hover .odd,.row-item.status4xx:hover .even,.row-item.status4xx:hover .odd,.row-item.status5xx:hover .even,.row-item.status5xx:hover .odd{opacity:.5}.tooltip-holder{overflow:visible}.tooltip *{padding:0;margin:0}.tooltip html{font-size:10px;line-height:1.2em}.tooltip body{position:relative}.tooltip-payload{position:absolute;top:0;left:0;padding:.25em;font-size:10px;display:inline-block;background:rgba(255,255,255,.9);border:solid 1px #f0f0f0;word-break:break-all;overflow-wrap:break-word;transition:opacity .3s}.tooltip-payload.no-anim{transition:none}.row-item,.time-scale line,.time-scale text,.water-fall-chart .line-holder line,.water-fall-chart .line-label-holder{transition:transform 60ms}.water-fall-chart.closing{transition-delay:60ms}.labels{overflow:hidden}.block-css{fill:#a6d18f}.block-html,.block-iframe,.block-internal,.block-svg{fill:#82a8de}.block-image,.block-img{fill:#b394cf}.block-javascript,.block-js,.block-script{fill:#e0b483}.block-link{fill:#89afe6}.block-flash,.block-swf{fill:#42aab1}.block-font{fill:#e15d4e}.block-ajax,.block-xmlhttprequest{fill:#f00}.block-other,.block-plain{fill:#b3b3b3}.block-blocked{fill:#aaa}.block-dns{fill:#159588}.block-connect{fill:#fd9727}.block-ssl{fill:#c141cd}.block-send{fill:#b0bec5}.block-wait{fill:#1ec659}.block-receive{fill:#1eaaf1}.block-receive-chunk{fill:#a1c3fa}.block-undefined{fill:#0f0}.info-overlay-bg{fill:#fff;stroke:#cdcdcd}.info-overlay-close-btn{fill:rgba(205,205,205,0.8);transform:translate(-23px,-23px);cursor:pointer}.info-overlay-close-btn text{fill:#111;pointer-events:none}.info-overlay-close-btn:focus{border:solid 1px #36c}.info-overlay-holder .connect{border-right:solid 5px #fd9727;padding-right:5px}.info-overlay-holder .blocked{border-right:solid 5px #aaa;padding-right:5px}.info-overlay-holder .ssltls{border-right:solid 5px #c141cd;padding-right:5px}.info-overlay-holder .send{border-right:solid 5px #b0bec5;padding-right:5px}.info-overlay-holder .wait{border-right:solid 5px #1ec659;padding-right:5px}.info-overlay-holder .receive{border-right:solid 5px #1eaaf1;padding-right:5px}.info-overlay-holder .dns{border-right:solid 5px #159588;padding-right:5px}.type-css{background:#406b29}.type-html,.type-iframe,.type-internal,.type-svg{background:#1c4278}.type-image,.type-img{background:#4d2e69}.type-javascript,.type-js,.type-script{background:#7a4e1d}.type-link{background:#89afe6}.type-flash,.type-swf{background:#234980}.type-font{background:#ae2a1b}.type-ajax,.type-xmlhttprequest{background:#c00}.type-other,.type-plain{background:grey}.info-overlay-holder *{padding:0;margin:0;font-size:12px}.info-overlay-holder body{position:relative;height:450px;clear:both;padding:0;margin:0;width:100%;background:#fff;color:#666}.info-overlay-holder body .wrapper{height:450px;width:100%;overflow:scroll}.info-overlay-holder header{position:relative;box-shadow:0 0 2px 2px rgba(0,0,0,.25)}.info-overlay-holder header,.info-overlay-holder header a,.info-overlay-holder header button{color:#fff;text-decoration:none}.info-overlay-holder header a:focus,.info-overlay-holder header a:hover{text-decoration:underline}.info-overlay-holder .requestID{font-weight:700}.info-overlay-holder h3,.info-overlay-holder h3 a{font-size:1.1em;padding:1em;margin:0;font-weight:400;overflow-wrap:break-word}.info-overlay-holder h3 strong{font-size:1.1em}.info-overlay-holder .tab-nav ul{margin:0;padding:0}.info-overlay-holder .tab-nav li{margin:0;padding:0;display:inline-block}.info-overlay-holder button{background:0 0;outline:0;border:0;border-bottom:solid 2px transparent;padding:.5em 1em;margin:0 .25em}.info-overlay-holder li:first-child button{margin-left:1em}.info-overlay-holder button.active:focus,.info-overlay-holder button:focus,.info-overlay-holder button:hover{border-color:rgba(255,255,255,.6)}.info-overlay-holder button.active{border-color:#fff;cursor:default}.info-overlay-holder button.active:focus{border-color:rgba(255,255,255,.8)}.info-overlay-holder button.copy-tab-data{position:absolute;top:.5em;right:.5em;border:0;margin:0;border-radius:1em;background:#e0e0e0}.info-overlay-holder button.copy-tab-data:focus,.info-overlay-holder button.copy-tab-data:hover{background:#ccc}.info-overlay-holder dt{float:left;clear:both;margin-top:.5em;width:25%;text-align:right;font-weight:700}.info-overlay-holder dd{float:left;width:73%;margin:.5em 0 0 2%;padding:0 0 .5em 0}.info-overlay-holder dt:after{content:":"}.info-overlay-holder pre{font-size:11px;line-height:23px;border-radius:0;background:#f6f3f3}.info-overlay-holder .tab{float:left;position:relative;width:100%;height:350px;padding:12px 12px 24px}.info-overlay-holder .tab h2{font-size:1.2em;margin:.5em 0 0;padding:.5em 0 .5em 1em;clear:both;border-top:solid 1px #efefef}.info-overlay-holder .tab h2:first-child{border-top:0;padding-top:0}.info-overlay-holder .tab pre{overflow-y:hidden;width:100%;min-height:100%}.info-overlay-holder .tab .preview{width:auto;max-width:100%;max-height:500px;border:solid 1px #666}.info-overlay-holder .tab dl:after{content:"";display:table;clear:both}.info-overlay-holder .tab.rendered-data{padding:0}.info-overlay-holder .tab.rendered-data pre{padding:12px 12px 24px}.info-overlay-holder .tab.rendered-data pre>code{white-space:pre-wrap}.resource-legend{margin:0;padding:0;font-size:.75em;line-height:1.5em;display:inline-block}.resource-legend li{margin:0 1em 0 0;padding:0;white-space:nowrap;display:inline-block}.resource-legend li:before{content:"";width:1em;height:1em;margin:0 .5em 0 0;vertical-align:text-top;display:inline-block}.resource-legend .legend-blocked:before{background:#aaa}.resource-legend .legend-dns:before{background:#159588}.resource-legend .legend-connect:before{background:#fd9727}.resource-legend .legend-ssl:before{background:#c141cd}.resource-legend .legend-send:before{background:#b0bec5}.resource-legend .legend-wait:before{background:#1ec659}.resource-legend .legend-receive:before{background:#1eaaf1}.icon{fill:#666}.icon-4xx,.icon-5xx,.icon-no-cache,.icon-no-gzip,.icon-warning{fill:#b55}.water-fall-chart .type-firstpaint .line-holder{stroke:#42f46e;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-firstvisualchange .line-holder{stroke:#42f46e;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-visualcomplete85 .line-holder{stroke:#ee7777;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-lastvisualchange .line-holder{stroke:#ee42f4;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-onload .line-holder{stroke:#9c99e5;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-oncontentload .line-holder{stroke:#9842f4;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-dominteractivetime .line-holder{stroke:#1842f4;stroke-opacity:1;stroke-width:2}.water-fall-chart .type-domcontentloadedtime .line-holder{stroke:#1212f4;stroke-opacity:1;stroke-width:2}.water-fall-chart .type-lastcpulongtask .line-holder{stroke:#f41229;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-largestcontentfulpaint .line-holder{stroke:#c7f412;stroke-opacity:1;stroke-width:3}.water-fall-chart{font-size:14px}#page-selector{display:none;clear:both;margin:1em 0}*{box-sizing:border-box}@media screen and (max-width:400px){select{max-width:100%}}.screenshot{padding:4px;background-color:#fff;border:1px solid #ddd;border-radius:4px}*,:after,:before{margin:0;padding:0;box-sizing:border-box}section{display:block;padding:20px 0 0;border-top:1px solid #ddd}#tabs a{display:inline-block;margin:0 0 -1px;padding:15px 22px;font-weight:600;text-align:center;color:#bbb;border:1px solid transparent;text-decoration:none;text-transform:uppercase}#tabs a:before{font-weight:400}#tabs a:hover{color:#888;cursor:pointer}#tabs a:target:focus{outline:0}#tabs a[selected]{color:#555;border:1px solid #ddd;border-top:2px solid #0095d2;border-bottom:1px solid #fff}@media screen and (max-width:650px){#tabs a:before{margin:0;font-size:18px}}@media screen and (max-width:400px){#tabs a{padding:13px}}.group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd;font-weight:700}.group-item.active{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.loader,.loader:after,.loader:before{background:#000;-webkit-animation:load1 1s infinite ease-in-out;animation:load1 1s infinite ease-in-out;width:1em;height:4em}.loader{color:#000;text-indent:-9999em;margin:88px auto;position:relative;font-size:11px;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation-delay:-.16s;animation-delay:-.16s}.loader:after,.loader:before{position:absolute;top:0;content:""}.loader:before{left:-1.5em;-webkit-animation-delay:-.32s;animation-delay:-.32s}.loader:after{left:1.5em}@-webkit-keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}@keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}.ct-label{fill:rgba(0,0,0,0.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,0.2);stroke-width:1px;stroke-dasharray:2px}.ct-grid-background{fill:none}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{fill:none;stroke-width:4px}.ct-area{stroke:none;fill-opacity:0.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{content:"";display:table;clear:both}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{content:"";display:table;clear:both}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{content:"";display:table;clear:both}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{content:"";display:table;clear:both}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{content:"";display:table;clear:both}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{content:"";display:table;clear:both}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{content:"";display:table;clear:both}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{content:"";display:table;clear:both}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{content:"";display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{content:"";display:table;clear:both}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{content:"";display:table;clear:both}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{content:"";display:table;clear:both}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{content:"";display:table;clear:both}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{content:"";display:table;clear:both}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{content:"";display:table;clear:both}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{content:"";display:table;clear:both}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{content:"";display:table;clear:both}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0}.chartist-tooltip{position:absolute;display:inline-block;opacity:0;min-width:5em;padding:.5em;background:#f4c63d;color:#453d3f;font-family:Oxygen,Helvetica,Arial,sans-serif;font-weight:700;text-align:center;pointer-events:none;z-index:1;-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;-o-transition:opacity .2s linear;transition:opacity .2s linear}.chartist-tooltip:before{content:"";position:absolute;top:100%;left:50%;width:0;height:0;margin-left:-15px;border:15px solid transparent;border-top-color:#f4c63d}.chartist-tooltip.tooltip-show{opacity:1}.ct-area,.ct-line{pointer-events:none}.chartist-tooltip{background:#0095d2;color:#fff}.chartist-tooltip:before{border-top-color:#0095d2}.ct-bar{stroke-width:16px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#82b5fc}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#468847}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#b2ea94}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#c09853}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#fec584}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#b94a48}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#c49ae8}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#c49ae8}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#ff523e}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#ff523e}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#c4c4c4}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#c4c4c4}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#c4c4c4}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#c4c4c4}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#c4c4c4}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#c4c4c4}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#EAB839}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#EAB839}.ct-chart .ct-legend{position:relative;z-index:10;list-style:none;text-align:left;line-height:.8;font-size:.8em}.ct-chart .ct-legend li{padding-left:23px;margin-right:10px;margin-bottom:3px;cursor:pointer}.ct-chart .ct-legend li:before{width:12px;height:12px;position:absolute;left:0;content:"";border:3px solid transparent;border-radius:2px}.ct-chart .ct-legend li .inactive:before{background:0 0}.ct-chart .ct-legend li:first-child::before{background-color:#468847}.ct-chart .ct-legend li:nth-child(2)::before{background-color:#c09853}.ct-chart .ct-legend li:nth-child(3)::before{background-color:#b94a48}.ct-chart .ct-legend .ct-legend-inside{position:absolute;top:0;right:0}.ct-chart g:not(.ct-grids):not(.ct-labels) g:first-child .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:first-child .ct-point{stroke:#468847}.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(2) .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(2) .ct-point{stroke:#c09853}.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(3) .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(3) .ct-point{stroke:#b94a48}.filmstrip{padding-bottom:20px}.videoframe{vertical-align:top;display:inline-block;padding:4px;background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%}.videoframe.blue{border:2px solid #0095d2}.videoframetime{text-align:center;display:block}.videoframetext{text-align:left;display:block;line-height:1.2em;font-size:.8em;white-space:nowrap;margin-bottom:.2em} \ No newline at end of file +/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}*,:after,:before{box-sizing:border-box}ol,ul{margin-bottom:2.5rem}ul{margin-top:0;padding-left:0;list-style:circle inside}ul ol,ul ul{margin:1.5rem 0 1.5rem 3rem;font-size:.9rem}ol{margin-top:0;padding-left:0;list-style:decimal inside}ol ol,ol ul{margin:1.5rem 0 1.5rem 3rem;font-size:.9rem}li{margin-bottom:0}figure{margin-bottom:2.5rem}footer{text-align:center}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #e1e1e1}.container:after,.row:after{content:"";display:table;clear:both}.container{position:relative;width:100%;max-width:1140px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column,.columns{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column,.columns{margin-left:4%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.6666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.6666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}@media (min-width:1550px){.container{max-width:1400px;font-size:1.2em}}@media (min-width:1900px){.container{max-width:1800px;font-size:1.4em}}table{width:100%;margin-bottom:2.5rem;border-collapse:separate;border-spacing:1px;background-color:#e1e1e1}td,th{padding:4px 1rem;vertical-align:top;text-align:left}td:first-child,th:first-child{padding-left:1rem}td:last-child,th:last-child{padding-right:1rem}tr:nth-child(odd){background:#fafafa}tr:nth-child(2n){background:#fff}tr.odd{background:#fafafa}tr.even{background:#fff}th{background:#f1fbff}td.number,th.number{text-align:right}td.right{text-align:right}td.url{overflow-wrap:break-word;word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}td.assetsurl{max-width:400px}td.pagesurl{max-width:350px}td.offendingurl{max-width:900px}td.break{word-break:break-all}td.extraheader{font-weight:700;background:#f5f5f5}@media only screen and (max-width:800px){.responsive table,.responsive tbody,.responsive td,.responsive th,.responsive thead,.responsive tr{display:block}.responsive tr.u-hideable{display:none}.responsive thead tr{position:absolute;top:-9999px;left:-9999px}.responsive tr{border:2px solid #e1e1e1}.responsive td{border:none;border-bottom:1px solid #e1e1e1;position:relative;padding-left:50%;white-space:normal;text-align:left;max-width:none}.responsive td:before{position:absolute;top:6px;left:6px;width:30%;padding-right:10px;white-space:nowrap;text-align:left;font-weight:700}.responsive td:before{content:attr(data-title)}.responsive td.url.offendingurl{word-break:break-all;padding-left:2px}.hidden-small{display:none}}html{font-size:100%}body{font-size:1em;line-height:1.6;font-weight:400;font-family:-apple-system,BlinkMacSystemFont,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h1,h2,h3,h4,h5,h6{margin-top:1rem;margin-bottom:2rem;font-weight:300}h1{font-size:3rem;line-height:1.2}h2{font-size:2.8rem;line-height:1.25}h3{font-size:2.6rem;line-height:1.3}h4{font-size:2.4rem;line-height:1.35}h5{font-size:1.8rem;line-height:1.5}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}p{margin-top:0}a{color:#0095d2}a:hover{color:#00719f}blockquote,dl,p,pre{margin-bottom:2.5rem}code{padding:.2rem .5rem;margin:0 .2rem;font-size:.9rem;white-space:nowrap;background:#fafafa;border:1px solid #e1e1e1;border-radius:4px}pre>code{display:block;padding:1rem 1.5rem;white-space:pre;overflow:auto}.u-full-width{width:100%}.u-max-full-width{max-width:100%}.u-pull-right{float:right}.u-pull-left{float:left}.u-cf{content:"";display:table;clear:both}.u-hideable{display:none}.button{display:inline-block;height:38px;padding:0 30px;margin-bottom:1rem;color:#222;text-align:center;font-size:80%;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #e1e1e1;cursor:pointer}.button:active,.button:focus,.button:hover{color:#222;border-color:#aeaeae;outline:0}.button--primary{color:#fff;background-color:#0095d2;border-color:#0095d2}.button--primary:active,.button--primary:focus,.button--primary:hover{color:#fff;background-color:#0087be;border-color:#0087be}.button-download{color:#fff;background-color:#ec971f;border-color:#eb9316;padding:0 10px;margin-right:1rem}.button-download:active,.button-download:focus,.button-download:hover{color:#fff;background-color:#e38d13;border-color:#da8813}.navgrid{width:100%;min-width:0;margin-left:0;margin-right:0;padding-left:0;padding-right:10px}.nav{background:#0095d2}.nav ul{list-style:none;text-align:center;padding:0;margin:0;background-color:#0095d2}.nav li{line-height:40px;height:40px;border-bottom:none;margin-bottom:0}.nav a{text-decoration:none;color:#fff;display:block}.nav a:hover{background-color:#0073b0}.nav a.active{background-color:#0073b0;color:#fff;cursor:default}.logo{text-align:center;background-color:#0095d2}.navbar-brand{padding:0;font-size:18px;max-width:250px}@media screen and (min-width:820px){body{padding-top:50px}.navgrid{width:100%;max-width:1140px;min-width:755px;margin:0 auto;overflow:hidden}.nav{height:50px;width:100%;z-index:1000;position:fixed;top:0}.navbar-brand{padding:0;font-size:18px;float:left;max-width:250px}.nav{z-index:10;top:0;background-color:#0095d2}.nav a{padding-left:20px;padding-right:20px}.nav li{border-bottom:none;height:50px;line-height:50px;float:left;display:inline-block;margin-right:0}.nav a{text-decoration:none;color:#fff;display:block}.nav ul{list-style:none;text-align:center;padding:0;margin:0;background-color:#0095d2}:target:before{content:"";display:block;height:50px;margin:-50px 0 0}}@media (min-width:1550px){.nav li{font-size:1.2em}}@media (min-width:1900px){.nav li{font-size:1.4em}}table[data-sortable] th[data-sorted=true]{color:#3a87ad;background:#d9edf7;border-bottom-color:#bce8f1}table[data-sortable] th:not([data-sortable=false]){cursor:pointer;color:#222;text-decoration:underline}.summarybox{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.summarybox.ok{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.summarybox.warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.summarybox.error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.summarybox.info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}a.summaries{text-decoration:none}.summarynumber{font-size:2rem;line-height:1;font-weight:700}.summarysmall{font-size:1rem;line-height:1}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.normal{font-size:100%}.ok{background-color:#468847}.warning{background-color:#f0ad4e}.error{background-color:#d9534f}.info{background-color:#0095d2}ul.menu{list-style:none;font-size:125%;text-transform:uppercase}.errors{margin-bottom:1.333em;background:#ffb6c1;padding-left:1em}.subtableheader{background:#ffe3eb}.large{font-size:1.333rem;line-height:1.8rem}.hidden-small{display:inline}.url{overflow-wrap:break-word;word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;-moz-hyphens:auto;hyphens:auto}/*! github.com/micmro/PerfCascade Version:2.11.0 (24/11/2021) */.water-fall-chart{width:100%;overflow:visible;font-size:12px;line-height:1em}.water-fall-chart *{box-sizing:border-box}.water-fall-chart button{cursor:pointer}.water-fall-holder{fill:#ccc}.water-fall-chart .left-fixed-holder{overflow:visible}.water-fall-chart .marker-holder{width:100%}.water-fall-chart .line-label-holder{cursor:pointer}.water-fall-chart .line-holder{stroke-width:1;stroke:#ccc;stroke-opacity:0.5;transition:all 60ms}.water-fall-chart .line-holder .line-mark{fill:#69009e;opacity:.01;stroke-width:0;transition:all 60ms}.water-fall-chart .line-holder.active{stroke:#69009e;stroke-width:2;stroke-opacity:1}.water-fall-chart .line-holder.active .line-mark{opacity:.4}.water-fall-chart .type-onload .line-holder{stroke:#c0c0ff}.water-fall-chart .type-oncontentload .line-holder{stroke:#d888df}.water-fall-chart .labels{width:100%}.water-fall-chart .labels .inner-label{pointer-events:none}.water-fall-chart .time-block.active{opacity:.8}.water-fall-chart .line-end,.water-fall-chart .line-start{display:none;stroke-width:1;stroke-opacity:0.5;stroke:#000}.water-fall-chart .line-end.active,.water-fall-chart .line-start.active{display:block}.left-fixed-holder .label-full-bg{fill:#fff;opacity:.9}.time-scale line{stroke:#0cc;stroke-width:1}.time-scale line.sub-second-line{stroke:#ccc;opacity:.75;stroke-width:.5}.time-scale text{font-weight:700}.row-item{cursor:pointer}.row-item .even{fill:#ccc;opacity:.05}.row-item .odd{fill:#000;opacity:.05}.row-item:hover .even,.row-item:hover .odd{fill:#000;opacity:.1}.row-item:focus{outline:solid 1.5px #aaa;outline-offset:-1.5px}.row-item:focus .even,.row-item:focus .odd{fill:#000;opacity:.2}.row-item .rect-holder text{fill:#aaa}.row-item.status0 .even,.row-item.status5xx .even{fill:#f66}.row-item.status0 .odd,.row-item.status5xx .odd{fill:#f00}.row-item.status4xx .even{fill:#c33}.row-item.status4xx .odd{fill:#c00}.row-item.status3xx .even{fill:#ff6}.row-item.status3xx .odd{fill:#ff0}.row-item.potentiallyRenderBlocking .even,.row-item.potentiallyRenderBlocking .odd{fill:#e5a331}.row-item.renderBlocking .even,.row-item.renderBlocking .odd{fill:#e57231;stroke-dasharray:5;stroke:black;stroke-width:2px}.row-item.largestContentfulPaint .even,.row-item.largestContentfulPaint .odd{fill:#c9e531;stroke-dasharray:8;stroke-width:2px;stroke:black}.row-item.largestContentfulPaint .even,.row-item.largestContentfulPaint .odd,.row-item.potentiallyRenderBlocking .even,.row-item.potentiallyRenderBlocking .odd,.row-item.renderBlocking .even,.row-item.renderBlocking .odd,.row-item.status0 .even,.row-item.status0 .odd,.row-item.status3xx .even,.row-item.status3xx .odd,.row-item.status4xx .even,.row-item.status4xx .odd,.row-item.status5xx .even,.row-item.status5xx .odd{opacity:.3}.row-item.largestContentfulPaint:hover .even,.row-item.largestContentfulPaint:hover .odd,.row-item.potentiallyRenderBlocking:hover .even,.row-item.potentiallyRenderBlocking:hover .odd,.row-item.renderBlocking:hover .even,.row-item.renderBlocking:hover .odd,.row-item.status0:hover .even,.row-item.status0:hover .odd,.row-item.status3xx:hover .even,.row-item.status3xx:hover .odd,.row-item.status4xx:hover .even,.row-item.status4xx:hover .odd,.row-item.status5xx:hover .even,.row-item.status5xx:hover .odd{opacity:.5}.tooltip-holder{overflow:visible}.tooltip *{padding:0;margin:0}.tooltip html{font-size:10px;line-height:1.2em}.tooltip body{position:relative}.tooltip-payload{position:absolute;top:0;left:0;padding:.25em;font-size:10px;display:inline-block;background:rgba(255,255,255,.9);border:solid 1px #f0f0f0;word-break:break-all;overflow-wrap:break-word;transition:opacity .3s}.tooltip-payload.no-anim{transition:none}.row-item,.time-scale line,.time-scale text,.water-fall-chart .line-holder line,.water-fall-chart .line-label-holder{transition:transform 60ms}.water-fall-chart.closing{transition-delay:60ms}.labels{overflow:hidden}.block-css{fill:#a6d18f}.block-html,.block-iframe,.block-internal,.block-svg{fill:#82a8de}.block-image,.block-img{fill:#b394cf}.block-javascript,.block-js,.block-script{fill:#e0b483}.block-link{fill:#89afe6}.block-flash,.block-swf{fill:#42aab1}.block-font{fill:#e15d4e}.block-ajax,.block-xmlhttprequest{fill:#f00}.block-other,.block-plain{fill:#b3b3b3}.block-blocked{fill:#aaa}.block-dns{fill:#159588}.block-connect{fill:#fd9727}.block-ssl{fill:#c141cd}.block-send{fill:#b0bec5}.block-wait{fill:#1ec659}.block-receive{fill:#1eaaf1}.block-receive-chunk{fill:#a1c3fa}.block-undefined{fill:#0f0}.info-overlay-bg{fill:#fff;stroke:#cdcdcd}.info-overlay-close-btn{fill:rgba(205,205,205,0.8);transform:translate(-23px,-23px);cursor:pointer}.info-overlay-close-btn text{fill:#111;pointer-events:none}.info-overlay-close-btn:focus{border:solid 1px #36c}.info-overlay-holder .connect{border-right:solid 5px #fd9727;padding-right:5px}.info-overlay-holder .blocked{border-right:solid 5px #aaa;padding-right:5px}.info-overlay-holder .ssltls{border-right:solid 5px #c141cd;padding-right:5px}.info-overlay-holder .send{border-right:solid 5px #b0bec5;padding-right:5px}.info-overlay-holder .wait{border-right:solid 5px #1ec659;padding-right:5px}.info-overlay-holder .receive{border-right:solid 5px #1eaaf1;padding-right:5px}.info-overlay-holder .dns{border-right:solid 5px #159588;padding-right:5px}.type-css{background:#406b29}.type-html,.type-iframe,.type-internal,.type-svg{background:#1c4278}.type-image,.type-img{background:#4d2e69}.type-javascript,.type-js,.type-script{background:#7a4e1d}.type-link{background:#89afe6}.type-flash,.type-swf{background:#234980}.type-font{background:#ae2a1b}.type-ajax,.type-xmlhttprequest{background:#c00}.type-other,.type-plain{background:grey}.info-overlay-holder *{padding:0;margin:0;font-size:12px}.info-overlay-holder body{position:relative;height:450px;clear:both;padding:0;margin:0;width:100%;background:#fff;color:#666}.info-overlay-holder body .wrapper{height:450px;width:100%;overflow:scroll}.info-overlay-holder header{position:relative;box-shadow:0 0 2px 2px rgba(0,0,0,.25)}.info-overlay-holder header,.info-overlay-holder header a,.info-overlay-holder header button{color:#fff;text-decoration:none}.info-overlay-holder header a:focus,.info-overlay-holder header a:hover{text-decoration:underline}.info-overlay-holder .requestID{font-weight:700}.info-overlay-holder h3,.info-overlay-holder h3 a{font-size:1.1em;padding:1em;margin:0;font-weight:400;overflow-wrap:break-word}.info-overlay-holder h3 strong{font-size:1.1em}.info-overlay-holder .tab-nav ul{margin:0;padding:0}.info-overlay-holder .tab-nav li{margin:0;padding:0;display:inline-block}.info-overlay-holder button{background:0 0;outline:0;border:0;border-bottom:solid 2px transparent;padding:.5em 1em;margin:0 .25em}.info-overlay-holder li:first-child button{margin-left:1em}.info-overlay-holder button.active:focus,.info-overlay-holder button:focus,.info-overlay-holder button:hover{border-color:rgba(255,255,255,.6)}.info-overlay-holder button.active{border-color:#fff;cursor:default}.info-overlay-holder button.active:focus{border-color:rgba(255,255,255,.8)}.info-overlay-holder button.copy-tab-data{position:absolute;top:.5em;right:.5em;border:0;margin:0;border-radius:1em;background:#e0e0e0}.info-overlay-holder button.copy-tab-data:focus,.info-overlay-holder button.copy-tab-data:hover{background:#ccc}.info-overlay-holder dt{float:left;clear:both;margin-top:.5em;width:25%;text-align:right;font-weight:700}.info-overlay-holder dd{float:left;width:73%;margin:.5em 0 0 2%;padding:0 0 .5em 0}.info-overlay-holder dt:after{content:":"}.info-overlay-holder pre{font-size:11px;line-height:23px;border-radius:0;background:#f6f3f3}.info-overlay-holder .tab{float:left;position:relative;width:100%;height:350px;padding:12px 12px 24px}.info-overlay-holder .tab h2{font-size:1.2em;margin:.5em 0 0;padding:.5em 0 .5em 1em;clear:both;border-top:solid 1px #efefef}.info-overlay-holder .tab h2:first-child{border-top:0;padding-top:0}.info-overlay-holder .tab pre{overflow-y:hidden;width:100%;min-height:100%}.info-overlay-holder .tab .preview{width:auto;max-width:100%;max-height:500px;border:solid 1px #666}.info-overlay-holder .tab dl:after{content:"";display:table;clear:both}.info-overlay-holder .tab.rendered-data{padding:0}.info-overlay-holder .tab.rendered-data pre{padding:12px 12px 24px}.info-overlay-holder .tab.rendered-data pre>code{white-space:pre-wrap}.resource-legend{margin:0;padding:0;font-size:.75em;line-height:1.5em;display:inline-block}.resource-legend li{margin:0 1em 0 0;padding:0;white-space:nowrap;display:inline-block}.resource-legend li:before{content:"";width:1em;height:1em;margin:0 .5em 0 0;vertical-align:text-top;display:inline-block}.resource-legend .legend-blocked:before{background:#aaa}.resource-legend .legend-dns:before{background:#159588}.resource-legend .legend-connect:before{background:#fd9727}.resource-legend .legend-ssl:before{background:#c141cd}.resource-legend .legend-send:before{background:#b0bec5}.resource-legend .legend-wait:before{background:#1ec659}.resource-legend .legend-receive:before{background:#1eaaf1}.icon{fill:#666}.icon-4xx,.icon-5xx,.icon-no-cache,.icon-no-gzip,.icon-warning{fill:#b55}.water-fall-chart .type-firstpaint .line-holder{stroke:#42f46e;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-firstvisualchange .line-holder{stroke:#42f46e;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-visualcomplete85 .line-holder{stroke:#ee7777;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-lastvisualchange .line-holder{stroke:#ee42f4;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-onload .line-holder{stroke:#9c99e5;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-oncontentload .line-holder{stroke:#9842f4;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-dominteractivetime .line-holder{stroke:#1842f4;stroke-opacity:1;stroke-width:2}.water-fall-chart .type-domcontentloadedtime .line-holder{stroke:#1212f4;stroke-opacity:1;stroke-width:2}.water-fall-chart .type-lastcpulongtask .line-holder{stroke:#f41229;stroke-opacity:1;stroke-width:3}.water-fall-chart .type-largestcontentfulpaint .line-holder{stroke:#c7f412;stroke-opacity:1;stroke-width:3}.water-fall-chart{font-size:14px}#page-selector{display:none;clear:both;margin:1em 0}*{box-sizing:border-box}@media screen and (max-width:400px){select{max-width:100%}}.screenshot{padding:4px;background-color:#fff;border:1px solid #ddd;border-radius:4px}*,:after,:before{margin:0;padding:0;box-sizing:border-box}section{display:block;padding:20px 0 0;border-top:1px solid #ddd}#tabs a{display:inline-block;margin:0 0 -1px;padding:15px 22px;font-weight:600;text-align:center;color:#bbb;border:1px solid transparent;text-decoration:none;text-transform:uppercase}#tabs a:before{font-weight:400}#tabs a:hover{color:#888;cursor:pointer}#tabs a:target:focus{outline:0}#tabs a[selected]{color:#555;border:1px solid #ddd;border-top:2px solid #0095d2;border-bottom:1px solid #fff}@media screen and (max-width:650px){#tabs a:before{margin:0;font-size:18px}}@media screen and (max-width:400px){#tabs a{padding:13px}}.group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd;font-weight:700}.group-item.active{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.loader,.loader:after,.loader:before{background:#000;-webkit-animation:load1 1s infinite ease-in-out;animation:load1 1s infinite ease-in-out;width:1em;height:4em}.loader{color:#000;text-indent:-9999em;margin:88px auto;position:relative;font-size:11px;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-animation-delay:-.16s;animation-delay:-.16s}.loader:after,.loader:before{position:absolute;top:0;content:""}.loader:before{left:-1.5em;-webkit-animation-delay:-.32s;animation-delay:-.32s}.loader:after{left:1.5em}@-webkit-keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}@keyframes load1{0%,100%,80%{box-shadow:0 0;height:4em}40%{box-shadow:0 -2em;height:5em}}.ct-label{fill:rgba(0,0,0,0.4);color:rgba(0,0,0,.4);font-size:.75rem;line-height:1}.ct-chart-bar .ct-label,.ct-chart-line .ct-label{display:block;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.ct-chart-donut .ct-label,.ct-chart-pie .ct-label{dominant-baseline:central}.ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-label.ct-vertical.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-label.ct-vertical.ct-end{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-start{-webkit-box-align:flex-end;-webkit-align-items:flex-end;-ms-flex-align:flex-end;align-items:flex-end;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-horizontal.ct-end{-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:start}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-start{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-end;-webkit-justify-content:flex-end;-ms-flex-pack:flex-end;justify-content:flex-end;text-align:right;text-anchor:end}.ct-chart-bar.ct-horizontal-bars .ct-label.ct-vertical.ct-end{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:flex-start;-webkit-justify-content:flex-start;-ms-flex-pack:flex-start;justify-content:flex-start;text-align:left;text-anchor:end}.ct-grid{stroke:rgba(0,0,0,0.2);stroke-width:1px;stroke-dasharray:2px}.ct-grid-background{fill:none}.ct-point{stroke-width:10px;stroke-linecap:round}.ct-line{fill:none;stroke-width:4px}.ct-area{stroke:none;fill-opacity:0.1}.ct-bar{fill:none;stroke-width:10px}.ct-slice-donut{fill:none;stroke-width:60px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#d70206}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#d70206}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#f05b4f}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#f05b4f}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#f4c63d}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#f4c63d}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#d17905}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#d17905}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#453d3f}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#453d3f}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#59922b}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#59922b}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#0544d3}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#0544d3}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#6b0392}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#6b0392}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#f05b4f}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#f05b4f}.ct-series-j .ct-bar,.ct-series-j .ct-line,.ct-series-j .ct-point,.ct-series-j .ct-slice-donut{stroke:#dda458}.ct-series-j .ct-area,.ct-series-j .ct-slice-donut-solid,.ct-series-j .ct-slice-pie{fill:#dda458}.ct-series-k .ct-bar,.ct-series-k .ct-line,.ct-series-k .ct-point,.ct-series-k .ct-slice-donut{stroke:#eacf7d}.ct-series-k .ct-area,.ct-series-k .ct-slice-donut-solid,.ct-series-k .ct-slice-pie{fill:#eacf7d}.ct-series-l .ct-bar,.ct-series-l .ct-line,.ct-series-l .ct-point,.ct-series-l .ct-slice-donut{stroke:#86797d}.ct-series-l .ct-area,.ct-series-l .ct-slice-donut-solid,.ct-series-l .ct-slice-pie{fill:#86797d}.ct-series-m .ct-bar,.ct-series-m .ct-line,.ct-series-m .ct-point,.ct-series-m .ct-slice-donut{stroke:#b2c326}.ct-series-m .ct-area,.ct-series-m .ct-slice-donut-solid,.ct-series-m .ct-slice-pie{fill:#b2c326}.ct-series-n .ct-bar,.ct-series-n .ct-line,.ct-series-n .ct-point,.ct-series-n .ct-slice-donut{stroke:#6188e2}.ct-series-n .ct-area,.ct-series-n .ct-slice-donut-solid,.ct-series-n .ct-slice-pie{fill:#6188e2}.ct-series-o .ct-bar,.ct-series-o .ct-line,.ct-series-o .ct-point,.ct-series-o .ct-slice-donut{stroke:#a748ca}.ct-series-o .ct-area,.ct-series-o .ct-slice-donut-solid,.ct-series-o .ct-slice-pie{fill:#a748ca}.ct-square{display:block;position:relative;width:100%}.ct-square:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:100%}.ct-square:after{content:"";display:table;clear:both}.ct-square>svg{display:block;position:absolute;top:0;left:0}.ct-minor-second{display:block;position:relative;width:100%}.ct-minor-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:93.75%}.ct-minor-second:after{content:"";display:table;clear:both}.ct-minor-second>svg{display:block;position:absolute;top:0;left:0}.ct-major-second{display:block;position:relative;width:100%}.ct-major-second:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:88.8888888889%}.ct-major-second:after{content:"";display:table;clear:both}.ct-major-second>svg{display:block;position:absolute;top:0;left:0}.ct-minor-third{display:block;position:relative;width:100%}.ct-minor-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:83.3333333333%}.ct-minor-third:after{content:"";display:table;clear:both}.ct-minor-third>svg{display:block;position:absolute;top:0;left:0}.ct-major-third{display:block;position:relative;width:100%}.ct-major-third:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:80%}.ct-major-third:after{content:"";display:table;clear:both}.ct-major-third>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fourth{display:block;position:relative;width:100%}.ct-perfect-fourth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:75%}.ct-perfect-fourth:after{content:"";display:table;clear:both}.ct-perfect-fourth>svg{display:block;position:absolute;top:0;left:0}.ct-perfect-fifth{display:block;position:relative;width:100%}.ct-perfect-fifth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:66.6666666667%}.ct-perfect-fifth:after{content:"";display:table;clear:both}.ct-perfect-fifth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-sixth{display:block;position:relative;width:100%}.ct-minor-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:62.5%}.ct-minor-sixth:after{content:"";display:table;clear:both}.ct-minor-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-golden-section{display:block;position:relative;width:100%}.ct-golden-section:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:61.804697157%}.ct-golden-section:after{content:"";display:table;clear:both}.ct-golden-section>svg{display:block;position:absolute;top:0;left:0}.ct-major-sixth{display:block;position:relative;width:100%}.ct-major-sixth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:60%}.ct-major-sixth:after{content:"";display:table;clear:both}.ct-major-sixth>svg{display:block;position:absolute;top:0;left:0}.ct-minor-seventh{display:block;position:relative;width:100%}.ct-minor-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:56.25%}.ct-minor-seventh:after{content:"";display:table;clear:both}.ct-minor-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-seventh{display:block;position:relative;width:100%}.ct-major-seventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:53.3333333333%}.ct-major-seventh:after{content:"";display:table;clear:both}.ct-major-seventh>svg{display:block;position:absolute;top:0;left:0}.ct-octave{display:block;position:relative;width:100%}.ct-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:50%}.ct-octave:after{content:"";display:table;clear:both}.ct-octave>svg{display:block;position:absolute;top:0;left:0}.ct-major-tenth{display:block;position:relative;width:100%}.ct-major-tenth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:40%}.ct-major-tenth:after{content:"";display:table;clear:both}.ct-major-tenth>svg{display:block;position:absolute;top:0;left:0}.ct-major-eleventh{display:block;position:relative;width:100%}.ct-major-eleventh:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:37.5%}.ct-major-eleventh:after{content:"";display:table;clear:both}.ct-major-eleventh>svg{display:block;position:absolute;top:0;left:0}.ct-major-twelfth{display:block;position:relative;width:100%}.ct-major-twelfth:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:33.3333333333%}.ct-major-twelfth:after{content:"";display:table;clear:both}.ct-major-twelfth>svg{display:block;position:absolute;top:0;left:0}.ct-double-octave{display:block;position:relative;width:100%}.ct-double-octave:before{display:block;float:left;content:"";width:0;height:0;padding-bottom:25%}.ct-double-octave:after{content:"";display:table;clear:both}.ct-double-octave>svg{display:block;position:absolute;top:0;left:0}.ct-legend{display:flex;margin-top:10px;margin-left:16px;margin-bottom:14x}.ct-legend-item{display:flex;margin-right:15px;line-height:18px}.ct-legend-item i{display:inline-block;width:18px;height:18px;margin-right:5px}.baseline-series .ct-line,.baseline-series .ct-point{stroke:green}.current-series .ct-line,.current-series .ct-point{stroke:blue}.chartist-tooltip{position:absolute;display:inline-block;opacity:0;min-width:5em;padding:.5em;background:#f4c63d;color:#453d3f;font-family:Oxygen,Helvetica,Arial,sans-serif;font-weight:700;text-align:center;pointer-events:none;z-index:1;-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;-o-transition:opacity .2s linear;transition:opacity .2s linear}.chartist-tooltip:before{content:"";position:absolute;top:100%;left:50%;width:0;height:0;margin-left:-15px;border:15px solid transparent;border-top-color:#f4c63d}.chartist-tooltip.tooltip-show{opacity:1}.ct-area,.ct-line{pointer-events:none}.chartist-tooltip{background:#0095d2;color:#fff}.chartist-tooltip:before{border-top-color:#0095d2}.ct-bar{stroke-width:16px}.ct-series-a .ct-bar,.ct-series-a .ct-line,.ct-series-a .ct-point,.ct-series-a .ct-slice-donut{stroke:#82b5fc}.ct-series-a .ct-area,.ct-series-a .ct-slice-donut-solid,.ct-series-a .ct-slice-pie{fill:#468847}.ct-series-b .ct-bar,.ct-series-b .ct-line,.ct-series-b .ct-point,.ct-series-b .ct-slice-donut{stroke:#b2ea94}.ct-series-b .ct-area,.ct-series-b .ct-slice-donut-solid,.ct-series-b .ct-slice-pie{fill:#c09853}.ct-series-c .ct-bar,.ct-series-c .ct-line,.ct-series-c .ct-point,.ct-series-c .ct-slice-donut{stroke:#fec584}.ct-series-c .ct-area,.ct-series-c .ct-slice-donut-solid,.ct-series-c .ct-slice-pie{fill:#b94a48}.ct-series-d .ct-bar,.ct-series-d .ct-line,.ct-series-d .ct-point,.ct-series-d .ct-slice-donut{stroke:#c49ae8}.ct-series-d .ct-area,.ct-series-d .ct-slice-donut-solid,.ct-series-d .ct-slice-pie{fill:#c49ae8}.ct-series-e .ct-bar,.ct-series-e .ct-line,.ct-series-e .ct-point,.ct-series-e .ct-slice-donut{stroke:#ff523e}.ct-series-e .ct-area,.ct-series-e .ct-slice-donut-solid,.ct-series-e .ct-slice-pie{fill:#ff523e}.ct-series-f .ct-bar,.ct-series-f .ct-line,.ct-series-f .ct-point,.ct-series-f .ct-slice-donut{stroke:#c4c4c4}.ct-series-f .ct-area,.ct-series-f .ct-slice-donut-solid,.ct-series-f .ct-slice-pie{fill:#c4c4c4}.ct-series-g .ct-bar,.ct-series-g .ct-line,.ct-series-g .ct-point,.ct-series-g .ct-slice-donut{stroke:#c4c4c4}.ct-series-g .ct-area,.ct-series-g .ct-slice-donut-solid,.ct-series-g .ct-slice-pie{fill:#c4c4c4}.ct-series-h .ct-bar,.ct-series-h .ct-line,.ct-series-h .ct-point,.ct-series-h .ct-slice-donut{stroke:#c4c4c4}.ct-series-h .ct-area,.ct-series-h .ct-slice-donut-solid,.ct-series-h .ct-slice-pie{fill:#c4c4c4}.ct-series-i .ct-bar,.ct-series-i .ct-line,.ct-series-i .ct-point,.ct-series-i .ct-slice-donut{stroke:#EAB839}.ct-series-i .ct-area,.ct-series-i .ct-slice-donut-solid,.ct-series-i .ct-slice-pie{fill:#EAB839}.ct-chart .ct-legend{position:relative;z-index:10;list-style:none;text-align:left;line-height:.8;font-size:.8em}.ct-chart .ct-legend li{padding-left:23px;margin-right:10px;margin-bottom:3px;cursor:pointer}.ct-chart .ct-legend li:before{width:12px;height:12px;position:absolute;left:0;content:"";border:3px solid transparent;border-radius:2px}.ct-chart .ct-legend li .inactive:before{background:0 0}.ct-chart .ct-legend li:first-child::before{background-color:#468847}.ct-chart .ct-legend li:nth-child(2)::before{background-color:#c09853}.ct-chart .ct-legend li:nth-child(3)::before{background-color:#b94a48}.ct-chart .ct-legend .ct-legend-inside{position:absolute;top:0;right:0}.ct-chart g:not(.ct-grids):not(.ct-labels) g:first-child .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:first-child .ct-point{stroke:#468847}.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(2) .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(2) .ct-point{stroke:#c09853}.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(3) .ct-line,.ct-chart g:not(.ct-grids):not(.ct-labels) g:nth-child(3) .ct-point{stroke:#b94a48}.filmstrip{padding-bottom:20px}.videoframe{vertical-align:top;display:inline-block;padding:4px;background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%}.videoframe.blue{border:2px solid #0095d2}.videoframetime{text-align:center;display:block}.videoframetext{text-align:left;display:block;line-height:1.2em;font-size:.8em;white-space:nowrap;margin-bottom:.2em} \ No newline at end of file diff --git a/lib/plugins/html/src/sass/components/chartist.scss b/lib/plugins/html/src/sass/components/chartist.scss index a377ad20ac..7e540e75ec 100644 --- a/lib/plugins/html/src/sass/components/chartist.scss +++ b/lib/plugins/html/src/sass/components/chartist.scss @@ -613,3 +613,34 @@ left: 0; } /*# sourceMappingURL=chartist.css.map */ + +.ct-legend { + display: flex; + // justify-content: center; + // align-items: center; + margin-top: 10px; + margin-left: 16px; + margin-bottom: 14x; +} + +.ct-legend-item { + display: flex; + // align-items: center; + margin-right: 15px; + line-height: 18px; +} + +.ct-legend-item i { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 5px; +} + +.baseline-series .ct-point, .baseline-series .ct-line { + stroke: green; +} + +.current-series .ct-point, .current-series .ct-line { + stroke: blue; +} diff --git a/lib/support/helpers/decimals.js b/lib/support/helpers/decimals.js index 63199a8256..4a465ec42d 100644 --- a/lib/support/helpers/decimals.js +++ b/lib/support/helpers/decimals.js @@ -1,4 +1,16 @@ export function decimals(decimals) { - let number = Number(decimals).toFixed(3); - return number === '0.000' ? 0 : number; + const num = Number(decimals); + + // Check if the number is an integer (no decimals) + if (Number.isInteger(num)) { + return num; + } + + // If the number is less than 1, use three decimals + if (num < 1) { + return Number(num.toFixed(3)); + } + + // If the number is greater than 1 and has decimals, use one decimal + return Number(num.toFixed(1)); } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 5e66c2abf2..1741a422d7 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -15,7 +15,7 @@ "@tgwf/co2": "0.13.6", "aws-sdk": "2.1327.0", "axe-core": "4.8.2", - "browsertime": "18.0.0", + "browsertime": "19.1.0", "cli-color": "2.0.3", "coach-core": "8.0.2", "concurrent-queue": "7.0.2", @@ -985,9 +985,9 @@ } }, "node_modules/@sitespeed.io/geckodriver": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@sitespeed.io/geckodriver/-/geckodriver-0.33.0.tgz", - "integrity": "sha512-w6w+x9/Q44JekTPi8NlRsfh5Uz4TfquJcUEs0tA/oEcxLVxRS7VtaiaJEE0GzzN6cUmFS6Twas7E4bCA4k/Yxg==", + "version": "0.33.0-c", + "resolved": "https://registry.npmjs.org/@sitespeed.io/geckodriver/-/geckodriver-0.33.0-c.tgz", + "integrity": "sha512-HRZubf9VOvPcMl9gjOjsom1vVGPZhc53n+BVdXh9uVloT603rO/1W6ny12WcR1EDVctPXfPoneFguqHdT5W5rQ==", "hasInstallScript": true, "dependencies": { "node-downloader-helper": "2.1.5", @@ -1759,15 +1759,15 @@ } }, "node_modules/browsertime": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-18.0.0.tgz", - "integrity": "sha512-E0BJVPahR/MgzyuANrvm2gnA7SiMgDw83VzEiYttd9YA/5uO26wmaRMs46Xo9xzah/d+0LQA/GO9a7bzc0dx+g==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-19.1.0.tgz", + "integrity": "sha512-LurCshUpj/W8XiN1Cfzem6RpqVSm/g9efW2PijO0y5KLuzZb4a+In5LIFH10Zm9PnHTyfs1aMow5ECnXNXQRag==", "dependencies": { "@cypress/xvfb": "1.2.4", "@devicefarmer/adbkit": "3.2.5", "@sitespeed.io/chromedriver": "119.0.6045-105", "@sitespeed.io/edgedriver": "119.0.2151-42", - "@sitespeed.io/geckodriver": "0.33.0", + "@sitespeed.io/geckodriver": "0.33.0-c", "@sitespeed.io/throttle": "5.0.0", "@sitespeed.io/tracium": "0.3.3", "btoa": "1.2.1", @@ -6115,9 +6115,9 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "engines": { "node": ">=8" } @@ -9990,9 +9990,9 @@ } }, "@sitespeed.io/geckodriver": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/@sitespeed.io/geckodriver/-/geckodriver-0.33.0.tgz", - "integrity": "sha512-w6w+x9/Q44JekTPi8NlRsfh5Uz4TfquJcUEs0tA/oEcxLVxRS7VtaiaJEE0GzzN6cUmFS6Twas7E4bCA4k/Yxg==", + "version": "0.33.0-c", + "resolved": "https://registry.npmjs.org/@sitespeed.io/geckodriver/-/geckodriver-0.33.0-c.tgz", + "integrity": "sha512-HRZubf9VOvPcMl9gjOjsom1vVGPZhc53n+BVdXh9uVloT603rO/1W6ny12WcR1EDVctPXfPoneFguqHdT5W5rQ==", "requires": { "node-downloader-helper": "2.1.5", "node-stream-zip": "1.15.0", @@ -10574,15 +10574,15 @@ } }, "browsertime": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-18.0.0.tgz", - "integrity": "sha512-E0BJVPahR/MgzyuANrvm2gnA7SiMgDw83VzEiYttd9YA/5uO26wmaRMs46Xo9xzah/d+0LQA/GO9a7bzc0dx+g==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/browsertime/-/browsertime-19.1.0.tgz", + "integrity": "sha512-LurCshUpj/W8XiN1Cfzem6RpqVSm/g9efW2PijO0y5KLuzZb4a+In5LIFH10Zm9PnHTyfs1aMow5ECnXNXQRag==", "requires": { "@cypress/xvfb": "1.2.4", "@devicefarmer/adbkit": "3.2.5", "@sitespeed.io/chromedriver": "119.0.6045-105", "@sitespeed.io/edgedriver": "119.0.2151-42", - "@sitespeed.io/geckodriver": "0.33.0", + "@sitespeed.io/geckodriver": "0.33.0-c", "@sitespeed.io/throttle": "5.0.0", "@sitespeed.io/tracium": "0.3.3", "btoa": "1.2.1", @@ -13878,9 +13878,9 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "minipass": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", - "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==" }, "minizlib": { "version": "2.1.2", diff --git a/package.json b/package.json index db1bebb599..bfb48bc9d7 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@tgwf/co2": "0.13.6", "aws-sdk": "2.1327.0", "axe-core": "4.8.2", - "browsertime": "18.0.0", + "browsertime": "19.1.0", "coach-core": "8.0.2", "cli-color": "2.0.3", "concurrent-queue": "7.0.2", diff --git a/test/compare-1.json b/test/compare-1.json new file mode 100644 index 0000000000..694815ba7c --- /dev/null +++ b/test/compare-1.json @@ -0,0 +1 @@ +{"info":{"browsertime":{"version":"18.0.0"},"url":"https://www.sitespeed.io","timestamp":"2023-11-24T04:16:28+01:00","connectivity":{"engine":"external","profile":"native"},"extra":{},"browser":{"name":"Chrome","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","version":"119.0.6045.159"}},"files":{"video":["pages/www_sitespeed_io/data/video/1.mp4","pages/www_sitespeed_io/data/video/2.mp4","pages/www_sitespeed_io/data/video/3.mp4"],"screenshot":[["pages/www_sitespeed_io/data/screenshots/1/afterPageCompleteCheck.png","pages/www_sitespeed_io/data/screenshots/1/layoutShift.png","pages/www_sitespeed_io/data/screenshots/1/largestContentfulPaint.png"],["pages/www_sitespeed_io/data/screenshots/2/afterPageCompleteCheck.png","pages/www_sitespeed_io/data/screenshots/2/layoutShift.png","pages/www_sitespeed_io/data/screenshots/2/largestContentfulPaint.png"],["pages/www_sitespeed_io/data/screenshots/3/afterPageCompleteCheck.png","pages/www_sitespeed_io/data/screenshots/3/layoutShift.png","pages/www_sitespeed_io/data/screenshots/3/largestContentfulPaint.png"]],"timeline":[],"consoleLog":[],"netLog":[],"perfLog":[],"geckoProfiles":[],"memoryReports":[]},"markedAsFailure":0,"failureMessages":[],"cdp":{"performance":[{"AudioHandlers":0,"AudioWorkletProcessors":0,"Documents":4,"Frames":1,"JSEventListeners":4,"LayoutObjects":645,"MediaKeySessions":0,"MediaKeys":0,"Nodes":702,"Resources":10,"ContextLifecycleStateObservers":5,"V8PerContextDatas":3,"WorkerGlobalScopes":0,"UACSSResources":0,"RTCPeerConnections":0,"ResourceFetchers":4,"AdSubframes":0,"DetachedScriptStates":2,"ArrayBufferContents":0,"LayoutCount":6,"RecalcStyleCount":5,"LayoutDuration":121.092,"RecalcStyleDuration":5.365,"DevToolsCommandDuration":27.226,"ScriptDuration":22.325000000000003,"V8CompileDuration":0.415,"TaskDuration":266.00800000000004,"TaskOtherDuration":89.585,"ThreadTime":0.204547,"ProcessTime":0.436757,"JSHeapUsedSize":2027704,"JSHeapTotalSize":2793472,"FirstMeaningfulPaint":898.0640000081621},{"AudioHandlers":0,"AudioWorkletProcessors":0,"Documents":4,"Frames":1,"JSEventListeners":4,"LayoutObjects":645,"MediaKeySessions":0,"MediaKeys":0,"Nodes":702,"Resources":10,"ContextLifecycleStateObservers":5,"V8PerContextDatas":3,"WorkerGlobalScopes":0,"UACSSResources":0,"RTCPeerConnections":0,"ResourceFetchers":4,"AdSubframes":0,"DetachedScriptStates":2,"ArrayBufferContents":0,"LayoutCount":6,"RecalcStyleCount":5,"LayoutDuration":103.473,"RecalcStyleDuration":4.497,"DevToolsCommandDuration":21.946,"ScriptDuration":31.144000000000002,"V8CompileDuration":0.432,"TaskDuration":242.352,"TaskOtherDuration":80.86,"ThreadTime":0.202672,"ProcessTime":0.422026,"JSHeapUsedSize":2020440,"JSHeapTotalSize":3055616,"FirstMeaningfulPaint":671.833999993396},{"AudioHandlers":0,"AudioWorkletProcessors":0,"Documents":4,"Frames":1,"JSEventListeners":4,"LayoutObjects":645,"MediaKeySessions":0,"MediaKeys":0,"Nodes":702,"Resources":10,"ContextLifecycleStateObservers":5,"V8PerContextDatas":3,"WorkerGlobalScopes":0,"UACSSResources":0,"RTCPeerConnections":0,"ResourceFetchers":4,"AdSubframes":0,"DetachedScriptStates":2,"ArrayBufferContents":0,"LayoutCount":6,"RecalcStyleCount":4,"LayoutDuration":110.89699999999999,"RecalcStyleDuration":3.741,"DevToolsCommandDuration":20.811,"ScriptDuration":19.959,"V8CompileDuration":0.335,"TaskDuration":219.809,"TaskOtherDuration":64.066,"ThreadTime":0.183858,"ProcessTime":0.390395,"JSHeapUsedSize":2067180,"JSHeapTotalSize":3055616,"FirstMeaningfulPaint":684.4899999996414}]},"android":{"batteryTemperature":[],"power":[]},"timestamps":["2023-11-24T04:15:58+01:00","2023-11-24T04:16:30+01:00","2023-11-24T04:17:00+01:00"],"browserScripts":[{"browser":{"cpuBenchmark":87,"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36","windowSize":"1366x623"},"pageinfo":{"cumulativeLayoutShift":0,"cumulativeLayoutShiftInfo":[],"documentHeight":4372,"documentSize":{"decodedBodySize":37800,"encodedBodySize":9383,"transferSize":9683},"documentTitle":"Welcome to the wonderful world of Web Performance","documentWidth":1366,"domElements":368,"largestContentfulPaintInfo":[{"className":"","domPath":"body > main > div:eq(0) > div > div:eq(0) > picture > img","duration":0,"id":"","loadTime":728,"renderTime":898,"size":216250,"startTime":898,"tag":"","tagName":"IMG","url":"https://www.sitespeed.io/img/team.png"}],"navigationStartTime":1700795762871,"nextHopProtocol":"h2","resources":{"count":10,"duration":2191.10000000149,"servedFromCache":0},"responsive":true,"url":"https://www.sitespeed.io/","visualElements":{"heroes":[{"filename":"team.png","height":433,"html":"","name":"LargestImage","width":500,"x":153,"y":50},{"filename":null,"height":173,"html":"
","name":"Heading","width":510,"x":703,"y":66},{"filename":"sitespeed-logo-2c.png","height":50,"html":"","name":"header-logo","width":162,"x":153,"y":0},{"filename":"team.png","height":433,"html":"","name":"logo","width":500,"x":153,"y":50},{"filename":"team.png","height":433,"html":"","name":"LargestContentfulPaint","width":500,"x":153,"y":50}],"viewport":{"height":623,"width":1366}},"longTask":[{"attribution":[{"containerId":"","containerName":"","containerSrc":"","containerType":"window"}],"duration":103,"name":"unknown","startTime":725.1000000014901}]},"timings":{"elementTimings":{"header-logo":{"duration":0,"loadTime":663,"naturalHeight":100,"naturalWidth":324,"renderTime":717,"startTime":717,"tagName":"IMG","url":"https://www.sitespeed.io/img/sitespeed-logo-2c.png"},"logo":{"duration":0,"loadTime":728,"naturalHeight":865,"naturalWidth":1000,"renderTime":717,"startTime":717,"tagName":"IMG","url":"https://www.sitespeed.io/img/team.png"}},"firstPaint":717,"largestContentfulPaint":{"className":"","domPath":"body > main > div:eq(0) > div > div:eq(0) > picture > img","duration":0,"id":"","loadTime":728,"renderTime":898,"size":216250,"startTime":898,"tag":"","tagName":"IMG","url":"https://www.sitespeed.io/img/team.png"},"loadEventEnd":835,"navigationTiming":{"connectStart":41,"domComplete":834,"domContentLoadedEventEnd":834,"domContentLoadedEventStart":834,"domInteractive":828,"domainLookupEnd":41,"domainLookupStart":41,"duration":835,"fetchStart":6,"loadEventEnd":835,"loadEventStart":835,"redirectEnd":0,"redirectStart":0,"requestStart":261,"responseEnd":458,"responseStart":422,"secureConnectionStart":72,"startTime":0,"unloadEventEnd":0,"unloadEventStart":0,"workerStart":0},"pageTimings":{"backEndTime":422,"domContentLoadedTime":834,"domInteractiveTime":828,"domainLookupTime":0,"frontEndTime":376,"pageDownloadTime":37,"pageLoadTime":835,"redirectionTime":0,"serverConnectionTime":219,"serverResponseTime":197},"paintTiming":{"first-contentful-paint":717,"first-paint":717},"serverTimings":[],"ttfb":422,"userTimings":{"marks":[{"name":"userTimingHeader","startTime":721.5},{"name":"userTimingFooter","startTime":724.5}],"measures":[{"duration":3,"name":"exampleMeasurement","startTime":721.5}]}},"coach":{"coachAdvice":{"advice":{"bestpractice":{"adviceList":{"amp":{"advice":"","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.","id":"amp","offending":[],"score":100,"tags":["bestpractice"],"title":"Avoid using AMP","weight":10},"charset":{"advice":"","description":"The Unicode Standard (UTF-8) covers (almost) all the characters, punctuations, and symbols in the world. Please use that.","id":"charset","offending":[],"score":100,"tags":["bestpractice"],"title":"Declare a charset in your document","weight":2},"cumulativeLayoutShift":{"advice":"There is no Layout Shift on the page.","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.","id":"cumulativeLayoutShift","offending":[],"score":100,"tags":["bestpractice"],"title":"Cumulative Layout Shift","weight":8},"doctype":{"advice":"","description":"The declaration is not an HTML tag; it is an instruction to the web browser about what version of HTML the page is written in.","id":"doctype","offending":[],"score":100,"tags":["bestpractice"],"title":"Declare a doctype in your document","weight":2},"language":{"advice":"","description":"According to the W3C recommendation you should declare the primary language for each Web page with the lang attribute inside the tag https://www.w3.org/International/questions/qa-html-language-declarations#basics.","id":"language","offending":[],"score":100,"tags":["bestpractice"],"title":"Declare the language code for your document","weight":3},"metaDescription":{"advice":"","description":"Use a page description to make the page more relevant to search engines.","id":"metaDescription","offending":[],"score":100,"tags":["bestpractice"],"title":"Meta description","weight":5},"optimizely":{"advice":"","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.","id":"optimizely","offending":[],"score":100,"tags":["bestpractice"],"title":"Only use Optimizely when you need it","weight":2},"pageTitle":{"advice":"","description":"Use a title to make the page more relevant to search engines.","id":"pageTitle","offending":[],"score":100,"tags":["bestpractice"],"title":"Page title","weight":5},"spdy":{"advice":"","description":"Chrome dropped supports for SPDY in Chrome 51, upgrade to HTTP/2 as soon as possible. The page has more users (browsers) supporting HTTP/2 than supports SPDY.","id":"spdy","offending":[],"score":100,"tags":["bestpractice"],"title":"EOL for SPDY in Chrome","weight":1},"url":{"advice":"","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.","id":"url","offending":[],"score":100,"tags":["bestpractice"],"title":"Have a good URL format","weight":2},"longHeaders":{"id":"longHeaders","title":"Do not send too long headers","description":"Do not send response headers that are too long.","advice":"","weight":1,"tags":["bestpractice","header"],"score":100,"offending":[]},"manyHeaders":{"id":"manyHeaders","title":"Avoid use too many response headers","description":"Avoid send too many response headers.","advice":"","weight":1,"tags":["bestpractice","headers"],"score":100,"offending":[]},"thirdParty":{"id":"thirdParty","title":"Avoid too many third party requests","description":"Do not load most of your content from third party URLs.","advice":"","weight":7,"tags":["bestpractice"],"score":100,"offending":[]},"unnecessaryHeaders":{"id":"unnecessaryHeaders","title":"Avoid unnecessary headers","description":"Do not send headers that you don't need. We look for p3p, cache-control and max-age, pragma, server and x-frame-options headers. Have a look at Andrew Betts - Headers for Hackers talk as a guide https://www.youtube.com/watch?v=k92ZbrY815c or read https://www.fastly.com/blog/headers-we-dont-want.","advice":"There are 11 responses that sets a server header. ","weight":1,"tags":["bestpractice","header"],"score":89,"offending":["https://www.sitespeed.io/","https://www.sitespeed.io/css/prism-1.15.css","https://www.sitespeed.io/js/clipboard-2.0.4.min.js","https://www.sitespeed.io/img/team.png","https://www.sitespeed.io/img/pippi.png","https://www.sitespeed.io/img/dashboard-front.png","https://www.sitespeed.io/js/prism-1.15.js","https://www.sitespeed.io/img/public.png","https://www.sitespeed.io/img/sitespeed-logo-2c.png","https://www.sitespeed.io/img/black-logo-120.png","https://www.sitespeed.io/img/ico/sitespeed.io.ico"]}},"score":100},"info":{"amp":false,"browser":"Chrome 119.0.0.0","connectionType":"h2","documentHeight":4372,"documentTitle":"Welcome to the wonderful world of Web Performance","documentWidth":1366,"domDepth":{"avg":8,"max":13},"domElements":368,"generator":null,"head":{"css":["https://www.sitespeed.io/css/prism-1.15.css"],"jsasync":[],"jssync":[]},"iframes":0,"localStorageSize":0,"metaDescription":"Sitespeed.io is an open source tool that helps you analyse and optimise your website speed and performance, based on performance best practices.","networkConnectionType":"4g","resourceHints":{"dns-prefetch":[],"preconnect":[],"prefetch":[],"prerender":[]},"responsive":true,"scripts":4,"serializedDomSize":16837,"serviceWorker":false,"sessionStorageSize":0,"userTiming":{"marks":2,"measures":1},"windowSize":"1366x623","pageTransferSize":"210.7 kB","pageContentSize":"257.7 kB","pageRequests":11,"pageDomains":1,"pageContentTypes":{"html":{"transferSize":9852,"contentSize":37800,"headerSize":0,"requests":1},"css":{"transferSize":1250,"contentSize":3553,"headerSize":0,"requests":1},"javascript":{"transferSize":9707,"contentSize":27296,"headerSize":0,"requests":2},"image":{"transferSize":183283,"contentSize":182560,"headerSize":0,"requests":6},"font":{"transferSize":0,"contentSize":0,"headerSize":0,"requests":0},"favicon":{"transferSize":6636,"contentSize":6518,"headerSize":0,"requests":1}},"pageExpireStats":{"min":"0 seconds","median":"6 weeks","max":"6 weeks","total":"1 year","values":"11 seconds"},"thirdParty":{"requestsByCategory":{},"requestsByTool":{}},"technology":[{"name":"Netlify","description":"Netlify providers hosting and server-less backend services for web applications and static websites.","slug":"netlify","categories":[{"id":62,"slug":"paas","groups":[7],"name":"PaaS","priority":8},{"id":31,"slug":"cdn","groups":[7],"name":"CDN","priority":9}],"confidence":100,"version":"","icon":"Netlify.svg","website":"https://www.netlify.com/","pricing":[],"cpe":null},{"name":"HSTS","description":"HTTP Strict Transport Security (HSTS) informs browsers that the site should only be accessed using HTTPS.","slug":"hsts","categories":[{"id":16,"slug":"security","groups":[11],"name":"Security","priority":9}],"confidence":100,"version":"","icon":"default.svg","website":"https://www.rfc-editor.org/rfc/rfc6797#section-6.1","pricing":[],"cpe":null}],"thirdparty":{"thirdPartyTransferSizeBytes":0,"totalThirdPartyRequests":0,"toolsByCategory":{},"byCategory":{},"offending":[]}},"performance":{"adviceList":{"avoidRenderBlocking":{"advice":"There are no render blocking resources.","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.","id":"avoidRenderBlocking","offending":[],"score":100,"tags":["performance"],"title":"Avoid slowing down the critical rendering path","weight":10},"avoidScalingImages":{"advice":"The page has 4 images 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.","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.","id":"avoidScalingImages","offending":["https://www.sitespeed.io/img/sitespeed-logo-2c.png","https://www.sitespeed.io/img/pippi.png","https://www.sitespeed.io/img/dashboard-front.png","https://www.sitespeed.io/img/public.png"],"score":60,"tags":["performance","image"],"title":"Don't scale images in the browser","weight":5},"cssPrint":{"advice":"","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.","id":"cssPrint","offending":[],"score":100,"tags":["performance","css"],"title":"Do not load specific print stylesheets.","weight":1},"firstContentfulPaint":{"advice":"","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),