├── .gitignore ├── .vscode └── settings.json ├── README.md ├── exo-2-latin-400.woff2 ├── index.html ├── package.json ├── rename.sh ├── rollup.config.js ├── script ├── gauge.js ├── main.js ├── math.js ├── metrics.js └── util.js ├── style.css ├── vercel.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | dist 4 | 5 | calc.js* 6 | 7 | stats.html -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.printWidth": 130 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lighthouse Scoring Calculator 2 | ------------------- 3 | 4 | [googlechrome.github.io/lighthouse/scorecalc/](https://googlechrome.github.io/lighthouse/scorecalc/) 5 | 6 | 7 | ![localhost_8000_ (1)](https://user-images.githubusercontent.com/39191/68729625-51d32900-057f-11ea-96c8-95747c35ccfd.png) 8 | -------------------------------------------------------------------------------- /exo-2-latin-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulirish/lh-scorecalc/05fc1c5a4e779e4801163dfc15bbcaa523db436a/exo-2-latin-400.woff2 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lighthouse Scoring calculator 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 |

20 | 21 | 50 | 51 | Lighthouse Scoring Calculator 52 | 53 |

54 | 55 |
56 | 57 | 66 | 67 | 68 | 69 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lh-scorecalc", 3 | "version": "1.0.0", 4 | "description": "wassup ===========", 5 | "main": "math.js", 6 | "scripts": { 7 | "watch": "find . script -depth 1 -type f | entr yarn build", 8 | "build": "rollup -c", 9 | "clean": "rm -rf dist", 10 | "deploy:ghpages": "yarn gh-pages --dist dist --repo git@github.com:GoogleChrome/lighthouse.git --dest scorecalc --add --message scorecalc-update", 11 | "deploy:copy": "mkdir -p dist && cp -r index.html *.css calc.* dist", 12 | "deploy": "yarn clean && yarn build && yarn deploy:copy && yarn deploy:ghpages", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "gh-pages": "^5.0.0", 20 | "rollup": "^2.45.2", 21 | "rollup-plugin-commonjs": "^10.1.0", 22 | "rollup-plugin-node-resolve": "^5.2.0", 23 | "rollup-plugin-visualizer": "^5.4.1" 24 | }, 25 | "dependencies": { 26 | "@rollup/plugin-buble": "^0.21.3", 27 | "preact": "^10.4.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git filter-branch --env-filter ' 4 | 5 | OLD_EMAIL="none" 6 | CORRECT_NAME="Paul Irish" 7 | CORRECT_EMAIL="commits@paul.irish" 8 | 9 | if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ] 10 | then 11 | export GIT_COMMITTER_NAME="$CORRECT_NAME" 12 | export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL" 13 | fi 14 | if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ] 15 | then 16 | export GIT_AUTHOR_NAME="$CORRECT_NAME" 17 | export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL" 18 | fi 19 | ' a5b7254bc3cdc7dc50b49b3d86c153614d183a82...91f7df52bf211c99b164e8b2f56221f074a85fa9 -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import buble from '@rollup/plugin-buble'; 5 | import { visualizer } from 'rollup-plugin-visualizer'; 6 | 7 | 8 | export default { 9 | input: 'script/main.js', 10 | output: { 11 | file: 'calc.js', 12 | format: 'es', 13 | sourcemap: true, 14 | }, 15 | plugins: [ 16 | resolve(), 17 | buble({ 18 | jsx: 'h', 19 | objectAssign: 'Object.assign', 20 | target: { chrome: 71, safari: 11.1, firefox: 64 }, // roughly disable ES feature transpilation 21 | }), 22 | commonjs(), 23 | visualizer(), // build and view stats.html 24 | ] 25 | }; -------------------------------------------------------------------------------- /script/gauge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Most of the impressive code here authored by Ana Tudor, Nov 2019 */ 4 | 5 | import {calculateRating} from './util.js'; 6 | 7 | const delay = delay => new Promise(resolve => setTimeout(resolve, delay)); 8 | 9 | export function updateGauge(wrapper, category) { 10 | _setPerfGaugeExplodey(wrapper, category); 11 | } 12 | 13 | function _determineTrig(sizeSVG, percent, strokeWidth) { 14 | strokeWidth = strokeWidth || sizeSVG / 32; 15 | 16 | const radiusInner = sizeSVG / strokeWidth; 17 | const strokeGap = 0.5 * strokeWidth; 18 | const radiusOuter = radiusInner + strokeGap + strokeWidth; 19 | 20 | const circumferenceInner = 2 * Math.PI * radiusInner; 21 | // arc length we need to subtract 22 | // for very small strokeWidth:radius ratios this is ≈ strokeWidth 23 | // angle = acute angle of isosceles △ with 2 edges equal to radius & 3rd equal to strokeWidth 24 | // angle formula given by law of cosines 25 | const endDiffInner = Math.acos(1 - 0.5 * Math.pow((0.5 * strokeWidth) / radiusInner, 2)) * radiusInner; 26 | 27 | const circumferenceOuter = 2 * Math.PI * radiusOuter; 28 | const endDiffOuter = Math.acos(1 - 0.5 * Math.pow((0.5 * strokeWidth) / radiusOuter, 2)) * radiusOuter; 29 | 30 | return { 31 | radiusInner, 32 | radiusOuter, 33 | circumferenceInner, 34 | circumferenceOuter, 35 | getArcLength: _ => Math.max(0, +(percent * circumferenceInner - 2 * endDiffInner).toFixed(4)), 36 | // isButt case is for metricArcHoverTarget 37 | getMetricArcLength: (weightingPct, isButt) => { 38 | // TODO: this math isn't perfect butt it's very close. 39 | const linecapFactor = isButt ? 0 : 2 * endDiffOuter; 40 | return Math.max(0, +(weightingPct * circumferenceOuter - strokeGap - linecapFactor).toFixed(4)); 41 | }, 42 | endDiffInner, 43 | endDiffOuter, 44 | strokeWidth, 45 | strokeGap, 46 | }; 47 | } 48 | 49 | /** 50 | * @param {HTMLAnchorElement} wrapper 51 | * @param {LH.ReportResult.Category} category 52 | */ 53 | function _setPerfGaugeExplodey(wrapper, category) { 54 | const sizeSVG = 128; 55 | const offsetSVG = -0.5 * sizeSVG; 56 | 57 | const percent = Number(category.score); 58 | const { 59 | radiusInner, 60 | radiusOuter, 61 | circumferenceInner, 62 | circumferenceOuter, 63 | getArcLength, 64 | getMetricArcLength, 65 | endDiffInner, 66 | endDiffOuter, 67 | strokeWidth, 68 | strokeGap, 69 | } = _determineTrig(sizeSVG, percent); 70 | 71 | const SVG = wrapper.querySelector('.lh-gauge'); 72 | const NS_URI = 'http://www.w3.org/2000/svg'; 73 | 74 | SVG.setAttribute('viewBox', [offsetSVG, offsetSVG, sizeSVG, sizeSVG].join(' ')); 75 | SVG.style.setProperty('--stroke-width', `${strokeWidth}px`); 76 | SVG.style.setProperty('--circle-meas', 2 * Math.PI.toFixed(4)); 77 | 78 | const groupOuter = wrapper.querySelector('.lh-gauge__outer'); 79 | const groupInner = wrapper.querySelector('.lh-gauge__inner'); 80 | const cover = groupOuter.querySelector('.cover'); 81 | const gaugeArc = groupInner.querySelector('.lh-gauge__arc'); 82 | const gaugePerc = groupInner.querySelector('.lh-gauge__percentage'); 83 | 84 | groupOuter.style.setProperty('--scale-initial', radiusInner / radiusOuter); 85 | groupOuter.style.setProperty('--radius', `${radiusOuter}px`); 86 | cover.style.setProperty('--radius', `${0.5 * (radiusInner + radiusOuter)}px`); 87 | cover.setAttribute('stroke-width', strokeGap); 88 | SVG.style.setProperty('--radius', `${radiusInner}px`); 89 | 90 | gaugeArc.setAttribute('stroke-dasharray', `${getArcLength()} ${(circumferenceInner - getArcLength()).toFixed(4)}`); 91 | gaugeArc.setAttribute('stroke-dashoffset', 0.25 * circumferenceInner - endDiffInner); 92 | 93 | gaugePerc.textContent = Math.round(percent * 100); 94 | 95 | const radiusTextOuter = radiusOuter + strokeWidth; 96 | const radiusTextInner = radiusOuter - strokeWidth; 97 | 98 | const metrics = category.auditRefs.filter(r => r.group === 'metrics' && r.weight); 99 | const totalWeight = metrics.reduce((sum, each) => (sum += each.weight), 0); 100 | let offsetAdder = 0.25 * circumferenceOuter - endDiffOuter - 0.5 * strokeGap; 101 | let angleAdder = -0.5 * Math.PI; 102 | 103 | // Extra hack on top of the HACK for element reuse below. Delete any metric elems that aren't needed anymore (happens when the same gauge goes from v5 to v6) 104 | groupOuter.querySelectorAll('.metric').forEach(metricElem => { 105 | const classNamesToRetain = metrics.map(metric => `metric--${metric.id}`); 106 | const match = classNamesToRetain.find(selector => metricElem.classList.contains(selector)); 107 | if (!match) metricElem.remove(); 108 | }); 109 | 110 | metrics.forEach((metric, i) => { 111 | // TODO(porting to real LHR..): in scorecalc we dont use the real audit ID just the acronym. 112 | const alias = metric.id; 113 | 114 | // Hack 115 | const needsDomPopulation = !groupOuter.querySelector(`.metric--${alias}`); 116 | 117 | // HACK:This isn't ideal but it was quick. Create element during initialization or reuse existing during updates 118 | const metricGroup = groupOuter.querySelector(`.metric--${alias}`) || document.createElementNS(NS_URI, 'g'); 119 | const metricArcMax = groupOuter.querySelector(`.metric--${alias} .lh-gauge--faded`) || document.createElementNS(NS_URI, 'circle'); 120 | const metricArc = groupOuter.querySelector(`.metric--${alias} .lh-gauge--miniarc`) || document.createElementNS(NS_URI, 'circle'); 121 | const metricArcHoverTarget = groupOuter.querySelector(`.metric--${alias} .lh-gauge-hover`) || document.createElementNS(NS_URI, 'circle'); 122 | const metricLabel = groupOuter.querySelector(`.metric--${alias} .metric__label`) || document.createElementNS(NS_URI, 'text'); 123 | const metricValue = groupOuter.querySelector(`.metric--${alias} .metric__value`) || document.createElementNS(NS_URI, 'text'); 124 | 125 | metricGroup.classList.add('metric', `metric--${alias}`); 126 | metricArcMax.classList.add('lh-gauge__arc', 'lh-gauge__arc--metric', 'lh-gauge--faded'); 127 | metricArc.classList.add('lh-gauge__arc', 'lh-gauge__arc--metric', 'lh-gauge--miniarc'); 128 | metricArcHoverTarget.classList.add('lh-gauge__arc', 'lh-gauge__arc--metric', 'lh-gauge--faded', 'lh-gauge-hover'); 129 | 130 | const weightingPct = metric.weight / totalWeight; 131 | const metricLengthMax = getMetricArcLength(weightingPct); 132 | const metricPercent = metric.result.score * weightingPct; 133 | const metricLength = getMetricArcLength(metricPercent); 134 | const metricOffset = weightingPct * circumferenceOuter; 135 | const metricHoverLength = getMetricArcLength(weightingPct, true); 136 | 137 | metricGroup.style.setProperty('--metric-color', `var(--palette-${i})`); 138 | metricGroup.style.setProperty('--metric-offset', `${offsetAdder}`); 139 | metricGroup.style.setProperty('--i', i); 140 | 141 | metricArcMax.setAttribute('stroke-dasharray', `${metricLengthMax} ${circumferenceOuter - metricLengthMax}`); 142 | metricArc.style.setProperty('--metric-array', `${metricLength} ${circumferenceOuter - metricLength}`); 143 | metricArcHoverTarget.setAttribute('stroke-dasharray', `${metricHoverLength} ${circumferenceOuter - metricHoverLength - endDiffOuter}`); 144 | 145 | metricLabel.classList.add('metric__label'); 146 | metricValue.classList.add('metric__value'); 147 | metricLabel.textContent = alias; 148 | metricValue.textContent = `+${Math.round(metricPercent * 100)}`; 149 | 150 | const midAngle = angleAdder + weightingPct * Math.PI; 151 | const cos = Math.cos(midAngle); 152 | const sin = Math.sin(midAngle); 153 | 154 | // only set non-default alignments 155 | switch (true) { 156 | case cos > 0: 157 | metricValue.setAttribute('text-anchor', 'end'); 158 | break; 159 | case cos < 0: 160 | metricLabel.setAttribute('text-anchor', 'end'); 161 | break; 162 | case cos === 0: 163 | metricLabel.setAttribute('text-anchor', 'middle'); 164 | metricValue.setAttribute('text-anchor', 'middle'); 165 | break; 166 | } 167 | 168 | switch (true) { 169 | case sin > 0: 170 | metricLabel.setAttribute('dominant-baseline', 'hanging'); 171 | break; 172 | case sin < 0: 173 | metricValue.setAttribute('dominant-baseline', 'hanging'); 174 | break; 175 | case sin === 0: 176 | metricLabel.setAttribute('dominant-baseline', 'middle'); 177 | metricValue.setAttribute('dominant-baseline', 'middle'); 178 | break; 179 | } 180 | 181 | metricLabel.setAttribute('x', (radiusTextOuter * cos).toFixed(2)); 182 | metricLabel.setAttribute('y', (radiusTextOuter * sin).toFixed(2)); 183 | metricValue.setAttribute('x', (radiusTextInner * cos).toFixed(2)); 184 | metricValue.setAttribute('y', (radiusTextInner * sin).toFixed(2)); 185 | 186 | if (needsDomPopulation) { 187 | metricGroup.appendChild(metricArcMax); 188 | metricGroup.appendChild(metricArc); 189 | metricGroup.appendChild(metricArcHoverTarget); 190 | metricGroup.appendChild(metricLabel); 191 | metricGroup.appendChild(metricValue); 192 | groupOuter.appendChild(metricGroup); 193 | } 194 | 195 | offsetAdder -= metricOffset; 196 | angleAdder += weightingPct * 2 * Math.PI; 197 | }); 198 | 199 | // Hack. Not ideal. 200 | if (SVG.dataset.listenersSetup) return; 201 | SVG.dataset.listenersSetup = true; 202 | 203 | // peekGauge(SVG); 204 | 205 | /* 206 | wrapper.state-expanded: gauge is exploded 207 | wrapper.state-highlight: gauge is exploded and one of the metrics is being highlighted 208 | metric.metric-highlight: highlight this particular metric 209 | */ 210 | SVG.addEventListener('mouseover', e => { 211 | // if hovering on the SVG and its expanded, get rid of everything 212 | if (e.target === SVG && wrapper.classList.contains('state--expanded')) { 213 | // paul: not sure why we want to remove this.. seems like we want to keep it expanded... 214 | SVG.classList.remove('state--expanded'); 215 | 216 | if (SVG.classList.contains('state--highlight')) { 217 | SVG.classList.remove('state--highlight'); 218 | SVG.querySelector('.metric--highlight').classList.remove('metric--highlight'); 219 | } 220 | return; 221 | } 222 | 223 | const parent = e.target.parentNode; 224 | 225 | // if hovering on a metric, highlight that one. 226 | // TODO: The hover target is a little small. ideally it's thicker. 227 | if (parent && parent.classList && parent.classList.contains('metric')) { 228 | // match the bg color of the gauge during a metric highlight 229 | wrapper.style.setProperty('--color-highlight', `var(--palette-${parent.style.getPropertyValue('--i')})`); 230 | 231 | if (!SVG.classList.contains('state--highlight')) { 232 | SVG.classList.add('state--highlight'); 233 | parent.classList.add('metric--highlight'); 234 | } else { 235 | const highlighted = SVG.querySelector('.metric--highlight'); 236 | 237 | if (highlighted && parent !== highlighted) { 238 | highlighted.classList.remove('metric--highlight'); 239 | parent.classList.add('metric--highlight'); 240 | } 241 | } 242 | } 243 | }); 244 | 245 | // clear on mouselave even if mousemove didn't catch it. 246 | SVG.addEventListener('mouseleave', e => { 247 | SVG.classList.remove('state--highlight'); 248 | const mh = SVG.querySelector('.metric--highlight'); 249 | mh && mh.classList.remove('metric--highlight'); 250 | }); 251 | 252 | // On the first run, tease with a little peek reveal 253 | async function peekGauge(SVG) { 254 | // Delay just a tad to let the user aclimatize beforehand. 255 | await delay(1000); 256 | 257 | // To visually get the outer ring to peek on the edge, we need the inner ring on top. This is SVG's equivalent to `innerElem.zIndex = 100` 258 | const inner = SVG.querySelector('.lh-gauge__inner'); 259 | let id = `uniq-${Math.random()}`; 260 | inner.setAttribute('id', id); 261 | const useElem = document.createElementNS(NS_URI, 'use'); 262 | useElem.setAttribute('href', `#${id}`); 263 | // for paint order this must come _after_ the outer. 264 | SVG.appendChild(useElem); 265 | 266 | const peekDurationSec = 2.5; 267 | SVG.style.setProperty('--peek-dur', `${peekDurationSec}s`); 268 | SVG.classList.add('state--peek', 'state--expanded'); 269 | 270 | // Fancy double cleanup 271 | const cleanup = _ => SVG.classList.remove('state--peek', 'state--expanded') || useElem.remove(); 272 | const tId = setTimeout(_ => { 273 | SVG.removeEventListener('mouseenter', handleEarlyInteraction); 274 | cleanup(); 275 | }, peekDurationSec * 1000 * 1.5); // lil extra time just cuz 276 | function handleEarlyInteraction() { 277 | clearTimeout(tId); 278 | cleanup(); 279 | } 280 | SVG.addEventListener('mouseenter', handleEarlyInteraction, {once: true}); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /script/main.js: -------------------------------------------------------------------------------- 1 | import { h, render, createRef, Component } from 'preact'; 2 | import { QUANTILE_AT_VALUE, VALUE_AT_QUANTILE } from './math.js'; 3 | import { $, NBSP, numberFormatter, calculateRating, arithmeticMean } from './util.js'; 4 | import { metrics, scoringGuides } from './metrics.js'; 5 | import { updateGauge } from './gauge.js'; 6 | 7 | const params = new URLSearchParams(location.hash.substr(1)); 8 | 9 | function determineMinMax(metricScoring) { 10 | const valueAtScore100 = VALUE_AT_QUANTILE(metricScoring, 0.995); 11 | const valueAtScore5 = VALUE_AT_QUANTILE(metricScoring, 0.05); 12 | 13 | let min = Math.floor(valueAtScore100 / 1000) * 1000; 14 | let max = Math.ceil(valueAtScore5 / 1000) * 1000; 15 | let step = 10; 16 | 17 | // Special handling for CLS 18 | if (metricScoring.units === 'unitless') { 19 | min = 0; 20 | max = Math.ceil(valueAtScore5 * 100) / 100; 21 | step = 0.01; 22 | } 23 | 24 | return { 25 | min, 26 | max, 27 | step, 28 | }; 29 | } 30 | 31 | /** 32 | * @param {string} version 33 | */ 34 | function getMajorVersion(version) { 35 | return version.split('.')[0]; 36 | } 37 | 38 | class Metric extends Component { 39 | onValueChange(e) { 40 | const {id} = this.props; 41 | 42 | this.props.app.setState({ 43 | metricValues: { 44 | ...this.props.app.state.metricValues, 45 | [id]: e.target.valueAsNumber, 46 | }, 47 | }); 48 | } 49 | 50 | onScoreChange(e) { 51 | const {id, metricScoring} = this.props; 52 | 53 | const score = e.target.valueAsNumber; 54 | let computedValue = VALUE_AT_QUANTILE(metricScoring, score / 100); 55 | 56 | // Clamp because we can end up with Infinity 57 | const { min, max } = determineMinMax(metricScoring); 58 | computedValue = Math.max(Math.min(computedValue, max), min); 59 | 60 | if (metricScoring.units !== 'unitless') { 61 | computedValue = Math.round(computedValue); 62 | } 63 | 64 | this.props.app.setState({ 65 | metricValues: { 66 | ...this.props.app.state.metricValues, 67 | [id]: computedValue, 68 | }, 69 | }); 70 | } 71 | 72 | render({ id, value, score, weightMax, metricScoring }) { 73 | const { min, max, step } = determineMinMax(metricScoring, id); 74 | const weight = metricScoring.weight; 75 | const valueFormatted = metricScoring.units === 'unitless' ? 76 | value.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : 77 | // TODO: Use https://github.com/tc39/proposal-unified-intl-numberformat#i-units when Safari/FF support it 78 | `${numberFormatter.format(value)}${NBSP}ms`; 79 | const weightFormatted = (weight * 100).toLocaleString(undefined, {maximumFractionDigits: 1}); 80 | 81 | return 82 | 83 | 84 | 85 | {`${id} (${metricScoring.name})`} 86 | 87 | this.onValueChange(e)} /> 88 | {valueFormatted} 89 | 90 | 91 | 92 | 93 | this.onScoreChange(e)} /> 94 | {score} 95 | 96 | 97 | 98 | {weightFormatted}% 99 | 100 | 101 | } 102 | } 103 | 104 | class Gauge extends Component { 105 | constructor(props) { 106 | super(props); 107 | this.ref = createRef(); 108 | } 109 | 110 | refreshGauge() { 111 | updateGauge(this.ref.current, { 112 | title: 'Performance', 113 | auditRefs: this.props.auditRefs, 114 | id: 'performance', 115 | score: this.props.score, 116 | }); 117 | } 118 | 119 | componentDidMount() { 120 | this.refreshGauge(); 121 | } 122 | 123 | componentDidUpdate() { 124 | this.refreshGauge(); 125 | } 126 | 127 | render({ score }) { 128 | return ( 129 |
130 |
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 |
143 |
144 | ); 145 | } 146 | } 147 | 148 | class ScoringGuide extends Component { 149 | render({ app, name, values, scoring }) { 150 | // Make sure weights total to 1 151 | const weights = Object.values(scoring).map(metricScoring => metricScoring.weight); 152 | const weightSum = weights.reduce((agg, val) => (agg += val)); 153 | const weightMax = Math.max(...Object.values(weights)); 154 | console.assert(weightSum > 0.999 && weightSum < 1.0001); // lol rounding is hard. 155 | 156 | const metricsData = Object.keys(scoring).map(id => { 157 | const metricScoring = scoring[id]; 158 | return { 159 | id, 160 | metricScoring, 161 | value: values[id], 162 | score: Math.round(QUANTILE_AT_VALUE(metricScoring, values[id]) * 100), 163 | }; 164 | }); 165 | 166 | const auditRefs = metricsData.map(metric => { 167 | return { 168 | id: metric.id, 169 | weight: metric.metricScoring.weight, 170 | group: 'metrics', 171 | result: { 172 | score: metric.score / 100, 173 | }, 174 | }; 175 | }); 176 | 177 | const score = arithmeticMean(auditRefs); 178 | 179 | let title =

{name}

; 180 | if (name === 'v10') { 181 | title =

latest
v10

; 182 | } else if (name === 'v8') { 183 | title =

v8, v9

; 184 | } else if (name === 'v6') { 185 | title =

v6, v7

; 186 | } 187 | 188 | return
189 | {title} 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | {metricsData.map(metric => { 203 | return 204 | })} 205 | 206 |
ValueMetric ScoreWeighting
207 | 208 |
209 | 210 |
211 |
212 | } 213 | } 214 | 215 | const debounce = (callback, time = 250, interval) => 216 | ((...args) => { 217 | clearTimeout(interval); 218 | interval = setTimeout(() => callback(...args), time); 219 | }); 220 | 221 | class App extends Component { 222 | constructor(props) { 223 | super(props); 224 | this.state = getInitialState(); 225 | this.onDeviceChange = this.onDeviceChange.bind(this); 226 | this.onVersionsChange = this.onVersionsChange.bind(this); 227 | // debounce to avoid flooding with new URLs 228 | this.debouncedUpdatePermalink = debounce(this.updatePermalink); 229 | } 230 | 231 | updatePermalink(state) { 232 | const {versions, device, metricValues} = state; 233 | const url = new URL(location.href); 234 | const auditIdValuePairs = Object.entries(metricValues).map(([id, value]) => { 235 | return [id, value]; 236 | }); 237 | const params = new URLSearchParams(auditIdValuePairs); 238 | params.set('device', device); 239 | for (const version of versions) params.append('version', version); 240 | url.hash = params.toString(); 241 | history.replaceState(state, '', url.toString()); 242 | } 243 | 244 | componentDidUpdate() { 245 | this.debouncedUpdatePermalink(this.state); 246 | } 247 | 248 | onDeviceChange(e) { 249 | this.setState({device: e.target.value}); 250 | } 251 | 252 | onVersionsChange(e) { 253 | this.setState({versions: e.target.value.split(',')}); 254 | } 255 | 256 | normalizeVersions(versions) { 257 | return versions.map(version => { 258 | version = parseInt(version, 10); 259 | if (version < 5) { 260 | throw new Error(`Unsupported Lighthouse version (${version})`); 261 | } 262 | 263 | switch (version) { 264 | default: 265 | case 12: 266 | case 11: 267 | case 10: 268 | return 10; 269 | case 9: 270 | case 8: 271 | return 8; 272 | case 7: 273 | case 6: 274 | return 6; 275 | } 276 | }); 277 | } 278 | 279 | render() { 280 | const {versions, device, metricValues} = this.state; 281 | 282 | const normalizedVersions = this.normalizeVersions(versions); 283 | 284 | const scoringGuideEls = normalizedVersions.map(version => { 285 | const key = `v${version}`; 286 | console.assert(scoringGuides[key], `scoringGuide for ${key} doesnt exist`) 287 | return ; 288 | }); 289 | return
290 |
291 | 297 | 306 |
307 | {scoringGuideEls} 308 |
309 | } 310 | } 311 | 312 | function getInitialState() { 313 | const availableScoringGuides = Object.keys(scoringGuides).map(k => parseInt(k.replace('v',''), 10)).sort((a, b) => b - a); 314 | 315 | const versions = params.has('version') ? 316 | params.getAll('version').map(getMajorVersion) : 317 | [`${availableScoringGuides.at(0) || 8}`]; // version (or versions) to show by default 318 | 319 | // Default to mobile if it's not matching our known emulatedFormFactors. https://github.com/GoogleChrome/lighthouse/blob/master/types/externs.d.ts#:~:text=emulatedFormFactor 320 | let device = params.get('device'); 321 | if (device && device !== 'mobile' && device !== 'desktop') { 322 | console.warn(`Invalid emulatedFormFactors value: ${device}. Fallback to mobile scoring.`); 323 | device = 'mobile'; 324 | } else if (!device) { 325 | // Device not expressed in the params 326 | device = 'mobile'; 327 | } 328 | 329 | const metricValues = {}; 330 | // If no metric values come in w/ params, initalize with mobile medians (score of 50) 331 | const metricScorings = {...scoringGuides.v5.mobile, ...scoringGuides.v8.mobile}; // v5 is neccessary for FCI 332 | for (const id in metricScorings) { 333 | metricValues[id] = metricScorings[id].median; 334 | } 335 | 336 | // Populate metricValues from query string. 337 | for (const [id, metric] of Object.entries(metrics)) { 338 | const value = params.get(id) || params.get(metric.auditId); 339 | if (!value) continue; 340 | metricValues[id] = Number(value); 341 | } 342 | 343 | return { 344 | versions, 345 | device, 346 | metricValues, 347 | };; 348 | } 349 | 350 | function main() { 351 | render(, $('#container')); 352 | } 353 | 354 | // just one call to main because i'm basic like that 355 | main(); 356 | -------------------------------------------------------------------------------- /script/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Approximates the Gauss error function, the probability that a random variable 3 | * from the standard normal distribution lies within [-x, x]. Moved from 4 | * traceviewer.b.math.erf, based on Abramowitz and Stegun, formula 7.1.26. 5 | * @param {number} x 6 | * @return {number} 7 | */ 8 | function internalErf_(x) { 9 | // erf(-x) = -erf(x); 10 | var sign = x < 0 ? -1 : 1; 11 | x = Math.abs(x); 12 | 13 | var a1 = 0.254829592; 14 | var a2 = -0.284496736; 15 | var a3 = 1.421413741; 16 | var a4 = -1.453152027; 17 | var a5 = 1.061405429; 18 | var p = 0.3275911; 19 | var t = 1 / (1 + p * x); 20 | var y = t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5)))); 21 | return sign * (1 - y * Math.exp(-x * x)); 22 | } 23 | 24 | /** 25 | * Creates a log-normal distribution and finds the complementary 26 | * quantile (1-percentile) of that distribution at value. All 27 | * arguments should be in the same units (e.g. milliseconds). 28 | * 29 | * @param {{median: number, podr?: number, p10?: number}} curve 30 | * @param {number} value 31 | * @return The complement of the quantile at value. 32 | * @customfunction 33 | */ 34 | export function QUANTILE_AT_VALUE({median, podr, p10}, value) { 35 | if (!podr) { 36 | podr = derivePodrFromP10(median, p10); 37 | } 38 | 39 | var location = Math.log(median); 40 | 41 | // The "podr" value specified the location of the smaller of the positive 42 | // roots of the third derivative of the log-normal CDF. Calculate the shape 43 | // parameter in terms of that value and the median. 44 | // See https://www.desmos.com/calculator/2t1ugwykrl 45 | var logRatio = Math.log(podr / median); 46 | var shape = Math.sqrt(1 - 3 * logRatio - Math.sqrt((logRatio - 3) * (logRatio - 3) - 8)) / 2; 47 | 48 | var standardizedX = (Math.log(value) - location) / (Math.SQRT2 * shape); 49 | return (1 - internalErf_(standardizedX)) / 2; 50 | } 51 | 52 | /** 53 | * Approximates the inverse error function. Based on Winitzki, "A handy 54 | * approximation for the error function and its inverse" 55 | * @param {number} x 56 | * @return {number} 57 | */ 58 | function internalErfInv_(x) { 59 | // erfinv(-x) = -erfinv(x); 60 | var sign = x < 0 ? -1 : 1; 61 | var a = 0.147; 62 | 63 | var log1x = Math.log(1 - x*x); 64 | var p1 = 2 / (Math.PI * a) + log1x / 2; 65 | var sqrtP1Log = Math.sqrt(p1 * p1 - (log1x / a)); 66 | return sign * Math.sqrt(sqrtP1Log - p1); 67 | } 68 | 69 | /** 70 | * Calculates the value at the given quantile. Median, podr, and 71 | * expected value should all be in the same units (e.g. milliseconds). 72 | * quantile should be within [0,1]. 73 | * 74 | * @param {{median: number, podr?: number, p10?: number}} curve 75 | * @return The value at this quantile. 76 | * @customfunction 77 | */ 78 | export function VALUE_AT_QUANTILE({median, podr, p10}, quantile) { 79 | if (!podr) { 80 | podr = derivePodrFromP10(median, p10); 81 | } 82 | 83 | var location = Math.log(median); 84 | var logRatio = Math.log(podr / median); 85 | var shape = Math.sqrt(1 - 3 * logRatio - Math.sqrt((logRatio - 3) * (logRatio - 3) - 8)) / 2; 86 | 87 | return Math.exp(location + shape * Math.SQRT2 * internalErfInv_(1 - 2 * quantile)); 88 | } 89 | 90 | // https://www.desmos.com/calculator/oqlvmezbze 91 | function derivePodrFromP10(median, p10) { 92 | const u = Math.log(median); 93 | const shape = Math.abs(Math.log(p10) - u) / (Math.SQRT2 * 0.9061938024368232); 94 | const inner1 = -3 * shape - Math.sqrt(4 + shape * shape); 95 | const podr = Math.exp(u + shape/2 * inner1) 96 | return podr; 97 | } 98 | -------------------------------------------------------------------------------- /script/metrics.js: -------------------------------------------------------------------------------- 1 | export const metrics = { 2 | FCP: {auditId: 'first-contentful-paint', name: 'First Contentful Paint'}, 3 | SI: {auditId: 'speed-index', name: 'Speed Index'}, 4 | LCP: {auditId: 'largest-contentful-paint', name: 'Largest Contentful Paint'}, 5 | TTI: {auditId: 'interactive', name: 'Time to Interactive'}, 6 | TBT: {auditId: 'total-blocking-time', name: 'Total Blocking Time'}, 7 | CLS: {auditId: 'cumulative-layout-shift', name: 'Cumulative Layout Shift', units: 'unitless'}, 8 | FMP: {auditId: 'first-meaningful-paint', name: 'First Meaningful Paint'}, 9 | FCI: {auditId: 'first-cpu-idle', name: 'First CPU Idle'}, 10 | }; 11 | 12 | export const curves = { 13 | v10: { 14 | mobile: { 15 | FCP: {weight: 0.10, median: 3000, p10: 1800}, 16 | SI: {weight: 0.10, median: 5800, p10: 3387}, 17 | LCP: {weight: 0.25, median: 4000, p10: 2500}, 18 | TBT: {weight: 0.30, median: 600, p10: 200}, 19 | CLS: {weight: 0.25, median: 0.25, p10: 0.1}, 20 | }, 21 | desktop: { 22 | FCP: {weight: 0.10, median: 1600, p10: 934}, 23 | SI: {weight: 0.10, median: 2300, p10: 1311}, 24 | LCP: {weight: 0.25, median: 2400, p10: 1200}, 25 | TBT: {weight: 0.30, median: 350, p10: 150}, 26 | CLS: {weight: 0.25, median: 0.25, p10: 0.1}, 27 | }, 28 | }, 29 | v8: { 30 | mobile: { 31 | FCP: {weight: 0.10, median: 3000, p10: 1800}, 32 | SI: {weight: 0.10, median: 5800, p10: 3387}, 33 | LCP: {weight: 0.25, median: 4000, p10: 2500}, 34 | TTI: {weight: 0.10, median: 7300, p10: 3785}, 35 | TBT: {weight: 0.30, median: 600, p10: 200}, 36 | CLS: {weight: 0.15, median: 0.25, p10: 0.1}, 37 | }, 38 | desktop: { 39 | FCP: {weight: 0.10, median: 1600, p10: 934}, 40 | SI: {weight: 0.10, median: 2300, p10: 1311}, 41 | LCP: {weight: 0.25, median: 2400, p10: 1200}, 42 | TTI: {weight: 0.10, median: 4500, p10: 2468}, 43 | TBT: {weight: 0.30, median: 350, p10: 150}, 44 | CLS: {weight: 0.15, median: 0.25, p10: 0.1}, 45 | }, 46 | }, 47 | v6: { 48 | mobile: { 49 | FCP: {weight: 0.15, median: 4000, p10: 2336}, 50 | SI: {weight: 0.15, median: 5800, p10: 3387}, 51 | LCP: {weight: 0.25, median: 4000, p10: 2500}, 52 | TTI: {weight: 0.15, median: 7300, p10: 3785}, 53 | TBT: {weight: 0.25, median: 600, p10: 287}, 54 | CLS: {weight: 0.05, median: 0.25, p10: 0.1}, 55 | }, 56 | desktop: { 57 | FCP: {weight: 0.15, median: 1600, p10: 934}, 58 | SI: {weight: 0.15, median: 2300, p10: 1311}, 59 | LCP: {weight: 0.25, median: 2400, p10: 1200}, 60 | TTI: {weight: 0.15, median: 4500, p10: 2468}, 61 | TBT: {weight: 0.25, median: 350, p10: 150}, 62 | CLS: {weight: 0.05, median: 0.25, p10: 0.1}, 63 | }, 64 | }, 65 | v5: { 66 | FCP: {weight: 0.2, median: 4000, podr: 2000}, 67 | SI: {weight: 0.26666, median: 5800, podr: 2900}, 68 | FMP: {weight: 0.066666, median: 4000, podr: 2000}, 69 | TTI: {weight: 0.33333, median: 7300, podr: 2900}, 70 | FCI: {weight: 0.133333, median: 6500, podr: 2900}, 71 | }, 72 | }; 73 | 74 | /** 75 | * @param {Record} curves 76 | */ 77 | function makeScoringGuide(curves) { 78 | const scoringGuide = {}; 79 | for (const [key, curve] of Object.entries(curves)) { 80 | scoringGuide[key] = {...metrics[key], ...curve}; 81 | } 82 | return scoringGuide; 83 | } 84 | 85 | export const scoringGuides = { 86 | // v9 => v8 and v7 => v6 is handled in normalizeVersions() 87 | v10: { 88 | mobile: makeScoringGuide(curves.v10.mobile), 89 | desktop: makeScoringGuide(curves.v10.desktop), 90 | }, 91 | v8: { 92 | mobile: makeScoringGuide(curves.v8.mobile), 93 | desktop: makeScoringGuide(curves.v8.desktop), 94 | }, 95 | v6: { 96 | mobile: makeScoringGuide(curves.v6.mobile), 97 | desktop: makeScoringGuide(curves.v6.desktop), 98 | }, 99 | v5: { 100 | mobile: makeScoringGuide(curves.v5), 101 | desktop: makeScoringGuide(curves.v5), 102 | }, 103 | }; 104 | -------------------------------------------------------------------------------- /script/util.js: -------------------------------------------------------------------------------- 1 | 2 | // blingjs 3 | export const $ = document.querySelector.bind(document); 4 | export const $$ = document.querySelectorAll.bind(document); 5 | 6 | Element.prototype.$ = Element.prototype.querySelector; 7 | Element.prototype.$$ = Element.prototype.querySelectorAll; 8 | 9 | export const NBSP = '\xa0'; 10 | export const numberFormatter = new Intl.NumberFormat(); 11 | 12 | // thx Lighthouse's util.js 13 | export function arithmeticMean(items) { 14 | items = items.filter(item => item.weight > 0); 15 | const results = items.reduce( 16 | (result, item) => { 17 | const score = item.result.score; 18 | const weight = item.weight; 19 | return { 20 | weight: result.weight + weight, 21 | sum: result.sum + score * weight, 22 | }; 23 | }, 24 | {weight: 0, sum: 0} 25 | ); 26 | return results.sum / results.weight || 0; 27 | } 28 | 29 | export function calculateRating(score) { 30 | const RATINGS = { 31 | PASS: {label: 'pass', minScore: 0.9}, 32 | AVERAGE: {label: 'average', minScore: 0.5}, 33 | FAIL: {label: 'fail'}, 34 | }; 35 | 36 | let rating = RATINGS.FAIL.label; 37 | if (score >= RATINGS.PASS.minScore) { 38 | rating = RATINGS.PASS.label; 39 | } else if (score >= RATINGS.AVERAGE.minScore) { 40 | rating = RATINGS.AVERAGE.label; 41 | } 42 | return rating; 43 | } 44 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | background-color: hsla(225, 68%, 25%, 1); 8 | height: 100%; 9 | padding: 0; 10 | 11 | /* lighthouse css */ 12 | font-family: var(--report-font-family); 13 | font-size: var(--report-font-size); 14 | margin: 0; 15 | line-height: var(--report-line-height); 16 | background: var(--report-background-color); 17 | scroll-behavior: smooth; 18 | color: var(--report-text-color); 19 | 20 | } 21 | 22 | [hidden] { 23 | display: none !important; 24 | } 25 | 26 | body { 27 | padding: 20px 0; 28 | } 29 | 30 | table { 31 | table-layout: fixed; 32 | 33 | /* this is some amazing shit. https://kennethormandy.com/journal/flipping-on-tabular-numbers/ */ 34 | -webkit-font-feature-settings: "tnum"; 35 | font-feature-settings: "tnum"; 36 | font-variant-numeric: tabular-nums; 37 | 38 | margin: 0 auto; 39 | } 40 | 41 | th, .controls { 42 | font-weight: normal; 43 | color: var(--color-gray-600); 44 | word-break: normal; 45 | border-bottom: 1px solid var(--color-gray-200); 46 | } 47 | 48 | tr:first-child td { 49 | border-top: 8px solid transparent; 50 | } 51 | 52 | td { 53 | vertical-align: top; 54 | } 55 | 56 | /* icon */ 57 | tbody td:nth-child(1) { 58 | width: 16px; 59 | } 60 | 61 | /* metric value */ 62 | tbody td:nth-child(3) { 63 | text-align: right; 64 | } 65 | 66 | input.metric-value, tbody td:nth-child(3) { 67 | max-width: 210px; 68 | width: 18vw; 69 | } 70 | 71 | tbody td:nth-child(4) { 72 | width: 30px; 73 | } 74 | 75 | /* metric score */ 76 | tbody td:nth-child(5) { 77 | text-align: right; 78 | font-size: 11px; 79 | } 80 | /* weighting */ 81 | tbody td:nth-child(6) { 82 | width: 40px; 83 | text-align: right; 84 | } 85 | 86 | 87 | input.metric-score { 88 | opacity: 0.85; 89 | } 90 | output { 91 | display: block; 92 | line-height: 16px; 93 | margin-bottom: 8px; 94 | } 95 | 96 | 97 | 98 | section { 99 | display: flex; 100 | justify-content: center; 101 | align-items: center; 102 | } 103 | 104 | h1 { 105 | font-size: var(--gauge-label-font-size-big); 106 | line-height: var(--gauge-label-line-height-big); 107 | font-weight: normal; 108 | text-align: center; 109 | } 110 | 111 | h1 svg { 112 | height: var(--gauge-label-font-size-big); 113 | vertical-align: text-top; 114 | } 115 | 116 | h2 i { 117 | font-size: 60%; 118 | } 119 | 120 | footer { 121 | font-size: 80%; 122 | display: block !important; 123 | } 124 | 125 | @media screen and (max-width: 1040px) { 126 | footer { 127 | padding: 0 20px; 128 | } 129 | } 130 | 131 | .wrapper { 132 | max-width: 1020px; 133 | margin: 60px auto; 134 | min-width: 380px; 135 | 136 | display: flex; 137 | align-items: center; 138 | } 139 | 140 | .controls { 141 | margin: 30px auto; 142 | justify-content: center; 143 | border: 0; 144 | } 145 | select { 146 | color: var(--color-gray-600); 147 | } 148 | 149 | label { 150 | margin: 0 10px; 151 | } 152 | 153 | /* the SVG pretty big so we'll subtract some of its whitespace */ 154 | .perfscore { 155 | margin-left: -20px; 156 | } 157 | /* keep the table on top tho. */ 158 | table { 159 | position: relative; 160 | z-index: 10; 161 | } 162 | 163 | @media screen and (max-width: 890px) { 164 | .wrapper { 165 | flex-direction: column; 166 | } 167 | .perfscore { 168 | margin-left: 0; 169 | margin-top: -20px; 170 | } 171 | tbody td:nth-child(4) { 172 | width: 3px 173 | } 174 | } 175 | 176 | h2 { 177 | font-weight: 200; 178 | font-size: 21px; 179 | margin: 0; 180 | text-align: center; 181 | width: 70px; 182 | } 183 | 184 | form { 185 | overflow: hidden; 186 | } 187 | 188 | 189 | 190 | 191 | .weight-text { 192 | color: var(--color-gray-500); 193 | } 194 | 195 | 196 | 197 | 198 | /* Lighthouse styles */ 199 | 200 | /* no idea which of these variables i dont need.. */ 201 | 202 | :root { 203 | 204 | --palette-0: #1de9b6; 205 | --palette-1: #7c4dff; 206 | --palette-2: #ffb200; 207 | --palette-3: #00affe; 208 | --palette-4: #ff4181; 209 | --palette-5: #a1a1a1; 210 | 211 | 212 | /* Palette using Material Design Colors 213 | * https://www.materialui.co/colors */ 214 | --color-amber-50: #FFF8E1; 215 | --color-blue-200: #90CAF9; 216 | --color-blue-900: #0D47A1; 217 | --color-blue-A700: #2962FF; 218 | --color-cyan-500: #00BCD4; 219 | --color-gray-100: #F5F5F5; 220 | --color-gray-200: #E0E0E0; 221 | --color-gray-400: #BDBDBD; 222 | --color-gray-50: #FAFAFA; 223 | --color-gray-500: #9E9E9E; 224 | --color-gray-600: #757575; 225 | --color-gray-700: #616161; 226 | --color-gray-800: #424242; 227 | --color-gray-900: #212121; 228 | --color-gray: #000000; 229 | --color-green-700: #018642; 230 | --color-green: #0CCE6B; 231 | --color-orange-700: #D04900; 232 | --color-orange: #FFA400; 233 | --color-red-700: #EB0F00; 234 | --color-red: #FF4E42; 235 | --color-teal-600: #00897B; 236 | --color-white: #FFFFFF; 237 | 238 | /* Context-specific colors */ 239 | --color-average-secondary: var(--color-orange-700); 240 | --color-average: var(--color-orange); 241 | --color-fail-secondary: var(--color-red-700); 242 | --color-fail: var(--color-red); 243 | --color-informative: var(--color-blue-900); 244 | --color-pass-secondary: var(--color-green-700); 245 | --color-pass: var(--color-green); 246 | --color-hover: var(--color-gray-50); 247 | 248 | /* Component variables */ 249 | --audit-description-padding-left: calc(var(--score-icon-size) + var(--score-icon-margin-left) + var(--score-icon-margin-right)); 250 | --audit-explanation-line-height: 16px; 251 | --audit-group-margin-bottom: 40px; 252 | --audit-group-padding-vertical: 8px; 253 | --audit-margin-horizontal: 5px; 254 | --audit-padding-vertical: 8px; 255 | --category-header-font-size: 20px; 256 | --category-padding: 40px; 257 | --chevron-line-stroke: var(--color-gray-600); 258 | --chevron-size: 12px; 259 | --default-padding: 12px; 260 | --env-item-background-color: var(--color-gray-100); 261 | --env-item-font-size: 28px; 262 | --env-item-line-height: 36px; 263 | --env-item-padding: 10px 0px; 264 | --env-name-min-width: 220px; 265 | --footer-padding-vertical: 16px; 266 | --gauge-circle-size-big: 112px; 267 | --gauge-circle-size: 120px; 268 | --gauge-label-font-size-big: 28px; 269 | --gauge-label-font-size: 20px; 270 | --gauge-label-line-height-big: 36px; 271 | --gauge-label-line-height: 26px; 272 | --gauge-percentage-font-size-big: 38px; 273 | --gauge-percentage-font-size: 28px; 274 | --gauge-wrapper-width: 148px; 275 | --header-line-height: 24px; 276 | --highlighter-background-color: var(--report-text-color); 277 | --icon-square-size: calc(var(--score-icon-size) * 0.88); 278 | --image-preview-size: 48px; 279 | --metric-toggle-lines-fill: #7F7F7F; 280 | --metrics-toggle-background-color: var(--color-gray-200); 281 | --plugin-badge-background-color: var(--color-white); 282 | --plugin-badge-size-big: calc(var(--gauge-circle-size-big) / 2.7); 283 | --plugin-badge-size: calc(var(--gauge-circle-size) / 2.7); 284 | --plugin-icon-size: 65%; 285 | --pwa-icon-margin: 0 6px 0 -2px; 286 | --pwa-icon-size: var(--topbar-logo-size); 287 | --report-background-color: #fff; 288 | --report-border-color-secondary: #ebebeb; 289 | --report-font-family-monospace: 'Roboto Mono', 'Menlo', 'dejavu sans mono', 'Consolas', 'Lucida Console', monospace; 290 | --report-font-family: Roboto, Helvetica, Arial, sans-serif; 291 | --report-font-size: 16px; 292 | --report-line-height: 24px; 293 | --report-min-width: 400px; 294 | --report-text-color-secondary: var(--color-gray-800); 295 | --report-text-color: var(--color-gray-900); 296 | --report-width: calc(60 * var(--report-font-size)); 297 | --score-container-padding: 8px; 298 | --score-icon-background-size: 24px; 299 | --score-icon-margin-left: 4px; 300 | --score-icon-margin-right: 12px; 301 | --score-icon-margin: 0 var(--score-icon-margin-right) 0 var(--score-icon-margin-left); 302 | --score-icon-size: 12px; 303 | --scores-container-padding: 20px 0 20px 0; 304 | --scorescale-height: 6px; 305 | --scorescale-width: 18px; 306 | --section-padding-vertical: 12px; 307 | --snippet-background-color: var(--color-gray-50); 308 | --snippet-color: var(--color-gray-800); 309 | --sparkline-height: 5px; 310 | --stackpack-padding-horizontal: 10px; 311 | --sticky-header-background-color: var(--report-background-color); 312 | --table-higlight-background-color: hsla(0, 0%, 75%, 0.1); 313 | --tools-icon-color: var(--color-gray-600); 314 | --tools-icon-size: var(--score-icon-background-size); 315 | --topbar-background-color: var(--color-gray-100); 316 | --topbar-height: 32px; 317 | --topbar-logo-size: 24px; 318 | --topbar-padding: 0 8px; 319 | --toplevel-warning-padding: 22px; 320 | 321 | } 322 | 323 | 324 | /* score icon */ 325 | 326 | .lh-metric__score-icon { 327 | width: var(--score-icon-size); 328 | height: var(--score-icon-size); 329 | display: inline-block; 330 | } 331 | 332 | .lh-metric--pass .lh-metric__display-text { 333 | color: var(--color-pass-secondary); 334 | } 335 | .lh-metric--pass .lh-metric__score-icon { 336 | border-radius: 100%; 337 | background: var(--color-pass); 338 | } 339 | 340 | .lh-metric--average .lh-metric__display-text { 341 | color: var(--color-average-secondary); 342 | } 343 | .lh-metric--average .lh-metric__score-icon { 344 | background: var(--color-average); 345 | width: var(--icon-square-size); 346 | height: var(--icon-square-size); 347 | } 348 | 349 | .lh-metric--fail .lh-metric__display-text { 350 | color: var(--color-fail-secondary); 351 | } 352 | .lh-metric--fail .lh-metric__score-icon, 353 | .lh-metric--error .lh-metric__score-icon { 354 | border-left: calc(var(--score-icon-size) / 2) solid transparent; 355 | border-right: calc(var(--score-icon-size) / 2) solid transparent; 356 | border-bottom: var(--score-icon-size) solid var(--color-fail); 357 | } 358 | 359 | 360 | /* the gauge! */ 361 | 362 | 363 | circle { 364 | stroke: currentcolor; 365 | r: var(--radius); 366 | } 367 | 368 | text { 369 | font-size: calc(var(--radius) * 0.2); 370 | } 371 | 372 | 373 | .lh-gauge { 374 | margin: 0 auto; 375 | width: 225px; 376 | stroke-width: var(--stroke-width); 377 | stroke-linecap: round; 378 | } 379 | .lh-gauge--faded { 380 | opacity: 0.1; 381 | } 382 | .lh-gauge__wrapper { 383 | font-family: var(--report-font-family-monospace); 384 | text-align: center; 385 | text-decoration: none; 386 | transition: .3s; 387 | cursor:default; 388 | } 389 | .lh-gauge__wrapper--pass { 390 | color: var(--color-pass); 391 | } 392 | .lh-gauge__wrapper--average { 393 | color: var(--color-average); 394 | } 395 | .lh-gauge__wrapper--fail { 396 | color: var(--color-fail); 397 | } 398 | .state--expanded { 399 | /* color: var(--color-gray-600); */ 400 | transition: color .3s; 401 | } 402 | .state--highlight { 403 | color: var(--color-highlight); 404 | } 405 | .lh-gauge__svg-wrapper { 406 | display: flex; 407 | flex-direction: column-reverse; 408 | } 409 | 410 | .lh-gauge__cutout { 411 | opacity: .999; 412 | transition: opacity .3s; 413 | } 414 | .state--highlight .lh-gauge__cutout { 415 | opacity: 0; 416 | } 417 | 418 | .lh-gauge__inner { 419 | color: inherit; 420 | } 421 | .lh-gauge__base { 422 | fill: currentcolor; 423 | } 424 | 425 | 426 | .lh-gauge__arc { 427 | fill: none; 428 | transition: opacity .3s; 429 | } 430 | .lh-gauge__arc--metric { 431 | color: var(--metric-color); 432 | stroke-dashoffset: var(--metric-offset); 433 | } 434 | .lh-gauge-hover { 435 | color: currentcolor; 436 | opacity: 0.001; 437 | stroke-linecap: butt; 438 | stroke-width: 24; 439 | /* hack. move the hover target out of the center. ideally i tweak the r instead but that rquires considerably more math. */ 440 | transform: scale(1.15); 441 | /* I'll remove it when someone goes 'eh' */ 442 | cursor: help; 443 | } 444 | .lh-gauge__arc--metric.lh-gauge--miniarc { 445 | opacity: 0; 446 | stroke-dasharray: 0 calc(var(--circle-meas) * var(--radius)); 447 | transition: 0s .005s; 448 | } 449 | .state--expanded .lh-gauge__arc--metric.lh-gauge--miniarc { 450 | opacity: .999; 451 | stroke-dasharray: var(--metric-array); 452 | transition: 0.3s; /* calc(.005s + var(--i)*.05s); entrace animation */ 453 | } 454 | 455 | .state--expanded .lh-gauge__base { 456 | fill: var(--color-gray-600); 457 | stroke: var(--color-gray-600); 458 | } 459 | 460 | .state--expanded .lh-gauge__inner .lh-gauge__arc { 461 | opacity: 0; 462 | } 463 | 464 | 465 | .lh-gauge__percentage { 466 | text-anchor: middle; 467 | dominant-baseline: middle; 468 | fill: currentcolor; 469 | opacity: .999; 470 | font-size: calc(var(--radius) * 0.625); 471 | transition: opacity .3s ease-in; 472 | } 473 | .state--highlight .lh-gauge__percentage { 474 | opacity: 0; 475 | } 476 | 477 | .cover { 478 | fill: none; 479 | opacity: .001; 480 | pointer-events: none; 481 | } 482 | .state--expanded .cover { 483 | pointer-events: auto; 484 | } 485 | 486 | .metric { 487 | transform: scale(var(--scale-initial)); 488 | opacity: 0; 489 | transition: transform .1s .2s ease-out, opacity .3s ease-out; 490 | pointer-events: none; 491 | } 492 | .metric text { 493 | pointer-events: none; 494 | } 495 | .metric__value { 496 | fill: currentcolor; 497 | opacity: 0; 498 | transition: opacity 0.2s; 499 | } 500 | .state--expanded .metric { 501 | transform: scale(1); 502 | opacity: .999; 503 | transition: transform .3s ease-out, opacity .3s ease-in, stroke-width .1s ease-out; 504 | transition-delay: calc(var(--i)*.05s); 505 | pointer-events: auto; 506 | } 507 | .state--highlight .metric { 508 | opacity: .3; 509 | } 510 | .state--highlight .metric--highlight { 511 | opacity: .999; 512 | stroke-width: calc(1.5*var(--stroke-width)); 513 | } 514 | .state--highlight .metric--highlight .metric__value { 515 | opacity: 0.999; 516 | } 517 | 518 | 519 | /* 520 | the initial first load peek 521 | */ 522 | .lh-gauge__bg { /* needed for the use zindex stacking w/ transparency */ 523 | fill: var(--report-background-color); 524 | stroke: var(--report-background-color); 525 | } 526 | .state--peek .metric { 527 | transition-delay: 0ms; 528 | animation: peek var(--peek-dur) cubic-bezier(0.46, 0.03, 0.52, 0.96); 529 | animation-fill-mode: forwards; 530 | } 531 | .state--peek .lh-gauge__inner .lh-gauge__arc { 532 | opacity: 1; 533 | } 534 | .state--peek .lh-gauge__arc.lh-gauge--faded:not(.lh-gauge-hover) { 535 | opacity: 0.3; /* just a tad stronger cuz its fighting with a big solid arg */ 536 | } 537 | /* do i need to set expanded and override this? */ 538 | .state--peek .lh-gauge__arc--metric.lh-gauge--miniarc { 539 | transition: opacity 0.3s; 540 | } 541 | .state--peek { 542 | color: unset; 543 | } 544 | .state--peek .metric__label { 545 | display: none; 546 | } 547 | 548 | 549 | @keyframes peek { 550 | /* biggest it should go is 0.92. smallest is 0.8 */ 551 | 0% { transform: scale(0.8); opacity: 0.8; } 552 | 50% { transform: scale(0.92); opacity: 1; } 553 | 100% { transform: scale(0.8); opacity: 0.8; } 554 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "github": { 4 | "silent": true 5 | } 6 | } -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@rollup/plugin-buble@^0.21.3": 6 | version "0.21.3" 7 | resolved "https://registry.yarnpkg.com/@rollup/plugin-buble/-/plugin-buble-0.21.3.tgz#1649a915b1d051a4f430d40e7734a7f67a69b33e" 8 | integrity sha512-Iv8cCuFPnMdqV4pcyU+OrfjOfagPArRQ1PyQjx5KgHk3dARedI+8PNTLSMpJts0lQJr8yF2pAU4GxpxCBJ9HYw== 9 | dependencies: 10 | "@rollup/pluginutils" "^3.0.8" 11 | "@types/buble" "^0.19.2" 12 | buble "^0.20.0" 13 | 14 | "@rollup/pluginutils@^3.0.8": 15 | version "3.0.10" 16 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.10.tgz#a659b9025920378494cd8f8c59fbf9b3a50d5f12" 17 | integrity sha512-d44M7t+PjmMrASHbhgpSbVgtL6EFyX7J4mYxwQ/c5eoaE6N2VgCgEcWVzNnwycIloti+/MpwFr8qfw+nRw00sw== 18 | dependencies: 19 | "@types/estree" "0.0.39" 20 | estree-walker "^1.0.1" 21 | picomatch "^2.2.2" 22 | 23 | "@types/buble@^0.19.2": 24 | version "0.19.2" 25 | resolved "https://registry.yarnpkg.com/@types/buble/-/buble-0.19.2.tgz#a4289d20b175b3c206aaad80caabdabe3ecdfdd1" 26 | integrity sha512-uUD8zIfXMKThmFkahTXDGI3CthFH1kMg2dOm3KLi4GlC5cbARA64bEcUMbbWdWdE73eoc/iBB9PiTMqH0dNS2Q== 27 | dependencies: 28 | magic-string "^0.25.0" 29 | 30 | "@types/estree@0.0.39": 31 | version "0.0.39" 32 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" 33 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== 34 | 35 | "@types/node@*": 36 | version "12.12.3" 37 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.3.tgz#ebfe83507ac506bc3486314a8aa395be66af8d23" 38 | integrity sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw== 39 | 40 | "@types/resolve@0.0.8": 41 | version "0.0.8" 42 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" 43 | integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== 44 | dependencies: 45 | "@types/node" "*" 46 | 47 | acorn-dynamic-import@^4.0.0: 48 | version "4.0.0" 49 | resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" 50 | integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== 51 | 52 | acorn-jsx@^5.2.0: 53 | version "5.2.0" 54 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" 55 | integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== 56 | 57 | acorn@^6.4.1: 58 | version "6.4.1" 59 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" 60 | integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== 61 | 62 | ansi-regex@^5.0.0: 63 | version "5.0.1" 64 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 65 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 66 | 67 | ansi-styles@^3.2.1: 68 | version "3.2.1" 69 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 70 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 71 | dependencies: 72 | color-convert "^1.9.0" 73 | 74 | ansi-styles@^4.0.0: 75 | version "4.3.0" 76 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 77 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 78 | dependencies: 79 | color-convert "^2.0.1" 80 | 81 | array-union@^1.0.1: 82 | version "1.0.2" 83 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 84 | integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= 85 | dependencies: 86 | array-uniq "^1.0.1" 87 | 88 | array-uniq@^1.0.1: 89 | version "1.0.3" 90 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 91 | integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= 92 | 93 | async@^3.2.4: 94 | version "3.2.4" 95 | resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" 96 | integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== 97 | 98 | balanced-match@^1.0.0: 99 | version "1.0.0" 100 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 101 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 102 | 103 | brace-expansion@^1.1.7: 104 | version "1.1.11" 105 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 106 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 107 | dependencies: 108 | balanced-match "^1.0.0" 109 | concat-map "0.0.1" 110 | 111 | buble@^0.20.0: 112 | version "0.20.0" 113 | resolved "https://registry.yarnpkg.com/buble/-/buble-0.20.0.tgz#a143979a8d968b7f76b57f38f2e7ce7cfe938d1f" 114 | integrity sha512-/1gnaMQE8xvd5qsNBl+iTuyjJ9XxeaVxAMF86dQ4EyxFJOZtsgOS8Ra+7WHgZTam5IFDtt4BguN0sH0tVTKrOw== 115 | dependencies: 116 | acorn "^6.4.1" 117 | acorn-dynamic-import "^4.0.0" 118 | acorn-jsx "^5.2.0" 119 | chalk "^2.4.2" 120 | magic-string "^0.25.7" 121 | minimist "^1.2.5" 122 | regexpu-core "4.5.4" 123 | 124 | builtin-modules@^3.1.0: 125 | version "3.1.0" 126 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" 127 | integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== 128 | 129 | chalk@^2.4.2: 130 | version "2.4.2" 131 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 132 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 133 | dependencies: 134 | ansi-styles "^3.2.1" 135 | escape-string-regexp "^1.0.5" 136 | supports-color "^5.3.0" 137 | 138 | cliui@^7.0.2: 139 | version "7.0.4" 140 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 141 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 142 | dependencies: 143 | string-width "^4.2.0" 144 | strip-ansi "^6.0.0" 145 | wrap-ansi "^7.0.0" 146 | 147 | color-convert@^1.9.0: 148 | version "1.9.3" 149 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 150 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 151 | dependencies: 152 | color-name "1.1.3" 153 | 154 | color-convert@^2.0.1: 155 | version "2.0.1" 156 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 157 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 158 | dependencies: 159 | color-name "~1.1.4" 160 | 161 | color-name@1.1.3: 162 | version "1.1.3" 163 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 164 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 165 | 166 | color-name@~1.1.4: 167 | version "1.1.4" 168 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 169 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 170 | 171 | commander@^2.18.0: 172 | version "2.20.3" 173 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 174 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 175 | 176 | commondir@^1.0.1: 177 | version "1.0.1" 178 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" 179 | integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== 180 | 181 | concat-map@0.0.1: 182 | version "0.0.1" 183 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 184 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 185 | 186 | email-addresses@^5.0.0: 187 | version "5.0.0" 188 | resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" 189 | integrity sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw== 190 | 191 | emoji-regex@^8.0.0: 192 | version "8.0.0" 193 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 194 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 195 | 196 | escalade@^3.1.1: 197 | version "3.1.1" 198 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 199 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 200 | 201 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 202 | version "1.0.5" 203 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 204 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 205 | 206 | estree-walker@^0.6.1: 207 | version "0.6.1" 208 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" 209 | integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== 210 | 211 | estree-walker@^1.0.1: 212 | version "1.0.1" 213 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" 214 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== 215 | 216 | filename-reserved-regex@^2.0.0: 217 | version "2.0.0" 218 | resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" 219 | integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== 220 | 221 | filenamify@^4.3.0: 222 | version "4.3.0" 223 | resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" 224 | integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== 225 | dependencies: 226 | filename-reserved-regex "^2.0.0" 227 | strip-outer "^1.0.1" 228 | trim-repeated "^1.0.0" 229 | 230 | find-cache-dir@^3.3.1: 231 | version "3.3.2" 232 | resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" 233 | integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== 234 | dependencies: 235 | commondir "^1.0.1" 236 | make-dir "^3.0.2" 237 | pkg-dir "^4.1.0" 238 | 239 | find-up@^4.0.0: 240 | version "4.1.0" 241 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 242 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 243 | dependencies: 244 | locate-path "^5.0.0" 245 | path-exists "^4.0.0" 246 | 247 | fs-extra@^8.1.0: 248 | version "8.1.0" 249 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 250 | integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== 251 | dependencies: 252 | graceful-fs "^4.2.0" 253 | jsonfile "^4.0.0" 254 | universalify "^0.1.0" 255 | 256 | fs.realpath@^1.0.0: 257 | version "1.0.0" 258 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 259 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 260 | 261 | fsevents@~2.3.1: 262 | version "2.3.2" 263 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 264 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 265 | 266 | get-caller-file@^2.0.5: 267 | version "2.0.5" 268 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 269 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 270 | 271 | gh-pages@^5.0.0: 272 | version "5.0.0" 273 | resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-5.0.0.tgz#e0893272a0e33f0453e53a3c017c33b91ddd6394" 274 | integrity sha512-Nqp1SjkPIB94Xw/3yYNTUL+G2dxlhjvv1zeN/4kMC1jfViTEqhtVz/Ba1zSXHuvXCN9ADNS1dN4r5/J/nZWEQQ== 275 | dependencies: 276 | async "^3.2.4" 277 | commander "^2.18.0" 278 | email-addresses "^5.0.0" 279 | filenamify "^4.3.0" 280 | find-cache-dir "^3.3.1" 281 | fs-extra "^8.1.0" 282 | globby "^6.1.0" 283 | 284 | glob@^7.0.3: 285 | version "7.1.6" 286 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 287 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 288 | dependencies: 289 | fs.realpath "^1.0.0" 290 | inflight "^1.0.4" 291 | inherits "2" 292 | minimatch "^3.0.4" 293 | once "^1.3.0" 294 | path-is-absolute "^1.0.0" 295 | 296 | globby@^6.1.0: 297 | version "6.1.0" 298 | resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" 299 | integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= 300 | dependencies: 301 | array-union "^1.0.1" 302 | glob "^7.0.3" 303 | object-assign "^4.0.1" 304 | pify "^2.0.0" 305 | pinkie-promise "^2.0.0" 306 | 307 | graceful-fs@^4.1.6, graceful-fs@^4.2.0: 308 | version "4.2.3" 309 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 310 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 311 | 312 | has-flag@^3.0.0: 313 | version "3.0.0" 314 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 315 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 316 | 317 | inflight@^1.0.4: 318 | version "1.0.6" 319 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 320 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 321 | dependencies: 322 | once "^1.3.0" 323 | wrappy "1" 324 | 325 | inherits@2: 326 | version "2.0.4" 327 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 328 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 329 | 330 | is-docker@^2.0.0: 331 | version "2.2.1" 332 | resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" 333 | integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== 334 | 335 | is-fullwidth-code-point@^3.0.0: 336 | version "3.0.0" 337 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 338 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 339 | 340 | is-module@^1.0.0: 341 | version "1.0.0" 342 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" 343 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= 344 | 345 | is-reference@^1.1.2: 346 | version "1.1.4" 347 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" 348 | integrity sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw== 349 | dependencies: 350 | "@types/estree" "0.0.39" 351 | 352 | is-wsl@^2.1.1: 353 | version "2.2.0" 354 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" 355 | integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== 356 | dependencies: 357 | is-docker "^2.0.0" 358 | 359 | jsesc@~0.5.0: 360 | version "0.5.0" 361 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" 362 | integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= 363 | 364 | jsonfile@^4.0.0: 365 | version "4.0.0" 366 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 367 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 368 | optionalDependencies: 369 | graceful-fs "^4.1.6" 370 | 371 | locate-path@^5.0.0: 372 | version "5.0.0" 373 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 374 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 375 | dependencies: 376 | p-locate "^4.1.0" 377 | 378 | magic-string@^0.25.0, magic-string@^0.25.7: 379 | version "0.25.7" 380 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" 381 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== 382 | dependencies: 383 | sourcemap-codec "^1.4.4" 384 | 385 | magic-string@^0.25.2: 386 | version "0.25.4" 387 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.4.tgz#325b8a0a79fc423db109b77fd5a19183b7ba5143" 388 | integrity sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw== 389 | dependencies: 390 | sourcemap-codec "^1.4.4" 391 | 392 | make-dir@^3.0.2: 393 | version "3.1.0" 394 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 395 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 396 | dependencies: 397 | semver "^6.0.0" 398 | 399 | minimatch@^3.0.4: 400 | version "3.0.4" 401 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 402 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 403 | dependencies: 404 | brace-expansion "^1.1.7" 405 | 406 | minimist@^1.2.5: 407 | version "1.2.5" 408 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 409 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 410 | 411 | nanoid@^3.1.22: 412 | version "3.1.22" 413 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" 414 | integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== 415 | 416 | object-assign@^4.0.1: 417 | version "4.1.1" 418 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 419 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 420 | 421 | once@^1.3.0: 422 | version "1.4.0" 423 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 424 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 425 | dependencies: 426 | wrappy "1" 427 | 428 | open@^7.4.2: 429 | version "7.4.2" 430 | resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" 431 | integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== 432 | dependencies: 433 | is-docker "^2.0.0" 434 | is-wsl "^2.1.1" 435 | 436 | p-limit@^2.2.0: 437 | version "2.3.0" 438 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 439 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 440 | dependencies: 441 | p-try "^2.0.0" 442 | 443 | p-locate@^4.1.0: 444 | version "4.1.0" 445 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 446 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 447 | dependencies: 448 | p-limit "^2.2.0" 449 | 450 | p-try@^2.0.0: 451 | version "2.2.0" 452 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 453 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 454 | 455 | path-exists@^4.0.0: 456 | version "4.0.0" 457 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 458 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 459 | 460 | path-is-absolute@^1.0.0: 461 | version "1.0.1" 462 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 463 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 464 | 465 | path-parse@^1.0.6: 466 | version "1.0.7" 467 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 468 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 469 | 470 | picomatch@^2.2.2: 471 | version "2.2.2" 472 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 473 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 474 | 475 | pify@^2.0.0: 476 | version "2.3.0" 477 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 478 | integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= 479 | 480 | pinkie-promise@^2.0.0: 481 | version "2.0.1" 482 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 483 | integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= 484 | dependencies: 485 | pinkie "^2.0.0" 486 | 487 | pinkie@^2.0.0: 488 | version "2.0.4" 489 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 490 | integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= 491 | 492 | pkg-dir@^4.1.0: 493 | version "4.2.0" 494 | resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" 495 | integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== 496 | dependencies: 497 | find-up "^4.0.0" 498 | 499 | preact@^10.4.1: 500 | version "10.4.1" 501 | resolved "https://registry.yarnpkg.com/preact/-/preact-10.4.1.tgz#9b3ba020547673a231c6cf16f0fbaef0e8863431" 502 | integrity sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q== 503 | 504 | regenerate-unicode-properties@^8.0.2: 505 | version "8.2.0" 506 | resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" 507 | integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== 508 | dependencies: 509 | regenerate "^1.4.0" 510 | 511 | regenerate@^1.4.0: 512 | version "1.4.0" 513 | resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" 514 | integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== 515 | 516 | regexpu-core@4.5.4: 517 | version "4.5.4" 518 | resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae" 519 | integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ== 520 | dependencies: 521 | regenerate "^1.4.0" 522 | regenerate-unicode-properties "^8.0.2" 523 | regjsgen "^0.5.0" 524 | regjsparser "^0.6.0" 525 | unicode-match-property-ecmascript "^1.0.4" 526 | unicode-match-property-value-ecmascript "^1.1.0" 527 | 528 | regjsgen@^0.5.0: 529 | version "0.5.1" 530 | resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" 531 | integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== 532 | 533 | regjsparser@^0.6.0: 534 | version "0.6.4" 535 | resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" 536 | integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== 537 | dependencies: 538 | jsesc "~0.5.0" 539 | 540 | require-directory@^2.1.1: 541 | version "2.1.1" 542 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 543 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 544 | 545 | resolve@^1.11.0, resolve@^1.11.1: 546 | version "1.12.0" 547 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" 548 | integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== 549 | dependencies: 550 | path-parse "^1.0.6" 551 | 552 | rollup-plugin-commonjs@^10.1.0: 553 | version "10.1.0" 554 | resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb" 555 | integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q== 556 | dependencies: 557 | estree-walker "^0.6.1" 558 | is-reference "^1.1.2" 559 | magic-string "^0.25.2" 560 | resolve "^1.11.0" 561 | rollup-pluginutils "^2.8.1" 562 | 563 | rollup-plugin-node-resolve@^5.2.0: 564 | version "5.2.0" 565 | resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz#730f93d10ed202473b1fb54a5997a7db8c6d8523" 566 | integrity sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw== 567 | dependencies: 568 | "@types/resolve" "0.0.8" 569 | builtin-modules "^3.1.0" 570 | is-module "^1.0.0" 571 | resolve "^1.11.1" 572 | rollup-pluginutils "^2.8.1" 573 | 574 | rollup-plugin-visualizer@^5.4.1: 575 | version "5.4.1" 576 | resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.4.1.tgz#ec25c88d1ebab6d523e938c0084ca14cfe63b02e" 577 | integrity sha512-mwrUIfOamkCw3dCtLvgnn/H0rvNSDA1RAe0sO9uHBpmdf86j/xOX/2yeCrVh2Ia/gCGLG846JB00MW0chq8CHQ== 578 | dependencies: 579 | nanoid "^3.1.22" 580 | open "^7.4.2" 581 | source-map "^0.7.3" 582 | yargs "^16.2.0" 583 | 584 | rollup-pluginutils@^2.8.1: 585 | version "2.8.2" 586 | resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" 587 | integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== 588 | dependencies: 589 | estree-walker "^0.6.1" 590 | 591 | rollup@^2.45.2: 592 | version "2.45.2" 593 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.45.2.tgz#8fb85917c9f35605720e92328f3ccbfba6f78b48" 594 | integrity sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ== 595 | optionalDependencies: 596 | fsevents "~2.3.1" 597 | 598 | semver@^6.0.0: 599 | version "6.3.0" 600 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 601 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 602 | 603 | source-map@^0.7.3: 604 | version "0.7.3" 605 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" 606 | integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== 607 | 608 | sourcemap-codec@^1.4.4: 609 | version "1.4.6" 610 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" 611 | integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== 612 | 613 | string-width@^4.1.0, string-width@^4.2.0: 614 | version "4.2.2" 615 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" 616 | integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== 617 | dependencies: 618 | emoji-regex "^8.0.0" 619 | is-fullwidth-code-point "^3.0.0" 620 | strip-ansi "^6.0.0" 621 | 622 | strip-ansi@^6.0.0: 623 | version "6.0.0" 624 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" 625 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== 626 | dependencies: 627 | ansi-regex "^5.0.0" 628 | 629 | strip-outer@^1.0.1: 630 | version "1.0.1" 631 | resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" 632 | integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== 633 | dependencies: 634 | escape-string-regexp "^1.0.2" 635 | 636 | supports-color@^5.3.0: 637 | version "5.5.0" 638 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 639 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 640 | dependencies: 641 | has-flag "^3.0.0" 642 | 643 | trim-repeated@^1.0.0: 644 | version "1.0.0" 645 | resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" 646 | integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE= 647 | dependencies: 648 | escape-string-regexp "^1.0.2" 649 | 650 | unicode-canonical-property-names-ecmascript@^1.0.4: 651 | version "1.0.4" 652 | resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" 653 | integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== 654 | 655 | unicode-match-property-ecmascript@^1.0.4: 656 | version "1.0.4" 657 | resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" 658 | integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== 659 | dependencies: 660 | unicode-canonical-property-names-ecmascript "^1.0.4" 661 | unicode-property-aliases-ecmascript "^1.0.4" 662 | 663 | unicode-match-property-value-ecmascript@^1.1.0: 664 | version "1.2.0" 665 | resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" 666 | integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== 667 | 668 | unicode-property-aliases-ecmascript@^1.0.4: 669 | version "1.1.0" 670 | resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" 671 | integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== 672 | 673 | universalify@^0.1.0: 674 | version "0.1.2" 675 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 676 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 677 | 678 | wrap-ansi@^7.0.0: 679 | version "7.0.0" 680 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 681 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 682 | dependencies: 683 | ansi-styles "^4.0.0" 684 | string-width "^4.1.0" 685 | strip-ansi "^6.0.0" 686 | 687 | wrappy@1: 688 | version "1.0.2" 689 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 690 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 691 | 692 | y18n@^5.0.5: 693 | version "5.0.8" 694 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 695 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 696 | 697 | yargs-parser@^20.2.2: 698 | version "20.2.7" 699 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" 700 | integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== 701 | 702 | yargs@^16.2.0: 703 | version "16.2.0" 704 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 705 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 706 | dependencies: 707 | cliui "^7.0.2" 708 | escalade "^3.1.1" 709 | get-caller-file "^2.0.5" 710 | require-directory "^2.1.1" 711 | string-width "^4.2.0" 712 | y18n "^5.0.5" 713 | yargs-parser "^20.2.2" 714 | --------------------------------------------------------------------------------