@percy/report 0.0.7 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  require('dotenv').config()
3
3
  const { Command } = require('commander');
4
- const { ar } = require('date-fns/locale');
5
4
  const { Generate,Summary } = require('../src');
6
5
  const program = new Command();
7
6
  const {endOfDay,startOfDay} = require('date-fns')
@@ -15,7 +14,6 @@ program.command('generate')
15
14
  .description('Genetate Report')
16
15
  .argument('<buildId>')
17
16
  .option('--percy-token <percyToken>',"Percy ReadOnly or FullAccess Token",process.env.PERCY_TOKEN)
18
- .option('--diff-threshold <diffThreshold>',"Percy Diff Percentage Threshold to highlight")
19
17
  .option('--download-path <downloadPath>',"Directory path where to generate the report","./Reports")
20
18
  .option('--download-images',"If present Images will be downloaded",false)
21
19
  .action(async (args,options)=>{
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@percy/report",
3
3
  "description": "Package to generate a build report and project summary report for Percy, BrowserStack's visual testing platform",
4
- "version": "0.0.7",
4
+ "version": "1.0.1",
5
5
  "main": "src/index.js",
6
6
  "license": "MIT",
7
7
  "author": "BrowserStack Pvt Ltd",
package/src/generate.js CHANGED
@@ -92,9 +92,9 @@ module.exports.Generate = async function (config) {
92
92
  Object.assign(comparison, comp.attributes, { images }, isApp?{ device:compTag }:{browser:compTag});
93
93
  if (isApp) {
94
94
  let device = getComparisonDevice(comp)
95
- compTag = device.name
96
- if(!report.devices.includes(device.name)){
97
- report.devices.push(device.name)
95
+ compTag = `${device.name} (${device['os-name']} ${device['os-version']})`
96
+ if(!report.devices.includes(compTag)){
97
+ report.devices.push(compTag)
98
98
  }
99
99
  } else {
100
100
  let browser = getComparisonBrowser(comp)
@@ -3,35 +3,21 @@ const ejs = require('ejs');
3
3
  const path = require('path')
4
4
 
5
5
  function HtmlReportGenerator(config, jsonReport, isApp) {
6
- if (isApp) {
7
- let {
8
- buildId,
9
- downloadPath
10
- } = config
11
- let template_path = path.resolve(__dirname, 'template/app-report.html')
12
- const template = fs.readFileSync(template_path, {
13
- encoding: 'utf-8'
14
- }).toString()
15
- let htmlReport = ejs.render(template, {
16
- buildId,
17
- ...jsonReport
18
- })
19
- fs.writeFileSync(`${downloadPath}/${buildId}/app-report.html`, htmlReport)
20
- } else {
21
- let {
22
- buildId,
23
- downloadPath
24
- } = config
25
- let template_path = path.resolve(__dirname, 'template/report.html')
26
- const template = fs.readFileSync(template_path, {
27
- encoding: 'utf-8'
28
- }).toString()
29
- let htmlReport = ejs.render(template, {
6
+ let {
7
+ buildId,
8
+ downloadPath
9
+ } = config
10
+ let template_path = path.resolve(__dirname, 'template/report.html')
11
+ const template = fs.readFileSync(template_path, {
12
+ encoding: 'utf-8'
13
+ }).toString()
14
+ let htmlReport = ejs.render(template, {
15
+ data: {
30
16
  buildId,
31
17
  ...jsonReport
32
- })
33
- fs.writeFileSync(`${downloadPath}/${buildId}/report.html`, htmlReport)
34
- }
18
+ }
19
+ })
20
+ fs.writeFileSync(`${downloadPath}/${buildId}/report.html`, htmlReport)
35
21
  }
36
22
 
37
23
  function HtmlSummary(summary, filename, isApp) {
@@ -1,221 +1,511 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html>
3
3
 
4
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>Document</title>
9
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
10
- integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
5
+ <title>Percy Report</title>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
11
8
  <style>
12
- img {
9
+ :root {
10
+ --background-color: #fefefe;
11
+ --color-highlight-light: #f9f9f9;
12
+ --color-faint: #fafafa;
13
+ --color-secondary: #9c9ca1;
14
+ --color-primary: rgb(130, 69, 165);
15
+ --border-color: rgba(0, 0, 0, 0.1);
16
+ --color-backdrop: rgba(0, 0, 0, 0.7);
17
+ --color-diff: rgb(255, 0, 0, 0.2);
18
+ }
19
+
20
+ body {
21
+ background-color: var(--background-color);
22
+ margin: 0;
23
+ padding: 0;
24
+ font-size: 14px;
25
+ }
26
+
27
+ nav {
28
+ z-index: 999;
29
+ }
30
+
31
+ input[type=text] {
32
+ border: 1px solid var(--border-color);
33
+ padding: 5px;
34
+ border-radius: 10px;
35
+ }
36
+
37
+ input[type=range] {
38
+ -webkit-appearance: none;
39
+ /* Override default CSS styles */
40
+ appearance: none;
13
41
  width: 100%;
14
- object-fit: contain;
15
- border: 1px solid gray;
42
+ height: 10px;
43
+ background: var(--color-highlight-light);
44
+ outline: none;
45
+ border-radius: 20px;
16
46
  }
17
47
 
18
- .comparison-container {
19
- position: relative;
20
- cursor: pointer;
48
+ input[type=range]:hover {
49
+ opacity: 1;
50
+ }
51
+
52
+ input[type=range]::-webkit-slider-thumb {
53
+ -webkit-appearance: none;
54
+ appearance: none;
55
+ background-color: var(--color-primary);
56
+ width: 15px;
57
+ height: 15px;
58
+ border-radius: 50%;
59
+ }
60
+
61
+ input[type=range]::-moz-range-thumb {
62
+ background-color: var(--color-primary);
63
+ width: 15px;
64
+ height: 15px;
65
+ border-radius: 50%;
21
66
  }
22
67
 
23
- .overlay {
68
+ input[type=range]::before {
69
+ display: block;
24
70
  position: absolute;
25
- top: 0;
26
71
  left: 0;
27
- right: 0;
28
- bottom: 0;
29
- z-index: 50;
30
- width: 100%;
72
+ width: 80%;
31
73
  height: 100%;
32
- background-color: rgba(0, 0, 0, 0.7);
74
+ background-color: var(--color-primary);
33
75
  }
34
76
 
35
- .browser-icon{
36
- width: 30px;
37
- height: 30px;
77
+ .app {
78
+ display: flex;
79
+ flex-direction: row;
80
+ height: 100vh;
81
+ overflow: auto;
38
82
  }
39
- .color-red{
40
- background-color: red !important;
41
- color: white !important;
83
+
84
+ .text-secondary {
85
+ color: var(--color-secondary);
42
86
  }
43
- .color-green{
44
- background-color: green !important;
45
- color: white !important;
87
+
88
+ .filter-panel {
89
+ position: relative;
90
+ flex: 0.2;
91
+ border-right: solid 1px var(--border-color);
92
+ overflow-y: auto;
93
+ max-height: 100%;
46
94
  }
47
- select{
48
- max-width: 100%;
95
+
96
+ .bg-faint {
97
+ background-color: var(--color-faint) !important;
49
98
  }
50
- </style>
51
- <script type="text/javascript">
52
- function onClickOverlay(e, element) {
53
- let hidden = e.currentTarget.querySelector(".overlay").classList.contains('d-none')
54
- if(hidden){
55
- e.currentTarget.querySelector(".overlay").classList.remove('d-none')
56
- }else{
57
- e.currentTarget.querySelector(".overlay").classList.add('d-none')
58
- }
99
+
100
+ .bg-secondary {
101
+ background-color: var(--color-secondary) !important;
59
102
  }
60
- const snapshots = JSON.parse('<%- JSON.stringify(details.map(snp=>snp.name)) %>')
61
- const widths = JSON.parse('<%- JSON.stringify(widths) %>')
62
- const browsers = JSON.parse('<%- JSON.stringify(browsers) %>')
63
- var snapshotSelect, widthSelect,browserSelect,images
64
- window.addEventListener('DOMContentLoaded', () => {
65
- snapshotSelect = document.getElementById('select-snapshot')
66
- widthSelect = document.getElementById('select-width')
67
- browserSelect = document.getElementById('select-browser')
68
- images = document.getElementsByTagName('img')
69
- tables = document.getElementsByTagName('table')
70
- snapshotSelect.addEventListener('change', ApplyFilter)
71
- widthSelect.addEventListener('change', ApplyFilter)
72
- browserSelect.addEventListener('change', ApplyFilter)
73
- ApplyFilter()
74
- })
75
-
76
- function ApplyFilter() {
77
- const shouldEnable = (dataset) => {
78
- if(dataset.browser == "Chrome on Android" || dataset.browser == "Safari on iPhone" ){
79
- return (snapshotSelect.value == "All" || snapshotSelect.value == dataset.name) && browserSelect.value == dataset.browser
80
- }
81
- return (snapshotSelect.value == "All" || snapshotSelect.value == dataset.name) && widthSelect.value == dataset.width && browserSelect.value == dataset.browser
103
+
104
+ .bg-highlight {
105
+ background-color: var(--color-highlight-light) !important;
106
+ }
107
+
108
+ .bg-backdrop {
109
+ background-color: var(--color-backdrop);
110
+ }
111
+
112
+ .bg-diff {
113
+ background-color: var(--color-diff);
114
+ }
115
+
116
+ .bg-primary {
117
+ background-color: var(--color-primary);
118
+ }
119
+
120
+ .border-primary {
121
+ border: 1px solid var(--color-primary);
122
+ }
123
+
124
+ .border-secondary {
125
+ border: 1px solid var(--color-secondary);
126
+ }
127
+
128
+ #thumbnails {
129
+ flex: 0.3;
130
+ overflow-y: auto;
131
+ max-height: 100%;
132
+ }
133
+
134
+ #comparisons {
135
+ flex: 1;
136
+ overflow-x: auto;
137
+ max-height: 100%;
138
+ }
139
+
140
+ #split {
141
+ width: 5px;
142
+ border-right: solid 1px var(--border-color);
143
+ cursor: ew-resize;
144
+ }
145
+
146
+ .skeleton {
147
+ animation: skeleton-loading 1s linear infinite alternate;
148
+ }
149
+
150
+ img {
151
+ width: 100%;
152
+ height: 100%;
153
+ object-fit: contain;
154
+ transition: opacity ease 0.3s;
155
+ }
156
+
157
+ @keyframes skeleton-loading {
158
+ 0% {
159
+ background-color: hsl(200, 20%, 80%);
82
160
  }
83
- for (let i = 0; i < images.length; i++) {
84
- let image = images.item(i)
85
- image.hidden = !shouldEnable(image.dataset)
161
+
162
+ 100% {
163
+ background-color: hsl(200, 20%, 95%);
86
164
  }
165
+ }
166
+
167
+ .artboard {
168
+ background: url(https://percy.io/static/images/artboard-bg-c5ce8844100cad58983978e3d2794e06.svg);
169
+ }
170
+ </style>
171
+ </head>
172
+
173
+ <body>
174
+ <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
175
+ <!-- Load React. -->
176
+ <!-- Note: when deploying, replace "development.js" with "production.min.js". -->
177
+ <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
178
+ <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
87
179
 
88
- for(let i = 0; i < tables.length; i++){
89
- let table = tables.item(i)
90
- if(table.id !== "build-details"){
91
- table.hidden = !shouldEnable(table.dataset)
180
+ <main id="root"></main>
181
+
182
+ <script type="text/babel">
183
+ let BuildData = JSON.parse(`<%- JSON.stringify(data) %>`);
184
+
185
+ const PercyLogo = () => <a className="text-secondary" href="https://percy.io" target="_blank" ><svg width="31" height="24" fill="none" role="img"><path fill-rule="evenodd" clip-rule="evenodd" d="M30.111 5.456c-.173-.568-.63-1.127-1.173-.9-.319.134-.816.29-1.438.319-1.071-1.006-4.223-3-6.298-4.129 0 0 .253.74.603 2.124 0 0-2.234-1.745-4.762-2.87 0 0 .712 1.46.77 1.932 0 0-2.319-.807-5.572-1.113 0 0 1.562 1.09 2.168 1.923 0 0-2.299-.117-5.733 0 0 0 1.952.792 2.814 1.519 0 0-4.076.413-6.378 1.076 0 0 2.594.915 3.315 1.596 0 0-3.31.942-6.645 2.868 0 0 1.853.324 3.304.958 0 0-1.848 1.291-4.586 4.815 0 0 2.023-.386 3.412-.278 0 0-1.76 2.055-2.677 5.198 0 0 1.018-.648 2.003-.883 0 0 .106 3.748 2.155 4.349l.002-.003c.1.032.196.043.28.043.1 0 .204-.015.31-.046.65-.186 1.143-.828 1.713-1.57.186-.244.378-.494.582-.737.227-.322.524-.658.882-.953.724-.597 1.727-1.05 2.948-.868 1.375.116 2.225 1.486 2.907 2.587.356.573.825 1.587 1.612 1.587.87 0 1.176-1.042 1.563-2.328.363-1.208.911-2.371 1.612-3.415 1.357-2.02 3.094-3.118 5.175-4.2 1.995-1.037 3.88-2.017 4.824-3.504.473-.743.707-1.655.697-2.71-.01-.948-.217-1.821-.389-2.387zm-9.848-.581l-3.22-1.5 4.762 1.181-1.541.319zm-7.318.462l3.98 1.018 1.735-.576-5.715-.442zm7.846 1.995l.849-.643-3.827.811 2.978-.168zm-6.382 0l-1.36.912-3.773-.36 5.133-.552zm4.25 2.279l1.382-.772-5.133.552 3.752.22zm-3.802.857l-.962 1.582-4.816 1.178 5.778-2.76zm-6.017.974l-3.728 1.642L9.688 9.8l-.848 1.641zM7.55 13.41l-.146 1.642L5 17.25l2.55-3.84z" fill="currentColor"></path><path d="M20.249 23.593c-.418-.007-.863-.365-1.35-1.091.287-1.644 1.423-3.743 2.789-5.151-.349 1.307-.395 2.62-.433 3.676-.024.683-.045 1.272-.142 1.7-.13.575-.416.866-.85.866h-.014z" fill="currentColor"></path><path d="M8.676 22.406c.354.802.733 1.192 1.156 1.192a.66.66 0 0 0 .103-.008c.575-.091 1.107-1.205 1.552-2.282.116-.28.224-.56.321-.818-1.272.015-2.509 1.038-3.132 1.916z" fill="currentColor"></path></svg></a>;
186
+ const DiffIcon = () => <svg width="16" height="16" fill="none" role="img" aria-label="diff on"><path d="M8.13 4.675l-5.134.444a1.157 1.157 0 0 0-1.06 1.25l.661 7.482A1.163 1.163 0 0 0 3.861 14.9l5.135-.445a1.158 1.158 0 0 0 1.06-1.25l-.661-7.481a1.163 1.163 0 0 0-1.264-1.05z" fill="#999" fill-opacity=".2" stroke="#999" stroke-width="1.5"></path><path d="M7.144 1.087l5.86.507a1.158 1.158 0 0 1 1.06 1.25l-.725 8.2a1.163 1.163 0 0 1-1.264 1.049l-5.86-.507a1.158 1.158 0 0 1-1.06-1.25l.725-8.2a1.163 1.163 0 0 1 1.264-1.05z" fill="currentColor" fill-opacity=".4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-dasharray="1.5 2.25"></path></svg>
187
+ const EmptyPlaceholder = () => <svg class="mx-auto mb-3 opacity-50" width="96" height="105" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M50.847 34.316l-34.876 3.06c-4.402.386-7.659 4.277-7.274 8.69l4.445 50.943c.385 4.413 4.266 7.678 8.669 7.292l34.875-3.06c4.403-.386 7.66-4.277 7.275-8.69l-4.445-50.943c-.386-4.413-4.267-7.678-8.669-7.292z" fill="#1D8DDB" fill-opacity=".1"></path><path d="M44.847 28.317L9.971 31.376c-4.402.386-7.659 4.277-7.274 8.69L7.142 91.01c.385 4.413 4.266 7.678 8.669 7.292l34.875-3.06c4.403-.386 7.66-4.277 7.275-8.69l-4.445-50.943c-.386-4.413-4.267-7.678-8.669-7.291z" stroke="#1D8DDB" stroke-width="5.05"></path><path d="M88.028 14.185l-39.85-3.487c-4.403-.386-8.285 2.88-8.671 7.293l-4.895 55.936c-.386 4.413 2.87 8.303 7.272 8.688l39.85 3.488c4.403.385 8.285-2.88 8.671-7.294L95.3 22.873c.386-4.413-2.87-8.303-7.272-8.688z" fill="#1D8DDB" fill-opacity=".1"></path><path d="M81.822 7.185l-39.85-3.487c-4.403-.386-8.285 2.88-8.671 7.293l-4.895 55.936c-.386 4.413 2.87 8.303 7.272 8.688l39.85 3.488c4.403.385 8.285-2.88 8.671-7.294l4.895-55.936c.386-4.413-2.87-8.303-7.272-8.688z" stroke="#1D8DDB" stroke-width="5.05" stroke-linecap="round" stroke-dasharray="0.44 0.44 8.69 10.99"></path></svg>
188
+ const Empty = (props) => (
189
+ <div className="artboard w-full h-full flex items-center justify-center flex-col gap-4" >
190
+ <EmptyPlaceholder />
191
+ <h4 className="text-xl" >{props.children}</h4>
192
+ </div>
193
+ )
194
+ const LayoutDiff = () => <svg class="layout-diff-icon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none"><path d="M2.25 3.75v10.5a1.5 1.5 0 0 0 1.5 1.5h4.5V2.25h-4.5a1.5 1.5 0 0 0-1.5 1.5zm4.5 10.5h-3V3.75h3v10.5z" fill="#64748B"></path><path d="M14.25 2.25h-4.5v6h6v-4.5c0-.825-.675-1.5-1.5-1.5zm0 4.5h-3v-3h3v3z" fill="#64748B"></path><path d="M9.75 15.75h4.5c.825 0 1.5-.675 1.5-1.5v-4.5h-6v6zm1.5-4.5h3v3h-3v-3z" fill="#64748B"></path></svg>
195
+ const SnapshotData = BuildData.details.reduce((map, current) => {
196
+ if (current.comparisons) {
197
+ let diffRatios = current.comparisons.map((c) => Number(c['diff-percentage']))
198
+ current['diff-ratios'] = diffRatios;
199
+ current['max-diff'] = Math.max(...diffRatios);
200
+ const isChanged = diffRatios.some((d) => d > 0)
201
+ if (isChanged) {
202
+ map.changed.push(current)
203
+ } else {
204
+ map.unchanged.push(current)
92
205
  }
93
206
  }
207
+ return map
208
+ }, {
209
+ unchanged: [],
210
+ changed: []
211
+ });
212
+
213
+ const Browsers = {
214
+ "Safari": <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="38.698" cy="38.568" r="38" fill="#BABABA"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#x)"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#w)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="#309CFF"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#cc)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#dd)"></circle><path fill-rule="evenodd" clip-rule="evenodd" d="M37.168 35.58L19.705 58.046 39.668 38.08l-2.501-2.501z" fill="#fff"></path><path d="M19.704 58.046L42.17 40.582l-2.5-2.501-19.966 19.965z" fill="#fff"></path><path d="M42.166 40.59l-2.629-2.641-19.816 20.096L42.166 40.59z" fill="#E5E5E5"></path><path d="M37.19 35.59l22.254-17.298-17.362 22.336-.184.047-4.809-4.809.101-.275z" fill="#EA3939"></path><path d="M42.159 40.574l-2.641-2.63 20.096-19.816-17.455 22.446z" fill="#AD2C2C"></path><rect x="38.698" y="8.255" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="38.698" y="63.584" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="69.013" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 69.013 37.93)" fill="#E8F4FF"></rect><rect x="13.683" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 13.683 37.93)" fill="#E8F4FF"></rect><rect x="60.661" y="59.733" width="1.64" height="5.66" rx=".82" transform="rotate(135 60.66 59.733)" fill="#E8F4FF"></rect><rect x="21.537" y="20.609" width="1.64" height="5.66" rx=".82" transform="rotate(135 21.537 20.61)" fill="#E8F4FF"></rect><defs><radialGradient id="w" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(38)"><stop offset=".927" stop-color="#B6C6FF" stop-opacity="0"></stop><stop offset="1" stop-color="#CFCFD1"></stop></radialGradient><radialGradient id="cc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -10.487 32.545) scale(56.3527)"><stop offset=".594" stop-color="#60D9FF" stop-opacity=".59"></stop><stop offset="1" stop-color="#003CB1" stop-opacity=".83"></stop></radialGradient><radialGradient id="dd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(32.7068)"><stop offset=".87" stop-color="#5587E8" stop-opacity="0"></stop><stop offset="1" stop-color="#175ECA"></stop></radialGradient><linearGradient id="x" x1="12.796" y1="12.307" x2="65.319" y2="65.307" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity=".79"></stop><stop offset="1" stop-color="#fff" stop-opacity="0"></stop></linearGradient></defs></svg>,
215
+ "Firefox": <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M36.977 0c20.42 0 36.976 16.464 36.976 36.778 0 20.308-16.555 36.777-36.976 36.777S0 57.086 0 36.778C0 16.462 16.556 0 36.977 0z" transform="translate(2.465)" fill="url(#a)"></path><path d="M45.762 70.558C64.532 67.545 78.886 51.4 78.886 31.88l-.36.444c.562-3.71.464-7.015-.308-9.898-.276 2.136-.577 3.425-.88 3.943-.002-.167-.044-2.393-.757-5.5-.338-2.265-.833-4.396-1.518-6.365.167.902.266 1.655.315 2.337C72.514 9.196 65.461-.217 48.084.004c0 0 6.111.642 8.988 4.975 0 0-2.94-.7-5.164.372 2.709 1.074 5.058 2.197 7.057 3.37l.173.105c.513.306.95.62 1.413.931 3.692 2.58 7.119 6.26 6.858 11.007-.797-1.25-1.857-2.07-3.215-2.482 1.674 6.522 1.839 11.909.486 16.158-.93-2.83-1.785-4.526-2.547-5.124 1.063 8.743-.37 15.209-4.287 19.423.745-2.572 1.04-4.678.873-6.34-4.602 6.919-9.838 10.496-15.708 10.717-2.32-.02-4.51-.368-6.572-1.045-3.027-1.017-5.766-2.756-8.204-5.222 3.806.316 7.282-.34 10.342-1.893l5.011-3.266-.02-.014c.651-.246 1.263-.226 1.86.05 1.222-.166 1.65-.818 1.242-1.919-.592-.819-1.487-1.562-2.636-2.216-2.507-1.302-5.125-1.096-7.856.637-2.6 1.471-5.103 1.412-7.538-.152-1.595-1.096-3.136-2.572-4.625-4.416l-.591-.87c-.28 2.089.037 4.769.98 8.069l.02.044-.02-.04c-.946-3.3-1.26-5.986-.98-8.073v-.02c.071-1.824.83-2.83 2.277-3.055l-.614-.051.62.051c1.634.15 3.51.523 5.625 1.136.355-2.03-.111-4.156-1.39-6.336v-.04c1.986-1.843 3.749-3.184 5.255-4.04.668-.355 1.057-.895 1.186-1.623l.054-.04.02-.019.074-.071c.389-.579.258-1.044-.407-1.456a16.664 16.664 0 0 1-4.179-.297l-.02.056c-.574-.166-1.298-.674-2.19-1.532l-2.286-2.236-.685-.537v.071h-.02l.02-.09-.13-.136.184-.13c.316-1.694.836-3.15 1.578-4.4l.167-.15c.745-1.231 2.172-2.553 4.27-3.965-3.9.483-7.427 2.231-10.563 5.254-2.6-.948-5.682-.747-9.23.613l-.426.324-.032.017.463-.338.02-.02C10.835 8.71 9.33 5.78 8.605.992 5.766 3.789 4.39 8.784 4.483 15.995l-.814 1.223-.21.142-.041.04-.018.017-.04.08c-.43.673-1.025 1.688-1.774 3.05C.508 22.476.145 24.094.04 25.504l-.01.017.006.047-.035.37.062-.098c.007.326.014.655.098.949l2.303-1.883c-.836 2.106-1.39 4.34-1.672 6.708l-.066 1.084-.723-.822c0 8.405 2.683 16.175 7.208 22.56l.136.21.217.258a39.539 39.539 0 0 0 12.222 10.945c3.459 2.038 7.176 3.465 11.14 4.313l.816.181c.82.157 1.661.275 2.504.38.624.081 1.248.16 1.879.214l.838.095 1.19.008 1.295.064 1.03-.052 1.699-.083a39.852 39.852 0 0 0 2.987-.322l.6-.088zM70.704 22.77l-.017.321.012-.323.005.002z" transform="translate(0 4.897)" fill="url(#b)"></path><defs><linearGradient id="a" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 73.5555 -73.9534 0 73.953 0)"><stop stop-color="#0093F7"></stop><stop offset="1" stop-color="#372893"></stop></linearGradient><linearGradient id="b" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(78.8861 0 0 88.4522 0 -9.471)"><stop stop-color="#FE3229"></stop><stop offset="1" stop-color="#FFD71F"></stop></linearGradient></defs></svg>,
216
+ "Chrome": <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M38 57.447c-3.86 0-7.372-1.04-10.54-3.117-3.169-2.078-5.544-4.75-7.125-8.015L4.75 19C1.484 24.84 0 31.371 0 38c0 9.5 3.092 17.789 9.277 24.864 6.184 7.075 13.88 11.305 23.082 12.692l11.023-19.074c-1.11.326-2.96.965-5.382.965z" fill="#4AAE48"></path><path d="M26.05 22.636C29.562 19.916 33.547 19 38 19h32.656c-3.366-5.738-7.942-10.588-13.73-13.953C51.136 1.684 44.828 0 37.999 0c-5.937 0-11.48 1.261-16.625 3.786-5.146 2.522-9.908 6.196-13.397 10.91L18.999 33.25c1.088-4.254 3.537-7.89 7.052-10.614z" fill="#EA3939"></path><path d="M73.143 23.75h-22.08c3.86 3.86 6.384 8.809 6.384 14.25 0 4.059-1.138 7.769-3.415 11.134L38.447 76c10.39-.1 19.247-3.86 26.571-11.281C72.338 57.297 76 48.39 76 38c0-4.847-.817-9.947-2.857-14.25z" fill="#FED14B"></path><path d="M38 52.25c7.87 0 14.25-6.38 14.25-14.25S45.87 23.75 38 23.75 23.75 30.13 23.75 38 30.13 52.25 38 52.25z" fill="#188FD1"></path></svg>,
217
+ "Edge": <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#z)"><path d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#y)"></path><path opacity=".35" d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#c)"></path><path d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#d)"></path><path opacity=".41" d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#e)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#f)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#g)"></path></g><defs><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(28.3129 0 0 26.8972 46.662 52.974)"><stop offset=".72" stop-opacity="0"></stop><stop offset=".95" stop-opacity=".53"></stop><stop offset="1"></stop></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-81.384 44.823 17.356) scale(42.575 34.3963)"><stop offset=".76" stop-opacity="0"></stop><stop offset=".95" stop-opacity=".5"></stop><stop offset="1"></stop></radialGradient><radialGradient id="f" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(92.291 -2.921 10.717) scale(60.1379 128.081)"><stop stop-color="#35C1F1"></stop><stop offset=".11" stop-color="#34C1ED"></stop><stop offset=".23" stop-color="#2FC2DF"></stop><stop offset=".31" stop-color="#2BC3D2"></stop><stop offset=".67" stop-color="#36C752"></stop></radialGradient><radialGradient id="g" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(73.74 20.31 58.993) scale(28.8947 23.4971)"><stop stop-color="#66EB6E"></stop><stop offset="1" stop-color="#66EB6E" stop-opacity="0"></stop></radialGradient><linearGradient id="y" x1="17.423" y1="52.556" x2="70.362" y2="52.556" gradientUnits="userSpaceOnUse"><stop stop-color="#0C59A4"></stop><stop offset="1" stop-color="#114A8B"></stop></linearGradient><linearGradient id="d" x1="45.332" y1="29.592" x2="12.267" y2="65.608" gradientUnits="userSpaceOnUse"><stop stop-color="#1B9DE2"></stop><stop offset=".16" stop-color="#1595DF"></stop><stop offset=".67" stop-color="#0680D7"></stop><stop offset="1" stop-color="#0078D4"></stop></linearGradient><clipPath id="z"><path fill="#fff" d="M0 0h76v76H0z"></path></clipPath></defs></svg>,
218
+ "Safari on iPhone": <svg class="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#aaa)"><path d="M28.375 0h-16.25A3.09 3.09 0 0 0 9 3.047v33.906A3.09 3.09 0 0 0 12.125 40h16.25a3.09 3.09 0 0 0 3.125-3.047V3.047A3.09 3.09 0 0 0 28.375 0zm1.875 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#000"></path><path d="M30.25 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#BFD2EE"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="#BABABA"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#bbb)"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#ccc)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="#309CFF"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#ddd)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#eee)"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M19.88 19.505l-3.416 4.394 3.905-3.905-.49-.49h.001z" fill="#fff"></path><path d="M16.464 23.9l4.394-3.417-.49-.49-3.904 3.906z" fill="#fff"></path><path d="M20.856 20.484l-.514-.517-3.876 3.93 4.39-3.413z" fill="#E5E5E5"></path><path d="M19.882 19.508l4.353-3.383-3.396 4.369-.036.009-.94-.94.02-.055h-.001z" fill="#EA3939"></path><path d="M20.854 20.482l-.517-.514 3.93-3.876-3.413 4.39z" fill="#AD2C2C"></path><path d="M20.498 14.32a.16.16 0 1 0-.32 0v.786a.16.16 0 0 0 .32 0v-.786zm0 10.823a.16.16 0 1 0-.32 0v.787a.16.16 0 0 0 .32 0v-.787zm5.45-4.86a.16.16 0 0 0 0-.32h-.787a.16.16 0 1 0 0 .32h.787zm-10.661-.16a.16.16 0 0 0-.16-.16h-.786a.16.16 0 1 0 0 .32h.786a.16.16 0 0 0 .16-.16zm8.847 4.217a.16.16 0 0 0 .227-.227l-.556-.556a.16.16 0 0 0-.274.114.162.162 0 0 0 .047.113l.556.556zm-7.427-7.652a.16.16 0 0 0 0-.226l-.556-.556a.16.16 0 1 0-.227.226l.556.556a.161.161 0 0 0 .227 0z" fill="#E8F4FF"></path></g><defs><radialGradient id="ccc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .045 20.134) scale(7.43234)"><stop offset=".927" stop-color="#B6C6FF" stop-opacity="0"></stop><stop offset="1" stop-color="#CFCFD1"></stop></radialGradient><radialGradient id="ddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -8.346 25.3) scale(11.0219)"><stop offset=".594" stop-color="#60D9FF" stop-opacity=".59"></stop><stop offset="1" stop-color="#003CB1" stop-opacity=".83"></stop></radialGradient><radialGradient id="eee" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .044 20.132) scale(6.39705)"><stop offset=".87" stop-color="#5587E8" stop-opacity="0"></stop><stop offset="1" stop-color="#175ECA"></stop></radialGradient><linearGradient id="bbb" x1="15.113" y1="14.952" x2="25.386" y2="25.319" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity=".79"></stop><stop offset="1" stop-color="#fff" stop-opacity="0"></stop></linearGradient><clipPath id="aaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>,
219
+ "Chrome on Android": <svg class="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#aaaa)"><path d="M10.152.38H28.62v39.298H10.152V.381z" fill="#A4A9AF"></path><g filter="url(#bbbb)"><path d="M19.425 25.791a5.531 5.531 0 1 0 0-11.062 5.531 5.531 0 0 0 0 11.062z" fill="#F9F9F9"></path><path d="M19.425 23.09a2.743 2.743 0 0 1-1.535-.453 2.88 2.88 0 0 1-1.037-1.168l-2.268-3.975c-.476.85-.694 1.8-.694 2.766 0 1.383.452 2.59 1.353 3.619.899 1.03 2.019 1.646 3.36 1.848l1.604-2.776c-.162.046-.43.14-.783.14z" fill="#4AAE48"></path><path d="M17.684 18.024c.51-.396 1.091-.53 1.739-.53h4.754a5.576 5.576 0 0 0-2-2.03 5.39 5.39 0 0 0-2.754-.735c-.865 0-1.67.183-2.42.552a5.35 5.35 0 0 0-1.951 1.587l1.605 2.7a2.82 2.82 0 0 1 1.027-1.544z" fill="#EA3939"></path><path d="M24.54 18.186h-3.214c.562.562.93 1.282.93 2.077 0 .59-.166 1.13-.498 1.619l-2.27 3.912a5.311 5.311 0 0 0 3.868-1.644c1.067-1.08 1.6-2.377 1.6-3.89 0-.704-.12-1.447-.417-2.074z" fill="#FED14B"></path><path d="M19.425 22.335a2.075 2.075 0 0 0 1.525-3.573 2.075 2.075 0 1 0-1.525 3.573z" fill="#188FD1"></path></g><path d="M10.076.19h18.505" stroke="#435553" stroke-width=".5"></path><path d="M10.038 39.985v-.381M10 .19h.114H10zm.038.191V0v.381zm0 0v39.566V.38zM10 .191h.114H10zm.038.19V0v.381zM28.62.191h.114-.114zm.038.19V0v.381zM10 39.794h.114H10z" stroke="#4E494B" stroke-width=".5"></path><path d="M10.038.38v39.567" stroke="#2E403E" stroke-width=".5"></path><path d="M28.651 39.81l-18.504.012" stroke="#3C4F4D" stroke-width=".5"></path><path d="M28.688 39.62V40m.038-.19h-.114.114z" stroke="#4E494B" stroke-width=".5"></path><path d="M28.688 39.62L28.667.053" stroke="#2E403E" stroke-width=".5"></path><path d="M29.168 14.826v2.442m0-9.766v4.88-4.88z" stroke="#0A2C28" stroke-width=".15"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M19.585 2.501a.417.417 0 1 0 0-.834.417.417 0 0 0 0 .834z" fill="#212022"></path><g filter="url(#cccc)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.342a.26.26 0 1 0 .044-.519.26.26 0 0 0-.044.52z" fill="url(#dddd)"></path></g><g filter="url(#eeee)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="url(#ffff)"></path></g><mask id="gggg" style={{ maskType: "alpha" }} maskUnits="userSpaceOnUse" x="19" y="1" width="1" height="2"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="#fff"></path></mask><g mask="url(#gggg)"><g filter="url(#hhhh)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.261a.166.166 0 1 0 0-.333.166.166 0 0 0 0 .333z" fill="#1A1719"></path></g><g opacity=".776" filter="url(#iiii)"><path opacity=".776" fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.033c.07 0 .126-.029.126-.063s-.058-.063-.126-.063c-.069 0-.124.029-.124.063s.057.063.124.063z" fill="url(#jjjj)"></path></g><g filter="url(#kkkk)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.579 2.293c.044 0 .076-.02.076-.046 0-.027-.034-.048-.076-.048-.044 0-.078.021-.078.048 0 .025.034.046.076.046h.002z" fill="url(#llll)"></path></g></g></g><defs><filter id="bbbb" x="13.892" y="14.729" width="11.065" height="15.065" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="4"></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"></feColorMatrix><feBlend in2="shape" result="effect1_innerShadow_803_329"></feBlend></filter><filter id="cccc" x="15.344" y="-2.179" width="8.522" height="8.521" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="65"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="eeee" x="15.425" y="-2.072" width="8.312" height="8.313" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="1"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="hhhh" x="14.252" y="-3.236" width="10.661" height="10.661" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="iiii" x="11.305" y="-6.247" width="16.558" height="16.434" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="4.077" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="kkkk" x="14.337" y="-2.965" width="10.482" height="10.422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><radialGradient id="dddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.583 2.083) scale(.26042)"><stop offset=".24" stop-color="#8F8F8F"></stop><stop offset=".5" stop-color="#090A0F"></stop><stop offset=".998" stop-color="#090A0F"></stop></radialGradient><radialGradient id="ffff" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.58 2.084) scale(.15625)"><stop offset=".092" stop-color="#242C33"></stop><stop offset=".289" stop-color="#1A1719"></stop><stop offset="1" stop-color="#1A1719"></stop></radialGradient><radialGradient id="jjjj" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .0929 -.18581 0 19.583 1.972)"><stop stop-color="#65BBFF"></stop><stop offset=".581" stop-color="#E0DDE7"></stop><stop offset="1" stop-color="#968BAC"></stop></radialGradient><radialGradient id="llll" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .07918 -.13196 0 19.579 2.173)"><stop stop-color="#88888A"></stop><stop offset=".558" stop-color="#8B8B8B"></stop><stop offset="1" stop-color="#505050"></stop></radialGradient><clipPath id="aaaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>
94
220
  }
95
- </script>
96
- </head>
97
221
 
98
- <body class="p-5">
99
- <div class="container">
100
- <h1 class="my-3 text-center"> <u> <%= projectName %> </u></h1>
101
- <h4 class="my-3 text-center"> <u> <a href="<%= buildURL %>">Go to Percy Dashboard Build</a> </u></h3>
102
- <table class="table table-bordered table-striped" id="build-details">
103
- <thead>
104
- <th class="text-center">Build Number</th>
105
- <th class="text-center">Browsers</th>
106
- <th class="text-center">Total Snapshots</th>
107
- <th class="text-center">Total Screenshots</th>
108
- <th class="text-center">Snapshots Unreviewed</th>
109
- <th class="text-center">Screenshots Unreviewed</th>
110
- </thead>
111
- <tbody>
112
- <tr>
113
- <td class="text-center">
114
- <%= buildNumber %>
115
- </td>
116
- <td class="text-center">
117
- <% if(browsers.includes('Safari')) { %>
118
- <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="38.698" cy="38.568" r="38" fill="#BABABA"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#x)"></circle><circle cx="38.698" cy="38.568" r="38" fill="url(#w)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="#309CFF"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#cc)"></circle><circle cx="38.698" cy="38.568" r="32.707" fill="url(#dd)"></circle><path fill-rule="evenodd" clip-rule="evenodd" d="M37.168 35.58L19.705 58.046 39.668 38.08l-2.501-2.501z" fill="#fff"></path><path d="M19.704 58.046L42.17 40.582l-2.5-2.501-19.966 19.965z" fill="#fff"></path><path d="M42.166 40.59l-2.629-2.641-19.816 20.096L42.166 40.59z" fill="#E5E5E5"></path><path d="M37.19 35.59l22.254-17.298-17.362 22.336-.184.047-4.809-4.809.101-.275z" fill="#EA3939"></path><path d="M42.159 40.574l-2.641-2.63 20.096-19.816-17.455 22.446z" fill="#AD2C2C"></path><rect x="38.698" y="8.255" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="38.698" y="63.584" width="1.64" height="5.66" rx=".82" fill="#E8F4FF"></rect><rect x="69.013" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 69.013 37.93)" fill="#E8F4FF"></rect><rect x="13.683" y="37.93" width="1.64" height="5.66" rx=".82" transform="rotate(90 13.683 37.93)" fill="#E8F4FF"></rect><rect x="60.661" y="59.733" width="1.64" height="5.66" rx=".82" transform="rotate(135 60.66 59.733)" fill="#E8F4FF"></rect><rect x="21.537" y="20.609" width="1.64" height="5.66" rx=".82" transform="rotate(135 21.537 20.61)" fill="#E8F4FF"></rect><defs><radialGradient id="w" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(38)"><stop offset=".927" stop-color="#B6C6FF" stop-opacity="0"></stop><stop offset="1" stop-color="#CFCFD1"></stop></radialGradient><radialGradient id="cc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -10.487 32.545) scale(56.3527)"><stop offset=".594" stop-color="#60D9FF" stop-opacity=".59"></stop><stop offset="1" stop-color="#003CB1" stop-opacity=".83"></stop></radialGradient><radialGradient id="dd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .065 38.633) scale(32.7068)"><stop offset=".87" stop-color="#5587E8" stop-opacity="0"></stop><stop offset="1" stop-color="#175ECA"></stop></radialGradient><linearGradient id="x" x1="12.796" y1="12.307" x2="65.319" y2="65.307" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity=".79"></stop><stop offset="1" stop-color="#fff" stop-opacity="0"></stop></linearGradient></defs></svg>
119
- <% } %>
120
- <% if(browsers.includes('Firefox')) { %>
121
- <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M36.977 0c20.42 0 36.976 16.464 36.976 36.778 0 20.308-16.555 36.777-36.976 36.777S0 57.086 0 36.778C0 16.462 16.556 0 36.977 0z" transform="translate(2.465)" fill="url(#a)"></path><path d="M45.762 70.558C64.532 67.545 78.886 51.4 78.886 31.88l-.36.444c.562-3.71.464-7.015-.308-9.898-.276 2.136-.577 3.425-.88 3.943-.002-.167-.044-2.393-.757-5.5-.338-2.265-.833-4.396-1.518-6.365.167.902.266 1.655.315 2.337C72.514 9.196 65.461-.217 48.084.004c0 0 6.111.642 8.988 4.975 0 0-2.94-.7-5.164.372 2.709 1.074 5.058 2.197 7.057 3.37l.173.105c.513.306.95.62 1.413.931 3.692 2.58 7.119 6.26 6.858 11.007-.797-1.25-1.857-2.07-3.215-2.482 1.674 6.522 1.839 11.909.486 16.158-.93-2.83-1.785-4.526-2.547-5.124 1.063 8.743-.37 15.209-4.287 19.423.745-2.572 1.04-4.678.873-6.34-4.602 6.919-9.838 10.496-15.708 10.717-2.32-.02-4.51-.368-6.572-1.045-3.027-1.017-5.766-2.756-8.204-5.222 3.806.316 7.282-.34 10.342-1.893l5.011-3.266-.02-.014c.651-.246 1.263-.226 1.86.05 1.222-.166 1.65-.818 1.242-1.919-.592-.819-1.487-1.562-2.636-2.216-2.507-1.302-5.125-1.096-7.856.637-2.6 1.471-5.103 1.412-7.538-.152-1.595-1.096-3.136-2.572-4.625-4.416l-.591-.87c-.28 2.089.037 4.769.98 8.069l.02.044-.02-.04c-.946-3.3-1.26-5.986-.98-8.073v-.02c.071-1.824.83-2.83 2.277-3.055l-.614-.051.62.051c1.634.15 3.51.523 5.625 1.136.355-2.03-.111-4.156-1.39-6.336v-.04c1.986-1.843 3.749-3.184 5.255-4.04.668-.355 1.057-.895 1.186-1.623l.054-.04.02-.019.074-.071c.389-.579.258-1.044-.407-1.456a16.664 16.664 0 0 1-4.179-.297l-.02.056c-.574-.166-1.298-.674-2.19-1.532l-2.286-2.236-.685-.537v.071h-.02l.02-.09-.13-.136.184-.13c.316-1.694.836-3.15 1.578-4.4l.167-.15c.745-1.231 2.172-2.553 4.27-3.965-3.9.483-7.427 2.231-10.563 5.254-2.6-.948-5.682-.747-9.23.613l-.426.324-.032.017.463-.338.02-.02C10.835 8.71 9.33 5.78 8.605.992 5.766 3.789 4.39 8.784 4.483 15.995l-.814 1.223-.21.142-.041.04-.018.017-.04.08c-.43.673-1.025 1.688-1.774 3.05C.508 22.476.145 24.094.04 25.504l-.01.017.006.047-.035.37.062-.098c.007.326.014.655.098.949l2.303-1.883c-.836 2.106-1.39 4.34-1.672 6.708l-.066 1.084-.723-.822c0 8.405 2.683 16.175 7.208 22.56l.136.21.217.258a39.539 39.539 0 0 0 12.222 10.945c3.459 2.038 7.176 3.465 11.14 4.313l.816.181c.82.157 1.661.275 2.504.38.624.081 1.248.16 1.879.214l.838.095 1.19.008 1.295.064 1.03-.052 1.699-.083a39.852 39.852 0 0 0 2.987-.322l.6-.088zM70.704 22.77l-.017.321.012-.323.005.002z" transform="translate(0 4.897)" fill="url(#b)"></path><defs><linearGradient id="a" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 73.5555 -73.9534 0 73.953 0)"><stop stop-color="#0093F7"></stop><stop offset="1" stop-color="#372893"></stop></linearGradient><linearGradient id="b" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(78.8861 0 0 88.4522 0 -9.471)"><stop stop-color="#FE3229"></stop><stop offset="1" stop-color="#FFD71F"></stop></linearGradient></defs></svg>
122
- <% } %>
123
- <% if(browsers.includes('Chrome')) { %>
124
- <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M38 57.447c-3.86 0-7.372-1.04-10.54-3.117-3.169-2.078-5.544-4.75-7.125-8.015L4.75 19C1.484 24.84 0 31.371 0 38c0 9.5 3.092 17.789 9.277 24.864 6.184 7.075 13.88 11.305 23.082 12.692l11.023-19.074c-1.11.326-2.96.965-5.382.965z" fill="#4AAE48"></path><path d="M26.05 22.636C29.562 19.916 33.547 19 38 19h32.656c-3.366-5.738-7.942-10.588-13.73-13.953C51.136 1.684 44.828 0 37.999 0c-5.937 0-11.48 1.261-16.625 3.786-5.146 2.522-9.908 6.196-13.397 10.91L18.999 33.25c1.088-4.254 3.537-7.89 7.052-10.614z" fill="#EA3939"></path><path d="M73.143 23.75h-22.08c3.86 3.86 6.384 8.809 6.384 14.25 0 4.059-1.138 7.769-3.415 11.134L38.447 76c10.39-.1 19.247-3.86 26.571-11.281C72.338 57.297 76 48.39 76 38c0-4.847-.817-9.947-2.857-14.25z" fill="#FED14B"></path><path d="M38 52.25c7.87 0 14.25-6.38 14.25-14.25S45.87 23.75 38 23.75 23.75 30.13 23.75 38 30.13 52.25 38 52.25z" fill="#188FD1"></path></svg>
125
- <% } %>
126
- <% if(browsers.includes('Edge')) { %>
127
- <svg class="w-full browser-icon" viewBox="0 0 77 77" width="100" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#z)"><path d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#y)"></path><path opacity=".35" d="M68.584 56.56c-1.014.53-2.058.997-3.129 1.399a30.242 30.242 0 0 1-10.657 1.917c-14.046 0-26.282-9.662-26.282-22.061a9.343 9.343 0 0 1 4.877-8.107c-12.705.535-15.97 13.774-15.97 21.53 0 21.931 20.212 24.154 24.567 24.154 2.348 0 5.889-.682 8.014-1.353l.39-.13a38.096 38.096 0 0 0 19.769-15.674 1.187 1.187 0 0 0-1.58-1.674z" fill="url(#c)"></path><path d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#d)"></path><path opacity=".41" d="M31.378 71.663a23.51 23.51 0 0 1-6.75-6.334 23.96 23.96 0 0 1 8.765-35.621c.926-.437 2.508-1.226 4.613-1.188a9.603 9.603 0 0 1 7.626 3.86 9.463 9.463 0 0 1 1.888 5.538c0-.062 7.26-23.628-23.748-23.628C10.741 14.29.025 26.656.025 37.506A38.634 38.634 0 0 0 3.62 54.129a37.996 37.996 0 0 0 46.42 19.92 22.426 22.426 0 0 1-18.636-2.374l-.026-.012z" fill="url(#e)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#f)"></path><path d="M45.211 44.188c-.24.312-.98.742-.98 1.68 0 .775.505 1.52 1.402 2.146 4.268 2.969 12.316 2.577 12.336 2.577a17.68 17.68 0 0 0 8.986-2.479 18.22 18.22 0 0 0 9.033-15.697c.077-6.652-2.375-11.075-3.366-13.034C66.332 7.077 52.754 0 37.989 0A37.996 37.996 0 0 0-.007 37.462c.143-10.847 10.924-19.607 23.748-19.607 1.038 0 6.963.101 12.467 2.99 4.85 2.546 7.391 5.622 9.158 8.67 1.834 3.167 2.16 7.169 2.16 8.763 0 1.594-.813 3.957-2.315 5.91z" fill="url(#g)"></path></g><defs><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(28.3129 0 0 26.8972 46.662 52.974)"><stop offset=".72" stop-opacity="0"></stop><stop offset=".95" stop-opacity=".53"></stop><stop offset="1"></stop></radialGradient><radialGradient id="e" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-81.384 44.823 17.356) scale(42.575 34.3963)"><stop offset=".76" stop-opacity="0"></stop><stop offset=".95" stop-opacity=".5"></stop><stop offset="1"></stop></radialGradient><radialGradient id="f" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(92.291 -2.921 10.717) scale(60.1379 128.081)"><stop stop-color="#35C1F1"></stop><stop offset=".11" stop-color="#34C1ED"></stop><stop offset=".23" stop-color="#2FC2DF"></stop><stop offset=".31" stop-color="#2BC3D2"></stop><stop offset=".67" stop-color="#36C752"></stop></radialGradient><radialGradient id="g" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(73.74 20.31 58.993) scale(28.8947 23.4971)"><stop stop-color="#66EB6E"></stop><stop offset="1" stop-color="#66EB6E" stop-opacity="0"></stop></radialGradient><linearGradient id="y" x1="17.423" y1="52.556" x2="70.362" y2="52.556" gradientUnits="userSpaceOnUse"><stop stop-color="#0C59A4"></stop><stop offset="1" stop-color="#114A8B"></stop></linearGradient><linearGradient id="d" x1="45.332" y1="29.592" x2="12.267" y2="65.608" gradientUnits="userSpaceOnUse"><stop stop-color="#1B9DE2"></stop><stop offset=".16" stop-color="#1595DF"></stop><stop offset=".67" stop-color="#0680D7"></stop><stop offset="1" stop-color="#0078D4"></stop></linearGradient><clipPath id="z"><path fill="#fff" d="M0 0h76v76H0z"></path></clipPath></defs></svg>
128
- <% } %>
129
- <% if(browsers.includes('Safari on iPhone')) { %>
130
- <svg class="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#aaa)"><path d="M28.375 0h-16.25A3.09 3.09 0 0 0 9 3.047v33.906A3.09 3.09 0 0 0 12.125 40h16.25a3.09 3.09 0 0 0 3.125-3.047V3.047A3.09 3.09 0 0 0 28.375 0zm1.875 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#000"></path><path d="M30.25 36.953a1.84 1.84 0 0 1-1.875 1.796h-16.25a1.84 1.84 0 0 1-1.875-1.796V3.047a1.84 1.84 0 0 1 1.875-1.797h3.125A1.301 1.301 0 0 0 16.5 2.5H24a1.343 1.343 0 0 0 1.25-1.25h3.125a1.84 1.84 0 0 1 1.875 1.797v33.906z" fill="#BFD2EE"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="#BABABA"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#bbb)"></path><path d="M20.18 27.52a7.432 7.432 0 1 0 0-14.865 7.432 7.432 0 0 0 0 14.866z" fill="url(#ccc)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="#309CFF"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#ddd)"></path><path d="M20.177 26.485a6.397 6.397 0 1 0 0-12.794 6.397 6.397 0 0 0 0 12.794z" fill="url(#eee)"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M19.88 19.505l-3.416 4.394 3.905-3.905-.49-.49h.001z" fill="#fff"></path><path d="M16.464 23.9l4.394-3.417-.49-.49-3.904 3.906z" fill="#fff"></path><path d="M20.856 20.484l-.514-.517-3.876 3.93 4.39-3.413z" fill="#E5E5E5"></path><path d="M19.882 19.508l4.353-3.383-3.396 4.369-.036.009-.94-.94.02-.055h-.001z" fill="#EA3939"></path><path d="M20.854 20.482l-.517-.514 3.93-3.876-3.413 4.39z" fill="#AD2C2C"></path><path d="M20.498 14.32a.16.16 0 1 0-.32 0v.786a.16.16 0 0 0 .32 0v-.786zm0 10.823a.16.16 0 1 0-.32 0v.787a.16.16 0 0 0 .32 0v-.787zm5.45-4.86a.16.16 0 0 0 0-.32h-.787a.16.16 0 1 0 0 .32h.787zm-10.661-.16a.16.16 0 0 0-.16-.16h-.786a.16.16 0 1 0 0 .32h.786a.16.16 0 0 0 .16-.16zm8.847 4.217a.16.16 0 0 0 .227-.227l-.556-.556a.16.16 0 0 0-.274.114.162.162 0 0 0 .047.113l.556.556zm-7.427-7.652a.16.16 0 0 0 0-.226l-.556-.556a.16.16 0 1 0-.227.226l.556.556a.161.161 0 0 0 .227 0z" fill="#E8F4FF"></path></g><defs><radialGradient id="ccc" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .045 20.134) scale(7.43234)"><stop offset=".927" stop-color="#B6C6FF" stop-opacity="0"></stop><stop offset="1" stop-color="#CFCFD1"></stop></radialGradient><radialGradient id="ddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(52.933 -8.346 25.3) scale(11.0219)"><stop offset=".594" stop-color="#60D9FF" stop-opacity=".59"></stop><stop offset="1" stop-color="#003CB1" stop-opacity=".83"></stop></radialGradient><radialGradient id="eee" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90 .044 20.132) scale(6.39705)"><stop offset=".87" stop-color="#5587E8" stop-opacity="0"></stop><stop offset="1" stop-color="#175ECA"></stop></radialGradient><linearGradient id="bbb" x1="15.113" y1="14.952" x2="25.386" y2="25.319" gradientUnits="userSpaceOnUse"><stop stop-color="#fff" stop-opacity=".79"></stop><stop offset="1" stop-color="#fff" stop-opacity="0"></stop></linearGradient><clipPath id="aaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>
131
- <% } %>
132
- <% if(browsers.includes('Chrome on Android')) { %>
133
- <svg class="w-full browser-icon" width="100" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#aaaa)"><path d="M10.152.38H28.62v39.298H10.152V.381z" fill="#A4A9AF"></path><g filter="url(#bbbb)"><path d="M19.425 25.791a5.531 5.531 0 1 0 0-11.062 5.531 5.531 0 0 0 0 11.062z" fill="#F9F9F9"></path><path d="M19.425 23.09a2.743 2.743 0 0 1-1.535-.453 2.88 2.88 0 0 1-1.037-1.168l-2.268-3.975c-.476.85-.694 1.8-.694 2.766 0 1.383.452 2.59 1.353 3.619.899 1.03 2.019 1.646 3.36 1.848l1.604-2.776c-.162.046-.43.14-.783.14z" fill="#4AAE48"></path><path d="M17.684 18.024c.51-.396 1.091-.53 1.739-.53h4.754a5.576 5.576 0 0 0-2-2.03 5.39 5.39 0 0 0-2.754-.735c-.865 0-1.67.183-2.42.552a5.35 5.35 0 0 0-1.951 1.587l1.605 2.7a2.82 2.82 0 0 1 1.027-1.544z" fill="#EA3939"></path><path d="M24.54 18.186h-3.214c.562.562.93 1.282.93 2.077 0 .59-.166 1.13-.498 1.619l-2.27 3.912a5.311 5.311 0 0 0 3.868-1.644c1.067-1.08 1.6-2.377 1.6-3.89 0-.704-.12-1.447-.417-2.074z" fill="#FED14B"></path><path d="M19.425 22.335a2.075 2.075 0 0 0 1.525-3.573 2.075 2.075 0 1 0-1.525 3.573z" fill="#188FD1"></path></g><path d="M10.076.19h18.505" stroke="#435553" stroke-width=".5"></path><path d="M10.038 39.985v-.381M10 .19h.114H10zm.038.191V0v.381zm0 0v39.566V.38zM10 .191h.114H10zm.038.19V0v.381zM28.62.191h.114-.114zm.038.19V0v.381zM10 39.794h.114H10z" stroke="#4E494B" stroke-width=".5"></path><path d="M10.038.38v39.567" stroke="#2E403E" stroke-width=".5"></path><path d="M28.651 39.81l-18.504.012" stroke="#3C4F4D" stroke-width=".5"></path><path d="M28.688 39.62V40m.038-.19h-.114.114z" stroke="#4E494B" stroke-width=".5"></path><path d="M28.688 39.62L28.667.053" stroke="#2E403E" stroke-width=".5"></path><path d="M29.168 14.826v2.442m0-9.766v4.88-4.88z" stroke="#0A2C28" stroke-width=".15"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M19.585 2.501a.417.417 0 1 0 0-.834.417.417 0 0 0 0 .834z" fill="#212022"></path><g filter="url(#cccc)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.342a.26.26 0 1 0 .044-.519.26.26 0 0 0-.044.52z" fill="url(#dddd)"></path></g><g filter="url(#eeee)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="url(#ffff)"></path></g><mask id="gggg" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="19" y="1" width="1" height="2"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.58 2.24a.156.156 0 1 0 .001-.312.156.156 0 0 0 0 .312z" fill="#fff"></path></mask><g mask="url(#gggg)"><g filter="url(#hhhh)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.261a.166.166 0 1 0 0-.333.166.166 0 0 0 0 .333z" fill="#1A1719"></path></g><g opacity=".776" filter="url(#iiii)"><path opacity=".776" fill-rule="evenodd" clip-rule="evenodd" d="M19.583 2.033c.07 0 .126-.029.126-.063s-.058-.063-.126-.063c-.069 0-.124.029-.124.063s.057.063.124.063z" fill="url(#jjjj)"></path></g><g filter="url(#kkkk)"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.579 2.293c.044 0 .076-.02.076-.046 0-.027-.034-.048-.076-.048-.044 0-.078.021-.078.048 0 .025.034.046.076.046h.002z" fill="url(#llll)"></path></g></g></g><defs><filter id="bbbb" x="13.892" y="14.729" width="11.065" height="15.065" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="4"></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"></feColorMatrix><feBlend in2="shape" result="effect1_innerShadow_803_329"></feBlend></filter><filter id="cccc" x="15.344" y="-2.179" width="8.522" height="8.521" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="65"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="eeee" x="15.425" y="-2.072" width="8.312" height="8.313" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_803_329"></feBlend><feBlend in="SourceGraphic" in2="effect1_dropShadow_803_329" result="shape"></feBlend><feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"></feColorMatrix><feOffset dy="1"></feOffset><feGaussianBlur stdDeviation="1.5"></feGaussianBlur><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"></feComposite><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"></feColorMatrix><feBlend in2="shape" result="effect2_innerShadow_803_329"></feBlend></filter><filter id="hhhh" x="14.252" y="-3.236" width="10.661" height="10.661" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="iiii" x="11.305" y="-6.247" width="16.558" height="16.434" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="4.077" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><filter id="kkkk" x="14.337" y="-2.965" width="10.482" height="10.422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"></feBlend><feGaussianBlur stdDeviation="2.582" result="effect1_foregroundBlur_803_329"></feGaussianBlur></filter><radialGradient id="dddd" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.583 2.083) scale(.26042)"><stop offset=".24" stop-color="#8F8F8F"></stop><stop offset=".5" stop-color="#090A0F"></stop><stop offset=".998" stop-color="#090A0F"></stop></radialGradient><radialGradient id="ffff" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.58 2.084) scale(.15625)"><stop offset=".092" stop-color="#242C33"></stop><stop offset=".289" stop-color="#1A1719"></stop><stop offset="1" stop-color="#1A1719"></stop></radialGradient><radialGradient id="jjjj" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .0929 -.18581 0 19.583 1.972)"><stop stop-color="#65BBFF"></stop><stop offset=".581" stop-color="#E0DDE7"></stop><stop offset="1" stop-color="#968BAC"></stop></radialGradient><radialGradient id="llll" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 .07918 -.13196 0 19.579 2.173)"><stop stop-color="#88888A"></stop><stop offset=".558" stop-color="#8B8B8B"></stop><stop offset="1" stop-color="#505050"></stop></radialGradient><clipPath id="aaaa"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath></defs></svg>
134
- <% } %>
135
- </td>
136
- <td class="text-center">
137
- <%= totalSnapshots %>
138
- </td>
139
- <td class="text-center">
140
- <%= totalScreenshots %>
141
- </td>
142
- <td class="text-center">
143
- <%= unreviewedSnapshots %>
144
- </td>
145
- <td class="text-center">
146
- <%= unreviewedScreenshots %>
147
- </td>
148
- </tr>
149
- </tbody>
150
- </table>
151
- <div class="row g-0 my-3">
152
- <div class="col-md-4 text-center border p-2">
153
- <label>Snapshot Name</label>
154
- <select id="select-snapshot">
155
- <option selected>All</option>
156
- <% for(let snapshot of details){ %>
157
- <option value="<%= snapshot['name'] %>">
158
- <%= snapshot['name'] %>
159
- </option>
160
- <% } %>
161
- </select>
222
+ const BrowserSegments = function (props) {
223
+ return (
224
+ <div className="flex gap-2">
225
+ {props.browsers.map((browser) => {
226
+ let active = props.activeBrowser == browser
227
+ return <button
228
+ onClick={() => props.onChange(browser)}
229
+ style={{ width: 30, height: 30 }}
230
+ className={`p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`} >
231
+ {Browsers[browser]}
232
+ </button>
233
+ })}
162
234
  </div>
163
- <div class="col-md-4 text-center border p-2">
164
- <label>Snapshot Width</label>
165
- <select id="select-width">
166
- <% for(let width of widths){ %>
167
- <option value="<%= width %>">
168
- <%= width %>
169
- </option>
170
- <% } %>
171
- </select>
172
- </div>
173
- <div class="col-md-4 text-center border p-2">
174
- <label>Browser</label>
175
- <select id="select-browser">
176
- <% for(let browser of browsers){ %>
177
- <option value="<%= browser %>">
178
- <%= browser %>
179
- </option>
180
- <% } %>
235
+ )
236
+ }
237
+
238
+ const Picker = function (props) {
239
+ React.useEffect(() => {
240
+ if (props.items.length > 0 && !props.items.includes(props.value)) {
241
+ props.onChange(props.items.at(0))
242
+ }
243
+ }, [props])
244
+ if (!props.items || props.items.length == 0) {
245
+ return null
246
+ }
247
+ return (
248
+ <div className="flex gap-2">
249
+ <select onChange={(e) => props.onChange(e.target.value)} className="border-primary bg-highlight p-2 rounded" value={props.value} >
250
+ {props.items.map((width) => {
251
+ return (
252
+ <option>{width}</option>
253
+ )
254
+ })}
181
255
  </select>
182
256
  </div>
257
+ )
258
+ }
259
+
260
+ const BaseImage = (props) => {
261
+ const [loading, SetLoading] = React.useState(false)
262
+ const image = React.useRef()
263
+ React.useEffect(() => {
264
+ setTimeout(() => {
265
+ SetLoading(image.current ? !image.current.complete : true)
266
+ })
267
+ }, [props.src])
268
+ if (!props.src) {
269
+ return <Empty>No Base Image</Empty>
270
+ }
271
+ return <div className={`w-full h-full ${loading ? 'skeleton' : 'artboard'}`}>
272
+ <img ref={image} style={{ opacity: loading ? 0 : 1 }} onLoad={() => {
273
+ SetLoading(false)
274
+ }} {...props} />
183
275
  </div>
184
- <hr>
185
- <% for(let snapshot of details){ %>
186
- <% for(let comparison of snapshot.comparisons){ %>
187
- <div class="row gx-2">
188
- <div class="col-md-12">
189
- <table hidden data-type="diff-ratio" data-name="<%= snapshot.name %>"
190
- data-width="<%= comparison.width %>" data-browser="<%= comparison.browser %>" class="m-3">
191
- <tbody>
192
- <tr>
193
- <td>Diff Ratio:</td>
194
- <td class="color-<%= comparison['diff-color'] %>" ><%= comparison['diff-percentage'] %></td>
195
- </tr>
196
- </tbody>
197
- </table>
276
+ }
277
+
278
+ const HeadImage = (props) => {
279
+ const [loadingHead, SetLoadingHead] = React.useState(false)
280
+ const [loadingDIFF, SetLoadingDiff] = React.useState(false)
281
+ const headImage = React.useRef()
282
+ const diffImage = React.useRef()
283
+ const loading = loadingDIFF || loadingHead
284
+ React.useEffect(() => {
285
+ setTimeout(() => {
286
+ SetLoadingHead(headImage.current ? !headImage.current.complete : true)
287
+ })
288
+ }, [props.headSrc])
289
+
290
+ React.useEffect(() => {
291
+ setTimeout(() => {
292
+ SetLoadingDiff(diffImage.current ? !diffImage.current.complete : true)
293
+ })
294
+ }, [props.diffSrc])
295
+ if (!props.headSrc) {
296
+ return <Empty>No Image</Empty>
297
+ }
298
+ return (
299
+ <div className={`relative w-full h-full ${loading ? 'skeleton' : 'artboard'}`}>
300
+ <img ref={headImage} style={{ opacity: loading ? 0 : 1 }} onLoad={() => {
301
+ SetLoadingHead(false)
302
+ }} src={props.headSrc} />
303
+ <img className="absolute top-0 left-0 right-0 bottom-0 bg-backdrop" ref={diffImage} style={{ opacity: loading || !props.diffOverlay ? 0 : 1 }} onLoad={() => {
304
+ SetLoadingDiff(false)
305
+ }} src={props.diffSrc} />
306
+ </div>
307
+ )
308
+ }
309
+
310
+ function App() {
311
+ const [filters, SetFilters] = React.useState(() => {
312
+ const snapshot = BuildData.details.at(0)
313
+ const comparison = snapshot && snapshot.comparisons.at(0)
314
+ return {
315
+ snapshots: 'changed',
316
+ diffThreshold: 0,
317
+ search: '',
318
+ activeSnapshot: snapshot ? BuildData.details.at(0).name : '',
319
+ activeBrowser: comparison ? comparison.browser : null,
320
+ activeWidth: comparison ? comparison.width : null,
321
+ activeDevice: comparison ? comparison.device : null
322
+ }
323
+ })
324
+ const [diffOverlay, SetDiffOverlay] = React.useState(true);
325
+ const SetFilter = (state) => {
326
+ SetFilters(Object.assign({}, filters, state))
327
+ }
328
+
329
+ const snapshots = React.useMemo(() => {
330
+ let category = SnapshotData[filters.snapshots]
331
+ let snapshots = category
332
+ if (filters.snapshots == 'changed') {
333
+ snapshots = snapshots.filter((snapshot) => snapshot['diff-ratios'].some((r) => r >= filters.diffThreshold))
334
+ }
335
+ if (filters.search) {
336
+ snapshots = snapshots.filter((snapshot) => snapshot.name.toLowerCase().includes(filters.search.toLowerCase()))
337
+ }
338
+ return snapshots
339
+ }, [filters])
340
+
341
+ const activeSnapshot = React.useMemo(() => {
342
+ let index = snapshots.findIndex((s) => s.name == filters.activeSnapshot);
343
+ if (index < 0 && snapshots.length == 0) {
344
+ return undefined
345
+ }
346
+ if (index < 0) {
347
+ index = 0
348
+ }
349
+ const snapshot = snapshots[index]
350
+ const details = snapshot.comparisons.reduce((map, current) => {
351
+ if (current.browser && !map.browsers.includes(current.browser)) {
352
+ map.browsers.push(current.browser)
353
+ if (!map.widths[current.browser]) {
354
+ map.widths[current.browser] = []
355
+ }
356
+ }
357
+ if (current.browser && current.width && !map.widths[current.browser].includes(current.width)) {
358
+ map.widths[current.browser].push(current.width)
359
+ }
360
+ if (current.device && !map.devices.includes(current.device)) {
361
+ map.devices.push(current.device)
362
+ }
363
+ return map
364
+ }, {
365
+ browsers: [],
366
+ widths: {},
367
+ devices: []
368
+ })
369
+ return {
370
+ index,
371
+ snapshot: snapshots[index],
372
+ ...details
373
+ }
374
+ }, [snapshots, filters])
375
+
376
+ const activeComparison = React.useMemo(() => {
377
+ if (activeSnapshot && activeSnapshot.snapshot) {
378
+ return activeSnapshot.snapshot.comparisons.find((c) => {
379
+ if (c.device) {
380
+ return c.device == filters.activeDevice
381
+ } else {
382
+ return c.browser == filters.activeBrowser && c.width == filters.activeWidth && c.device == filters.activeDevice
383
+ }
384
+ })
385
+ }
386
+ }, [activeSnapshot, filters])
387
+
388
+ React.useEffect(() => {
389
+ if (activeSnapshot && activeSnapshot.snapshot.name != filters.activeSnapshot) {
390
+ SetFilter({ activeSnapshot: activeSnapshot.snapshot.name })
391
+ }
392
+ }, [activeSnapshot, filters])
393
+
394
+ const NextSnapshot = () => {
395
+ if (activeSnapshot && activeSnapshot.index < snapshots.length - 1) {
396
+ SetFilter({ activeSnapshot: snapshots[activeSnapshot.index + 1].name })
397
+ }
398
+ }
399
+ const PrevSnapshot = () => {
400
+ if (activeSnapshot && activeSnapshot.index > 0) {
401
+ SetFilter({ activeSnapshot: snapshots.at(activeSnapshot.index - 1).name })
402
+ }
403
+ }
404
+ const getImage = (comp, kind) => {
405
+ if (comp && comp.images[kind]) {
406
+ return comp.images[kind].file || comp.images[kind].url
407
+ }
408
+ }
409
+ return (
410
+ <div className="app">
411
+ <div className="filter-panel">
412
+ <div className="p-2">
413
+ <PercyLogo />
414
+ </div>
415
+ <div className="p-2 flex justify-between">
416
+ <h6><b>Build #{BuildData.buildNumber}</b></h6>
417
+ <a target="_blank" href={BuildData.buildURL}><i class="fa fa-external-link"></i></a>
198
418
  </div>
199
- <div class="col-md-6 image-container">
200
- <img hidden data-type="base" data-name="<%= snapshot.name %>"
201
- data-width="<%= comparison.width %>" data-browser="<%= comparison.browser %>"
202
- src="<%= comparison.images['base']?.file || comparison.images['base']?.url %>" alt="No Base Image Found">
419
+ <div className="p-2 flex flex-col">
420
+ <div className="font-bold mb-2">Snapshots</div>
421
+ {["changed", "unchanged"].map((item) => {
422
+ const active = filters.snapshots == item
423
+ return (
424
+ <button onClick={() => SetFilter({ snapshots: item })} className={`flex p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`}>
425
+ <div className="text-start flex-1 capitalize">{item}</div>
426
+ <div >{SnapshotData[item].length}</div>
427
+ </button>
428
+ )
429
+ })}
203
430
  </div>
204
- <% if(!comparison.images['base'] || comparison.images['diff'] || comparison.images['head']){ %>
205
- <div onclick="onClickOverlay(event,this)" class="col-md-6 image-container comparison-container">
206
- <img hidden data-type="head" data-name="<%= snapshot.name %>"
207
- data-width="<%= comparison.width %>" data-browser="<%= comparison.browser %>"
208
- src="<%= comparison.images['head'].file || comparison.images['head']?.url %>" alt="No Head Image Found">
209
- <% if (comparison.images['diff']) { %> <img hidden data-type="diff"
210
- data-name="<%= snapshot.name %>" data-width="<%= comparison.width %>"
211
- data-browser="<%= comparison.browser %>" class="overlay"
212
- src="<%= comparison.images['diff']?.file || comparison.images['diff']?.url %>" alt="">
213
- <% } %>
431
+ {filters && filters.snapshots == 'changed' ? <div className="p-2 flex flex-col">
432
+ <div className="font-bold mb-2 flex justify-between">
433
+ <span>Diff Threshold</span>
434
+ <span>{">"}</span>
435
+ <span>{filters.diffThreshold}%</span>
214
436
  </div>
215
- <% } %>
437
+ <input onChange={(e) => SetFilter({ diffThreshold: Number(e.target.value) })} type="range" min="0" max="100" value={filters.diffThreshold} />
438
+ </div> : null}
216
439
  </div>
217
- <% }} %>
218
- </div>
440
+ <div classNamef="flex flex-col h-full" id="thumbnails" className="p-2">
441
+ <input onChange={(e) => SetFilter({ search: e.target.value })} className="w-full mb-3 sticky top-0" type="text" placeholder="Search Snapshot" />
442
+ <div className="flex flex-col overflow-y-auto flex-1 gap-2">
443
+ {snapshots.length > 0 ? snapshots.map((snapshot) => {
444
+ const active = snapshot.name == filters.activeSnapshot
445
+ return (
446
+ <button onClick={() => SetFilter({ activeSnapshot: snapshot.name })} className={`flex p-1 rounded ${active ? 'bg-highlight border-primary' : ''}`}>
447
+ <div className="text-start flex-1">{snapshot['name']}</div>
448
+ <div>{snapshot['max-diff']}%</div>
449
+ </button>
450
+ )
451
+ }) : <EmptyPlaceholder />}
452
+ </div>
453
+ </div>
454
+ <div id="split" />
455
+ <div className="flex flex-col" id="comparisons">
456
+ <nav className="p-4 flex flex-row justify-between bg-white sticky top-0">
457
+ <div hidden={snapshots.length <= 0} className="flex gap-4 items-center flex-1">
458
+ <button onClick={PrevSnapshot} className="bg-highlight p-1 rounded">
459
+ <i className="fa fa-arrow-up"></i>
460
+ </button>
461
+ <button onClick={NextSnapshot} className="bg-highlight p-1 rounded">
462
+ <i className="fa fa-arrow-down"></i>
463
+ </button>
464
+ <h6 hidden={snapshots.length <= 0} className="p-2" >{activeSnapshot ? activeSnapshot.index + 1 : ''} / {snapshots.length}</h6>
465
+ <h6 hidden={snapshots.length <= 0} className="font-bold truncate flex-1 max-w-md">{activeSnapshot ? activeSnapshot.snapshot.name : ''}</h6>
466
+ </div>
467
+ <div className="flex gap-2 items-center w-fit">
468
+ <div onClick={() => SetDiffOverlay(!diffOverlay)} className={`${diffOverlay ? "bg-diff" : "bg-highlight"} p-2 cursor-pointer rounded`} >
469
+ <DiffIcon />
470
+ </div>
471
+ <div>
472
+ <BrowserSegments onChange={(browser) => SetFilter({ activeBrowser: browser })} activeBrowser={filters.activeBrowser} browsers={activeSnapshot ? activeSnapshot.browsers : []} />
473
+ </div>
474
+ <div>
475
+ <Picker onChange={(value) => SetFilter({ activeWidth: Number(value) })} value={filters.activeWidth} items={activeSnapshot ? activeSnapshot.widths[filters.activeBrowser] || [] : []} />
476
+ </div>
477
+ <div>
478
+ <Picker onChange={(value) => SetFilter({ activeDevice: value })} value={filters.activeDevice} items={activeSnapshot ? activeSnapshot.devices : []} />
479
+ </div>
480
+ </div>
481
+ </nav>
482
+ <div className="grid grid-cols-2">
483
+ {activeSnapshot && activeSnapshot.snapshot['enable-layout']?<div className="flex justify-center items-center gap-4 col-span-2">
484
+ <LayoutDiff /> Layout Diff
485
+ </div>:null}
486
+ <div className="text-center font-bold" >Base Image</div>
487
+ <div className="text-center font-bold">Comparison Image</div>
488
+ </div>
489
+ <div className="mt-2 grid grid-cols-2 flex-1 gap-1 p-4" >
490
+ <div className="border-secondary" >
491
+ <BaseImage src={getImage(activeComparison, 'base')} alt="" loading="lazy" />
492
+ </div>
493
+
494
+ <div onClick={() => SetDiffOverlay(!diffOverlay)} className="border-secondary relative cursor-pointer" >
495
+ <HeadImage
496
+ diffOverlay={diffOverlay}
497
+ headSrc={getImage(activeComparison, 'head')}
498
+ diffSrc={getImage(activeComparison, 'diff')}
499
+ />
500
+ </div>
501
+ </div>
502
+ </div>
503
+ </div>
504
+ );
505
+ }
506
+ ReactDOM.render(<App />, document.getElementById('root'));
507
+
508
+ </script>
219
509
  </body>
220
510
 
221
511
  </html>
@@ -1,180 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>Document</title>
9
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
10
- integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
11
- <style>
12
- img {
13
- width: 100%;
14
- object-fit: contain;
15
- border: 1px solid gray;
16
- }
17
-
18
- .comparison-container {
19
- position: relative;
20
- cursor: pointer;
21
- }
22
-
23
- .overlay {
24
- position: absolute;
25
- top: 0;
26
- left: 0;
27
- right: 0;
28
- bottom: 0;
29
- z-index: 50;
30
- width: 100%;
31
- height: 100%;
32
- background-color: rgba(0, 0, 0, 0.7);
33
- }
34
-
35
- .color-red{
36
- background-color: red !important;
37
- color: white !important;
38
- }
39
- .color-green{
40
- background-color: green !important;
41
- color: white !important;
42
- }
43
- select{
44
- max-width: 100%;
45
- }
46
- </style>
47
- <script type="text/javascript">
48
- function onClickOverlay(e, element) {
49
- let hidden = e.currentTarget.querySelector(".overlay").classList.contains('d-none')
50
- if(hidden){
51
- e.currentTarget.querySelector(".overlay").classList.remove('d-none')
52
- }else{
53
- e.currentTarget.querySelector(".overlay").classList.add('d-none')
54
- }
55
- }
56
- const snapshots = JSON.parse('<%- JSON.stringify(details.map(snp=>snp.name)) %>')
57
- const widths = JSON.parse('<%- JSON.stringify(widths) %>')
58
- const devices = JSON.parse('<%- JSON.stringify(devices) %>')
59
- var snapshotSelect, widthSelect,deviceSelect,images
60
- window.addEventListener('DOMContentLoaded', () => {
61
- snapshotSelect = document.getElementById('select-snapshot')
62
- deviceSelect = document.getElementById('select-device')
63
- images = document.getElementsByTagName('img')
64
- tables = document.getElementsByTagName('table')
65
- snapshotSelect.addEventListener('change', ApplyFilter)
66
- deviceSelect.addEventListener('change', ApplyFilter)
67
- ApplyFilter()
68
- })
69
-
70
- function ApplyFilter() {
71
- const shouldEnable = (dataset) => {
72
- return (snapshotSelect.value == "All" || snapshotSelect.value == dataset.name) && deviceSelect.value == dataset.device
73
- }
74
- for (let i = 0; i < images.length; i++) {
75
- let image = images.item(i)
76
- image.hidden = !shouldEnable(image.dataset)
77
- }
78
-
79
- for(let i = 0; i < tables.length; i++){
80
- let table = tables.item(i)
81
- if(table.id !== "build-details"){
82
- table.hidden = !shouldEnable(table.dataset)
83
- }
84
- }
85
- }
86
- </script>
87
- </head>
88
-
89
- <body class="p-5">
90
- <div class="container">
91
- <h1 class="my-3 text-center"> <u> <%= projectName %> </u></h1>
92
- <h4 class="my-3 text-center"> <u> <a href="<%= buildURL %>">Go to Percy Dashboard Build</a> </u></h3>
93
- <table class="table table-bordered table-striped" id="build-details">
94
- <thead>
95
- <th class="text-center">Build Number</th>
96
- <th class="text-center">Device Count</th>
97
- <th class="text-center">Total Snapshots</th>
98
- <th class="text-center">Total Screenshots</th>
99
- <th class="text-center">Snapshots Unreviewed</th>
100
- <th class="text-center">Screenshots Unreviewed</th>
101
- </thead>
102
- <tbody>
103
- <tr>
104
- <td class="text-center">
105
- <%= buildNumber %>
106
- </td>
107
- <td class="text-center"><%= devices.length %></td>
108
- <td class="text-center">
109
- <%= totalSnapshots %>
110
- </td>
111
- <td class="text-center">
112
- <%= totalScreenshots %>
113
- </td>
114
- <td class="text-center">
115
- <%= unreviewedSnapshots %>
116
- </td>
117
- <td class="text-center">
118
- <%= unreviewedScreenshots %>
119
- </td>
120
- </tr>
121
- </tbody>
122
- </table>
123
- <div class="row g-0 my-3">
124
- <div class="col-md-6 text-center border p-2">
125
- <label>Snapshot Name</label>
126
- <select id="select-snapshot">
127
- <option selected>All</option>
128
- <% for(let snapshot of details){ %>
129
- <option value="<%= snapshot['name'] %>">
130
- <%= snapshot['name'] %>
131
- </option>
132
- <% } %>
133
- </select>
134
- </div>
135
- <div class="col-md-6 text-center border p-2">
136
- <label>Devices</label>
137
- <select id="select-device">
138
- <% for(let device of devices){ %>
139
- <option value="<%= device %>">
140
- <%= device %>
141
- </option>
142
- <% } %>
143
- </select>
144
- </div>
145
- </div>
146
- <hr>
147
- <% for(let snapshot of details){ %>
148
- <% for(let comparison of snapshot.comparisons){ %>
149
- <div class="row gx-2">
150
- <div class="col-md-12">
151
- <table hidden data-type="diff-ratio" data-name="<%= snapshot.name %>" data-device="<%= comparison.device %>" class="m-3">
152
- <tbody>
153
- <tr>
154
- <td>Diff Ratio:</td>
155
- <td class="color-<%= comparison['diff-color'] %>" ><%= comparison['diff-percentage'] %></td>
156
- </tr>
157
- </tbody>
158
- </table>
159
- </div>
160
- <div class="col-md-6 image-container">
161
- <img hidden data-type="base" data-name="<%= snapshot.name %>" data-device="<%= comparison.device %>"
162
- src="<%= comparison.images['base']?.file || comparison.images['base']?.url %>" alt="No Base Image Found">
163
- </div>
164
- <% if(!comparison.images['base'] || comparison.images['diff'] || comparison.images['head']){ %>
165
- <div onclick="onClickOverlay(event,this)" class="col-md-6 image-container comparison-container">
166
- <img hidden data-type="head" data-name="<%= snapshot.name %>" data-device="<%= comparison.device %>"
167
- src="<%= comparison.images['head'].file || comparison.images['head']?.url %>" alt="No Head Image Found">
168
- <% if (comparison.images['diff']) { %> <img hidden data-type="diff"
169
- data-name="<%= snapshot.name %>"
170
- data-device="<%= comparison.device %>" class="overlay"
171
- src="<%= comparison.images['diff']?.file || comparison.images['diff']?.url %>" alt="">
172
- <% } %>
173
- </div>
174
- <% } %>
175
- </div>
176
- <% }} %>
177
- </div>
178
- </body>
179
-
180
- </html>