[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [lissy93]\n"
  },
  {
    "path": ".github/README.md",
    "content": "<h1 align=\"center\">Web-Check</h1>\n\n\n<p align=\"center\">\n<img src=\"https://i.ibb.co/q1gZN2p/web-check-logo.png\" width=\"96\" /><br />\n<b><i>Comprehensive, on-demand open source intelligence for any website</i></b>\n<br />\n<b>🌐 <a href=\"https://web-check.xyz/\">web-check.xyz</a></b><br />\n\n</p>\n\n---\n<p align=\"center\">\n  <sup>Kindly supported by:</sup><br>\n<a href=\"https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n  <img src=\"https://i.ibb.co/8jrrcZ0/IMG-7210.jpg\" width=\"300\" alt=\"Terminal Trove\">\n  <br>\n  <strong>The $HOME of all things in the terminal.</strong>\n</a>\n<br>\n<a href=\"https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n  <sub>Find your next CLI / TUI tool and more at Terminal Trove,</sub>\n  <br>\n  <sup>Get updates on new tools on our newsletter.</sup>\n</a>\n</p>\n\n<p align=\"center\">\n\t<sup>Kindly supported by:</sup><br>\n\t<a href=\"https://go.warp.dev/web-check\"><b>Warp</b>, built for coding with multiple AI agents</a><br><br>\n\t<a href=\"https://go.warp.dev/web-check\"><img width=\"640\" src=\"https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-02.png?raw=true\" /></a>\n</p>\n\n---\n\n#### Contents\n\n- **[About](#about)**\n  - [Screenshot](#screenshot)\n  - [Live Demo](#live-demo)\n  - [Mirror](#mirror)\n  - [Features](#features)\n- **[Usage](#usage)**\n  - [Deployment](#deployment)\n    - [Option#1: Netlify](#deploying---option-1-netlify)\n    - [Option#2: Vercel](#deploying---option-2-vercel)\n    - [Option#3: Docker](#deploying---option-3-docker)\n    - [Option#4: Source](#deploying---option-4-from-source)\n  - [Configuration Options](#configuring)\n  - [Developer Setup](#developing)\n- **[Community](#community)**\n  - [Contributing](#contributing)\n  - [Bugs](#reporting-bugs)\n  - [Support](#supporting)\n- **[License](#license)**\n\n---\n\n## About\nGet an insight into the inner-workings of a given website: uncover potential attack vectors, analyse server architecture, view security configurations, and learn what technologies a site is using.\n\nCurrently the dashboard will show: IP info, SSL chain, DNS records, cookies, headers, domain info, search crawl rules, page map, server location, redirect ledger, open ports, traceroute, DNS security extensions, site performance, trackers, associated hostnames, carbon footprint. Stay tuned, as I'll add more soon!\n\nThe aim is to help you easily understand, optimize and secure your website.\n\n### Screenshot\n\n<details>\n      <summary>Expand Screenshot</summary>\n\n[![Screenshot](https://raw.githubusercontent.com/Lissy93/web-check/master/.github/screenshots/web-check-screenshot1.png)](https://web-check.as93.net/)\n      \n</details>\n\n[![Screenshot](https://i.ibb.co/r0jXN6s/web-check.png)](https://github.com/Lissy93/web-check/tree/master/.github/screenshots)\n\n### Live Demo\nA hosted version can be accessed at: **[web-check.as93.net](https://web-check.as93.net)**\n\n### Mirror\nThe source for this repo is mirrored to CodeBerg, available at: **[codeberg.org/alicia/web-check](https://codeberg.org/alicia/web-check)**\n\n### Status\n\n\nBuild & Deploys: [![Netlify Status](https://api.netlify.com/api/v1/badges/c43453c1-5333-4df7-889b-c1d2b52183c0/deploy-status)](https://app.netlify.com/sites/web-check/deploys)\n[![Vercel Status](https://therealsujitk-vercel-badge.vercel.app/?app=web-check-ten)](https://vercel.com/as93/web-check/)\n[![🐳 Build + Publish Docker Image](https://github.com/Lissy93/web-check/actions/workflows/docker.yml/badge.svg)](https://github.com/Lissy93/web-check/actions/workflows/docker.yml)\n[![🚀 Deploy to AWS](https://github.com/Lissy93/web-check/actions/workflows/deploy-aws.yml/badge.svg)](https://github.com/Lissy93/web-check/actions/workflows/deploy-aws.yml)\n<br />\nRepo Management & Miscellaneous: [![🪞 Mirror to Codeberg](https://github.com/Lissy93/web-check/actions/workflows/mirror.yml/badge.svg)](https://github.com/Lissy93/web-check/actions/workflows/mirror.yml)\n[![💓 Inserts Contributors & Sponsors](https://github.com/Lissy93/web-check/actions/workflows/credits.yml/badge.svg)](https://github.com/Lissy93/web-check/actions/workflows/credits.yml)\n\n\n### Features\n\n<details open>\n<summary><b>Click to expand / collapse section</b></summary>\n\n<sup>**Note** _this list needs updating, many more jobs have been added since..._</sup>\n\nThe following section outlines the core features, and briefly explains why this data might be useful for you to know, as well as linking to further resources for learning more.\n\n<details>\n<summary><b>IP Info</b></summary>\n\n###### Description\nAn IP address (Internet Protocol address) is a numerical label assigned to each device connected to a network / the internet. The IP associated with a given domain can be found by querying the Domain Name System (DNS) for the domain's A (address) record.\n\n###### Use Cases\nFinding the IP of a given server is the first step to conducting further investigations, as it allows us to probe the server for additional info. Including creating a detailed map of a target's network infrastructure, pinpointing the physical location of a server, identifying the hosting service, and even discovering other domains that are hosted on the same IP address.\n\n###### Useful Links\n- [Understanding IP Addresses](https://www.digitalocean.com/community/tutorials/understanding-ip-addresses-subnets-and-cidr-notation-for-networking)\n- [IP Addresses - Wiki](https://en.wikipedia.org/wiki/IP_address)\n- [RFC-791 Internet Protocol](https://tools.ietf.org/html/rfc791)\n- [whatismyipaddress.com](https://whatismyipaddress.com/)\n\n</details>\n<details>\n<summary><b>SSL Chain</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/kB7LsV1/wc-ssl.png\" align=\"right\" />\n\n###### Description\nSSL certificates are digital certificates that authenticate the identity of a website or server, enable secure encrypted communication (HTTPS), and establish trust between clients and servers. A valid SSL certificate is required for a website to be able to use the HTTPS protocol, and encrypt user + site data in transit. SSL certificates are issued by Certificate Authorities (CAs), which are trusted third parties that verify the identity and legitimacy of the certificate holder.\n\n###### Use Cases\nSSL certificates not only provide the assurance that data transmission to and from the website is secure, but they also provide valuable OSINT data. Information from an SSL certificate can include the issuing authority, the domain name, its validity period, and sometimes even organization details. This can be useful for verifying the authenticity of a website, understanding its security setup, or even for discovering associated subdomains or other services.\n\n###### Useful Links\n- [TLS - Wiki](https://en.wikipedia.org/wiki/Transport_Layer_Security)\n- [What is SSL (via Cloudflare learning)](https://www.cloudflare.com/learning/ssl/what-is-ssl/)\n- [RFC-8446 - TLS](https://tools.ietf.org/html/rfc8446)\n- [SSL Checker](https://www.sslshopper.com/ssl-checker.html)\n\n</details>\n<details>\n<summary><b>DNS Records</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/7Q1kMwM/wc-dns.png\" align=\"right\" />\n\n###### Description\nThis task involves looking up the DNS records associated with a specific domain. DNS is a system that translates human-readable domain names into IP addresses that computers use to communicate. Various types of DNS records exist, including A (address), MX (mail exchange), NS (name server), CNAME (canonical name), and TXT (text), among others.\n\n###### Use Cases\nExtracting DNS records can provide a wealth of information in an OSINT investigation. For example, A and AAAA records can disclose IP addresses associated with a domain, potentially revealing the location of servers. MX records can give clues about a domain's email provider. TXT records are often used for various administrative purposes and can sometimes inadvertently leak internal information. Understanding a domain's DNS setup can also be useful in understanding how its online infrastructure is built and managed.\n\n###### Useful Links\n- [What are DNS records? (via Cloudflare learning)](https://www.cloudflare.com/learning/dns/dns-records/)\n- [DNS Record Types](https://en.wikipedia.org/wiki/List_of_DNS_record_types)\n- [RFC-1035 - DNS](https://tools.ietf.org/html/rfc1035)\n- [DNS Lookup (via MxToolbox)](https://mxtoolbox.com/DNSLookup.aspx)\n\n</details>\n<details>\n<summary><b>Cookies</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/TTQ6DtP/wc-cookies.png\" align=\"right\" />\n\n###### Description\nThe Cookies task involves examining the HTTP cookies set by the target website. Cookies are small pieces of data stored on the user's computer by the web browser while browsing a website. They hold a modest amount of data specific to a particular client and website, such as site preferences, the state of the user's session, or tracking information.\n\n###### Use Cases\nCookies can disclose information about how the website tracks and interacts with its users. For instance, session cookies can reveal how user sessions are managed, and tracking cookies can hint at what kind of tracking or analytics frameworks are being used. Additionally, examining cookie policies and practices can offer insights into the site's security settings and compliance with privacy regulations.\n\n###### Useful Links\n- [HTTP Cookie Docs (Mozilla)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)\n- [What are Cookies (via Cloudflare Learning)](https://www.cloudflare.com/learning/privacy/what-are-cookies/)\n- [Testing for Cookie Attributes (OWASP)](https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes)\n- [RFC-6265 - Coolies](https://tools.ietf.org/html/rfc6265)\n\n</details>\n<details>\n<summary><b>Crawl Rules</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/KwQCjPf/wc-robots.png\" align=\"right\" />\n\n###### Description\nRobots.txt is a file found (usually) at the root of a domain, and is used to implement the Robots Exclusion Protocol (REP) to indicate which pages should be ignored by which crawlers and bots. It's good practice to avoid search engine crawlers from over-loading your site, but should not be used to keep pages out of search results (use the noindex meta tag or header instead).\n\n###### Use Cases\nIt's often useful to check the robots.txt file during an investigation, as it can sometimes disclose the directories and pages that the site owner doesn't want to be indexed, potentially because they contain sensitive information, or reveal the existence of otherwise hidden or unlinked directories. Additionally, understanding crawl rules may offer insights into a website's SEO strategies.\n\n###### Useful Links\n- [Google Search Docs - Robots.txt](https://developers.google.com/search/docs/advanced/robots/intro)\n- [Learn about robots.txt (via Moz.com)](https://moz.com/learn/seo/robotstxt)\n- [RFC-9309 -  Robots Exclusion Protocol](https://datatracker.ietf.org/doc/rfc9309/)\n- [Robots.txt - wiki](https://en.wikipedia.org/wiki/Robots_exclusion_standard)\n\n</details>\n<details>\n<summary><b>Headers</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/t3xcwP1/wc-headers.png\" align=\"right\" />\n\n###### Description\nThe Headers task involves extracting and interpreting the HTTP headers sent by the target website during the request-response cycle. HTTP headers are key-value pairs sent at the start of an HTTP response, or before the actual data. Headers contain important directives for how to handle the data being transferred, including cache policies, content types, encoding, server information, security policies, and more.\n\n###### Use Cases\nAnalyzing HTTP headers can provide significant insights in an OSINT investigation. Headers can reveal specific server configurations, chosen technologies, caching directives, and various security settings. This information can help to determine a website's underlying technology stack, server-side security measures, potential vulnerabilities, and general operational practices.\n\n###### Useful Links\n- [HTTP Headers - Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)\n- [RFC-7231 Section 7 - Headers](https://datatracker.ietf.org/doc/html/rfc7231#section-7)\n- [List of header response fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)\n- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)\n\n</details>\n<details>\n<summary><b>Quality Metrics</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/Kqg8rx7/wc-quality.png\" align=\"right\" />\n\n###### Description\nUsing Lighthouse, the Quality Metrics task measures the performance, accessibility, best practices, and SEO of the target website. This returns a simple checklist of 100 core metrics, along with a score for each category, to gauge the overall quality of a given site.\n\n###### Use Cases\nUseful for assessing a site's technical health, SEO issues, identify vulnerabilities, and ensure compliance with standards.\n\n###### Useful Links\n- [Lighthouse Docs](https://developer.chrome.com/docs/lighthouse/)\n- [Google Page Speed Tools](https://developers.google.com/speed)\n- [W3 Accessibility Tools](https://www.w3.org/WAI/test-evaluate/)\n- [Google Search Console](https://search.google.com/search-console)\n- [SEO Checker](https://www.seobility.net/en/seocheck/)\n- [PWA Builder](https://www.pwabuilder.com/)\n\n</details>\n<details>\n<summary><b>Server Location</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/cXH2hfR/wc-location.png\" align=\"right\" />\n\n###### Description\nThe Server Location task determines the physical location of the server hosting a given website based on its IP address. This is done by looking up the IP in a location database, which maps the IP to a lat + long of known data centers and ISPs. From the latitude and longitude, it's then possible to show additional contextual info, like a pin on the map, along with address, flag, time zone, currency, etc.\n\n###### Use Cases\nKnowing the server location is a good first step in better understanding a website. For site owners this aids in optimizing content delivery, ensuring compliance with data residency requirements, and identifying potential latency issues that may impact user experience in specific geographical regions. And for security researcher, assess the risk posed by specific regions or jurisdictions regarding cyber threats and regulations.\n\n###### Useful Links\n- [IP Locator](https://geobytes.com/iplocator/)\n- [Internet Geolocation - Wiki](https://en.wikipedia.org/wiki/Internet_geolocation)\n\n</details>\n<details>\n<summary><b>Associated Hosts</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/25j1sT7/wc-hosts.png\" align=\"right\" />\n\n###### Description\nThis task involves identifying and listing all domains and subdomains (hostnames) that are associated with the website's primary domain. This process often involves DNS enumeration to discover any linked domains and hostnames, as well as looking at known DNS records.\n\n###### Use Cases\nDuring an investigation, understanding the full scope of a target's web presence is critical. Associated domains could lead to uncovering related projects, backup sites, development/test sites, or services linked to the main site. These can sometimes provide additional information or potential security vulnerabilities. A comprehensive list of associated domains and hostnames can also give an overview of the organization's structure and online footprint.\n\n###### Useful Links\n- [DNS Enumeration - Wiki](https://en.wikipedia.org/wiki/DNS_enumeration)\n- [OWASP - Enumerate Applications on Webserver](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/01-Information_Gathering/04-Enumerate_Applications_on_Webserver)\n- [DNS Enumeration - DNS Dumpster](https://dnsdumpster.com/)\n- [Subdomain Finder](https://subdomainfinder.c99.nl/)\n\n</details>\n<details>\n<summary><b>Redirect Chain</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/hVVrmwh/wc-redirects.png\" align=\"right\" />\n\n###### Description\nThis task traces the sequence of HTTP redirects that occur from the original URL to the final destination URL. An HTTP redirect is a response with a status code that advises the client to go to another URL. Redirects can occur for several reasons, such as URL normalization (directing to the www version of the site), enforcing HTTPS, URL shorteners, or forwarding users to a new site location.\n\n###### Use Cases\nUnderstanding the redirect chain can be useful for several reasons. From a security perspective, long or complicated redirect chains can be a sign of potential security risks, such as unencrypted redirects in the chain. Additionally, redirects can impact website performance and SEO, as each redirect introduces additional round-trip-time (RTT). For OSINT, understanding the redirect chain can help identify relationships between different domains or reveal the use of certain technologies or hosting providers.\n\n###### Useful Links\n- [HTTP Redirects - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections)\n- [URL Redirection - Wiki](https://en.wikipedia.org/wiki/URL_redirection)\n- [301 Redirects explained](https://ahrefs.com/blog/301-redirects/)\n\n</details>\n<details>\n<summary><b>TXT Records</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/wyt21QN/wc-txt-records.png\" align=\"right\" />\n\n###### Description\nTXT records are a type of DNS record that provides text information to sources outside your domain. They can be used for a variety of purposes, such as verifying domain ownership, ensuring email security, and even preventing unauthorized changes to your website.\n\n###### Use Cases\nThe TXT records often reveal which external services and technologies are being used with a given domain. They may reveal details about the domain's email configuration, the use of specific services like Google Workspace or Microsoft 365, or security measures in place such as SPF and DKIM. Understanding these details can give an insight into the technologies used by the organization, their email security practices, and potential vulnerabilities.\n\n###### Useful Links\n- [TXT Records (via Cloudflare Learning)](https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/)\n- [TXT Records - Wiki](https://en.wikipedia.org/wiki/TXT_record)\n- [RFC-1464 - TXT Records](https://datatracker.ietf.org/doc/html/rfc1464)\n- [TXT Record Lookup (via MxToolbox)](https://mxtoolbox.com/TXTLookup.aspx)\n\n</details>\n<details>\n<summary><b>Server Status</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/V9CNLBK/wc-status.png\" align=\"right\" />\n\n###### Description\nChecks if a server is online and responding to requests.\n\n###### Use Cases\n\n\n###### Useful Links\n\n</details>\n<details>\n<summary><b>Open Ports</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/F8D1hmf/wc-ports.png\" align=\"right\" />\n\n###### Description\nOpen ports on a server are endpoints of communication which are available for establishing connections with clients. Each port corresponds to a specific service or protocol, such as HTTP (port 80), HTTPS (port 443), FTP (port 21), etc. The open ports on a server can be determined using techniques such as port scanning.\n\n###### Use Cases\nKnowing which ports are open on a server can provide information about the services running on that server, useful for understanding the potential vulnerabilities of the system, or for understanding the nature of the services the server is providing.\n\n###### Useful Links\n- [List of TCP & UDP Port Numbers](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers)\n- [NMAP - Port Scanning Basics](https://nmap.org/book/man-port-scanning-basics.html)\n\n</details>\n<details>\n<summary><b>Traceroute</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/M59qgxP/wc-trace-route.png\" align=\"right\" />\n\n###### Description\nTraceroute is a network diagnostic tool used to track in real-time the pathway taken by a packet of information from one system to another. It records each hop along the route, providing details about the IPs of routers and the delay at each point.\n\n###### Use Cases\nIn OSINT investigations, traceroute can provide insights about the routing paths and geography of the network infrastructure supporting a website or service. This can help to identify network bottlenecks, potential censorship or manipulation of network traffic, and give an overall sense of the network's structure and efficiency. Additionally, the IP addresses collected during the traceroute may provide additional points of inquiry for further OSINT investigation.\n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>Carbon Footprint</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/5v6fSyw/Screenshot-from-2023-07-29-19-07-50.png\" align=\"right\" />\n\n###### Description\nThis task calculates the estimated carbon footprint of a website. It's based on the amount of data being transferred and processed, and the energy usage of the servers that host and deliver the website. The larger the website and the more complex its features, the higher its carbon footprint is likely to be.\n\n###### Use Cases\nFrom an OSINT perspective, understanding a website's carbon footprint doesn't directly provide insights into its internal workings or the organization behind it. However, it can still be valuable data in broader analyses, especially in contexts where environmental impact is a consideration. For example, it can be useful for activists, researchers, or ethical hackers who are interested in the sustainability of digital infrastructure, and who want to hold organizations accountable for their environmental impact.\n\n###### Useful Links\n- [WebsiteCarbon - Carbon Calculator](https://www.websitecarbon.com/)\n- [The Green Web Foundation](https://www.thegreenwebfoundation.org/)\n- [The Eco Friendly Web Alliance](https://ecofriendlyweb.org/)\n- [Reset.org](https://en.reset.org/)\n- [Your website is killing the planet - via Wired](https://www.wired.co.uk/article/internet-carbon-footprint)\n\n</details>\n<details>\n<summary><b>Server Info</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/Mk1jx32/wc-server.png\" align=\"right\" />\n\n###### Description\nThis task retrieves various pieces of information about the server hosting the target website. This can include the server type (e.g., Apache, Nginx), the hosting provider, the Autonomous System Number (ASN), and more. The information is usually obtained through a combination of IP address lookups and analysis of HTTP response headers.\n\n###### Use Cases\nIn an OSINT context, server information can provide valuable clues about the organization behind a website. For instance, the choice of hosting provider could suggest the geographical region in which the organization operates, while the server type could hint at the technologies used by the organization. The ASN could also be used to find other domains hosted by the same organization.\n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>Whois Lookup</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/89WLp14/wc-domain.png\" align=\"right\" />\n\n###### Description\nThis task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.\n\n###### Use Cases\nIn an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.\n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>Domain Info</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/89WLp14/wc-domain.png\" align=\"right\" />\n\n###### Description\nThis task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.\n\n###### Use Cases\nIn an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.\n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>DNS Security Extensions</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/J54zVmQ/wc-dnssec.png\" align=\"right\" />\n\n###### Description\nWithout DNSSEC, it's possible for MITM attackers to spoof records and lead users to phishing sites. This is because the DNS system includes no built-in methods to verify that the response to the request was not forged, or that any other part of the process wasn’t interrupted by an attacker. The DNS Security Extensions (DNSSEC) secures DNS lookups by signing your DNS records using public keys, so browsers can detect if the response has been tampered with. Another solution to this issue is DoH (DNS over HTTPS) and DoT (DNS over TLD).\n\n###### Use Cases\nDNSSEC information provides insight into an organization's level of cybersecurity maturity and potential vulnerabilities, particularly around DNS spoofing and cache poisoning. If no DNS secururity (DNSSEC, DoH, DoT, etc) is implemented, this may provide an entry point for an attacker.\n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>Site Features</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/gP4P6kp/wc-features.png\" align=\"right\" />\n\n###### Description\nChecks which core features are present on a site. If a feature as marked as dead, that means it's not being actively used at load time\n\n###### Use Cases\nThis is useful to understand what a site is capable of, and what technologies to look for\n\n###### Useful Links\n\n</details>\n<details>\n<summary><b>HTTP Strict Transport Security</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png\" align=\"right\" />\n\n###### Description\nHTTP Strict Transport Security (HSTS) is a web security policy mechanism that helps protect websites against protocol downgrade attacks and cookie hijacking. A website can be included in the HSTS preload list by conforming to a set of requirements and then submitting itself to the list.\n\n###### Use Cases\nThere are several reasons why it's important for a site to be HSTS enabled:\n      1. User bookmarks or manually types http://example.com and is subject to a man-in-the-middle attacker\n        HSTS automatically redirects HTTP requests to HTTPS for the target domain\n      2. Web application that is intended to be purely HTTPS inadvertently contains HTTP links or serves content over HTTP\n        HSTS automatically redirects HTTP requests to HTTPS for the target domain\n      3. A man-in-the-middle attacker attempts to intercept traffic from a victim user using an invalid certificate and hopes the user will accept the bad certificate\n        HSTS does not allow a user to override the invalid certificate message\n        \n\n###### Useful Links\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n- [undefined](function link() { [native code] })\n\n</details>\n<details>\n<summary><b>DNS Server</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/tKpL8F9/Screenshot-from-2023-08-12-15-43-12.png\" align=\"right\" />\n\n###### Description\nThis check determines the DNS server(s) that the requested URL / IP resolves to. Also fires off a rudimentary check to see if the DNS server supports DoH, and weather it's vulnerable to DNS cache poisoning.\n\n###### Use Cases\n\n\n###### Useful Links\n\n</details>\n<details>\n<summary><b>Tech Stack</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/bBQSQNz/Screenshot-from-2023-08-12-15-43-46.png\" align=\"right\" />\n\n###### Description\nChecks what technologies a site is built with. This is done by fetching and parsing the site, then comparing it against a bit list of RegEx maintained by Wappalyzer to identify the unique fingerprints that different technologies leave.\n\n###### Use Cases\nIdentifying a website's tech stack aids in evaluating its security by exposing potential vulnerabilities, informs competitive analyses and development decisions, and can guide tailored marketing strategies. Ethical application of this knowledge is crucial to avoid harmful activities like data theft or unauthorized intrusion.\n\n###### Useful Links\n- [Wappalyzer fingerprints](https://github.com/wappalyzer/wappalyzer/tree/master/src/technologies)\n- [BuiltWith - Check what tech a site is using](https://builtwith.com/)\n\n</details>\n<details>\n<summary><b>Listed Pages</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/GtrCQYq/Screenshot-from-2023-07-21-12-28-38.png\" align=\"right\" />\n\n###### Description\nThis job finds and parses a site's listed sitemap. This file lists public sub-pages on the site, which the author wishes to be crawled by search engines. Sitemaps help with SEO, but are also useful for seeing all a sites public content at a glance.\n\n###### Use Cases\nUnderstand the structure of a site's public-facing content, and for site-owners, check that you're site's sitemap is accessible, parsable and contains everything you wish it to.\n\n###### Useful Links\n- [Learn about Sitemaps](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview)\n- [Sitemap XML spec](https://www.sitemaps.org/protocol.html)\n- [Sitemap tutorial](https://www.conductor.com/academy/xml-sitemap/)\n\n</details>\n<details>\n<summary><b>Security.txt</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/tq1FT5r/Screenshot-from-2023-07-24-20-31-21.png\" align=\"right\" />\n\n###### Description\nThe security.txt file tells researchers how they can responsibly disclose any security issues found on your site. The standard was proposed in RFC 9116, and specifies that this file should include a point of contact (email address), as well as optionally other info, like a link to the security disclosure policy, PGP key, proffered language, policy expiry and more. The file should be located at the root of your domain, either at /security.txt or /.well-known/security.txt.\n\n###### Use Cases\nThis is important, as without a defined point of contact a security researcher may be unable to report a critical security issue, or may use insecure or possibly public channels to do so. From an OSINT perspective, you may also glean info about a site including their posture on security, their CSAF provider, and meta data from the PGP public key.\n\n###### Useful Links\n- [securitytxt.org](https://securitytxt.org/)\n- [RFC-9116 Proposal](https://datatracker.ietf.org/doc/html/rfc9116)\n- [RFC-9116 History](https://datatracker.ietf.org/doc/rfc9116/)\n- [Security.txt (Wikipedia)](https://en.wikipedia.org/wiki/Security.txt)\n- [Example security.txt (Cloudflare)](https://www.cloudflare.com/.well-known/security.txt)\n- [Tutorial for creating security.txt (Pieter Bakker)](https://pieterbakker.com/implementing-security-txt/)\n\n</details>\n<details>\n<summary><b>Linked Pages</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/LtK14XR/Screenshot-from-2023-07-29-11-16-44.png\" align=\"right\" />\n\n###### Description\nDisplays all internal and external links found on a site, identified by the href attributes attached to anchor elements.\n\n###### Use Cases\nFor site owners, this is useful for diagnosing SEO issues, improving the site structure, understanding how content is inter-connected. External links can show partnerships, dependencies, and potential reputation risks. From a security standpoint, the outbound links can help identify any potential malicious or compromised sites the website is unknowingly linking to. Analyzing internal links can aid in understanding the site's structure and potentially uncover hidden or vulnerable pages which are not intended to be public. And for an OSINT investigator, it can aid in building a comprehensive understanding of the target, uncovering related entities, resources, or even potential hidden parts of the site.\n\n###### Useful Links\n- [W3C Link Checker](https://validator.w3.org/checklink)\n\n</details>\n<details>\n<summary><b>Social Tags</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/4srTT1w/Screenshot-from-2023-07-29-11-15-27.png\" align=\"right\" />\n\n###### Description\nWebsites can include certain meta tags, that tell search engines and social media platforms what info to display. This usually includes a title, description, thumbnail, keywords, author, social accounts, etc.\n\n###### Use Cases\nAdding this data to your site will boost SEO, and as an OSINT researcher it can be useful to understand how a given web app describes itself\n\n###### Useful Links\n- [SocialSharePreview.com](https://socialsharepreview.com/)\n- [The guide to social meta tags](https://css-tricks.com/essential-meta-tags-social-media/)\n- [Web.dev metadata tags](https://web.dev/learn/html/metadata/)\n- [Open Graph Protocol](https://ogp.me/)\n- [Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards)\n- [Facebook Open Graph](https://developers.facebook.com/docs/sharing/webmasters)\n\n</details>\n<details>\n<summary><b>Email Configuration</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/yqhwx5G/Screenshot-from-2023-07-29-18-22-20.png\" align=\"right\" />\n\n###### Description\nDMARC (Domain-based Message Authentication, Reporting & Conformance): DMARC is an email authentication protocol that works with SPF and DKIM to prevent email spoofing and phishing. It allows domain owners to specify how to handle unauthenticated mail via a published policy in DNS, and provides a way for receiving mail servers to send feedback about emails' compliance to the sender. BIMI (Brand Indicators for Message Identification): BIMI is an emerging email standard that enables organizations to display a logo in their customers' email clients automatically. BIMI ties the logo to the domain's DMARC record, providing another level of visual assurance to recipients that the email is legitimate. DKIM (DomainKeys Identified Mail): DKIM is an email security standard designed to make sure that messages were not altered in transit between the sending and recipient servers. It uses digital signatures linked to the domain of the sender to verify the sender and ensure message integrity. SPF (Sender Policy Framework): SPF is an email authentication method designed to prevent email spoofing. It specifies which mail servers are authorized to send email on behalf of a domain by creating a DNS record. This helps protect against spam by providing a way for receiving mail servers to check that incoming mail from a domain comes from a host authorized by that domain's administrators.\n\n###### Use Cases\nThis information is helpful for researchers as it helps assess a domain's email security posture, uncover potential vulnerabilities, and verify the legitimacy of emails for phishing detection. These details can also provide insight into the hosting environment, potential service providers, and the configuration patterns of a target organization, assisting in investigative efforts.\n\n###### Useful Links\n- [Intro to DMARC, DKIM, and SPF (via Cloudflare)](https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/)\n- [EasyDMARC Domain Scanner](https://easydmarc.com/tools/domain-scanner)\n- [MX Toolbox](https://mxtoolbox.com/)\n- [RFC-7208 - SPF](https://datatracker.ietf.org/doc/html/rfc7208)\n- [RFC-6376 - DKIM](https://datatracker.ietf.org/doc/html/rfc6376)\n- [RFC-7489 - DMARC](https://datatracker.ietf.org/doc/html/rfc7489)\n- [BIMI Group](https://bimigroup.org/)\n\n</details>\n<details>\n<summary><b>Firewall Detection</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/MfcxQt2/Screenshot-from-2023-08-12-15-40-52.png\" align=\"right\" />\n\n###### Description\nA WAF or web application firewall helps protect web applications by filtering and monitoring HTTP traffic between a web application and the Internet. It typically protects web applications from attacks such as cross-site forgery, cross-site-scripting (XSS), file inclusion, and SQL injection, among others.\n\n###### Use Cases\nIt's useful to understand if a site is using a WAF, and which firewall software / service it is using, as this provides an insight into the sites protection against several attack vectors, but also may reveal vulnerabilities in the firewall itself.\n\n###### Useful Links\n- [What is a WAF (via Cloudflare Learning)](https://www.cloudflare.com/learning/ddos/glossary/web-application-firewall-waf/)\n- [OWASP - Web Application Firewalls](https://owasp.org/www-community/Web_Application_Firewall)\n- [Web Application Firewall Best Practices](https://owasp.org/www-pdf-archive/Best_Practices_Guide_WAF_v104.en.pdf)\n- [WAF - Wiki](https://en.wikipedia.org/wiki/Web_application_firewall)\n\n</details>\n<details>\n<summary><b>HTTP Security Features</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/LP05HMV/Screenshot-from-2023-08-12-15-40-28.png\" align=\"right\" />\n\n###### Description\nCorrectly configured security HTTP headers adds a layer of protection against common attacks to your site. The main headers to be aware of are: HTTP Strict Transport Security (HSTS): Enforces the use of HTTPS, mitigating man-in-the-middle attacks and protocol downgrade attempts. Content Security Policy (CSP): Constrains web page resources to prevent cross-site scripting and data injection attacks. X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content type, curbing MIME-type confusion attacks. X-Frame-Options: Protects users from clickjacking attacks by controlling whether a browser should render the page in a `<frame>`, `<iframe>`, `<embed>`, or `<object>`. \n\n###### Use Cases\nReviewing security headers is important, as it offers insights into a site's defensive posture and potential vulnerabilities, enabling proactive mitigation and ensuring compliance with security best practices.\n\n###### Useful Links\n- [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/)\n- [HTTP Header Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)\n- [content-security-policy.com](https://content-security-policy.com/)\n- [resourcepolicy.fyi](https://resourcepolicy.fyi/)\n- [HTTP Security Headers](https://securityheaders.com/)\n- [Mozilla Observatory](https://observatory.mozilla.org/)\n- [CSP Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)\n- [HSTS Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)\n- [X-Content-Type-Options Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)\n- [X-Frame-Options Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)\n- [X-XSS-Protection Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection)\n\n</details>\n<details>\n<summary><b>Archive History</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/nB9szT1/Screenshot-from-2023-08-14-22-31-16.png\" align=\"right\" />\n\n###### Description\nFetches full history of archives from the Wayback machine\n\n###### Use Cases\nThis is useful for understanding the history of a site, and how it has changed over time. It can also be useful for finding old versions of a site, or for finding content that has been removed.\n\n###### Useful Links\n- [Wayback Machine](https://archive.org/web/)\n\n</details>\n<details>\n<summary><b>Global Ranking</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/nkbczgb/Screenshot-from-2023-08-14-22-02-40.png\" align=\"right\" />\n\n###### Description\nThis check shows the global rank of the requested site. This is only accurate for websites which are in the top 100 million list. We're using data from the Tranco project (see below), which collates the top sites on the web from Umbrella, Majestic, Quantcast, the Chrome User Experience Report and Cloudflare Radar.\n\n###### Use Cases\nKnowing a websites overall global rank can be useful for understanding the scale of the site, and for comparing it to other sites. It can also be useful for understanding the relative popularity of a site, and for identifying potential trends.\n\n###### Useful Links\n- [Tranco List](https://tranco-list.eu/)\n- [Tranco Research Paper](https://tranco-list.eu/assets/tranco-ndss19.pdf)\n\n</details>\n<details>\n<summary><b>Block Detection</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png\" align=\"right\" />\n\n###### Description\nChecks access to the URL using 10+ of the most popular privacy, malware and parental control blocking DNS servers.\n\n###### Use Cases\n\n\n###### Useful Links\n- [ThreatJammer Lists](https://threatjammer.com/osint-lists)\n\n</details>\n<details>\n<summary><b>Malware & Phishing Detection</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png\" align=\"right\" />\n\n###### Description\nChecks if a site appears in several common malware and phishing lists, to determine it's threat level.\n\n###### Use Cases\nKnowing if a site is listed as a threat by any of these services can be useful for understanding the reputation of a site, and for identifying potential trends.\n\n###### Useful Links\n- [URLHaus](https://urlhaus-api.abuse.ch/)\n- [PhishTank](https://www.phishtank.com/)\n\n</details>\n<details>\n<summary><b>TLS Cipher Suites</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png\" align=\"right\" />\n\n###### Description\nThese are combinations of cryptographic algorithms used by the server to establish a secure connection. It includes the key exchange algorithm, bulk encryption algorithm, MAC algorithm, and PRF (pseudorandom function).\n\n###### Use Cases\nThis is important info to test for from a security perspective. Because a cipher suite is only as secure as the algorithms that it contains. If the version of encryption or authentication algorithm in a cipher suite have known vulnerabilities the cipher suite and TLS connection may then vulnerable to a downgrade or other attack\n\n###### Useful Links\n- [sslscan2 CLI](https://github.com/rbsec/sslscan)\n- [ssl-enum-ciphers (NPMAP script)](https://nmap.org/nsedoc/scripts/ssl-enum-ciphers.html)\n\n</details>\n<details>\n<summary><b>TLS Security Config</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png\" align=\"right\" />\n\n###### Description\nThis uses guidelines from Mozilla's TLS Observatory to check the security of the TLS configuration. It checks for bad configurations, which may leave the site vulnerable to attack, as well as giving advice on how to fix. It will also give suggestions around outdated and modern TLS configs\n\n###### Use Cases\nUnderstanding issues with a site's TLS configuration will help you address potential vulnerabilities, and ensure the site is using the latest and most secure TLS configuration.\n\n###### Useful Links\n\n</details>\n<details>\n<summary><b>TLS Handshake Simulation</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png\" align=\"right\" />\n\n###### Description\nThis simulates how different clients (browsers, operating systems) would perform a TLS handshake with the server. It helps identify compatibility issues and insecure configurations.\n\n###### Use Cases\n\n\n###### Useful Links\n- [TLS Handshakes (via Cloudflare Learning)](https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/)\n- [SSL Test (via SSL Labs)](https://www.ssllabs.com/ssltest/)\n\n</details>\n<details>\n<summary><b>Screenshot</b></summary>\n\n<img width=\"300\" src=\"https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png\" align=\"right\" />\n\n###### Description\nThis check takes a screenshot of webpage that the requested URL / IP resolves to, and displays it.\n\n###### Use Cases\nThis may be useful to see what a given website looks like, free of the constraints of your browser, IP, or location.\n\n\n</details>\n\n</details>\n\nRead more here: **[web-check.xyz/about](https://web-check.xyz/about)**\n\n---\n\n## Usage\n\n### Deployment\n\n### Deploying - Option #1: Netlify\n\nClick the button below, to deploy to Netlify 👇\n\n[![Deploy to Netlify](https://img.shields.io/badge/Deploy-Netlify-%2330c8c9?style=for-the-badge&logo=netlify&labelColor=1e0e41 'Deploy Web-Check to Netlify, via 1-Click Script')](https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check)\n\n### Deploying - Option #2: Vercel\n\nClick the button below, to deploy to Vercel 👇\n\n[![Deploy with Vercel](https://img.shields.io/badge/Deploy-Vercel-%23ffffff?style=for-the-badge&logo=vercel&labelColor=1e0e41)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flissy93%2Fweb-check&project-name=web-check&repository-name=web-check-fork&demo-title=Web-Check%20Demo&demo-description=Check%20out%20web-check.xyz%20to%20see%20a%20live%20demo%20of%20this%20application%20running.&demo-url=https%3A%2F%2Fweb-check.xyz&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2FLissy93%2Fweb-check%2Fmaster%2F.github%2Fscreenshots%2Fweb-check-screenshot10.png)\n\n### Deploying - Option #3: Docker\n\nRun `docker run -p 3000:3000 lissy93/web-check`, then open [`localhost:3000`](http://localhost:3000)\n\n<details>\n<summary>Docker Options</summary>\n\nYou can get the Docker image from:\n- DockerHub: [`lissy93/web-check`](https://hub.docker.com/r/lissy93/web-check)\n- GHCR: [`ghcr.io/lissy93/web-check`](https://github.com/Lissy93/web-check/pkgs/container/web-check)\n- Or build the image yourself by cloning the repo and running `docker build -t web-check .`\n\n</details>\n\n### Deploying - Option #4: From Source\n\nInstall the prerequisites listed in the [Developing](#developing) section, then run: \n\n```bash\ngit clone https://github.com/Lissy93/web-check.git  # Download the code from GitHub\ncd web-check                                        # Navigate into the project dir\nyarn install                                        # Install the NPM dependencies\nyarn build                                          # Build the app for production\nyarn serve                                          # Start the app (API and GUI)\n```\n\n---\n\n### Configuring\n\nBy default, no configuration is needed.\n\nBut there are some optional environmental variables that you can set to give you access to some additional checks, or to increase rate-limits for some checks that use external APIs.\n\n**API Keys & Credentials**:\n\nKey | Value\n---|---\n`GOOGLE_CLOUD_API_KEY` | A Google API key ([get here](https://cloud.google.com/api-gateway/docs/authenticate-api-keys)). This can be used to return quality metrics for a site\n`REACT_APP_SHODAN_API_KEY` | A Shodan API key ([get here](https://account.shodan.io/)). This will show associated host names for a given domain\n`REACT_APP_WHO_API_KEY` | A WhoAPI key ([get here](https://whoapi.com/)). This will show more comprehensive WhoIs records than the default job\n\n<details>\n  <summary><small>Full / Upcoming Vals</small></summary>\n  \n- `GOOGLE_CLOUD_API_KEY` - A Google API key ([get here](https://cloud.google.com/api-gateway/docs/authenticate-api-keys)). This can be used to return quality metrics for a site\n- `REACT_APP_SHODAN_API_KEY` - A Shodan API key ([get here](https://account.shodan.io/)). This will show associated host names for a given domain\n- `REACT_APP_WHO_API_KEY` - A WhoAPI key ([get here](https://whoapi.com/)). This will show more comprehensive WhoIs records than the default job\n- `SECURITY_TRAILS_API_KEY` - A Security Trails API key ([get here](https://securitytrails.com/corp/api)). This will show org info associated with the IP\n- `CLOUDMERSIVE_API_KEY` - API key for Cloudmersive ([get here](https://account.cloudmersive.com/)). This will show known threats associated with the IP\n- `TRANCO_USERNAME` - A Tranco email ([get here](https://tranco-list.eu/)). This will show the rank of a site, based on traffic\n- `TRANCO_API_KEY` - A Tranco API key ([get here](https://tranco-list.eu/)). This will show the rank of a site, based on traffic\n- `URL_SCAN_API_KEY` - A URLScan API key ([get here](https://urlscan.io/)). This will fetch miscalanious info about a site\n- `BUILT_WITH_API_KEY` - A BuiltWith API key ([get here](https://api.builtwith.com/)). This will show the main features of a site\n- `TORRENT_IP_API_KEY` - A torrent API key ([get here](https://iknowwhatyoudownload.com/en/api/)). This will show torrents downloaded by an IP\n  \n</details>\n\n**Configuration Settings**:\n\nKey | Value\n---|---\n`PORT` | Port to serve the API, when running server.js (e.g. `3000`)\n`API_ENABLE_RATE_LIMIT` | Enable rate-limiting for the /api endpoints (e.g. `true`)\n`API_TIMEOUT_LIMIT` | The timeout limit for API requests, in milliseconds (e.g. `10000`)\n`API_CORS_ORIGIN` | Enable CORS, by setting your allowed hostname(s) here (e.g. `example.com`)\n`CHROME_PATH` | The path the Chromium executable (e.g. `/usr/bin/chromium`)\n`DISABLE_GUI` | Disable the GUI, and only serve the API (e.g. `false`)\n`REACT_APP_API_ENDPOINT` | The endpoint for the API, either local or remote (e.g. `/api`)\n\nAll values are optional.\n\nYou can add these as environmental variables. Either put them directly into an `.env` file in the projects root, or via the Netlify / Vercel UI, or by passing to the Docker container with the --env flag, or using your own environmental variable management system\n\nNote that keys that are prefixed with `REACT_APP_` are used client-side, and as such they must be scoped correctly with minimum privileges, since may be made visible when intercepting browser <-> server network requests\n\n---\n\n### Developing\n\n1. Clone the repo, `git clone git@github.com:Lissy93/web-check.git`\n2. Cd into it, `cd web-check`\n3. Install dependencies: `yarn`\n4. Start the dev server, with `yarn dev`\n\nYou'll need [Node.js](https://nodejs.org/en) (V 18.16.1 or later) installed, plus [yarn](https://yarnpkg.com/getting-started/install) as well as [git](https://git-scm.com/).\nSome checks also require `chromium`, `traceroute` and `dns` to be installed within your environment. These jobs will just be skipped if those packages aren't present.\n\n\n---\n\n## Community\n\n### Contributing\n\nContributions of any kind are very welcome, and would be much appreciated.\nFor Code of Conduct, see [Contributor Convent](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).\n\nTo get started, fork the repo, make your changes, add, commit and push the code, then come back here to open a pull request. If you're new to GitHub or open source, [this guide](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3#let-s-make-our-first-pull-request-) or the [git docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) may help you get started, but feel free to reach out if you need any support.\n\n[![Submit a PR](https://img.shields.io/badge/Submit_a_PR-GitHub-%23060606?style=for-the-badge&logo=github&logoColor=fff)](https://github.com/Lissy93/web-check/compare)\n\n\n### Reporting Bugs\n\nIf you've found something that doesn't work as it should, or would like to suggest a new feature, then go ahead and raise a ticket on GitHub.\nFor bugs, please outline the steps needed to reproduce, and include relevant info like system info and resulting logs.\n\n[![Raise an Issue](https://img.shields.io/badge/Raise_an_Issue-GitHub-%23060606?style=for-the-badge&logo=github&logoColor=fff)](https://github.com/Lissy93/web-check/issues/new/choose)\n\n### Supporting\n\nThe app will remain 100% free and open source.\nBut due to the amount of traffic that the hosted instance gets, the lambda function usage is costing about $25/month.\nAny help with covering the costs via GitHub Sponsorship would be much appreciated.\nIt's thanks to the support of the community that this project is able to be freely available for everyone :)\n\n[![Sponsor Lissy93 on GitHub](https://img.shields.io/badge/Sponsor_on_GitHub-Lissy93-%23ff4dda?style=for-the-badge&logo=githubsponsors&logoColor=ff4dda)](https://github.com/sponsors/Lissy93)\n\n\n### Contributors\n\nCredit to the following users for contributing to Web-Check\n\n<!-- readme: contributors -start -->\n<table>\n\t<tbody>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/Lissy93\">\n                    <img src=\"https://avatars.githubusercontent.com/u/1862727?v=4\" width=\"80;\" alt=\"Lissy93\"/>\n                    <br />\n                    <sub><b>Alicia Sykes</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/liss-bot\">\n                    <img src=\"https://avatars.githubusercontent.com/u/87835202?v=4\" width=\"80;\" alt=\"liss-bot\"/>\n                    <br />\n                    <sub><b>Alicia Bot</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/n0a\">\n                    <img src=\"https://avatars.githubusercontent.com/u/14150948?v=4\" width=\"80;\" alt=\"n0a\"/>\n                    <br />\n                    <sub><b>Denis Simonov</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/muni106\">\n                    <img src=\"https://avatars.githubusercontent.com/u/65845442?v=4\" width=\"80;\" alt=\"muni106\"/>\n                    <br />\n                    <sub><b>Mounir Samite</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/ChrisCarini\">\n                    <img src=\"https://avatars.githubusercontent.com/u/6374067?v=4\" width=\"80;\" alt=\"ChrisCarini\"/>\n                    <br />\n                    <sub><b>Chris Carini</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/bolens\">\n                    <img src=\"https://avatars.githubusercontent.com/u/1218380?v=4\" width=\"80;\" alt=\"bolens\"/>\n                    <br />\n                    <sub><b>Michael Bolens</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/HeroGamers\">\n                    <img src=\"https://avatars.githubusercontent.com/u/15278940?v=4\" width=\"80;\" alt=\"HeroGamers\"/>\n                    <br />\n                    <sub><b>Marcus Sand</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/jinnabaalu\">\n                    <img src=\"https://avatars.githubusercontent.com/u/11784253?v=4\" width=\"80;\" alt=\"jinnabaalu\"/>\n                    <br />\n                    <sub><b>Jinna Baalu</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/GreyXor\">\n                    <img src=\"https://avatars.githubusercontent.com/u/79602273?v=4\" width=\"80;\" alt=\"GreyXor\"/>\n                    <br />\n                    <sub><b>GreyXor</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/brianteeman\">\n                    <img src=\"https://avatars.githubusercontent.com/u/1296369?v=4\" width=\"80;\" alt=\"brianteeman\"/>\n                    <br />\n                    <sub><b>Brian Teeman</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/vitalykarasik\">\n                    <img src=\"https://avatars.githubusercontent.com/u/7628795?v=4\" width=\"80;\" alt=\"vitalykarasik\"/>\n                    <br />\n                    <sub><b>Vitaly Karasik</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/Its-Just-Nans\">\n                    <img src=\"https://avatars.githubusercontent.com/u/56606507?v=4\" width=\"80;\" alt=\"Its-Just-Nans\"/>\n                    <br />\n                    <sub><b>n4n5</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/robinson\">\n                    <img src=\"https://avatars.githubusercontent.com/u/237874?v=4\" width=\"80;\" alt=\"robinson\"/>\n                    <br />\n                    <sub><b>Lth</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/abhishekMuge\">\n                    <img src=\"https://avatars.githubusercontent.com/u/49590582?v=4\" width=\"80;\" alt=\"abhishekMuge\"/>\n                    <br />\n                    <sub><b>Abhishek Muge</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/UlisesGascon\">\n                    <img src=\"https://avatars.githubusercontent.com/u/5110813?v=4\" width=\"80;\" alt=\"UlisesGascon\"/>\n                    <br />\n                    <sub><b>Ulises Gascón</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/PhiRequiem\">\n                    <img src=\"https://avatars.githubusercontent.com/u/1323576?v=4\" width=\"80;\" alt=\"PhiRequiem\"/>\n                    <br />\n                    <sub><b>PhiRequiem</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/ntaiko\">\n                    <img src=\"https://avatars.githubusercontent.com/u/108784453?v=4\" width=\"80;\" alt=\"ntaiko\"/>\n                    <br />\n                    <sub><b>Nikolaos G. Ntaiko</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/Myzel394\">\n                    <img src=\"https://avatars.githubusercontent.com/u/50424412?v=4\" width=\"80;\" alt=\"Myzel394\"/>\n                    <br />\n                    <sub><b>Myzel394</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/murrple-1\">\n                    <img src=\"https://avatars.githubusercontent.com/u/5559656?v=4\" width=\"80;\" alt=\"murrple-1\"/>\n                    <br />\n                    <sub><b>Murray Christopherson</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/t3chn0m4g3\">\n                    <img src=\"https://avatars.githubusercontent.com/u/4318452?v=4\" width=\"80;\" alt=\"t3chn0m4g3\"/>\n                    <br />\n                    <sub><b>Marco Ochse</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/treatmesubj\">\n                    <img src=\"https://avatars.githubusercontent.com/u/39680353?v=4\" width=\"80;\" alt=\"treatmesubj\"/>\n                    <br />\n                    <sub><b>John Hupperts</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/eltociear\">\n                    <img src=\"https://avatars.githubusercontent.com/u/22633385?v=4\" width=\"80;\" alt=\"eltociear\"/>\n                    <br />\n                    <sub><b>Ikko Eltociear Ashimine</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/Gertje823\">\n                    <img src=\"https://avatars.githubusercontent.com/u/36937387?v=4\" width=\"80;\" alt=\"Gertje823\"/>\n                    <br />\n                    <sub><b>Gertje823</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/epreston\">\n                    <img src=\"https://avatars.githubusercontent.com/u/347224?v=4\" width=\"80;\" alt=\"epreston\"/>\n                    <br />\n                    <sub><b>Ed Preston</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/dimitri-kandassamy\">\n                    <img src=\"https://avatars.githubusercontent.com/u/21193806?v=4\" width=\"80;\" alt=\"dimitri-kandassamy\"/>\n                    <br />\n                    <sub><b>Dimitri Kandassamy</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/0xflotus\">\n                    <img src=\"https://avatars.githubusercontent.com/u/26602940?v=4\" width=\"80;\" alt=\"0xflotus\"/>\n                    <br />\n                    <sub><b>0xflotus</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t<tbody>\n</table>\n<!-- readme: contributors -end -->\n\n### Sponsors\n\nHuge thanks to these wonderful people, who sponsor me on GitHub, their support helps cover the costs required to keep Web-Check and my other projects free for everyone. Consider joining them, by [sponsoring me on GitHub](https://github.com/sponsors/Lissy93) if you're able.\n\n<!-- readme: sponsors -start -->\n<table>\n\t<tbody>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/vincentkoc\">\n                    <img src=\"https://avatars.githubusercontent.com/u/25068?v=4\" width=\"80;\" alt=\"vincentkoc\"/>\n                    <br />\n                    <sub><b>Vincent Koc</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/AnandChowdhary\">\n                    <img src=\"https://avatars.githubusercontent.com/u/2841780?u=747e554b3a7f12eb20b7910e1c87d817844f714f&v=4\" width=\"80;\" alt=\"AnandChowdhary\"/>\n                    <br />\n                    <sub><b>Anand Chowdhary</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/shrippen\">\n                    <img src=\"https://avatars.githubusercontent.com/u/2873570?v=4\" width=\"80;\" alt=\"shrippen\"/>\n                    <br />\n                    <sub><b>Shrippen</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/bile0026\">\n                    <img src=\"https://avatars.githubusercontent.com/u/5022496?u=aec96ad173c0ea9baaba93807efa8a848af6595c&v=4\" width=\"80;\" alt=\"bile0026\"/>\n                    <br />\n                    <sub><b>Zach Biles</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/UlisesGascon\">\n                    <img src=\"https://avatars.githubusercontent.com/u/5110813?u=3c41facd8aa26154b9451de237c34b0f78d672a5&v=4\" width=\"80;\" alt=\"UlisesGascon\"/>\n                    <br />\n                    <sub><b>Ulises Gascón</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/digitalarche\">\n                    <img src=\"https://avatars.githubusercontent.com/u/6546135?u=564756d7f44ab2206819eb3148f6d822673f5066&v=4\" width=\"80;\" alt=\"digitalarche\"/>\n                    <br />\n                    <sub><b>Digital Archeology</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/InDieTasten\">\n                    <img src=\"https://avatars.githubusercontent.com/u/7047377?u=8d8f8017628b38bc46dcbf3620e194b01d3fb2d1&v=4\" width=\"80;\" alt=\"InDieTasten\"/>\n                    <br />\n                    <sub><b>InDieTasten</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/araguaci\">\n                    <img src=\"https://avatars.githubusercontent.com/u/7318668?v=4\" width=\"80;\" alt=\"araguaci\"/>\n                    <br />\n                    <sub><b>Araguaci</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/bmcgonag\">\n                    <img src=\"https://avatars.githubusercontent.com/u/7346620?u=2a0f9284f3e12ac1cc15288c254d1ec68a5081e8&v=4\" width=\"80;\" alt=\"bmcgonag\"/>\n                    <br />\n                    <sub><b>Brian McGonagill</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/arcestia\">\n                    <img src=\"https://avatars.githubusercontent.com/u/7936962?v=4\" width=\"80;\" alt=\"arcestia\"/>\n                    <br />\n                    <sub><b>Laurensius Jeffrey</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/vlad-tim\">\n                    <img src=\"https://avatars.githubusercontent.com/u/11474041?u=eee43705b54d2ec9f51fc4fcce5ad18dd17c87e4&v=4\" width=\"80;\" alt=\"vlad-tim\"/>\n                    <br />\n                    <sub><b>Vlad</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/helixzz\">\n                    <img src=\"https://avatars.githubusercontent.com/u/12218889?u=d06d0c103dfbdb99450623064f7da3c5a3675fb6&v=4\" width=\"80;\" alt=\"helixzz\"/>\n                    <br />\n                    <sub><b>HeliXZz</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/mryesiller\">\n                    <img src=\"https://avatars.githubusercontent.com/u/24632172?u=0d20f2d615158f87cd60a3398d3efb026c32f291&v=4\" width=\"80;\" alt=\"mryesiller\"/>\n                    <br />\n                    <sub><b>Göksel Yeşiller</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/sushibait\">\n                    <img src=\"https://avatars.githubusercontent.com/u/26634535?v=4\" width=\"80;\" alt=\"sushibait\"/>\n                    <br />\n                    <sub><b>Shiverme Timbers</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/getumbrel\">\n                    <img src=\"https://avatars.githubusercontent.com/u/59408891?v=4\" width=\"80;\" alt=\"getumbrel\"/>\n                    <br />\n                    <sub><b>Umbrel</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/OlliVHH\">\n                    <img src=\"https://avatars.githubusercontent.com/u/84959562?v=4\" width=\"80;\" alt=\"OlliVHH\"/>\n                    <br />\n                    <sub><b>HamburgerJung</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/frankdez93\">\n                    <img src=\"https://avatars.githubusercontent.com/u/87549420?v=4\" width=\"80;\" alt=\"frankdez93\"/>\n                    <br />\n                    <sub><b>Frankdez93</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/terminaltrove\">\n                    <img src=\"https://avatars.githubusercontent.com/u/121595180?v=4\" width=\"80;\" alt=\"terminaltrove\"/>\n                    <br />\n                    <sub><b>Terminal Trove</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t\t<tr>\n            <td align=\"center\">\n                <a href=\"https://github.com/st617\">\n                    <img src=\"https://avatars.githubusercontent.com/u/128325650?v=4\" width=\"80;\" alt=\"st617\"/>\n                    <br />\n                    <sub><b>st617</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/hudsonrock-partnerships\">\n                    <img src=\"https://avatars.githubusercontent.com/u/163282900?u=5f2667f7fe5d284ac7a2da6b0800ea8970b0fcbf&v=4\" width=\"80;\" alt=\"hudsonrock-partnerships\"/>\n                    <br />\n                    <sub><b>hudsonrock-partnerships</b></sub>\n                </a>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://github.com/CarterPerez-dev\">\n                    <img src=\"https://avatars.githubusercontent.com/u/188120068?v=4\" width=\"80;\" alt=\"CarterPerez-dev\"/>\n                    <br />\n                    <sub><b>Carter Perez</b></sub>\n                </a>\n            </td>\n\t\t</tr>\n\t<tbody>\n</table>\n<!-- readme: sponsors -end -->\n\n---\n\n## License\n\n> _**[Lissy93/Web-Check](https://github.com/Lissy93/web-check)** is licensed under [MIT](https://github.com/Lissy93/web-check/blob/HEAD/LICENSE) © [Alicia Sykes](https://aliciasykes.com) 2023._<br>\n> <sup align=\"right\">For information, see <a href=\"https://tldrlegal.com/license/mit-license\">TLDR Legal > MIT</a></sup>\n\n<details>\n<summary>Expand License</summary>\n\n```\nThe MIT License (MIT)\nCopyright (c) Alicia Sykes <alicia@omg.com> \n\nPermission is hereby granted, free of charge, to any person obtaining a copy \nof this software and associated documentation files (the \"Software\"), to deal \nin the Software without restriction, including without limitation the rights \nto use, copy, modify, merge, publish, distribute, sub-license, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished \nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included install \ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANT ABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n```\n\n[![View Dependency Licenses & SBOM on FOSSA](https://app.fossa.com/api/projects/git%2Bgithub.com%2FLissy93%2Fweb-check.svg?type=large&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2FLissy93%2Fweb-check?ref=badge_large&issueType=license)\n\n</details>\n\n\n<!-- License + Copyright -->\n<p  align=\"center\">\n  <i>© <a href=\"https://aliciasykes.com\">Alicia Sykes</a> 2023</i><br>\n  <i>Licensed under <a href=\"https://gist.github.com/Lissy93/143d2ee01ccc5c052a17\">MIT</a></i><br>\n  <a href=\"https://github.com/lissy93\"><img src=\"https://i.ibb.co/4KtpYxb/octocat-clean-mini.png\" /></a><br>\n  <sup>Thanks for visiting :)</sup>\n</p>\n\n<!-- Dinosaurs are Awesome -->\n<!-- \n                        . - ~ ~ ~ - .\n      ..     _      .-~               ~-.\n     //|     \\ `..~                      `.\n    || |      }  }              /       \\  \\\n(\\   \\\\ \\~^..'                 |         }  \\\n \\`.-~  o      /       }       |        /    \\\n (__          |       /        |       /      `.\n  `- - ~ ~ -._|      /_ - ~ ~ ^|      /- _      `.\n              |     /          |     /     ~-.     ~- _\n              |_____|          |_____|         ~ - . _ _~_-_\n-->\n\n"
  },
  {
    "path": ".github/screenshots/README.md",
    "content": "![Screenshot](https://raw.githubusercontent.com/Lissy93/web-check/HEAD/.github/screenshots/web-check-screenshot3.png)\n"
  },
  {
    "path": ".github/workflows/credits.yml",
    "content": "# Inserts list of community members into ./README.md\nname: 💓 Inserts Contributors & Sponsors\non:\n  workflow_dispatch: # Manual dispatch\n  schedule:\n    - cron: '45 1 * * 0' # At 01:45 on Sunday.\n\njobs:\n  # Job #1 - Fetches sponsors and inserts table into readme\n  insert-sponsors:\n    runs-on: ubuntu-latest\n    name: Inserts Sponsors 💓\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Updates readme with sponsors\n        uses: JamesIves/github-sponsors-readme-action@v1\n        with:\n          token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n          file: .github/README.md\n\n  # Job #2 - Fetches contributors and inserts table into readme\n  insert-contributors:\n    runs-on: ubuntu-latest\n    name: Inserts Contributors 💓\n    steps:\n      - name: Updates readme with contributors\n        uses: akhilmhdh/contributors-readme-action@v2.3.10\n        env:\n          GITHUB_TOKEN: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n        with:\n          image_size: 80\n          readme_path: .github/README.md\n          columns_per_row: 6\n          commit_message: 'docs: Updates contributors list'\n          committer_username: liss-bot\n          committer_email: liss-bot@d0h.co\n"
  },
  {
    "path": ".github/workflows/deploy-aws.yml",
    "content": "name: 🚀 Deploy to AWS\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n    tags:\n      - '*'\n    paths:\n      - api/**\n      - serverless.yml\n      - package.json\n      - .github/workflows/deploy-aws.yml\n\njobs:\n  deploy-api:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: 16\n\n    - name: Cache node_modules\n      uses: actions/cache@v4\n      with:\n        path: node_modules\n        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}\n        restore-keys: |\n          ${{ runner.os }}-yarn-\n\n    - name: Create GitHub deployment for API\n      uses: chrnorm/deployment-action@releases/v2\n      id: deployment_api\n      with:\n        token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n        environment: AWS (Backend API)\n        ref: ${{ github.ref }}\n      \n    - name: Install Serverless CLI and dependencies\n      run: |\n        npm i -g serverless\n        yarn\n\n    - name: Deploy to AWS\n      env:\n        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n        AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}\n      run: serverless deploy\n      \n    - name: Update GitHub deployment status (API)\n      if: always()\n      uses: chrnorm/deployment-status@v2\n      with:\n        token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n        state: \"${{ job.status }}\"\n        deployment_id: ${{ steps.deployment_api.outputs.deployment_id }}\n        ref: ${{ github.ref }}\n\n  deploy-frontend:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: 16\n    \n    - name: Cache node_modules\n      uses: actions/cache@v4\n      with:\n        path: node_modules\n        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}\n        restore-keys: |\n          ${{ runner.os }}-yarn-\n\n    - name: Create GitHub deployment for Frontend\n      uses: chrnorm/deployment-action@v2\n      id: deployment_frontend\n      with:\n        token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n        environment: AWS (Frontend Web UI)\n        ref: ${{ github.ref }}\n        \n    - name: Install dependencies and build\n      run: |\n        yarn install\n        yarn build\n    \n    - name: Setup AWS\n      uses: aws-actions/configure-aws-credentials@v4\n      with:\n        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}\n        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n        aws-region: us-east-1\n\n    - name: Upload to S3\n      env:\n        AWS_S3_BUCKET: 'web-check-frontend'\n      run: aws s3 sync ./build/ s3://$AWS_S3_BUCKET/ --delete\n\n    - name: Invalidate CloudFront cache\n      uses: chetan/invalidate-cloudfront-action@v2\n      env:\n        DISTRIBUTION: E30XKAM2TG9FD8\n        PATHS: '/*'\n        AWS_REGION: 'us-east-1'\n        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n        \n    - name: Update GitHub deployment status (Frontend)\n      if: always()\n      uses: chrnorm/deployment-status@v2\n      with:\n        token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}\n        state: \"${{ job.status }}\"\n        deployment_id: ${{ steps.deployment_frontend.outputs.deployment_id }}\n        ref: ${{ github.ref }}\n\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "name: 🐳 Build + Publish Docker Image\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n    tags:\n      - '*'\n    paths:\n      - src/**\n      - api/**\n      - public/**\n      - Dockerfile\n\nenv:\n  IMAGE_NAME: web-check\n  DOCKER_USER: lissy93\n  GHCR_REGISTRY: ghcr.io\n  DOCKERHUB_REGISTRY: docker.io\n\njobs:\n  docker:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v2\n\n      - name: Extract tag name 🏷️\n        shell: bash\n        run: echo \"GIT_TAG=$(echo ${GITHUB_REF#refs/tags/} | sed 's/\\//_/g')\" >> $GITHUB_ENV\n\n      - name: Compute tags 🔖\n        id: compute-tags\n        run: |\n          if [[ \"${{ github.ref }}\" == \"refs/heads/master\" ]]; then\n            echo \"GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest\" >> $GITHUB_ENV\n            echo \"DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:latest\" >> $GITHUB_ENV\n          else\n            echo \"GHCR_TAG=${GHCR_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}\" >> $GITHUB_ENV\n            echo \"DOCKERHUB_TAG=${DOCKERHUB_REGISTRY}/${DOCKER_USER}/${IMAGE_NAME}:${GIT_TAG}\" >> $GITHUB_ENV\n          fi\n\n      - name: Set up QEMU 🐧\n        uses: docker/setup-qemu-action@v1\n        \n      - name: Set up Docker Buildx 🐳\n        uses: docker/setup-buildx-action@v1\n        \n      - name: Login to GitHub Container Registry 🔑\n        uses: docker/login-action@v1\n        with:\n          registry: ${{ env.GHCR_REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Login to DockerHub 🔑\n        uses: docker/login-action@v1\n        with:\n          registry: ${{ env.DOCKERHUB_REGISTRY }}\n          username: ${{ env.DOCKER_USER }}\n          password: ${{ secrets.DOCKERHUB_PASSWORD }}\n\n      - name: Build and push Docker images 🛠️\n        uses: docker/build-push-action@v2\n        with:\n          context: .\n          file: ./Dockerfile\n          push: true\n          platforms: linux/amd64,linux/arm64/v8\n          tags: |\n            ${{ env.GHCR_TAG }}\n            ${{ env.DOCKERHUB_TAG }}\n"
  },
  {
    "path": ".github/workflows/mirror.yml",
    "content": "# Pushes the contents of the repo to the Codeberg mirror\nname: 🪞 Mirror to Codeberg\non:\n  workflow_dispatch:\n  schedule:\n    - cron: '30 0 * * 0'\njobs:\n  codeberg:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - uses: pixta-dev/repository-mirroring-action@v1\n        with:\n          target_repo_url: git@codeberg.org:alicia/web-check.git\n          ssh_private_key: ${{ secrets.CODEBERG_SSH }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# ------------------------\n#   ENVIRONMENT SETTINGS\n# ------------------------\n.env\n\n# ------------------------\n#       PRODUCTION\n# ------------------------\n/build/\n\n# ------------------------\n#         BUILT FILES\n# ------------------------\ndist/\n.vercel/\n.netlify/\n.webpack/\n.serverless/\n.astro/\n\n# ------------------------\n#         DEPENDENCIES\n# ------------------------\nnode_modules/\n.yarn/cache/\n.yarn/unplugged/\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnpm/\n.pnp.*\n\n# ------------------------\n#            LOGS\n# ------------------------\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# ------------------------\n#          TESTING\n# ------------------------\ncoverage/\n.nyc_output/\n\n# ------------------------\n#       OS SPECIFIC\n# ------------------------\n.DS_Store\nThumbs.db\n\n# ------------------------\n#         EDITORS\n# ------------------------\n.idea/\n.vscode/\n*.swp\n*.swo\n\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Specify the Node.js version to use\nARG NODE_VERSION=21\n\n# Specify the Debian version to use, the default is \"bullseye\"\nARG DEBIAN_VERSION=bullseye\n\n# Use Node.js Docker image as the base image, with specific Node and Debian versions\nFROM node:${NODE_VERSION}-${DEBIAN_VERSION} AS build\n\n# Set the container's default shell to Bash and enable some options\nSHELL [\"/bin/bash\", \"-euo\", \"pipefail\", \"-c\"]\n\n# Install Chromium browser and Download and verify Google Chrome’s signing key\nRUN apt-get update -qq --fix-missing && \\\n    apt-get -qqy install --allow-unauthenticated gnupg wget && \\\n    wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \\\n    echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" > /etc/apt/sources.list.d/google.list && \\\n    apt-get update -qq && \\\n    apt-get -qqy --no-install-recommends install chromium traceroute python make g++ && \\\n    rm -rf /var/lib/apt/lists/* \n\n# Run the Chromium browser's version command and redirect its output to the /etc/chromium-version file\nRUN /usr/bin/chromium --no-sandbox --version > /etc/chromium-version\n\n# Set the working directory to /app\nWORKDIR /app\n\n# Copy package.json and yarn.lock to the working directory\nCOPY package.json yarn.lock ./\n\n# Run yarn install to install dependencies and clear yarn cache\nRUN apt-get update && \\\n    yarn install --frozen-lockfile --network-timeout 100000 && \\\n    rm -rf /app/node_modules/.cache\n\n# Copy all files to working directory\nCOPY . .\n\n# Run yarn build to build the application\nRUN yarn build --production\n\n# Final stage\nFROM node:${NODE_VERSION}-${DEBIAN_VERSION}  AS final\n\nWORKDIR /app\n\nCOPY package.json yarn.lock ./\nCOPY --from=build /app .\n\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends chromium traceroute && \\\n    chmod 755 /usr/bin/chromium && \\\n    rm -rf /var/lib/apt/lists/* /app/node_modules/.cache\n\n# Exposed container port, the default is 3000, which can be modified through the environment variable PORT\nEXPOSE ${PORT:-3000}\n\n# Set the environment variable CHROME_PATH to specify the path to the Chromium binaries\nENV CHROME_PATH='/usr/bin/chromium'\n\n# Define the command executed when the container starts and start the server.js of the Node.js application\nCMD [\"yarn\", \"start\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 Alicia Sykes <https://github.com/lissy93>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "api/_common/aws-webpack.config.js",
    "content": "const path = require('path');\nconst nodeExternals = require('webpack-node-externals');\n\nmodule.exports = {\n    target: 'node',\n    mode: 'production',\n    entry: {\n        'carbon': './api/carbon.js',\n        'cookies': './api/cookies.js',\n        'dns-server': './api/dns-server.js',\n        'dns': './api/dns.js',\n        'dnssec': './api/dnssec.js',\n        'features': './api/features.js',\n        'get-ip': './api/get-ip.js',\n        'headers': './api/headers.js',\n        'hsts': './api/hsts.js',\n        'linked-pages': './api/linked-pages.js',\n        'mail-config': './api/mail-config.js',\n        'ports': './api/ports.js',\n        'quality': './api/quality.js',\n        'redirects': './api/redirects.js',\n        'robots-txt': './api/robots-txt.js',\n        'screenshot': './api/screenshot.js',\n        'security-txt': './api/security-txt.js',\n        'sitemap': './api/sitemap.js',\n        'social-tags': './api/social-tags.js',\n        'ssl': './api/ssl.js',\n        'status': './api/status.js',\n        'tech-stack': './api/tech-stack.js',\n        'trace-route': './api/trace-route.js',\n        'txt-records': './api/txt-records.js',\n        'whois': './api/whois.js',\n    },\n    externals: [nodeExternals()],\n    output: {\n        filename: '[name].js',\n        path: path.resolve(__dirname, '.webpack'),\n        libraryTarget: 'commonjs2'\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.js$/,\n                use: {\n                    loader: 'babel-loader'\n                },\n                exclude: /node_modules/,\n            }\n        ]\n    }\n};\n"
  },
  {
    "path": "api/_common/middleware.js",
    "content": "const normalizeUrl = (url) => {\n  return url.startsWith('http') ? url : `https://${url}`;\n};\n\n// If present, set a shorter timeout for API requests\nconst TIMEOUT = process.env.API_TIMEOUT_LIMIT ? parseInt(process.env.API_TIMEOUT_LIMIT, 10) : 60000;\n\n// If present, set CORS allowed origins for responses\nconst ALLOWED_ORIGINS = process.env.API_CORS_ORIGIN || '*';\n\n// Disable everything :( Setting this env var will turn off the instance, and show message\nconst DISABLE_EVERYTHING = !!process.env.VITE_DISABLE_EVERYTHING;\n\n// Set the platform currently being used\nlet PLATFORM = 'NETLIFY';\nif (process.env.PLATFORM) { PLATFORM = process.env.PLATFORM.toUpperCase(); }\nelse if (process.env.VERCEL) { PLATFORM = 'VERCEL'; }\nelse if (process.env.WC_SERVER) { PLATFORM = 'NODE'; }\n\n// Define the headers to be returned with each response\nconst headers = {\n  'Access-Control-Allow-Origin': ALLOWED_ORIGINS,\n  'Access-Control-Allow-Credentials': true,\n  'Content-Type': 'application/json;charset=UTF-8',\n};\n\nconst timeoutErrorMsg = 'You can re-trigger this request, by clicking \"Retry\"\\n'\n+ 'If you\\'re running your own instance of Web Check, then you can '\n+ 'resolve this issue, by increasing the timeout limit in the '\n+ '`API_TIMEOUT_LIMIT` environmental variable to a higher value (in milliseconds), '\n+ 'or if you\\'re hosting on Vercel increase the maxDuration in vercel.json.\\n\\n'\n+ `The public instance currently has a lower timeout of ${TIMEOUT}ms `\n+ 'in order to keep running costs affordable, so that Web Check can '\n+ 'remain freely available for everyone.';\n\nconst disabledErrorMsg = 'Error - WebCheck Temporarily Disabled.\\n\\n'\n+ 'We\\'re sorry, but due to the increased cost of running Web Check '\n+ 'we\\'ve had to temporatily disable the public instand. '\n+ 'We\\'re activley looking for affordable ways to keep Web Check running, '\n+ 'while free to use for everybody.\\n'\n+ 'In the meantime, since we\\'ve made our code free and open source, '\n+ 'you can get Web Check running on your own system, by following the instructions in our GitHub repo';\n\n// A middleware function used by all API routes on all platforms\nconst commonMiddleware = (handler) => {\n\n  // Create a timeout promise, to throw an error if a request takes too long\n  const createTimeoutPromise = (timeoutMs) => {\n    return new Promise((_, reject) => {\n      setTimeout(() => {\n        reject(new Error(`Request timed-out after ${timeoutMs} ms`));\n      }, timeoutMs);\n    });\n  };\n\n  // Vercel\n  const vercelHandler = async (request, response) => {\n\n    if (DISABLE_EVERYTHING) {\n      response.status(503).json({ error: disabledErrorMsg });\n    }\n\n    const queryParams = request.query || {};\n    const rawUrl = queryParams.url;\n\n    if (!rawUrl) {\n      return response.status(500).json({ error: 'No URL specified' });\n    }\n\n    const url = normalizeUrl(rawUrl);\n\n    try {\n      // Race the handler against the timeout\n      const handlerResponse = await Promise.race([\n        handler(url, request),\n        createTimeoutPromise(TIMEOUT)\n      ]);\n\n      if (handlerResponse.body && handlerResponse.statusCode) {\n        response.status(handlerResponse.statusCode).json(handlerResponse.body);\n      } else {\n        response.status(200).json(\n          typeof handlerResponse === 'object' ? handlerResponse : JSON.parse(handlerResponse)\n        );\n      }\n    } catch (error) {\n      let errorCode = 500;\n      if (error.message.includes('timed-out') || response.statusCode === 504) {\n        errorCode = 408;\n        error.message = `${error.message}\\n\\n${timeoutErrorMsg}`;\n      }\n      response.status(errorCode).json({ error: error.message });\n    }\n  };\n\n  // Netlify\n  const netlifyHandler = async (event, context, callback) => {\n    const queryParams = event.queryStringParameters || event.query || {};\n    const rawUrl = queryParams.url;\n\n    if (DISABLE_EVERYTHING) {\n      callback(null, {\n        statusCode: 503,\n        body: JSON.stringify({ error: 'Web-Check is temporarily disabled. Please try again later.' }),\n        headers,\n      });\n      return;\n    }\n\n    if (!rawUrl) {\n      callback(null, {\n        statusCode: 500,\n        body: JSON.stringify({ error: 'No URL specified' }),\n        headers,\n      });\n      return;\n    }\n\n    const url = normalizeUrl(rawUrl);\n\n    try {\n      // Race the handler against the timeout\n      const handlerResponse = await Promise.race([\n        handler(url, event, context),\n        createTimeoutPromise(TIMEOUT)\n      ]);\n\n      if (handlerResponse.body && handlerResponse.statusCode) {\n        callback(null, handlerResponse);\n      } else {\n        callback(null, {\n          statusCode: 200,\n          body: typeof handlerResponse === 'object' ? JSON.stringify(handlerResponse) : handlerResponse,\n          headers,\n        });\n      }\n    } catch (error) {\n      callback(null, {\n        statusCode: 500,\n        body: JSON.stringify({ error: error.message }),\n        headers,\n      });\n    }\n  };\n\n  // The format of the handlers varies between platforms\n  const nativeMode = (['VERCEL', 'NODE'].includes(PLATFORM));\n  return nativeMode ? vercelHandler : netlifyHandler;\n};\n\nif (PLATFORM === 'NETLIFY') {\n  module.exports = commonMiddleware;\n}\n\nexport default commonMiddleware;\n"
  },
  {
    "path": "api/archives.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst convertTimestampToDate = (timestamp) => {\n  const [year, month, day, hour, minute, second] = [\n    timestamp.slice(0, 4),\n    timestamp.slice(4, 6) - 1,\n    timestamp.slice(6, 8),\n    timestamp.slice(8, 10),\n    timestamp.slice(10, 12),\n    timestamp.slice(12, 14),\n  ].map(num => parseInt(num, 10));\n\n  return new Date(year, month, day, hour, minute, second);\n}\n\nconst countPageChanges = (results) => {\n  let prevDigest = null;\n  return results.reduce((acc, curr) => {\n    if (curr[2] !== prevDigest) {\n      prevDigest = curr[2];\n      return acc + 1;\n    }\n    return acc;\n  }, -1);\n}\n\nconst getAveragePageSize = (scans) => {\n    const totalSize = scans.map(scan => parseInt(scan[3], 10)).reduce((sum, size) => sum + size, 0);\n    return Math.round(totalSize / scans.length);\n};\n\nconst getScanFrequency = (firstScan, lastScan, totalScans, changeCount) => {\n  const formatToTwoDecimal = num => parseFloat(num.toFixed(2));\n\n  const dayFactor = (lastScan - firstScan) / (1000 * 60 * 60 * 24);  \n  const daysBetweenScans = formatToTwoDecimal(dayFactor / totalScans);\n  const daysBetweenChanges = formatToTwoDecimal(dayFactor / changeCount);\n  const scansPerDay = formatToTwoDecimal((totalScans - 1) / dayFactor);\n  const changesPerDay = formatToTwoDecimal(changeCount / dayFactor);\n  return {\n    daysBetweenScans,\n    daysBetweenChanges,\n    scansPerDay,\n    changesPerDay,\n  };\n};\n\nconst wayBackHandler = async (url) => {\n  const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${url}&output=json&fl=timestamp,statuscode,digest,length,offset`;\n\n  try {\n    const { data } = await axios.get(cdxUrl);\n    \n    // Check there's data\n    if (!data || !Array.isArray(data) || data.length <= 1) {\n      return { skipped: 'Site has never before been archived via the Wayback Machine' };\n    }\n\n    // Remove the header row\n    data.shift();\n\n    // Process and return the results\n    const firstScan = convertTimestampToDate(data[0][0]);\n    const lastScan = convertTimestampToDate(data[data.length - 1][0]);\n    const totalScans = data.length;\n    const changeCount = countPageChanges(data);\n    return {\n      firstScan,\n      lastScan,\n      totalScans,\n      changeCount,\n      averagePageSize: getAveragePageSize(data),\n      scanFrequency: getScanFrequency(firstScan, lastScan, totalScans, changeCount),\n      scans: data,\n      scanUrl: url,\n    };\n  } catch (err) {\n    return { error: `Error fetching Wayback data: ${err.message}` };\n  }\n};\n\nexport const handler = middleware(wayBackHandler);\nexport default handler;\n"
  },
  {
    "path": "api/block-lists.js",
    "content": "import dns from 'dns';\nimport { URL } from 'url';\nimport middleware from './_common/middleware.js';\n\nconst DNS_SERVERS = [\n  { name: 'AdGuard', ip: '176.103.130.130' },\n  { name: 'AdGuard Family', ip: '176.103.130.132' },\n  { name: 'CleanBrowsing Adult', ip: '185.228.168.10' },\n  { name: 'CleanBrowsing Family', ip: '185.228.168.168' },\n  { name: 'CleanBrowsing Security', ip: '185.228.168.9' },\n  { name: 'CloudFlare', ip: '1.1.1.1' },\n  { name: 'CloudFlare Family', ip: '1.1.1.3' },\n  { name: 'Comodo Secure', ip: '8.26.56.26' },\n  { name: 'Google DNS', ip: '8.8.8.8' },\n  { name: 'Neustar Family', ip: '156.154.70.3' },\n  { name: 'Neustar Protection', ip: '156.154.70.2' },\n  { name: 'Norton Family', ip: '199.85.126.20' },\n  { name: 'OpenDNS', ip: '208.67.222.222' },\n  { name: 'OpenDNS Family', ip: '208.67.222.123' },\n  { name: 'Quad9', ip: '9.9.9.9' },\n  { name: 'Yandex Family', ip: '77.88.8.7' },\n  { name: 'Yandex Safe', ip: '77.88.8.88' },\n];\nconst knownBlockIPs = [\n  '146.112.61.106', // OpenDNS\n  '185.228.168.10', // CleanBrowsing\n  '8.26.56.26',     // Comodo\n  '9.9.9.9',        // Quad9\n  '208.69.38.170',  // Some OpenDNS IPs\n  '208.69.39.170',  // Some OpenDNS IPs\n  '208.67.222.222', // OpenDNS\n  '208.67.222.123', // OpenDNS FamilyShield\n  '199.85.126.10',  // Norton\n  '199.85.126.20',  // Norton Family\n  '156.154.70.22',  // Neustar\n  '77.88.8.7',      // Yandex\n  '77.88.8.8',      // Yandex\n  '::1',              // Localhost IPv6\n  '2a02:6b8::feed:0ff', // Yandex DNS\n  '2a02:6b8::feed:bad', // Yandex Safe\n  '2a02:6b8::feed:a11', // Yandex Family\n  '2620:119:35::35',    // OpenDNS\n  '2620:119:53::53',    // OpenDNS FamilyShield\n  '2606:4700:4700::1111', // Cloudflare\n  '2606:4700:4700::1001', // Cloudflare\n  '2001:4860:4860::8888', // Google DNS\n  '2a0d:2a00:1::',        // AdGuard\n  '2a0d:2a00:2::'         // AdGuard Family\n];\n\nconst isDomainBlocked = async (domain, serverIP) => {\n  return new Promise((resolve) => {\n    dns.resolve4(domain, { server: serverIP }, (err, addresses) => {\n      if (!err) {\n        if (addresses.some(addr => knownBlockIPs.includes(addr))) {\n          resolve(true);\n          return;\n        }\n        resolve(false);\n        return;\n      }\n\n      dns.resolve6(domain, { server: serverIP }, (err6, addresses6) => {\n        if (!err6) {\n          if (addresses6.some(addr => knownBlockIPs.includes(addr))) {\n            resolve(true);\n            return;\n          }\n          resolve(false);\n          return;\n        }\n        if (err6.code === 'ENOTFOUND' || err6.code === 'SERVFAIL') {\n          resolve(true);\n        } else {\n          resolve(false);\n        }\n      });\n    });\n  });\n};\n\nconst checkDomainAgainstDnsServers = async (domain) => {\n  let results = [];\n\n  for (let server of DNS_SERVERS) {\n    const isBlocked = await isDomainBlocked(domain, server.ip);\n    results.push({\n      server: server.name,\n      serverIp: server.ip,\n      isBlocked,\n    });\n  }\n\n  return results;\n};\n\nexport const blockListHandler = async (url) => {\n  const domain = new URL(url).hostname;\n  const results = await checkDomainAgainstDnsServers(domain);\n  return { blocklists: results };\n};\n\nexport const handler = middleware(blockListHandler);\nexport default handler;\n\n"
  },
  {
    "path": "api/carbon.js",
    "content": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst carbonHandler = async (url) => {\n\n  // First, get the size of the website's HTML\n  const getHtmlSize = (url) => new Promise((resolve, reject) => {\n    https.get(url, res => {\n      let data = '';\n      res.on('data', chunk => {\n        data += chunk;\n      });\n      res.on('end', () => {\n        const sizeInBytes = Buffer.byteLength(data, 'utf8');\n        resolve(sizeInBytes);\n      });\n    }).on('error', reject);\n  });\n\n  try {\n    const sizeInBytes = await getHtmlSize(url);\n    const apiUrl = `https://api.websitecarbon.com/data?bytes=${sizeInBytes}&green=0`;\n\n    // Then use that size to get the carbon data\n    const carbonData = await new Promise((resolve, reject) => {\n      https.get(apiUrl, res => {\n        let data = '';\n        res.on('data', chunk => {\n          data += chunk;\n        });\n        res.on('end', () => {\n          // Check if response looks like HTML (e.g., Cloudflare challenge page)\n          const trimmedData = data.trim();\n          if (trimmedData.startsWith('<!DOCTYPE') || trimmedData.startsWith('<html') || trimmedData.startsWith('<')) {\n            reject(new Error('WebsiteCarbon API returned HTML instead of JSON. This may be due to Cloudflare protection when running from a datacenter IP.'));\n            return;\n          }\n          try {\n            resolve(JSON.parse(data));\n          } catch (parseError) {\n            reject(new Error(`Failed to parse WebsiteCarbon API response as JSON: ${parseError.message}`));\n          }\n        });\n      }).on('error', reject);\n    });\n\n    if (!carbonData.statistics || (carbonData.statistics.adjustedBytes === 0 && carbonData.statistics.energy === 0)) {\n      return {\n        statusCode: 200,\n        body: JSON.stringify({ skipped: 'Not enough info to get carbon data' }),\n      };\n    }\n\n    carbonData.scanUrl = url;\n    return carbonData;\n  } catch (error) {\n    throw new Error(`Error: ${error.message}`);\n  }\n};\n\nexport const handler = middleware(carbonHandler);\nexport default handler;\n"
  },
  {
    "path": "api/cookies.js",
    "content": "import axios from 'axios';\nimport puppeteer from 'puppeteer';\nimport middleware from './_common/middleware.js';\n\nconst getPuppeteerCookies = async (url) => {\n  const browser = await puppeteer.launch({\n    headless: 'new',\n    args: ['--no-sandbox', '--disable-setuid-sandbox'],\n  });\n\n  try {\n    const page = await browser.newPage();\n    const navigationPromise = page.goto(url, { waitUntil: 'networkidle2' });\n        const timeoutPromise = new Promise((_, reject) => \n      setTimeout(() => reject(new Error('Puppeteer took too long!')), 3000)\n    );\n    await Promise.race([navigationPromise, timeoutPromise]);\n    return await page.cookies();\n  } finally {\n    await browser.close();\n  }\n};\n\nconst cookieHandler = async (url) => {\n  let headerCookies = null;\n  let clientCookies = null;\n\n  try {\n    const response = await axios.get(url, {\n      withCredentials: true,\n      maxRedirects: 5,\n    });\n    headerCookies = response.headers['set-cookie'];\n  } catch (error) {\n    if (error.response) {\n      return { error: `Request failed with status ${error.response.status}: ${error.message}` };\n    } else if (error.request) {\n      return { error: `No response received: ${error.message}` };\n    } else {\n      return { error: `Error setting up request: ${error.message}` };\n    }\n  }\n\n  try {\n    clientCookies = await getPuppeteerCookies(url);\n  } catch (_) {\n    clientCookies = null;\n  }\n\n  if (!headerCookies && (!clientCookies || clientCookies.length === 0)) {\n    return { skipped: 'No cookies' };\n  }\n\n  return { headerCookies, clientCookies };\n};\n\nexport const handler = middleware(cookieHandler);\nexport default handler;\n"
  },
  {
    "path": "api/dns-server.js",
    "content": "import { promises as dnsPromises, lookup } from 'dns';\nimport axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst dnsHandler = async (url) => {\n  try {\n    const domain = url.replace(/^(?:https?:\\/\\/)?/i, \"\");\n    const addresses = await dnsPromises.resolve4(domain);\n    const results = await Promise.all(addresses.map(async (address) => {\n      const hostname = await dnsPromises.reverse(address).catch(() => null);\n      let dohDirectSupports = false;\n      try {\n        await axios.get(`https://${address}/dns-query`);\n        dohDirectSupports = true;\n      } catch (error) {\n        dohDirectSupports = false;\n      }\n      return {\n        address,\n        hostname,\n        dohDirectSupports,\n      };\n    }));\n\n    // let dohMozillaSupport = false;\n    // try {\n    //   const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');\n    //   dohMozillaSupport = results.some(({ hostname }) => mozillaList.data.data.some(({ id }) => id.includes(hostname)));\n    // } catch (error) {\n    //   console.error(error);\n    // }\n\n    return {\n      domain,\n      dns: results,\n      // dohMozillaSupport,\n    };\n  } catch (error) {\n    throw new Error(`An error occurred while resolving DNS. ${error.message}`); // This will be caught and handled by the commonMiddleware\n  }\n};\n\n\nexport const handler = middleware(dnsHandler);\nexport default handler;\n\n"
  },
  {
    "path": "api/dns.js",
    "content": "import dns from 'dns';\nimport util from 'util';\nimport middleware from './_common/middleware.js';\n\nconst dnsHandler = async (url) => {\n  let hostname = url;\n\n  // Handle URLs by extracting hostname\n  if (hostname.startsWith('http://') || hostname.startsWith('https://')) {\n    hostname = new URL(hostname).hostname;\n  }\n\n  try {\n    const lookupPromise = util.promisify(dns.lookup);\n    const resolve4Promise = util.promisify(dns.resolve4);\n    const resolve6Promise = util.promisify(dns.resolve6);\n    const resolveMxPromise = util.promisify(dns.resolveMx);\n    const resolveTxtPromise = util.promisify(dns.resolveTxt);\n    const resolveNsPromise = util.promisify(dns.resolveNs);\n    const resolveCnamePromise = util.promisify(dns.resolveCname);\n    const resolveSoaPromise = util.promisify(dns.resolveSoa);\n    const resolveSrvPromise = util.promisify(dns.resolveSrv);\n    const resolvePtrPromise = util.promisify(dns.resolvePtr);\n\n    const [a, aaaa, mx, txt, ns, cname, soa, srv, ptr] = await Promise.all([\n      lookupPromise(hostname),\n      resolve4Promise(hostname).catch(() => []), // A record\n      resolve6Promise(hostname).catch(() => []), // AAAA record\n      resolveMxPromise(hostname).catch(() => []), // MX record\n      resolveTxtPromise(hostname).catch(() => []), // TXT record\n      resolveNsPromise(hostname).catch(() => []), // NS record\n      resolveCnamePromise(hostname).catch(() => []), // CNAME record\n      resolveSoaPromise(hostname).catch(() => []), // SOA record\n      resolveSrvPromise(hostname).catch(() => []), // SRV record\n      resolvePtrPromise(hostname).catch(() => [])  // PTR record\n    ]);\n\n    return {\n      A: a,\n      AAAA: aaaa,\n      MX: mx,\n      TXT: txt,\n      NS: ns,\n      CNAME: cname,\n      SOA: soa,\n      SRV: srv,\n      PTR: ptr\n    };\n  } catch (error) {\n    throw new Error(error.message);\n  }\n};\n\nexport const handler = middleware(dnsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/dnssec.js",
    "content": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst dnsSecHandler = async (domain) => {\n  const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];\n  const records = {};\n\n  for (const type of dnsTypes) {\n    const options = {\n      hostname: 'dns.google',\n      path: `/resolve?name=${encodeURIComponent(domain)}&type=${type}`,\n      method: 'GET',\n      headers: {\n        'Accept': 'application/dns-json'\n      }\n    };\n\n    try {\n      const dnsResponse = await new Promise((resolve, reject) => {\n        const req = https.request(options, res => {\n          let data = '';\n          \n          res.on('data', chunk => {\n            data += chunk;\n          });\n\n          res.on('end', () => {\n            try {\n              resolve(JSON.parse(data));\n            } catch (error) {\n              reject(new Error('Invalid JSON response'));\n            }\n          });\n\n          res.on('error', error => {\n            reject(error);\n          });\n        });\n\n        req.end();\n      });\n\n      if (dnsResponse.Answer) {\n        records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };\n      } else {\n        records[type] = { isFound: false, answer: null, response: dnsResponse };\n      }\n    } catch (error) {\n      throw new Error(`Error fetching ${type} record: ${error.message}`); // This will be caught and handled by the commonMiddleware\n    }\n  }\n\n  return records;\n};\n\nexport const handler = middleware(dnsSecHandler);\nexport default handler;\n"
  },
  {
    "path": "api/features.js",
    "content": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst featuresHandler = async (url) => {\n  const apiKey = process.env.BUILT_WITH_API_KEY;\n\n  if (!url) {\n    throw new Error('URL query parameter is required');\n  }\n\n  if (!apiKey) {\n    throw new Error('Missing BuiltWith API key in environment variables');\n  }\n\n  const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;\n\n  try {\n    const response = await new Promise((resolve, reject) => {\n      const req = https.get(apiUrl, res => {\n        let data = '';\n\n        res.on('data', chunk => {\n          data += chunk;\n        });\n\n        res.on('end', () => {\n          if (res.statusCode >= 200 && res.statusCode <= 299) {\n            resolve(data);\n          } else {\n            reject(new Error(`Request failed with status code: ${res.statusCode}`));\n          }\n        });\n      });\n\n      req.on('error', error => {\n        reject(error);\n      });\n\n      req.end();\n    });\n\n    return response;\n  } catch (error) {\n    throw new Error(`Error making request: ${error.message}`);\n  }\n};\n\nexport const handler = middleware(featuresHandler);\nexport default handler;\n"
  },
  {
    "path": "api/firewall.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst hasWaf = (waf) => {\n  return {\n    hasWaf: true, waf,\n  }\n};\n\nconst firewallHandler = async (url) => {\n  const fullUrl = url.startsWith('http') ? url : `http://${url}`;\n  \n  try {\n    const response = await axios.get(fullUrl);\n\n    const headers = response.headers;\n\n    if (headers['server'] && headers['server'].includes('cloudflare')) {\n      return hasWaf('Cloudflare');\n    }\n\n    if (headers['x-powered-by'] && headers['x-powered-by'].includes('AWS Lambda')) {\n      return hasWaf('AWS WAF');\n    }\n\n    if (headers['server'] && headers['server'].includes('AkamaiGHost')) {\n      return hasWaf('Akamai');\n    }\n\n    if (headers['server'] && headers['server'].includes('Sucuri')) {\n      return hasWaf('Sucuri');\n    }\n\n    if (headers['server'] && headers['server'].includes('BarracudaWAF')) {\n      return hasWaf('Barracuda WAF');\n    }\n\n    if (headers['server'] && (headers['server'].includes('F5 BIG-IP') || headers['server'].includes('BIG-IP'))) {\n      return hasWaf('F5 BIG-IP');\n    }\n\n    if (headers['x-sucuri-id'] || headers['x-sucuri-cache']) {\n      return hasWaf('Sucuri CloudProxy WAF');\n    }\n\n    if (headers['server'] && headers['server'].includes('FortiWeb')) {\n      return hasWaf('Fortinet FortiWeb WAF');\n    }\n\n    if (headers['server'] && headers['server'].includes('Imperva')) {\n      return hasWaf('Imperva SecureSphere WAF');\n    }\n\n    if (headers['x-protected-by'] && headers['x-protected-by'].includes('Sqreen')) {\n      return hasWaf('Sqreen');\n    }\n\n    if (headers['x-waf-event-info']) {\n      return hasWaf('Reblaze WAF');\n    }\n\n    if (headers['set-cookie'] && headers['set-cookie'].includes('_citrix_ns_id')) {\n      return hasWaf('Citrix NetScaler');\n    }\n\n    if (headers['x-denied-reason'] || headers['x-wzws-requested-method']) {\n      return hasWaf('WangZhanBao WAF');\n    }\n\n    if (headers['x-webcoment']) {\n      return hasWaf('Webcoment Firewall');\n    }\n\n    if (headers['server'] && headers['server'].includes('Yundun')) {\n      return hasWaf('Yundun WAF');\n    }\n\n    if (headers['x-yd-waf-info'] || headers['x-yd-info']) {\n      return hasWaf('Yundun WAF');\n    }\n\n    if (headers['server'] && headers['server'].includes('Safe3WAF')) {\n      return hasWaf('Safe3 Web Application Firewall');\n    }\n\n    if (headers['server'] && headers['server'].includes('NAXSI')) {\n      return hasWaf('NAXSI WAF');\n    }\n\n    if (headers['x-datapower-transactionid']) {\n      return hasWaf('IBM WebSphere DataPower');\n    }\n\n    if (headers['server'] && headers['server'].includes('QRATOR')) {\n      return hasWaf('QRATOR WAF');\n    }\n\n    if (headers['server'] && headers['server'].includes('ddos-guard')) {\n      return hasWaf('DDoS-Guard WAF');\n    }\n\n    return {\n      hasWaf: false,\n    }\n  } catch (error) {\n    return {\n      statusCode: 500,\n      body: JSON.stringify({ error: error.message }),\n    };\n  }\n};\n\nexport const handler = middleware(firewallHandler);\nexport default handler;\n"
  },
  {
    "path": "api/get-ip.js",
    "content": "import dns from 'dns';\nimport middleware from './_common/middleware.js';\n\nconst lookupAsync = (address) => {\n  return new Promise((resolve, reject) => {\n    dns.lookup(address, (err, ip, family) => {\n      if (err) {\n        reject(err);\n      } else {\n        resolve({ ip, family });\n      }\n    });\n  });\n};\n\nconst ipHandler = async (url) => {\n  const address = url.replaceAll('https://', '').replaceAll('http://', '');\n  return await lookupAsync(address);\n};\n\n\nexport const handler = middleware(ipHandler);\nexport default handler;\n"
  },
  {
    "path": "api/headers.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst headersHandler = async (url, event, context) => {\n  try {\n    const response = await axios.get(url, {\n      validateStatus: function (status) {\n        return status >= 200 && status < 600; // Resolve only if the status code is less than 600\n      },\n    });\n\n    return response.headers;\n  } catch (error) {\n    throw new Error(error.message);\n  }\n};\n\nexport const handler = middleware(headersHandler);\nexport default handler;\n"
  },
  {
    "path": "api/hsts.js",
    "content": "import https from 'https';\nimport middleware from './_common/middleware.js';\n\nconst hstsHandler = async (url, event, context) => {\n  const errorResponse = (message, statusCode = 500) => {\n    return {\n      statusCode: statusCode,\n      body: JSON.stringify({ error: message }),\n    };\n  };\n  const hstsIncompatible = (message, compatible = false, hstsHeader = null ) => {\n    return { message, compatible, hstsHeader };\n  };\n\n\n  return new Promise((resolve, reject) => {\n    const req = https.request(url, res => {\n      const headers = res.headers;\n      const hstsHeader = headers['strict-transport-security'];\n\n      if (!hstsHeader) {\n        resolve(hstsIncompatible(`Site does not serve any HSTS headers.`));\n      } else {\n        const maxAgeMatch = hstsHeader.match(/max-age=(\\d+)/);\n        const includesSubDomains = hstsHeader.includes('includeSubDomains');\n        const preload = hstsHeader.includes('preload');\n\n        if (!maxAgeMatch || parseInt(maxAgeMatch[1]) < 10886400) {\n          resolve(hstsIncompatible(`HSTS max-age is less than 10886400.`));\n        } else if (!includesSubDomains) {\n          resolve(hstsIncompatible(`HSTS header does not include all subdomains.`));\n        } else if (!preload) {\n          resolve(hstsIncompatible(`HSTS header does not contain the preload directive.`));\n        } else {\n          resolve(hstsIncompatible(`Site is compatible with the HSTS preload list!`, true, hstsHeader));\n        }\n      }\n    });\n\n    req.on('error', (error) => {\n      resolve(errorResponse(`Error making request: ${error.message}`));\n    });\n\n    req.end();\n  });\n};\n\nexport const handler = middleware(hstsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/http-security.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst httpsSecHandler = async (url) => {\n  const fullUrl = url.startsWith('http') ? url : `http://${url}`;\n  \n  try {\n    const response = await axios.get(fullUrl);\n    const headers = response.headers;\n    return {\n      strictTransportPolicy: headers['strict-transport-security'] ? true : false,\n      xFrameOptions: headers['x-frame-options'] ? true : false,\n      xContentTypeOptions: headers['x-content-type-options'] ? true : false,\n      xXSSProtection: headers['x-xss-protection'] ? true : false,\n      contentSecurityPolicy: headers['content-security-policy'] ? true : false,\n    }\n  } catch (error) {\n    return {\n      statusCode: 500,\n      body: JSON.stringify({ error: error.message }),\n    };\n  }\n};\n\nexport const handler = middleware(httpsSecHandler);\nexport default handler;\n"
  },
  {
    "path": "api/legacy-rank.js",
    "content": "import axios from 'axios';\nimport unzipper from 'unzipper';\nimport csv from 'csv-parser';\nimport fs from 'fs';\nimport middleware from './_common/middleware.js';\n\n// Should also work with the following sources:\n// https://www.domcop.com/files/top/top10milliondomains.csv.zip\n// https://tranco-list.eu/top-1m.csv.zip\n// https://www.domcop.com/files/top/top10milliondomains.csv.zip\n// https://radar.cloudflare.com/charts/LargerTopDomainsTable/attachment?id=525&top=1000000\n// https://statvoo.com/dl/top-1million-sites.csv.zip\n\nconst FILE_URL = 'https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip';\nconst TEMP_FILE_PATH = '/tmp/top-1m.csv';\n\nconst rankHandler = async (url) => {\n  let domain = null;\n\n  try {\n    domain = new URL(url).hostname;\n  } catch (e) {\n    throw new Error('Invalid URL');\n  }\n\n// Download and unzip the file if not in cache\nif (!fs.existsSync(TEMP_FILE_PATH)) {\n  const response = await axios({\n    method: 'GET',\n    url: FILE_URL,\n    responseType: 'stream'\n  });\n\n  await new Promise((resolve, reject) => {\n    response.data\n      .pipe(unzipper.Extract({ path: '/tmp' }))\n      .on('close', resolve)\n      .on('error', reject);\n  });\n}\n\n// Parse the CSV and find the rank\nreturn new Promise((resolve, reject) => {\n  const csvStream = fs.createReadStream(TEMP_FILE_PATH)\n    .pipe(csv({\n      headers: ['rank', 'domain'],\n    }))\n    .on('data', (row) => {\n      if (row.domain === domain) {\n        csvStream.destroy();\n        resolve({\n          domain: domain,\n          rank: row.rank,\n          isFound: true,\n        });\n      }\n    })\n    .on('end', () => {\n      resolve({\n        skipped: `Skipping, as ${domain} is not present in the Umbrella top 1M list.`,\n        domain: domain,\n        isFound: false,\n      });\n    })\n    .on('error', reject);\n});\n};\n\nexport const handler = middleware(rankHandler);\nexport default handler;\n"
  },
  {
    "path": "api/linked-pages.js",
    "content": "import axios from 'axios';\nimport cheerio from 'cheerio';\nimport urlLib from 'url';\nimport middleware from './_common/middleware.js';\n\nconst linkedPagesHandler = async (url) => {\n  const response = await axios.get(url);\n  const html = response.data;\n  const $ = cheerio.load(html);\n  const internalLinksMap = new Map();\n  const externalLinksMap = new Map();\n\n  // Get all links on the page\n  $('a[href]').each((i, link) => {\n    const href = $(link).attr('href');\n    const absoluteUrl = urlLib.resolve(url, href);\n    \n    // Check if absolute / relative, append to appropriate map or increment occurrence count\n    if (absoluteUrl.startsWith(url)) {\n      const count = internalLinksMap.get(absoluteUrl) || 0;\n      internalLinksMap.set(absoluteUrl, count + 1);\n    } else if (href.startsWith('http://') || href.startsWith('https://')) {\n      const count = externalLinksMap.get(absoluteUrl) || 0;\n      externalLinksMap.set(absoluteUrl, count + 1);\n    }\n  });\n\n  // Sort by most occurrences, remove supplicates, and convert to array\n  const internalLinks = [...internalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);\n  const externalLinks = [...externalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);\n\n  // If there were no links, then mark as skipped and show reasons\n  if (internalLinks.length === 0 && externalLinks.length === 0) {\n    return {\n      statusCode: 400,\n      body: {\n        skipped: 'No internal or external links found. '\n          + 'This may be due to the website being dynamically rendered, using a client-side framework (like React), and without SSR enabled. '\n          + 'That would mean that the static HTML returned from the HTTP request doesn\\'t contain any meaningful content for Web-Check to analyze. '\n          + 'You can rectify this by using a headless browser to render the page instead.',\n        },\n    };\n  }\n\n  return { internal: internalLinks, external: externalLinks };\n};\n\nexport const handler = middleware(linkedPagesHandler);\nexport default handler;\n"
  },
  {
    "path": "api/mail-config.js",
    "content": "import dns from 'dns';\nimport URL from 'url-parse';\nimport middleware from './_common/middleware.js';\n\n// TODO: Fix.\n\nconst mailConfigHandler = async (url, event, context) => {\n  try {\n    const domain = new URL(url).hostname || new URL(url).pathname;\n\n    // Get MX records\n    const mxRecords = await dns.resolveMx(domain);\n\n    // Get TXT records\n    const txtRecords = await dns.resolveTxt(domain);\n\n    // Filter for only email related TXT records (SPF, DKIM, DMARC, and certain provider verifications)\n    const emailTxtRecords = txtRecords.filter(record => {\n      const recordString = record.join('');\n      return (\n        recordString.startsWith('v=spf1') ||\n        recordString.startsWith('v=DKIM1') ||\n        recordString.startsWith('v=DMARC1') ||\n        recordString.startsWith('protonmail-verification=') ||\n        recordString.startsWith('google-site-verification=') || // Google Workspace\n        recordString.startsWith('MS=') || // Microsoft 365\n        recordString.startsWith('zoho-verification=') || // Zoho\n        recordString.startsWith('titan-verification=') || // Titan\n        recordString.includes('bluehost.com') // BlueHost\n      );\n    });\n\n    // Identify specific mail services\n    const mailServices = emailTxtRecords.map(record => {\n      const recordString = record.join('');\n      if (recordString.startsWith('protonmail-verification=')) {\n        return { provider: 'ProtonMail', value: recordString.split('=')[1] };\n      } else if (recordString.startsWith('google-site-verification=')) {\n        return { provider: 'Google Workspace', value: recordString.split('=')[1] };\n      } else if (recordString.startsWith('MS=')) {\n        return { provider: 'Microsoft 365', value: recordString.split('=')[1] };\n      } else if (recordString.startsWith('zoho-verification=')) {\n        return { provider: 'Zoho', value: recordString.split('=')[1] };\n      } else if (recordString.startsWith('titan-verification=')) {\n        return { provider: 'Titan', value: recordString.split('=')[1] };\n      } else if (recordString.includes('bluehost.com')) {\n        return { provider: 'BlueHost', value: recordString };\n      } else {\n        return null;\n      }\n    }).filter(record => record !== null);\n\n    // Check MX records for Yahoo\n    const yahooMx = mxRecords.filter(record => record.exchange.includes('yahoodns.net'));\n    if (yahooMx.length > 0) {\n      mailServices.push({ provider: 'Yahoo', value: yahooMx[0].exchange });\n    }\n    // Check MX records for Mimecast\n    const mimecastMx = mxRecords.filter(record => record.exchange.includes('mimecast.com'));\n    if (mimecastMx.length > 0) {\n      mailServices.push({ provider: 'Mimecast', value: mimecastMx[0].exchange });\n    }\n\n    return {\n        mxRecords,\n        txtRecords: emailTxtRecords,\n        mailServices,\n      };\n  } catch (error) {\n    if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {\n      return { skipped: 'No mail server in use on this domain' };\n    } else {\n      return {\n        statusCode: 500,\n        body: { error: error.message },\n      };\n    }\n  }\n};\n\nexport const handler = middleware(mailConfigHandler);\nexport default handler;\n"
  },
  {
    "path": "api/ports.js",
    "content": "import net from 'net';\nimport middleware from './_common/middleware.js';\n\n// A list of commonly used ports.\nconst DEFAULT_PORTS_TO_CHECK = [\n  20, 21, 22, 23, 25, 53, 80, 67, 68, 69,\n  110, 119, 123, 143, 156, 161, 162, 179, 194,\n  389, 443, 587, 993, 995,\n  3000, 3306, 3389, 5060, 5900, 8000, 8080, 8888\n];\n/*\n * Checks if the env PORTS_TO_CHECK is set, if so the string is split via \",\" to get an array of ports to check.\n * If the env is not set, return the default commonly used ports.\n */\nconst PORTS = process.env.PORTS_TO_CHECK ? process.env.PORTS_TO_CHECK.split(\",\") : DEFAULT_PORTS_TO_CHECK\n\nasync function checkPort(port, domain) {\n    return new Promise((resolve, reject) => {\n        const socket = new net.Socket();\n\n        socket.setTimeout(1500);\n\n        socket.once('connect', () => {\n            socket.destroy();\n            resolve(port);\n        });\n\n        socket.once('timeout', () => {\n          socket.destroy();\n          reject(new Error(`Timeout at port: ${port}`));\n        });\n\n        socket.once('error', (e) => {\n            socket.destroy();\n            reject(e);\n        });\n        \n        socket.connect(port, domain);\n    });\n}\n\nconst portsHandler = async (url, event, context) => {\n  const domain = url.replace(/(^\\w+:|^)\\/\\//, '');\n  \n  const delay = ms => new Promise(res => setTimeout(res, ms));\n  const timeout = delay(9000);\n\n  const openPorts = [];\n  const failedPorts = [];\n\n  const promises = PORTS.map(port => checkPort(port, domain)\n    .then(() => {\n      openPorts.push(port);\n      return { status: 'fulfilled', port };\n    })\n    .catch(() => {\n      failedPorts.push(port);\n      return { status: 'rejected', port };\n    }));\n\n  let timeoutReached = false;\n\n  for (const promise of promises) {\n    const result = await Promise.race([promise, timeout.then(() => ({ status: 'timeout', timeout: true }))]);\n    if (result.status === 'timeout') {\n      timeoutReached = true;\n      if (result.timeout) {\n        // Add the ports not checked yet to the failedPorts array\n        const checkedPorts = [...openPorts, ...failedPorts];\n        const portsNotChecked = PORTS.filter(port => !checkedPorts.includes(port));\n        failedPorts.push(...portsNotChecked);\n      }\n      break;\n    }\n  }\n\n  if(timeoutReached){\n    return errorResponse('The function timed out before completing.');\n  }\n  \n  // Sort openPorts and failedPorts before returning\n  openPorts.sort((a, b) => a - b);\n  failedPorts.sort((a, b) => a - b);\n  \n  return { openPorts, failedPorts };\n};\n\nconst errorResponse = (message, statusCode = 444) => {\n  return { error: message };\n};\n\nexport const handler = middleware(portsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/quality.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst qualityHandler = async (url, event, context) => {\n  const apiKey = process.env.GOOGLE_CLOUD_API_KEY;\n\n  if (!apiKey) {\n    throw new Error(\n      'Missing Google API. You need to set the `GOOGLE_CLOUD_API_KEY` environment variable'\n    );\n  }\n\n  const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?`\n  + `url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY`\n  + `&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile`\n  + `&key=${apiKey}`;\n\n  return (await axios.get(endpoint)).data;\n};\n\nexport const handler = middleware(qualityHandler);\nexport default handler;\n"
  },
  {
    "path": "api/rank.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst rankHandler = async (url) => { \n  const domain = url ? new URL(url).hostname : null;\n  if (!domain) throw new Error('Invalid URL');\n\n  try {\n    const auth = process.env.TRANCO_API_KEY ? // Auth is optional.\n      { auth: { username: process.env.TRANCO_USERNAME, password: process.env.TRANCO_API_KEY } }\n      : {};\n    const response = await axios.get(\n      `https://tranco-list.eu/api/ranks/domain/${domain}`, { timeout: 5000 }, auth,\n      );\n    if (!response.data || !response.data.ranks || response.data.ranks.length === 0) {\n      return { skipped: `Skipping, as ${domain} isn't ranked in the top 100 million sites yet.`};\n    }\n    return response.data;\n  } catch (error) {\n    return { error: `Unable to fetch rank, ${error.message}` };\n  }\n};\n\nexport const handler = middleware(rankHandler);\nexport default handler;\n\n"
  },
  {
    "path": "api/redirects.js",
    "content": "import got from 'got';\nimport middleware from './_common/middleware.js';\n\nconst redirectsHandler = async (url) => {\n  const redirects = [url];\n  try {\n    await got(url, {\n      followRedirect: true,\n      maxRedirects: 12,\n      hooks: {\n        beforeRedirect: [\n          (options, response) => {\n            redirects.push(response.headers.location);\n          },\n        ],\n      },\n    });\n\n    return {\n      redirects: redirects,\n    };\n  } catch (error) {\n    throw new Error(`Error: ${error.message}`);\n  }\n};\n\nexport const handler = middleware(redirectsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/robots-txt.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst parseRobotsTxt = (content) => {\n  const lines = content.split('\\n');\n  const rules = [];\n\n  lines.forEach(line => {\n    line = line.trim();  // This removes trailing and leading whitespaces\n\n    let match = line.match(/^(Allow|Disallow):\\s*(\\S*)$/i);\n    if (match) {\n      const rule = {\n        lbl: match[1],\n        val: match[2],\n      };\n      \n      rules.push(rule);\n    } else {\n      match = line.match(/^(User-agent):\\s*(\\S*)$/i);\n      if (match) {\n        const rule = {\n          lbl: match[1],\n          val: match[2],\n        };\n        \n        rules.push(rule);\n      }\n    }\n  });\n  return { robots: rules };\n}\n\nconst robotsHandler = async function(url) {\n  let parsedURL;\n  try {\n    parsedURL = new URL(url);\n  } catch (error) {\n    return {\n      statusCode: 400,\n      body: JSON.stringify({ error: 'Invalid url query parameter' }),\n    };\n  }\n\n  const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;\n\n  try {\n    const response = await axios.get(robotsURL);\n\n    if (response.status === 200) {\n      const parsedData = parseRobotsTxt(response.data);\n      if (!parsedData.robots || parsedData.robots.length === 0) {\n        return { skipped: 'No robots.txt file present, unable to continue' };\n      }\n      return parsedData;\n    } else {\n      return {\n        statusCode: response.status,\n        body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),\n      };\n    }\n  } catch (error) {\n    return {\n      statusCode: 500,\n      body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),\n    };\n  }\n};\n\nexport const handler = middleware(robotsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/screenshot.js",
    "content": "import puppeteer from 'puppeteer-core';\nimport chromium from 'chrome-aws-lambda';\nimport middleware from './_common/middleware.js';\nimport { execFile } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport pkg from 'uuid';\nconst { v4: uuidv4 } = pkg;\n\n// Helper function for direct chromium screenshot as fallback\nconst directChromiumScreenshot = async (url) => {\n  console.log(`[DIRECT-SCREENSHOT] Starting direct screenshot process for URL: ${url}`);\n  \n  // Create a tmp filename\n  const tmpDir = '/tmp';\n  const uuid = uuidv4();\n  const screenshotPath = path.join(tmpDir, `screenshot-${uuid}.png`);\n  \n  console.log(`[DIRECT-SCREENSHOT] Will save screenshot to: ${screenshotPath}`);\n  \n  return new Promise((resolve, reject) => {\n    const chromePath = process.env.CHROME_PATH || '/usr/bin/chromium';\n    const args = [\n      '--headless',\n      '--disable-gpu',\n      '--no-sandbox',\n      `--screenshot=${screenshotPath}`,\n      url\n    ];\n\n    console.log(`[DIRECT-SCREENSHOT] Executing: ${chromePath} ${args.join(' ')}`);\n    \n    execFile(chromePath, args, async (error, stdout, stderr) => {\n      if (error) {\n        console.error(`[DIRECT-SCREENSHOT] Chromium error: ${error.message}`);\n        return reject(error);\n      }\n  \n      try {\n        // Read the screenshot file\n        const screenshotData = await fs.readFile(screenshotPath);\n        console.log(`[DIRECT-SCREENSHOT] Screenshot read successfully`);\n        \n        // Convert to base64\n        const base64Data = screenshotData.toString('base64');\n  \n        await fs.unlink(screenshotPath).catch(err =>\n          console.warn(`[DIRECT-SCREENSHOT] Failed to delete temp file: ${err.message}`)\n        );\n  \n        resolve(base64Data);\n      } catch (readError) {\n        console.error(`[DIRECT-SCREENSHOT] Failed reading screenshot: ${readError.message}`);\n        reject(readError);\n      }\n    });\n  });\n};\n\nconst screenshotHandler = async (targetUrl) => {\n  console.log(`[SCREENSHOT] Request received for URL: ${targetUrl}`);\n  \n  if (!targetUrl) {\n    console.error('[SCREENSHOT] URL is missing from queryStringParameters');\n    throw new Error('URL is missing from queryStringParameters');\n  }\n  \n  if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {\n    targetUrl = 'http://' + targetUrl;\n  }\n  \n  try {\n    new URL(targetUrl);\n  } catch (error) {\n    console.error(`[SCREENSHOT] URL provided is invalid: ${targetUrl}`);\n    throw new Error('URL provided is invalid');\n  }\n\n  // First try direct Chromium\n  try {\n    console.log(`[SCREENSHOT] Using direct Chromium method for URL: ${targetUrl}`);\n    const base64Screenshot = await directChromiumScreenshot(targetUrl);\n    console.log(`[SCREENSHOT] Direct screenshot successful`);\n    return { image: base64Screenshot };\n  } catch (directError) {\n    console.error(`[SCREENSHOT] Direct screenshot method failed: ${directError.message}`);\n    console.log(`[SCREENSHOT] Falling back to puppeteer method...`);\n  }\n  \n  // fall back puppeteer \n  let browser = null;\n  try {\n    console.log(`[SCREENSHOT] Launching puppeteer browser`);\n    browser = await puppeteer.launch({\n      args: [...chromium.args, '--no-sandbox'], // Add --no-sandbox flag\n      defaultViewport: { width: 800, height: 600 },\n      executablePath: process.env.CHROME_PATH || '/usr/bin/chromium',\n      headless: true,\n      ignoreHTTPSErrors: true,\n      ignoreDefaultArgs: ['--disable-extensions'],\n    });\n    \n    console.log(`[SCREENSHOT] Creating new page`);\n    let page = await browser.newPage();\n    \n    console.log(`[SCREENSHOT] Setting page preferences`);\n    await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);\n    page.setDefaultNavigationTimeout(8000);\n    \n    console.log(`[SCREENSHOT] Navigating to URL: ${targetUrl}`);\n    await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });\n    \n    console.log(`[SCREENSHOT] Checking if body element exists`);\n    await page.evaluate(() => {\n      const selector = 'body';\n      return new Promise((resolve, reject) => {\n        const element = document.querySelector(selector);\n        if (!element) {\n          reject(new Error(`Error: No element found with selector: ${selector}`));\n        }\n        resolve();\n      });\n    });\n    \n    console.log(`[SCREENSHOT] Taking screenshot`);\n    const screenshotBuffer = await page.screenshot();\n    \n    console.log(`[SCREENSHOT] Converting screenshot to base64`);\n    const base64Screenshot = screenshotBuffer.toString('base64');\n    \n    console.log(`[SCREENSHOT] Screenshot complete, returning image`);\n    return { image: base64Screenshot };\n  } catch (error) {\n    console.error(`[SCREENSHOT] Puppeteer screenshot failed: ${error.message}`);\n    throw error;\n  } finally {\n    if (browser !== null) {\n      console.log(`[SCREENSHOT] Closing browser`);\n      await browser.close();\n    }\n  }\n};\n\nexport const handler = middleware(screenshotHandler);\nexport default handler;\n"
  },
  {
    "path": "api/security-txt.js",
    "content": "import { URL } from 'url';\nimport followRedirects from 'follow-redirects';\nimport middleware from './_common/middleware.js';\n\nconst { https } = followRedirects;\n\nconst SECURITY_TXT_PATHS = [\n  '/security.txt',\n  '/.well-known/security.txt',\n];\n\nconst parseResult = (result) => {\n  let output = {};\n  let counts = {};\n  const lines = result.split('\\n');\n  const regex = /^([^:]+):\\s*(.+)$/;\n  \n  for (const line of lines) {\n    if (!line.startsWith(\"#\") && !line.startsWith(\"-----\") && line.trim() !== '') {\n      const match = line.match(regex);\n      if (match && match.length > 2) {\n        let key = match[1].trim();\n        const value = match[2].trim();\n        if (output.hasOwnProperty(key)) {\n          counts[key] = counts[key] ? counts[key] + 1 : 1;\n          key += counts[key];\n        }\n        output[key] = value;\n      }\n    }\n  }\n  \n  return output;\n};\n\nconst isPgpSigned = (result) => {\n  if (result.includes('-----BEGIN PGP SIGNED MESSAGE-----')) {\n    return true;\n  }\n  return false;\n};\n\nconst securityTxtHandler = async (urlParam) => {\n\n  let url;\n  try {\n    url = new URL(urlParam.includes('://') ? urlParam : 'https://' + urlParam);\n  } catch (error) {\n    throw new Error('Invalid URL format');\n  }\n  url.pathname = '';\n  \n  for (let path of SECURITY_TXT_PATHS) {\n    try {\n      const result = await fetchSecurityTxt(url, path);\n      if (result && result.includes('<html')) return { isPresent: false };\n      if (result) {\n        return {\n          isPresent: true,\n          foundIn: path,\n          content: result,\n          isPgpSigned: isPgpSigned(result),\n          fields: parseResult(result),\n        };\n      }\n    } catch (error) {\n      throw new Error(error.message);\n    }\n  }\n  \n  return { isPresent: false };\n};\n\nasync function fetchSecurityTxt(baseURL, path) {\n  return new Promise((resolve, reject) => {\n    const url = new URL(path, baseURL);\n    https.get(url.toString(), (res) => {\n      if (res.statusCode === 200) {\n        let data = '';\n        res.on('data', (chunk) => {\n          data += chunk;\n        });\n        res.on('end', () => {\n          resolve(data);\n        });\n      } else {\n        resolve(null);\n      }\n    }).on('error', (err) => {\n      reject(err);\n    });\n  });\n}\n\nexport const handler = middleware(securityTxtHandler);\nexport default handler;\n"
  },
  {
    "path": "api/sitemap.js",
    "content": "import axios from 'axios';\nimport xml2js from 'xml2js';\nimport middleware from './_common/middleware.js';\n\nconst sitemapHandler = async (url) => {\n  let sitemapUrl = `${url}/sitemap.xml`;\n\n  const hardTimeOut = 5000;\n\n  try {\n    // Try to fetch sitemap directly\n    let sitemapRes;\n    try {\n      sitemapRes = await axios.get(sitemapUrl, { timeout: hardTimeOut });\n    } catch (error) {\n      if (error.response && error.response.status === 404) {\n        // If sitemap not found, try to fetch it from robots.txt\n        const robotsRes = await axios.get(`${url}/robots.txt`, { timeout: hardTimeOut });\n        const robotsTxt = robotsRes.data.split('\\n');\n\n        for (let line of robotsTxt) {\n          if (line.toLowerCase().startsWith('sitemap:')) {\n            sitemapUrl = line.split(' ')[1].trim();\n            break;\n          }\n        }\n\n        if (!sitemapUrl) {\n          return { skipped: 'No sitemap found' };\n        }\n\n        sitemapRes = await axios.get(sitemapUrl, { timeout: hardTimeOut });\n      } else {\n        throw error; // If other error, throw it\n      }\n    }\n\n    const parser = new xml2js.Parser();\n    const sitemap = await parser.parseStringPromise(sitemapRes.data);\n\n    return sitemap;\n  } catch (error) {\n    if (error.code === 'ECONNABORTED') {\n      return { error: `Request timed-out after ${hardTimeOut}ms` };\n    } else {\n      return { error: error.message };\n    }\n  }\n};\n\nexport const handler = middleware(sitemapHandler);\nexport default handler;\n\n"
  },
  {
    "path": "api/social-tags.js",
    "content": "import axios from 'axios';\nimport cheerio from 'cheerio';\nimport middleware from './_common/middleware.js';\n\nconst socialTagsHandler = async (url) => {\n  \n  // Check if url includes protocol\n  if (!url.startsWith('http://') && !url.startsWith('https://')) {\n    url = 'http://' + url;\n  }\n  \n  try {\n    const response = await axios.get(url);\n    const html = response.data;\n    const $ = cheerio.load(html);\n    \n    const metadata = {\n      // Basic meta tags\n      title: $('head title').text(),\n      description: $('meta[name=\"description\"]').attr('content'),\n      keywords: $('meta[name=\"keywords\"]').attr('content'),\n      canonicalUrl: $('link[rel=\"canonical\"]').attr('href'),\n\n      // OpenGraph Protocol\n      ogTitle: $('meta[property=\"og:title\"]').attr('content'),\n      ogType: $('meta[property=\"og:type\"]').attr('content'),\n      ogImage: $('meta[property=\"og:image\"]').attr('content'),\n      ogUrl: $('meta[property=\"og:url\"]').attr('content'),\n      ogDescription: $('meta[property=\"og:description\"]').attr('content'),\n      ogSiteName: $('meta[property=\"og:site_name\"]').attr('content'),\n      \n      // Twitter Cards\n      twitterCard: $('meta[name=\"twitter:card\"]').attr('content'),\n      twitterSite: $('meta[name=\"twitter:site\"]').attr('content'),\n      twitterCreator: $('meta[name=\"twitter:creator\"]').attr('content'),\n      twitterTitle: $('meta[name=\"twitter:title\"]').attr('content'),\n      twitterDescription: $('meta[name=\"twitter:description\"]').attr('content'),\n      twitterImage: $('meta[name=\"twitter:image\"]').attr('content'),\n\n      // Misc\n      themeColor: $('meta[name=\"theme-color\"]').attr('content'),\n      robots: $('meta[name=\"robots\"]').attr('content'),\n      googlebot: $('meta[name=\"googlebot\"]').attr('content'),\n      generator: $('meta[name=\"generator\"]').attr('content'),\n      viewport: $('meta[name=\"viewport\"]').attr('content'),\n      author: $('meta[name=\"author\"]').attr('content'),\n      publisher: $('link[rel=\"publisher\"]').attr('href'),\n      favicon: $('link[rel=\"icon\"]').attr('href')\n    };\n\n    if (Object.keys(metadata).length === 0) {\n      return { skipped: 'No metadata found' };\n    }\n    return metadata;\n  } catch (error) {\n    return {\n      statusCode: 500,\n      body: JSON.stringify({ error: 'Failed fetching data' }),\n    };\n  }\n};\n\nexport const handler = middleware(socialTagsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/ssl.js",
    "content": "import tls from 'tls';\nimport middleware from './_common/middleware.js';\n\nconst sslHandler = async (urlString) => {\n  try {\n    const parsedUrl = new URL(urlString);\n    const options = {\n      host: parsedUrl.hostname,\n      port: parsedUrl.port || 443,\n      servername: parsedUrl.hostname,\n      rejectUnauthorized: false,\n    };\n\n    return new Promise((resolve, reject) => {\n      const socket = tls.connect(options, () => {\n        if (!socket.authorized) {\n          return reject(new Error(`SSL handshake not authorized. Reason: ${socket.authorizationError}`));\n        }\n\n        const cert = socket.getPeerCertificate();\n        if (!cert || Object.keys(cert).length === 0) {\n          return reject(new Error(`\n          No certificate presented by the server.\\n\n          The server is possibly not using SNI (Server Name Indication) to identify itself, and you are connecting to a hostname-aliased IP address.\n          Or it may be due to an invalid SSL certificate, or an incomplete SSL handshake at the time the cert is being read.`));\n        }\n\n        const { raw, issuerCertificate, ...certWithoutRaw } = cert;\n        resolve(certWithoutRaw);\n        socket.end();\n      });\n\n      socket.on('error', (error) => {\n        reject(new Error(`Error fetching site certificate: ${error.message}`));\n      });\n    });\n\n  } catch (error) {\n    throw new Error(error.message);\n  }\n};\n\nexport const handler = middleware(sslHandler);\nexport default handler;\n"
  },
  {
    "path": "api/status.js",
    "content": "import https from 'https';\nimport { performance, PerformanceObserver } from 'perf_hooks';\nimport middleware from './_common/middleware.js';\n\nconst statusHandler = async (url) => {\n  if (!url) {\n    throw new Error('You must provide a URL query parameter!');\n  }\n\n  let dnsLookupTime;\n  let responseCode;\n  let startTime;\n\n  const obs = new PerformanceObserver((items) => {\n    dnsLookupTime = items.getEntries()[0].duration;\n    performance.clearMarks();\n  });\n\n  obs.observe({ entryTypes: ['measure'] });\n\n  performance.mark('A');\n\n  try {\n    startTime = performance.now();\n    const response = await new Promise((resolve, reject) => {\n      const req = https.get(url, res => {\n        let data = '';\n        responseCode = res.statusCode;\n        res.on('data', chunk => {\n          data += chunk;\n        });\n        res.on('end', () => {\n          resolve(res);\n        });\n      });\n\n      req.on('error', reject);\n      req.end();\n    });\n\n    if (responseCode < 200 || responseCode >= 400) {\n      throw new Error(`Received non-success response code: ${responseCode}`);\n    }\n\n    performance.mark('B');\n    performance.measure('A to B', 'A', 'B');\n    let responseTime = performance.now() - startTime;\n    obs.disconnect();\n\n    return { isUp: true, dnsLookupTime, responseTime, responseCode };\n\n  } catch (error) {\n    obs.disconnect();\n    throw error;\n  }\n};\n\nexport const handler = middleware(statusHandler);\nexport default handler;\n"
  },
  {
    "path": "api/tech-stack.js",
    "content": "import Wappalyzer from 'wappalyzer';\nimport middleware from './_common/middleware.js';\n\nconst techStackHandler = async (url) => {\n  const options = {};\n\n  const wappalyzer = new Wappalyzer(options);\n\n  try {\n    await wappalyzer.init();\n    const headers = {};\n    const storage = {\n      local: {},\n      session: {},\n    };\n    const site = await wappalyzer.open(url, headers, storage);\n    const results = await site.analyze();\n\n    if (!results.technologies || results.technologies.length === 0) {\n      throw new Error('Unable to find any technologies for site');\n    }\n    return results;\n  } catch (error) {\n    throw new Error(error.message);\n  } finally {\n    await wappalyzer.destroy();\n  }\n};\n\nexport const handler = middleware(techStackHandler);\nexport default handler;\n"
  },
  {
    "path": "api/threats.js",
    "content": "import axios from 'axios';\nimport xml2js from 'xml2js';\nimport middleware from './_common/middleware.js';\n\nconst getGoogleSafeBrowsingResult = async (url) => {\n  try {\n    const apiKey = process.env.GOOGLE_CLOUD_API_KEY;\n    if (!apiKey) {\n      return { error: 'GOOGLE_CLOUD_API_KEY is required for the Google Safe Browsing check' };\n    }\n    const apiEndpoint = `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${apiKey}`;\n\n    const requestBody = {\n      threatInfo: {\n        threatTypes: [\n          'MALWARE', 'SOCIAL_ENGINEERING', 'UNWANTED_SOFTWARE', 'POTENTIALLY_HARMFUL_APPLICATION', 'API_ABUSE'\n        ],\n        platformTypes: [\"ANY_PLATFORM\"],\n        threatEntryTypes: [\"URL\"],\n        threatEntries: [{ url }]\n      }\n    };\n\n    const response = await axios.post(apiEndpoint, requestBody);\n    if (response.data && response.data.matches) {\n      return {\n        unsafe: true,\n        details: response.data.matches\n      };\n    } else {\n      return { unsafe: false };\n    }\n  } catch (error) {\n    return { error: `Request failed: ${error.message}` };\n  }\n};\n\nconst getUrlHausResult = async (url) => {\n  let domain = new URL(url).hostname;\n  return await axios({\n    method: 'post',\n    url: 'https://urlhaus-api.abuse.ch/v1/host/',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded'\n    },\n    data: `host=${domain}`\n  })\n  .then((x) => x.data)\n  .catch((e) => ({ error: `Request to URLHaus failed, ${e.message}`}));\n};\n\n\nconst getPhishTankResult = async (url) => {\n  try {\n    const encodedUrl = Buffer.from(url).toString('base64');\n    const endpoint = `https://checkurl.phishtank.com/checkurl/?url=${encodedUrl}`;\n    const headers = {\n      'User-Agent': 'phishtank/web-check',\n    };\n    const response = await axios.post(endpoint, null, { headers, timeout: 3000 });\n    const parsed = await xml2js.parseStringPromise(response.data, { explicitArray: false });\n    return parsed.response.results;\n  } catch (error) {\n    return { error: `Request to PhishTank failed: ${error.message}` };\n  }\n}\n\nconst getCloudmersiveResult = async (url) => {\n  const apiKey = process.env.CLOUDMERSIVE_API_KEY;\n  if (!apiKey) {\n    return { error: 'CLOUDMERSIVE_API_KEY is required for the Cloudmersive check' };\n  }\n  try {\n    const endpoint = 'https://api.cloudmersive.com/virus/scan/website';\n    const headers = {\n      'Content-Type': 'application/x-www-form-urlencoded',\n      'Apikey': apiKey,\n    };\n    const data = `Url=${encodeURIComponent(url)}`;\n    const response = await axios.post(endpoint, data, { headers });\n    return response.data;\n  } catch (error) {\n    return { error: `Request to Cloudmersive failed: ${error.message}` };\n  }\n};\n\nconst threatsHandler = async (url) => {\n  try {\n    const urlHaus = await getUrlHausResult(url);\n    const phishTank = await getPhishTankResult(url);\n    const cloudmersive = await getCloudmersiveResult(url);\n    const safeBrowsing = await getGoogleSafeBrowsingResult(url);\n    if (urlHaus.error && phishTank.error && cloudmersive.error && safeBrowsing.error) {\n      throw new Error(`All requests failed - ${urlHaus.error} ${phishTank.error} ${cloudmersive.error} ${safeBrowsing.error}`);\n    }\n    return JSON.stringify({ urlHaus, phishTank, cloudmersive, safeBrowsing });\n  } catch (error) {\n    throw new Error(error.message);\n  }\n};\n\nexport const handler = middleware(threatsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/tls.js",
    "content": "import axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mozilla.com/api/v1';\n\nconst tlsHandler = async (url) => {\n  try {\n    const domain = new URL(url).hostname;\n    const scanResponse = await axios.post(`${MOZILLA_TLS_OBSERVATORY_API}/scan?target=${domain}`);\n    const scanId = scanResponse.data.scan_id;\n\n    if (typeof scanId !== 'number') {\n      return {\n        statusCode: 500,\n        body: { error: 'Failed to get scan_id from TLS Observatory' },\n      };\n    }\n    const resultResponse = await axios.get(`${MOZILLA_TLS_OBSERVATORY_API}/results?id=${scanId}`);\n    return {\n      statusCode: 200,\n      body: resultResponse.data,\n    };\n  } catch (error) {\n    return { error: error.message };\n  }\n};\n\nexport const handler = middleware(tlsHandler);\nexport default handler;\n"
  },
  {
    "path": "api/trace-route.js",
    "content": "import url from 'url';\nimport traceroute from 'traceroute';\nimport middleware from './_common/middleware.js';\n\nconst traceRouteHandler = async (urlString, context) => {\n  // Parse the URL and get the hostname\n  const urlObject = url.parse(urlString);\n  const host = urlObject.hostname;\n\n  if (!host) {\n    throw new Error('Invalid URL provided');\n  }\n\n  // Traceroute with callback\n  const result = await new Promise((resolve, reject) => {\n    traceroute.trace(host, (err, hops) => {\n      if (err || !hops) {\n        reject(err || new Error('No hops found'));\n      } else {\n        resolve(hops);\n      }\n    });\n  });\n\n  return {\n    message: \"Traceroute completed!\",\n    result,\n  };\n};\n\nexport const handler = middleware(traceRouteHandler);\nexport default handler;\n"
  },
  {
    "path": "api/txt-records.js",
    "content": "import dns from 'dns/promises';\nimport middleware from './_common/middleware.js';\n\nconst txtRecordHandler = async (url, event, context) => {\n  try {\n    const parsedUrl = new URL(url);\n    \n    const txtRecords = await dns.resolveTxt(parsedUrl.hostname);\n\n    // Parsing and formatting TXT records into a single object\n    const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {\n      const recordObject = recordArray.reduce((recordAcc, recordString) => {\n        const splitRecord = recordString.split('=');\n        const key = splitRecord[0];\n        const value = splitRecord.slice(1).join('=');\n        return { ...recordAcc, [key]: value };\n      }, {});\n      return { ...acc, ...recordObject };\n    }, {});\n\n    return readableTxtRecords;\n\n  } catch (error) {\n    if (error.code === 'ERR_INVALID_URL') {\n      throw new Error(`Invalid URL ${error}`);\n    } else {\n      throw error;\n    }\n  }\n};\n\nexport const handler = middleware(txtRecordHandler);\nexport default handler;\n"
  },
  {
    "path": "api/whois.js",
    "content": "import net from 'net';\nimport psl from 'psl';\nimport axios from 'axios';\nimport middleware from './_common/middleware.js';\n\nconst getBaseDomain = (url) => {\n  let protocol = '';\n  if (url.startsWith('http://')) {\n      protocol = 'http://';\n  } else if (url.startsWith('https://')) {\n      protocol = 'https://';\n  }\n  let noProtocolUrl = url.replace(protocol, '');\n  const parsed = psl.parse(noProtocolUrl);\n  return protocol + parsed.domain;\n};\n\nconst parseWhoisData = (data) => {\n  if (data.includes('No match for')) {\n    return { error: 'No matches found for domain in internic database'};\n  }\n  \n  const lines = data.split('\\r\\n');\n  const parsedData = {};\n\n  let lastKey = '';\n\n  for (const line of lines) {\n    const index = line.indexOf(':');\n    if (index === -1) {\n      if (lastKey !== '') {\n        parsedData[lastKey] += ' ' + line.trim();\n      }\n      continue;\n    }\n    let key = line.slice(0, index).trim();\n    const value = line.slice(index + 1).trim();\n    if (value.length === 0) continue;\n    key = key.replace(/\\W+/g, '_');\n    lastKey = key;\n\n    parsedData[key] = value;\n  }\n\n  return parsedData;\n};\n\nconst fetchFromInternic = async (hostname) => {\n  return new Promise((resolve, reject) => {\n    const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {\n      client.write(hostname + '\\r\\n');\n    });\n\n    let data = '';\n    client.on('data', (chunk) => {\n      data += chunk;\n    });\n\n    client.on('end', () => {\n      try {\n        const parsedData = parseWhoisData(data);\n        resolve(parsedData);\n      } catch (error) {\n        reject(error);\n      }\n    });\n\n    client.on('error', (err) => {\n      reject(err);\n    });\n  });\n};\n\nconst fetchFromMyAPI = async (hostname) => {\n  try {\n    const response = await axios.post('https://whois-api-zeta.vercel.app/', {\n      domain: hostname\n    });\n    return response.data;\n  } catch (error) {\n    console.error('Error fetching data from your API:', error.message);\n    return null;\n  }\n};\n\nconst whoisHandler = async (url) => {\n  if (!url.startsWith('http://') && !url.startsWith('https://')) {\n    url = 'http://' + url;\n  }\n\n  let hostname;\n  try {\n    hostname = getBaseDomain(new URL(url).hostname);\n  } catch (error) {\n    throw new Error(`Unable to parse URL: ${error}`);\n  }\n\n  const [internicData, whoisData] = await Promise.all([\n    fetchFromInternic(hostname),\n    fetchFromMyAPI(hostname)\n  ]);\n\n  return {\n    internicData,\n    whoisData\n  };\n};\n\nexport const handler = middleware(whoisHandler);\nexport default handler;\n\n"
  },
  {
    "path": "astro.config.mjs",
    "content": "import { defineConfig } from 'astro/config';\n\n// Integrations\nimport svelte from '@astrojs/svelte';\nimport react from \"@astrojs/react\";\nimport partytown from '@astrojs/partytown';\nimport sitemap from '@astrojs/sitemap';\n\n// Adapters\nimport vercelAdapter from '@astrojs/vercel/serverless';\nimport netlifyAdapter from '@astrojs/netlify';\nimport nodeAdapter from '@astrojs/node';\nimport cloudflareAdapter from '@astrojs/cloudflare';\n\n// Helper function to unwrap both Vite and Node environment variables\nconst unwrapEnvVar = (varName, fallbackValue) => {\n  const classicEnvVar = process?.env && process.env[varName];\n  const viteEnvVar = import.meta.env[varName];\n  return classicEnvVar || viteEnvVar || fallbackValue;\n}\n\n// Determine the deploy target (vercel, netlify, cloudflare, node)\nconst deployTarget = unwrapEnvVar('PLATFORM', 'node').toLowerCase();\n\n// Determine the output mode (server, hybrid or static)\nconst output = unwrapEnvVar('OUTPUT', 'hybrid');\n\n// The FQDN of where the site is hosted (used for sitemaps & canonical URLs)\nconst site = unwrapEnvVar('SITE_URL', 'https://web-check.xyz');\n\n// The base URL of the site (if serving from a subdirectory)\nconst base = unwrapEnvVar('BASE_URL', '/');\n\n// Should run the app in boss-mode (requires extra configuration)\nconst isBossServer = unwrapEnvVar('BOSS_SERVER', false);\n\n// Initialize Astro integrations\nconst integrations = [svelte(), react(), partytown(), sitemap()];\n\n// Set the appropriate adapter, based on the deploy target\nfunction getAdapter(target) {\n  switch(target) {\n    case 'vercel':\n      return vercelAdapter();\n    case 'netlify':\n      return netlifyAdapter();\n    case 'cloudflare':\n      return cloudflareAdapter();\n    case 'node':\n      return nodeAdapter({ mode: 'middleware' });\n    default:\n      throw new Error(`Unsupported deploy target: ${target}`);\n  }\n}\nconst adapter = getAdapter(deployTarget);\n\n// Print build information to console\nconsole.log(\n  `\\n\\x1b[1m\\x1b[35m Preparing to start build of Web Check.... \\x1b[0m\\n`,\n  `\\x1b[35m\\x1b[2mCompiling for \"${deployTarget}\" using \"${output}\" mode, `\n  + `to deploy to \"${site}\" at \"${base}\"\\x1b[0m\\n`,\n  `\\x1b[2m\\x1b[36m🛟 For documentation and support, visit the GitHub repo: ` +\n  `https://github.com/lissy93/web-check \\n`,\n  `💖 Found Web-Check useful? Consider sponsoring us on GitHub ` +\n  `to help fund maintenance & development.\\x1b[0m\\n`,\n);\n\nconst redirects = {\n  '/about': '/check/about',\n};\n\n// Skip the marketing homepage for self-hosted users\nif (!isBossServer && isBossServer !== true) {\n  redirects['/'] = '/check';\n}\n\n// Export Astro configuration\nexport default defineConfig({ output, base, integrations, site, adapter, redirects });\n\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: '3.9'\nservices:\n  web-check:\n    container_name: Web-Check\n    image: lissy93/web-check\n    ports:\n      - 3000:3000\n    restart: unless-stopped\n"
  },
  {
    "path": "fly.toml",
    "content": "app = 'web-check'\nprimary_region = 'lhr'\n\n[build]\n\n[http_service]\n  internal_port = 3000\n  force_https = true\n  auto_stop_machines = true\n  auto_start_machines = true\n  min_machines_running = 0\n  processes = ['app']\n\n[[vm]]\n  memory = '1gb'\n  cpu_kind = 'shared'\n  cpus = 1\n"
  },
  {
    "path": "netlify.toml",
    "content": "# Build settings and site core config\n[build]\n  base = \"/\"\n  command = \"yarn build\"\n  publish = \"dist\"\n  functions = \"api\"\n\n# Environmental variables and optional secrets\n[build.environment]\n  PLATFORM = \"netlify\"\n  PUBLIC_API_ENDPOINT = \"/.netlify/functions\"\n  # Build configuration env vars (uncomment if you want to conigure these)\n  # CI=\"false\" # Set CI to false, to prevent warnings from exiting the build\n  # CHROME_PATH='/usr/bin/chromium' # Path to Chromium binary\n  # NODE_VERSION = \"16.16.0\"  # Set the version of Node.js to use\n\n  # Optional secrets and API keys (uncomment if you want to add these)\n  # GOOGLE_CLOUD_API_KEY=''     # Google Cloud API key, for running Lighthouse scans\n  # BUILT_WITH_API_KEY=''       # BuiltWith API key, for detecting site features\n  # REACT_APP_SHODAN_API_KEY='' # Shodan API key, for using Shodan scan API\n  # REACT_APP_WHO_API_KEY=''    # WhoAPI key, for iniiating client-side whois lookup\n\n# Redirect the /api/* path to the lambda functions\n[[redirects]]\n  from = \"/api/*\"\n  to = \"/.netlify/functions/:splat\"\n  status = 301\n  force = true\n\n# Plugins\n[[plugins]]\npackage = \"netlify-plugin-chromium\"\n  [plugins.inputs]\n  packageManager = \"yarn\"\n\n# Set any security headers here\n[[headers]]\n  for = \"/*\"\n  [headers.values]\n  # Uncomment to enable Netlify user control. Requires premium plan.\n  # Basic-Auth = \"someuser:somepassword anotheruser:anotherpassword\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"web-check\",\n  \"type\": \"module\",\n  \"version\": \"2.0.2\",\n  \"homepage\": \"https://web-check.xyz\",\n  \"scripts\": {\n    \"start\": \"node server\",\n    \"start-pm\": \"pm2 start server.js -i max\",\n    \"build\": \"astro check && astro build\",\n    \"dev:vercel\": \"PLATFORM='vercel' npx vercel dev\",\n    \"dev:netlify\": \"PLATFORM='netlify' npx netlify dev\",\n    \"dev:api\": \"DISABLE_GUI='true' PORT='3001' nodemon server\",\n    \"dev:astro\": \"PUBLIC_API_ENDPOINT=http://localhost:3001/api astro dev\",\n    \"dev\": \"concurrently -c magenta,cyan -n backend,frontend 'yarn dev:api' 'yarn dev:astro'\"\n  },\n  \"dependencies\": {\n    \"@astrojs/check\": \"^0.5.10\",\n    \"@astrojs/react\": \"^3.3.2\",\n    \"@emotion/react\": \"^11.11.4\",\n    \"@emotion/styled\": \"^11.11.5\",\n    \"@fortawesome/fontawesome-svg-core\": \"^6.5.2\",\n    \"@fortawesome/free-brands-svg-icons\": \"^6.5.2\",\n    \"@fortawesome/free-regular-svg-icons\": \"^6.5.2\",\n    \"@fortawesome/free-solid-svg-icons\": \"^6.5.2\",\n    \"@fortawesome/svelte-fontawesome\": \"^0.2.2\",\n    \"@types/react\": \"^18.3.1\",\n    \"@types/react-dom\": \"^18.3.0\",\n    \"astro\": \"^4.7.1\",\n    \"axios\": \"^1.4.8\",\n    \"cheerio\": \"^1.0.0-rc.12\",\n    \"chrome-aws-lambda\": \"^10.1.0\",\n    \"chromium\": \"^3.0.3\",\n    \"connect-history-api-fallback\": \"^2.0.0\",\n    \"cors\": \"^2.8.5\",\n    \"csv-parser\": \"^3.0.0\",\n    \"dotenv\": \"^16.4.5\",\n    \"express\": \"^4.19.2\",\n    \"express-rate-limit\": \"^7.2.0\",\n    \"framer-motion\": \"^11.2.6\",\n    \"got\": \"^14.2.1\",\n    \"pm2\": \"^5.3.1\",\n    \"psl\": \"^1.9.0\",\n    \"puppeteer\": \"^22.8.0\",\n    \"puppeteer-core\": \"^22.8.0\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"react-masonry-css\": \"^1.0.16\",\n    \"react-router-dom\": \"^6.23.0\",\n    \"react-simple-maps\": \"^3.0.0\",\n    \"react-toastify\": \"^10.0.5\",\n    \"recharts\": \"^2.12.6\",\n    \"svelte\": \"^4.2.17\",\n    \"traceroute\": \"^1.0.0\",\n    \"typescript\": \"^5.4.5\",\n    \"unzipper\": \"^0.11.5\",\n    \"url-parse\": \"^1.5.10\",\n    \"wappalyzer\": \"^6.10.65\",\n    \"xml2js\": \"^0.6.2\"\n  },\n  \"devDependencies\": {\n    \"@astrojs/cloudflare\": \"^10.2.5\",\n    \"@astrojs/netlify\": \"^5.2.0\",\n    \"@astrojs/node\": \"^8.2.5\",\n    \"@astrojs/partytown\": \"^2.1.0\",\n    \"@astrojs/sitemap\": \"^3.1.4\",\n    \"@astrojs/svelte\": \"^5.4.0\",\n    \"@astrojs/ts-plugin\": \"^1.6.1\",\n    \"@astrojs/vercel\": \"^7.5.4\",\n    \"concurrently\": \"^8.2.2\",\n    \"nodemon\": \"^3.1.0\",\n    \"sass\": \"^1.77.1\"\n  }\n}\n"
  },
  {
    "path": "public/error.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Web-Check</title>\n  </head>\n  <body>\n    <section>\n      <a class=\"t\" href=\"/\"><h1><img src=\"https://i.ibb.co/q1gZN2p/web-check-logo.png\" width=\"48\" />Web-Check</h1></a>\n      <p class=\"moji\">😪</p>\n      <p>There was an error finding this route.</p>\n      <span>Docs and Source: <a href=\"https://github.com/lissy93/web-check\">github.com/lissy93/web-check</a></span>\n    </section>\n    <style>\n      body { background: #141d2b; color: #fff; }\n      h1 {\n        color: #9fef00; text-shadow: #0f1620 2px 2px 0px;\n        font-size: 3rem; margin: 1rem auto; flex-wrap: wrap;\n        display: flex; align-items: flex-start; gap: 1rem;\n      }\n      section {\n        display: flex; flex-direction: column; align-items: center; margin: 1rem; gap: 0.5rem;\n        background: #1a2332; box-shadow: #0f1620 4px 4px 0px; border-radius: 8px; padding: 1rem;\n        max-width: 800px; margin: 1rem auto;\n      }\n      p { font-size: 1.2rem; }\n      a { color: #9fef00; font-family: monospace; }\n      a.t { text-decoration: none; margin: 0;}\n      span { opacity: 0.8; font-size: 0.85rem; }\n      h1 {\n        font-family: PTMono, Ubuntu, Fira Sans, Helvetica, sans-serif;\n      }\n      p, span, a, section, div { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }\n      code { color: #9fef00cc;}\n      .moji { font-size: 8rem; margin: 0; }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "public/fonts/Hubot-Sans/LICENSE",
    "content": "Copyright (c) 2022, GitHub https://github.com/github/hubot-sans\nwith Reserved Font Name \"Hubot Sans\"\n\nThis Font Software is licensed under the SIL Open Font License, Version 1.1.\nThis license is copied below, and is also available with a FAQ at:\nhttp://scripts.sil.org/OFL\n\n-----------------------------------------------------------\nSIL OPEN FONT LICENSE Version 1.1 - 26 February 2007\n-----------------------------------------------------------\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to stimulate worldwide\ndevelopment of collaborative font projects, to support the font creation\nefforts of academic and linguistic communities, and to provide a free and\nopen framework in which fonts may be shared and improved in partnership\nwith others.\n\nThe OFL allows the licensed fonts to be used, studied, modified and\nredistributed freely as long as they are not sold by themselves. The\nfonts, including any derivative works, can be bundled, embedded, \nredistributed and/or sold with any software provided that any reserved\nnames are not used by derivative works. The fonts and derivatives,\nhowever, cannot be released under any other type of license. The\nrequirement for fonts to remain under this license does not apply\nto any document created using the fonts or their derivatives.\n\nDEFINITIONS\n\"Font Software\" refers to the set of files released by the Copyright\nHolder(s) under this license and clearly marked as such. This may\ninclude source files, build scripts and documentation.\n\n\"Reserved Font Name\" refers to any names specified as such after the\ncopyright statement(s).\n\n\"Original Version\" refers to the collection of Font Software components as\ndistributed by the Copyright Holder(s).\n\n\"Modified Version\" refers to any derivative made by adding to, deleting,\nor substituting — in part or in whole — any of the components of the\nOriginal Version, by changing formats or by porting the Font Software to a\nnew environment.\n\n\"Author\" refers to any designer, engineer, programmer, technical\nwriter or other person who contributed to the Font Software.\n\nPERMISSION & CONDITIONS\nPermission is hereby granted, free of charge, to any person obtaining\na copy of the Font Software, to use, study, copy, merge, embed, modify,\nredistribute, and sell modified and unmodified copies of the Font\nSoftware, subject to the following conditions:\n\n1) Neither the Font Software nor any of its individual components,\nin Original or Modified Versions, may be sold by itself.\n\n2) Original or Modified Versions of the Font Software may be bundled,\nredistributed and/or sold with any software, provided that each copy\ncontains the above copyright notice and this license. These can be\nincluded either as stand-alone text files, human-readable headers or\nin the appropriate machine-readable metadata fields within text or\nbinary files as long as those fields can be easily viewed by the user.\n\n3) No Modified Version of the Font Software may use the Reserved Font\nName(s) unless explicit written permission is granted by the corresponding\nCopyright Holder. This restriction only applies to the primary font name as\npresented to the users.\n\n4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font\nSoftware shall not be used to promote, endorse or advertise any\nModified Version, except to acknowledge the contribution(s) of the\nCopyright Holder(s) and the Author(s) or with their explicit written\npermission.\n\n5) The Font Software, modified or unmodified, in part or in whole,\nmust be distributed entirely under this license, and must not be\ndistributed under any other license. The requirement for fonts to\nremain under this license does not apply to any document created\nusing the Font Software.\n\nTERMINATION\nThis license becomes null and void if any of the above conditions are\nnot met.\n\nDISCLAIMER\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\nOF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE\nCOPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\nINCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL\nDAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM\nOTHER DEALINGS IN THE FONT SOFTWARE."
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%PUBLIC_URL%/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"theme-color\" content=\"#9fef00\" />\n    <meta\n      name=\"description\"\n      content=\"All-in-one OSINT tool, for quickly checking a websites data\"\n    />\n    <link rel=\"apple-touch-icon\" href=\"%PUBLIC_URL%/logo192.png\" />\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" />\n    <title>Web Check</title>\n    <script defer data-domain=\"web-check.as93.net\" src=\"https://no-track.as93.net/js/script.js\"></script>\n\n    <!-- OpenGraph Social Tags -->\n    <meta property=\"og:title\" content=\"Web Check\">\n    <meta property=\"og:description\" content=\"All-in-one Website OSINT Scanner\">\n    <meta property=\"og:image\" content=\"/banner.png\">\n    <meta property=\"og:url\" content=\"https://web-check.xyz\">\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n\n    <!-- DarkReader -->\n    <meta name=\"darkreader-lock\">\n  </head>\n  <body>\n    <noscript>\n      <b>Welcome to Web-Check, the free and open source tool for viewing all available information about a website.</b><br />\n      Get started by entering a URL, and clicking the \"Scan\" button, or view the code and docs\n      on <a href=\"https://github.com/lissy93/web-check\">GitHub</a>.<br />\n      <small>Licensed under MIT, ©️ <a href=\"https://aliciasykes.com\">Alicia Sykes</a> 2023.</small>\n      <br /><br />\n      JavaScript is required to continue, please enable it in your browser.\n    </noscript>\n    <div id=\"root\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"short_name\": \"Web Check\",\n  \"name\": \"Lissy93/Web-Check\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    },\n    {\n      \"src\": \"apple-touch-icon.png\",\n      \"type\": \"image/png\",\n      \"sizes\": \"180x180\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#9fef00\",\n  \"background_color\": \"#141d2b\"\n}\n"
  },
  {
    "path": "public/placeholder.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Web-Check</title>\n  </head>\n  <body>\n    <section>\n      <a class=\"t\" href=\"/\"><h1><img src=\"https://i.ibb.co/q1gZN2p/web-check-logo.png\" width=\"48\" />Web-Check</h1></a>\n      <p><!-- CONTENT --></p>\n      <span>Docs and Source: <a href=\"https://github.com/lissy93/web-check\">github.com/lissy93/web-check</a></span>\n    </section>\n    <style>\n      body { background: #141d2b; color: #fff; }\n      h1 {\n        color: #9fef00; text-shadow: #0f1620 2px 2px 0px;\n        font-size: 3rem; margin: 1rem auto; flex-wrap: wrap;\n        display: flex; align-items: flex-start; gap: 1rem;\n      }\n      section {\n        display: flex; flex-direction: column; align-items: center; margin: 1rem; gap: 0.5rem;\n        background: #1a2332; box-shadow: #0f1620 4px 4px 0px; border-radius: 8px; padding: 1rem;\n        max-width: 800px; margin: 1rem auto;\n      }\n      p { font-size: 1.2rem; }\n      a { color: #9fef00; font-family: monospace; }\n      a.t { text-decoration: none; margin: 0;}\n      span { opacity: 0.8; font-size: 0.85rem; }\n      h1 {\n        font-family: PTMono, Ubuntu, Fira Sans, Helvetica, sans-serif;\n      }\n      p, span, a, section, div { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }\n      code { color: #9fef00cc;}\n      .logs { background: #141d2b; padding: 0.5rem;}\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "public/resources/openapi-spec.yml",
    "content": "openapi: 3.0.0\ninfo:\n  title: Web Check 🕵\n  description: >\n    **API documentation for the [Web Check](https://github.com/lissy93/web-check) backend endpoints.**<br>\n    _Web Check gives you x-ray vision, revealing the configration and inner workings of any website._\n    <br><br>\n    [![Website - Web-Check.xyz](https://img.shields.io/badge/Website-webcheck.zyz-9fef00?style=flat&logo=googlecloudstorage&logoColor=white&labelColor=1c1d28)](https://web-check.xyz/)\n    [![DockerHub - Lissy93/Web-Check](https://img.shields.io/badge/DockerHub-Lissy93/Web_Check-1fb1f4?style=flat&logo=docker&logoColor=white&labelColor=1c1d28)](https://hub.docker.com/r/lissy93/web-check)\n    [![GitHub - Lissy93/Web-Check](https://img.shields.io/badge/GitHub-Lissy93/Web_Check-a832fc?style=flat&logo=github&logoColor=white&labelColor=1c1d28)](https://github.com/lissy93/web-check)\n    [![Sponsor - Alicia Sykes](https://img.shields.io/badge/Sponsor-Alicia_Sykes-f2159a?style=flat&logo=githubsponsors&logoColor=white&labelColor=1c1d28)](https://github.com/sponsors/Lissy93)\n  \n  version: 1.0.0\n  license:\n    name: 'License: MIT'\n    url: https://github.com/Lissy93/web-check/blob/master/LICENSE\n  termsOfService: https://web-check.xyz/about#terms-info\nexternalDocs:\n  description: 'Source: GitHub'\n  url: https://github.com/Lissy93/web-check\nservers:\n  - url: http://localhost:3001/api\n    description: Local (Development)\n  - url: http://localhost:3000/api\n    description: Local (Production)\n  - url: https://web-check.xyz/api\n    description: Public Demo (Vercel)\n  - url: https://web-check.as93.net/api\n    description: Public Demo (Netlify)\ntags:\n  - name: Quality & Info\n    description: Endpoints providing quality metrics, and general website information.\n  - name: Security\n    description: Endpoints related to website and server security configurations.\n  - name: Server Info\n    description: Endpoints providing information about the server hosting the website.\n  - name: Client-Side Information\n    description: Endpoints providing metrics about the website's client-side content.\ncomponents:\n  responses:\n    Error:\n      description: Internal Server Error - An error occurred while processing the request.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/ErrorResponse'\n    Skipped:\n      description: No Content - The request was successful, but no content is returned.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/SkippedResponse'\n    MissingParam:\n      description: Bad Request - Missing or incorrect input parameters.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/ErrorResponse'\n    Unauthorized:\n      description: Unauthorized - Authentication credentials were missing or incorrect.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/ErrorResponse'\n    Forbidden:\n      description: Forbidden - The credentials provided do not grant the necessary permissions.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/ErrorResponse'\n    TooManyRequests:\n      description: Too Many Requests - Rate limit exceeded.\n      content:\n        application/json:\n          schema:\n            $ref: '#/components/schemas/ErrorResponse'\n  schemas:\n    ErrorResponse:\n      type: object\n      properties:\n        error:\n          type: string\n          description: A description of the error\n    SkippedResponse:\n      type: object\n      properties:\n        skipped:\n          type: string\n          description: A description of why the check was skipped\npaths:\n  /archives:\n    get:\n      summary: Retrieve archive data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  firstScan:\n                    type: string\n                    format: date-time\n                    description: The timestamp of the first scan\n                    example: \"1996-12-21T17:11:14.000Z\"\n                  lastScan:\n                    type: string\n                    format: date-time\n                    description: The timestamp of the last scan\n                    example: \"2024-05-14T13:45:47.000Z\"\n                  totalScans:\n                    type: integer\n                    description: The total number of scans\n                    example: 3393\n                  changeCount:\n                    type: integer\n                    description: The total number of changes\n                    example: 1946\n                  averagePageSize:\n                    type: integer\n                    description: The average page size in KB\n                    example: 527\n                  scanFrequency:\n                    type: object\n                    properties:\n                      daysBetweenScans:\n                        type: number\n                        format: float\n                        description: Average days between scans\n                        example: 2.95\n                      daysBetweenChanges:\n                        type: number\n                        format: float\n                        description: Average days between changes\n                        example: 5.14\n                      scansPerDay:\n                        type: number\n                        format: float\n                        description: Number of scans per day\n                        example: 0.34\n                      changesPerDay:\n                        type: number\n                        format: float\n                        description: Number of changes per day\n                        example: 0.19\n                  scans:\n                    type: array\n                    items:\n                      type: array\n                      items:\n                        type: string\n                      description: List of scan details\n                    example: \n                      - [\"19961221171114\", \"200\", \"RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ\", \"971\", null]\n                      - [\"19970209085620\", \"200\", \"6PUZMQAGXXV3RCEQ65WWUCMIJVSG4OQI\", \"1025\", null]\n                      - [\"19970209085620\", \"200\", \"RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ\", \"975\", null]\n                      - [\"19970412142046\", \"200\", \"RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ\", \"1022\", null]\n                      - [\"19980206180754\", \"200\", \"B6ACYSFKSPWKXRPASZYOK3IBIBY7HB3I\", \"1102\", null]\n                  scanUrl:\n                    type: string\n                    format: uri\n                    description: The URL to the scan\n                    example: \"https://duck.com\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /block-lists:\n    get:\n      summary: Retrieve block lists data\n      tags:\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  blocklists:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        server:\n                          type: string\n                          description: The name of the blocklist server\n                          example: \"AdGuard\"\n                        serverIp:\n                          type: string\n                          description: The IP address of the blocklist server\n                          example: \"176.103.130.130\"\n                        isBlocked:\n                          type: boolean\n                          description: Whether the URL is blocked by the server\n                          example: false\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /carbon:\n    get:\n      summary: Retrieve carbon data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  statistics:\n                    type: object\n                    properties:\n                      adjustedBytes:\n                        type: number\n                        format: float\n                        description: Adjusted bytes transferred\n                        example: 104.19\n                      energy:\n                        type: number\n                        format: float\n                        description: Energy consumption in kWh\n                        example: 7.859794422984123e-8\n                      co2:\n                        type: object\n                        properties:\n                          grid:\n                            type: object\n                            properties:\n                              grams:\n                                type: number\n                                format: float\n                                description: CO2 emissions in grams from grid energy\n                                example: 0.00003474029134958983\n                              litres:\n                                type: number\n                                format: float\n                                description: CO2 emissions in litres from grid energy\n                                example: 0.00001932255004864186\n                          renewable:\n                            type: object\n                            properties:\n                              grams:\n                                type: number\n                                format: float\n                                description: CO2 emissions in grams from renewable energy\n                                example: 0.000030118732228875164\n                              litres:\n                                type: number\n                                format: float\n                                description: CO2 emissions in litres from renewable energy\n                                example: 0.000016752038865700363\n                  cleanerThan:\n                    type: integer\n                    description: Percentage of websites that are less clean than the queried site\n                    example: 1\n                  rating:\n                    type: string\n                    description: Environmental rating\n                    example: \"A+\"\n                  green:\n                    type: boolean\n                    description: Whether the site is green\n                    example: false\n                  scanUrl:\n                    type: string\n                    format: uri\n                    description: The URL to the scan\n                    example: \"https://duck.com\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /cookies:\n    get:\n      summary: Retrieve cookies data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  headerCookies:\n                    type: array\n                    items:\n                      type: string\n                      description: List of cookies from the HTTP headers\n                    example:\n                      - \"SOCS=CAAaBgiAmZWyBg; expires=Sun, 15-Jun-2025 12:33:07 GMT; path=/; domain=.google.com; Secure; SameSite=lax\"\n                      - \"AEC=AQTF6HyLiMgX13QRJxvRyBXFos8vw-et4igVlyhgeZaeLlfDgvkgkhQmSg; expires=Tue, 12-Nov-2024 12:33:07 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax\"\n                      - \"__Secure-ENID=19.SE=YMEDMLYol9GgV7AzLehnn5lFsrHNqD3emYqfTaVxHQPMmAHiIqeKLyqdjfwED8gPGFN5Gb7OqkGiGTki34F94cir_SXxYvVJXTFuUU9hPAMaxoluY4JeeWKP3ma4RlsBZDKDUrBBVIA-PoPwMMBRyy8Fu0m_nOPTGjlz8WMhm_jMuHdWEF8; expires=Mon, 16-Jun-2025 04:51:25 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax\"\n                  clientCookies:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        name:\n                          type: string\n                          description: The name of the cookie\n                          example: \"__Secure-ENID\"\n                        value:\n                          type: string\n                          description: The value of the cookie\n                          example: \"19.SE=LZ6Zj5jPrWer7Kk1PqTOWpwREJDJlMaxrCes3es4eXfLZ3-9e-HQ0tnRU-RNA_ezSw_CSyFbUWAd4A3FnTJGGYRsjK2FcqJfIum0UEcSy-oFXM49VreKYjxFyUNEshqsY_VQnZw1lFuRqRyH2JA2V90uHmaL_AVOlL_Myv1_PXwcPYgOCqsBQTkxYPeDVpS6QyHO9g\"\n                        domain:\n                          type: string\n                          description: The domain of the cookie\n                          example: \".google.com\"\n                        path:\n                          type: string\n                          description: The path of the cookie\n                          example: \"/\"\n                        expires:\n                          type: number\n                          format: float\n                          description: The expiration time of the cookie in Unix time\n                          example: 1750049486.285637\n                        size:\n                          type: integer\n                          description: The size of the cookie\n                          example: 217\n                        httpOnly:\n                          type: boolean\n                          description: Whether the cookie is HttpOnly\n                          example: true\n                        secure:\n                          type: boolean\n                          description: Whether the cookie is Secure\n                          example: true\n                        session:\n                          type: boolean\n                          description: Whether the cookie is a session cookie\n                          example: false\n                        sameSite:\n                          type: string\n                          description: The SameSite attribute of the cookie\n                          example: \"Lax\"\n                        priority:\n                          type: string\n                          description: The priority of the cookie\n                          example: \"Medium\"\n                        sameParty:\n                          type: boolean\n                          description: Whether the cookie is SameParty\n                          example: false\n                        sourceScheme:\n                          type: string\n                          description: The source scheme of the cookie\n                          example: \"Secure\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /dns-server:\n    get:\n      summary: Retrieve DNS server data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  domain:\n                    type: string\n                    description: The domain name queried\n                    example: \"duck.com\"\n                  dns:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        address:\n                          type: string\n                          description: The IP address of the DNS server\n                          example: \"52.142.124.215\"\n                        hostname:\n                          type: array\n                          items:\n                            type: string\n                            description: Hostnames associated with the DNS server\n                          nullable: true\n                          example: \n                            - \"lhr48s30-in-f14.1e100.net\"\n                        dohDirectSupports:\n                          type: boolean\n                          description: Whether the server supports DoH (DNS over HTTPS) directly\n                          example: false\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /dns:\n    get:\n      summary: Retrieve DNS data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  A:\n                    type: object\n                    properties:\n                      address:\n                        type: string\n                        description: IPv4 address\n                        example: \"52.142.124.215\"\n                      family:\n                        type: integer\n                        description: IP family\n                        example: 4\n                  AAAA:\n                    type: array\n                    items:\n                      type: string\n                    description: List of IPv6 addresses\n                    example: [\"52.142.124.215\"]\n                  MX:\n                    type: array\n                    items:\n                      type: string\n                    description: List of mail exchange servers\n                    example: []\n                  TXT:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        exchange:\n                          type: string\n                          description: Exchange server\n                          example: \"smtp-inbound1.duck.com\"\n                        priority:\n                          type: integer\n                          description: Priority of the exchange server\n                          example: 5\n                    description: List of TXT records\n                  NS:\n                    type: array\n                    items:\n                      type: array\n                      items:\n                        type: string\n                    description: List of name servers\n                    example:\n                      - [\"v=spf1 ip4:20.13.235.192/26 ip4:20.67.221.0/24 ip4:20.67.222.0/24 ip4:20.67.223.0/24 -all\"]\n                      - [\"google-site-verification=xWLxaaNt2iGObwJ_jeX1E3Wn-xro--W75DBKWs5uufc\"]\n                  CNAME:\n                    type: array\n                    items:\n                      type: string\n                    description: List of canonical names\n                    example:\n                      - \"dns1.p03.nsone.net\"\n                      - \"dns2.p03.nsone.net\"\n                      - \"dns3.p03.nsone.net\"\n                      - \"dns4.p03.nsone.net\"\n                      - \"ns01.quack-dns.com\"\n                      - \"ns02.quack-dns.com\"\n                      - \"ns03.quack-dns.com\"\n                      - \"ns04.quack-dns.com\"\n                  SOA:\n                    type: array\n                    items:\n                      type: string\n                    description: Start of Authority records\n                    example: []\n                  SRV:\n                    type: object\n                    properties:\n                      nsname:\n                        type: string\n                        description: Name server\n                        example: \"dns1.p03.nsone.net\"\n                      hostmaster:\n                        type: string\n                        description: Hostmaster email\n                        example: \"hostmaster.nsone.net\"\n                      serial:\n                        type: integer\n                        description: Serial number\n                        example: 1654974460\n                      refresh:\n                        type: integer\n                        description: Refresh interval\n                        example: 7200\n                      retry:\n                        type: integer\n                        description: Retry interval\n                        example: 7200\n                      expire:\n                        type: integer\n                        description: Expiration time\n                        example: 1209600\n                      minttl:\n                        type: integer\n                        description: Minimum TTL\n                        example: 14400\n                    description: Service records\n                  PTR:\n                    type: array\n                    items:\n                      type: string\n                    description: Pointer records\n                    example: []\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /dnssec:\n    get:\n      summary: Retrieve DNSSEC data\n      tags:\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  DNSKEY:\n                    type: object\n                    properties:\n                      isFound:\n                        type: boolean\n                        description: Whether the DNSKEY record is found\n                        example: false\n                      answer:\n                        type: string\n                        nullable: true\n                        description: The DNSKEY answer (if any)\n                        example: null\n                      response:\n                        type: object\n                        properties:\n                          Status:\n                            type: integer\n                            description: The status code of the response\n                            example: 3\n                          TC:\n                            type: boolean\n                            description: Truncated response flag\n                            example: false\n                          RD:\n                            type: boolean\n                            description: Recursion desired flag\n                            example: true\n                          RA:\n                            type: boolean\n                            description: Recursion available flag\n                            example: true\n                          AD:\n                            type: boolean\n                            description: Authentic data flag\n                            example: false\n                          CD:\n                            type: boolean\n                            description: Checking disabled flag\n                            example: false\n                          Question:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Question name\n                                  example: \"https://duck.com.\"\n                                type:\n                                  type: integer\n                                  description: Question type\n                                  example: 48\n                          Authority:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Authority name\n                                  example: \"com.\"\n                                type:\n                                  type: integer\n                                  description: Authority type\n                                  example: 6\n                                TTL:\n                                  type: integer\n                                  description: Time to live\n                                  example: 900\n                                data:\n                                  type: string\n                                  description: Authority data\n                                  example: \"a.gtld-servers.net. nstld.verisign-grs.com. 1715938809 1800 900 604800 86400\"\n                          Comment:\n                            type: string\n                            description: Additional comments\n                            example: \"Response from 192.41.162.30.\"\n                  DS:\n                    type: object\n                    properties:\n                      isFound:\n                        type: boolean\n                        description: Whether the DS record is found\n                        example: false\n                      answer:\n                        type: string\n                        nullable: true\n                        description: The DS answer (if any)\n                        example: null\n                      response:\n                        type: object\n                        properties:\n                          Status:\n                            type: integer\n                            description: The status code of the response\n                            example: 3\n                          TC:\n                            type: boolean\n                            description: Truncated response flag\n                            example: false\n                          RD:\n                            type: boolean\n                            description: Recursion desired flag\n                            example: true\n                          RA:\n                            type: boolean\n                            description: Recursion available flag\n                            example: true\n                          AD:\n                            type: boolean\n                            description: Authentic data flag\n                            example: false\n                          CD:\n                            type: boolean\n                            description: Checking disabled flag\n                            example: false\n                          Question:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Question name\n                                  example: \"https://duck.com.\"\n                                type:\n                                  type: integer\n                                  description: Question type\n                                  example: 43\n                          Authority:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Authority name\n                                  example: \"com.\"\n                                type:\n                                  type: integer\n                                  description: Authority type\n                                  example: 6\n                                TTL:\n                                  type: integer\n                                  description: Time to live\n                                  example: 900\n                                data:\n                                  type: string\n                                  description: Authority data\n                                  example: \"a.gtld-servers.net. nstld.verisign-grs.com. 1715938824 1800 900 604800 86400\"\n                          Comment:\n                            type: string\n                            description: Additional comments\n                            example: \"Response from 192.52.178.30.\"\n                  RRSIG:\n                    type: object\n                    properties:\n                      isFound:\n                        type: boolean\n                        description: Whether the RRSIG record is found\n                        example: false\n                      answer:\n                        type: string\n                        nullable: true\n                        description: The RRSIG answer (if any)\n                        example: null\n                      response:\n                        type: object\n                        properties:\n                          Status:\n                            type: integer\n                            description: The status code of the response\n                            example: 3\n                          TC:\n                            type: boolean\n                            description: Truncated response flag\n                            example: false\n                          RD:\n                            type: boolean\n                            description: Recursion desired flag\n                            example: true\n                          RA:\n                            type: boolean\n                            description: Recursion available flag\n                            example: true\n                          AD:\n                            type: boolean\n                            description: Authentic data flag\n                            example: false\n                          CD:\n                            type: boolean\n                            description: Checking disabled flag\n                            example: false\n                          Question:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Question name\n                                  example: \"https://duck.com.\"\n                                type:\n                                  type: integer\n                                  description: Question type\n                                  example: 46\n                          Authority:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                name:\n                                  type: string\n                                  description: Authority name\n                                  example: \"com.\"\n                                type:\n                                  type: integer\n                                  description: Authority type\n                                  example: 6\n                                TTL:\n                                  type: integer\n                                  description: Time to live\n                                  example: 900\n                                data:\n                                  type: string\n                                  description: Authority data\n                                  example: \"a.gtld-servers.net. nstld.verisign-grs.com. 1715938809 1800 900 604800 86400\"\n                          Comment:\n                            type: string\n                            description: Additional comments\n                            example: \"Response from 2001:502:7094::30.\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /firewall:\n    get:\n      summary: Retrieve firewall data\n      tags:\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  hasWaf:\n                    type: boolean\n                    description: Whether a Web Application Firewall (WAF) is present\n                    example: false\n                  waf:\n                    type: string\n                    nullable: true\n                    description: The name of the WAF, if present\n                    example: \"Cloudflare\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /get-ip:\n    get:\n      summary: Retrieve IP data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  ip:\n                    type: string\n                    description: The IP address\n                    example: \"52.142.124.215\"\n                  family:\n                    type: integer\n                    description: The IP family (4 for IPv4, 6 for IPv6)\n                    example: 4\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /headers:\n    get:\n      summary: Retrieve headers data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                additionalProperties:\n                  oneOf:\n                    - type: string\n                    - type: array\n                      items:\n                        type: string\n                example:\n                  date: \"Fri, 17 May 2024 11:51:38 GMT\"\n                  content-type: \"text/html; charset=utf-8\"\n                  transfer-encoding: \"chunked\"\n                  connection: \"keep-alive\"\n                  cache-control: \"public, max-age=0, must-revalidate\"\n                  strict-transport-security: \"max-age=31536000; includeSubDomains\"\n                  permissions-policy: \"geolocation=(), camera=(), microphone=()\"\n                  referrer-policy: \"strict-origin-when-cross-origin\"\n                  x-content-type-options: \"nosniff\"\n                  x-frame-options: \"SAMEORIGIN\"\n                  x-gww-loc: \"EN-US\"\n                  x-pgs-loc: \"EN-US\"\n                  x-rm: \"GW\"\n                  x-xss-protection: \"1; mode=block\"\n                  vary: \"Accept-Encoding\"\n                  server: \"cloudflare\"\n                  cf-ray: \"8853658f9da5940b-LHR\"\n                  alt-svc: \"h3=\\\":443\\\"; ma=86400\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /hsts:\n    get:\n      summary: Retrieve HSTS data\n      tags:\n        - Server Info\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    description: A message regarding the HSTS status\n                    example: \"HSTS header does not include all subdomains.\"\n                  compatible:\n                    type: boolean\n                    description: Whether the site is compatible with HSTS\n                    example: false\n                  hstsHeader:\n                    type: string\n                    nullable: true\n                    description: The HSTS header if present\n                    example: null\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /http-security:\n    get:\n      summary: Retrieve HTTP security data\n      tags:\n        - Server Info\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  strictTransportPolicy:\n                    type: boolean\n                    description: Whether Strict Transport Security is enabled\n                    example: false\n                  xFrameOptions:\n                    type: boolean\n                    description: Whether X-Frame-Options header is set\n                    example: true\n                  xContentTypeOptions:\n                    type: boolean\n                    description: Whether X-Content-Type-Options header is set\n                    example: false\n                  xXSSProtection:\n                    type: boolean\n                    description: Whether X-XSS-Protection header is set\n                    example: true\n                  contentSecurityPolicy:\n                    type: boolean\n                    description: Whether Content Security Policy is enabled\n                    example: false\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /linked-pages:\n    get:\n      summary: Retrieve linked pages data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  internal:\n                    type: array\n                    items:\n                      type: string\n                    description: List of internal links\n                    example:\n                      - \"https://bbc.com/news/business\"\n                      - \"https://bbc.com/news/entertainment_and_arts\"\n                      - \"https://bbc.com/news/uk\"\n                      - \"https://bbc.com/news/world\"\n                      - \"https://bbc.com/#chameleon-global-navigation-more-menu\"\n                      - \"https://bbc.com/sport/golf\"\n                      - \"https://bbc.com/sport\"\n                  external:\n                    type: array\n                    items:\n                      type: string\n                    description: List of external links\n                    example:\n                      - \"https://www.bbc.co.uk/\"\n                      - \"https://www.bbc.co.uk/news\"\n                      - \"https://www.bbc.co.uk/sport\"\n                      - \"https://www.bbc.co.uk/weather\"\n                      - \"https://www.bbc.co.uk/iplayer\"\n                      - \"https://www.bbc.co.uk/sounds\"\n                      - \"https://www.bbc.co.uk/bitesize\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /mail-config:\n    get:\n      summary: Retrieve mail configuration data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  mxRecords:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        exchange:\n                          type: string\n                          description: The mail exchange server\n                          example: \"smtp.google.com\"\n                        priority:\n                          type: integer\n                          description: The priority of the mail exchange server\n                          example: 10\n                    description: List of MX (Mail Exchange) records\n                  txtRecords:\n                    type: array\n                    items:\n                      type: array\n                      items:\n                        type: string\n                    description: List of TXT records\n                    example:\n                      - [\"v=spf1 include:_spf.google.com ~all\"]\n                      - [\"google-site-verification=wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o\"]\n                  mailServices:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        provider:\n                          type: string\n                          description: The mail service provider\n                          example: \"Google Workspace\"\n                        value:\n                          type: string\n                          description: The verification value for the mail service\n                          example: \"wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o\"\n                    description: List of mail services and their verification values\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /ports:\n    get:\n      summary: Retrieve open and failed ports data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  openPorts:\n                    type: array\n                    items:\n                      type: integer\n                    description: List of open ports\n                    example:\n                      - 80\n                      - 443\n                      - 8080\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /quality:\n    get:\n      summary: Retrieve website quality metrics\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL of the website to analyze\n          schema:\n            type: string\n        - name: apiKey\n          in: query\n          required: true\n          description: The API key for accessing Google PageSpeed Insights\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  performance:\n                    type: object\n                    description: Performance category data\n                    properties:\n                      score:\n                        type: number\n                        format: float\n                        description: Performance score\n                        example: 0.85\n                  accessibility:\n                    type: object\n                    description: Accessibility category data\n                    properties:\n                      score:\n                        type: number\n                        format: float\n                        description: Accessibility score\n                        example: 0.92\n                  best_practices:\n                    type: object\n                    description: Best Practices category data\n                    properties:\n                      score:\n                        type: number\n                        format: float\n                        description: Best Practices score\n                        example: 0.88\n                  seo:\n                    type: object\n                    description: SEO category data\n                    properties:\n                      score:\n                        type: number\n                        format: float\n                        description: SEO score\n                        example: 0.95\n                  pwa:\n                    type: object\n                    description: Progressive Web App category data\n                    properties:\n                      score:\n                        type: number\n                        format: float\n                        description: PWA score\n                        example: 0.75\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /rank:\n    get:\n      summary: Retrieve rank data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch rank data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  ranks:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        date:\n                          type: string\n                          format: date\n                          description: The date of the rank\n                          example: \"2024-05-16\"\n                        rank:\n                          type: integer\n                          description: The rank value\n                          example: 145896\n                    description: List of rank entries\n                  domain:\n                    type: string\n                    description: The domain name for which rank data is provided\n                    example: \"duck.com\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /redirects:\n    get:\n      summary: Retrieve redirects data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch redirect data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  redirects:\n                    type: array\n                    items:\n                      type: string\n                    description: List of URLs the given URL redirects to\n                    example:\n                      - \"https://duck.com\"\n                      - \"https://duckduckgo.com/\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /robots-txt:\n    get:\n      summary: Retrieve robots.txt data\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch robots.txt data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  robots:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        lbl:\n                          type: string\n                          description: The label of the robots.txt entry (e.g., User-agent, Disallow, Allow)\n                          example: \"User-agent\"\n                        val:\n                          type: string\n                          description: The value of the robots.txt entry\n                          example: \"*\"\n                    description: List of robots.txt entries\n                    example:\n                      - lbl: \"User-agent\"\n                        val: \"*\"\n                      - lbl: \"Disallow\"\n                        val: \"/lite\"\n                      - lbl: \"Disallow\"\n                        val: \"/html\"\n                      - lbl: \"Disallow\"\n                        val: \"/*?\"\n                      - lbl: \"Disallow\"\n                        val: \"/chrome_newtab\"\n                      - lbl: \"Disallow\"\n                        val: \"/email/\"\n                      - lbl: \"Allow\"\n                        val: \"/email/$\"\n                      - lbl: \"Allow\"\n                        val: \"/email/privacy-guarantees\"\n                      - lbl: \"Allow\"\n                        val: \"/email/privacy-terms\"\n                      - lbl: \"Disallow\"\n                        val: \"/2012-privacy-policy\"\n                      - lbl: \"User-agent\"\n                        val: \"ia_archiver\"\n                      - lbl: \"Disallow\"\n                        val: \"/\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /screenshot:\n    get:\n      summary: Retrieve screenshot data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties: {}\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /security-txt:\n    get:\n      summary: Retrieve security.txt data\n      tags:\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch security.txt data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  isPresent:\n                    type: boolean\n                    description: Whether the security.txt file is present\n                    example: true\n                  foundIn:\n                    type: string\n                    nullable: true\n                    description: The location where the security.txt file was found\n                    example: \"/.well-known/security.txt\"\n                  content:\n                    type: string\n                    nullable: true\n                    description: The content of the security.txt file\n                    example: \"Contact: mailto:security@proton.me\\nExpires: 2024-12-31T23:59:59.999Z\\nEncryption: https://api.protonmail.ch/pks/lookup?op=get&search=security@proton.me\\nPreferred-Languages: en\\nCanonical: https://proton.me/.well-known/security.txt\\nPolicy: https://proton.me/blog/protonmail-bug-bounty-program\\nHiring: https://proton.me/careers\\n\"\n                  isPgpSigned:\n                    type: boolean\n                    description: Whether the security.txt file is PGP signed\n                    example: false\n                  fields:\n                    type: object\n                    nullable: true\n                    description: Key-value pairs of the security.txt fields\n                    properties:\n                      Contact:\n                        type: string\n                        description: Contact information\n                        example: \"mailto:security@proton.me\"\n                      Expires:\n                        type: string\n                        format: date-time\n                        description: Expiry date of the security.txt information\n                        example: \"2024-12-31T23:59:59.999Z\"\n                      Encryption:\n                        type: string\n                        description: Encryption key location\n                        example: \"https://api.protonmail.ch/pks/lookup?op=get&search=security@proton.me\"\n                      Preferred-Languages:\n                        type: string\n                        description: Preferred languages for contact\n                        example: \"en\"\n                      Canonical:\n                        type: string\n                        description: Canonical URL for the security.txt file\n                        example: \"https://proton.me/.well-known/security.txt\"\n                      Policy:\n                        type: string\n                        description: Policy URL\n                        example: \"https://proton.me/blog/protonmail-bug-bounty-program\"\n                      Hiring:\n                        type: string\n                        description: Hiring information URL\n                        example: \"https://proton.me/careers\"\n                      Acknowledgments:\n                        type: string\n                        nullable: true\n                        description: Acknowledgments information\n                        example: \"https://hackerone.com/github/hacktivity\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /sitemap:\n    get:\n      summary: Retrieve sitemap data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch sitemap data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  urlset:\n                    type: object\n                    properties:\n                      $:\n                        type: object\n                        properties:\n                          xmlns:\n                            type: string\n                            description: XML namespace\n                            example: \"http://www.sitemaps.org/schemas/sitemap/0.9\"\n                          xmlns:xsi:\n                            type: string\n                            description: XML Schema instance namespace\n                            example: \"http://www.w3.org/2001/XMLSchema-instance\"\n                          xsi:schemaLocation:\n                            type: string\n                            description: Schema location\n                            example: \"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\"\n                      url:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            loc:\n                              type: array\n                              items:\n                                type: string\n                              description: The URL of the page\n                              example: \"https://duckduckgo.com/\"\n                            lastmod:\n                              type: array\n                              items:\n                                type: string\n                                format: date\n                              description: The last modification date of the page\n                              example: \"2023-08-22\"\n                            changefreq:\n                              type: array\n                              items:\n                                type: string\n                              description: The frequency of changes to the page\n                              example: \"monthly\"\n                            priority:\n                              type: array\n                              items:\n                                type: number\n                                format: float\n                              description: The priority of the page\n                              example: 1.00\n    description: |\n      Note: The structure might need changing to show pages below each user-agent.\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /social-tags:\n    get:\n      summary: Retrieve social media tags data\n      tags:\n        - Quality & Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch social tags data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  title:\n                    type: string\n                    description: The title of the page\n                    example: \"DuckDuckGo — Privacy, simplified.\"\n                  description:\n                    type: string\n                    description: The description of the page\n                    example: \"The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs.\"\n                  canonicalUrl:\n                    type: string\n                    description: The canonical URL of the page\n                    example: \"https://duckduckgo.com\"\n                  ogTitle:\n                    type: string\n                    description: The Open Graph title of the page\n                    example: \"DuckDuckGo — Privacy, simplified.\"\n                  ogType:\n                    type: string\n                    description: The Open Graph type of the page\n                    example: \"website\"\n                  ogImage:\n                    type: string\n                    description: The Open Graph image URL of the page\n                    example: \"https://duckduckgo.com/assets/logo_social-media.png\"\n                  ogUrl:\n                    type: string\n                    description: The Open Graph URL of the page\n                    example: \"https://duckduckgo.com\"\n                  ogDescription:\n                    type: string\n                    description: The Open Graph description of the page\n                    example: \"The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs.\"\n                  ogSiteName:\n                    type: string\n                    description: The Open Graph site name of the page\n                    example: \"DuckDuckGo\"\n                  twitterCard:\n                    type: string\n                    description: The Twitter card type of the page\n                    example: \"summary_large_image\"\n                  twitterSite:\n                    type: string\n                    description: The Twitter handle of the site\n                    example: \"@duckduckgo\"\n                  twitterTitle:\n                    type: string\n                    description: The Twitter title of the page\n                    example: \"DuckDuckGo — Privacy, simplified.\"\n                  twitterDescription:\n                    type: string\n                    description: The Twitter description of the page\n                    example: \"The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs.\"\n                  twitterImage:\n                    type: string\n                    description: The Twitter image URL of the page\n                    example: \"https://duckduckgo.com/assets/logo_social-media.png\"\n                  viewport:\n                    type: string\n                    description: The viewport settings of the page\n                    example: \"width=device-width, initial-scale=1, user-scalable=1 , viewport-fit=auto\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /ssl:\n    get:\n      summary: Retrieve SSL certificate data\n      tags:\n        - Security\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch SSL certificate data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  subject:\n                    type: object\n                    description: The subject of the SSL certificate\n                    properties:\n                      C:\n                        type: string\n                        description: Country\n                        example: \"US\"\n                      ST:\n                        type: string\n                        description: State or province\n                        example: \"Pennsylvania\"\n                      L:\n                        type: string\n                        description: Locality or city\n                        example: \"Paoli\"\n                      O:\n                        type: string\n                        description: Organization\n                        example: \"Duck Duck Go, Inc.\"\n                      CN:\n                        type: string\n                        description: Common Name\n                        example: \"*.duck.com\"\n                  issuer:\n                    type: object\n                    description: The issuer of the SSL certificate\n                    properties:\n                      C:\n                        type: string\n                        description: Country\n                        example: \"US\"\n                      O:\n                        type: string\n                        description: Organization\n                        example: \"DigiCert Inc\"\n                      CN:\n                        type: string\n                        description: Common Name\n                        example: \"DigiCert Global G2 TLS RSA SHA256 2020 CA1\"\n                  subjectaltname:\n                    type: string\n                    description: Subject alternative name\n                    example: \"DNS:*.duck.com, DNS:duck.com\"\n                  infoAccess:\n                    type: object\n                    description: Information Access details\n                    properties:\n                      \"OCSP - URI\":\n                        type: array\n                        items:\n                          type: string\n                        description: OCSP URI\n                        example:\n                          - \"http://ocsp.digicert.com\"\n                      \"CA Issuers - URI\":\n                        type: array\n                        items:\n                          type: string\n                        description: CA Issuers URI\n                        example:\n                          - \"http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt\"\n                  ca:\n                    type: boolean\n                    description: Whether it is a CA certificate\n                    example: false\n                  modulus:\n                    type: string\n                    description: The modulus of the public key\n                    example: \"A4B4F5B418A538F2570010FEDA319C3F73400EF8C20BB36668B34AE776233C5F06BE0572E36180FD8064D8AC41BD54AD2C6BAAEDEB31308F37B11EED1CD5D2F046C8B6B16381C4E80E817352C16B6F2A8687589BB564BF4B3C87A45284DB9A8240805002846A067C8CCEB50290D2A799907DFBF6EB0432528F413C5CC913F0230374250062C027664240E5A3A8574B8914F26216CEF076CECE04427A489137EA5AACFCD446827432C18CFF5E31AF0F3999B60A303925082FDDB1535513D6B131497F003397FD1EA4D58C24640261E1C205F33A83FDEE9D8C48A638F98927CEFE42C7DD3852C62AD62ECA74A417DE79290755689B3A6D19E44152695A5872491F\"\n                  bits:\n                    type: integer\n                    description: The number of bits in the key\n                    example: 2048\n                  exponent:\n                    type: string\n                    description: The public exponent\n                    example: \"0x10001\"\n                  pubkey:\n                    type: object\n                    description: The public key\n                    properties:\n                      type:\n                        type: string\n                        description: Type of the public key data\n                        example: \"Buffer\"\n                      data:\n                        type: array\n                        items:\n                          type: integer\n                        description: The public key data\n                        example: [48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 164, 180, 245, 180, 24, 165, 56, 242, 87, 0, 16, 254, 218, 49, 156, 63, 115, 64, 14, 248, 194, 11, 179, 102, 104, 179, 74, 231, 118, 35, 60, 95, 6, 190, 5, 114, 227, 97, 128, 253, 128, 100, 216, 172, 65, 189, 84, 173, 44, 107, 170, 237, 235, 49, 48, 143, 55, 177, 30, 237, 28, 213, 210, 240, 70, 200, 182, 177, 99, 129, 196, 232, 14, 129, 115, 82, 193, 107, 111, 42, 134, 135, 88, 155, 181, 100, 191, 75, 60, 135, 164, 82, 132, 219, 154, 130, 64, 128, 80, 2, 132, 106, 6, 124, 140, 206, 181, 2, 144, 210, 167, 153, 144, 125, 251, 246, 235, 4, 50, 82, 143, 65, 60, 92, 201, 19, 240, 35, 3, 116, 37, 0, 98, 192, 39, 102, 66, 64, 229, 163, 168, 87, 75, 137, 20, 242, 98, 22, 206, 240, 118, 206, 206, 4, 66, 122, 72, 145, 55, 234, 90, 172, 252, 212, 70, 130, 116, 50, 193, 140, 255, 94, 49, 175, 15, 57, 153, 182, 10, 48, 57, 37, 8, 47, 221, 177, 83, 85, 19, 214, 177, 49, 73, 127, 0, 51, 151, 253, 30, 164, 213, 140, 36, 100, 2, 97, 225, 194, 5, 243, 58, 131, 253, 238, 157, 140, 72, 166, 56, 249, 137, 39, 206, 254, 66, 199, 221, 56, 82, 198, 42, 214, 46, 202, 116, 164, 23, 222, 121, 41, 7, 85, 104, 155, 58, 109, 25, 228, 65, 82, 105, 90, 88, 114, 73, 31, 2, 3, 1, 0, 1]\n                  valid_from:\n                    type: string\n                    description: The start date of the certificate's validity period\n                    example: \"Oct  3 00:00:00 2023 GMT\"\n                  valid_to:\n                    type: string\n                    description: The end date of the certificate's validity period\n                    example: \"Nov  2 23:59:59 2024 GMT\"\n                  fingerprint:\n                    type: string\n                    description: The SHA-1 fingerprint of the certificate\n                    example: \"54:3C:CB:82:A2:33:D0:CB:81:4D:7D:2C:AA:E6:84:CE:37:9A:0B:7A\"\n                  fingerprint256:\n                    type: string\n                    description: The SHA-256 fingerprint of the certificate\n                    example: \"F3:64:C5:26:C1:BD:31:DE:67:BB:98:57:96:7B:B6:9B:C7:1A:CD:44:21:29:62:4C:CE:E2:C9:DF:49:73:DE:05\"\n                  fingerprint512:\n                    type: string\n                    description: The SHA-512 fingerprint of the certificate\n                    example: \"FC:5A:8F:4D:27:DC:A1:4B:2B:31:8A:DD:37:DF:7E:7B:64:18:49:C9:B8:AB:29:9E:B2:5E:F6:43:36:D0:8B:9D:D0:3E:10:83:4D:B2:78:DF:E2:44:B3:7F:70:22:C7:1F:29:81:72:9E:D5:F2:7C:25:2A:02:FE:90:75:D6:35:CB\"\n                  ext_key_usage:\n                    type: array\n                    items:\n                      type: string\n                    description: Extended key usage\n                    example:\n                      - \"1.3.6.1.5.5.7.3.1\"\n                      - \"1.3.6.1.5.5.7.3.2\"\n                  serialNumber:\n                    type: string\n                    description: The serial number of the certificate\n                    example: \"0ABA674EF6840EFD177509B0BF82C4A5\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /status:\n    get:\n      summary: Retrieve website status\n      tags:\n        - Server Info\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to check the status of\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  isUp:\n                    type: boolean\n                    description: Whether the website is up\n                    example: true\n                  responseTime:\n                    type: number\n                    format: float\n                    description: The response time in milliseconds\n                    example: 105.34512500000005\n                  responseCode:\n                    type: integer\n                    description: The HTTP response code\n                    example: 302\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /tech-stack:\n    get:\n      summary: Retrieve technology stack data\n      tags:\n        - Quality & Info\n        - Client-Side Information\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch technology stack data about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  urls:\n                    type: object\n                    description: Status of different URLs\n                    additionalProperties:\n                      type: object\n                      properties:\n                        status:\n                          type: integer\n                          description: HTTP status code\n                          example: 302\n                    example:\n                      \"https://duck.com/\":\n                        status: 302\n                      \"https://duckduckgo.com/\":\n                        status: 200\n                  technologies:\n                    type: array\n                    description: List of detected technologies\n                    items:\n                      type: object\n                      properties:\n                        slug:\n                          type: string\n                          description: Slug identifier for the technology\n                          example: \"node-js\"\n                        name:\n                          type: string\n                          description: Name of the technology\n                          example: \"Node.js\"\n                        description:\n                          type: string\n                          nullable: true\n                          description: Description of the technology\n                          example: \"Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside a web browser.\"\n                        confidence:\n                          type: integer\n                          description: Confidence level of the detection\n                          example: 100\n                        version:\n                          type: string\n                          nullable: true\n                          description: Detected version of the technology\n                          example: \"12.3.4\"\n                        icon:\n                          type: string\n                          description: Icon file name for the technology\n                          example: \"Node.js.svg\"\n                        website:\n                          type: string\n                          description: Official website for the technology\n                          example: \"https://nodejs.org\"\n                        cpe:\n                          type: string\n                          nullable: true\n                          description: Common Platform Enumeration (CPE) identifier\n                          example: \"cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*\"\n                        categories:\n                          type: array\n                          description: List of categories the technology belongs to\n                          items:\n                            type: object\n                            properties:\n                              id:\n                                type: integer\n                                description: Category ID\n                                example: 27\n                              slug:\n                                type: string\n                                description: Category slug\n                                example: \"programming-languages\"\n                              name:\n                                type: string\n                                description: Category name\n                                example: \"Programming languages\"\n                        rootPath:\n                          type: boolean\n                          description: Whether the technology is detected at the root path\n                          example: true\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /threats:\n    get:\n      summary: Retrieve threats data\n      tags:\n        - Security\n      parameters:\n        - name: url\n          in: query\n          required: true\n          description: The URL to fetch results about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties: {}\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /tls:\n    get:\n      summary: Retrieve TLS information for a target\n      tags:\n        - Security\n        - Server Info\n      parameters:\n        - name: target\n          in: query\n          required: true\n          description: The target domain to fetch TLS information about\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  id:\n                    type: integer\n                    description: The ID of the TLS scan\n                    example: 57369181\n                  timestamp:\n                    type: string\n                    format: date-time\n                    description: Timestamp of the scan\n                    example: \"2024-05-18T13:52:49.230114Z\"\n                  target:\n                    type: string\n                    description: Target domain of the TLS scan\n                    example: \"duck.com\"\n                  replay:\n                    type: integer\n                    description: Replay value\n                    example: -1\n                  has_tls:\n                    type: boolean\n                    description: Indicates if TLS is present\n                    example: true\n                  cert_id:\n                    type: integer\n                    description: Certificate ID\n                    example: 189148642\n                  trust_id:\n                    type: integer\n                    description: Trust ID\n                    example: 343378161\n                  is_valid:\n                    type: boolean\n                    description: Indicates if the TLS certificate is valid\n                    example: true\n                  completion_perc:\n                    type: integer\n                    description: Completion percentage of the scan\n                    example: 100\n                  connection_info:\n                    type: object\n                    description: Connection information of the TLS scan\n                    properties:\n                      scanIP:\n                        type: string\n                        description: Scan IP address\n                        example: \"52.250.42.157\"\n                      serverside:\n                        type: boolean\n                        description: Indicates if the scan is server-side\n                        example: true\n                      ciphersuite:\n                        type: array\n                        description: List of cipher suites used\n                        items:\n                          type: object\n                          properties:\n                            cipher:\n                              type: string\n                              description: Name of the cipher suite\n                              example: \"ECDHE-RSA-AES128-GCM-SHA256\"\n                            code:\n                              type: integer\n                              description: Cipher code\n                              example: 49199\n                            protocols:\n                              type: array\n                              description: Supported protocols\n                              items:\n                                type: string\n                                example: \"TLSv1.2\"\n                            pubkey:\n                              type: integer\n                              description: Public key length\n                              example: 2048\n                            sigalg:\n                              type: string\n                              description: Signature algorithm\n                              example: \"sha256WithRSAEncryption\"\n                            ticket_hint:\n                              type: string\n                              description: Ticket hint\n                              example: \"1200\"\n                            ocsp_stapling:\n                              type: boolean\n                              description: Indicates if OCSP stapling is enabled\n                              example: false\n                            pfs:\n                              type: string\n                              description: Perfect forward secrecy information\n                              example: \"ECDH,P-256,256bits\"\n                            curves:\n                              type: array\n                              description: Supported curves\n                              items:\n                                type: string\n                                example: \"prime256v1\"\n                            curvesFallback:\n                              type: boolean\n                              description: Indicates if curve fallback is used\n                              example: false\n                  analysis:\n                    type: array\n                    description: Analysis results\n                    items:\n                      type: object\n                      properties:\n                        id:\n                          type: integer\n                          description: Analyzer ID\n                          example: 157573176\n                        analyzer:\n                          type: string\n                          description: Analyzer name\n                          example: \"awsCertlint\"\n                        result:\n                          type: object\n                          description: Analyzer result\n                          properties:\n                            bugs:\n                              type: array\n                              nullable: true\n                              description: List of bugs found\n                              items:\n                                type: string\n                            errors:\n                              type: array\n                              nullable: true\n                              description: List of errors found\n                              items:\n                                type: string\n                            notices:\n                              type: array\n                              nullable: true\n                              description: List of notices found\n                              items:\n                                type: string\n                            warnings:\n                              type: array\n                              nullable: true\n                              description: List of warnings found\n                              items:\n                                type: string\n                            fatalErrors:\n                              type: array\n                              nullable: true\n                              description: List of fatal errors found\n                              items:\n                                type: string\n                            informational:\n                              type: array\n                              nullable: true\n                              description: List of informational messages\n                              items:\n                                type: string\n                            host:\n                              type: string\n                              description: Host information\n                              example: \"\"\n                            issue:\n                              type: array\n                              nullable: true\n                              description: List of issues found\n                              items:\n                                type: string\n                            has_caa:\n                              type: boolean\n                              description: Indicates if CAA is present\n                              example: false\n                            issuewild:\n                              type: array\n                              nullable: true\n                              description: List of wildcard issues found\n                              items:\n                                type: string\n                            revoked:\n                              type: boolean\n                              description: Indicates if the certificate is revoked\n                              example: false\n                            RevocationTime:\n                              type: string\n                              format: date-time\n                              description: Time of revocation\n                              example: \"0001-01-01T00:00:00Z\"\n                            level:\n                              type: string\n                              description: Mozilla evaluation level\n                              example: \"intermediate\"\n                            failures:\n                              type: object\n                              description: List of failures\n                              properties:\n                                bad:\n                                  type: array\n                                  nullable: true\n                                  description: List of bad failures\n                                  items:\n                                    type: string\n                                old:\n                                  type: array\n                                  nullable: true\n                                  description: List of old failures\n                                  items:\n                                    type: string\n                                  example: [\n                                    \"sha256WithRSAEncryption is not an old certificate signature, use sha1WithRSAEncryption\",\n                                    \"consider adding ciphers ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES256-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-RSA-AES256-SHA256, DHE-DSS-AES256-SHA, DHE-RSA-AES256-SHA, ECDHE-RSA-DES-CBC3-SHA, ECDHE-ECDSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-AES128-SHA, DES-CBC3-SHA, DHE-RSA-CHACHA20-POLY1305, ECDHE-RSA-CAMELLIA256-SHA384, ECDHE-ECDSA-CAMELLIA256-SHA384, DHE-RSA-CAMELLIA256-SHA256, DHE-DSS-CAMELLIA256-SHA256, DHE-RSA-CAMELLIA256-SHA, DHE-DSS-CAMELLIA256-SHA, CAMELLIA256-SHA256, CAMELLIA256-SHA, ECDHE-RSA-CAMELLIA128-SHA256, ECDHE-ECDSA-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA128-SHA256, DHE-DSS-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA128-SHA, DHE-DSS-CAMELLIA128-SHA, CAMELLIA128-SHA256, CAMELLIA128-SHA, DHE-RSA-SEED-SHA, DHE-DSS-SEED-SHA, SEED-SHA\",\n                                    \"add protocols TLSv1.1, TLSv1, SSLv3\",\n                                    \"consider enabling OCSP stapling\",\n                                    \"add cipher DES-CBC3-SHA for backward compatibility\"\n                                  ]\n                                modern:\n                                  type: array\n                                  nullable: true\n                                  description: List of modern failures\n                                  items:\n                                    type: string\n                                  example: [\n                                    \"remove ciphersuites ECDHE-RSA-AES128-SHA, AES128-GCM-SHA256, AES128-SHA256, AES128-SHA, ECDHE-RSA-AES256-SHA, AES256-GCM-SHA384, AES256-SHA256, AES256-SHA\",\n                                    \"consider adding ciphers ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES128-SHA256\",\n                                    \"consider enabling OCSP stapling\",\n                                    \"use a certificate of type ecdsa, not RSA\"\n                                  ]\n                                intermediate:\n                                  type: array\n                                  nullable: true\n                                  description: List of intermediate failures\n                                  items:\n                                    type: string\n                                  example: [\n                                    \"consider adding ciphers ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES256-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA, ECDHE-ECDSA-DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, DES-CBC3-SHA\",\n                                    \"add protocols TLSv1.1, TLSv1\",\n                                    \"consider enabling OCSP stapling\",\n                                    \"increase priority of ECDHE-RSA-AES256-GCM-SHA384 over AES128-SHA\",\n                                    \"fix ciphersuite ordering, use recommended intermediate ciphersuite\"\n                                  ]\n                        grade:\n                          type: integer\n                          description: Mozilla grading worker grade\n                          example: 93\n                        lettergrade:\n                          type: string\n                          description: Mozilla grading worker letter grade\n                          example: \"A\"\n                        rank:\n                          type: integer\n                          description: Rank of the target domain\n                          example: 2147483647\n                        domain:\n                          type: string\n                          description: Domain name\n                          example: \"duck.com\"\n                        alexa_rank:\n                          type: integer\n                          description: Alexa rank of the domain\n                          example: 2147483647\n                        cisco_rank:\n                          type: integer\n                          description: Cisco rank of the domain\n                          example: 2147483647\n                        alexa_domain:\n                          type: string\n                          description: Alexa domain name\n                          example: \"duck.com\"\n                        cisco_domain:\n                          type: string\n                          description: Cisco domain name\n                          example: \"duck.com\"\n                        reasons:\n                          type: array\n                          description: List of reasons for distrust\n                          items:\n                            type: string\n                          example: [\n                            \"path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 (id=123)\",\n                            \"whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=187368699) override blacklisting of 123\",\n                            \"path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 (id=188669208)\",\n                            \"whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=187368699) override blacklisting of 188669208\",\n                            \"path uses a root not trusted by Mozilla: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root (id=16)\",\n                            \"whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=189284449) override blacklisting of 34174538\",\n                            \"whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=189094508) override blacklisting of 34174538\",\n                            \"path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2008 VeriSign, Inc. - For authorized use only, CN=VeriSign Universal Root Certification Authority (id=124)\",\n                            \"whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=188535492) override blacklisting of 124\"\n                          ]\n                        isDistrusted:\n                          type: boolean\n                          description: Indicates if the certificate is distrusted\n                          example: false\n                  ack:\n                    type: boolean\n                    description: Acknowledgment flag\n                    example: true\n                  attempts:\n                    type: integer\n                    description: Number of attempts\n                    example: 1\n                  analysis_params:\n                    type: object\n                    description: Parameters used for the analysis\n                    example: {}\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /trace-route:\n    get:\n      summary: Perform a traceroute to the specified URL\n      tags:\n        - Server Info\n      parameters:\n        - name: urlString\n          in: query\n          required: true\n          description: The URL to trace the route to\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    description: The status message of the traceroute\n                    example: \"Traceroute completed!\"\n                  result:\n                    type: array\n                    description: The result of the traceroute\n                    items:\n                      oneOf:\n                        - type: object\n                          additionalProperties:\n                            type: array\n                            items:\n                              type: number\n                              description: The response time in milliseconds\n                              example: 2.015\n                        - type: boolean\n                          description: Indicates if a hop was not found\n                          example: false\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /txt-records:\n    get:\n      summary: Retrieve the TXT records for a specified domain\n      tags:\n        - Server Info\n      parameters:\n        - name: domain\n          in: query\n          required: true\n          description: The domain to retrieve TXT records for\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                additionalProperties:\n                  type: string\n                  description: The value of the TXT record\n                example:\n                  brave-ledger-verification: \"b9067df1ce0bb02c06924b88b62143d0c3c0b9c03a8f5c4aba3c0d0ec99f0a98\"\n                  google-site-verification: \"qFBMUPljNGHE2S-NwmQmYvxpa58UfmmIidAbJG9BiuM\"\n                  pinterest-site-verification: \"c71cdf6ff37b315830c4af9d98cf2add\"\n                  protonmail-verification: \"2493afe89892e5b986b59415cdb104152c24f29e\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n  /whois:\n    get:\n      summary: Retrieve WHOIS information for a specified domain\n      tags:\n        - Server Info\n      parameters:\n        - name: domain\n          in: query\n          required: true\n          description: The domain to retrieve WHOIS information for\n          schema:\n            type: string\n      responses:\n        '200':\n          description: Successful response\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  internicData:\n                    type: object\n                    properties:\n                      Domain_Name:\n                        type: string\n                      Registry_Domain_ID:\n                        type: string\n                      Registrar_WHOIS_Server:\n                        type: string\n                      Registrar_URL:\n                        type: string\n                      Updated_Date:\n                        type: string\n                        format: date-time\n                      Creation_Date:\n                        type: string\n                        format: date-time\n                      Registry_Expiry_Date:\n                        type: string\n                        format: date-time\n                      Registrar:\n                        type: string\n                      Registrar_IANA_ID:\n                        type: string\n                      Domain_Status:\n                        type: string\n                      Name_Server:\n                        type: string\n                      DNSSEC:\n                        type: string\n                      URL_of_the_ICANN_Whois_Inaccuracy_Complaint_Form:\n                        type: string\n                      _Last_update_of_whois_database:\n                        type: string\n                        format: date-time\n                      For_more_information_on_Whois_status_codes_please_visit_https:\n                        type: string\n                      NOTICE:\n                        type: string\n                      TERMS_OF_USE:\n                        type: string\n                      by_the_following_terms_of_use:\n                        type: string\n                      to:\n                        type: string\n                  whoisData:\n                    type: object\n                    properties:\n                      domain:\n                        type: object\n                        properties:\n                          id:\n                            type: string\n                          domain:\n                            type: string\n                          name:\n                            type: string\n                          extension:\n                            type: string\n                          whois_server:\n                            type: string\n                          status:\n                            type: array\n                            items:\n                              type: string\n                          name_servers:\n                            type: array\n                            items:\n                              type: string\n                          created_date:\n                            type: string\n                            format: date-time\n                          updated_date:\n                            type: string\n                            format: date-time\n                          expiration_date:\n                            type: string\n                            format: date-time\n                      registrar:\n                        type: object\n                        properties:\n                          id:\n                            type: string\n                          name:\n                            type: string\n                          phone:\n                            type: string\n                          email:\n                            type: string\n                          referral_url:\n                            type: string\n                      registrant:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          organization:\n                            type: string\n                          street:\n                            type: string\n                          city:\n                            type: string\n                          province:\n                            type: string\n                          postal_code:\n                            type: string\n                          country:\n                            type: string\n                          phone:\n                            type: string\n                          phone_ext:\n                            type: string\n                          fax:\n                            type: string\n                          fax_ext:\n                            type: string\n                          email:\n                            type: string\n                      administrative:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          organization:\n                            type: string\n                          street:\n                            type: string\n                          city:\n                            type: string\n                          province:\n                            type: string\n                          postal_code:\n                            type: string\n                          country:\n                            type: string\n                          phone:\n                            type: string\n                          phone_ext:\n                            type: string\n                          fax:\n                            type: string\n                          fax_ext:\n                            type: string\n                          email:\n                            type: string\n                      technical:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          organization:\n                            type: string\n                          street:\n                            type: string\n                          city:\n                            type: string\n                          province:\n                            type: string\n                          postal_code:\n                            type: string\n                          country:\n                            type: string\n                          phone:\n                            type: string\n                          phone_ext:\n                            type: string\n                          fax:\n                            type: string\n                          fax_ext:\n                            type: string\n                          email:\n                            type: string\n                      billing:\n                        type: object\n                        properties:\n                          name:\n                            type: string\n                          organization:\n                            type: string\n                          street:\n                            type: string\n                          city:\n                            type: string\n                          province:\n                            type: string\n                          postal_code:\n                            type: string\n                          country:\n                            type: string\n                          phone:\n                            type: string\n                          phone_ext:\n                            type: string\n                          fax:\n                            type: string\n                          fax_ext:\n                            type: string\n                          email:\n                            type: string\n                example:\n                  internicData: \n                    Domain_Name: \"AS93.NET\"\n                    Registry_Domain_ID: \"1722029712_DOMAIN_NET-VRSN\"\n                    Registrar_WHOIS_Server: \"whois.cloudflare.com\"\n                    Registrar_URL: \"http://www.cloudflare.com\"\n                    Updated_Date: \"2019-05-07T04:20:11Z\"\n                    Creation_Date: \"2012-05-22T16:36:05Z\"\n                    Registry_Expiry_Date: \"2025-05-22T16:36:05Z\"\n                    Registrar: \"CloudFlare, Inc.\"\n                    Registrar_IANA_ID: \"1910\"\n                    Domain_Status: \"clientTransferProhibited https://icann.org/epp#clientTransferProhibited\"\n                    Name_Server: \"ALEX.NS.CLOUDFLARE.COM\"\n                    DNSSEC: \"unsigned\"\n                    URL_of_the_ICANN_Whois_Inaccuracy_Complaint_Form: \"https://www.icann.org/wicf/\"\n                    _Last_update_of_whois_database: \"2024-05-18T13:59:10Z <<< \"\n                    For_more_information_on_Whois_status_codes_please_visit_https: \"//icann.org/epp \"\n                    NOTICE: \"The expiration date displayed in this record is the date the registrar's sponsorship of the domain name registration in the registry is currently set to expire. This date does not necessarily reflect the expiration date of the domain name registrant's agreement with the sponsoring registrar.  Users may consult the sponsoring registrar's Whois database to view the registrar's reported date of expiration for this registration. \"\n                    TERMS_OF_USE: \"You are not authorized to access or query our Whois database through the use of electronic processes that are high-volume and automated except as reasonably necessary to register domain names or modify existing registrations; the Data in VeriSign Global Registry Services' (\\\"VeriSign\\\") Whois database is provided by VeriSign for information purposes only, and to assist persons in obtaining information about or related to a domain name registration record. VeriSign does not guarantee its accuracy. By submitting a Whois query, you agree to abide\"\n                    by_the_following_terms_of_use: \"You agree that you may use this Data only for lawful purposes and that under no circumstances will you use this Data\"\n                    to: \"(1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations via e-mail, telephone, or facsimile; or (2) enable high volume, automated, electronic processes that apply to VeriSign (or its computer systems). The compilation, repackaging, dissemination or other use of this Data is expressly prohibited without the prior written consent of VeriSign. You agree not to use electronic processes that are automated and high-volume to access or query the Whois database except as reasonably necessary to register domain names or modify existing registrations. VeriSign reserves the right to restrict your access to the Whois database in its sole discretion to ensure operational stability.  VeriSign may restrict or terminate your access to the Whois database for failure to abide by these terms of use. VeriSign reserves the right to modify these terms at any time.  The Registry database contains ONLY .COM, .NET, .EDU domains and Registrars. \"\n                  whoisData: \n                    domain: \n                      id: \"1722029712_DOMAIN_NET-VRSN\"\n                      domain: \"as93.net\"\n                      name: \"as93\"\n                      extension: \"net\"\n                      whois_server: \"whois.cloudflare.com\"\n                      status: \n                        - \"clienttransferprohibited\"\n                      name_servers: \n                        - \"adrian.ns.cloudflare.com\"\n                        - \"alex.ns.cloudflare.com\"\n                      created_date: \"2012-05-22T16:36:05Z\"\n                      updated_date: \"2019-05-07T04:20:11Z\"\n                      expiration_date: \"2025-05-22T16:36:05Z\"\n                    registrar: \n                      id: \"1910\"\n                      name: \"Cloudflare, Inc.\"\n                      phone: \"+1.4153197517\"\n                      email: \"registrar-abuse@cloudflare.com\"\n                      referral_url: \"https://www.cloudflare.com\"\n                    registrant: \n                      name: \"DATA REDACTED\"\n                      organization: \"DATA REDACTED\"\n                      street: \"DATA REDACTED\"\n                      city: \"DATA REDACTED\"\n                      province: \"London\"\n                      postal_code: \"DATA REDACTED\"\n                      country: \"GB\"\n                      phone: \"DATA REDACTED\"\n                      phone_ext: \"DATA REDACTED\"\n                      fax: \"DATA REDACTED\"\n                      fax_ext: \"DATA REDACTED\"\n                      email: \"https://domaincontact.cloudflareregistrar.com/as93.net\"\n                    administrative: \n                      name: \"DATA REDACTED\"\n                      organization: \"DATA REDACTED\"\n                      street: \"DATA REDACTED\"\n                      city: \"DATA REDACTED\"\n                      province: \"DATA REDACTED\"\n                      postal_code: \"DATA REDACTED\"\n                      country: \"DATA REDACTED\"\n                      phone: \"DATA REDACTED\"\n                      phone_ext: \"DATA REDACTED\"\n                      fax: \"DATA REDACTED\"\n                      fax_ext: \"DATA REDACTED\"\n                      email: \"https://domaincontact.cloudflareregistrar.com/as93.net\"\n                    technical: \n                      name: \"DATA REDACTED\"\n                      organization: \"DATA REDACTED\"\n                      street: \"DATA REDACTED\"\n                      city: \"DATA REDACTED\"\n                      province: \"DATA REDACTED\"\n                      postal_code: \"DATA REDACTED\"\n                      country: \"DATA REDACTED\"\n                      phone: \"DATA REDACTED\"\n                      phone_ext: \"DATA REDACTED\"\n                      fax: \"DATA REDACTED\"\n                      fax_ext: \"DATA REDACTED\"\n                      email: \"https://domaincontact.cloudflareregistrar.com/as93.net\"\n                    billing: \n                      name: \"DATA REDACTED\"\n                      organization: \"DATA REDACTED\"\n                      street: \"DATA REDACTED\"\n                      city: \"DATA REDACTED\"\n                      province: \"DATA REDACTED\"\n                      postal_code: \"DATA REDACTED\"\n                      country: \"DATA REDACTED\"\n                      phone: \"DATA REDACTED\"\n                      phone_ext: \"DATA REDACTED\"\n                      fax: \"DATA REDACTED\"\n                      fax_ext: \"DATA REDACTED\"\n                      email: \"https://domaincontact.cloudflareregistrar.com/as93.net\"\n        '204':\n          $ref: '#/components/responses/Skipped'\n        '500':\n          $ref: '#/components/responses/Error'\n        '400':\n          $ref: '#/components/responses/MissingParam'\n        '401':\n          $ref: '#/components/responses/Unauthorized'\n        '429':\n          $ref: '#/components/responses/TooManyRequests'\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "public/security.txt",
    "content": "Contact: mailto:security@as93.net\nContact: mailto:alicia@omg.lol\nExpires: 2024-12-31T23:59:00.000Z\nEncryption: https://keybase.io/aliciasykes/pgp_keys.asc?fingerprint=0688f8d34587d954e9e51fb8fedb68f55c0283a7\nPreferred-Languages: en\nCanonical: /security.txt\n"
  },
  {
    "path": "server.js",
    "content": "\nimport fs from 'fs';\nimport path from 'path';\nimport cors from 'cors';\nimport dotenv from 'dotenv';\nimport express from 'express';\nimport rateLimit from 'express-rate-limit';\nimport historyApiFallback from 'connect-history-api-fallback';\n\n// Load environment variables from .env file\ndotenv.config();\n\n// Create the Express app\nconst app = express();\n\nconst __filename = new URL(import.meta.url).pathname;\nconst __dirname = path.dirname(__filename);\n\nconst port = process.env.PORT || 3000; // The port to run the server on\nconst API_DIR = '/api'; // Name of the dir containing the lambda functions\nconst dirPath = path.join(__dirname, API_DIR); // Path to the lambda functions dir\nconst guiPath = path.join(__dirname, 'dist', 'client');\nconst placeholderFilePath = path.join(__dirname, 'public', 'placeholder.html');\nconst handlers = {}; // Will store list of API endpoints\nprocess.env.WC_SERVER = 'true'; // Tells middleware to return in non-lambda mode\n\n// Enable CORS\napp.use(cors({\n  origin: process.env.API_CORS_ORIGIN || '*',\n}));\n\n// Define max requests within each time frame\nconst limits = [\n  { timeFrame: 10 * 60, max: 100, messageTime: '10 minutes' },\n  { timeFrame: 60 * 60, max: 250, messageTime: '1 hour' },\n  { timeFrame: 12 * 60 * 60, max: 500, messageTime: '12 hours' },\n];\n\n// Construct a message to be returned if the user has been rate-limited\nconst makeLimiterResponseMsg = (retryAfter) => {\n  const why = 'This keeps the service running smoothly for everyone. '\n  + 'You can get around these limits by running your own instance of Web Check.';\n  return `You've been rate-limited, please try again in ${retryAfter} seconds.\\n${why}`;\n};\n\n// Create rate limiters for each time frame\nconst limiters = limits.map(limit => rateLimit({\n  windowMs: limit.timeFrame * 1000,\n  max: limit.max,\n  standardHeaders: true,\n  legacyHeaders: false,\n  message: { error: makeLimiterResponseMsg(limit.messageTime) }\n}));\n\n// If rate-limiting enabled, then apply the limiters to the /api endpoint\nif (process.env.API_ENABLE_RATE_LIMIT === 'true') {\n  app.use(API_DIR, limiters);\n}\n\n// Read and register each API function as an Express routes\nfs.readdirSync(dirPath, { withFileTypes: true })\n  .filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))\n  .forEach(async dirent => {\n    const routeName = dirent.name.split('.')[0];\n    const route = `${API_DIR}/${routeName}`;\n    // const handler = require(path.join(dirPath, dirent.name));\n\n    const handlerModule = await import(path.join(dirPath, dirent.name));\n    const handler = handlerModule.default || handlerModule;\n    handlers[route] = handler;\n\n    app.get(route, async (req, res) => {\n      try {\n        await handler(req, res);\n      } catch (err) {\n        res.status(500).json({ error: err.message });\n      }\n    });\n  });\n\nconst renderPlaceholderPage = async (res, msgId, logs) => {\n  const errorMessages = {\n    notCompiled: 'Looks like the GUI app has not yet been compiled.<br />'\n    + 'Run <code>yarn build</code> to continue, then restart the server.',\n    notCompiledSsrHandler: 'Server-side rendering failed to initiate, as SSR handler not found.<br />'\n    + 'This can be fixed by running <code>yarn build</code>, then restarting the server.<br />',\n    disabledGui:  'Web-Check API is up and running!<br />Access the endpoints at '\n    + `<a href=\"${API_DIR}\"><code>${API_DIR}</code></a>`,\n  };\n  const logOutput = logs ? `<div class=\"logs\"><code>${logs}</code></div>` : '';\n  const errorMessage = (errorMessages[msgId] || 'An mystery error occurred.') + logOutput;\n  const placeholderContent = await fs.promises.readFile(placeholderFilePath, 'utf-8');\n  const htmlContent = placeholderContent.replace('<!-- CONTENT -->', errorMessage );\n  res.status(500).send(htmlContent);\n};\n\n// Create a single API endpoint to execute all lambda functions\napp.get(API_DIR, async (req, res) => {\n  const results = {};\n  const { url } = req.query;\n  const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 20000;\n\n  const executeHandler = async (handler, req) => {\n    return new Promise(async (resolve, reject) => {\n      try {\n        const mockRes = {\n          status: () => mockRes,\n          json: (body) => resolve({ body }),\n        };\n        await handler({ ...req, query: { url } }, mockRes);\n      } catch (err) {\n        reject(err);\n      }\n    });\n  };\n\n  const timeout = (ms, jobName = null) => {\n    return new Promise((_, reject) => {\n      setTimeout(() => {\n        reject(new Error(\n          `Timed out after ${ms/1000} seconds${jobName ? `, when executing ${jobName}` : ''}`\n        ));\n      }, ms);\n    });\n  };\n\n  const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => {\n    const routeName = route.replace(`${API_DIR}/`, '');\n\n    try {\n      const result = await Promise.race([\n        executeHandler(handler, req, res),\n        timeout(maxExecutionTime, routeName)\n      ]);\n      results[routeName] = result.body;\n    } catch (err) {\n      results[routeName] = { error: err.message };\n    }\n  });\n\n  await Promise.all(handlerPromises);\n  res.json(results);\n});\n\n// Skip the marketing homepage, for self-hosted users\napp.use((req, res, next) => {\n  if (req.path === '/' && process.env.BOSS_SERVER !== 'true' && !process.env.DISABLE_GUI) {\n    req.url = '/check';\n  }\n  next();\n});\n\n// Serve up the GUI - if build dir exists, and GUI feature enabled\nif (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') {\n  app.get('/', async (req, res) => {\n    renderPlaceholderPage(res, 'disabledGui');\n  });\n} else if (!fs.existsSync(guiPath)) {\n  app.get('/', async (req, res) => {\n    renderPlaceholderPage(res, 'notCompiled');\n  });\n} else { // GUI enabled, and build files present, let's go!!\n  app.use(express.static('dist/client/'));\n  app.use(async (req, res, next) => {\n    const ssrHandlerPath = path.join(__dirname, 'dist', 'server', 'entry.mjs');\n    import(ssrHandlerPath).then(({ handler: ssrHandler }) => {\n      ssrHandler(req, res, next);\n    }).catch(async err => {\n      renderPlaceholderPage(res, 'notCompiledSsrHandler', err.message);\n    });\n  });  \n}\n\n// Handle SPA routing\napp.use(historyApiFallback({\n  rewrites: [\n    { from: new RegExp(`^${API_DIR}/.*$`), to: (context) => context.parsedUrl.path },\n    { from: /^.*$/, to: '/index.html' }\n  ]\n}));\n\n// Anything left unhandled (which isn't an API endpoint), return a 404\napp.use((req, res, next) => {\n  if (!req.path.startsWith(`${API_DIR}/`)) {\n    res.status(404).sendFile(path.join(__dirname, 'public', 'error.html'));\n  } else {\n    next();\n  }\n});\n\n// Print nice welcome message to user\nconst printMessage = () => {\n  console.log(\n    `\\x1b[36m\\n` +\n    '    __      __   _         ___ _           _   \\n' +\n    '    \\\\ \\\\    / /__| |__ ___ / __| |_  ___ __| |__\\n' +\n    '     \\\\ \\\\/\\\\/ / -_) \\'_ \\\\___| (__| \\' \\\\/ -_) _| / /\\n' +\n    '      \\\\_/\\\\_/\\\\___|_.__/    \\\\___|_||_\\\\___\\\\__|_\\\\_\\\\\\n' +\n    `\\x1b[0m\\n`,\n    `\\x1b[1m\\x1b[32m🚀 Web-Check is up and running at http://localhost:${port} \\x1b[0m\\n\\n`,\n    `\\x1b[2m\\x1b[36m🛟 For documentation and support, visit the GitHub repo: ` +\n    `https://github.com/lissy93/web-check \\n`,\n    `💖 Found Web-Check useful? Consider sponsoring us on GitHub ` +\n    `to help fund maintenance & development.\\x1b[0m`\n  );\n};\n\n// Create server\napp.listen(port, () => {\n  printMessage();\n});\n\n"
  },
  {
    "path": "src/components/homepage/AboutSection.astro",
    "content": "---\n\nimport ButtonGroup from '@components/homepage/ButtonGroup.astro';\nimport Features from '@components/homepage/Features.astro';\nimport SponsorSegment from '@components/homepage/SponsorSegment.astro';\n\nconst supportedChecks = [\n  'Archive History',\n  'Block List Check',\n  'Carbon Footprint',\n  'Cookies',\n  'DNS Server',\n  'DNS Records',\n  'DNSSEC',\n  'Site Features',\n  'Firewall Types',\n  'Get IP Address',\n  'Headers',\n  'HSTS',\n  'HTTP Security',\n  'Linked Pages',\n  'Mail Config',\n  'Open Ports',\n  'Quality Check',\n  'Global Rank',\n  'Redirects',\n  'Robots.txt',\n  'Screenshot',\n  'Security.txt',\n  'Sitemap',\n  'Social Tags',\n  'SSL Certificate',\n  'Uptime Status',\n  'Tech Stack',\n  'Known Threats',\n  'TLS Version',\n  'Trace Route',\n  'TXT Records',\n  'Whois Lookup'\n];\n\nconst links = [\n  {\n    title: 'View on GitHub',\n    url: 'https://github.com/lissy93/web-check',\n    icon: 'github',\n    isCta: true,\n  },\n  {\n    title: 'Deploy your Own',\n    url: '/self-hosted-setup',\n    icon: 'rocket',\n    isCta: false,\n  },\n  {\n    title: 'Use the API',\n    url: '/web-check-api',\n    icon: 'code',\n    isCta: false,\n  },\n];\n\n---\n\n<div class=\"about-section\">\n  <div class=\"features-wrap\">\n    <h4>Ready to get started?</h4>\n    <p class=\"what\">\n      With over <span>30 supported checks</span>\n      you can view and analyse key website information in an instant\n    </p>\n  </div>\n  <hr />\n  <Features supportedChecks={supportedChecks} />\n  <ButtonGroup links={links} />\n  <hr />\n  <SponsorSegment />\n</div>\n\n<style lang=\"scss\">\n  .about-section {\n    width: 80vw;\n    margin: 0 auto;\n  }\n  .features-wrap {\n    width: 80vw;\n    max-width: 650px;\n    margin: 0 auto;\n  }\n  h4 {\n    text-transform: capitalize;\n    font-size: 1.8rem;\n    text-align: center;\n    background: linear-gradient(90deg, var(--text-color), var(--primary));\n    background-clip: text;\n    text-fill-color: transparent;\n    color: transparent;\n  }\n  .what {\n    text-align: center;\n    font-size: 1.2rem;\n    line-height: 1.5;\n    span {\n      color: var(--primary);\n      font-style: italic;\n    }\n  }\n  hr {\n    width: 3rem;\n    margin: 3rem auto;\n    color: var(--primary);\n  }\n</style>\n"
  },
  {
    "path": "src/components/homepage/AnimatedButton.astro",
    "content": "---\nconst buttonText = 'Analyze URL';\nconst buttonType = 'submit';\n---\n\n<button class=\"button\" type={buttonType}>{buttonText}</button>\n\n<style lang=\"scss\">\n  @property --angle {\n    syntax: '<angle>';\n    initial-value: 90deg;\n    inherits: true;\n  }\n\n  @property --gradX {\n    syntax: '<percentage>';\n    initial-value: 50%;\n    inherits: true;\n  }\n\n  @property --gradY {\n    syntax: '<percentage>';\n    initial-value: 0%;\n    inherits: true;\n  }\n\n  .button {\n    padding: 1rem;\n    background: transparent;\n    font-size: 2rem;\n    color: var(--text-color-secondary);\n    border: 2px solid var(--text-color-thirdly);\n    border-radius: 3px;\n    font-weight: bold;\n    background: var(--background);   \n    cursor: pointer;\n    overflow: hidden;\n    --angle: 90deg;\n    --gradX: 100%;\n    --gradY: 50%;\n    --c1: var(--primary);\n    --c2: var(--text-color-thirdly);\n    border-image: conic-gradient(from var(--angle), var(--c2), var(--c1) 0.5turn, var(--c1) 0.15turn, var(--c2) 0.25turn) 1;\n    animation: borderRotate 3500ms linear infinite forwards;\n    transition: border 0.3s ease-in-out;\n    &:hover {\n      border: 2px solid var(--primary);\n    }\n  }\n\n  @keyframes borderRotate {\n    0% { --angle: 90deg; }\n    25% { --angle: 180deg; }\n    50% { --angle: 270deg; }\n    75% { --angle: 360deg; }\n    100% { --angle: 450deg; }\n  }\n</style>\n\n<script>\n// Workaround to add fancy border animation for users who use a REAL browser\n// Since CSS @property attribute isn't supported in Firefox yet\ndocument.addEventListener('DOMContentLoaded', () => {\n  const isFirefox = typeof (window as any).InstallTrigger !== 'undefined';\n  if (isFirefox) {\n    const button = document.querySelector('.button') as HTMLElement | null;\n    if (button) {\n      const duration = 3500;\n      const startAngle = 90;\n      const endAngle = 420;\n      function animateAngle() {\n        let start: number | null = null;\n        function step(timestamp: number) {\n          if (!start) start = timestamp;\n          const progress = (timestamp - start) / duration;\n          const angle = startAngle + progress * (endAngle - startAngle);\n\n          button && button.style.setProperty('--angle', `${angle}deg`);\n\n          if (progress < 1) {\n            window.requestAnimationFrame(step);\n          } else {\n            start = null;\n            window.requestAnimationFrame(step);\n          }\n        }\n        window.requestAnimationFrame(step);\n      }\n      animateAngle();\n    }\n  }\n});\n</script>\n"
  },
  {
    "path": "src/components/homepage/AnimatedInput.astro",
    "content": "---\nconst placeholders = [\n  'duck.com',\n  'github.com',\n  'google.com',\n  'x.com',\n  'bbc.co.uk',\n  'wikipedia.org',\n  'openai.com',\n];\n---\n\n\n<div class=\"input-container\">\n  <input\n    required\n    id=\"url-input\"\n    type=\"url\"\n    name=\"url\"\n    placeholder=\"E.g. duck.com\"\n  />\n  <div class=\"placeholder-container\">\n    <span class=\"starter\" aria-hidden=\"true\">E.g.</span>\n    {placeholders.map((placeholder, index) => (\n      <span class={`placeholder ${index === 0 ? 'active' : ''}`} aria-hidden=\"true\">{placeholder}</span>\n    ))}\n  </div>\n</div>\n\n<script>\ndocument.addEventListener('DOMContentLoaded', () => {\n  // Grab the DOM elements we need\n  const placeholders = document.querySelectorAll('.placeholder');\n  const starter = document.querySelector('.starter') as HTMLElement;\n  const inputField = document.getElementById('url-input') as HTMLInputElement;\n\n  // Variables for the configuring + tracking the placeholder animation\n  let currentIndex = 0;\n  const timeBetweenPlaceholders = 3000;\n  let interval: ReturnType<typeof setInterval>;\n  \n  // Function to change which placeholder is currently visible\n  const changePlaceholder = () => {\n    placeholders.forEach((el, index) => {\n      starter.classList.remove('hide');\n      if (index === currentIndex) {\n        el.classList.add('active');\n        el.classList.remove('inactive');\n        el.classList.remove('still-inactive');\n      } else {\n        el.classList.add('inactive');\n        el.classList.remove('active');\n        setTimeout(() => {\n          el.classList.add('still-inactive');\n        }, timeBetweenPlaceholders / 5);\n      }\n    });\n    currentIndex = (currentIndex + 1) % placeholders.length;\n  };\n\n  // Begin the placeholder animation\n  const startPlaceholderAnimation = () => {\n    interval = setInterval(changePlaceholder, timeBetweenPlaceholders);\n  };\n\n  // Stop the placeholder animation\n  const stopPlaceholderAnimation = () => {\n    clearInterval(interval);\n    starter.classList.add('hide');\n    placeholders.forEach((el) => {\n      el.classList.remove('active');\n      el.classList.add('inactive');\n    });\n  };\n\n  // When user focuses on the input field, stop the animation\n  inputField.addEventListener('focus', () => {\n    stopPlaceholderAnimation();\n  });\n\n  // And, when user un-focuses on input, resume the placeholder animation\n  inputField.addEventListener('blur', () => {\n    if (!inputField.value) {\n      startPlaceholderAnimation();\n    }\n  });\n\n  // If user types something, stop the animation\n  inputField.addEventListener('input', () => {\n    if (inputField.value) {\n      stopPlaceholderAnimation();\n    } else if (document.activeElement !== inputField) {\n      startPlaceholderAnimation();\n    }\n  });\n\n  // Disable the input's placeholder attribute\n  inputField.setAttribute('placeholder', '');\n\n  // Make visible the placeholder container\n  (document.querySelector('.placeholder-container') as HTMLElement).style.display = 'flex';\n\n  // And finally, start the animation!\n  startPlaceholderAnimation();\n});\n</script>\n\n\n<style lang=\"scss\">\n@import '@styles/typography.scss';\n@import '@styles/global.scss';\n\n.input-container {\n  position: relative;\n  overflow: hidden;\n  input {\n    width: 100%;\n    padding: 1rem;\n    background: var(--background-50);\n    font-size: 2rem;\n    color: var(--text-color-secondary);\n    border: 2px solid var(--text-color);\n    border-radius: 3px;\n    transition: all 0.3s ease-in-out;\n    &:focus {\n      border-color: var(--primary);\n    }\n  }\n}\n\n.input-container .placeholder-container {\n  display: none;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  align-items: center;\n  padding-left: 16px;\n  pointer-events: none;\n}\n\n.starter, .placeholder {\n  position: absolute;\n  width: 100%;\n  font-size: 2rem;\n  color: var(--text-color-thirdly);\n  padding: 0.1rem 0;\n  user-select: none;\n}\n\n.starter {\n  transition: opacity 0.6s, transform 0.5s;\n  &.hide {\n    opacity: 0;\n    transform: translateX(-50%);\n  }\n}\n\n.placeholder {\n  padding-left: 4rem;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  transition: transform 0.5s ease-in-out, opacity 0.6s;\n  opacity: 0;\n  transform: translateY(-150%);\n}\n\n.placeholder.active {\n  transform: translateY(0);\n  opacity: 1;\n}\n\n.placeholder.inactive {\n  transform: translateY(150%);\n  opacity: 0;\n}\n\n.placeholder.still-inactive {\n  transform: translateY(-150%);\n  opacity: 0;\n}\n\n\n</style>\n"
  },
  {
    "path": "src/components/homepage/ButtonGroup.astro",
    "content": "---\nimport Icon from '@components/molecules/Icon.svelte';\n\ninterface Props {\n  links: {\n    title: string;\n    url: string;\n    icon: string;\n    isCta: boolean;\n  }[];\n}\n\nconst { links } = Astro.props;\n\n---\n\n<div class=\"button-wrap\">\n  {links.map(link => (\n    <a href={link.url} class={link.isCta ? 'cta' : ''}><Icon name={link.icon} size={2} />{link.title}</a>\n  ))}\n</div>\n\n<style lang=\"scss\">\n@import '@styles/global.scss';\n.button-wrap {\n  margin: 3rem auto;\n  display: flex;\n  gap: 2rem;\n  justify-content: center;\n  a {\n    width: 20rem;\n    display: flex;\n    gap: 1rem;\n    align-items: center;\n    justify-content: center;\n    text-align: center;\n    font-weight: bold;\n    box-sizing: border-box;\n    padding: 0.75rem;\n    background: var(--background-50);\n    font-size: 1.2rem;\n    color: var(--text-color);\n    border: 2px solid var(--text-color);\n    border-radius: 3px;\n    transition: all 0.25s ease-in-out;\n    &:hover {\n      cursor: pointer;\n      text-decoration: none;\n      background: var(--text-color);\n      color: var(--background);\n    }\n    &.cta {\n      color: var(--primary);\n      border-color: var(--primary);\n      &:hover {\n        background: var(--primary);\n        color: var(--background);\n      }\n    }\n  }\n  @include for-mobile-only {\n    flex-direction: column;\n    align-items: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/homepage/Features.astro",
    "content": "---\n\nimport Icon from '@components/molecules/Icon.svelte';\n\ninterface Props {\n  supportedChecks: string[];\n}\n\nconst { supportedChecks } = Astro.props;\n\n---\n\n<ul class=\"features\">\n  {supportedChecks.map((check) => (\n    <li>\n      <Icon name=\"check\" />\n      {check}\n    </li>\n  ))}\n  <li><Icon name=\"plus\" /><a href=\"/about#features\">More</a></li>\n</ul>\n\n<style lang=\"scss\">\n.features {\n  columns: 5;\n  column-width: 150px;\n  list-style: none;\n  padding: 0;\n  li {\n    font-size: 1.2rem;\n    line-height: 1.25;\n    margin: 0.5rem 0;\n    display: flex;\n    gap: 0.5rem;\n    :global(svg) {\n      color: var(--primary);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/homepage/HeroForm.astro",
    "content": "---\nimport AnimatedButton from \"./AnimatedButton.astro\"\nimport AnimatedInput from \"./AnimatedInput.astro\"\nimport Screenshots from \"./Screenshots.astro\"\n---\n\n<div class=\"hero\">\n<div class=\"left\">\n  <h1>\n    <img src=\"/favicon.svg\" alt=\"Check Web\" width=\"64\" />\n    <span class=\"web\">Web</span>\n    <span class=\"check\">Check</span>\n  </h1>\n  <div class=\"homepage-action-content\">\n    <h2>We give you X-Ray<br />Vision for your Website</h2>\n    <h3>\n      In just 20 seconds, you can see\n      <span>what attackers already know</span>\n    </h3>\n    <form name=\"live-start\" autocomplete=\"off\" action=\"/check\" class=\"live-start\" id=\"live-start\">\n      <label for=\"url\">Enter a URL to start 👇</label>\n      <AnimatedInput />\n      <AnimatedButton />\n    </form>\n  </div>\n</div>\n<Screenshots />\n</div>\n\n<script>\n  /**\n   * Form management actions (validation, submission, etc.)\n   * We just use normal, old school JavaScript for this\n   */\n\n  // Select the form and input elements from the DOM\n  const form = document.getElementById('live-start');\n  const urlInput = document.getElementById('url-input') as HTMLInputElement;\n    \n  // Submit Event - called when user submits form with a valid URL\n  // Gets and checks the URL, then redirects user to /check/:url\n  form?.addEventListener('submit', (event) => {\n    event.preventDefault();\n    const url = urlInput.value.trim();\n    if (url) {\n      const encodedUrl = encodeURIComponent(url);\n      window.location.href = `/check/${encodedUrl}`;\n    }\n  });\n  \n  // User presses enter, forgets to add protocol\n  // Will add https:// to the URL, and retry form submit\n  urlInput?.addEventListener('keydown', (event) => {\n    if (event.key === 'Enter') {\n      const url = urlInput.value.trim();\n      const urlWithoutProtocolRegex = /^[a-zA-Z0-9]+[a-zA-Z0-9.-]*\\.[a-zA-Z]{2,}$/;\n      if (url && !/^https?:\\/\\//i.test(url) && urlWithoutProtocolRegex.test(url)) {\n        urlInput.value = 'https://' + url;\n        form?.dispatchEvent(new Event('submit'));\n      }\n    }\n  });\n  </script>\n\n<style lang=\"scss\">\n@import '@styles/global.scss';\n.hero {\n  display: flex;\n  justify-content: space-around;\n  width: 100vw;\n  @include desktop-down {\n    display: block;\n    width: 80vw;\n    margin: 0 auto;\n  }\n}\n.left {\n    min-height: 100vh;\n    display: flex;\n    flex-direction: column;\n    gap: 20vh;\n    @include tablet-landscape-down {\n      gap: 6rem;\n    }\n    @include mobile-down {\n      gap: 4rem;\n    }\n    h1 {\n      margin: 0;\n      fontis-size: 3em;\n      z-index: 5;\n      display: flex;\n      gap: 0.1rem;\n      align-items: center;\n      img {\n        vertical-align: middle;\n        width: 3rem;\n        margin-right: 0.5rem;\n      }\n      .web {\n        color: var(--text-color);\n      }\n      .check {\n        color: var(--primary);\n        font-style: italic;\n      }\n    }\n    .homepage-action-content {\n      display: flex;\n      flex-direction: column;\n      gap: 1rem;\n      max-width: 700px;\n      z-index: 1;\n      @include tablet-landscape-down {\n        gap: 0.25rem;\n      }\n      h2 {\n        font-size: 3rem;\n        font-weight: bold;\n      }\n      h3 {\n        font-weight: normal;\n        font-size: 1.3rem;\n        span {\n          color: var(--primary);\n          font-style: italic;\n        }\n      }\n      form {\n        margin-top: 1.5rem;\n        display: flex;\n        flex-direction: column;\n        gap: 1rem;\n        label {\n          font-size: 1.3rem;\n          font-weight: bold;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/components/homepage/HomeBackground.tsx",
    "content": "import { useState, useEffect } from 'react';\nimport { motion } from 'framer-motion';\nimport styled from '@emotion/styled';\n\n// Global Animation Configuration Constants\nconst dotSpacing = 32; // Number of px between each dot\nconst meteorCount = 4; // Number of meteors to display at any given time\nconst tailLength = 80; // Length of the meteor tail in px\nconst distanceBase = 5; // Base distance for meteor to travel in grid units\nconst distanceVariance = 5; // Variance for randomization to append to travel in grid units\nconst durationBase = 1.5; // Base duration for meteor to travel in seconds\nconst durationVariance = 1; // Variance for randomization to append to travel in seconds\nconst delayBase = 500; // Base delay for meteor to respawn in milliseconds\nconst delayVariance = 1500; // Variance for randomization to append to respawn in milliseconds\nconst tailDuration = 0.25; // Duration for meteor tail to retract in seconds\nconst headEasing = [0.8, 0.6, 1, 1]; // Easing for meteor head\nconst tailEasing = [0.5, 0.6, 0.6, 1]; // Easing for meteor tail\n\nconst MeteorContainer = styled(motion.div)`\n  position: absolute;\n  width: 4px;\n  height: 4px;\n  border-radius: 50%;\n  background-color: #9fef00;\n  top: 1px;\n`;\n\nconst Tail = styled(motion.div)`\n  position: absolute;\n  top: -80px;\n  left: 1px;\n  width: 2px;\n  height: 80px;\n  background: linear-gradient(to bottom, transparent, #9fef00);\n`;\n\nconst StyledSvg = styled.svg`\n  pointer-events: none;\n  position: absolute;\n  inset: 0;\n  height: 100%;\n  width: 100%;\n  fill: rgba(100, 100, 100, 0.5);\n  height: 100vh;\n`;\n\nconst StyledRect = styled.rect`\n  width: 100%;\n  height: 100%;\n  stroke-width: 0;\n`;\n\nconst Container = styled.div`\n  pointer-events: none;\n  position: absolute;\n  height: 100vh;\n  width: 100vw;\n  z-index: 1;\n  top: 0;\n  left: 0;\n  background: radial-gradient(circle at center top, transparent, transparent 60%, var(--background) 100%);\n`;\n\nconst generateMeteor = (id: number, gridSizeX: number, gridSizeY: number) => {\n  const column = Math.floor(Math.random() * gridSizeX);\n  const startRow = Math.floor(Math.random() * (gridSizeY - 12));\n  const travelDistance = distanceBase + Math.floor(Math.random() * distanceVariance);\n  const duration = durationBase +  Math.floor(Math.random() * durationVariance);\n\n  return {\n    id,\n    column,\n    startRow,\n    endRow: startRow + travelDistance,\n    duration,\n    tailVisible: true,\n    animationStage: 'traveling',\n    opacity: 1,\n  };\n};\n\nconst generateInitialMeteors = (gridSizeX: number, gridSizeY: number) => {\n  const seen = new Set();\n  return Array.from({ length: meteorCount }, (_, index) => generateMeteor(index, gridSizeX, gridSizeY))\n    .filter(item => !seen.has(item.column) && seen.add(item.column));\n};\n\nconst WebCheckHomeBackground = () => {\n  const [gridSizeX, setGridSizeX] = useState(Math.floor(window.innerWidth / dotSpacing));\n  const [gridSizeY, setGridSizeY] = useState(Math.floor(window.innerHeight / dotSpacing));\n  const [meteors, setMeteors] = useState(() => generateInitialMeteors(gridSizeX, gridSizeY));\n\n  const handleAnimationComplete = (id: number) => {\n    setMeteors(current =>\n      current.map(meteor => {\n        if (meteor.id === id) {\n          if (meteor.animationStage === 'traveling') {\n            // Transition to retracting tail\n            return { ...meteor, tailVisible: false, animationStage: 'retractingTail' };\n          } else if (meteor.animationStage === 'retractingTail') {\n            // Set to resetting and make invisible\n            return { ...meteor, animationStage: 'resetting', opacity: 0 };\n          } else if (meteor.animationStage === 'resetting') {\n            // Respawn the meteor after a delay\n            setTimeout(() => {\n              setMeteors(current =>\n                current.map(m => m.id === id ? generateMeteor(id, gridSizeX, gridSizeY) : m)\n              );\n            }, delayBase + Math.random() * delayVariance);\n          }\n        }\n        return meteor;\n      })\n    );\n  };\n\n  useEffect(() => {\n    const handleResize = () => {\n      setGridSizeX(Math.floor(window.innerWidth / dotSpacing));\n      setGridSizeY(Math.floor(window.innerHeight / dotSpacing));\n    };\n    window.addEventListener('resize', handleResize);\n    return () => window.removeEventListener('resize', handleResize);\n  }, []);\n\n  return (\n    <>\n      <Container />\n      <StyledSvg>\n        <defs>\n          <pattern id=\"dot-pattern\" width={dotSpacing} height={dotSpacing} patternUnits=\"userSpaceOnUse\">\n            <circle cx={1} cy={1} r={2} />\n          </pattern>\n        </defs>\n        <StyledRect fill=\"url(#dot-pattern)\" />\n      </StyledSvg>\n\n      {meteors.map(({ id, column, startRow, endRow, duration, tailVisible, animationStage, opacity }) => {\n        return (\n        <MeteorContainer\n          key={id}\n          initial={{\n            x: column * dotSpacing,\n            y: startRow * dotSpacing,\n            opacity: 1,\n          }}\n          animate={{\n            opacity: tailVisible ? 1 : 0,\n            y: animationStage === 'resetting' ? startRow * dotSpacing : endRow * dotSpacing,\n          }}\n          transition={{\n            duration: animationStage === 'resetting' ? 0 : duration,\n            ease: headEasing,\n          }}\n          onAnimationComplete={() => handleAnimationComplete(id)}\n        >\n          <Tail\n            initial={{ top: `-${tailLength}px`, height: `${tailLength}px` }}\n            animate={{ top: tailVisible ? `-${tailLength}px` : 0, height: tailVisible ? `${tailLength}px` : 0 }}\n            transition={{\n              duration: tailDuration,\n              ease: tailEasing,\n            }}\n          />\n        </MeteorContainer>\n      );\n      })}\n    </>\n  );\n};\n\nexport default WebCheckHomeBackground;\n"
  },
  {
    "path": "src/components/homepage/Screenshots.astro",
    "content": "---\nconst screenshots = [\n  \"https://i.ibb.co/kB7LsV1/wc-ssl.png\",\n  \"https://i.ibb.co/7Q1kMwM/wc-dns.png\",\n  \"https://i.ibb.co/TTQ6DtP/wc-cookies.png\",\n  \"https://i.ibb.co/KwQCjPf/wc-robots.png\",\n  \"https://i.ibb.co/t3xcwP1/wc-headers.png\",\n  \"https://i.ibb.co/Kqg8rx7/wc-quality.png\",\n  \"https://i.ibb.co/cXH2hfR/wc-location.png\",\n  \"https://i.ibb.co/25j1sT7/wc-hosts.png\",\n  \"https://i.ibb.co/hVVrmwh/wc-redirects.png\",\n  \"https://i.ibb.co/wyt21QN/wc-txt-records.png\",\n  \"https://i.ibb.co/V9CNLBK/wc-status.png\",\n  \"https://i.ibb.co/F8D1hmf/wc-ports.png\",\n  \"https://i.ibb.co/M59qgxP/wc-trace-route.png\",\n  \"https://i.ibb.co/5v6fSyw/Screenshot-from-2023-07-29-19-07-50.png\",\n  \"https://i.ibb.co/Mk1jx32/wc-server.png\",\n  \"https://i.ibb.co/89WLp14/wc-domain.png\",\n  \"https://i.ibb.co/89WLp14/wc-domain.png\",\n  \"https://i.ibb.co/J54zVmQ/wc-dnssec.png\",\n  \"https://i.ibb.co/gP4P6kp/wc-features.png\",\n  \"https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png\",\n  \"https://i.ibb.co/tKpL8F9/Screenshot-from-2023-08-12-15-43-12.png\",\n  \"https://i.ibb.co/bBQSQNz/Screenshot-from-2023-08-12-15-43-46.png\",\n  \"https://i.ibb.co/GtrCQYq/Screenshot-from-2023-07-21-12-28-38.png\",\n  \"https://i.ibb.co/tq1FT5r/Screenshot-from-2023-07-24-20-31-21.png\",\n  \"https://i.ibb.co/LtK14XR/Screenshot-from-2023-07-29-11-16-44.png\",\n  \"https://i.ibb.co/4srTT1w/Screenshot-from-2023-07-29-11-15-27.png\",\n  \"https://i.ibb.co/yqhwx5G/Screenshot-from-2023-07-29-18-22-20.png\",\n  \"https://i.ibb.co/MfcxQt2/Screenshot-from-2023-08-12-15-40-52.png\",\n  \"https://i.ibb.co/LP05HMV/Screenshot-from-2023-08-12-15-40-28.png\",\n  \"https://i.ibb.co/nB9szT1/Screenshot-from-2023-08-14-22-31-16.png\",\n  \"https://i.ibb.co/nkbczgb/Screenshot-from-2023-08-14-22-02-40.png\",\n  \"https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png\",\n  \"https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png\",\n  \"https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png\",\n  \"https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png\",\n  \"https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png\",\n  \"https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png\"\n];\n---\n\n<div class=\"container\">\n  <div class=\"column\">\n    {screenshots.slice(0, 20).map((src, idx) => (\n      <img src={src} class=\"screenshot\" alt={`Screenshot ${idx}`} />\n    ))}\n  </div>\n  <div class=\"column\">\n    {screenshots.slice(20).map((src, idx) => (\n      <img src={src} class=\"screenshot\" alt={`Screenshot ${idx + 20}`} />\n    ))}\n  </div>\n</div>\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n\n  .container {\n    display: flex;\n    height: 95vh;\n    overflow: hidden;\n    width: 33vw;\n    justify-content: flex-end;\n    gap: 1.5rem;\n    z-index: 2;\n    @include desktop-down {\n      display: none;\n    }\n  }\n\n  .column {\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: flex-start;\n    animation: scroll 24s linear infinite;\n    &:nth-child(2) {\n      animation: scroll 16s linear infinite;\n    }\n    &:hover {\n      animation-play-state: paused;\n    }\n  }\n\n  .screenshot {\n    width: 100%;\n    margin-bottom: 1.5rem;\n    border-radius: 4px;\n    filter: contrast(120%) grayscale(50%) hue-rotate(-20deg);\n    clip-path: inset(0.5rem);\n    transform: scale(1.1);\n    transition: transform 0.3s ease-in-out;\n  }\n\n  @keyframes scroll {\n    0% {\n      transform: translateY(0);\n    }\n    100% {\n      transform: translateY(-100%);\n    }\n  }\n</style>\n\n\n"
  },
  {
    "path": "src/components/homepage/SponsorSegment.astro",
    "content": "---\nconst sponsorName = 'Terminal Trove';\nconst sponsorTagline = 'The $HOME of all things terminal.';\nconst sponsorLink = 'https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh';\n\nconst ctaPreText = 'Get updates on the latest CLI/TUI tools via the';\nconst ctaLinkHref = 'https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh';\nconst ctaLinkText = 'Terminal Trove Newsletter';\nconst ctaImageSrc = 'https://i.ibb.co/5jJ4bzZ/terminal-trove-cta.png';\n---\n\n<div class=\"sponsored-but-dont-block\">\n  <div>\n    <h5>Sponsored by <a href={sponsorLink}>{sponsorName}</a></h5>\n    <p>\n      {sponsorTagline}<br>\n      {ctaPreText} <a href={ctaLinkHref}>{ctaLinkText}</a>\n    </p>\n  </div>\n  <a href={sponsorLink}>\n    <img width=\"256\" src={ctaImageSrc} alt={`Check out ${sponsorName}`} />\n  </a>\n</div>\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  .sponsored-but-dont-block {\n    background: var(--text-color);\n    color: var(--background);\n    padding: 2rem;\n    border-radius: 3px;\n    gap: 1rem;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    \n    @include tablet-portrait-down {\n      flex-wrap: wrap;\n    }\n    @include for-mobile-only {\n      flex-direction: column;\n      align-items: center;\n    }\n    h5, p, a {\n      color: var(--background);\n    }\n    h5 {\n      font-size: 1.5rem;\n    }\n    p {\n      font-size: 1.2rem;\n      line-height: 1.5;\n    }\n    a {\n      text-decoration: underline;\n    }\n    img {\n      width: 20rem;\n      border-radius: 3px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/components/homepage/TempDisabled.astro",
    "content": "<div class=\"banner\">\n  <p>\n    ⚠️ Web Check is temporarily disabled due to excess demand and associated costs. \n    We apologize for any inconvenience and are working on a solution.\n  </p>\n</div>\n\n<style>\n  .banner {\n    position: relative;\n    top: 0;\n    left: 0;\n    width: 100%;\n    background-color: var(--primary);\n    color: var(--background);\n    padding: 0.75rem 1rem;\n    text-align: center;\n    z-index: 1000;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n  }\n\n  .banner p {\n    margin: 0;\n    font-size: 0.95rem;\n    line-height: 1.4;\n  }\n</style>\n"
  },
  {
    "path": "src/components/molecules/Icon.svelte",
    "content": "<script lang=\"ts\">\n  import { FontAwesomeIcon } from '@fortawesome/svelte-fontawesome';\n  import * as brands from '@fortawesome/free-brands-svg-icons';\n  import * as solidIcons from '@fortawesome/free-solid-svg-icons';\n  import type { IconDefinition } from '@fortawesome/fontawesome-svg-core';\n\n\n  export const iconMap: Record<string, IconDefinition> = {\n    check: solidIcons.faCheck,\n    plus: solidIcons.faPlus,\n    github: brands.faGithubAlt,\n    code: solidIcons.faCode,\n    rocket: solidIcons.faRocket,\n    copy: solidIcons.faCopy,\n  };\n\n  export let name: string;\n  export let size: number = 1;\n  export let color: string = 'currentColor';\n  export let styles: string = '';\n\n</script>\n\n{#if iconMap[name]}\n  <FontAwesomeIcon \n    class=\"fa-icon\"\n    style={`--icon-size: ${size}rem; --icon-color: ${color}; ${styles}`}\n    icon={iconMap[name]} />\n{/if}\n\n<style>\n  :global(.fa-icon) {\n    width: var(--icon-size, 1rem);\n    color: var(--icon-color, currentColor);\n  }\n</style>\n"
  },
  {
    "path": "src/components/scafold/Footer.astro",
    "content": "---\nconst repo = 'github.com/lissy93/web-check';\nconst github = `https://${repo}`;\n\nconst licenseText = 'MIT';\nconst licenseLink = `${github}/blob/master/LICENSE`;\n\nconst aboutLink = '/about';\nconst projectName = 'Web Check';\n\nconst authorName = 'Alicia Sykes';\nconst authorLink = 'https://aliciasykes.com';\nconst currentYear = new Date().getFullYear();\n---\n\n<footer>\n  <p>\n    View source at <a href={github}>{repo}</a>\n  </p>\n  <p>\n    <a href={aboutLink}>{projectName}</a>\n    is licensed under\n    <a href={licenseLink}>{licenseText}</a>\n    - © <a href={authorLink}>{authorName}</a>\n    {currentYear}\n  </p>\n</footer>\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  footer {\n    margin-top: 1rem;\n    background: var(--primary);\n    color: var(--background);\n    padding: 1rem;\n    display: flex;\n    justify-content: space-between;\n    gap: 1rem;\n    flex-wrap: wrap;\n    p, a {\n      margin: 0;\n      color: var(--background);\n      font-size: 1rem;\n    }\n    a {\n      text-decoration: underline;\n    }\n    @include tablet-portrait-down {\n      flex-direction: column;\n      align-items: center;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/components/scafold/Nav.astro",
    "content": "---\n\n---\n\n<nav class=\"navbar\">\n  <div class=\"nav-left\">\n    <a href=\"/\" class=\"logo-link\" aria-label=\"Home\">\n      <img width=\"64\" src=\"/favicon.svg\" alt=\"Site Logo\" />\n      <h2 class=\"site-title\">\n        <span class=\"first\">Web</span>\n        <span class=\"second\">Check</span>\n      </h2>\n    </a>\n  </div>\n  <div class=\"nav-right\">\n    <a href=\"/check\" class=\"nav-link\">Check</a>\n    <a href=\"/web-check-api\" class=\"nav-link\">API</a>\n    <a href=\"/account\" class=\"nav-link\">Login</a>\n  </div>\n</nav>\n\n\n<style lang=\"scss\">\n  .navbar {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 0.5rem 1rem;\n    flex-wrap: wrap;\n    background-color: var(--background);\n    color: var(--text-color);\n    width: 80vw;\n    margin: 0 auto;\n    border-radius: 2rem;\n    border: 1px solid var(--primary-transparent);\n    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);\n    backdrop-filter: blur(5px);\n    -webkit-backdrop-filter: blur(5px);\n    .nav-left {\n      display: flex;\n      align-items: center;\n\n      .logo-link {\n        display: flex;\n        align-items: center;\n        text-decoration: none;\n        color: var(--text-color);\n\n        img {\n          width: 2rem;\n          max-width: 64px;\n          margin-right: 0.5rem;\n        }\n\n        .site-title {\n          margin: 0;\n          font-weight: bold;\n          color: var(--primary);\n          font-size: 1.8rem;\n          .first {\n            color: var(--primary);\n          }\n          .second {\n            color: var(--text-color);\n            font-style: italic;\n          }\n        }\n      }\n    }\n\n    .nav-right {\n      display: flex;\n\n      .nav-link {\n        text-decoration: none;\n        color: var(--text-color);\n        margin-left: 1rem;\n        padding: 0.5rem;\n        transition: background-color 0.3s;\n        position: relative;\n        &:hover {\n          background-color: var(--primary-light);\n        }\n        &:focus {\n          outline: none;\n          background-color: var(--primary-dark);\n          box-shadow: 0 0 0 3px var(--primary);\n        }\n        &:after {    \n          background: none repeat scroll 0 0 transparent;\n          bottom: 0;\n          content: \"\";\n          display: block;\n          height: 2px;\n          left: 50%;\n          position: absolute;\n          background: var(--primary);\n          transition: width 0.3s ease 0s, left 0.3s ease 0s;\n          width: 0;\n        }\n        &:hover:after { \n          width: 100%; \n          left: 0; \n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/env.d.ts",
    "content": "/// <reference path=\"../.astro/types.d.ts\" />\n/// <reference types=\"astro/client\" />\n"
  },
  {
    "path": "src/layouts/Base.astro",
    "content": "---\nimport { ViewTransitions } from 'astro:transitions'\nimport MetaTags from '@layouts/MetaTags.astro';\n\nimport '@styles/typography.scss';\nimport '@styles/global.scss';\nimport '@styles/colors.scss';\nimport '@styles/media-queries.scss';\n\ninterface Props {\n\ttitle?: string;\n\tdescription?: string;\n  keywords?: string;\n\tcustomSchemaJson?: any;\n\tbreadcrumbs?: Array<{\n\t\tname: string;\n\t\titem: string;\n\t}>\n}\n\n---\n\n<!doctype html>\n<html lang=\"en\" data-theme=\"dark\">\n\t<head>\n\t\t<ViewTransitions />\n\t\t<MetaTags {...Astro.props } />\n    <slot name=\"head\" />\n\t\t<link href=\"/fonts/Hubot-Sans/Hubot-Sans.woff2\"\n\t\t\tas=\"font\"\n\t\t\trel=\"preload\"\n\t\t\ttype=\"font/woff2\"\n\t\t\tcrossorigin=\"anonymous\"\n\t\t\t>\n\t</head>\n\t<body>\n\t\t<slot />\n\t</body>\n</html>\n"
  },
  {
    "path": "src/layouts/MetaTags.astro",
    "content": "---\n\ninterface Props {\n\ttitle?: string;\n\tdescription?: string;\n  keywords?: string;\n\tcustomSchemaJson?: any;\n\tbreadcrumbs?: Array<{\n\t\tname: string;\n\t\titem: string;\n\t}>\n}\n\n// Default meta tag values\nconst siteInfo = {\n  title: 'Web Check',\n\ttitleLong: 'Web Check - X-Ray Vision for any Website',\n  description: 'Web Check is the all-in-one OSINT and security tool, for revealing the inner workings of any website',\n  keywords: '',\n  author: 'Alicia Sykes',\n  twitter: '@Lissy_Sykes',\n  site: import.meta.env.SITE_URL || 'https://web-check.xyz',\n  analytics: {\n    enable: import.meta.env.ENABLE_ANALYTICS,\n    domain: 'web-check.as93.net',\n    script: 'https://no-track.as93.net/js/script.js',\n  },\n};\n\n// Set values for the meta tags, from props or defaults\nconst {\n\ttitle = siteInfo.title,\n\tdescription = siteInfo.description,\n\tkeywords = siteInfo.keywords,\n\tbreadcrumbs,\n\tcustomSchemaJson,\n} = Astro.props;\n\n// Set non-customizable values for meta tags, from the siteInfo\nconst { site, author, twitter, analytics, titleLong } = siteInfo;\n\n// Given a map of breadcrumbs, return the JSON-LD for the BreadcrumbList schema\nconst makeBreadcrumbs = () => {\n\tif (!breadcrumbs) return null;\n\treturn {\n\t\t\t\"@context\": \"https://schema.org\",\n\t\t\t\"@type\": \"BreadcrumbList\",\n\t\t\t\"itemListElement\": breadcrumbs.map((breadcrumb, index) => ({\n\t\t\t\t\"@type\": \"ListItem\",\n\t\t\t\t\"position\": index + 1,\n\t\t\t\t\"name\": breadcrumb.name,\n\t\t\t\t\"item\": `${site}/${breadcrumb.item}`\n\t\t\t}))\n\t}\n}\n\n---\n\n<!-- Core info -->\n<title>{title}</title>\n<meta name=\"description\" content={description}>\n<meta name=\"keywords\" content={keywords}>\n<meta name=\"author\" content={author}>\n\n<!-- Page info, viewport, Astro credit -->\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"generator\" content={Astro.generator} />\n<meta name=\"robots\" content=\"index, follow\">\n\n<!-- Icons and colors -->\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n<link rel=\"icon\" type=\"image/png\" sizes=\"512x512\" href=\"/web-check.png\">\n<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/apple-touch-icon.png\">\n\n<!-- Social media meta tags (Open Graphh + Twitter) -->\n<meta property=\"og:site_name\" content={title}>\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:url\" content={site}>\n<meta property=\"og:title\" content={titleLong}>\n<meta property=\"og:description\" content={description}>\n<meta property=\"og:image\" content={`${site}/banner.png`}>\n<meta name=\"twitter:card\" content=\"summary\">\n<meta name=\"twitter:card\" content=\"summary_large_image\">\n<meta name=\"twitter:url\" content={site}>\n<meta name=\"twitter:title\" content={titleLong}>\n<meta name=\"twitter:description\" content={description}>\n<meta name=\"twitter:image\" content=`${site}/banner.png`}>\n<link rel=\"twitter:image\" sizes=\"180x180\" href={`${site}/apple-touch-icon.png`}>\n<meta name=\"twitter:site\" content={twitter}>\n<meta name=\"twitter:creator\" content={twitter}>\n\n<!-- Non-tracking hit counter -->\n{analytics.enable && (\n  <script defer data-domain={analytics.domain} src={analytics.script}></script>\n)}\n\n<!-- Schema.org markup for Google -->\n{breadcrumbs && (\n  <script type=\"application/ld+json\" set:html={JSON.stringify(makeBreadcrumbs())} />\n)}\n{customSchemaJson && (\n  <script type=\"application/ld+json\" set:html={JSON.stringify(customSchemaJson)} />\n)}\n"
  },
  {
    "path": "src/pages/account/index.astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from '@components/scafold/Footer.astro';\nimport Icon from '@components/molecules/Icon.svelte';\n\n---\n\n<BaseLayout\n  title=\"Account | Web Check\"\n  description=\"Login or sign up for a free account on Web Check, to manage API access, save reports, enable website change notificiations and more!\"\n>\n  <div class=\"web-check-page\">\n    <NavBar />\n    <main>\n      <!-- <h1>Login</h1>\n      <p>\n        Your Web Check account is your gateway to managing API access, as well as additional functionality\n      </p> -->\n\n      <div class=\"under-development\">\n        <h4>Account management is currently under development.</h4>\n        <p>\n          Once complete, you will be able to login to your account to manage API\n          access, save reports, enable website change notificiations and more!\n        </p>\n      </div>\n\n    </main>\n    <Footer />\n  </div>\n</BaseLayout>\n\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  .web-check-page {\n    padding-top: 2rem;\n    margin: 0 auto;\n    min-height: 100vh;\n    width: 100vw;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n    background-image: url('/assets/images/background-dots.svg');\n    @include mobile-down {\n      padding: 0.5rem;\n    }\n    .under-development {\n      border: 2px solid var(--warning);\n      background: #ffff0012;\n      padding: 1rem;\n      margin: 2rem auto;\n      border-radius: 4px;\n      h4 {\n        margin-bottom: 1rem;\n      }\n      p {\n        font-size: 1.1rem;\n        line-height: 1.5;\n      }\n    }\n    main {\n      width: 80vw;\n      margin: 0 auto;\n      min-height: 100%;\n      flex-grow: 1;\n      padding: 5rem 0 2rem 0;\n      max-width: 1200px;\n      @include mobile-down {\n      padding: 1rem 0;\n    }\n    }\n\n  }\n  \n\n\n</style>\n"
  },
  {
    "path": "src/pages/check/[...target].astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport Main from '../../web-check-live/main.tsx';\nimport '../../web-check-live/styles/index.css';\n\nexport const prerender = false;\n\nconst { pathname, search } = new URL(Astro.request.url);\n\nconst searchUrl = new URLSearchParams(search).get('url');\n\nif (searchUrl) {\n  Astro.redirect(`/check/${encodeURIComponent(searchUrl)}`);\n}\n\n---\n\n<BaseLayout>\n  <Main {pathname} client:load />\n</BaseLayout>\n\n<script>\n  // Fallback, if Astro hasn't initialized the RC comp yet, then check the url\n  // And if form has been submitted with ?url=, redirect to the results page\n  const searchParams = new URL(window.location.href).searchParams;\n  if (searchParams.has('url')) {\n    window.location.href = `/check/${encodeURIComponent(searchParams.get('url') || '')}`;\n  }\n\n  // And add a manual no-react form submit handler\n  const form = document.querySelector<HTMLFormElement>('form');\n  if (form) {\n    form.addEventListener('submit', function(event: Event) {\n      event.preventDefault();\n      const input = (this as HTMLFormElement).querySelector<HTMLInputElement>('input[name=\"url\"]');  \n      if (input && input.value) {\n        window.location.href = `/check/${encodeURIComponent(input.value)}`;\n      }\n    });\n  }\n\n</script>\n"
  },
  {
    "path": "src/pages/index.astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport HeroForm from '@components/homepage/HeroForm.astro';\nimport TempDisabled from '@/components/homepage/TempDisabled.astro';\nimport HomeBackground from '@/components/homepage/HomeBackground';\nimport AboutSection from '@/components/homepage/AboutSection.astro';\nimport Footer from '@components/scafold/Footer.astro';\n\nconst isBossServer = import.meta.env.BOSS_SERVER === true;\n\nconst disableEverything = import.meta.env.VITE_DISABLE_EVERYTHING === true;\n\n// Redirect strait to /check or /check/:url if running as self-hosted instance\nif (!isBossServer) {\n  const searchUrl = new URLSearchParams(new URL(Astro.request.url).search).get('url');\n  const redirectUrl = searchUrl ? `/check/${encodeURIComponent(searchUrl)}` : '/check';\n  Astro.redirect(redirectUrl);\n}\n\n---\n\n<BaseLayout>\n  <Fragment slot=\"head\">\n    {!isBossServer && (<meta http-equiv=\"refresh\" content=\"0; url=/check\" />)}\n  </Fragment>\n  { disableEverything && <TempDisabled />}\n  <main>\n    <HeroForm />\n    <AboutSection />\n  </main>\n  <Footer />\n  <HomeBackground client:only=\"react\" />\n</BaseLayout>\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  main {\n    min-height: 100vh;\n    padding: 2rem;\n    margin: 0 auto;\n    z-index: 3;\n    background: var(--background);\n    @include tablet-landscape-down {\n      width: 96vw;\n    }\n\n    .screenshot {\n      transform:\n        rotate3d(.5,-.866,0,15deg)\n        rotate(1deg);\n      box-shadow:\n        2em 4em 6em -2em rgba(0,0,0,.5),\n        1em 2em 3.5em -2.5em rgba(0,0,0,.5);\n      transition:\n        transform .4s ease,\n        box-shadow .4s ease;\n      border-radius: .5em;\n\n      &:hover {\n        transform:\n          rotate3d(0,0,0,0deg)\n          rotate(0deg);\n      }\n    }\n  }\n\n</style>\n"
  },
  {
    "path": "src/pages/self-hosted-setup.astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from '@components/scafold/Footer.astro';\nimport Icon from '@components/molecules/Icon.svelte';\n\nconst cardData = [\n  {\n    title: 'Deploy with Docker',\n    description: 'Just one command, and you\\'ll have your own Web Check instance running on your server in minutes.',\n    command: 'docker run -p 3000:3000 lissy93/web-check',\n  },\n  {\n    title: '1-Click Deploy',\n    description: 'Don\\'t have a server? Not a problem! Deploy Web Check with one click on Vercel or Netlify.',\n    buttons: [\n      {\n        name: 'Vercel',\n        image: '/assets/images/vercel.svg',\n        link: 'https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.'\n        +'com%2Fxray-web%2Fweb-check-free&project-name=web-check-free&repository'\n        + '-name=web-check-free&demo-title=Web%20Check&demo-description=Try%20ou'\n        + 't%20the%20live%20demo&demo-url=https%3A%2F%2Fweb-check.xyz%2F&demo-ima'\n        + 'ge=https%3A%2F%2Fi.ibb.co%2Fr0jXN6s%2Fweb-check.png'\n      },\n      {\n        name: 'Netlify',\n        image: '/assets/images/netlify.svg',\n        link: 'https://app.netlify.com/start/deploy?repository='\n        + 'https://github.com/xray-web/web-check-free',\n      }\n    ]\n  },\n  {\n    title: 'Build from Source',\n    description: 'Want to customize Web Check? Clone the repository and deploy it on your server.',\n    buttons: [\n      {\n        name: 'GitHub',\n        image: '/assets/images/github.svg',\n        link: '',\n      }\n    ]\n  }\n];\n\n---\n\n<BaseLayout\n  title=\"Self-Hosted Setup | Web Check\"\n  description=\"Get started with running Web Check locally or on your own server.\"\n>\n  <div class=\"web-check-page\">\n    <NavBar />\n    <main>\n      <h1>Self-Hosted Setup</h1>\n      <p>\n        Get your own free instance of Web Check, running locally or on your server.\n      </p>\n    \n      <div class=\"option-cards\">\n\n        { cardData.map((card, cardIndex) => (\n          <div class=\"card\">\n            <span class=\"option\">Option #{cardIndex + 1}</span>\n            <h3>{card.title}</h3>\n            <p>{card.description}</p>\n            { card.buttons && (\n              <div class=\"buttons\">\n                { card.buttons.map(button => (\n                  <a href={button.link}>\n                    <img src={button.image} />\n                    {button.name}\n                  </a>\n                ))}\n              </div>\n            )}\n            { card.command && (\n              <div class=\"shaddow docker-command\">\n                <code>{card.command}</code>\n                <button><Icon name=\"copy\" size={0.8} /></button>\n              </div>\n            )}\n          </div>\n        ))}\n    </div>\n    </main>\n    <Footer />\n  </div>\n</BaseLayout>\n\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  .web-check-page {\n    padding-top: 2rem;\n    margin: 0 auto;\n    min-height: 100vh;\n    width: 100vw;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n    background-image: url('/assets/images/background-dots.svg');\n    @include mobile-down {\n      padding: 0.5rem;\n    }\n    main {\n      width: 80vw;\n      margin: 0 auto;\n      min-height: 100%;\n      flex-grow: 1;\n      padding: 5rem 0 2rem 0;\n      max-width: 1200px;\n      @include mobile-down {\n      padding: 1rem 0;\n    }\n    }\n    .option-cards {\n      display: grid;\n      gap: 1rem;\n      grid-template-columns: repeat(2, 1fr);\n      margin-top: 2.5rem;\n      .card:first-child {\n        grid-column: span 2;\n      }\n      @include tablet-landscape-down {\n        grid-template-columns: 1fr;\n        .card:first-child {\n          grid-column: span 1;\n        }\n      }\n      .card {\n        padding: 0.5rem 1rem;\n        background-color: var(--background);\n        color: var(--text-color);\n        border-radius: 4px;\n        border: 1px solid var(--primary-transparent);\n        .option {\n          color: var(--primary);\n          font-size: 0.8rem;\n        }\n        h3 {\n          margin-top: 1rem;\n        }\n        p {\n          line-height: 1.3;\n        }\n        .docker-command {\n          display: flex;\n          margin: 1rem 0;\n          position: relative;\n          button {\n            background: var(--background);\n            border: none;\n            color: var(--text-color);\n            position: absolute;\n            right: 0.5rem;\n            top: 0.5rem;\n            z-index: 2;\n            opacity: 0.5;\n          }\n          code {\n            background-color: var(--background);\n            padding: 0.5rem 2rem 0.5rem 0.5rem;\n            display: block;\n            font-family: PTMono, source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;\n            z-index: 2;\n            border-radius: 3px;\n            width: fit-content;\n          }\n        }\n\n        .buttons {\n          display: flex;\n          gap: 1rem;\n          flex-wrap: wrap;\n          margin-bottom: 0.5rem;\n          a {\n            padding: 0.5rem 1rem;\n            background-color: var(--primary);\n            color: var(--background);\n            border-radius: 4px;\n            text-decoration: none;\n            transition: all 0.3s ease;\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            &:hover {\n              background-color: var(--text-color);\n              color: var(--background);\n            }\n            img {\n              width: 1rem;\n              filter: grayscale(100%);\n            }\n          }\n        }\n      }\n      \n    }\n  }\n  .shaddow {\n    --c0: #f4ffc7;\n    --c1: #d6fb41;\n    --c2: #c2f300;\n    --c3: #a8d200;\n    --c4: #5e7500;\n\n    --gradient-shadow: linear-gradient(\n      45deg,\n      var(--c0),\n      var(--c1),\n      var(--c2),\n      var(--c3),\n      var(--c4),\n      var(--c0),\n      var(--c1),\n      var(--c2),\n      var(--c3),\n      var(--c4),\n      var(--c3),\n      var(--c2),\n      var(--c1)\n    );\n    position: relative;\n    z-index: 1;\n    transition: all 0.3s ease;\n    width: fit-content;\n    &:before,\n    &:after {\n      content: \"\";\n      position: absolute;\n      top: -1px;\n      left: -1px;\n      background: var(--gradient-shadow);\n      background-size: 400%;\n      width: calc(100% + 2px);\n      height: calc(100% + 2px);\n      z-index: -1;\n      animation: animate 10s linear infinite;\n      transition: all 0.3s ease;\n    }\n\n    &:before {\n      opacity: 0.1;\n    }\n\n    &:after {\n      filter: blur(3px);\n      opacity: 0.3;\n    }\n\n    &:hover {\n      &:before,\n      &:after {\n        animation: animate 40s linear infinite;\n      }\n      &:after {\n        opacity: 0.7;\n      }\n    }\n\n    @keyframes animate {\n      0% {\n        background-position: 0 0;\n      }\n      50% {\n        background-position: 120% 0;\n      }\n      100% {\n        background-position: 0 0;\n      }\n    }\n  }\n\n\n</style>\n"
  },
  {
    "path": "src/pages/web-check-api/index.astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from '@components/scafold/Footer.astro';\nimport Icon from '@components/molecules/Icon.svelte';\n\n---\n\n<BaseLayout\n  title=\"API | Web Check\"\n  description=\"Get started with the Web Check API.\"\n>\n  <div class=\"web-check-page\">\n    <NavBar />\n    <main>\n      <h1>Web Check API</h1>\n      <p class=\"intro\">\n        The Web Check API gives you programatic access to over 30 categories of web metrics.<br />\n        Integrate into your own workflows and applications, to monitor any website's performance, SEO, security, and more.\n      </p>\n    \n      <div class=\"option-cards\">\n\n        <div class=\"card\">\n          <h3>API Documentation</h3>\n          <p>\n            View listing of all available endpoints, their usage, output and examples.\n          </p>\n          <div class=\"buttons\">\n            <a href=\"/web-check-api/spec\">\n              <img src=\"/assets/images/swagger.svg\" />\n              OpenAPI Spec\n            </a>\n          </div>\n        </div>\n        <div class=\"card\">\n          <h3>API Key</h3>\n          <p>\n            Get your API key, and start using the API.\n          </p>\n          <div class=\"buttons\">\n            <a href=\"/account\">\n              <img src=\"/assets/images/webauthn.svg\" />\n              Sign Up\n            </a>\n          </div>\n        </div>\n        <div class=\"card\">\n          <h3>API Source</h3>\n          <p>\n            View, edit, download or deploy the API from source.\n          </p>\n          <div class=\"buttons\">\n            <a href=\"https://github.com/xray-web/web-check-api\">\n              <img src=\"/assets/images/github.svg\" />\n              GitHub\n            </a>\n          </div>\n        </div>\n\n\n        <!-- { cardData.map((card, cardIndex) => (\n          <div class=\"card\">\n            <span class=\"option\">Option #{cardIndex + 1}</span>\n            <h3>{card.title}</h3>\n            <p>{card.description}</p>\n            { card.buttons && (\n              <div class=\"buttons\">\n                { card.buttons.map(button => (\n                  <a href={button.link}>\n                    <img src={button.image} />\n                    {button.name}\n                  </a>\n                ))}\n              </div>\n            )}\n            { card.command && (\n              <div class=\"shaddow docker-command\">\n                <code>{card.command}</code>\n                <button><Icon name=\"copy\" size={0.8} /></button>\n              </div>\n            )}\n          </div>\n        ))} -->\n    </div>\n    </main>\n    <Footer />\n  </div>\n</BaseLayout>\n\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  .web-check-page {\n    padding-top: 2rem;\n    margin: 0 auto;\n    min-height: 100vh;\n    width: 100vw;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n    background-image: url('/assets/images/background-dots.svg');\n    @include mobile-down {\n      padding: 0.5rem;\n    }\n    main {\n      width: 80vw;\n      margin: 0 auto;\n      min-height: 100%;\n      flex-grow: 1;\n      padding: 5rem 0 2rem 0;\n      max-width: 1200px;\n      @include mobile-down {\n        padding: 1rem 0;\n      }\n      .intro {\n        font-size: 1.2rem;\n        line-height: 1.5;\n        margin-top: 1rem;\n      }\n    }\n    .option-cards {\n      display: grid;\n      gap: 1rem;\n      grid-template-columns: repeat(2, 1fr);\n      margin-top: 2.5rem;\n      .card:first-child {\n        grid-column: span 2;\n      }\n      @include tablet-landscape-down {\n        grid-template-columns: 1fr;\n        .card:first-child {\n          grid-column: span 1;\n        }\n      }\n      .card {\n        padding: 0.5rem 1rem;\n        background-color: var(--background);\n        color: var(--text-color);\n        border-radius: 4px;\n        border: 1px solid var(--primary-transparent);\n        .option {\n          color: var(--primary);\n          font-size: 0.8rem;\n        }\n        h3 {\n          margin-top: 1rem;\n        }\n        p {\n          line-height: 1.3;\n        }\n        .docker-command {\n          display: flex;\n          margin: 1rem 0;\n          position: relative;\n          button {\n            background: var(--background);\n            border: none;\n            color: var(--text-color);\n            position: absolute;\n            right: 0.5rem;\n            top: 0.5rem;\n            z-index: 2;\n            opacity: 0.5;\n          }\n          code {\n            background-color: var(--background);\n            padding: 0.5rem 2rem 0.5rem 0.5rem;\n            display: block;\n            font-family: PTMono, source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;\n            z-index: 2;\n            border-radius: 3px;\n            width: fit-content;\n          }\n        }\n\n        .buttons {\n          display: flex;\n          gap: 1rem;\n          flex-wrap: wrap;\n          margin-bottom: 0.5rem;\n          a {\n            padding: 0.5rem 1rem;\n            background-color: var(--primary);\n            color: var(--background);\n            border-radius: 4px;\n            text-decoration: none;\n            transition: all 0.3s ease;\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            &:hover {\n              background-color: var(--text-color);\n              color: var(--background);\n            }\n            img {\n              width: 1rem;\n              filter: grayscale(100%);\n            }\n          }\n        }\n      }\n      \n    }\n  }\n  .shaddow {\n    --c0: #f4ffc7;\n    --c1: #d6fb41;\n    --c2: #c2f300;\n    --c3: #a8d200;\n    --c4: #5e7500;\n\n    --gradient-shadow: linear-gradient(\n      45deg,\n      var(--c0),\n      var(--c1),\n      var(--c2),\n      var(--c3),\n      var(--c4),\n      var(--c0),\n      var(--c1),\n      var(--c2),\n      var(--c3),\n      var(--c4),\n      var(--c3),\n      var(--c2),\n      var(--c1)\n    );\n    position: relative;\n    z-index: 1;\n    transition: all 0.3s ease;\n    width: fit-content;\n    &:before,\n    &:after {\n      content: \"\";\n      position: absolute;\n      top: -1px;\n      left: -1px;\n      background: var(--gradient-shadow);\n      background-size: 400%;\n      width: calc(100% + 2px);\n      height: calc(100% + 2px);\n      z-index: -1;\n      animation: animate 10s linear infinite;\n      transition: all 0.3s ease;\n    }\n\n    &:before {\n      opacity: 0.1;\n    }\n\n    &:after {\n      filter: blur(3px);\n      opacity: 0.3;\n    }\n\n    &:hover {\n      &:before,\n      &:after {\n        animation: animate 40s linear infinite;\n      }\n      &:after {\n        opacity: 0.7;\n      }\n    }\n\n    @keyframes animate {\n      0% {\n        background-position: 0 0;\n      }\n      50% {\n        background-position: 120% 0;\n      }\n      100% {\n        background-position: 0 0;\n      }\n    }\n  }\n\n\n</style>\n"
  },
  {
    "path": "src/pages/web-check-api/spec.astro",
    "content": "---\nimport BaseLayout from '@layouts/Base.astro';\nimport NavBar from '@components/scafold/Nav.astro';\nimport Footer from '@components/scafold/Footer.astro';\n---\n\n<BaseLayout\n  title=\"API Docs | Web Check\"\n  description=\"API documentation for the Web Check backend REST endpoints\"\n>\n  <Fragment slot=\"head\">\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.1.3/swagger-ui.css\">\n    <link rel=\"stylesheet\" href=\"https://rawcdn.githack.com/Amoenus/SwaggerDark/2064ccd45b571865a64c731fa6bfddfbf2a01fe1/SwaggerDark.css\">\n  </Fragment>\n  <main>\n    <NavBar />\n    <div id=\"swagger-ui\"></div>\n    <Footer />\n  </main>\n</BaseLayout>\n\n<script is:inline src=\"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.1.3/swagger-ui-bundle.js\"></script>\n<script is:inline>\n  function initializeSwagger() {\n    SwaggerUIBundle({\n      url: '/resources/openapi-spec.yml',\n      dom_id: '#swagger-ui',\n      deepLinking: true,\n      presets: [\n        SwaggerUIBundle.presets.apis,\n        SwaggerUIBundle.SwaggerUIStandalonePreset\n      ],\n    });\n  }\n  // Initialize Swagger when visiting page directly\n  document.addEventListener('DOMContentLoaded', initializeSwagger);\n  // Initialize Swagger when navigating to page from another page\n  if (document.readyState === 'complete' || document.readyState === 'interactive') {\n    initializeSwagger();\n  }\n</script>\n\n<style lang=\"scss\">\n  @import '@styles/global.scss';\n  main {\n    height: 100vh;\n    padding-top: 2rem;\n    #swagger-ui {\n      margin: 0 auto;\n      width: calc(100vw - 4rem);\n      max-width: 1600px;\n      min-height: 100vh;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/styles/colors.scss",
    "content": ":root {\n  --primary: #d6fb41;\n  --primary-lighter: #cff97a;\n  --text-color: #ffffff;\n  --text-color-secondary: #ffffffb6;\n  --text-color-thirdly: #ffffff5b;\n  --background: #111211;\n  --background-darker: #111927;\n  // --background-lighter: #1a2332;\n  --background-lighter: #3A3B3A;\n  --background-50: #11121180;\n  --bg-shadow-color: #0f1620;\n  --fg-shadow-color: #456602;\n  --primary-transparent: #d6fb4130;\n\n  // Action Colors\n  --info: #04e4f4;\n  --success: #20e253;\n  --warning: #f6f000;\n  --error: #fca016;\n  --danger: #f80363;\n  --neutral: #272f4d;\n}\n\nhtml[data-theme=\"light\"] {\n  --primary: #4a7700;\n  --primary-lighter: #a4cf50;\n  --text-color: #333333;\n  --text-color-secondary: #57667e;\n  --background: #ffffff;\n  --background-darker: #f0f0f0;\n  --background-lighter: #fafafa;\n  --bg-shadow-color: #e0e0e0;\n  --fg-shadow-color: #678800;\n  --primary-transparent: #4a770033;\n}\n"
  },
  {
    "path": "src/styles/global.scss",
    "content": "/* Global Stylesheet */\n@import './colors.scss';\n@import './media-queries.scss';\n@import './typography.scss';\n\n/* CSS Reset - Normalize dimensions and spacing across browsers */\n*,\n*::before,\n*::after {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n  font-family: inherit;\n}\n\n::selection {\n  background: var(--primary);\n  color: var(--background);\n}\n\n/* HTML and body basic setup */\nhtml {\n  font-size: 16px;\n  scroll-behavior: smooth;\n}\n\nbody {\n  font-family: 'Hubot Sans', 'Inter', 'Helvetica Neue', Arial, sans-serif;\n  line-height: 1;\n  color: var(--text-color);\n  background: var(--background);\n  overflow-x: hidden;\n  font-weight: 400;\n  margin: 0;\n  min-height: 100vh;\n  position: relative;\n}\n\n/* Links */\na {\n  color: inherit;\n  text-decoration: none;\n  &:hover,\n  &:focus {\n    text-decoration: underline;\n  }\n}\n\n/* Images */\nimg {\n  max-width: 100%;\n  height: auto;\n}\n\n/* Typography */\nh1, h2, h3, h4, h5, h6 {\n  color: inherit;\n  margin-top: 0;\n  margin-bottom: 0.5em;\n  font-weight: normal;\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1em;\n}\n\nul, ol {\n  margin-top: 0;\n  margin-bottom: 1em;\n  padding-left: 20px;\n}\n\n/* Forms */\ninput, button, textarea, select {\n  font: inherit;\n  &:focus {\n    outline: 1px solid var(--primary);\n  }\n}\n\n/* Utility Classes */\n.text-center { text-align: center; }\n.hidden { display: none; }\n.bold { font-weight: 600; }\n.italic { font-style: italic; }\n.underline { text-decoration: underline; }\n.uppercase { text-transform: uppercase; }\n\n@media (max-width: 768px) {\n  html {\n    font-size: 14px;\n  }\n}\n\nh1, h2, h3, h4, h5, h6 {\n  margin-top: 0;\n  margin-bottom: 0.5em;\n  font-weight: 600;\n  color: var(--text-color);\n}\n\nh1 { font-size: 2.25rem; }\nh2 { font-size: 1.8rem; }\nh3 { font-size: 1.5rem; }\nh4 { font-size: 1.2rem; }\nh5 { font-size: 1rem; }\nh6 { font-size: 0.85rem; }\n\n// Responsive font sizes\n@media (max-width: 768px) {\n  h1 { font-size: 2rem; }\n  h2 { font-size: 1.6rem; }\n  h3 { font-size: 1.4rem; }\n}\n\np {\n  margin-top: 0;\n  margin-bottom: 1em;\n}\n\na {\n  color: var(--primary);\n  text-decoration: none;\n  &:hover {\n    text-decoration: underline;\n  }\n}\n\n\n"
  },
  {
    "path": "src/styles/media-queries.scss",
    "content": "// Breakpoints\n$breakpoint-xs: 599px;  // Max width for mobile only\n$breakpoint-sm: 600px;  // Min width for tablet portrait\n$breakpoint-md: 768px;  // Min width for tablet landscape\n$breakpoint-lg: 1024px; // Min width for desktop\n$breakpoint-xl: 1440px; // Min width for large desktop\n\n// Mixins for specific breakpoints\n@mixin for-mobile-only {\n  @media (max-width: $breakpoint-xs) {\n    @content;\n  }\n}\n\n@mixin mobile-up {\n  @media (min-width: calc($breakpoint-xs + 1px)) {\n    @content;\n  }\n}\n\n@mixin for-tablet-portrait {\n  @media (min-width: $breakpoint-sm) and (max-width: calc($breakpoint-md - 1px)) {\n    @content;\n  }\n}\n\n@mixin tablet-portrait-down {\n  @media (max-width: calc($breakpoint-md - 1px)) {\n    @content;\n  }\n}\n\n@mixin tablet-up {\n  @media (min-width: $breakpoint-sm) {\n    @content;\n  }\n}\n\n@mixin for-tablet-landscape {\n  @media (min-width: $breakpoint-md) and (max-width: calc($breakpoint-lg - 1px)) {\n    @content;\n  }\n}\n\n@mixin tablet-landscape-down {\n  @media (max-width: calc($breakpoint-lg - 1px)) {\n    @content;\n  }\n}\n\n@mixin for-desktop {\n  @media (min-width: $breakpoint-lg) and (max-width: calc($breakpoint-xl - 1px)) {\n    @content;\n  }\n}\n\n@mixin desktop-down {\n  @media (max-width: calc($breakpoint-xl - 1px)) {\n    @content;\n  }\n}\n\n@mixin for-large-desktop {\n  @media (min-width: $breakpoint-xl) {\n    @content;\n  }\n}\n\n@mixin large-desktop-up {\n  @media (min-width: $breakpoint-xl) {\n    @content;\n  }\n}\n\n@mixin mobile-down {\n  @media (max-width: $breakpoint-xs) {\n    @content;\n  }\n}\n"
  },
  {
    "path": "src/styles/typography.scss",
    "content": "@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Thin.ttf') format('truetype');\n  font-weight: 100;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-ExtraLight.ttf') format('truetype');\n  font-weight: 200;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Light.ttf') format('truetype');\n  font-weight: 300;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Regular.ttf') format('truetype');\n  font-weight: 400;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Medium.ttf') format('truetype');\n  font-weight: 500;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-SemiBold.ttf') format('truetype');\n  font-weight: 600;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Bold.ttf') format('truetype');\n  font-weight: 700;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-ExtraBold.ttf') format('truetype');\n  font-weight: 800;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Inter';\n  src: url('/fonts/Inter-Black.ttf') format('truetype');\n  font-weight: 900;\n  font-style: normal;\n  font-display: swap;\n}\n\n// Regular Weights and Styles\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-Regular.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-Regular.ttf') format('truetype');\n  font-weight: 400;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-Italic.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-Italic.ttf') format('truetype');\n  font-weight: 400;\n  font-style: italic;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-Bold.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-Bold.ttf') format('truetype');\n  font-weight: 700;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-BoldItalic.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-BoldItalic.ttf') format('truetype');\n  font-weight: 700;\n  font-style: italic;\n  font-display: swap;\n}\n\n// Light, Medium, and ExtraBold variants\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-Light.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-Light.ttf') format('truetype');\n  font-weight: 300;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-LightItalic.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-LightItalic.ttf') format('truetype');\n  font-weight: 300;\n  font-style: italic;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-Medium.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-Medium.ttf') format('truetype');\n  font-weight: 500;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-MediumItalic.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-MediumItalic.ttf') format('truetype');\n  font-weight: 500;\n  font-style: italic;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBold.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-ExtraBold.ttf') format('truetype');\n  font-weight: 800;\n  font-style: normal;\n  font-display: swap;\n}\n\n@font-face {\n  font-family: 'Hubot Sans';\n  src: url('/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBoldItalic.woff2') format('woff2'),\n       url('/fonts/Hubot-Sans/TTF/HubotSans-ExtraBoldItalic.ttf') format('truetype');\n  font-weight: 800;\n  font-style: italic;\n  font-display: swap;\n}\n\n"
  },
  {
    "path": "src/web-check-live/App.tsx",
    "content": "import { Routes, Route, Outlet } from 'react-router-dom';\n\nimport Home from 'web-check-live/views/Home.tsx';\nimport Results from 'web-check-live/views/Results.tsx';\nimport About from 'web-check-live/views/About.tsx';\nimport NotFound from 'web-check-live/views/NotFound.tsx';\n\nimport ErrorBoundary from 'web-check-live/components/boundaries/PageError.tsx';\nimport GlobalStyles from './styles/globals.tsx';\n\nconst Layout = () => {\n  return (\n  <>\n    <GlobalStyles />\n    <Outlet />\n  </>\n  );\n}\n\nexport default function App() {\n  return (\n    <ErrorBoundary>\n      <Routes>\n        <Route path=\"/check\" element={<Layout />}>\n          <Route index element={<Home />} />\n          <Route path=\"home\" element={<Home />} />\n          <Route path=\"about\" element={<About />} />\n          <Route path=\":urlToScan\" element={<Results />} />\n          <Route path=\"*\" element={<NotFound />} />\n        </Route>\n      </Routes>\n    </ErrorBoundary>\n  );\n}\n"
  },
  {
    "path": "src/web-check-live/assets/data/map-features.json",
    "content": "{\n  \"type\": \"Topology\",\n  \"objects\": {\n    \"world\": {\n      \"type\": \"GeometryCollection\",\n      \"geometries\": [\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[0, 1, 2, 3, 4, 5]],\n          \"id\": \"AFG\",\n          \"properties\": { \"name\": \"Afghanistan\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[6, 7, 8, 9]], [[10, 11, 12]]],\n          \"id\": \"AGO\",\n          \"properties\": { \"name\": \"Angola\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[13, 14, 15, 16, 17]],\n          \"id\": \"ALB\",\n          \"properties\": { \"name\": \"Albania\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[18, 19, 20, 21, 22]],\n          \"id\": \"ARE\",\n          \"properties\": { \"name\": \"United Arab Emirates\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[23, 24]], [[25, 26, 27, 28, 29, 30]]],\n          \"id\": \"ARG\",\n          \"properties\": { \"name\": \"Argentina\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[31, 32, 33, 34, 35]],\n          \"id\": \"ARM\",\n          \"properties\": { \"name\": \"Armenia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[36]],\n          \"id\": \"ATF\",\n          \"properties\": { \"name\": \"French Southern Territories\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[37]], [[38]]],\n          \"id\": \"AUS\",\n          \"properties\": { \"name\": \"Australia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]],\n          \"id\": \"AUT\",\n          \"properties\": { \"name\": \"Austria\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[50, -35]], [[51, 52, -33, 53, 54]]],\n          \"id\": \"AZE\",\n          \"properties\": { \"name\": \"Azerbaijan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[55, 56, 57, 58]],\n          \"id\": \"BDI\",\n          \"properties\": { \"name\": \"Burundi\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[59, 60, 61, 62, 63]],\n          \"id\": \"BEL\",\n          \"properties\": { \"name\": \"Belgium\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[64, 65, 66, 67, 68]],\n          \"id\": \"BEN\",\n          \"properties\": { \"name\": \"Benin\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[69, 70, 71, -67, 72, 73]],\n          \"id\": \"BFA\",\n          \"properties\": { \"name\": \"Burkina Faso\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[74, 75, 76]],\n          \"id\": \"BGD\",\n          \"properties\": { \"name\": \"Bangladesh\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[77, 78, 79, 80, 81, 82]],\n          \"id\": \"BGR\",\n          \"properties\": { \"name\": \"Bulgaria\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[83]], [[84]], [[85]]],\n          \"id\": \"BHS\",\n          \"properties\": { \"name\": \"Bahamas\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[86, 87, 88, 89]],\n          \"id\": \"BIH\",\n          \"properties\": { \"name\": \"Bosnia and Herzegovina\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[90, 91, 92, 93, 94, 95, 96, 97]],\n          \"id\": \"BLR\",\n          \"properties\": { \"name\": \"Belarus\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[98, 99, 100]],\n          \"id\": \"BLZ\",\n          \"properties\": { \"name\": \"Belize\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[101, 102, 103, 104, -31]],\n          \"id\": \"BOL\",\n          \"properties\": { \"name\": \"Bolivia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-27, 105, -104, 106, 107, 108, 109, 110, 111, 112, 113]],\n          \"id\": \"BRA\",\n          \"properties\": { \"name\": \"Brazil\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[114, 115]],\n          \"id\": \"BRN\",\n          \"properties\": { \"name\": \"Brunei\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[116, 117]],\n          \"id\": \"BTN\",\n          \"properties\": { \"name\": \"Bhutan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[118, 119, 120, 121]],\n          \"id\": \"BWA\",\n          \"properties\": { \"name\": \"Botswana\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[122, 123, 124, 125, 126, 127, 128]],\n          \"id\": \"CAF\",\n          \"properties\": { \"name\": \"Central African Republic\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[129]],\n            [[130]],\n            [[131]],\n            [[132]],\n            [[133]],\n            [[134]],\n            [[135]],\n            [[136]],\n            [[137]],\n            [[138]],\n            [[139, 140, 141, 142, 143, 144]],\n            [[145]],\n            [[146]],\n            [[147]],\n            [[148]],\n            [[149]],\n            [[150]],\n            [[151]],\n            [[152]],\n            [[153]],\n            [[154]],\n            [[155]],\n            [[156]],\n            [[157]],\n            [[158]],\n            [[159]],\n            [[160]],\n            [[161]],\n            [[162]],\n            [[163]]\n          ],\n          \"id\": \"CAN\",\n          \"properties\": { \"name\": \"Canada\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-47, 164, 165, 166, -43, 167, 168, 169]],\n          \"id\": \"CHE\",\n          \"properties\": { \"name\": \"Switzerland\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[-24, 170]], [[-30, 171, 172, -102]]],\n          \"id\": \"CHL\",\n          \"properties\": { \"name\": \"Chile\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[173]],\n            [\n              [\n                174,\n                175,\n                176,\n                177,\n                178,\n                179,\n                180,\n                181,\n                182,\n                183,\n                184,\n                185,\n                186,\n                187,\n                -118,\n                188,\n                189,\n                190,\n                191,\n                -4,\n                192,\n                193,\n                194,\n                195,\n                196,\n                197,\n                198,\n                199,\n                200,\n                201,\n                202\n              ]\n            ]\n          ],\n          \"id\": \"CHN\",\n          \"properties\": { \"name\": \"China\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[203, 204, 205, 206, -70, 207]],\n          \"id\": \"CIV\",\n          \"properties\": { \"name\": \"Cote d'Ivoire\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[208, 209, 210, 211, 212, 213, 214, -129, 215]],\n          \"id\": \"CMR\",\n          \"properties\": { \"name\": \"Cameroon\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [\n              216,\n              217,\n              218,\n              219,\n              -56,\n              220,\n              221,\n              222,\n              -10,\n              223,\n              -13,\n              224,\n              -127,\n              225,\n              226\n            ]\n          ],\n          \"id\": \"COD\",\n          \"properties\": { \"name\": \"Democratic Republic of Congo\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-12, 227, 228, -216, -128, -225]],\n          \"id\": \"COG\",\n          \"properties\": { \"name\": \"Congo\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[229, 230, 231, 232, 233, -108, 234]],\n          \"id\": \"COL\",\n          \"properties\": { \"name\": \"Colombia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[235, 236, 237, 238]],\n          \"id\": \"CRI\",\n          \"properties\": { \"name\": \"Costa Rica\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[239]],\n          \"id\": \"CUB\",\n          \"properties\": { \"name\": \"Cuba\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[240]],\n          \"id\": \"CYP\",\n          \"properties\": { \"name\": \"Cyprus\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-49, 241, 242, 243]],\n          \"id\": \"CZE\",\n          \"properties\": { \"name\": \"Czechia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[244, 245, -242, -48, -170, 246, 247, -61, 248, 249, 250]],\n          \"id\": \"DEU\",\n          \"properties\": { \"name\": \"Germany\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[251, 252, 253, 254]],\n          \"id\": \"DJI\",\n          \"properties\": { \"name\": \"Djibouti\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[255]], [[-251, 256]]],\n          \"id\": \"DNK\",\n          \"properties\": { \"name\": \"Denmark\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[257]]],\n          \"id\": \"GRL\",\n          \"properties\": { \"name\": \"Greenland\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[258, 259]],\n          \"id\": \"DOM\",\n          \"properties\": { \"name\": \"Dominican Republic\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[260, 261, 262, 263, 264, 265, 266, 267]],\n          \"id\": \"DZA\",\n          \"properties\": { \"name\": \"Algeria\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[268, -230, 269]],\n          \"id\": \"ECU\",\n          \"properties\": { \"name\": \"Ecuador\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[270, 271, 272]],\n          \"id\": \"EGY\",\n          \"properties\": { \"name\": \"Egypt\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[273, 274, 275, 276, 277, 278, 279, -255]],\n          \"id\": \"ERI\",\n          \"properties\": { \"name\": \"Eritrea\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[280, 281, 282, 283]],\n          \"id\": \"ESP\",\n          \"properties\": { \"name\": \"Spain\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[284, 285, 286, 287]],\n          \"id\": \"EST\",\n          \"properties\": { \"name\": \"Estonia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [288, 289, -274, -254, 290, 291, 292, 293, 294, 295, 296, 297, -277]\n          ],\n          \"id\": \"ETH\",\n          \"properties\": { \"name\": \"Ethiopia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[298, 299, 300, 301]],\n          \"id\": \"FIN\",\n          \"properties\": { \"name\": \"Finland\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[302]], [[303]], [[304]]],\n          \"id\": \"FJI\",\n          \"properties\": { \"name\": \"Fiji\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[305]], [[306, -247, -169, 307, 308, -282, 309, -63]]],\n          \"id\": \"FRA\",\n          \"properties\": { \"name\": \"France\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[310, 311, 312, -112]],\n          \"id\": \"GUF\",\n          \"properties\": { \"name\": \"French Guiana\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[313, 314, -209, -229]],\n          \"id\": \"GAB\",\n          \"properties\": { \"name\": \"Gabon\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[315, 316]], [[317]]],\n          \"id\": \"GBR\",\n          \"properties\": { \"name\": \"United Kingdom\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[318, 319, 320, 321, 322, -54, -32, 323]],\n          \"id\": \"GEO\",\n          \"properties\": { \"name\": \"Georgia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[324, -208, -74, 325]],\n          \"id\": \"GHA\",\n          \"properties\": { \"name\": \"Ghana\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[326, 327, 328, 329, 330, 331, -206]],\n          \"id\": \"GIN\",\n          \"properties\": { \"name\": \"Guinea\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[332, 333]],\n          \"id\": \"GMB\",\n          \"properties\": { \"name\": \"Gambia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[334, 335, -330]],\n          \"id\": \"GNB\",\n          \"properties\": { \"name\": \"Guinea-Bissau\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[336, -210, -315]],\n          \"id\": \"GNQ\",\n          \"properties\": { \"name\": \"Equatorial Guinea\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[337]], [[338, -14, 339, -81, 340]]],\n          \"id\": \"GRC\",\n          \"properties\": { \"name\": \"Greece\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[341, 342, -101, 343, 344, 345]],\n          \"id\": \"GTM\",\n          \"properties\": { \"name\": \"Guatemala\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[346, 347, -110, 348]],\n          \"id\": \"GUY\",\n          \"properties\": { \"name\": \"Guyana\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[349, 350, -345, 351, 352]],\n          \"id\": \"HND\",\n          \"properties\": { \"name\": \"Honduras\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[353, 354, 355, -90, 356, 357, 358]],\n          \"id\": \"HRV\",\n          \"properties\": { \"name\": \"Croatia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-260, 359]],\n          \"id\": \"HTI\",\n          \"properties\": { \"name\": \"Haiti\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-40, 360, 361, 362, 363, 364, -359, 365]],\n          \"id\": \"HUN\",\n          \"properties\": { \"name\": \"Hungary\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[366]],\n            [[367, 368]],\n            [[369]],\n            [[370]],\n            [[371]],\n            [[372]],\n            [[373]],\n            [[374]],\n            [[375, 376]],\n            [[377]],\n            [[378]],\n            [[379, 380]],\n            [[381]]\n          ],\n          \"id\": \"IDN\",\n          \"properties\": { \"name\": \"Indonesia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-191, 382, -189, -117, -188, 383, -77, 384, 385]],\n          \"id\": \"IND\",\n          \"properties\": { \"name\": \"India\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[386, -316]],\n          \"id\": \"IRL\",\n          \"properties\": { \"name\": \"Ireland\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[387, -6, 388, 389, 390, 391, 392, -51, -34, -53, 393]],\n          \"id\": \"IRN\",\n          \"properties\": { \"name\": \"Iran\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-391, 394, 395, 396, 397, 398, 399, 400]],\n          \"id\": \"IRQ\",\n          \"properties\": { \"name\": \"Iraq\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[401]],\n          \"id\": \"ISL\",\n          \"properties\": { \"name\": \"Iceland\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[402, 403, 404, 405, 406, 407]],\n          \"id\": \"ISR\",\n          \"properties\": { \"name\": \"Israel\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[408]], [[409]], [[410, 411, -308, -168, -42]]],\n          \"id\": \"ITA\",\n          \"properties\": { \"name\": \"Italy\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[412]],\n          \"id\": \"JAM\",\n          \"properties\": { \"name\": \"Jamaica\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-403, 413, -398, 414, 415, -405, 416]],\n          \"id\": \"JOR\",\n          \"properties\": { \"name\": \"Jordan\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[417]], [[418]], [[419]]],\n          \"id\": \"JPN\",\n          \"properties\": { \"name\": \"Japan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [\n              420,\n              421,\n              422,\n              423,\n              424,\n              425,\n              426,\n              427,\n              428,\n              429,\n              430,\n              431,\n              432,\n              -195,\n              433\n            ]\n          ],\n          \"id\": \"KAZ\",\n          \"properties\": { \"name\": \"Kazakhstan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[434, 435, 436, 437, -295, 438]],\n          \"id\": \"KEN\",\n          \"properties\": { \"name\": \"Kenya\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-434, -194, 439, 440]],\n          \"id\": \"KGZ\",\n          \"properties\": { \"name\": \"Kyrgyzstan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[441, 442, 443, 444]],\n          \"id\": \"KHM\",\n          \"properties\": { \"name\": \"Cambodia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[445, 446]],\n          \"id\": \"KOR\",\n          \"properties\": { \"name\": \"South Korea\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[447, 448, 449, 450, 451]],\n          \"id\": \"XXK\",\n          \"properties\": { \"name\": \"Kosovo\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[452, 453, -396]],\n          \"id\": \"KWT\",\n          \"properties\": { \"name\": \"Kuwait\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[454, 455, -186, 456, -443]],\n          \"id\": \"LAO\",\n          \"properties\": { \"name\": \"Laos\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-407, 457, 458]],\n          \"id\": \"LBN\",\n          \"properties\": { \"name\": \"Lebanon\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[459, 460, -327, -205]],\n          \"id\": \"LBR\",\n          \"properties\": { \"name\": \"Liberia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[461, -268, 462, 463, -272, 464, 465]],\n          \"id\": \"LBY\",\n          \"properties\": { \"name\": \"Libya\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[466]],\n          \"id\": \"LKA\",\n          \"properties\": { \"name\": \"Sri Lanka\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[467]],\n          \"id\": \"LSO\",\n          \"properties\": { \"name\": \"Lesotho\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[468, 469, 470, -91, 471]],\n          \"id\": \"LTU\",\n          \"properties\": { \"name\": \"Lithuania\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-248, -307, -62]],\n          \"id\": \"LUX\",\n          \"properties\": { \"name\": \"Luxembourg\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[472, -288, 473, -92, -471]],\n          \"id\": \"LVA\",\n          \"properties\": { \"name\": \"Latvia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-265, 474, 475]],\n          \"id\": \"MAR\",\n          \"properties\": { \"name\": \"Morocco\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[476, 477]],\n          \"id\": \"MDA\",\n          \"properties\": { \"name\": \"Moldova\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[478]],\n          \"id\": \"MDG\",\n          \"properties\": { \"name\": \"Madagascar\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-99, -343, 479, 480, 481]],\n          \"id\": \"MEX\",\n          \"properties\": { \"name\": \"Mexico\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-452, 482, -82, -340, 483]],\n          \"id\": \"MKD\",\n          \"properties\": { \"name\": \"North Macedonia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[484, -262, 485, -71, -207, -332, 486]],\n          \"id\": \"MLI\",\n          \"properties\": { \"name\": \"Mali\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[487, -75, -384, -187, -456, 488]],\n          \"id\": \"MMR\",\n          \"properties\": { \"name\": \"Myanmar\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[489, -89, 490, -450, -16]],\n          \"id\": \"MNE\",\n          \"properties\": { \"name\": \"Montenegro\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[491, 492, 493, 494, 495, 496, 497, 498, -197]],\n          \"id\": \"MNG\",\n          \"properties\": { \"name\": \"Mongolia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[499, 500, 501, 502, 503, 504, 505, 506, 507]],\n          \"id\": \"MOZ\",\n          \"properties\": { \"name\": \"Mozambique\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[508, 509, 510, -263, -485]],\n          \"id\": \"MRT\",\n          \"properties\": { \"name\": \"Mauritania\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-508, 511, 512, 513]],\n          \"id\": \"MWI\",\n          \"properties\": { \"name\": \"Malawi\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[514, 515, 516, 517]], [[-380, 518, -116, 519]]],\n          \"id\": \"MYS\",\n          \"properties\": { \"name\": \"Malaysia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[520, -8, 521, -120, 522]],\n          \"id\": \"NAM\",\n          \"properties\": { \"name\": \"Namibia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[523]],\n          \"id\": \"NCL\",\n          \"properties\": { \"name\": \"New Caledonia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [-72, -486, -261, -462, 524, 525, 526, 527, 528, -213, 529, -68]\n          ],\n          \"id\": \"NER\",\n          \"properties\": { \"name\": \"Niger\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[530, -69, -530, -212]],\n          \"id\": \"NGA\",\n          \"properties\": { \"name\": \"Nigeria\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[531, -353, 532, -237]],\n          \"id\": \"NIC\",\n          \"properties\": { \"name\": \"Nicaragua\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-249, -60, 533]],\n          \"id\": \"NLD\",\n          \"properties\": { \"name\": \"Netherlands\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[-302, 534, 535]], [[536]], [[537]], [[538]]],\n          \"id\": \"NOR\",\n          \"properties\": { \"name\": \"Norway\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-383, -190]],\n          \"id\": \"NPL\",\n          \"properties\": { \"name\": \"Nepal\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[539]], [[540]]],\n          \"id\": \"NZL\",\n          \"properties\": { \"name\": \"New Zealand\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[541, 542, -22, 543]], [[-20, 544]]],\n          \"id\": \"OMN\",\n          \"properties\": { \"name\": \"Oman\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-192, -386, 545, -389, -5]],\n          \"id\": \"PAK\",\n          \"properties\": { \"name\": \"Pakistan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[546, -239, 547, -232]],\n          \"id\": \"PAN\",\n          \"properties\": { \"name\": \"Panama\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-173, 548, -270, -235, -107, -103]],\n          \"id\": \"PER\",\n          \"properties\": { \"name\": \"Peru\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[549]],\n            [[550]],\n            [[551]],\n            [[552]],\n            [[553]],\n            [[554]],\n            [[555]]\n          ],\n          \"id\": \"PHL\",\n          \"properties\": { \"name\": \"Philippines\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[556]], [[557]], [[-376, 558]], [[559]]],\n          \"id\": \"PNG\",\n          \"properties\": { \"name\": \"Papua New Guinea\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-246, 560, 561, -472, -98, 562, 563, -243]],\n          \"id\": \"POL\",\n          \"properties\": { \"name\": \"Poland\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[564]],\n          \"id\": \"PRI\",\n          \"properties\": { \"name\": \"Puerto Rico\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[565, -447, 566, -183]],\n          \"id\": \"PRK\",\n          \"properties\": { \"name\": \"North Korea\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-284, 567]],\n          \"id\": \"PRT\",\n          \"properties\": { \"name\": \"Portugal\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-105, -106, -26]],\n          \"id\": \"PRY\",\n          \"properties\": { \"name\": \"Paraguay\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[568, 569]],\n          \"id\": \"QAT\",\n          \"properties\": { \"name\": \"Qatar\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[570, -478, 571, 572, -78, 573, -363]],\n          \"id\": \"ROU\",\n          \"properties\": { \"name\": \"Romania\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[574]],\n            [[-562, 575, -469]],\n            [[576]],\n            [[577]],\n            [[578]],\n            [[579]],\n            [[580]],\n            [[581]],\n            [[582]],\n            [\n              [\n                -181,\n                583,\n                584,\n                585,\n                -177,\n                586,\n                -175,\n                587,\n                -202,\n                588,\n                -200,\n                589,\n                -198,\n                -499,\n                590,\n                591,\n                -496,\n                592,\n                -494,\n                593,\n                -492,\n                -196,\n                -433,\n                594,\n                -431,\n                595,\n                596,\n                -428,\n                597,\n                -426,\n                598,\n                599,\n                600,\n                -55,\n                601,\n                -322,\n                602,\n                -320,\n                603,\n                604,\n                605,\n                606,\n                607,\n                608,\n                609,\n                610,\n                611,\n                -95,\n                612,\n                -93,\n                -474,\n                -287,\n                613,\n                614,\n                -299,\n                615\n              ]\n            ],\n            [[616]],\n            [[617]],\n            [[618]]\n          ],\n          \"id\": \"RUS\",\n          \"properties\": { \"name\": \"Russia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[619, 620, -57, -220, 621]],\n          \"id\": \"RWA\",\n          \"properties\": { \"name\": \"Rwanda\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-475, -264, -511, 622]],\n          \"id\": \"ESH\",\n          \"properties\": { \"name\": \"Western Sahara\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[623, -415, -397, -454, 624, -570, 625, -23, -543, 626]],\n          \"id\": \"SAU\",\n          \"properties\": { \"name\": \"Saudi Arabia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [627, 628, -124, 629, -465, -271, 630, -279, 631, -298, 632]\n          ],\n          \"id\": \"SDN\",\n          \"properties\": { \"name\": \"Sudan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[633, -296, -438, 634, 635, -226, -126, 636, -628]],\n          \"id\": \"SSD\",\n          \"properties\": { \"name\": \"South Sudan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[637, -509, -487, -331, -336, 638, -334]],\n          \"id\": \"SEN\",\n          \"properties\": { \"name\": \"Senegal\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[639]], [[640]], [[641]], [[642]], [[643]]],\n          \"id\": \"SLB\",\n          \"properties\": { \"name\": \"Solomon Islands\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[644, -328, -461]],\n          \"id\": \"SLE\",\n          \"properties\": { \"name\": \"Sierra Leone\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[645, -346, -351]],\n          \"id\": \"SLV\",\n          \"properties\": { \"name\": \"El Salvador\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[646, 647, -291, -253, 648, -439, -294]],\n          \"id\": \"SOM\",\n          \"properties\": { \"name\": \"Somalia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-83, -483, -451, -491, -88, 649, -355, 650, -364, -574]],\n          \"id\": \"SRB\",\n          \"properties\": { \"name\": \"Serbia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[651, -312, 652, -111, -348]],\n          \"id\": \"SUR\",\n          \"properties\": { \"name\": \"Suriname\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-564, 653, -361, -50, -244]],\n          \"id\": \"SVK\",\n          \"properties\": { \"name\": \"Slovakia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-41, -366, -358, 654, -411]],\n          \"id\": \"SVN\",\n          \"properties\": { \"name\": \"Slovenia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-535, -301, 655]],\n          \"id\": \"SWE\",\n          \"properties\": { \"name\": \"Sweden\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[656, -504]],\n          \"id\": \"SWZ\",\n          \"properties\": { \"name\": \"Eswatini\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-414, -408, -459, 657, 658, -399]],\n          \"id\": \"SYR\",\n          \"properties\": { \"name\": \"Syria\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-529, 659, -527, 660, -525, -466, -630, -123, -215, 661]],\n          \"id\": \"TCD\",\n          \"properties\": { \"name\": \"Chad\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[662, -326, -73, -66]],\n          \"id\": \"TGO\",\n          \"properties\": { \"name\": \"Togo\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[663, -518, 664, -489, -455, -442]],\n          \"id\": \"THA\",\n          \"properties\": { \"name\": \"Thailand\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-440, -193, -3, 665]],\n          \"id\": \"TJK\",\n          \"properties\": { \"name\": \"Tajikistan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-388, 666, -422, 667, -1]],\n          \"id\": \"TKM\",\n          \"properties\": { \"name\": \"Turkmenistan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[668, -368]],\n          \"id\": \"TLS\",\n          \"properties\": { \"name\": \"Timor\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[669]],\n          \"id\": \"TTO\",\n          \"properties\": { \"name\": \"Trinidad and Tobago\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-267, 670, -463]],\n          \"id\": \"TUN\",\n          \"properties\": { \"name\": \"Tunisia\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[-324, -36, -393, 671, -400, -659, 672]],\n            [[-341, -80, 673]]\n          ],\n          \"id\": \"TUR\",\n          \"properties\": { \"name\": \"Turkey\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[674]],\n          \"id\": \"TWN\",\n          \"properties\": { \"name\": \"Taiwan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [\n              -436,\n              675,\n              676,\n              -500,\n              677,\n              678,\n              679,\n              680,\n              -221,\n              -59,\n              681,\n              -620,\n              682\n            ]\n          ],\n          \"id\": \"TZA\",\n          \"properties\": { \"name\": \"Tanzania\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-622, -219, 683, -217, 684, -635, -437, -683]],\n          \"id\": \"UGA\",\n          \"properties\": { \"name\": \"Uganda\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [\n            [\n              685,\n              -611,\n              686,\n              -609,\n              687,\n              688,\n              689,\n              -605,\n              690,\n              -572,\n              -477,\n              -571,\n              -362,\n              -654,\n              -563,\n              -97\n            ]\n          ],\n          \"id\": \"UKR\",\n          \"properties\": { \"name\": \"Ukraine\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-114, 691, -28]],\n          \"id\": \"URY\",\n          \"properties\": { \"name\": \"Uruguay\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[692]],\n            [[693]],\n            [[694]],\n            [[695]],\n            [[696]],\n            [[697, -481, 698, -140]],\n            [[699]],\n            [[700]],\n            [[701]],\n            [[-144, 702, -142, 703]]\n          ],\n          \"id\": \"USA\",\n          \"properties\": { \"name\": \"United States\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-668, -421, -441, -666, -2]],\n          \"id\": \"UZB\",\n          \"properties\": { \"name\": \"Uzbekistan\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[704, -349, -109, -234]],\n          \"id\": \"VEN\",\n          \"properties\": { \"name\": \"Venezuela\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[705, -444, -457, -185]],\n          \"id\": \"VNM\",\n          \"properties\": { \"name\": \"Vietnam\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[706]], [[707]]],\n          \"id\": \"VUT\",\n          \"properties\": { \"name\": \"Vanuatu\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-417, -404]],\n          \"id\": \"PSX\",\n          \"properties\": { \"name\": \"West Bank\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[708, -627, -542]],\n          \"id\": \"YEM\",\n          \"properties\": { \"name\": \"Yemen\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-523, -119, 709, -505, -657, -503, 710], [-468]],\n          \"id\": \"ZAF\",\n          \"properties\": { \"name\": \"South Africa\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-512, -507, 711, -121, -522, -7, -223, 712, -680]],\n          \"id\": \"ZMB\",\n          \"properties\": { \"name\": \"Zambia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-710, -122, -712, -506]],\n          \"id\": \"ZWE\",\n          \"properties\": { \"name\": \"Zimbabwe\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[713]],\n            [[714]],\n            [[715]],\n            [[716]],\n            [[717]],\n            [[718]],\n            [[719]],\n            [[720]]\n          ],\n          \"id\": \"CPV\",\n          \"properties\": { \"name\": \"Cape Verde\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[721]], [[722]], [[723]]],\n          \"id\": \"COM\",\n          \"properties\": { \"name\": \"Comoros\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[724]],\n          \"id\": \"MUS\",\n          \"properties\": { \"name\": \"Mauritius\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[725]],\n          \"id\": \"SYC\",\n          \"properties\": { \"name\": \"Seychelles\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[726]],\n          \"id\": \"BHR\",\n          \"properties\": { \"name\": \"Bahrain\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[727]], [[728]]],\n          \"id\": \"MDV\",\n          \"properties\": { \"name\": \"Maldives\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[729]], [[730]]],\n          \"id\": \"MHL\",\n          \"properties\": { \"name\": \"Marshall Islands\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[731]], [[732]], [[733]], [[734]], [[735]]],\n          \"id\": \"FSM\",\n          \"properties\": { \"name\": \"Micronesia (country)\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[736]],\n          \"id\": \"NRU\",\n          \"properties\": { \"name\": \"Nauru\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[737]],\n          \"id\": \"PLW\",\n          \"properties\": { \"name\": \"Palau\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[738]], [[739]]],\n          \"id\": \"WSM\",\n          \"properties\": { \"name\": \"Samoa\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[515, 740]],\n          \"id\": \"SGP\",\n          \"properties\": { \"name\": \"Singapore\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[741]], [[742]], [[743]]],\n          \"id\": \"TON\",\n          \"properties\": { \"name\": \"Tonga\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [\n            [[744]],\n            [[745]],\n            [[746]],\n            [[747]],\n            [[748]],\n            [[749]],\n            [[750]]\n          ],\n          \"id\": \"TUV\",\n          \"properties\": { \"name\": \"Tuvalu\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[751]], [[752]]],\n          \"id\": \"ATG\",\n          \"properties\": { \"name\": \"Antigua and Barbuda\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[753]],\n          \"id\": \"BRB\",\n          \"properties\": { \"name\": \"Barbados\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[754]],\n          \"id\": \"DMA\",\n          \"properties\": { \"name\": \"Dominica\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[755]],\n          \"id\": \"GRD\",\n          \"properties\": { \"name\": \"Grenada\" }\n        },\n        {\n          \"type\": \"MultiPolygon\",\n          \"arcs\": [[[756]], [[757]]],\n          \"id\": \"KNA\",\n          \"properties\": { \"name\": \"Saint Kitts and Nevis\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[758]],\n          \"id\": \"LCA\",\n          \"properties\": { \"name\": \"Saint Lucia\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[759]],\n          \"id\": \"VCT\",\n          \"properties\": { \"name\": \"Saint Vincent and the Grenadines\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[760]],\n          \"id\": \"AND\",\n          \"properties\": { \"name\": \"Andorra\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[-45, 761, -166, 762]],\n          \"id\": \"LIE\",\n          \"properties\": { \"name\": \"Liechtenstein\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[763]],\n          \"id\": \"MLT\",\n          \"properties\": { \"name\": \"Malta\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[764]],\n          \"id\": \"MCO\",\n          \"properties\": { \"name\": \"Monaco\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[765]],\n          \"id\": \"SMR\",\n          \"properties\": { \"name\": \"San Marino\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[766]],\n          \"id\": \"KIR\",\n          \"properties\": { \"name\": \"Kiribati\" }\n        },\n        {\n          \"type\": \"Polygon\",\n          \"arcs\": [[767]],\n          \"id\": \"STP\",\n          \"properties\": { \"name\": \"Sao Tome and Principe\" }\n        }\n      ]\n    }\n  },\n  \"arcs\": [\n    [\n      [61.21, 35.65],\n      [62.23, 35.27],\n      [62.98, 35.4],\n      [63.19, 35.86],\n      [63.98, 36.01],\n      [64.55, 36.31],\n      [64.75, 37.11],\n      [65.59, 37.31],\n      [65.75, 37.66],\n      [66.22, 37.39],\n      [66.52, 37.36]\n    ],\n    [\n      [66.52, 37.36],\n      [67.08, 37.36],\n      [67.83, 37.14]\n    ],\n    [\n      [67.83, 37.14],\n      [68.14, 37.02],\n      [68.86, 37.34],\n      [69.2, 37.15],\n      [69.52, 37.61],\n      [70.12, 37.59],\n      [70.27, 37.74],\n      [70.38, 38.14],\n      [70.81, 38.49],\n      [71.35, 38.26],\n      [71.24, 37.95],\n      [71.54, 37.91],\n      [71.45, 37.07],\n      [71.84, 36.74],\n      [72.19, 36.95],\n      [72.64, 37.05],\n      [73.26, 37.5],\n      [73.95, 37.42],\n      [74.98, 37.42]\n    ],\n    [\n      [74.98, 37.42],\n      [75.16, 37.13]\n    ],\n    [\n      [75.16, 37.13],\n      [74.58, 37.02],\n      [74.07, 36.84],\n      [72.92, 36.72],\n      [71.85, 36.51],\n      [71.26, 36.07],\n      [71.5, 35.65],\n      [71.61, 35.15],\n      [71.12, 34.73],\n      [71.16, 34.35],\n      [70.88, 33.99],\n      [69.93, 34.02],\n      [70.32, 33.36],\n      [69.69, 33.11],\n      [69.26, 32.5],\n      [69.32, 31.9],\n      [68.93, 31.62],\n      [68.56, 31.71],\n      [67.79, 31.58],\n      [67.68, 31.3],\n      [66.94, 31.3],\n      [66.38, 30.74],\n      [66.35, 29.89],\n      [65.05, 29.47],\n      [64.35, 29.56],\n      [64.15, 29.34],\n      [63.55, 29.47],\n      [62.55, 29.32],\n      [60.87, 29.83]\n    ],\n    [\n      [60.87, 29.83],\n      [61.78, 30.74],\n      [61.7, 31.38],\n      [60.94, 31.55],\n      [60.86, 32.18],\n      [60.54, 32.98],\n      [60.96, 33.53],\n      [60.53, 33.68],\n      [60.8, 34.4],\n      [61.21, 35.65]\n    ],\n    [\n      [23.91, -10.93],\n      [24.02, -11.24],\n      [23.9, -11.72],\n      [24.08, -12.19],\n      [23.93, -12.57],\n      [24.02, -12.91],\n      [21.93, -12.9],\n      [21.89, -16.08],\n      [22.56, -16.9],\n      [23.22, -17.52]\n    ],\n    [\n      [23.22, -17.52],\n      [21.38, -17.93],\n      [18.96, -17.79],\n      [18.26, -17.31],\n      [14.21, -17.35],\n      [14.06, -17.42],\n      [13.46, -16.97],\n      [12.81, -16.94],\n      [12.22, -17.11],\n      [11.73, -17.3]\n    ],\n    [\n      [11.73, -17.3],\n      [11.64, -16.67],\n      [11.78, -15.79],\n      [12.12, -14.88],\n      [12.18, -14.45],\n      [12.5, -13.55],\n      [12.74, -13.14],\n      [13.31, -12.48],\n      [13.63, -12.04],\n      [13.74, -11.3],\n      [13.69, -10.73],\n      [13.39, -10.37],\n      [13.12, -9.77],\n      [12.88, -9.17],\n      [12.93, -8.96],\n      [13.24, -8.56],\n      [12.93, -7.6],\n      [12.73, -6.93],\n      [12.23, -6.29],\n      [12.32, -6.1]\n    ],\n    [\n      [12.32, -6.1],\n      [12.74, -5.97],\n      [13.02, -5.98],\n      [13.38, -5.86],\n      [16.33, -5.88],\n      [16.57, -6.62],\n      [16.86, -7.22],\n      [17.09, -7.55],\n      [17.47, -8.07],\n      [18.13, -7.99],\n      [18.46, -7.85],\n      [19.02, -7.99],\n      [19.17, -7.74],\n      [19.42, -7.16],\n      [20.04, -7.12],\n      [20.09, -6.94],\n      [20.6, -6.94],\n      [20.51, -7.3],\n      [21.73, -7.29],\n      [21.75, -7.92],\n      [21.95, -8.31],\n      [21.8, -8.91],\n      [21.88, -9.52],\n      [22.21, -9.89],\n      [22.16, -11.08],\n      [22.4, -10.99],\n      [22.84, -11.02],\n      [23.46, -10.87],\n      [23.91, -10.93]\n    ],\n    [\n      [12.18, -5.79],\n      [11.91, -5.04]\n    ],\n    [\n      [11.91, -5.04],\n      [12.32, -4.61],\n      [12.62, -4.44],\n      [13, -4.78]\n    ],\n    [\n      [13, -4.78],\n      [12.63, -4.99],\n      [12.47, -5.25],\n      [12.44, -5.68],\n      [12.18, -5.79]\n    ],\n    [\n      [21.02, 40.84],\n      [21, 40.58],\n      [20.67, 40.44],\n      [20.62, 40.11],\n      [20.15, 39.62]\n    ],\n    [\n      [20.15, 39.62],\n      [19.98, 39.69],\n      [19.96, 39.92],\n      [19.41, 40.25],\n      [19.32, 40.73],\n      [19.4, 41.41],\n      [19.54, 41.72],\n      [19.37, 41.88],\n      [19.3, 42.2],\n      [19.74, 42.69]\n    ],\n    [\n      [19.74, 42.69],\n      [19.8, 42.5],\n      [20.07, 42.59]\n    ],\n    [\n      [20.07, 42.59],\n      [20.28, 42.32],\n      [20.52, 42.22]\n    ],\n    [\n      [20.52, 42.22],\n      [20.59, 41.86],\n      [20.46, 41.52],\n      [20.61, 41.09],\n      [21.02, 40.84]\n    ],\n    [\n      [51.58, 24.25],\n      [51.76, 24.29],\n      [51.79, 24.02],\n      [52.58, 24.18],\n      [53.4, 24.15],\n      [54.01, 24.12],\n      [54.69, 24.8],\n      [55.44, 25.44],\n      [56.07, 26.06]\n    ],\n    [\n      [56.07, 26.06],\n      [56.26, 25.71]\n    ],\n    [\n      [56.26, 25.71],\n      [56.4, 24.92]\n    ],\n    [\n      [56.4, 24.92],\n      [55.89, 24.92],\n      [55.8, 24.27],\n      [55.98, 24.13],\n      [55.53, 23.93],\n      [55.53, 23.52],\n      [55.23, 23.11],\n      [55.21, 22.71]\n    ],\n    [\n      [55.21, 22.71],\n      [55.01, 22.5],\n      [52, 23],\n      [51.62, 24.01],\n      [51.58, 24.25]\n    ],\n    [\n      [-66.96, -54.9],\n      [-67.56, -54.87],\n      [-68.63, -54.87],\n      [-68.63, -52.64]\n    ],\n    [\n      [-68.63, -52.64],\n      [-68.25, -53.1],\n      [-67.75, -53.85],\n      [-66.45, -54.45],\n      [-65.05, -54.7],\n      [-65.5, -55.2],\n      [-66.45, -55.25],\n      [-66.96, -54.9]\n    ],\n    [\n      [-62.69, -22.25],\n      [-60.85, -23.88],\n      [-60.03, -24.03],\n      [-58.81, -24.77],\n      [-57.78, -25.16],\n      [-57.63, -25.6],\n      [-58.62, -27.12],\n      [-57.61, -27.4],\n      [-56.49, -27.55],\n      [-55.7, -27.39],\n      [-54.79, -26.62],\n      [-54.63, -25.74]\n    ],\n    [\n      [-54.63, -25.74],\n      [-54.13, -25.55],\n      [-53.63, -26.12],\n      [-53.65, -26.92],\n      [-54.49, -27.47],\n      [-55.16, -27.88],\n      [-56.29, -28.85],\n      [-57.63, -30.22]\n    ],\n    [\n      [-57.63, -30.22],\n      [-57.87, -31.02],\n      [-58.14, -32.04],\n      [-58.13, -33.04],\n      [-58.35, -33.26],\n      [-58.43, -33.91]\n    ],\n    [\n      [-58.43, -33.91],\n      [-58.5, -34.43],\n      [-57.23, -35.29],\n      [-57.36, -35.98],\n      [-56.74, -36.41],\n      [-56.79, -36.9],\n      [-57.75, -38.18],\n      [-59.23, -38.72],\n      [-61.24, -38.93],\n      [-62.34, -38.83],\n      [-62.13, -39.42],\n      [-62.33, -40.17],\n      [-62.15, -40.68],\n      [-62.75, -41.03],\n      [-63.77, -41.17],\n      [-64.73, -40.8],\n      [-65.12, -41.06],\n      [-64.98, -42.06],\n      [-64.3, -42.36],\n      [-63.76, -42.04],\n      [-63.46, -42.56],\n      [-64.38, -42.87],\n      [-65.18, -43.5],\n      [-65.33, -44.5],\n      [-65.57, -45.04],\n      [-66.51, -45.04],\n      [-67.29, -45.55],\n      [-67.58, -46.3],\n      [-66.6, -47.03],\n      [-65.64, -47.24],\n      [-65.99, -48.13],\n      [-67.17, -48.7],\n      [-67.82, -49.87],\n      [-68.73, -50.26],\n      [-69.14, -50.73],\n      [-68.82, -51.77],\n      [-68.15, -52.35],\n      [-68.57, -52.3]\n    ],\n    [\n      [-68.57, -52.3],\n      [-69.5, -52.14],\n      [-71.91, -52.01],\n      [-72.33, -51.43],\n      [-72.31, -50.68],\n      [-72.98, -50.74],\n      [-73.33, -50.38],\n      [-73.42, -49.32],\n      [-72.65, -48.88],\n      [-72.33, -48.24],\n      [-72.45, -47.74],\n      [-71.92, -46.88],\n      [-71.55, -45.56],\n      [-71.66, -44.97],\n      [-71.22, -44.78],\n      [-71.33, -44.41],\n      [-71.79, -44.21],\n      [-71.46, -43.79],\n      [-71.92, -43.41],\n      [-72.15, -42.25],\n      [-71.75, -42.05],\n      [-71.92, -40.83],\n      [-71.68, -39.81],\n      [-71.41, -38.92],\n      [-70.81, -38.55],\n      [-71.12, -37.58],\n      [-71.12, -36.66],\n      [-70.36, -36.01],\n      [-70.39, -35.17],\n      [-69.82, -34.19],\n      [-69.81, -33.27],\n      [-70.07, -33.09],\n      [-70.54, -31.37],\n      [-69.92, -30.34],\n      [-70.01, -29.37],\n      [-69.66, -28.46],\n      [-69, -27.52],\n      [-68.3, -26.9],\n      [-68.59, -26.51],\n      [-68.39, -26.19],\n      [-68.42, -24.52],\n      [-67.33, -24.03],\n      [-66.99, -22.99],\n      [-67.11, -22.74]\n    ],\n    [\n      [-67.11, -22.74],\n      [-66.27, -21.83],\n      [-64.96, -22.08],\n      [-64.38, -22.8],\n      [-63.99, -21.99],\n      [-62.85, -22.03],\n      [-62.69, -22.25]\n    ],\n    [\n      [43.58, 41.09],\n      [44.97, 41.25]\n    ],\n    [\n      [44.97, 41.25],\n      [45.18, 40.99],\n      [45.56, 40.81],\n      [45.36, 40.56],\n      [45.89, 40.22],\n      [45.61, 39.9],\n      [46.03, 39.63],\n      [46.48, 39.46],\n      [46.51, 38.77]\n    ],\n    [\n      [46.51, 38.77],\n      [46.14, 38.74]\n    ],\n    [\n      [46.14, 38.74],\n      [45.74, 39.32],\n      [45.74, 39.47],\n      [45.3, 39.47],\n      [45, 39.74],\n      [44.79, 39.71]\n    ],\n    [\n      [44.79, 39.71],\n      [44.4, 40.01],\n      [43.66, 40.25],\n      [43.75, 40.74],\n      [43.58, 41.09]\n    ],\n    [\n      [68.94, -48.62],\n      [69.58, -48.94],\n      [70.53, -49.06],\n      [70.56, -49.25],\n      [70.28, -49.71],\n      [68.75, -49.77],\n      [68.72, -49.24],\n      [68.87, -48.83],\n      [68.94, -48.62]\n    ],\n    [\n      [145.4, -40.79],\n      [146.36, -41.14],\n      [146.91, -41],\n      [147.69, -40.81],\n      [148.29, -40.88],\n      [148.36, -42.06],\n      [148.02, -42.41],\n      [147.91, -43.21],\n      [147.56, -42.94],\n      [146.87, -43.63],\n      [146.66, -43.58],\n      [146.05, -43.55],\n      [145.43, -42.69],\n      [145.3, -42.03],\n      [144.72, -41.16],\n      [144.74, -40.7],\n      [145.4, -40.79]\n    ],\n    [\n      [143.56, -13.76],\n      [143.92, -14.55],\n      [144.56, -14.17],\n      [144.89, -14.59],\n      [145.37, -14.98],\n      [145.27, -15.43],\n      [145.49, -16.29],\n      [145.64, -16.78],\n      [145.89, -16.91],\n      [146.16, -17.76],\n      [146.06, -18.28],\n      [146.39, -18.96],\n      [147.47, -19.48],\n      [148.18, -19.96],\n      [148.85, -20.39],\n      [148.72, -20.63],\n      [149.29, -21.26],\n      [149.68, -22.34],\n      [150.08, -22.12],\n      [150.48, -22.56],\n      [150.73, -22.4],\n      [150.9, -23.46],\n      [151.61, -24.08],\n      [152.07, -24.46],\n      [152.86, -25.27],\n      [153.14, -26.07],\n      [153.16, -26.64],\n      [153.09, -27.26],\n      [153.57, -28.11],\n      [153.51, -29],\n      [153.34, -29.46],\n      [153.07, -30.35],\n      [153.09, -30.92],\n      [152.89, -31.64],\n      [152.45, -32.55],\n      [151.71, -33.04],\n      [151.34, -33.82],\n      [151.01, -34.31],\n      [150.71, -35.17],\n      [150.33, -35.67],\n      [150.08, -36.42],\n      [149.95, -37.11],\n      [150, -37.43],\n      [149.42, -37.77],\n      [148.3, -37.81],\n      [147.38, -38.22],\n      [146.92, -38.61],\n      [146.32, -39.04],\n      [145.49, -38.59],\n      [144.88, -38.42],\n      [145.03, -37.9],\n      [144.49, -38.09],\n      [143.61, -38.81],\n      [142.75, -38.54],\n      [142.18, -38.38],\n      [141.61, -38.31],\n      [140.64, -38.02],\n      [139.99, -37.4],\n      [139.81, -36.64],\n      [139.57, -36.14],\n      [139.08, -35.73],\n      [138.12, -35.61],\n      [138.45, -35.13],\n      [138.21, -34.38],\n      [137.72, -35.08],\n      [136.83, -35.26],\n      [137.35, -34.71],\n      [137.5, -34.13],\n      [137.89, -33.64],\n      [137.81, -32.9],\n      [137, -33.75],\n      [136.37, -34.09],\n      [135.99, -34.89],\n      [135.21, -34.48],\n      [135.24, -33.95],\n      [134.61, -33.22],\n      [134.09, -32.85],\n      [134.27, -32.62],\n      [132.99, -32.01],\n      [132.29, -31.98],\n      [131.33, -31.5],\n      [129.54, -31.59],\n      [128.24, -31.95],\n      [127.1, -32.28],\n      [126.15, -32.22],\n      [125.09, -32.73],\n      [124.22, -32.96],\n      [124.03, -33.48],\n      [123.66, -33.89],\n      [122.81, -33.91],\n      [122.18, -34],\n      [121.3, -33.82],\n      [120.58, -33.93],\n      [119.89, -33.98],\n      [119.3, -34.51],\n      [119.01, -34.46],\n      [118.51, -34.75],\n      [118.02, -35.06],\n      [117.3, -35.03],\n      [116.63, -35.03],\n      [115.56, -34.39],\n      [115.03, -34.2],\n      [115.05, -33.62],\n      [115.55, -33.49],\n      [115.71, -33.26],\n      [115.68, -32.9],\n      [115.8, -32.21],\n      [115.69, -31.61],\n      [115.16, -30.6],\n      [115, -30.03],\n      [115.04, -29.46],\n      [114.64, -28.81],\n      [114.62, -28.52],\n      [114.17, -28.12],\n      [114.05, -27.33],\n      [113.48, -26.54],\n      [113.34, -26.12],\n      [113.78, -26.55],\n      [113.44, -25.62],\n      [113.94, -25.91],\n      [114.23, -26.3],\n      [114.22, -25.79],\n      [113.72, -25],\n      [113.63, -24.68],\n      [113.39, -24.38],\n      [113.5, -23.81],\n      [113.71, -23.56],\n      [113.84, -23.06],\n      [113.74, -22.48],\n      [114.15, -21.76],\n      [114.23, -22.52],\n      [114.65, -21.83],\n      [115.46, -21.5],\n      [115.95, -21.07],\n      [116.71, -20.7],\n      [117.17, -20.62],\n      [117.44, -20.75],\n      [118.23, -20.37],\n      [118.84, -20.26],\n      [118.99, -20.04],\n      [119.25, -19.95],\n      [119.81, -19.98],\n      [120.86, -19.68],\n      [121.4, -19.24],\n      [121.66, -18.71],\n      [122.24, -18.2],\n      [122.29, -17.8],\n      [122.31, -17.25],\n      [123.01, -16.41],\n      [123.43, -17.27],\n      [123.86, -17.07],\n      [123.5, -16.6],\n      [123.82, -16.11],\n      [124.26, -16.33],\n      [124.38, -15.57],\n      [124.93, -15.08],\n      [125.17, -14.68],\n      [125.67, -14.51],\n      [125.69, -14.23],\n      [126.13, -14.35],\n      [126.14, -14.1],\n      [126.58, -13.95],\n      [127.07, -13.82],\n      [127.8, -14.28],\n      [128.36, -14.87],\n      [128.99, -14.88],\n      [129.62, -14.97],\n      [129.41, -14.42],\n      [129.89, -13.62],\n      [130.34, -13.36],\n      [130.18, -13.11],\n      [130.62, -12.54],\n      [131.22, -12.18],\n      [131.74, -12.3],\n      [132.58, -12.11],\n      [132.56, -11.6],\n      [131.82, -11.27],\n      [132.36, -11.13],\n      [133.02, -11.38],\n      [133.55, -11.79],\n      [134.39, -12.04],\n      [134.68, -11.94],\n      [135.3, -12.25],\n      [135.88, -11.96],\n      [136.26, -12.05],\n      [136.49, -11.86],\n      [136.95, -12.35],\n      [136.69, -12.89],\n      [136.31, -13.29],\n      [135.96, -13.32],\n      [136.08, -13.72],\n      [135.78, -14.22],\n      [135.43, -14.72],\n      [135.5, -15],\n      [136.3, -15.55],\n      [137.07, -15.87],\n      [137.58, -16.22],\n      [138.3, -16.81],\n      [138.59, -16.81],\n      [139.11, -17.06],\n      [139.26, -17.37],\n      [140.22, -17.71],\n      [140.88, -17.37],\n      [141.07, -16.83],\n      [141.27, -16.39],\n      [141.4, -15.84],\n      [141.7, -15.04],\n      [141.56, -14.56],\n      [141.64, -14.27],\n      [141.52, -13.7],\n      [141.65, -12.94],\n      [141.84, -12.74],\n      [141.69, -12.41],\n      [141.93, -11.88],\n      [142.12, -11.33],\n      [142.14, -11.04],\n      [142.52, -10.67],\n      [142.8, -11.16],\n      [142.87, -11.78],\n      [143.12, -11.91],\n      [143.16, -12.33],\n      [143.52, -12.83],\n      [143.6, -13.4],\n      [143.56, -13.76]\n    ],\n    [\n      [16.98, 48.12],\n      [16.9, 47.71],\n      [16.34, 47.71],\n      [16.53, 47.5],\n      [16.2, 46.85]\n    ],\n    [\n      [16.2, 46.85],\n      [16.01, 46.68],\n      [15.14, 46.66],\n      [14.63, 46.43],\n      [13.81, 46.51]\n    ],\n    [\n      [13.81, 46.51],\n      [12.38, 46.77],\n      [12.15, 47.12],\n      [11.16, 46.94],\n      [11.05, 46.75],\n      [10.44, 46.89]\n    ],\n    [\n      [10.44, 46.89],\n      [9.88, 46.93],\n      [9.87, 47.02]\n    ],\n    [\n      [9.87, 47.02],\n      [9.61, 47.06]\n    ],\n    [\n      [9.61, 47.06],\n      [9.64, 47.1],\n      [9.62, 47.15],\n      [9.56, 47.17],\n      [9.58, 47.21]\n    ],\n    [\n      [9.58, 47.21],\n      [9.53, 47.27],\n      [9.63, 47.36]\n    ],\n    [\n      [9.63, 47.36],\n      [9.59, 47.53]\n    ],\n    [\n      [9.59, 47.53],\n      [9.9, 47.58],\n      [10.4, 47.3],\n      [10.54, 47.57],\n      [11.43, 47.52],\n      [12.14, 47.7],\n      [12.62, 47.67],\n      [12.93, 47.47],\n      [13.03, 47.64],\n      [12.88, 48.29],\n      [13.24, 48.42],\n      [13.6, 48.88]\n    ],\n    [\n      [13.6, 48.88],\n      [14.34, 48.56],\n      [14.9, 48.96],\n      [15.25, 49.04],\n      [16.03, 48.73],\n      [16.5, 48.79],\n      [16.96, 48.6]\n    ],\n    [\n      [16.96, 48.6],\n      [16.88, 48.47],\n      [16.98, 48.12]\n    ],\n    [\n      [46.14, 38.74],\n      [45.46, 38.87],\n      [44.95, 39.34],\n      [44.79, 39.71]\n    ],\n    [\n      [47.99, 41.41],\n      [48.58, 41.81],\n      [49.11, 41.28],\n      [49.62, 40.57],\n      [50.08, 40.53],\n      [50.39, 40.26],\n      [49.57, 40.18],\n      [49.4, 39.4],\n      [49.22, 39.05],\n      [48.86, 38.82],\n      [48.88, 38.32]\n    ],\n    [\n      [48.88, 38.32],\n      [48.63, 38.27],\n      [48.01, 38.79],\n      [48.36, 39.29],\n      [48.06, 39.58],\n      [47.69, 39.51],\n      [46.51, 38.77]\n    ],\n    [\n      [44.97, 41.25],\n      [45.22, 41.41],\n      [45.96, 41.12],\n      [46.5, 41.06],\n      [46.64, 41.18],\n      [46.15, 41.72],\n      [46.4, 41.86]\n    ],\n    [\n      [46.4, 41.86],\n      [46.69, 41.83],\n      [47.37, 41.22],\n      [47.82, 41.15],\n      [47.99, 41.41]\n    ],\n    [\n      [29.34, -4.5],\n      [29.28, -3.29],\n      [29.02, -2.84]\n    ],\n    [\n      [29.02, -2.84],\n      [29.63, -2.92],\n      [29.94, -2.35],\n      [30.47, -2.41]\n    ],\n    [\n      [30.47, -2.41],\n      [30.53, -2.81],\n      [30.74, -3.03],\n      [30.75, -3.36],\n      [30.51, -3.57],\n      [30.12, -4.09],\n      [29.75, -4.45]\n    ],\n    [\n      [29.75, -4.45],\n      [29.34, -4.5]\n    ],\n    [\n      [4.05, 51.27],\n      [4.97, 51.48],\n      [5.61, 51.04],\n      [6.16, 50.8]\n    ],\n    [\n      [6.16, 50.8],\n      [6.04, 50.13]\n    ],\n    [\n      [6.04, 50.13],\n      [5.78, 50.09],\n      [5.67, 49.53]\n    ],\n    [\n      [5.67, 49.53],\n      [4.8, 49.99],\n      [4.29, 49.91],\n      [3.59, 50.38],\n      [3.12, 50.78],\n      [2.66, 50.8],\n      [2.51, 51.15]\n    ],\n    [\n      [2.51, 51.15],\n      [3.31, 51.35],\n      [4.05, 51.27]\n    ],\n    [\n      [2.69, 6.26],\n      [1.87, 6.14]\n    ],\n    [\n      [1.87, 6.14],\n      [1.62, 6.83],\n      [1.66, 9.13],\n      [1.46, 9.33],\n      [1.43, 9.83],\n      [1.08, 10.18],\n      [0.77, 10.47],\n      [0.9, 11]\n    ],\n    [\n      [0.9, 11],\n      [1.24, 11.11],\n      [1.45, 11.55],\n      [1.94, 11.64],\n      [2.15, 11.94]\n    ],\n    [\n      [2.15, 11.94],\n      [2.49, 12.23],\n      [2.85, 12.24],\n      [3.61, 11.66]\n    ],\n    [\n      [3.61, 11.66],\n      [3.57, 11.33],\n      [3.8, 10.73],\n      [3.6, 10.33],\n      [3.71, 10.06],\n      [3.22, 9.44],\n      [2.91, 9.14],\n      [2.72, 8.51],\n      [2.75, 7.87],\n      [2.69, 6.26]\n    ],\n    [\n      [-2.83, 9.64],\n      [-3.51, 9.9],\n      [-3.98, 9.86],\n      [-4.33, 9.61],\n      [-4.78, 9.82],\n      [-4.95, 10.15],\n      [-5.4, 10.37]\n    ],\n    [\n      [-5.4, 10.37],\n      [-5.47, 10.95],\n      [-5.2, 11.38],\n      [-5.22, 11.71],\n      [-4.43, 12.54],\n      [-4.28, 13.23],\n      [-4.01, 13.47],\n      [-3.52, 13.34],\n      [-3.1, 13.54],\n      [-2.97, 13.8],\n      [-2.19, 14.25],\n      [-2, 14.56],\n      [-1.07, 14.97],\n      [-0.52, 15.12],\n      [-0.27, 14.92],\n      [0.37, 14.93]\n    ],\n    [\n      [0.37, 14.93],\n      [0.3, 14.44],\n      [0.43, 13.99],\n      [0.99, 13.34],\n      [1.02, 12.85],\n      [2.18, 12.63],\n      [2.15, 11.94]\n    ],\n    [\n      [0.9, 11],\n      [0.02, 11.02]\n    ],\n    [\n      [0.02, 11.02],\n      [-0.44, 11.1],\n      [-0.76, 10.94],\n      [-1.2, 11.01],\n      [-2.94, 10.96],\n      [-2.96, 10.4],\n      [-2.83, 9.64]\n    ],\n    [\n      [92.67, 22.04],\n      [92.65, 21.32],\n      [92.3, 21.48],\n      [92.37, 20.67]\n    ],\n    [\n      [92.37, 20.67],\n      [92.08, 21.19],\n      [92.03, 21.7],\n      [91.83, 22.18],\n      [91.42, 22.77],\n      [90.5, 22.81],\n      [90.59, 22.39],\n      [90.27, 21.84],\n      [89.85, 22.04],\n      [89.7, 21.86],\n      [89.42, 21.97],\n      [89.03, 22.06]\n    ],\n    [\n      [89.03, 22.06],\n      [88.88, 22.88],\n      [88.53, 23.63],\n      [88.7, 24.23],\n      [88.08, 24.5],\n      [88.31, 24.87],\n      [88.93, 25.24],\n      [88.21, 25.77],\n      [88.56, 26.45],\n      [89.36, 26.01],\n      [89.83, 25.97],\n      [89.92, 25.27],\n      [90.87, 25.13],\n      [91.8, 25.15],\n      [92.38, 24.98],\n      [91.92, 24.13],\n      [91.47, 24.07],\n      [91.16, 23.5],\n      [91.71, 22.99],\n      [91.87, 23.62],\n      [92.15, 23.63],\n      [92.67, 22.04]\n    ],\n    [\n      [22.66, 44.23],\n      [22.94, 43.82],\n      [23.33, 43.9],\n      [24.1, 43.74],\n      [25.57, 43.69],\n      [26.07, 43.94],\n      [27.24, 44.18],\n      [27.97, 43.81],\n      [28.56, 43.71]\n    ],\n    [\n      [28.56, 43.71],\n      [28.04, 43.29],\n      [27.67, 42.58],\n      [28, 42.01]\n    ],\n    [\n      [28, 42.01],\n      [27.14, 42.14],\n      [26.12, 41.83]\n    ],\n    [\n      [26.12, 41.83],\n      [26.11, 41.33],\n      [25.2, 41.23],\n      [24.49, 41.58],\n      [23.69, 41.31],\n      [22.95, 41.34]\n    ],\n    [\n      [22.95, 41.34],\n      [22.88, 42],\n      [22.38, 42.32]\n    ],\n    [\n      [22.38, 42.32],\n      [22.55, 42.46],\n      [22.44, 42.58],\n      [22.6, 42.9],\n      [22.99, 43.21],\n      [22.5, 43.64],\n      [22.41, 44.01],\n      [22.66, 44.23]\n    ],\n    [\n      [-77.53, 23.76],\n      [-77.78, 23.71],\n      [-78.03, 24.29],\n      [-78.41, 24.58],\n      [-78.19, 25.21],\n      [-77.89, 25.17],\n      [-77.54, 24.34],\n      [-77.53, 23.76]\n    ],\n    [\n      [-77.82, 26.58],\n      [-78.91, 26.42],\n      [-78.98, 26.79],\n      [-78.51, 26.87],\n      [-77.85, 26.84],\n      [-77.82, 26.58]\n    ],\n    [\n      [-77, 26.59],\n      [-77.17, 25.88],\n      [-77.36, 26.01],\n      [-77.34, 26.53],\n      [-77.79, 26.93],\n      [-77.79, 27.04],\n      [-77, 26.59]\n    ],\n    [\n      [19.01, 44.86],\n      [19.37, 44.86]\n    ],\n    [\n      [19.37, 44.86],\n      [19.12, 44.42],\n      [19.6, 44.04],\n      [19.45, 43.57],\n      [19.22, 43.52]\n    ],\n    [\n      [19.22, 43.52],\n      [19.03, 43.43],\n      [18.71, 43.2],\n      [18.56, 42.65]\n    ],\n    [\n      [18.56, 42.65],\n      [17.67, 43.03],\n      [17.3, 43.45],\n      [16.92, 43.67],\n      [16.46, 44.04],\n      [16.24, 44.35],\n      [15.75, 44.82],\n      [15.96, 45.23],\n      [16.32, 45],\n      [16.53, 45.21],\n      [17, 45.23],\n      [17.86, 45.07],\n      [18.55, 45.08],\n      [19.01, 44.86]\n    ],\n    [\n      [23.48, 53.91],\n      [24.45, 53.91],\n      [25.54, 54.28],\n      [25.77, 54.85],\n      [26.59, 55.17],\n      [26.49, 55.62]\n    ],\n    [\n      [26.49, 55.62],\n      [27.1, 55.78],\n      [28.18, 56.17]\n    ],\n    [\n      [28.18, 56.17],\n      [29.23, 55.92],\n      [29.37, 55.67],\n      [29.9, 55.79],\n      [30.87, 55.55],\n      [30.97, 55.08],\n      [30.76, 54.81],\n      [31.38, 54.16],\n      [31.79, 53.97],\n      [31.73, 53.79],\n      [32.41, 53.62],\n      [32.69, 53.35],\n      [32.3, 53.13]\n    ],\n    [\n      [32.3, 53.13],\n      [31.5, 53.17],\n      [31.31, 53.07]\n    ],\n    [\n      [31.31, 53.07],\n      [31.54, 52.74]\n    ],\n    [\n      [31.54, 52.74],\n      [31.79, 52.1]\n    ],\n    [\n      [31.79, 52.1],\n      [30.93, 52.04],\n      [30.62, 51.82],\n      [30.56, 51.32],\n      [30.16, 51.42],\n      [29.25, 51.37],\n      [28.99, 51.6],\n      [28.62, 51.43],\n      [28.24, 51.57],\n      [27.45, 51.59],\n      [26.34, 51.83],\n      [25.33, 51.91],\n      [24.55, 51.89],\n      [24.01, 51.62],\n      [23.53, 51.58]\n    ],\n    [\n      [23.53, 51.58],\n      [23.51, 52.02],\n      [23.2, 52.49],\n      [23.8, 52.69],\n      [23.8, 53.09],\n      [23.53, 53.47],\n      [23.48, 53.91]\n    ],\n    [\n      [-89.14, 17.81],\n      [-89.15, 17.96],\n      [-89.03, 18],\n      [-88.85, 17.88],\n      [-88.49, 18.49],\n      [-88.3, 18.5]\n    ],\n    [\n      [-88.3, 18.5],\n      [-88.3, 18.35],\n      [-88.11, 18.35],\n      [-88.12, 18.08],\n      [-88.29, 17.64],\n      [-88.2, 17.49],\n      [-88.3, 17.13],\n      [-88.24, 17.04],\n      [-88.36, 16.53],\n      [-88.55, 16.27],\n      [-88.73, 16.23],\n      [-88.93, 15.89]\n    ],\n    [\n      [-88.93, 15.89],\n      [-89.23, 15.89],\n      [-89.15, 17.02],\n      [-89.14, 17.81]\n    ],\n    [\n      [-67.11, -22.74],\n      [-67.83, -22.87],\n      [-68.22, -21.49],\n      [-68.76, -20.37],\n      [-68.44, -19.41],\n      [-68.97, -18.98],\n      [-69.1, -18.26],\n      [-69.59, -17.58]\n    ],\n    [\n      [-69.59, -17.58],\n      [-68.96, -16.5],\n      [-69.39, -15.66],\n      [-69.16, -15.32],\n      [-69.34, -14.95],\n      [-68.95, -14.45],\n      [-68.93, -13.6],\n      [-68.88, -12.9],\n      [-68.67, -12.56],\n      [-69.53, -10.95]\n    ],\n    [\n      [-69.53, -10.95],\n      [-68.79, -11.04],\n      [-68.27, -11.01],\n      [-68.05, -10.71],\n      [-67.17, -10.31],\n      [-66.65, -9.93],\n      [-65.34, -9.76],\n      [-65.44, -10.51],\n      [-65.32, -10.9],\n      [-65.4, -11.57],\n      [-64.32, -12.46],\n      [-63.2, -12.63],\n      [-62.8, -13],\n      [-62.13, -13.2],\n      [-61.71, -13.49],\n      [-61.08, -13.48],\n      [-60.5, -13.78],\n      [-60.46, -14.35],\n      [-60.26, -14.65],\n      [-60.25, -15.08],\n      [-60.54, -15.09],\n      [-60.16, -16.26],\n      [-58.24, -16.3],\n      [-58.39, -16.88],\n      [-58.28, -17.27],\n      [-57.73, -17.55],\n      [-57.5, -18.17],\n      [-57.68, -18.96],\n      [-57.95, -19.4],\n      [-57.85, -19.97],\n      [-58.17, -20.18]\n    ],\n    [\n      [-58.17, -20.18],\n      [-58.18, -19.87],\n      [-59.12, -19.36],\n      [-60.04, -19.34],\n      [-61.79, -19.63],\n      [-62.27, -20.51],\n      [-62.29, -21.05],\n      [-62.69, -22.25]\n    ],\n    [\n      [-54.63, -25.74],\n      [-54.43, -25.16],\n      [-54.29, -24.57],\n      [-54.29, -24.02],\n      [-54.65, -23.84],\n      [-55.03, -24],\n      [-55.4, -23.96],\n      [-55.52, -23.57],\n      [-55.61, -22.66],\n      [-55.8, -22.36],\n      [-56.47, -22.09],\n      [-56.88, -22.28],\n      [-57.94, -22.09],\n      [-57.87, -20.73],\n      [-58.17, -20.18]\n    ],\n    [\n      [-69.53, -10.95],\n      [-70.09, -11.12],\n      [-70.55, -11.01],\n      [-70.48, -9.49],\n      [-71.3, -10.08],\n      [-72.18, -10.05],\n      [-72.56, -9.52],\n      [-73.23, -9.46],\n      [-73.02, -9.03],\n      [-73.57, -8.42],\n      [-73.99, -7.52],\n      [-73.72, -7.34],\n      [-73.72, -6.92],\n      [-73.12, -6.63],\n      [-73.22, -6.09],\n      [-72.96, -5.74],\n      [-72.89, -5.27],\n      [-71.75, -4.59],\n      [-70.93, -4.4],\n      [-70.79, -4.25],\n      [-69.89, -4.3]\n    ],\n    [\n      [-69.89, -4.3],\n      [-69.44, -1.56],\n      [-69.42, -1.12],\n      [-69.58, -0.55],\n      [-70.02, -0.19],\n      [-70.02, 0.54],\n      [-69.45, 0.71],\n      [-69.25, 0.6],\n      [-69.22, 0.99],\n      [-69.8, 1.09],\n      [-69.82, 1.71],\n      [-67.87, 1.69],\n      [-67.54, 2.04],\n      [-67.26, 1.72],\n      [-67.07, 1.13],\n      [-66.88, 1.25]\n    ],\n    [\n      [-66.88, 1.25],\n      [-66.33, 0.72],\n      [-65.55, 0.79],\n      [-65.35, 1.1],\n      [-64.61, 1.33],\n      [-64.2, 1.49],\n      [-64.08, 1.92],\n      [-63.37, 2.2],\n      [-63.42, 2.41],\n      [-64.27, 2.5],\n      [-64.41, 3.13],\n      [-64.37, 3.8],\n      [-64.82, 4.06],\n      [-64.63, 4.15],\n      [-63.89, 4.02],\n      [-63.09, 3.77],\n      [-62.8, 4.01],\n      [-62.09, 4.16],\n      [-60.97, 4.54],\n      [-60.6, 4.92],\n      [-60.73, 5.2]\n    ],\n    [\n      [-60.73, 5.2],\n      [-60.21, 5.24],\n      [-59.98, 5.01],\n      [-60.11, 4.57],\n      [-59.77, 4.42],\n      [-59.54, 3.96],\n      [-59.82, 3.61],\n      [-59.97, 2.76],\n      [-59.72, 2.25],\n      [-59.65, 1.79],\n      [-59.03, 1.32],\n      [-58.54, 1.27],\n      [-58.43, 1.46],\n      [-58.11, 1.51],\n      [-57.66, 1.68],\n      [-57.34, 1.95],\n      [-56.78, 1.86],\n      [-56.54, 1.9]\n    ],\n    [\n      [-56.54, 1.9],\n      [-56, 1.82],\n      [-55.91, 2.02],\n      [-56.07, 2.22],\n      [-55.97, 2.51],\n      [-55.57, 2.42],\n      [-55.1, 2.52],\n      [-54.52, 2.31]\n    ],\n    [\n      [-54.52, 2.31],\n      [-54.09, 2.11],\n      [-53.78, 2.38],\n      [-53.55, 2.33],\n      [-53.42, 2.05],\n      [-52.94, 2.12],\n      [-52.56, 2.5],\n      [-52.25, 3.24],\n      [-51.66, 4.16]\n    ],\n    [\n      [-51.66, 4.16],\n      [-51.32, 4.2],\n      [-51.07, 3.65],\n      [-50.51, 1.9],\n      [-49.97, 1.74],\n      [-49.95, 1.05],\n      [-50.7, 0.22],\n      [-50.39, -0.08],\n      [-48.62, -0.24],\n      [-48.58, -1.24],\n      [-47.82, -0.58],\n      [-46.57, -0.94],\n      [-44.91, -1.55],\n      [-44.42, -2.14],\n      [-44.58, -2.69],\n      [-43.42, -2.38],\n      [-41.47, -2.91],\n      [-39.98, -2.87],\n      [-38.5, -3.7],\n      [-37.22, -4.82],\n      [-36.45, -5.11],\n      [-35.6, -5.15],\n      [-35.24, -5.46],\n      [-34.9, -6.74],\n      [-34.73, -7.34],\n      [-35.13, -9],\n      [-35.64, -9.65],\n      [-37.05, -11.04],\n      [-37.68, -12.17],\n      [-38.42, -13.04],\n      [-38.67, -13.06],\n      [-38.95, -13.79],\n      [-38.88, -15.67],\n      [-39.16, -17.21],\n      [-39.27, -17.87],\n      [-39.58, -18.26],\n      [-39.76, -19.6],\n      [-40.77, -20.9],\n      [-40.94, -21.94],\n      [-41.75, -22.37],\n      [-41.99, -22.97],\n      [-43.07, -22.97],\n      [-44.65, -23.35],\n      [-45.35, -23.8],\n      [-46.47, -24.09],\n      [-47.65, -24.89],\n      [-48.5, -25.88],\n      [-48.64, -26.62],\n      [-48.47, -27.18],\n      [-48.66, -28.19],\n      [-48.89, -28.67],\n      [-49.59, -29.22],\n      [-50.7, -30.98],\n      [-51.58, -31.78],\n      [-52.26, -32.25],\n      [-52.71, -33.2],\n      [-53.37, -33.77]\n    ],\n    [\n      [-53.37, -33.77],\n      [-53.65, -33.2],\n      [-53.21, -32.73],\n      [-53.79, -32.05],\n      [-54.57, -31.49],\n      [-55.6, -30.85],\n      [-55.97, -30.88],\n      [-56.98, -30.11],\n      [-57.63, -30.22]\n    ],\n    [\n      [114.2, 4.53],\n      [114.6, 4.9],\n      [115.45, 5.45]\n    ],\n    [\n      [115.45, 5.45],\n      [115.41, 4.96],\n      [115.35, 4.32],\n      [114.87, 4.35],\n      [114.66, 4.01],\n      [114.2, 4.53]\n    ],\n    [\n      [91.7, 27.77],\n      [92.1, 27.45],\n      [92.03, 26.84],\n      [91.22, 26.81],\n      [90.37, 26.88],\n      [89.74, 26.72],\n      [88.84, 27.1],\n      [88.81, 27.3]\n    ],\n    [\n      [88.81, 27.3],\n      [89.48, 28.04],\n      [90.02, 28.3],\n      [90.73, 28.06],\n      [91.26, 28.04],\n      [91.7, 27.77]\n    ],\n    [\n      [29.43, -22.09],\n      [28.02, -22.83],\n      [27.12, -23.57],\n      [26.79, -24.24],\n      [26.49, -24.62],\n      [25.94, -24.7],\n      [25.77, -25.17],\n      [25.66, -25.49],\n      [25.03, -25.72],\n      [24.21, -25.67],\n      [23.73, -25.39],\n      [23.31, -25.27],\n      [22.82, -25.5],\n      [22.58, -25.98],\n      [22.11, -26.28],\n      [21.61, -26.73],\n      [20.89, -26.83],\n      [20.67, -26.48],\n      [20.76, -25.87],\n      [20.17, -24.92],\n      [19.9, -24.77]\n    ],\n    [\n      [19.9, -24.77],\n      [19.9, -21.85],\n      [20.88, -21.81],\n      [20.91, -18.25],\n      [21.66, -18.22],\n      [23.2, -17.87],\n      [23.58, -18.28],\n      [24.22, -17.89],\n      [24.52, -17.89],\n      [25.08, -17.66]\n    ],\n    [\n      [25.08, -17.66],\n      [25.26, -17.74]\n    ],\n    [\n      [25.26, -17.74],\n      [25.65, -18.54],\n      [25.85, -18.71],\n      [26.16, -19.29],\n      [27.3, -20.39],\n      [27.72, -20.5],\n      [27.73, -20.85],\n      [28.02, -21.49],\n      [28.79, -21.64],\n      [29.43, -22.09]\n    ],\n    [\n      [15.28, 7.42],\n      [16.11, 7.5],\n      [16.29, 7.75],\n      [16.46, 7.73],\n      [16.71, 7.51],\n      [17.96, 7.89],\n      [18.39, 8.28],\n      [18.91, 8.63],\n      [18.81, 8.98],\n      [19.09, 9.07],\n      [20.06, 9.01],\n      [21, 9.48],\n      [21.72, 10.57],\n      [22.23, 10.97],\n      [22.86, 11.14]\n    ],\n    [\n      [22.86, 11.14],\n      [22.98, 10.71],\n      [23.55, 10.09],\n      [23.56, 9.68],\n      [23.39, 9.27],\n      [23.46, 8.95],\n      [23.81, 8.67]\n    ],\n    [\n      [23.81, 8.67],\n      [24.57, 8.23]\n    ],\n    [\n      [24.57, 8.23],\n      [25.11, 7.83],\n      [25.12, 7.5],\n      [25.8, 6.98],\n      [26.21, 6.55],\n      [26.47, 5.95],\n      [27.21, 5.55],\n      [27.37, 5.23]\n    ],\n    [\n      [27.37, 5.23],\n      [27.04, 5.13],\n      [26.4, 5.15],\n      [25.65, 5.26],\n      [25.28, 5.17],\n      [25.13, 4.93],\n      [24.81, 4.9],\n      [24.41, 5.11],\n      [23.3, 4.61],\n      [22.84, 4.71],\n      [22.7, 4.63],\n      [22.41, 4.03],\n      [21.66, 4.22],\n      [20.93, 4.32],\n      [20.29, 4.69],\n      [19.47, 5.03],\n      [18.93, 4.71],\n      [18.54, 4.2],\n      [18.45, 3.5]\n    ],\n    [\n      [18.45, 3.5],\n      [17.81, 3.56],\n      [17.13, 3.73],\n      [16.54, 3.2],\n      [16.01, 2.27]\n    ],\n    [\n      [16.01, 2.27],\n      [15.91, 2.56],\n      [15.86, 3.01],\n      [15.41, 3.34],\n      [15.04, 3.85],\n      [14.95, 4.21],\n      [14.48, 4.73],\n      [14.56, 5.03],\n      [14.46, 5.45],\n      [14.54, 6.23],\n      [14.78, 6.41],\n      [15.28, 7.42]\n    ],\n    [\n      [-63.66, 46.55],\n      [-62.94, 46.42],\n      [-62.01, 46.44],\n      [-62.5, 46.03],\n      [-62.87, 45.97],\n      [-64.14, 46.39],\n      [-64.39, 46.73],\n      [-64.01, 47.04],\n      [-63.66, 46.55]\n    ],\n    [\n      [-61.81, 49.11],\n      [-62.29, 49.09],\n      [-63.59, 49.4],\n      [-64.52, 49.87],\n      [-64.17, 49.96],\n      [-62.86, 49.71],\n      [-61.84, 49.29],\n      [-61.81, 49.11]\n    ],\n    [\n      [-123.51, 48.51],\n      [-124.01, 48.37],\n      [-125.66, 48.83],\n      [-125.95, 49.18],\n      [-126.85, 49.53],\n      [-127.03, 49.81],\n      [-128.06, 49.99],\n      [-128.44, 50.54],\n      [-128.36, 50.77],\n      [-127.31, 50.55],\n      [-126.7, 50.4],\n      [-125.76, 50.3],\n      [-125.42, 49.95],\n      [-124.92, 49.48],\n      [-123.92, 49.06],\n      [-123.51, 48.51]\n    ],\n    [\n      [-56.13, 50.69],\n      [-56.8, 49.81],\n      [-56.14, 50.15],\n      [-55.47, 49.94],\n      [-55.82, 49.59],\n      [-54.94, 49.31],\n      [-54.47, 49.56],\n      [-53.48, 49.25],\n      [-53.79, 48.52],\n      [-53.09, 48.69],\n      [-52.96, 48.16],\n      [-52.65, 47.54],\n      [-53.07, 46.66],\n      [-53.52, 46.62],\n      [-54.18, 46.81],\n      [-53.96, 47.63],\n      [-54.24, 47.75],\n      [-55.4, 46.88],\n      [-56, 46.92],\n      [-55.29, 47.39],\n      [-56.25, 47.63],\n      [-57.33, 47.57],\n      [-59.27, 47.6],\n      [-59.42, 47.9],\n      [-58.8, 48.25],\n      [-59.23, 48.52],\n      [-58.39, 49.13],\n      [-57.36, 50.72],\n      [-56.74, 51.29],\n      [-55.87, 51.63],\n      [-55.41, 51.59],\n      [-55.6, 51.32],\n      [-56.13, 50.69]\n    ],\n    [\n      [-132.71, 54.04],\n      [-132.71, 54.04],\n      [-132.71, 54.04],\n      [-132.71, 54.04],\n      [-131.75, 54.12],\n      [-132.05, 52.98],\n      [-131.18, 52.18],\n      [-131.58, 52.18],\n      [-132.18, 52.64],\n      [-132.55, 53.1],\n      [-133.05, 53.41],\n      [-133.24, 53.85],\n      [-133.18, 54.17],\n      [-132.71, 54.04]\n    ],\n    [\n      [-79.27, 62.16],\n      [-79.66, 61.63],\n      [-80.1, 61.72],\n      [-80.36, 62.02],\n      [-80.32, 62.09],\n      [-79.93, 62.39],\n      [-79.52, 62.36],\n      [-79.27, 62.16]\n    ],\n    [\n      [-81.9, 62.71],\n      [-83.07, 62.16],\n      [-83.77, 62.18],\n      [-83.99, 62.45],\n      [-83.25, 62.91],\n      [-81.88, 62.9],\n      [-81.9, 62.71]\n    ],\n    [\n      [-85.16, 65.66],\n      [-84.98, 65.22],\n      [-84.46, 65.37],\n      [-83.88, 65.11],\n      [-82.79, 64.77],\n      [-81.64, 64.46],\n      [-81.55, 63.98],\n      [-80.82, 64.06],\n      [-80.1, 63.73],\n      [-80.99, 63.41],\n      [-82.55, 63.65],\n      [-83.11, 64.1],\n      [-84.1, 63.57],\n      [-85.52, 63.05],\n      [-85.87, 63.64],\n      [-87.22, 63.54],\n      [-86.35, 64.04],\n      [-86.22, 64.82],\n      [-85.88, 65.74],\n      [-85.16, 65.66]\n    ],\n    [\n      [-75.87, 67.15],\n      [-76.99, 67.1],\n      [-77.24, 67.59],\n      [-76.81, 68.15],\n      [-75.9, 68.29],\n      [-75.11, 68.01],\n      [-75.1, 67.58],\n      [-75.22, 67.44],\n      [-75.87, 67.15]\n    ],\n    [\n      [-95.65, 69.11],\n      [-96.27, 68.76],\n      [-97.62, 69.06],\n      [-98.43, 68.95],\n      [-99.8, 69.4],\n      [-98.92, 69.71],\n      [-98.22, 70.14],\n      [-97.16, 69.86],\n      [-96.56, 69.68],\n      [-96.26, 69.49],\n      [-95.65, 69.11]\n    ],\n    [\n      [-67.14, 45.14],\n      [-67.79, 45.7],\n      [-67.79, 47.07],\n      [-68.23, 47.35],\n      [-68.9, 47.19],\n      [-69.24, 47.45],\n      [-70, 46.69],\n      [-70.31, 45.92],\n      [-70.66, 45.46],\n      [-71.08, 45.31],\n      [-71.4, 45.26],\n      [-71.51, 45.01],\n      [-73.35, 45.01],\n      [-74.87, 45],\n      [-75.32, 44.82],\n      [-76.37, 44.1],\n      [-76.5, 44.02],\n      [-76.82, 43.63],\n      [-77.74, 43.63],\n      [-78.72, 43.63],\n      [-79.17, 43.47],\n      [-79.01, 43.27],\n      [-78.92, 42.97],\n      [-78.94, 42.86],\n      [-80.25, 42.37],\n      [-81.28, 42.21],\n      [-82.44, 41.68],\n      [-82.69, 41.68],\n      [-83.03, 41.83],\n      [-83.14, 41.98],\n      [-83.12, 42.08],\n      [-82.9, 42.43],\n      [-82.43, 42.98],\n      [-82.14, 43.57],\n      [-82.34, 44.44],\n      [-82.55, 45.35],\n      [-83.59, 45.82],\n      [-83.47, 45.99],\n      [-83.62, 46.12],\n      [-83.89, 46.12],\n      [-84.09, 46.28],\n      [-84.14, 46.51],\n      [-84.34, 46.41],\n      [-84.6, 46.44],\n      [-84.54, 46.54],\n      [-84.78, 46.64],\n      [-84.88, 46.9],\n      [-85.65, 47.22],\n      [-86.46, 47.55],\n      [-87.44, 47.94],\n      [-88.38, 48.3],\n      [-89.27, 48.02],\n      [-89.6, 48.01],\n      [-90.83, 48.27],\n      [-91.64, 48.14],\n      [-92.61, 48.45],\n      [-93.63, 48.61],\n      [-94.33, 48.67],\n      [-94.64, 48.84],\n      [-94.82, 49.39],\n      [-95.16, 49.38],\n      [-95.16, 49],\n      [-97.23, 49],\n      [-100.65, 49],\n      [-104.05, 49],\n      [-107.05, 49],\n      [-110.05, 49],\n      [-113, 49],\n      [-116.05, 49],\n      [-117.03, 49],\n      [-120, 49],\n      [-122.84, 49]\n    ],\n    [\n      [-122.84, 49],\n      [-122.97, 49],\n      [-124.91, 49.98],\n      [-125.62, 50.42],\n      [-127.44, 50.83],\n      [-127.99, 51.72],\n      [-127.85, 52.33],\n      [-129.13, 52.76],\n      [-129.31, 53.56],\n      [-130.51, 54.29],\n      [-130.54, 54.8],\n      [-129.98, 55.29],\n      [-130.01, 55.92]\n    ],\n    [\n      [-130.01, 55.92],\n      [-131.71, 56.55],\n      [-132.73, 57.69]\n    ],\n    [\n      [-132.73, 57.69],\n      [-133.36, 58.41],\n      [-134.27, 58.86]\n    ],\n    [\n      [-134.27, 58.86],\n      [-134.94, 59.27],\n      [-135.48, 59.79],\n      [-136.48, 59.46],\n      [-137.45, 58.91],\n      [-138.34, 59.56]\n    ],\n    [\n      [-138.34, 59.56],\n      [-139.04, 60],\n      [-140.01, 60.28],\n      [-141, 60.31],\n      [-140.99, 66],\n      [-140.99, 69.71],\n      [-139.12, 69.47],\n      [-137.55, 68.99],\n      [-136.5, 68.9],\n      [-135.63, 69.32],\n      [-134.41, 69.63],\n      [-132.93, 69.51],\n      [-131.43, 69.94],\n      [-129.79, 70.19],\n      [-129.11, 69.78],\n      [-128.36, 70.01],\n      [-128.14, 70.48],\n      [-127.45, 70.38],\n      [-125.76, 69.48],\n      [-124.42, 70.16],\n      [-124.29, 69.4],\n      [-123.06, 69.56],\n      [-122.68, 69.86],\n      [-121.47, 69.8],\n      [-119.94, 69.38],\n      [-117.6, 69.01],\n      [-116.23, 68.84],\n      [-115.25, 68.91],\n      [-113.9, 68.4],\n      [-115.3, 67.9],\n      [-113.5, 67.69],\n      [-110.8, 67.81],\n      [-109.95, 67.98],\n      [-108.88, 67.38],\n      [-107.79, 67.89],\n      [-108.81, 68.31],\n      [-108.17, 68.65],\n      [-106.95, 68.7],\n      [-106.15, 68.8],\n      [-105.34, 68.56],\n      [-104.34, 68.02],\n      [-103.22, 68.1],\n      [-101.45, 67.65],\n      [-99.9, 67.81],\n      [-98.44, 67.78],\n      [-98.56, 68.4],\n      [-97.67, 68.58],\n      [-96.12, 68.24],\n      [-96.13, 67.29],\n      [-95.49, 68.09],\n      [-94.68, 68.06],\n      [-94.23, 69.07],\n      [-95.3, 69.69],\n      [-96.47, 70.09],\n      [-96.39, 71.19],\n      [-95.21, 71.92],\n      [-93.89, 71.76],\n      [-92.88, 71.32],\n      [-91.52, 70.19],\n      [-92.41, 69.7],\n      [-90.55, 69.5],\n      [-90.55, 68.47],\n      [-89.22, 69.26],\n      [-88.02, 68.62],\n      [-88.32, 67.87],\n      [-87.35, 67.2],\n      [-86.31, 67.92],\n      [-85.58, 68.78],\n      [-85.52, 69.88],\n      [-84.1, 69.81],\n      [-82.62, 69.66],\n      [-81.28, 69.16],\n      [-81.22, 68.67],\n      [-81.96, 68.13],\n      [-81.26, 67.6],\n      [-81.39, 67.11],\n      [-83.34, 66.41],\n      [-84.74, 66.26],\n      [-85.77, 66.56],\n      [-86.07, 66.06],\n      [-87.03, 65.21],\n      [-87.32, 64.78],\n      [-88.48, 64.1],\n      [-89.91, 64.03],\n      [-90.7, 63.61],\n      [-90.77, 62.96],\n      [-91.93, 62.84],\n      [-93.16, 62.02],\n      [-94.24, 60.9],\n      [-94.63, 60.11],\n      [-94.68, 58.95],\n      [-93.22, 58.78],\n      [-92.76, 57.85],\n      [-92.3, 57.09],\n      [-90.9, 57.28],\n      [-89.04, 56.85],\n      [-88.04, 56.47],\n      [-87.32, 56],\n      [-86.07, 55.72],\n      [-85.01, 55.3],\n      [-83.36, 55.24],\n      [-82.27, 55.15],\n      [-82.44, 54.28],\n      [-82.13, 53.28],\n      [-81.4, 52.16],\n      [-79.91, 51.21],\n      [-79.14, 51.53],\n      [-78.6, 52.56],\n      [-79.12, 54.14],\n      [-79.83, 54.67],\n      [-78.23, 55.14],\n      [-77.1, 55.84],\n      [-76.54, 56.53],\n      [-76.62, 57.2],\n      [-77.3, 58.05],\n      [-78.52, 58.8],\n      [-77.34, 59.85],\n      [-77.77, 60.76],\n      [-78.11, 62.32],\n      [-77.41, 62.55],\n      [-75.7, 62.28],\n      [-74.67, 62.18],\n      [-73.84, 62.44],\n      [-72.91, 62.11],\n      [-71.68, 61.53],\n      [-71.37, 61.14],\n      [-69.59, 61.06],\n      [-69.62, 60.22],\n      [-69.29, 58.96],\n      [-68.37, 58.8],\n      [-67.65, 58.21],\n      [-66.2, 58.77],\n      [-65.25, 59.87],\n      [-64.58, 60.34],\n      [-63.8, 59.44],\n      [-62.5, 58.17],\n      [-61.4, 56.97],\n      [-61.8, 56.34],\n      [-60.47, 55.78],\n      [-59.57, 55.2],\n      [-57.98, 54.95],\n      [-57.33, 54.63],\n      [-56.94, 53.78],\n      [-56.16, 53.65],\n      [-55.76, 53.27],\n      [-55.68, 52.15],\n      [-56.41, 51.77],\n      [-57.13, 51.42],\n      [-58.77, 51.06],\n      [-60.03, 50.24],\n      [-61.72, 50.08],\n      [-63.86, 50.29],\n      [-65.36, 50.3],\n      [-66.4, 50.23],\n      [-67.24, 49.51],\n      [-68.51, 49.07],\n      [-69.95, 47.74],\n      [-71.1, 46.82],\n      [-70.26, 46.99],\n      [-68.65, 48.3],\n      [-66.55, 49.13],\n      [-65.06, 49.23],\n      [-64.17, 48.74],\n      [-65.12, 48.07],\n      [-64.8, 46.99],\n      [-64.47, 46.24],\n      [-63.17, 45.74],\n      [-61.52, 45.88],\n      [-60.52, 47.01],\n      [-60.45, 46.28],\n      [-59.8, 45.92],\n      [-61.04, 45.27],\n      [-63.25, 44.67],\n      [-64.25, 44.27],\n      [-65.36, 43.55],\n      [-66.12, 43.62],\n      [-66.16, 44.47],\n      [-64.43, 45.29],\n      [-66.03, 45.26],\n      [-67.14, 45.14]\n    ],\n    [\n      [-114.17, 73.12],\n      [-114.67, 72.65],\n      [-112.44, 72.96],\n      [-111.05, 72.45],\n      [-109.92, 72.96],\n      [-109.01, 72.63],\n      [-108.19, 71.65],\n      [-107.69, 72.07],\n      [-108.4, 73.09],\n      [-107.52, 73.24],\n      [-106.52, 73.08],\n      [-105.4, 72.67],\n      [-104.77, 71.7],\n      [-104.46, 70.99],\n      [-102.79, 70.5],\n      [-100.98, 70.02],\n      [-101.09, 69.58],\n      [-102.73, 69.5],\n      [-102.09, 69.12],\n      [-102.43, 68.75],\n      [-104.24, 68.91],\n      [-105.96, 69.18],\n      [-107.12, 69.12],\n      [-109, 68.78],\n      [-111.53, 68.63],\n      [-113.31, 68.54],\n      [-113.85, 69.01],\n      [-115.22, 69.28],\n      [-116.11, 69.17],\n      [-117.34, 69.96],\n      [-116.67, 70.07],\n      [-115.13, 70.24],\n      [-113.72, 70.19],\n      [-112.42, 70.37],\n      [-114.35, 70.6],\n      [-116.49, 70.52],\n      [-117.9, 70.54],\n      [-118.43, 70.91],\n      [-116.11, 71.31],\n      [-117.66, 71.3],\n      [-119.4, 71.56],\n      [-118.56, 72.31],\n      [-117.87, 72.71],\n      [-115.19, 73.31],\n      [-114.17, 73.12]\n    ],\n    [\n      [-104.5, 73.42],\n      [-105.38, 72.76],\n      [-106.94, 73.46],\n      [-106.6, 73.6],\n      [-105.26, 73.64],\n      [-104.5, 73.42]\n    ],\n    [\n      [-76.34, 73.1],\n      [-76.25, 72.83],\n      [-77.31, 72.86],\n      [-78.39, 72.88],\n      [-79.49, 72.74],\n      [-79.78, 72.8],\n      [-80.88, 73.33],\n      [-80.83, 73.69],\n      [-80.35, 73.76],\n      [-78.06, 73.65],\n      [-76.34, 73.1]\n    ],\n    [\n      [-86.56, 73.16],\n      [-85.77, 72.53],\n      [-84.85, 73.34],\n      [-82.32, 73.75],\n      [-80.6, 72.72],\n      [-80.75, 72.06],\n      [-78.77, 72.35],\n      [-77.82, 72.75],\n      [-75.61, 72.24],\n      [-74.23, 71.77],\n      [-74.1, 71.33],\n      [-72.24, 71.56],\n      [-71.2, 70.92],\n      [-68.79, 70.53],\n      [-67.91, 70.12],\n      [-66.97, 69.19],\n      [-68.81, 68.72],\n      [-66.45, 68.07],\n      [-64.86, 67.85],\n      [-63.42, 66.93],\n      [-61.85, 66.86],\n      [-62.16, 66.16],\n      [-63.92, 65],\n      [-65.15, 65.43],\n      [-66.72, 66.39],\n      [-68.02, 66.26],\n      [-68.14, 65.69],\n      [-67.09, 65.11],\n      [-65.73, 64.65],\n      [-65.32, 64.38],\n      [-64.67, 63.39],\n      [-65.01, 62.67],\n      [-66.28, 62.95],\n      [-68.78, 63.75],\n      [-67.37, 62.88],\n      [-66.33, 62.28],\n      [-66.17, 61.93],\n      [-68.88, 62.33],\n      [-71.02, 62.91],\n      [-72.24, 63.4],\n      [-71.89, 63.68],\n      [-73.38, 64.19],\n      [-74.83, 64.68],\n      [-74.82, 64.39],\n      [-77.71, 64.23],\n      [-78.56, 64.57],\n      [-77.9, 65.31],\n      [-76.02, 65.33],\n      [-73.96, 65.45],\n      [-74.29, 65.81],\n      [-73.94, 66.31],\n      [-72.65, 67.28],\n      [-72.93, 67.73],\n      [-73.31, 68.07],\n      [-74.84, 68.55],\n      [-76.87, 68.89],\n      [-76.23, 69.15],\n      [-77.29, 69.77],\n      [-78.17, 69.83],\n      [-78.96, 70.17],\n      [-79.49, 69.87],\n      [-81.31, 69.74],\n      [-84.94, 69.97],\n      [-87.06, 70.26],\n      [-88.68, 70.41],\n      [-89.51, 70.76],\n      [-88.47, 71.22],\n      [-89.89, 71.22],\n      [-90.21, 72.24],\n      [-89.44, 73.13],\n      [-88.41, 73.54],\n      [-85.83, 73.8],\n      [-86.56, 73.16]\n    ],\n    [\n      [-100.36, 73.84],\n      [-99.16, 73.63],\n      [-97.38, 73.76],\n      [-97.12, 73.47],\n      [-98.05, 72.99],\n      [-96.54, 72.56],\n      [-96.72, 71.66],\n      [-98.36, 71.27],\n      [-99.32, 71.36],\n      [-100.01, 71.74],\n      [-102.5, 72.51],\n      [-102.48, 72.83],\n      [-100.44, 72.71],\n      [-101.54, 73.36],\n      [-100.36, 73.84]\n    ],\n    [\n      [-93.2, 72.77],\n      [-94.27, 72.02],\n      [-95.41, 72.06],\n      [-96.03, 72.94],\n      [-96.02, 73.44],\n      [-95.5, 73.86],\n      [-94.5, 74.13],\n      [-92.42, 74.1],\n      [-90.51, 73.86],\n      [-92, 72.97],\n      [-93.2, 72.77]\n    ],\n    [\n      [-120.46, 71.38],\n      [-123.09, 70.9],\n      [-123.62, 71.34],\n      [-125.93, 71.87],\n      [-125.5, 72.29],\n      [-124.81, 73.02],\n      [-123.94, 73.68],\n      [-124.92, 74.29],\n      [-121.54, 74.45],\n      [-120.11, 74.24],\n      [-117.56, 74.19],\n      [-116.58, 73.9],\n      [-115.51, 73.48],\n      [-116.77, 73.22],\n      [-119.22, 72.52],\n      [-120.46, 71.82],\n      [-120.46, 71.38]\n    ],\n    [\n      [-93.61, 74.98],\n      [-94.16, 74.59],\n      [-95.61, 74.67],\n      [-96.82, 74.93],\n      [-96.29, 75.38],\n      [-94.85, 75.65],\n      [-93.98, 75.3],\n      [-93.61, 74.98]\n    ],\n    [\n      [-98.5, 76.72],\n      [-97.74, 76.26],\n      [-97.7, 75.74],\n      [-98.16, 75],\n      [-99.81, 74.9],\n      [-100.88, 75.06],\n      [-100.86, 75.64],\n      [-102.5, 75.56],\n      [-102.57, 76.34],\n      [-101.49, 76.31],\n      [-99.98, 76.65],\n      [-98.58, 76.59],\n      [-98.5, 76.72]\n    ],\n    [\n      [-108.21, 76.2],\n      [-107.82, 75.85],\n      [-106.93, 76.01],\n      [-105.88, 75.97],\n      [-105.7, 75.48],\n      [-106.31, 75.01],\n      [-109.7, 74.85],\n      [-112.22, 74.42],\n      [-113.74, 74.39],\n      [-113.87, 74.72],\n      [-111.79, 75.16],\n      [-116.31, 75.04],\n      [-117.71, 75.22],\n      [-116.35, 76.2],\n      [-115.4, 76.48],\n      [-112.59, 76.14],\n      [-110.81, 75.55],\n      [-109.07, 75.47],\n      [-110.5, 76.43],\n      [-109.58, 76.79],\n      [-108.55, 76.68],\n      [-108.21, 76.2]\n    ],\n    [\n      [-94.68, 77.1],\n      [-93.57, 76.78],\n      [-91.61, 76.78],\n      [-90.74, 76.45],\n      [-90.97, 76.07],\n      [-89.82, 75.85],\n      [-89.19, 75.61],\n      [-87.84, 75.57],\n      [-86.38, 75.48],\n      [-84.79, 75.7],\n      [-82.75, 75.78],\n      [-81.13, 75.71],\n      [-80.06, 75.34],\n      [-79.83, 74.92],\n      [-80.46, 74.66],\n      [-81.95, 74.44],\n      [-83.23, 74.56],\n      [-86.1, 74.41],\n      [-88.15, 74.39],\n      [-89.76, 74.52],\n      [-92.42, 74.84],\n      [-92.77, 75.39],\n      [-92.89, 75.88],\n      [-93.89, 76.32],\n      [-95.96, 76.44],\n      [-97.12, 76.75],\n      [-96.75, 77.16],\n      [-94.68, 77.1]\n    ],\n    [\n      [-116.2, 77.65],\n      [-116.34, 76.88],\n      [-117.11, 76.53],\n      [-118.04, 76.48],\n      [-119.9, 76.05],\n      [-121.5, 75.9],\n      [-122.85, 76.12],\n      [-122.85, 76.12],\n      [-121.16, 76.86],\n      [-119.1, 77.51],\n      [-117.57, 77.5],\n      [-116.2, 77.65]\n    ],\n    [\n      [-93.84, 77.52],\n      [-94.3, 77.49],\n      [-96.17, 77.56],\n      [-96.44, 77.83],\n      [-94.42, 77.82],\n      [-93.72, 77.63],\n      [-93.84, 77.52]\n    ],\n    [\n      [-110.19, 77.7],\n      [-112.05, 77.41],\n      [-113.53, 77.73],\n      [-112.72, 78.05],\n      [-111.26, 78.15],\n      [-109.85, 78],\n      [-110.19, 77.7]\n    ],\n    [\n      [-109.66, 78.6],\n      [-110.88, 78.41],\n      [-112.54, 78.41],\n      [-112.53, 78.55],\n      [-111.5, 78.85],\n      [-110.96, 78.8],\n      [-109.66, 78.6]\n    ],\n    [\n      [-95.83, 78.06],\n      [-97.31, 77.85],\n      [-98.12, 78.08],\n      [-98.55, 78.46],\n      [-98.63, 78.87],\n      [-97.34, 78.83],\n      [-96.75, 78.77],\n      [-95.56, 78.42],\n      [-95.83, 78.06]\n    ],\n    [\n      [-100.06, 78.32],\n      [-99.67, 77.91],\n      [-101.3, 78.02],\n      [-102.95, 78.34],\n      [-105.18, 78.38],\n      [-104.21, 78.68],\n      [-105.42, 78.92],\n      [-105.49, 79.3],\n      [-103.53, 79.17],\n      [-100.83, 78.8],\n      [-100.06, 78.32]\n    ],\n    [\n      [-87.02, 79.66],\n      [-85.81, 79.34],\n      [-87.19, 79.04],\n      [-89.04, 78.29],\n      [-90.8, 78.22],\n      [-92.88, 78.34],\n      [-93.95, 78.75],\n      [-93.94, 79.11],\n      [-93.15, 79.38],\n      [-94.97, 79.37],\n      [-96.08, 79.71],\n      [-96.71, 80.16],\n      [-96.02, 80.6],\n      [-95.32, 80.91],\n      [-94.3, 80.98],\n      [-94.74, 81.21],\n      [-92.41, 81.26],\n      [-91.13, 80.72],\n      [-89.45, 80.51],\n      [-87.81, 80.32],\n      [-87.02, 79.66]\n    ],\n    [\n      [-68.5, 83.11],\n      [-65.83, 83.03],\n      [-63.68, 82.9],\n      [-61.85, 82.63],\n      [-61.89, 82.36],\n      [-64.33, 81.93],\n      [-66.75, 81.73],\n      [-67.66, 81.5],\n      [-65.48, 81.51],\n      [-67.84, 80.9],\n      [-69.47, 80.62],\n      [-71.18, 79.8],\n      [-73.24, 79.63],\n      [-73.88, 79.43],\n      [-76.91, 79.32],\n      [-75.53, 79.2],\n      [-76.22, 79.02],\n      [-75.39, 78.53],\n      [-76.34, 78.18],\n      [-77.89, 77.9],\n      [-78.36, 77.51],\n      [-79.76, 77.21],\n      [-79.62, 76.98],\n      [-77.91, 77.02],\n      [-77.89, 76.78],\n      [-80.56, 76.18],\n      [-83.17, 76.45],\n      [-86.11, 76.3],\n      [-87.6, 76.42],\n      [-89.49, 76.47],\n      [-89.62, 76.95],\n      [-87.77, 77.18],\n      [-88.26, 77.9],\n      [-87.65, 77.97],\n      [-84.98, 77.54],\n      [-86.34, 78.18],\n      [-87.96, 78.37],\n      [-87.15, 78.76],\n      [-85.38, 79],\n      [-85.09, 79.35],\n      [-86.51, 79.74],\n      [-86.93, 80.25],\n      [-84.2, 80.21],\n      [-83.41, 80.1],\n      [-81.85, 80.46],\n      [-84.1, 80.58],\n      [-87.6, 80.52],\n      [-89.37, 80.86],\n      [-90.2, 81.26],\n      [-91.37, 81.55],\n      [-91.59, 81.89],\n      [-90.1, 82.09],\n      [-88.93, 82.12],\n      [-86.97, 82.28],\n      [-85.5, 82.65],\n      [-84.26, 82.6],\n      [-83.18, 82.32],\n      [-82.42, 82.86],\n      [-81.1, 83.02],\n      [-79.31, 83.13],\n      [-76.25, 83.17],\n      [-75.72, 83.06],\n      [-72.83, 83.23],\n      [-70.67, 83.17],\n      [-68.5, 83.11]\n    ],\n    [\n      [9.63, 47.36],\n      [9.53, 47.27],\n      [9.49, 47.18]\n    ],\n    [\n      [9.49, 47.18],\n      [9.51, 47.09],\n      [9.47, 47.06],\n      [9.56, 47.05]\n    ],\n    [\n      [9.56, 47.05],\n      [9.61, 47.06],\n      [9.87, 47.02]\n    ],\n    [\n      [10.44, 46.89],\n      [10.36, 46.48],\n      [9.92, 46.31],\n      [9.18, 46.44],\n      [8.97, 46.04],\n      [8.49, 46.01],\n      [8.32, 46.16],\n      [7.76, 45.82],\n      [7.27, 45.78],\n      [6.84, 45.99]\n    ],\n    [\n      [6.84, 45.99],\n      [6.5, 46.43],\n      [6.02, 46.27],\n      [6.04, 46.73],\n      [6.77, 47.29],\n      [6.74, 47.54],\n      [7.19, 47.45],\n      [7.47, 47.62]\n    ],\n    [\n      [7.47, 47.62],\n      [8.32, 47.61],\n      [8.52, 47.83],\n      [9.59, 47.53]\n    ],\n    [\n      [-66.96, -54.9],\n      [-67.29, -55.3],\n      [-68.15, -55.61],\n      [-68.64, -55.58],\n      [-69.23, -55.5],\n      [-69.96, -55.2],\n      [-71.01, -55.05],\n      [-72.26, -54.5],\n      [-73.29, -53.96],\n      [-74.66, -52.84],\n      [-73.84, -53.05],\n      [-72.43, -53.72],\n      [-71.11, -54.07],\n      [-70.59, -53.62],\n      [-70.27, -52.93],\n      [-69.35, -52.52],\n      [-68.63, -52.64]\n    ],\n    [\n      [-68.57, -52.3],\n      [-69.46, -52.29],\n      [-69.94, -52.54],\n      [-70.85, -52.9],\n      [-71.01, -53.83],\n      [-71.43, -53.86],\n      [-72.56, -53.53],\n      [-73.7, -52.84],\n      [-73.7, -52.84],\n      [-74.95, -52.26],\n      [-75.26, -51.63],\n      [-74.98, -51.04],\n      [-75.48, -50.38],\n      [-75.61, -48.67],\n      [-75.18, -47.71],\n      [-74.13, -46.94],\n      [-75.64, -46.65],\n      [-74.69, -45.76],\n      [-74.35, -44.1],\n      [-73.24, -44.45],\n      [-72.72, -42.38],\n      [-73.39, -42.12],\n      [-73.7, -43.37],\n      [-74.33, -43.22],\n      [-74.02, -41.79],\n      [-73.68, -39.94],\n      [-73.22, -39.26],\n      [-73.51, -38.28],\n      [-73.59, -37.16],\n      [-73.17, -37.12],\n      [-72.55, -35.51],\n      [-71.86, -33.91],\n      [-71.44, -32.42],\n      [-71.67, -30.92],\n      [-71.37, -30.1],\n      [-71.49, -28.86],\n      [-70.91, -27.64],\n      [-70.72, -25.71],\n      [-70.4, -23.63],\n      [-70.09, -21.39],\n      [-70.16, -19.76],\n      [-70.37, -18.35]\n    ],\n    [\n      [-70.37, -18.35],\n      [-69.86, -18.09],\n      [-69.59, -17.58]\n    ],\n    [\n      [110.34, 18.68],\n      [109.48, 18.2],\n      [108.66, 18.51],\n      [108.63, 19.37],\n      [109.12, 19.82],\n      [110.21, 20.1],\n      [110.79, 20.08],\n      [111.01, 19.7],\n      [110.57, 19.26],\n      [110.34, 18.68]\n    ],\n    [\n      [129.4, 49.44],\n      [130.58, 48.73]\n    ],\n    [\n      [130.58, 48.73],\n      [130.99, 47.79],\n      [132.51, 47.79],\n      [133.37, 48.18]\n    ],\n    [\n      [133.37, 48.18],\n      [135.03, 48.48]\n    ],\n    [\n      [135.03, 48.48],\n      [134.5, 47.58],\n      [134.11, 47.21],\n      [133.77, 46.12]\n    ],\n    [\n      [133.77, 46.12],\n      [133.1, 45.14],\n      [131.88, 45.32]\n    ],\n    [\n      [131.88, 45.32],\n      [131.03, 44.97],\n      [131.29, 44.11]\n    ],\n    [\n      [131.29, 44.11],\n      [131.14, 42.93],\n      [130.63, 42.9]\n    ],\n    [\n      [130.63, 42.9],\n      [130.64, 42.4]\n    ],\n    [\n      [130.64, 42.4],\n      [129.99, 42.99],\n      [129.6, 42.42],\n      [128.05, 41.99],\n      [128.21, 41.47],\n      [127.34, 41.5],\n      [126.87, 41.82],\n      [126.18, 41.11],\n      [125.08, 40.57],\n      [124.27, 39.93]\n    ],\n    [\n      [124.27, 39.93],\n      [122.87, 39.64],\n      [122.13, 39.17],\n      [121.05, 38.9],\n      [121.59, 39.36],\n      [121.38, 39.75],\n      [122.17, 40.42],\n      [121.64, 40.95],\n      [120.77, 40.59],\n      [119.64, 39.9],\n      [119.02, 39.25],\n      [118.04, 39.2],\n      [117.53, 38.74],\n      [118.06, 38.06],\n      [118.88, 37.9],\n      [118.91, 37.45],\n      [119.7, 37.16],\n      [120.82, 37.87],\n      [121.71, 37.48],\n      [122.36, 37.45],\n      [122.52, 36.93],\n      [121.1, 36.65],\n      [120.64, 36.11],\n      [119.66, 35.61],\n      [119.15, 34.91],\n      [120.23, 34.36],\n      [120.62, 33.38],\n      [121.23, 32.46],\n      [121.91, 31.69],\n      [121.89, 30.95],\n      [121.26, 30.68],\n      [121.5, 30.14],\n      [122.09, 29.83],\n      [121.94, 29.02],\n      [121.68, 28.23],\n      [121.13, 28.14],\n      [120.4, 27.05],\n      [119.59, 25.74],\n      [118.66, 24.55],\n      [117.28, 23.62],\n      [115.89, 22.78],\n      [114.76, 22.67],\n      [114.15, 22.22],\n      [113.81, 22.55],\n      [113.24, 22.05],\n      [111.84, 21.55],\n      [110.79, 21.4],\n      [110.44, 20.34],\n      [109.89, 20.28],\n      [109.63, 21.01],\n      [109.86, 21.4],\n      [108.52, 21.72],\n      [108.05, 21.55]\n    ],\n    [\n      [108.05, 21.55],\n      [107.04, 21.81],\n      [106.57, 22.22],\n      [106.73, 22.79],\n      [105.81, 22.98],\n      [105.33, 23.35],\n      [104.48, 22.82],\n      [103.5, 22.7],\n      [102.71, 22.71],\n      [102.17, 22.46]\n    ],\n    [\n      [102.17, 22.46],\n      [101.65, 22.32],\n      [101.8, 21.17],\n      [101.27, 21.2],\n      [101.18, 21.44]\n    ],\n    [\n      [101.18, 21.44],\n      [101.15, 21.85],\n      [100.42, 21.56],\n      [99.98, 21.74],\n      [99.24, 22.12],\n      [99.53, 22.95],\n      [98.9, 23.14],\n      [98.66, 24.06],\n      [97.6, 23.9],\n      [97.72, 25.08],\n      [98.67, 25.92],\n      [98.71, 26.74],\n      [98.68, 27.51],\n      [98.25, 27.75],\n      [97.91, 28.34],\n      [97.33, 28.26]\n    ],\n    [\n      [97.33, 28.26],\n      [96.25, 28.41],\n      [96.59, 28.83],\n      [96.12, 29.45],\n      [95.4, 29.03],\n      [94.57, 29.28],\n      [93.41, 28.64],\n      [92.5, 27.9],\n      [91.7, 27.77]\n    ],\n    [\n      [88.81, 27.3],\n      [88.73, 28.09],\n      [88.12, 27.88]\n    ],\n    [\n      [88.12, 27.88],\n      [86.95, 27.97],\n      [85.82, 28.2],\n      [85.01, 28.64],\n      [84.23, 28.84],\n      [83.9, 29.32],\n      [83.34, 29.46],\n      [82.33, 30.12],\n      [81.53, 30.42],\n      [81.11, 30.18]\n    ],\n    [\n      [81.11, 30.18],\n      [79.72, 30.88],\n      [78.74, 31.52],\n      [78.46, 32.62],\n      [79.18, 32.48],\n      [79.21, 32.99],\n      [78.81, 33.51],\n      [78.91, 34.32],\n      [77.84, 35.49]\n    ],\n    [\n      [77.84, 35.49],\n      [76.19, 35.9],\n      [75.9, 36.67],\n      [75.16, 37.13]\n    ],\n    [\n      [74.98, 37.42],\n      [74.83, 37.99],\n      [74.86, 38.38],\n      [74.26, 38.61],\n      [73.93, 38.51],\n      [73.68, 39.43]\n    ],\n    [\n      [73.68, 39.43],\n      [73.96, 39.66],\n      [73.82, 39.89],\n      [74.78, 40.37],\n      [75.47, 40.56],\n      [76.53, 40.43],\n      [76.9, 41.07],\n      [78.19, 41.19],\n      [78.54, 41.58],\n      [80.12, 42.12],\n      [80.26, 42.35]\n    ],\n    [\n      [80.26, 42.35],\n      [80.18, 42.92],\n      [80.87, 43.18],\n      [79.97, 44.92],\n      [81.95, 45.32],\n      [82.46, 45.54],\n      [83.18, 47.33],\n      [85.16, 47],\n      [85.72, 47.45],\n      [85.77, 48.46],\n      [86.6, 48.55],\n      [87.36, 49.21]\n    ],\n    [\n      [87.36, 49.21],\n      [87.75, 49.3]\n    ],\n    [\n      [87.75, 49.3],\n      [88.01, 48.6],\n      [88.85, 48.07],\n      [90.28, 47.69],\n      [90.97, 46.89],\n      [90.59, 45.72],\n      [90.95, 45.29],\n      [92.13, 45.12],\n      [93.48, 44.98],\n      [94.69, 44.35],\n      [95.31, 44.24],\n      [95.76, 43.32],\n      [96.35, 42.73],\n      [97.45, 42.75],\n      [99.52, 42.52],\n      [100.85, 42.66],\n      [101.83, 42.51],\n      [103.31, 41.91],\n      [104.52, 41.91],\n      [104.96, 41.6],\n      [106.13, 42.13],\n      [107.74, 42.48],\n      [109.24, 42.52],\n      [110.41, 42.87],\n      [111.13, 43.41],\n      [111.83, 43.74],\n      [111.67, 44.07],\n      [111.35, 44.46],\n      [111.87, 45.1],\n      [112.44, 45.01],\n      [113.46, 44.81],\n      [114.46, 45.34],\n      [115.99, 45.73],\n      [116.72, 46.39],\n      [117.42, 46.67],\n      [118.87, 46.81],\n      [119.66, 46.69],\n      [119.77, 47.05],\n      [118.87, 47.75],\n      [118.06, 48.07],\n      [117.3, 47.7],\n      [116.31, 47.85],\n      [115.74, 47.73],\n      [115.49, 48.14],\n      [116.19, 49.13],\n      [116.68, 49.89]\n    ],\n    [\n      [116.68, 49.89],\n      [117.88, 49.51],\n      [119.29, 50.14]\n    ],\n    [\n      [119.29, 50.14],\n      [119.28, 50.58],\n      [120.18, 51.64],\n      [120.74, 51.96],\n      [120.73, 52.52]\n    ],\n    [\n      [120.73, 52.52],\n      [120.18, 52.75],\n      [121, 53.25],\n      [122.25, 53.43]\n    ],\n    [\n      [122.25, 53.43],\n      [123.57, 53.46],\n      [125.07, 53.16]\n    ],\n    [\n      [125.07, 53.16],\n      [125.95, 52.79],\n      [126.56, 51.78],\n      [126.94, 51.35],\n      [127.29, 50.74]\n    ],\n    [\n      [127.29, 50.74],\n      [127.66, 49.76],\n      [129.4, 49.44]\n    ],\n    [\n      [-2.86, 4.99],\n      [-3.31, 4.98],\n      [-4.01, 5.18],\n      [-4.65, 5.17],\n      [-5.83, 4.99],\n      [-6.53, 4.71],\n      [-7.52, 4.34],\n      [-7.71, 4.36]\n    ],\n    [\n      [-7.71, 4.36],\n      [-7.64, 5.19],\n      [-7.54, 5.31],\n      [-7.57, 5.71],\n      [-7.99, 6.13],\n      [-8.31, 6.19],\n      [-8.6, 6.47],\n      [-8.39, 6.91],\n      [-8.49, 7.4],\n      [-8.44, 7.69]\n    ],\n    [\n      [-8.44, 7.69],\n      [-8.28, 7.69],\n      [-8.22, 8.12],\n      [-8.3, 8.32],\n      [-8.2, 8.46],\n      [-7.83, 8.58],\n      [-8.08, 9.38],\n      [-8.31, 9.79],\n      [-8.23, 10.13],\n      [-8.03, 10.21]\n    ],\n    [\n      [-8.03, 10.21],\n      [-7.9, 10.3],\n      [-7.62, 10.15],\n      [-6.85, 10.14],\n      [-6.67, 10.43],\n      [-6.49, 10.41],\n      [-6.21, 10.52],\n      [-6.05, 10.1],\n      [-5.82, 10.22],\n      [-5.4, 10.37]\n    ],\n    [\n      [-2.83, 9.64],\n      [-2.56, 8.22],\n      [-2.98, 7.38],\n      [-3.24, 6.25],\n      [-2.81, 5.39],\n      [-2.86, 4.99]\n    ],\n    [\n      [13.08, 2.27],\n      [12.95, 2.32],\n      [12.36, 2.19],\n      [11.75, 2.33],\n      [11.28, 2.26]\n    ],\n    [\n      [11.28, 2.26],\n      [9.65, 2.28]\n    ],\n    [\n      [9.65, 2.28],\n      [9.8, 3.07],\n      [9.4, 3.73],\n      [8.95, 3.9],\n      [8.74, 4.35],\n      [8.49, 4.5],\n      [8.5, 4.77]\n    ],\n    [\n      [8.5, 4.77],\n      [8.76, 5.48],\n      [9.23, 6.44],\n      [9.52, 6.45],\n      [10.12, 7.04],\n      [10.5, 7.06],\n      [11.06, 6.64],\n      [11.75, 6.98],\n      [11.84, 7.4],\n      [12.06, 7.8],\n      [12.22, 8.31],\n      [12.75, 8.72],\n      [12.96, 9.42],\n      [13.17, 9.64],\n      [13.31, 10.16],\n      [13.57, 10.8],\n      [14.42, 11.57],\n      [14.47, 11.9],\n      [14.58, 12.09],\n      [14.18, 12.48]\n    ],\n    [\n      [14.18, 12.48],\n      [14.21, 12.8],\n      [14.5, 12.86]\n    ],\n    [\n      [14.5, 12.86],\n      [14.89, 12.22],\n      [14.96, 11.56]\n    ],\n    [\n      [14.96, 11.56],\n      [14.92, 10.89],\n      [15.47, 9.98],\n      [14.91, 9.99],\n      [14.63, 9.92],\n      [14.17, 10.02],\n      [13.95, 9.55],\n      [14.54, 8.97],\n      [14.98, 8.8],\n      [15.12, 8.38],\n      [15.44, 7.69],\n      [15.28, 7.42]\n    ],\n    [\n      [16.01, 2.27],\n      [15.94, 1.73],\n      [15.15, 1.96],\n      [14.34, 2.23],\n      [13.08, 2.27]\n    ],\n    [\n      [31.17, 2.2],\n      [30.85, 1.85],\n      [30.47, 1.58],\n      [30.09, 1.06],\n      [29.88, 0.6]\n    ],\n    [\n      [29.88, 0.6],\n      [29.82, -0.21],\n      [29.59, -0.59]\n    ],\n    [\n      [29.59, -0.59],\n      [29.58, -1.34]\n    ],\n    [\n      [29.58, -1.34],\n      [29.29, -1.62],\n      [29.25, -2.22],\n      [29.12, -2.29],\n      [29.02, -2.84]\n    ],\n    [\n      [29.34, -4.5],\n      [29.52, -5.42],\n      [29.42, -5.94]\n    ],\n    [\n      [29.42, -5.94],\n      [29.62, -6.52],\n      [30.2, -7.08],\n      [30.74, -8.34]\n    ],\n    [\n      [30.74, -8.34],\n      [30.35, -8.24],\n      [29, -8.41],\n      [28.73, -8.53],\n      [28.45, -9.16],\n      [28.67, -9.61],\n      [28.5, -10.79],\n      [28.37, -11.79],\n      [28.64, -11.97],\n      [29.34, -12.36],\n      [29.62, -12.18],\n      [29.7, -13.26],\n      [28.93, -13.25],\n      [28.52, -12.7],\n      [28.16, -12.27],\n      [27.39, -12.13],\n      [27.16, -11.61],\n      [26.55, -11.92],\n      [25.75, -11.78],\n      [25.42, -11.33],\n      [24.78, -11.24],\n      [24.31, -11.26],\n      [24.26, -10.95],\n      [23.91, -10.93]\n    ],\n    [\n      [12.32, -6.1],\n      [12.18, -5.79]\n    ],\n    [\n      [13, -4.78],\n      [13.26, -4.88],\n      [13.6, -4.5],\n      [14.14, -4.51],\n      [14.21, -4.79],\n      [14.58, -4.97],\n      [15.17, -4.34],\n      [15.75, -3.86],\n      [16.01, -3.54],\n      [15.97, -2.71],\n      [16.41, -1.74],\n      [16.87, -1.23],\n      [17.52, -0.74],\n      [17.64, -0.42],\n      [17.66, -0.06],\n      [17.83, 0.29],\n      [17.77, 0.86],\n      [17.9, 1.74],\n      [18.09, 2.37],\n      [18.39, 2.9],\n      [18.45, 3.5]\n    ],\n    [\n      [27.37, 5.23],\n      [27.98, 4.41],\n      [28.43, 4.29],\n      [28.7, 4.46],\n      [29.16, 4.39],\n      [29.72, 4.6]\n    ],\n    [\n      [29.72, 4.6],\n      [29.95, 4.17],\n      [30.83, 3.51],\n      [30.77, 2.34],\n      [31.17, 2.2]\n    ],\n    [\n      [11.91, -5.04],\n      [11.09, -3.98]\n    ],\n    [\n      [11.09, -3.98],\n      [11.86, -3.43],\n      [11.48, -2.77],\n      [11.82, -2.51],\n      [12.5, -2.39],\n      [12.58, -1.95],\n      [13.11, -2.43],\n      [13.99, -2.47],\n      [14.3, -2],\n      [14.43, -1.33],\n      [14.32, -0.55],\n      [13.84, 0.04],\n      [14.28, 1.2],\n      [14.03, 1.4],\n      [13.28, 1.31],\n      [13, 1.83],\n      [13.08, 2.27]\n    ],\n    [\n      [-75.37, -0.15],\n      [-75.8, 0.08],\n      [-76.29, 0.42],\n      [-76.58, 0.26],\n      [-77.42, 0.4],\n      [-77.67, 0.83],\n      [-77.86, 0.81],\n      [-78.86, 1.38]\n    ],\n    [\n      [-78.86, 1.38],\n      [-78.99, 1.69],\n      [-78.62, 1.77],\n      [-78.66, 2.27],\n      [-78.43, 2.63],\n      [-77.93, 2.7],\n      [-77.51, 3.33],\n      [-77.13, 3.85],\n      [-77.5, 4.09],\n      [-77.31, 4.67],\n      [-77.53, 5.58],\n      [-77.32, 5.85],\n      [-77.48, 6.69],\n      [-77.88, 7.22]\n    ],\n    [\n      [-77.88, 7.22],\n      [-77.75, 7.71],\n      [-77.43, 7.64],\n      [-77.24, 7.94],\n      [-77.47, 8.52],\n      [-77.35, 8.67]\n    ],\n    [\n      [-77.35, 8.67],\n      [-76.84, 8.64],\n      [-76.09, 9.34],\n      [-75.67, 9.44],\n      [-75.66, 9.77],\n      [-75.48, 10.62],\n      [-74.91, 11.08],\n      [-74.28, 11.1],\n      [-74.2, 11.31],\n      [-73.41, 11.23],\n      [-72.63, 11.73],\n      [-72.24, 11.96],\n      [-71.75, 12.44],\n      [-71.4, 12.38],\n      [-71.14, 12.11],\n      [-71.33, 11.78]\n    ],\n    [\n      [-71.33, 11.78],\n      [-71.97, 11.61],\n      [-72.23, 11.11],\n      [-72.61, 10.82],\n      [-72.91, 10.45],\n      [-73.03, 9.74],\n      [-73.3, 9.15],\n      [-72.79, 9.09],\n      [-72.66, 8.63],\n      [-72.44, 8.41],\n      [-72.36, 8],\n      [-72.48, 7.63],\n      [-72.44, 7.42],\n      [-72.2, 7.34],\n      [-71.96, 6.99],\n      [-70.67, 7.09],\n      [-70.09, 6.96],\n      [-69.39, 6.1],\n      [-68.99, 6.21],\n      [-68.27, 6.15],\n      [-67.7, 6.27],\n      [-67.34, 6.1],\n      [-67.52, 5.56],\n      [-67.74, 5.22],\n      [-67.82, 4.5],\n      [-67.62, 3.84],\n      [-67.34, 3.54],\n      [-67.3, 3.32],\n      [-67.81, 2.82],\n      [-67.45, 2.6],\n      [-67.18, 2.25],\n      [-66.88, 1.25]\n    ],\n    [\n      [-69.89, -4.3],\n      [-70.39, -3.77],\n      [-70.69, -3.74],\n      [-70.05, -2.73],\n      [-70.81, -2.26],\n      [-71.41, -2.34],\n      [-71.77, -2.17],\n      [-72.33, -2.43],\n      [-73.07, -2.31],\n      [-73.66, -1.26],\n      [-74.12, -1],\n      [-74.44, -0.53],\n      [-75.11, -0.06],\n      [-75.37, -0.15]\n    ],\n    [\n      [-82.97, 8.23],\n      [-83.51, 8.45],\n      [-83.71, 8.66],\n      [-83.6, 8.83],\n      [-83.63, 9.05],\n      [-83.91, 9.29],\n      [-84.3, 9.49],\n      [-84.65, 9.62],\n      [-84.71, 9.91],\n      [-84.98, 10.09],\n      [-84.91, 9.8],\n      [-85.11, 9.56],\n      [-85.34, 9.83],\n      [-85.66, 9.93],\n      [-85.8, 10.13],\n      [-85.79, 10.44],\n      [-85.66, 10.75],\n      [-85.94, 10.9],\n      [-85.71, 11.09]\n    ],\n    [\n      [-85.71, 11.09],\n      [-85.56, 11.22],\n      [-84.9, 10.95],\n      [-84.67, 11.08],\n      [-84.36, 11],\n      [-84.19, 10.79],\n      [-83.9, 10.73],\n      [-83.66, 10.94]\n    ],\n    [\n      [-83.66, 10.94],\n      [-83.4, 10.4],\n      [-83.02, 9.99],\n      [-82.55, 9.57]\n    ],\n    [\n      [-82.55, 9.57],\n      [-82.93, 9.48],\n      [-82.93, 9.07],\n      [-82.72, 8.93],\n      [-82.87, 8.81],\n      [-82.83, 8.63],\n      [-82.91, 8.42],\n      [-82.97, 8.23]\n    ],\n    [\n      [-82.27, 23.19],\n      [-81.4, 23.12],\n      [-80.62, 23.11],\n      [-79.68, 22.77],\n      [-79.28, 22.4],\n      [-78.35, 22.51],\n      [-77.99, 22.28],\n      [-77.15, 21.66],\n      [-76.52, 21.21],\n      [-76.19, 21.22],\n      [-75.6, 21.02],\n      [-75.67, 20.74],\n      [-74.93, 20.69],\n      [-74.18, 20.28],\n      [-74.3, 20.05],\n      [-74.96, 19.92],\n      [-75.63, 19.87],\n      [-76.32, 19.95],\n      [-77.76, 19.86],\n      [-77.09, 20.41],\n      [-77.49, 20.67],\n      [-78.14, 20.74],\n      [-78.48, 21.03],\n      [-78.72, 21.6],\n      [-79.28, 21.56],\n      [-80.22, 21.83],\n      [-80.52, 22.04],\n      [-81.82, 22.19],\n      [-82.17, 22.39],\n      [-81.8, 22.64],\n      [-82.78, 22.69],\n      [-83.49, 22.17],\n      [-83.91, 22.15],\n      [-84.05, 21.91],\n      [-84.55, 21.8],\n      [-84.97, 21.9],\n      [-84.45, 22.2],\n      [-84.23, 22.57],\n      [-83.78, 22.79],\n      [-83.27, 22.98],\n      [-82.51, 23.08],\n      [-82.27, 23.19]\n    ],\n    [\n      [32.73, 35.14],\n      [32.8, 35.15],\n      [32.95, 35.39],\n      [33.67, 35.37],\n      [34.58, 35.67],\n      [33.9, 35.25],\n      [33.97, 35.06],\n      [34, 34.98],\n      [32.98, 34.57],\n      [32.49, 34.7],\n      [32.26, 35.1],\n      [32.73, 35.14]\n    ],\n    [\n      [13.6, 48.88],\n      [13.03, 49.31],\n      [12.52, 49.55],\n      [12.42, 49.97],\n      [12.24, 50.27],\n      [12.97, 50.48],\n      [13.34, 50.73],\n      [14.06, 50.93],\n      [14.31, 51.12],\n      [14.57, 51],\n      [15.02, 51.11]\n    ],\n    [\n      [15.02, 51.11],\n      [15.49, 50.78],\n      [16.24, 50.7],\n      [16.18, 50.42],\n      [16.72, 50.22],\n      [16.87, 50.47],\n      [17.55, 50.36],\n      [17.65, 50.05],\n      [18.39, 49.99],\n      [18.85, 49.5]\n    ],\n    [\n      [18.85, 49.5],\n      [18.55, 49.5],\n      [18.4, 49.32],\n      [18.17, 49.27],\n      [18.1, 49.04],\n      [17.91, 49],\n      [17.89, 48.9],\n      [17.55, 48.8],\n      [17.1, 48.82],\n      [16.96, 48.6]\n    ],\n    [\n      [9.92, 54.98],\n      [9.94, 54.6],\n      [10.95, 54.36],\n      [10.94, 54.01],\n      [11.96, 54.2],\n      [12.52, 54.47],\n      [13.65, 54.08],\n      [14.12, 53.76]\n    ],\n    [\n      [14.12, 53.76],\n      [14.35, 53.25],\n      [14.07, 52.98],\n      [14.44, 52.62],\n      [14.69, 52.09],\n      [14.61, 51.75],\n      [15.02, 51.11]\n    ],\n    [\n      [7.47, 47.62],\n      [7.59, 48.33],\n      [8.1, 49.02],\n      [6.66, 49.2],\n      [6.19, 49.46]\n    ],\n    [\n      [6.19, 49.46],\n      [6.24, 49.9],\n      [6.04, 50.13]\n    ],\n    [\n      [6.16, 50.8],\n      [5.99, 51.85],\n      [6.59, 51.85],\n      [6.84, 52.23],\n      [7.09, 53.14],\n      [6.91, 53.48]\n    ],\n    [\n      [6.91, 53.48],\n      [7.1, 53.69],\n      [7.94, 53.75],\n      [8.12, 53.53],\n      [8.8, 54.02],\n      [8.57, 54.4],\n      [8.53, 54.96]\n    ],\n    [\n      [8.53, 54.96],\n      [9.28, 54.83],\n      [9.92, 54.98]\n    ],\n    [\n      [43.08, 12.7],\n      [43.32, 12.39],\n      [43.29, 11.97],\n      [42.72, 11.74],\n      [43.15, 11.46]\n    ],\n    [\n      [43.15, 11.46],\n      [42.78, 10.93]\n    ],\n    [\n      [42.78, 10.93],\n      [42.55, 11.11],\n      [42.31, 11.03],\n      [41.76, 11.05],\n      [41.74, 11.36],\n      [41.66, 11.63],\n      [42, 12.1],\n      [42.35, 12.54]\n    ],\n    [\n      [42.35, 12.54],\n      [42.78, 12.46],\n      [43.08, 12.7]\n    ],\n    [\n      [12.69, 55.61],\n      [12.09, 54.8],\n      [11.04, 55.36],\n      [10.9, 55.78],\n      [12.37, 56.11],\n      [12.69, 55.61]\n    ],\n    [\n      [8.53, 54.96],\n      [8.12, 55.52],\n      [8.09, 56.54],\n      [8.26, 56.81],\n      [8.54, 57.11],\n      [9.42, 57.17],\n      [9.78, 57.45],\n      [10.58, 57.73],\n      [10.55, 57.22],\n      [10.25, 56.89],\n      [10.37, 56.61],\n      [10.91, 56.46],\n      [10.67, 56.08],\n      [10.37, 56.19],\n      [9.65, 55.47],\n      [9.92, 54.98]\n    ],\n    [\n      [-46.76, 82.63],\n      [-43.41, 83.23],\n      [-39.9, 83.18],\n      [-38.62, 83.55],\n      [-35.09, 83.65],\n      [-27.1, 83.52],\n      [-20.85, 82.73],\n      [-22.69, 82.34],\n      [-26.52, 82.3],\n      [-31.9, 82.2],\n      [-31.4, 82.02],\n      [-27.86, 82.13],\n      [-24.84, 81.79],\n      [-22.9, 82.09],\n      [-22.07, 81.73],\n      [-23.17, 81.15],\n      [-20.62, 81.52],\n      [-15.77, 81.91],\n      [-12.77, 81.72],\n      [-12.21, 81.29],\n      [-16.29, 80.58],\n      [-16.85, 80.35],\n      [-20.05, 80.18],\n      [-17.73, 80.13],\n      [-18.9, 79.4],\n      [-19.7, 78.75],\n      [-19.67, 77.64],\n      [-18.47, 76.99],\n      [-20.04, 76.94],\n      [-21.68, 76.63],\n      [-19.83, 76.1],\n      [-19.6, 75.25],\n      [-20.67, 75.16],\n      [-19.37, 74.3],\n      [-21.59, 74.22],\n      [-20.43, 73.82],\n      [-20.76, 73.46],\n      [-22.17, 73.31],\n      [-23.57, 73.31],\n      [-22.31, 72.63],\n      [-22.3, 72.18],\n      [-24.28, 72.6],\n      [-24.79, 72.33],\n      [-23.44, 72.08],\n      [-22.13, 71.47],\n      [-21.75, 70.66],\n      [-23.54, 70.47],\n      [-24.31, 70.86],\n      [-25.54, 71.43],\n      [-25.2, 70.75],\n      [-26.36, 70.23],\n      [-23.73, 70.18],\n      [-22.35, 70.13],\n      [-25.03, 69.26],\n      [-27.75, 68.47],\n      [-30.67, 68.13],\n      [-31.78, 68.12],\n      [-32.81, 67.74],\n      [-34.2, 66.68],\n      [-36.35, 65.98],\n      [-37.04, 65.94],\n      [-38.38, 65.69],\n      [-39.81, 65.46],\n      [-40.67, 64.84],\n      [-40.68, 64.14],\n      [-41.19, 63.48],\n      [-42.82, 62.68],\n      [-42.42, 61.9],\n      [-42.87, 61.07],\n      [-43.38, 60.1],\n      [-44.79, 60.04],\n      [-46.26, 60.85],\n      [-48.26, 60.86],\n      [-49.23, 61.41],\n      [-49.9, 62.38],\n      [-51.63, 63.63],\n      [-52.14, 64.28],\n      [-52.28, 65.18],\n      [-53.66, 66.1],\n      [-53.3, 66.84],\n      [-53.97, 67.19],\n      [-52.98, 68.36],\n      [-51.48, 68.73],\n      [-51.08, 69.15],\n      [-50.87, 69.93],\n      [-52.01, 69.57],\n      [-52.56, 69.43],\n      [-53.46, 69.28],\n      [-54.68, 69.61],\n      [-54.75, 70.29],\n      [-54.36, 70.82],\n      [-53.43, 70.84],\n      [-51.39, 70.57],\n      [-53.11, 71.2],\n      [-54, 71.55],\n      [-55, 71.41],\n      [-55.83, 71.65],\n      [-54.72, 72.59],\n      [-55.33, 72.96],\n      [-56.12, 73.65],\n      [-57.32, 74.71],\n      [-58.6, 75.1],\n      [-58.59, 75.52],\n      [-61.27, 76.1],\n      [-63.39, 76.18],\n      [-66.06, 76.13],\n      [-68.5, 76.06],\n      [-69.66, 76.38],\n      [-71.4, 77.01],\n      [-68.78, 77.32],\n      [-66.76, 77.38],\n      [-71.04, 77.64],\n      [-73.3, 78.04],\n      [-73.16, 78.43],\n      [-69.37, 78.91],\n      [-65.71, 79.39],\n      [-65.32, 79.76],\n      [-68.02, 80.12],\n      [-67.15, 80.52],\n      [-63.69, 81.21],\n      [-62.23, 81.32],\n      [-62.65, 81.77],\n      [-60.28, 82.03],\n      [-57.21, 82.19],\n      [-54.13, 82.2],\n      [-53.04, 81.89],\n      [-50.39, 82.44],\n      [-48, 82.06],\n      [-46.6, 81.99],\n      [-44.52, 81.66],\n      [-46.9, 82.2],\n      [-46.76, 82.63]\n    ],\n    [\n      [-71.71, 19.71],\n      [-71.59, 19.88],\n      [-70.81, 19.88],\n      [-70.21, 19.62],\n      [-69.95, 19.65],\n      [-69.77, 19.29],\n      [-69.22, 19.31],\n      [-69.25, 19.02],\n      [-68.81, 18.98],\n      [-68.32, 18.61],\n      [-68.69, 18.21],\n      [-69.16, 18.42],\n      [-69.62, 18.38],\n      [-69.95, 18.43],\n      [-70.13, 18.25],\n      [-70.52, 18.18],\n      [-70.67, 18.43],\n      [-71, 18.28],\n      [-71.4, 17.6],\n      [-71.66, 17.76],\n      [-71.71, 18.04]\n    ],\n    [\n      [-71.71, 18.04],\n      [-71.69, 18.32],\n      [-71.95, 18.62],\n      [-71.7, 18.79],\n      [-71.62, 19.17],\n      [-71.71, 19.71]\n    ],\n    [\n      [12, 23.47],\n      [8.57, 21.57],\n      [5.68, 19.6],\n      [4.27, 19.16]\n    ],\n    [\n      [4.27, 19.16],\n      [3.16, 19.06],\n      [3.15, 19.69],\n      [2.68, 19.86],\n      [2.06, 20.14],\n      [1.82, 20.61],\n      [-1.55, 22.79],\n      [-4.92, 24.97]\n    ],\n    [\n      [-4.92, 24.97],\n      [-8.68, 27.4]\n    ],\n    [\n      [-8.68, 27.4],\n      [-8.67, 27.59],\n      [-8.67, 27.6]\n    ],\n    [\n      [-8.67, 27.6],\n      [-8.67, 28.84],\n      [-7.06, 29.58],\n      [-6.06, 29.73],\n      [-5.24, 30],\n      [-4.86, 30.5],\n      [-3.69, 30.9],\n      [-3.65, 31.64],\n      [-3.07, 31.72],\n      [-2.62, 32.09],\n      [-1.31, 32.26],\n      [-1.12, 32.65],\n      [-1.39, 32.86],\n      [-1.73, 33.92],\n      [-1.79, 34.53],\n      [-2.17, 35.17]\n    ],\n    [\n      [-2.17, 35.17],\n      [-1.21, 35.71],\n      [-0.13, 35.89],\n      [0.5, 36.3],\n      [1.47, 36.61],\n      [3.16, 36.78],\n      [4.82, 36.87],\n      [5.32, 36.72],\n      [6.26, 37.11],\n      [7.33, 37.12],\n      [7.74, 36.89],\n      [8.42, 36.95]\n    ],\n    [\n      [8.42, 36.95],\n      [8.22, 36.43],\n      [8.38, 35.48],\n      [8.14, 34.66],\n      [7.52, 34.1],\n      [7.61, 33.34],\n      [8.43, 32.75],\n      [8.44, 32.51],\n      [9.06, 32.1],\n      [9.48, 30.31]\n    ],\n    [\n      [9.48, 30.31],\n      [9.81, 29.42],\n      [9.86, 28.96],\n      [9.68, 28.14],\n      [9.76, 27.69],\n      [9.63, 27.14],\n      [9.72, 26.51],\n      [9.32, 26.09],\n      [9.91, 25.37],\n      [9.95, 24.94],\n      [10.3, 24.38],\n      [10.77, 24.56],\n      [11.56, 24.1],\n      [12, 23.47]\n    ],\n    [\n      [-80.3, -3.4],\n      [-79.77, -2.66],\n      [-79.99, -2.22],\n      [-80.37, -2.69],\n      [-80.97, -2.25],\n      [-80.76, -1.97],\n      [-80.93, -1.06],\n      [-80.58, -0.91],\n      [-80.4, -0.28],\n      [-80.02, 0.36],\n      [-80.09, 0.77],\n      [-79.54, 0.98],\n      [-78.86, 1.38]\n    ],\n    [\n      [-75.37, -0.15],\n      [-75.23, -0.91],\n      [-75.54, -1.56],\n      [-76.64, -2.61],\n      [-77.84, -3],\n      [-78.45, -3.87],\n      [-78.64, -4.55],\n      [-79.21, -4.96],\n      [-79.62, -4.45],\n      [-80.03, -4.35],\n      [-80.44, -4.43],\n      [-80.47, -4.06],\n      [-80.18, -3.82],\n      [-80.3, -3.4]\n    ],\n    [\n      [36.87, 22],\n      [32.9, 22],\n      [29.02, 22],\n      [25, 22]\n    ],\n    [\n      [25, 22],\n      [25, 25.68],\n      [25, 29.24],\n      [24.7, 30.04],\n      [24.96, 30.66],\n      [24.8, 31.09],\n      [25.16, 31.57]\n    ],\n    [\n      [25.16, 31.57],\n      [26.5, 31.59],\n      [27.46, 31.32],\n      [28.45, 31.03],\n      [28.91, 30.87],\n      [29.68, 31.19],\n      [30.1, 31.47],\n      [30.98, 31.56],\n      [31.69, 31.43],\n      [31.96, 30.93],\n      [32.19, 31.26],\n      [32.99, 31.02],\n      [33.77, 30.97],\n      [34.27, 31.22],\n      [34.92, 29.5],\n      [34.64, 29.1],\n      [34.43, 28.34],\n      [34.15, 27.82],\n      [33.92, 27.65],\n      [33.59, 27.97],\n      [33.14, 28.42],\n      [32.42, 29.85],\n      [32.32, 29.76],\n      [32.73, 28.71],\n      [33.35, 27.7],\n      [34.1, 26.14],\n      [34.47, 25.6],\n      [34.8, 25.03],\n      [35.69, 23.93],\n      [35.49, 23.75],\n      [35.53, 23.1],\n      [36.69, 22.2],\n      [36.87, 22]\n    ],\n    [\n      [42.35, 12.54],\n      [42.01, 12.87],\n      [41.6, 13.45]\n    ],\n    [\n      [41.6, 13.45],\n      [41.16, 13.77],\n      [40.9, 14.12]\n    ],\n    [\n      [40.9, 14.12],\n      [40.03, 14.52],\n      [39.34, 14.53]\n    ],\n    [\n      [39.34, 14.53],\n      [39.1, 14.74],\n      [38.51, 14.51],\n      [37.91, 14.96],\n      [37.59, 14.21],\n      [36.43, 14.42]\n    ],\n    [\n      [36.43, 14.42],\n      [36.32, 14.82],\n      [36.75, 16.29],\n      [36.85, 16.96]\n    ],\n    [\n      [36.85, 16.96],\n      [37.17, 17.26],\n      [37.9, 17.43],\n      [38.41, 18]\n    ],\n    [\n      [38.41, 18],\n      [38.99, 16.84],\n      [39.27, 15.92],\n      [39.81, 15.44],\n      [41.18, 14.49],\n      [41.73, 13.92],\n      [42.28, 13.34],\n      [42.59, 13],\n      [43.08, 12.7]\n    ],\n    [\n      [-9.03, 41.88],\n      [-8.98, 42.59],\n      [-9.39, 43.03],\n      [-7.98, 43.75],\n      [-6.75, 43.57],\n      [-5.41, 43.57],\n      [-4.35, 43.4],\n      [-3.52, 43.46],\n      [-1.9, 43.42]\n    ],\n    [\n      [-1.9, 43.42],\n      [-1.5, 43.03],\n      [0.34, 42.58],\n      [0.7, 42.8],\n      [1.83, 42.34],\n      [2.99, 42.47]\n    ],\n    [\n      [2.99, 42.47],\n      [3.04, 41.89],\n      [2.09, 41.23],\n      [0.81, 41.01],\n      [0.72, 40.68],\n      [0.11, 40.12],\n      [-0.28, 39.31],\n      [0.11, 38.74],\n      [-0.47, 38.29],\n      [-0.68, 37.64],\n      [-1.44, 37.44],\n      [-2.15, 36.67],\n      [-3.42, 36.66],\n      [-4.37, 36.68],\n      [-5, 36.32],\n      [-5.38, 35.95],\n      [-5.87, 36.03],\n      [-6.24, 36.37],\n      [-6.52, 36.94],\n      [-7.45, 37.1]\n    ],\n    [\n      [-7.45, 37.1],\n      [-7.54, 37.43],\n      [-7.17, 37.8],\n      [-7.03, 38.08],\n      [-7.37, 38.37],\n      [-7.1, 39.03],\n      [-7.5, 39.63],\n      [-7.07, 39.71],\n      [-7.03, 40.18],\n      [-6.86, 40.33],\n      [-6.85, 41.11],\n      [-6.39, 41.38],\n      [-6.67, 41.88],\n      [-7.25, 41.92],\n      [-7.42, 41.79],\n      [-8.01, 41.79],\n      [-8.26, 42.28],\n      [-8.67, 42.13],\n      [-9.03, 41.88]\n    ],\n    [\n      [24.31, 57.79],\n      [24.43, 58.38],\n      [24.06, 58.26],\n      [23.43, 58.61],\n      [23.34, 59.19],\n      [24.6, 59.47],\n      [25.86, 59.61],\n      [26.95, 59.45],\n      [27.98, 59.48],\n      [28.13, 59.3]\n    ],\n    [\n      [28.13, 59.3],\n      [27.42, 58.72],\n      [27.72, 57.79]\n    ],\n    [\n      [27.72, 57.79],\n      [27.29, 57.47]\n    ],\n    [\n      [27.29, 57.47],\n      [26.46, 57.48],\n      [25.6, 57.85],\n      [25.16, 57.97],\n      [24.31, 57.79]\n    ],\n    [\n      [39.34, 14.53],\n      [40.03, 14.52],\n      [40.9, 14.12]\n    ],\n    [\n      [40.9, 14.12],\n      [41.16, 13.77],\n      [41.6, 13.45]\n    ],\n    [\n      [42.78, 10.93],\n      [42.56, 10.57],\n      [42.93, 10.02]\n    ],\n    [\n      [42.93, 10.02],\n      [43.3, 9.54],\n      [43.68, 9.18]\n    ],\n    [\n      [43.68, 9.18],\n      [46.95, 8],\n      [47.79, 8]\n    ],\n    [\n      [47.79, 8],\n      [44.96, 5],\n      [43.66, 4.96],\n      [42.77, 4.25],\n      [42.13, 4.23],\n      [41.86, 3.92]\n    ],\n    [\n      [41.86, 3.92],\n      [41.17, 3.92],\n      [40.77, 4.26],\n      [39.85, 3.84],\n      [39.56, 3.42],\n      [38.89, 3.5],\n      [38.67, 3.62],\n      [38.44, 3.59],\n      [38.12, 3.6],\n      [36.86, 4.45],\n      [36.16, 4.45],\n      [35.82, 4.78],\n      [35.82, 5.34],\n      [35.3, 5.51]\n    ],\n    [\n      [35.3, 5.51],\n      [34.71, 6.59],\n      [34.25, 6.83],\n      [34.08, 7.23],\n      [33.57, 7.71],\n      [32.95, 7.78],\n      [33.29, 8.35],\n      [33.83, 8.38],\n      [33.97, 8.68]\n    ],\n    [\n      [33.97, 8.68],\n      [33.96, 9.58]\n    ],\n    [\n      [33.96, 9.58],\n      [34.26, 10.63],\n      [34.73, 10.91],\n      [34.83, 11.32],\n      [35.26, 12.08],\n      [35.86, 12.58],\n      [36.27, 13.56],\n      [36.43, 14.42]\n    ],\n    [\n      [28.59, 69.06],\n      [28.45, 68.36],\n      [29.98, 67.7],\n      [29.05, 66.94],\n      [30.22, 65.81],\n      [29.54, 64.95],\n      [30.44, 64.2],\n      [30.04, 63.55],\n      [31.52, 62.87],\n      [31.14, 62.36],\n      [30.21, 61.78]\n    ],\n    [\n      [30.21, 61.78],\n      [28.07, 60.5],\n      [26.26, 60.42],\n      [24.5, 60.06],\n      [22.87, 59.85],\n      [22.29, 60.39],\n      [21.32, 60.72],\n      [21.54, 61.71],\n      [21.06, 62.61],\n      [21.54, 63.19],\n      [22.44, 63.82],\n      [24.73, 64.9],\n      [25.4, 65.11],\n      [25.29, 65.53],\n      [23.9, 66.01]\n    ],\n    [\n      [23.9, 66.01],\n      [23.57, 66.4],\n      [23.54, 67.94],\n      [21.98, 68.62],\n      [20.65, 69.11]\n    ],\n    [\n      [20.65, 69.11],\n      [21.24, 69.37],\n      [22.36, 68.84],\n      [23.66, 68.89],\n      [24.74, 68.65],\n      [25.69, 69.09],\n      [26.18, 69.83],\n      [27.73, 70.16],\n      [29.02, 69.77],\n      [28.59, 69.06]\n    ],\n    [\n      [178.37, -17.34],\n      [178.72, -17.63],\n      [178.55, -18.15],\n      [177.93, -18.29],\n      [177.38, -18.16],\n      [177.29, -17.72],\n      [177.67, -17.38],\n      [178.13, -17.5],\n      [178.37, -17.34]\n    ],\n    [\n      [179.36, -16.8],\n      [178.73, -17.01],\n      [178.6, -16.64],\n      [179.1, -16.43],\n      [179.41, -16.38],\n      [180, -16.07],\n      [180, -16.56],\n      [179.36, -16.8]\n    ],\n    [\n      [-179.92, -16.5],\n      [-180, -16.56],\n      [-180, -16.07],\n      [-179.79, -16.02],\n      [-179.92, -16.5]\n    ],\n    [\n      [9.56, 42.15],\n      [9.23, 41.38],\n      [8.78, 41.58],\n      [8.54, 42.26],\n      [8.75, 42.63],\n      [9.39, 43.01],\n      [9.56, 42.15]\n    ],\n    [\n      [5.67, 49.53],\n      [5.9, 49.44],\n      [6.19, 49.46]\n    ],\n    [\n      [6.84, 45.99],\n      [6.8, 45.71],\n      [7.1, 45.33],\n      [6.75, 45.03],\n      [7.01, 44.25],\n      [7.55, 44.13],\n      [7.44, 43.69]\n    ],\n    [\n      [7.44, 43.69],\n      [6.53, 43.13],\n      [4.56, 43.4],\n      [3.1, 43.08],\n      [2.99, 42.47]\n    ],\n    [\n      [-1.9, 43.42],\n      [-1.38, 44.02],\n      [-1.19, 46.01],\n      [-2.23, 47.06],\n      [-2.96, 47.57],\n      [-4.49, 47.95],\n      [-4.59, 48.68],\n      [-3.3, 48.9],\n      [-1.62, 48.64],\n      [-1.93, 49.78],\n      [-0.99, 49.35],\n      [1.34, 50.13],\n      [1.64, 50.95],\n      [2.51, 51.15]\n    ],\n    [\n      [-54.52, 2.31],\n      [-54.27, 2.74],\n      [-54.18, 3.19],\n      [-54.01, 3.62],\n      [-54.4, 4.21]\n    ],\n    [\n      [-54.4, 4.21],\n      [-54.48, 4.9],\n      [-53.96, 5.76]\n    ],\n    [\n      [-53.96, 5.76],\n      [-53.62, 5.65],\n      [-52.88, 5.41],\n      [-51.82, 4.57],\n      [-51.66, 4.16]\n    ],\n    [\n      [11.09, -3.98],\n      [10.07, -2.97],\n      [9.41, -2.14],\n      [8.8, -1.11],\n      [8.83, -0.78],\n      [9.05, -0.46],\n      [9.29, 0.27],\n      [9.49, 1.01]\n    ],\n    [\n      [9.49, 1.01],\n      [9.83, 1.07],\n      [11.29, 1.06],\n      [11.28, 2.26]\n    ],\n    [\n      [-6.2, 53.87],\n      [-6.95, 54.07],\n      [-7.57, 54.06],\n      [-7.37, 54.6],\n      [-7.57, 55.13]\n    ],\n    [\n      [-7.57, 55.13],\n      [-6.73, 55.17],\n      [-5.66, 54.55],\n      [-6.2, 53.87]\n    ],\n    [\n      [-3.01, 58.64],\n      [-4.07, 57.55],\n      [-3.06, 57.69],\n      [-1.96, 57.68],\n      [-2.22, 56.87],\n      [-3.12, 55.97],\n      [-2.09, 55.91],\n      [-2.01, 55.8],\n      [-1.11, 54.62],\n      [-0.43, 54.46],\n      [0.18, 53.33],\n      [0.47, 52.93],\n      [1.68, 52.74],\n      [1.56, 52.1],\n      [1.05, 51.81],\n      [1.45, 51.29],\n      [0.55, 50.77],\n      [-0.79, 50.77],\n      [-2.49, 50.5],\n      [-2.96, 50.7],\n      [-3.62, 50.23],\n      [-4.54, 50.34],\n      [-5.25, 49.96],\n      [-5.78, 50.16],\n      [-4.31, 51.21],\n      [-3.41, 51.43],\n      [-3.42, 51.43],\n      [-4.98, 51.59],\n      [-5.27, 51.99],\n      [-4.22, 52.3],\n      [-4.77, 52.84],\n      [-4.58, 53.5],\n      [-3.09, 53.4],\n      [-3.09, 53.4],\n      [-2.95, 53.99],\n      [-3.61, 54.6],\n      [-3.63, 54.62],\n      [-4.84, 54.79],\n      [-5.08, 55.06],\n      [-4.72, 55.51],\n      [-5.05, 55.78],\n      [-5.59, 55.31],\n      [-5.64, 56.28],\n      [-6.15, 56.79],\n      [-5.79, 57.82],\n      [-5.01, 58.63],\n      [-4.21, 58.55],\n      [-3.01, 58.64]\n    ],\n    [\n      [41.55, 41.54],\n      [41.7, 41.96],\n      [41.45, 42.65],\n      [40.88, 43.01],\n      [40.32, 43.13],\n      [39.96, 43.43]\n    ],\n    [\n      [39.96, 43.43],\n      [40.08, 43.55]\n    ],\n    [\n      [40.08, 43.55],\n      [40.92, 43.38],\n      [42.39, 43.22],\n      [43.76, 42.74],\n      [43.93, 42.55],\n      [44.54, 42.71]\n    ],\n    [\n      [44.54, 42.71],\n      [45.47, 42.5]\n    ],\n    [\n      [45.47, 42.5],\n      [45.78, 42.09],\n      [46.4, 41.86]\n    ],\n    [\n      [43.58, 41.09],\n      [42.62, 41.58],\n      [41.55, 41.54]\n    ],\n    [\n      [1.06, 5.93],\n      [-0.51, 5.34],\n      [-1.06, 5],\n      [-1.96, 4.71],\n      [-2.86, 4.99]\n    ],\n    [\n      [0.02, 11.02],\n      [-0.05, 10.71],\n      [0.37, 10.19],\n      [0.37, 9.47],\n      [0.46, 8.68],\n      [0.71, 8.31],\n      [0.49, 7.41],\n      [0.57, 6.91],\n      [0.84, 6.28],\n      [1.06, 5.93]\n    ],\n    [\n      [-8.44, 7.69],\n      [-8.72, 7.71],\n      [-8.93, 7.31],\n      [-9.21, 7.31],\n      [-9.4, 7.53],\n      [-9.34, 7.93],\n      [-9.76, 8.54],\n      [-10.02, 8.43],\n      [-10.23, 8.41]\n    ],\n    [\n      [-10.23, 8.41],\n      [-10.51, 8.35],\n      [-10.49, 8.72],\n      [-10.65, 8.98],\n      [-10.62, 9.27],\n      [-10.84, 9.69],\n      [-11.12, 10.05],\n      [-11.92, 10.05],\n      [-12.15, 9.86],\n      [-12.43, 9.84],\n      [-12.6, 9.62],\n      [-12.71, 9.34],\n      [-13.25, 8.9]\n    ],\n    [\n      [-13.25, 8.9],\n      [-13.69, 9.49],\n      [-14.07, 9.89],\n      [-14.33, 10.02],\n      [-14.58, 10.21],\n      [-14.69, 10.66],\n      [-14.84, 10.88],\n      [-15.13, 11.04]\n    ],\n    [\n      [-15.13, 11.04],\n      [-14.69, 11.53],\n      [-14.38, 11.51],\n      [-14.12, 11.68],\n      [-13.9, 11.68],\n      [-13.74, 11.81],\n      [-13.83, 12.14],\n      [-13.72, 12.25],\n      [-13.7, 12.59]\n    ],\n    [\n      [-13.7, 12.59],\n      [-13.22, 12.58],\n      [-12.5, 12.33],\n      [-12.28, 12.35],\n      [-12.2, 12.47],\n      [-11.66, 12.39],\n      [-11.51, 12.44]\n    ],\n    [\n      [-11.51, 12.44],\n      [-11.46, 12.08],\n      [-11.3, 12.08],\n      [-11.04, 12.21],\n      [-10.87, 12.18],\n      [-10.59, 11.92],\n      [-10.17, 11.84],\n      [-9.89, 12.06],\n      [-9.57, 12.19],\n      [-9.33, 12.33],\n      [-9.13, 12.31],\n      [-8.91, 12.09],\n      [-8.79, 11.81],\n      [-8.38, 11.39],\n      [-8.58, 11.14],\n      [-8.62, 10.81],\n      [-8.41, 10.91],\n      [-8.28, 10.79],\n      [-8.34, 10.49],\n      [-8.03, 10.21]\n    ],\n    [\n      [-16.84, 13.15],\n      [-16.71, 13.59]\n    ],\n    [\n      [-16.71, 13.59],\n      [-15.62, 13.62],\n      [-15.4, 13.86],\n      [-15.08, 13.88],\n      [-14.69, 13.63],\n      [-14.38, 13.63],\n      [-14.05, 13.79],\n      [-13.84, 13.51],\n      [-14.28, 13.28],\n      [-14.71, 13.3],\n      [-15.14, 13.51],\n      [-15.51, 13.28],\n      [-15.69, 13.27],\n      [-15.93, 13.13],\n      [-16.84, 13.15]\n    ],\n    [\n      [-15.13, 11.04],\n      [-15.66, 11.46],\n      [-16.09, 11.52],\n      [-16.31, 11.81],\n      [-16.31, 11.96],\n      [-16.61, 12.17],\n      [-16.68, 12.38]\n    ],\n    [\n      [-16.68, 12.38],\n      [-16.15, 12.55],\n      [-15.82, 12.52],\n      [-15.55, 12.63],\n      [-13.7, 12.59]\n    ],\n    [\n      [9.49, 1.01],\n      [9.31, 1.16],\n      [9.65, 2.28]\n    ],\n    [\n      [23.7, 35.71],\n      [24.25, 35.37],\n      [25.03, 35.42],\n      [25.77, 35.35],\n      [25.75, 35.18],\n      [26.29, 35.3],\n      [26.16, 35],\n      [24.72, 34.92],\n      [24.74, 35.08],\n      [23.51, 35.28],\n      [23.7, 35.71]\n    ],\n    [\n      [26.06, 40.82],\n      [25.45, 40.85],\n      [24.93, 40.95],\n      [23.71, 40.69],\n      [24.41, 40.12],\n      [23.9, 39.96],\n      [23.34, 39.96],\n      [22.81, 40.48],\n      [22.63, 40.26],\n      [22.85, 39.66],\n      [23.35, 39.19],\n      [22.97, 38.97],\n      [23.53, 38.51],\n      [24.03, 38.22],\n      [24.04, 37.66],\n      [23.12, 37.92],\n      [23.41, 37.41],\n      [22.77, 37.31],\n      [23.15, 36.42],\n      [22.49, 36.41],\n      [21.67, 36.84],\n      [21.3, 37.64],\n      [21.12, 38.31],\n      [20.73, 38.77],\n      [20.22, 39.34],\n      [20.15, 39.62]\n    ],\n    [\n      [21.02, 40.84],\n      [21.67, 40.93],\n      [22.06, 41.15],\n      [22.6, 41.13],\n      [22.76, 41.3],\n      [22.95, 41.34]\n    ],\n    [\n      [26.12, 41.83],\n      [26.6, 41.56],\n      [26.29, 40.94],\n      [26.06, 40.82]\n    ],\n    [\n      [-90.1, 13.74],\n      [-90.61, 13.91],\n      [-91.23, 13.93],\n      [-91.69, 14.13],\n      [-92.23, 14.54]\n    ],\n    [\n      [-92.23, 14.54],\n      [-92.2, 14.83],\n      [-92.09, 15.06],\n      [-92.23, 15.25],\n      [-91.75, 16.07],\n      [-90.46, 16.07],\n      [-90.44, 16.41],\n      [-90.6, 16.47],\n      [-90.71, 16.69],\n      [-91.08, 16.92],\n      [-91.45, 17.25],\n      [-91, 17.25],\n      [-91, 17.82],\n      [-90.07, 17.82],\n      [-89.14, 17.81]\n    ],\n    [\n      [-88.93, 15.89],\n      [-88.6, 15.71],\n      [-88.52, 15.86],\n      [-88.23, 15.73]\n    ],\n    [\n      [-88.23, 15.73],\n      [-88.68, 15.35],\n      [-89.15, 15.07],\n      [-89.23, 14.87],\n      [-89.15, 14.68],\n      [-89.35, 14.42]\n    ],\n    [\n      [-89.35, 14.42],\n      [-89.59, 14.36],\n      [-89.53, 14.24],\n      [-89.72, 14.13],\n      [-90.06, 13.88],\n      [-90.1, 13.74]\n    ],\n    [\n      [-59.76, 8.37],\n      [-59.1, 8],\n      [-58.48, 7.35],\n      [-58.45, 6.83],\n      [-58.08, 6.81],\n      [-57.54, 6.32],\n      [-57.15, 5.97]\n    ],\n    [\n      [-57.15, 5.97],\n      [-57.31, 5.07],\n      [-57.91, 4.81],\n      [-57.86, 4.58],\n      [-58.04, 4.06],\n      [-57.6, 3.33],\n      [-57.28, 3.33],\n      [-57.15, 2.77],\n      [-56.54, 1.9]\n    ],\n    [\n      [-60.73, 5.2],\n      [-61.41, 5.96],\n      [-61.14, 6.23],\n      [-61.16, 6.7],\n      [-60.54, 6.86],\n      [-60.3, 7.04],\n      [-60.64, 7.42],\n      [-60.55, 7.78],\n      [-59.76, 8.37]\n    ],\n    [\n      [-87.32, 12.98],\n      [-87.49, 13.3],\n      [-87.79, 13.38]\n    ],\n    [\n      [-87.79, 13.38],\n      [-87.72, 13.79],\n      [-87.86, 13.89],\n      [-88.07, 13.96],\n      [-88.5, 13.85],\n      [-88.54, 13.98],\n      [-88.84, 14.14],\n      [-89.06, 14.34],\n      [-89.35, 14.42]\n    ],\n    [\n      [-88.23, 15.73],\n      [-88.12, 15.69],\n      [-87.9, 15.86],\n      [-87.62, 15.88],\n      [-87.52, 15.8],\n      [-87.37, 15.85],\n      [-86.9, 15.76],\n      [-86.44, 15.78],\n      [-86.12, 15.89],\n      [-86, 16.01],\n      [-85.68, 15.95],\n      [-85.44, 15.89],\n      [-85.18, 15.91],\n      [-84.98, 16],\n      [-84.53, 15.86],\n      [-84.37, 15.84],\n      [-84.06, 15.65],\n      [-83.77, 15.42],\n      [-83.41, 15.27],\n      [-83.15, 15]\n    ],\n    [\n      [-83.15, 15],\n      [-83.49, 15.02],\n      [-83.63, 14.88],\n      [-83.98, 14.75],\n      [-84.23, 14.75],\n      [-84.45, 14.62],\n      [-84.65, 14.67],\n      [-84.82, 14.82],\n      [-84.92, 14.79],\n      [-85.05, 14.55],\n      [-85.15, 14.56],\n      [-85.17, 14.35],\n      [-85.51, 14.08],\n      [-85.7, 13.96],\n      [-85.8, 13.84],\n      [-86.1, 14.04],\n      [-86.31, 13.77],\n      [-86.52, 13.78],\n      [-86.76, 13.75],\n      [-86.73, 13.26],\n      [-86.88, 13.25],\n      [-87.01, 13.03],\n      [-87.32, 12.98]\n    ],\n    [\n      [18.83, 45.91],\n      [19.07, 45.52]\n    ],\n    [\n      [19.07, 45.52],\n      [19.39, 45.24]\n    ],\n    [\n      [19.39, 45.24],\n      [19.01, 44.86]\n    ],\n    [\n      [18.56, 42.65],\n      [18.45, 42.48],\n      [17.51, 42.85],\n      [16.93, 43.21],\n      [16.02, 43.51],\n      [15.17, 44.24],\n      [15.38, 44.32],\n      [14.92, 44.74],\n      [14.9, 45.08],\n      [14.26, 45.23],\n      [13.95, 44.8],\n      [13.66, 45.14],\n      [13.68, 45.48],\n      [13.72, 45.5]\n    ],\n    [\n      [13.72, 45.5],\n      [14.41, 45.47],\n      [14.6, 45.63],\n      [14.94, 45.47],\n      [15.33, 45.45],\n      [15.32, 45.73],\n      [15.67, 45.83],\n      [15.77, 46.24],\n      [16.56, 46.5]\n    ],\n    [\n      [16.56, 46.5],\n      [16.88, 46.38],\n      [17.63, 45.95],\n      [18.46, 45.76],\n      [18.83, 45.91]\n    ],\n    [\n      [-71.71, 18.04],\n      [-72.37, 18.21],\n      [-72.84, 18.15],\n      [-73.45, 18.22],\n      [-73.92, 18.03],\n      [-74.46, 18.34],\n      [-74.37, 18.66],\n      [-73.45, 18.53],\n      [-72.69, 18.45],\n      [-72.33, 18.67],\n      [-72.79, 19.1],\n      [-72.78, 19.48],\n      [-73.42, 19.64],\n      [-73.19, 19.92],\n      [-72.58, 19.87],\n      [-71.71, 19.71]\n    ],\n    [\n      [16.98, 48.12],\n      [17.49, 47.87],\n      [17.86, 47.76],\n      [18.7, 47.88],\n      [18.78, 48.08],\n      [19.17, 48.11],\n      [19.66, 48.27],\n      [19.77, 48.2],\n      [20.24, 48.33],\n      [20.47, 48.56],\n      [20.8, 48.62],\n      [21.87, 48.32],\n      [22.09, 48.42]\n    ],\n    [\n      [22.09, 48.42],\n      [22.64, 48.15],\n      [22.71, 47.88]\n    ],\n    [\n      [22.71, 47.88],\n      [22.1, 47.67],\n      [21.63, 46.99],\n      [21.02, 46.32],\n      [20.22, 46.13]\n    ],\n    [\n      [20.22, 46.13],\n      [19.6, 46.17]\n    ],\n    [\n      [19.6, 46.17],\n      [18.83, 45.91]\n    ],\n    [\n      [16.56, 46.5],\n      [16.37, 46.84],\n      [16.2, 46.85]\n    ],\n    [\n      [120.72, -10.24],\n      [120.3, -10.26],\n      [118.97, -9.56],\n      [119.9, -9.36],\n      [120.43, -9.67],\n      [120.78, -9.97],\n      [120.72, -10.24]\n    ],\n    [\n      [124.97, -8.89],\n      [125.07, -9.09],\n      [125.09, -9.39]\n    ],\n    [\n      [125.09, -9.39],\n      [124.44, -10.14],\n      [123.58, -10.36],\n      [123.46, -10.24],\n      [123.55, -9.9],\n      [123.98, -9.29],\n      [124.97, -8.89]\n    ],\n    [\n      [117.9, -8.1],\n      [118.26, -8.36],\n      [118.88, -8.28],\n      [119.13, -8.71],\n      [117.97, -8.91],\n      [117.28, -9.04],\n      [116.74, -9.03],\n      [117.08, -8.46],\n      [117.63, -8.45],\n      [117.9, -8.1]\n    ],\n    [\n      [122.9, -8.09],\n      [122.76, -8.65],\n      [121.25, -8.93],\n      [119.92, -8.81],\n      [119.92, -8.44],\n      [120.72, -8.24],\n      [121.34, -8.54],\n      [122.01, -8.46],\n      [122.9, -8.09]\n    ],\n    [\n      [108.62, -6.78],\n      [110.54, -6.88],\n      [110.76, -6.47],\n      [112.61, -6.95],\n      [112.98, -7.59],\n      [114.48, -7.78],\n      [115.71, -8.37],\n      [114.56, -8.75],\n      [113.46, -8.35],\n      [112.56, -8.38],\n      [111.52, -8.3],\n      [110.59, -8.12],\n      [109.43, -7.74],\n      [108.69, -7.64],\n      [108.28, -7.77],\n      [106.45, -7.35],\n      [106.28, -6.92],\n      [105.37, -6.85],\n      [106.05, -5.9],\n      [107.27, -5.95],\n      [108.07, -6.35],\n      [108.49, -6.42],\n      [108.62, -6.78]\n    ],\n    [\n      [134.72, -6.21],\n      [134.21, -6.9],\n      [134.11, -6.14],\n      [134.29, -5.78],\n      [134.5, -5.45],\n      [134.73, -5.74],\n      [134.72, -6.21]\n    ],\n    [\n      [127.25, -3.46],\n      [126.87, -3.79],\n      [126.18, -3.61],\n      [125.99, -3.18],\n      [127, -3.13],\n      [127.25, -3.46]\n    ],\n    [\n      [130.47, -3.09],\n      [130.83, -3.86],\n      [129.99, -3.45],\n      [129.16, -3.36],\n      [128.59, -3.43],\n      [127.9, -3.39],\n      [128.14, -2.84],\n      [129.37, -2.8],\n      [130.47, -3.09]\n    ],\n    [\n      [141, -2.6],\n      [141.02, -5.86],\n      [141.03, -9.12]\n    ],\n    [\n      [141.03, -9.12],\n      [140.14, -8.3],\n      [139.13, -8.1],\n      [138.88, -8.38],\n      [137.61, -8.41],\n      [138.04, -7.6],\n      [138.67, -7.32],\n      [138.41, -6.23],\n      [137.93, -5.39],\n      [135.99, -4.55],\n      [135.16, -4.46],\n      [133.66, -3.54],\n      [133.37, -4.02],\n      [132.98, -4.11],\n      [132.76, -3.75],\n      [132.75, -3.31],\n      [131.99, -2.82],\n      [133.07, -2.46],\n      [133.78, -2.48],\n      [133.7, -2.21],\n      [132.23, -2.21],\n      [131.84, -1.62],\n      [130.94, -1.43],\n      [130.52, -0.94],\n      [131.87, -0.7],\n      [132.38, -0.37],\n      [133.99, -0.78],\n      [134.14, -1.15],\n      [134.42, -2.77],\n      [135.46, -3.37],\n      [136.29, -2.31],\n      [137.44, -1.7],\n      [138.33, -1.7],\n      [139.18, -2.05],\n      [139.93, -2.41],\n      [141, -2.6]\n    ],\n    [\n      [125.24, 1.42],\n      [124.44, 0.43],\n      [123.69, 0.24],\n      [122.72, 0.43],\n      [121.06, 0.38],\n      [120.18, 0.24],\n      [120.04, -0.52],\n      [120.94, -1.41],\n      [121.48, -0.96],\n      [123.34, -0.62],\n      [123.26, -1.08],\n      [122.82, -0.93],\n      [122.39, -1.52],\n      [121.51, -1.9],\n      [122.45, -3.19],\n      [122.27, -3.53],\n      [123.17, -4.68],\n      [123.16, -5.34],\n      [122.63, -5.63],\n      [122.24, -5.28],\n      [122.72, -4.46],\n      [121.74, -4.85],\n      [121.49, -4.57],\n      [121.62, -4.19],\n      [120.9, -3.6],\n      [120.97, -2.63],\n      [120.31, -2.93],\n      [120.39, -4.1],\n      [120.43, -5.53],\n      [119.8, -5.67],\n      [119.37, -5.38],\n      [119.65, -4.46],\n      [119.5, -3.49],\n      [119.08, -3.49],\n      [118.77, -2.8],\n      [119.18, -2.15],\n      [119.32, -1.35],\n      [119.83, 0.15],\n      [120.04, 0.57],\n      [120.89, 1.31],\n      [121.67, 1.01],\n      [122.93, 0.88],\n      [124.08, 0.92],\n      [125.07, 1.64],\n      [125.24, 1.42]\n    ],\n    [\n      [128.69, 1.13],\n      [128.64, 0.26],\n      [128.12, 0.36],\n      [127.97, -0.25],\n      [128.38, -0.78],\n      [128.1, -0.9],\n      [127.7, -0.27],\n      [127.4, 1.01],\n      [127.6, 1.81],\n      [127.93, 2.17],\n      [128, 1.63],\n      [128.59, 1.54],\n      [128.69, 1.13]\n    ],\n    [\n      [109.66, 2.01],\n      [109.83, 1.34],\n      [110.51, 0.77],\n      [111.16, 0.98],\n      [111.8, 0.9],\n      [112.38, 1.41],\n      [112.86, 1.5],\n      [113.81, 1.22],\n      [114.62, 1.43],\n      [115.13, 2.82],\n      [115.52, 3.17],\n      [115.87, 4.31],\n      [117.02, 4.31],\n      [117.88, 4.14]\n    ],\n    [\n      [117.88, 4.14],\n      [117.31, 3.23],\n      [118.05, 2.29],\n      [117.88, 1.83],\n      [119, 0.9],\n      [117.81, 0.78],\n      [117.48, 0.1],\n      [117.52, -0.8],\n      [116.56, -1.49],\n      [116.53, -2.48],\n      [116.15, -4.01],\n      [116, -3.66],\n      [114.86, -4.11],\n      [114.47, -3.5],\n      [113.76, -3.44],\n      [113.26, -3.12],\n      [112.07, -3.48],\n      [111.7, -2.99],\n      [111.05, -3.05],\n      [110.22, -2.93],\n      [110.07, -1.59],\n      [109.57, -1.31],\n      [109.09, -0.46],\n      [108.95, 0.42],\n      [109.07, 1.34],\n      [109.66, 2.01]\n    ],\n    [\n      [105.82, -5.85],\n      [104.71, -5.87],\n      [103.87, -5.04],\n      [102.58, -4.22],\n      [102.16, -3.61],\n      [101.4, -2.8],\n      [100.9, -2.05],\n      [100.14, -0.65],\n      [99.26, 0.18],\n      [98.97, 1.04],\n      [98.6, 1.82],\n      [97.7, 2.45],\n      [97.18, 3.31],\n      [96.42, 3.87],\n      [95.38, 4.97],\n      [95.29, 5.48],\n      [95.94, 5.44],\n      [97.48, 5.25],\n      [98.37, 4.27],\n      [99.14, 3.59],\n      [99.69, 3.17],\n      [100.64, 2.1],\n      [101.66, 2.08],\n      [102.5, 1.4],\n      [103.08, 0.56],\n      [103.84, 0.1],\n      [103.44, -0.71],\n      [104.01, -1.06],\n      [104.37, -1.08],\n      [104.54, -1.78],\n      [104.89, -2.34],\n      [105.62, -2.43],\n      [106.11, -3.06],\n      [105.86, -4.31],\n      [105.82, -5.85]\n    ],\n    [\n      [81.11, 30.18],\n      [80.48, 29.73],\n      [80.09, 28.79],\n      [81.06, 28.42],\n      [82, 27.93],\n      [83.3, 27.36],\n      [84.68, 27.23],\n      [85.25, 26.73],\n      [86.02, 26.63],\n      [87.23, 26.4],\n      [88.06, 26.41],\n      [88.17, 26.81],\n      [88.04, 27.45],\n      [88.12, 27.88]\n    ],\n    [\n      [97.33, 28.26],\n      [97.4, 27.88],\n      [97.05, 27.7],\n      [97.13, 27.08],\n      [96.42, 27.26],\n      [95.12, 26.57],\n      [95.16, 26],\n      [94.6, 25.16],\n      [94.55, 24.68],\n      [94.11, 23.85],\n      [93.33, 24.08],\n      [93.29, 23.04],\n      [93.06, 22.7],\n      [93.17, 22.28],\n      [92.67, 22.04]\n    ],\n    [\n      [89.03, 22.06],\n      [88.89, 21.69],\n      [88.21, 21.7],\n      [86.98, 21.5],\n      [87.03, 20.74],\n      [86.5, 20.15],\n      [85.06, 19.48],\n      [83.94, 18.3],\n      [83.19, 17.67],\n      [82.19, 17.02],\n      [82.19, 16.56],\n      [81.69, 16.31],\n      [80.79, 15.95],\n      [80.32, 15.9],\n      [80.03, 15.14],\n      [80.23, 13.84],\n      [80.29, 13.01],\n      [79.86, 12.06],\n      [79.86, 10.36],\n      [79.34, 10.31],\n      [78.89, 9.55],\n      [79.19, 9.22],\n      [78.28, 8.93],\n      [77.94, 8.25],\n      [77.54, 7.97],\n      [76.59, 8.9],\n      [76.13, 10.3],\n      [75.75, 11.31],\n      [75.4, 11.78],\n      [74.86, 12.74],\n      [74.62, 13.99],\n      [74.44, 14.62],\n      [73.53, 15.99],\n      [73.12, 17.93],\n      [72.82, 19.21],\n      [72.82, 20.42],\n      [72.63, 21.36],\n      [71.18, 20.76],\n      [70.47, 20.88],\n      [69.16, 22.09],\n      [69.64, 22.45],\n      [69.35, 22.84],\n      [68.18, 23.69]\n    ],\n    [\n      [68.18, 23.69],\n      [68.84, 24.36],\n      [71.04, 24.36],\n      [70.84, 25.22],\n      [70.28, 25.72],\n      [70.17, 26.49],\n      [69.51, 26.94],\n      [70.62, 27.99],\n      [71.78, 27.91],\n      [72.82, 28.96],\n      [73.45, 29.98],\n      [74.42, 30.98],\n      [74.41, 31.69],\n      [75.26, 32.27],\n      [74.45, 32.76],\n      [74.1, 33.44],\n      [73.75, 34.32],\n      [74.24, 34.75],\n      [75.76, 34.5],\n      [76.87, 34.65],\n      [77.84, 35.49]\n    ],\n    [\n      [-6.2, 53.87],\n      [-6.03, 53.15],\n      [-6.79, 52.26],\n      [-8.56, 51.67],\n      [-9.98, 51.82],\n      [-9.17, 52.86],\n      [-9.69, 53.88],\n      [-8.33, 54.66],\n      [-7.57, 55.13]\n    ],\n    [\n      [53.92, 37.2],\n      [54.8, 37.39],\n      [55.51, 37.96],\n      [56.18, 37.94],\n      [56.62, 38.12],\n      [57.33, 38.03],\n      [58.44, 37.52],\n      [59.23, 37.41],\n      [60.38, 36.53],\n      [61.12, 36.49],\n      [61.21, 35.65]\n    ],\n    [\n      [60.87, 29.83],\n      [61.37, 29.3],\n      [61.77, 28.7],\n      [62.73, 28.26],\n      [62.76, 27.38],\n      [63.23, 27.22],\n      [63.32, 26.76],\n      [61.87, 26.24],\n      [61.5, 25.08]\n    ],\n    [\n      [61.5, 25.08],\n      [59.62, 25.38],\n      [58.53, 25.61],\n      [57.4, 25.74],\n      [56.97, 26.97],\n      [56.49, 27.14],\n      [55.72, 26.96],\n      [54.72, 26.48],\n      [53.49, 26.81],\n      [52.48, 27.58],\n      [51.52, 27.87],\n      [50.85, 28.81],\n      [50.12, 30.15],\n      [49.58, 29.99],\n      [48.94, 30.32],\n      [48.57, 29.93]\n    ],\n    [\n      [48.57, 29.93],\n      [48.01, 30.45],\n      [48, 30.99],\n      [47.69, 30.98],\n      [47.85, 31.71],\n      [47.33, 32.47],\n      [46.11, 33.02],\n      [45.42, 33.97],\n      [45.65, 34.75],\n      [46.15, 35.09],\n      [46.08, 35.68],\n      [45.42, 35.98]\n    ],\n    [\n      [45.42, 35.98],\n      [44.77, 37.17],\n      [44.23, 37.97]\n    ],\n    [\n      [44.23, 37.97],\n      [44.42, 38.28],\n      [44.11, 39.43],\n      [44.79, 39.71]\n    ],\n    [\n      [48.88, 38.32],\n      [49.2, 37.58],\n      [50.15, 37.37],\n      [50.84, 36.87],\n      [52.26, 36.7],\n      [53.83, 36.97],\n      [53.92, 37.2]\n    ],\n    [\n      [48.57, 29.93],\n      [47.97, 29.98]\n    ],\n    [\n      [47.97, 29.98],\n      [47.3, 30.06],\n      [46.57, 29.1]\n    ],\n    [\n      [46.57, 29.1],\n      [44.71, 29.18],\n      [41.89, 31.19],\n      [40.4, 31.89],\n      [39.2, 32.16]\n    ],\n    [\n      [39.2, 32.16],\n      [38.79, 33.38]\n    ],\n    [\n      [38.79, 33.38],\n      [41.01, 34.42],\n      [41.38, 35.63],\n      [41.29, 36.36],\n      [41.84, 36.61],\n      [42.35, 37.23]\n    ],\n    [\n      [42.35, 37.23],\n      [42.78, 37.39],\n      [43.94, 37.26],\n      [44.29, 37],\n      [44.77, 37.17]\n    ],\n    [\n      [44.77, 37.17],\n      [45.42, 35.98]\n    ],\n    [\n      [-14.51, 66.46],\n      [-14.74, 65.81],\n      [-13.61, 65.13],\n      [-14.91, 64.36],\n      [-17.79, 63.68],\n      [-18.66, 63.5],\n      [-19.97, 63.64],\n      [-22.76, 63.96],\n      [-21.78, 64.4],\n      [-23.96, 64.89],\n      [-22.18, 65.08],\n      [-22.23, 65.38],\n      [-24.33, 65.61],\n      [-23.65, 66.26],\n      [-22.13, 66.41],\n      [-20.58, 65.73],\n      [-19.06, 66.28],\n      [-17.8, 65.99],\n      [-16.17, 66.53],\n      [-14.51, 66.46]\n    ],\n    [\n      [35.72, 32.71],\n      [35.55, 32.39]\n    ],\n    [\n      [35.55, 32.39],\n      [35.18, 32.53],\n      [34.97, 31.87],\n      [35.23, 31.75],\n      [34.97, 31.62],\n      [34.93, 31.35],\n      [35.4, 31.49]\n    ],\n    [\n      [35.4, 31.49],\n      [35.42, 31.1],\n      [34.92, 29.5]\n    ],\n    [\n      [34.92, 29.5],\n      [34.27, 31.22],\n      [34.56, 31.55],\n      [34.49, 31.61],\n      [34.75, 32.07],\n      [34.96, 32.83],\n      [35.1, 33.08],\n      [35.13, 33.09]\n    ],\n    [\n      [35.13, 33.09],\n      [35.46, 33.09],\n      [35.55, 33.26],\n      [35.82, 33.28]\n    ],\n    [\n      [35.82, 33.28],\n      [35.84, 32.87],\n      [35.7, 32.72],\n      [35.72, 32.71]\n    ],\n    [\n      [15.52, 38.23],\n      [15.16, 37.44],\n      [15.31, 37.13],\n      [15.1, 36.62],\n      [14.34, 37],\n      [13.83, 37.1],\n      [12.43, 37.61],\n      [12.57, 38.13],\n      [13.74, 38.03],\n      [14.76, 38.14],\n      [15.52, 38.23]\n    ],\n    [\n      [9.21, 41.21],\n      [9.81, 40.5],\n      [9.67, 39.18],\n      [9.21, 39.24],\n      [8.81, 38.91],\n      [8.43, 39.17],\n      [8.39, 40.38],\n      [8.16, 40.95],\n      [8.71, 40.9],\n      [9.21, 41.21]\n    ],\n    [\n      [13.81, 46.51],\n      [13.7, 46.02],\n      [13.94, 45.59]\n    ],\n    [\n      [13.94, 45.59],\n      [13.14, 45.74],\n      [12.33, 45.38],\n      [12.38, 44.89],\n      [12.26, 44.6],\n      [12.59, 44.09],\n      [13.53, 43.59],\n      [14.03, 42.76],\n      [15.14, 41.96],\n      [15.93, 41.96],\n      [16.17, 41.74],\n      [15.89, 41.54],\n      [16.79, 41.18],\n      [17.52, 40.88],\n      [18.38, 40.36],\n      [18.48, 40.17],\n      [18.29, 39.81],\n      [17.74, 40.28],\n      [16.87, 40.44],\n      [16.45, 39.8],\n      [17.17, 39.42],\n      [17.05, 38.9],\n      [16.64, 38.84],\n      [16.1, 37.99],\n      [15.68, 37.91],\n      [15.69, 38.21],\n      [15.89, 38.75],\n      [16.11, 38.96],\n      [15.72, 39.54],\n      [15.41, 40.05],\n      [15, 40.17],\n      [14.7, 40.6],\n      [14.06, 40.79],\n      [13.63, 41.19],\n      [12.89, 41.25],\n      [12.11, 41.7],\n      [11.19, 42.36],\n      [10.51, 42.93],\n      [10.2, 43.92],\n      [9.7, 44.04],\n      [8.89, 44.37],\n      [8.43, 44.23],\n      [7.85, 43.77],\n      [7.44, 43.69]\n    ],\n    [\n      [-77.57, 18.49],\n      [-76.9, 18.4],\n      [-76.37, 18.16],\n      [-76.2, 17.89],\n      [-76.9, 17.87],\n      [-77.21, 17.7],\n      [-77.77, 17.86],\n      [-78.34, 18.23],\n      [-78.22, 18.45],\n      [-77.8, 18.52],\n      [-77.57, 18.49]\n    ],\n    [\n      [35.72, 32.71],\n      [36.83, 32.31],\n      [38.79, 33.38]\n    ],\n    [\n      [39.2, 32.16],\n      [39, 32.01],\n      [37, 31.51],\n      [38, 30.51],\n      [37.67, 30.34],\n      [37.5, 30],\n      [36.74, 29.87],\n      [36.5, 29.51],\n      [36.07, 29.2],\n      [34.96, 29.36]\n    ],\n    [\n      [34.96, 29.36],\n      [34.92, 29.5]\n    ],\n    [\n      [35.4, 31.49],\n      [35.55, 31.78],\n      [35.55, 32.39]\n    ],\n    [\n      [134.64, 34.15],\n      [134.77, 33.81],\n      [134.2, 33.2],\n      [133.79, 33.52],\n      [133.28, 33.29],\n      [133.01, 32.7],\n      [132.36, 32.99],\n      [132.37, 33.46],\n      [132.92, 34.06],\n      [133.49, 33.94],\n      [133.9, 34.36],\n      [134.64, 34.15]\n    ],\n    [\n      [140.98, 37.14],\n      [140.6, 36.34],\n      [140.77, 35.84],\n      [140.25, 35.14],\n      [138.98, 34.67],\n      [137.22, 34.61],\n      [135.79, 33.46],\n      [135.12, 33.85],\n      [135.08, 34.6],\n      [133.34, 34.38],\n      [132.16, 33.9],\n      [130.99, 33.89],\n      [132, 33.15],\n      [131.33, 31.45],\n      [130.69, 31.03],\n      [130.2, 31.42],\n      [130.45, 32.32],\n      [129.81, 32.61],\n      [129.41, 33.3],\n      [130.35, 33.6],\n      [130.88, 34.23],\n      [131.88, 34.75],\n      [132.62, 35.43],\n      [134.61, 35.73],\n      [135.68, 35.53],\n      [136.72, 37.3],\n      [137.39, 36.83],\n      [138.86, 37.83],\n      [139.43, 38.22],\n      [140.05, 39.44],\n      [139.88, 40.56],\n      [140.31, 41.2],\n      [141.37, 41.38],\n      [141.91, 39.99],\n      [141.88, 39.18],\n      [140.96, 38.17],\n      [140.98, 37.14]\n    ],\n    [\n      [143.91, 44.17],\n      [144.61, 43.96],\n      [145.32, 44.38],\n      [145.54, 43.26],\n      [144.06, 42.99],\n      [143.18, 42],\n      [141.61, 42.68],\n      [141.07, 41.58],\n      [139.96, 41.57],\n      [139.82, 42.56],\n      [140.31, 43.33],\n      [141.38, 43.39],\n      [141.67, 44.77],\n      [141.97, 45.55],\n      [143.14, 44.51],\n      [143.91, 44.17]\n    ],\n    [\n      [70.96, 42.27],\n      [70.39, 42.08],\n      [69.07, 41.38],\n      [68.63, 40.67],\n      [68.26, 40.66],\n      [67.99, 41.14],\n      [66.71, 41.17],\n      [66.51, 41.99],\n      [66.02, 41.99],\n      [66.1, 43],\n      [64.9, 43.73],\n      [63.19, 43.65],\n      [62.01, 43.5],\n      [61.06, 44.41],\n      [60.24, 44.78],\n      [58.69, 45.5],\n      [58.5, 45.59],\n      [55.93, 45],\n      [55.97, 41.31]\n    ],\n    [\n      [55.97, 41.31],\n      [55.46, 41.26],\n      [54.76, 42.04],\n      [54.08, 42.32],\n      [52.94, 42.12],\n      [52.5, 41.78]\n    ],\n    [\n      [52.5, 41.78],\n      [52.45, 42.03],\n      [52.69, 42.44],\n      [52.5, 42.79],\n      [51.34, 43.13],\n      [50.89, 44.03],\n      [50.34, 44.28],\n      [50.31, 44.61],\n      [51.28, 44.51],\n      [51.32, 45.25],\n      [52.17, 45.41],\n      [53.04, 45.26],\n      [53.22, 46.23],\n      [53.04, 46.85],\n      [52.04, 46.8],\n      [51.19, 47.05],\n      [50.03, 46.61],\n      [49.1, 46.4]\n    ],\n    [\n      [49.1, 46.4],\n      [48.59, 46.56],\n      [48.69, 47.08]\n    ],\n    [\n      [48.69, 47.08],\n      [48.06, 47.74],\n      [47.32, 47.72],\n      [46.47, 48.39]\n    ],\n    [\n      [46.47, 48.39],\n      [47.04, 49.15],\n      [46.75, 49.36],\n      [47.55, 50.45],\n      [48.58, 49.87],\n      [48.7, 50.61],\n      [50.77, 51.69],\n      [52.33, 51.72],\n      [54.53, 51.03]\n    ],\n    [\n      [54.53, 51.03],\n      [55.72, 50.62],\n      [56.78, 51.04],\n      [58.36, 51.06],\n      [59.64, 50.55]\n    ],\n    [\n      [59.64, 50.55],\n      [59.93, 50.84],\n      [61.34, 50.8],\n      [61.59, 51.27],\n      [59.97, 51.96],\n      [60.93, 52.45],\n      [60.74, 52.72],\n      [61.7, 52.98],\n      [60.98, 53.66]\n    ],\n    [\n      [60.98, 53.66],\n      [61.44, 54.01],\n      [65.18, 54.35]\n    ],\n    [\n      [65.18, 54.35],\n      [65.67, 54.6],\n      [68.17, 54.97]\n    ],\n    [\n      [68.17, 54.97],\n      [69.07, 55.39],\n      [70.87, 55.17],\n      [71.18, 54.13],\n      [72.22, 54.38],\n      [73.51, 54.04],\n      [73.43, 53.49]\n    ],\n    [\n      [73.43, 53.49],\n      [74.38, 53.55],\n      [76.89, 54.49]\n    ],\n    [\n      [76.89, 54.49],\n      [76.53, 54.18],\n      [77.8, 53.4],\n      [80.04, 50.86],\n      [80.57, 51.39],\n      [81.95, 50.81],\n      [83.38, 51.07],\n      [83.94, 50.89],\n      [84.42, 50.31],\n      [85.12, 50.12],\n      [85.54, 49.69],\n      [86.83, 49.83],\n      [87.36, 49.21]\n    ],\n    [\n      [80.26, 42.35],\n      [79.64, 42.5],\n      [79.14, 42.86],\n      [77.66, 42.96],\n      [76, 42.99],\n      [75.64, 42.88],\n      [74.21, 43.3],\n      [73.65, 43.09],\n      [73.49, 42.5],\n      [71.84, 42.85],\n      [71.19, 42.7],\n      [70.96, 42.27]\n    ],\n    [\n      [41.59, -1.68],\n      [40.88, -2.08],\n      [40.64, -2.5],\n      [40.26, -2.57],\n      [40.12, -3.28],\n      [39.8, -3.68],\n      [39.6, -4.35],\n      [39.2, -4.68]\n    ],\n    [\n      [39.2, -4.68],\n      [37.77, -3.68],\n      [37.7, -3.1],\n      [34.07, -1.06],\n      [33.9, -0.95]\n    ],\n    [\n      [33.9, -0.95],\n      [33.89, 0.11],\n      [34.18, 0.52],\n      [34.67, 1.18],\n      [35.04, 1.91],\n      [34.6, 3.05],\n      [34.48, 3.56],\n      [34.01, 4.25]\n    ],\n    [\n      [34.01, 4.25],\n      [34.62, 4.85],\n      [35.3, 5.51]\n    ],\n    [\n      [41.86, 3.92],\n      [40.98, 2.78],\n      [40.99, -0.86],\n      [41.59, -1.68]\n    ],\n    [\n      [73.68, 39.43],\n      [71.78, 39.28],\n      [70.55, 39.6],\n      [69.46, 39.53],\n      [69.56, 40.1],\n      [70.65, 39.94],\n      [71.01, 40.24]\n    ],\n    [\n      [71.01, 40.24],\n      [71.77, 40.15],\n      [73.06, 40.87],\n      [71.87, 41.39],\n      [71.16, 41.14],\n      [70.42, 41.52],\n      [71.26, 42.17],\n      [70.96, 42.27]\n    ],\n    [\n      [102.58, 12.19],\n      [102.35, 13.39],\n      [102.99, 14.23],\n      [104.28, 14.42],\n      [105.22, 14.27]\n    ],\n    [\n      [105.22, 14.27],\n      [106.04, 13.88],\n      [106.5, 14.57],\n      [107.38, 14.2]\n    ],\n    [\n      [107.38, 14.2],\n      [107.61, 13.54],\n      [107.49, 12.34],\n      [105.81, 11.57],\n      [106.25, 10.96],\n      [105.2, 10.89],\n      [104.33, 10.49]\n    ],\n    [\n      [104.33, 10.49],\n      [103.5, 10.63],\n      [103.09, 11.15],\n      [102.58, 12.19]\n    ],\n    [\n      [128.35, 38.61],\n      [129.21, 37.43],\n      [129.46, 36.78],\n      [129.47, 35.63],\n      [129.09, 35.08],\n      [128.19, 34.89],\n      [127.39, 34.48],\n      [126.49, 34.39],\n      [126.37, 34.93],\n      [126.56, 35.68],\n      [126.12, 36.73],\n      [126.86, 36.89],\n      [126.17, 37.75]\n    ],\n    [\n      [126.17, 37.75],\n      [126.24, 37.84],\n      [126.68, 37.8],\n      [127.07, 38.26],\n      [127.78, 38.3],\n      [128.21, 38.37],\n      [128.35, 38.61]\n    ],\n    [\n      [20.59, 41.86],\n      [20.52, 42.22]\n    ],\n    [\n      [20.52, 42.22],\n      [20.28, 42.32],\n      [20.07, 42.59]\n    ],\n    [\n      [20.07, 42.59],\n      [20.26, 42.81]\n    ],\n    [\n      [20.26, 42.81],\n      [20.5, 42.88],\n      [20.64, 43.22],\n      [20.81, 43.27],\n      [20.96, 43.13],\n      [21.14, 43.07],\n      [21.27, 42.91],\n      [21.44, 42.86],\n      [21.63, 42.68],\n      [21.78, 42.68],\n      [21.66, 42.44],\n      [21.54, 42.32],\n      [21.58, 42.25]\n    ],\n    [\n      [21.58, 42.25],\n      [21.35, 42.21],\n      [20.76, 42.05],\n      [20.72, 41.85],\n      [20.59, 41.86]\n    ],\n    [\n      [47.97, 29.98],\n      [48.18, 29.53],\n      [48.09, 29.31],\n      [48.42, 28.55]\n    ],\n    [\n      [48.42, 28.55],\n      [47.71, 28.53],\n      [47.46, 29],\n      [46.57, 29.1]\n    ],\n    [\n      [105.22, 14.27],\n      [105.54, 14.72],\n      [105.59, 15.57],\n      [104.78, 16.44],\n      [104.72, 17.43],\n      [103.96, 18.24],\n      [103.2, 18.31],\n      [103, 17.96],\n      [102.41, 17.93],\n      [102.11, 18.11],\n      [101.06, 17.51],\n      [101.04, 18.41],\n      [101.28, 19.46],\n      [100.61, 19.51],\n      [100.55, 20.11],\n      [100.12, 20.42]\n    ],\n    [\n      [100.12, 20.42],\n      [100.33, 20.79],\n      [101.18, 21.44]\n    ],\n    [\n      [102.17, 22.46],\n      [102.75, 21.68],\n      [103.2, 20.77],\n      [104.44, 20.76],\n      [104.82, 19.89],\n      [104.18, 19.62],\n      [103.9, 19.27],\n      [105.09, 18.67],\n      [105.93, 17.49],\n      [106.56, 16.6],\n      [107.31, 15.91],\n      [107.56, 15.2],\n      [107.38, 14.2]\n    ],\n    [\n      [35.13, 33.09],\n      [35.48, 33.91],\n      [35.98, 34.61],\n      [36, 34.64]\n    ],\n    [\n      [36, 34.64],\n      [36.45, 34.59],\n      [36.61, 34.2],\n      [36.07, 33.82],\n      [35.82, 33.28]\n    ],\n    [\n      [-7.71, 4.36],\n      [-7.97, 4.36],\n      [-9, 4.83],\n      [-9.91, 5.59],\n      [-10.77, 6.14],\n      [-11.44, 6.79]\n    ],\n    [\n      [-11.44, 6.79],\n      [-11.2, 7.11],\n      [-11.15, 7.4],\n      [-10.7, 7.94],\n      [-10.23, 8.41]\n    ],\n    [\n      [14.85, 22.86],\n      [14.14, 22.49],\n      [13.58, 23.04],\n      [12, 23.47]\n    ],\n    [\n      [9.48, 30.31],\n      [9.97, 30.54],\n      [10.06, 30.96],\n      [9.95, 31.38],\n      [10.64, 31.76],\n      [10.94, 32.08],\n      [11.43, 32.37],\n      [11.49, 33.14]\n    ],\n    [\n      [11.49, 33.14],\n      [12.66, 32.79],\n      [13.08, 32.88],\n      [13.92, 32.71],\n      [15.25, 32.27],\n      [15.71, 31.38],\n      [16.61, 31.18],\n      [18.02, 30.76],\n      [19.09, 30.27],\n      [19.57, 30.53],\n      [20.05, 30.99],\n      [19.82, 31.75],\n      [20.13, 32.24],\n      [20.85, 32.71],\n      [21.54, 32.84],\n      [22.9, 32.64],\n      [23.24, 32.19],\n      [23.61, 32.19],\n      [23.93, 32.02],\n      [24.92, 31.9],\n      [25.16, 31.57]\n    ],\n    [\n      [25, 22],\n      [25, 20],\n      [23.85, 20],\n      [23.84, 19.58]\n    ],\n    [\n      [23.84, 19.58],\n      [19.85, 21.5],\n      [15.86, 23.41],\n      [14.85, 22.86]\n    ],\n    [\n      [81.79, 7.52],\n      [81.64, 6.48],\n      [81.22, 6.2],\n      [80.35, 5.97],\n      [79.87, 6.76],\n      [79.7, 8.2],\n      [80.15, 9.82],\n      [80.84, 9.27],\n      [81.3, 8.56],\n      [81.79, 7.52]\n    ],\n    [\n      [28.98, -28.96],\n      [29.33, -29.26],\n      [29.02, -29.74],\n      [28.85, -30.07],\n      [28.29, -30.23],\n      [28.11, -30.55],\n      [27.75, -30.65],\n      [27, -29.88],\n      [27.53, -29.24],\n      [28.07, -28.85],\n      [28.54, -28.65],\n      [28.98, -28.96]\n    ],\n    [\n      [22.73, 54.33],\n      [22.65, 54.58],\n      [22.76, 54.86],\n      [22.32, 55.02],\n      [21.27, 55.19]\n    ],\n    [\n      [21.27, 55.19],\n      [21.06, 56.03]\n    ],\n    [\n      [21.06, 56.03],\n      [22.2, 56.34],\n      [23.88, 56.27],\n      [24.86, 56.37],\n      [25, 56.16],\n      [25.53, 56.1],\n      [26.49, 55.62]\n    ],\n    [\n      [23.48, 53.91],\n      [23.24, 54.22],\n      [22.73, 54.33]\n    ],\n    [\n      [21.06, 56.03],\n      [21.09, 56.78],\n      [21.58, 57.41],\n      [22.52, 57.75],\n      [23.32, 57.01],\n      [24.12, 57.03],\n      [24.31, 57.79]\n    ],\n    [\n      [27.29, 57.47],\n      [27.77, 57.24],\n      [27.86, 56.76],\n      [28.18, 56.17]\n    ],\n    [\n      [-8.67, 27.6],\n      [-13.1, 27.6]\n    ],\n    [\n      [-13.1, 27.6],\n      [-12.36, 28.16],\n      [-10.9, 28.83],\n      [-10.4, 29.1],\n      [-9.56, 29.93],\n      [-9.81, 31.18],\n      [-9.43, 32.04],\n      [-9.3, 32.56],\n      [-8.66, 33.24],\n      [-7.65, 33.7],\n      [-6.91, 34.11],\n      [-6.24, 35.15],\n      [-5.93, 35.76],\n      [-5.19, 35.76],\n      [-4.59, 35.33],\n      [-3.64, 35.4],\n      [-2.6, 35.18],\n      [-2.17, 35.17]\n    ],\n    [\n      [26.62, 48.22],\n      [26.86, 48.37],\n      [27.52, 48.47],\n      [28.26, 48.16],\n      [28.67, 48.12],\n      [29.12, 47.85],\n      [29.05, 47.51],\n      [29.42, 47.35],\n      [29.56, 46.93],\n      [29.91, 46.67],\n      [29.84, 46.53],\n      [30.02, 46.42],\n      [29.76, 46.35],\n      [29.17, 46.38],\n      [29.07, 46.52],\n      [28.86, 46.44],\n      [28.93, 46.26],\n      [28.66, 45.94],\n      [28.49, 45.6],\n      [28.23, 45.49]\n    ],\n    [\n      [28.23, 45.49],\n      [28.05, 45.94],\n      [28.16, 46.37],\n      [28.13, 46.81],\n      [27.55, 47.41],\n      [27.23, 47.83],\n      [26.92, 48.12],\n      [26.62, 48.22]\n    ],\n    [\n      [49.54, -12.47],\n      [49.81, -12.9],\n      [50.06, -13.56],\n      [50.22, -14.76],\n      [50.48, -15.23],\n      [50.38, -15.71],\n      [50.2, -16],\n      [49.86, -15.41],\n      [49.67, -15.71],\n      [49.86, -16.45],\n      [49.77, -16.88],\n      [49.5, -17.11],\n      [49.44, -17.95],\n      [49.04, -19.12],\n      [48.55, -20.5],\n      [47.93, -22.39],\n      [47.55, -23.78],\n      [47.1, -24.94],\n      [46.28, -25.18],\n      [45.41, -25.6],\n      [44.83, -25.35],\n      [44.04, -24.99],\n      [43.76, -24.46],\n      [43.7, -23.57],\n      [43.35, -22.78],\n      [43.25, -22.06],\n      [43.43, -21.34],\n      [43.89, -21.16],\n      [43.9, -20.83],\n      [44.37, -20.07],\n      [44.46, -19.44],\n      [44.23, -18.96],\n      [44.04, -18.33],\n      [43.96, -17.41],\n      [44.31, -16.85],\n      [44.45, -16.22],\n      [44.94, -16.18],\n      [45.5, -15.97],\n      [45.87, -15.79],\n      [46.31, -15.78],\n      [46.88, -15.21],\n      [47.71, -14.59],\n      [48.01, -14.09],\n      [47.87, -13.66],\n      [48.29, -13.78],\n      [48.85, -13.09],\n      [48.86, -12.49],\n      [49.19, -12.04],\n      [49.54, -12.47]\n    ],\n    [\n      [-92.23, 14.54],\n      [-93.36, 15.62],\n      [-93.88, 15.94],\n      [-94.69, 16.2],\n      [-95.25, 16.13],\n      [-96.05, 15.75],\n      [-96.56, 15.65],\n      [-97.26, 15.92],\n      [-98.01, 16.11],\n      [-98.95, 16.57],\n      [-99.7, 16.71],\n      [-100.83, 17.17],\n      [-101.67, 17.65],\n      [-101.92, 17.92],\n      [-102.48, 17.98],\n      [-103.5, 18.29],\n      [-103.92, 18.75],\n      [-104.99, 19.32],\n      [-105.49, 19.95],\n      [-105.73, 20.43],\n      [-105.4, 20.53],\n      [-105.5, 20.82],\n      [-105.27, 21.08],\n      [-105.27, 21.42],\n      [-105.6, 21.87],\n      [-105.69, 22.27],\n      [-106.03, 22.77],\n      [-106.91, 23.77],\n      [-107.92, 24.55],\n      [-108.4, 25.17],\n      [-109.26, 25.58],\n      [-109.44, 25.82],\n      [-109.29, 26.44],\n      [-109.8, 26.68],\n      [-110.39, 27.16],\n      [-110.64, 27.86],\n      [-111.18, 27.94],\n      [-111.76, 28.47],\n      [-112.23, 28.95],\n      [-112.27, 29.27],\n      [-112.81, 30.02],\n      [-113.16, 30.79],\n      [-113.15, 31.17],\n      [-113.87, 31.57],\n      [-114.21, 31.52],\n      [-114.78, 31.8],\n      [-114.94, 31.39],\n      [-114.77, 30.91],\n      [-114.67, 30.16],\n      [-114.33, 29.75],\n      [-113.59, 29.06],\n      [-113.42, 28.83],\n      [-113.27, 28.75],\n      [-113.14, 28.41],\n      [-112.96, 28.43],\n      [-112.76, 27.78],\n      [-112.46, 27.53],\n      [-112.24, 27.17],\n      [-111.62, 26.66],\n      [-111.28, 25.73],\n      [-110.99, 25.29],\n      [-110.71, 24.83],\n      [-110.66, 24.3],\n      [-110.17, 24.27],\n      [-109.77, 23.81],\n      [-109.41, 23.36],\n      [-109.43, 23.19],\n      [-109.85, 22.82],\n      [-110.03, 22.82],\n      [-110.3, 23.43],\n      [-110.95, 24],\n      [-111.67, 24.48],\n      [-112.18, 24.74],\n      [-112.15, 25.47],\n      [-112.3, 26.01],\n      [-112.78, 26.32],\n      [-113.46, 26.77],\n      [-113.6, 26.64],\n      [-113.85, 26.9],\n      [-114.47, 27.14],\n      [-115.06, 27.72],\n      [-114.98, 27.8],\n      [-114.57, 27.74],\n      [-114.2, 28.12],\n      [-114.16, 28.57],\n      [-114.93, 29.28],\n      [-115.52, 29.56],\n      [-115.89, 30.18],\n      [-116.26, 30.84],\n      [-116.72, 31.64],\n      [-117.13, 32.54]\n    ],\n    [\n      [-117.13, 32.54],\n      [-115.99, 32.61],\n      [-114.72, 32.72],\n      [-114.81, 32.53],\n      [-113.3, 32.04],\n      [-111.02, 31.33],\n      [-109.03, 31.34],\n      [-108.24, 31.34],\n      [-108.24, 31.75],\n      [-106.51, 31.75],\n      [-106.14, 31.4],\n      [-105.63, 31.08],\n      [-105.04, 30.64],\n      [-104.71, 30.12],\n      [-104.46, 29.57],\n      [-103.94, 29.27],\n      [-103.11, 28.97],\n      [-102.48, 29.76],\n      [-101.66, 29.78],\n      [-100.96, 29.38],\n      [-100.46, 28.7],\n      [-100.11, 28.11],\n      [-99.52, 27.54],\n      [-99.3, 26.84],\n      [-99.02, 26.37],\n      [-98.24, 26.06],\n      [-97.53, 25.84]\n    ],\n    [\n      [-97.53, 25.84],\n      [-97.14, 25.87],\n      [-97.53, 24.99],\n      [-97.7, 24.27],\n      [-97.78, 22.93],\n      [-97.87, 22.44],\n      [-97.7, 21.9],\n      [-97.39, 21.41],\n      [-97.19, 20.64],\n      [-96.53, 19.89],\n      [-96.29, 19.32],\n      [-95.9, 18.83],\n      [-94.84, 18.56],\n      [-94.43, 18.14],\n      [-93.55, 18.42],\n      [-92.79, 18.52],\n      [-92.04, 18.7],\n      [-91.41, 18.88],\n      [-90.77, 19.28],\n      [-90.53, 19.87],\n      [-90.45, 20.71],\n      [-90.28, 21],\n      [-89.6, 21.26],\n      [-88.54, 21.49],\n      [-87.66, 21.46],\n      [-87.05, 21.54],\n      [-86.81, 21.33],\n      [-86.85, 20.85],\n      [-87.38, 20.26],\n      [-87.62, 19.65],\n      [-87.44, 19.47],\n      [-87.59, 19.04],\n      [-87.84, 18.26],\n      [-88.09, 18.52],\n      [-88.3, 18.5]\n    ],\n    [\n      [21.58, 42.25],\n      [21.92, 42.3],\n      [22.38, 42.32]\n    ],\n    [\n      [21.02, 40.84],\n      [20.61, 41.09],\n      [20.46, 41.52],\n      [20.59, 41.86]\n    ],\n    [\n      [-12.17, 14.62],\n      [-11.83, 14.8],\n      [-11.67, 15.39],\n      [-11.35, 15.41],\n      [-10.65, 15.13],\n      [-10.09, 15.33],\n      [-9.7, 15.26],\n      [-9.55, 15.49],\n      [-5.54, 15.5],\n      [-5.32, 16.2],\n      [-5.49, 16.33],\n      [-5.97, 20.64],\n      [-6.45, 24.96],\n      [-4.92, 24.97]\n    ],\n    [\n      [4.27, 19.16],\n      [4.27, 16.85],\n      [3.72, 16.18],\n      [3.64, 15.57],\n      [2.75, 15.41],\n      [1.39, 15.32],\n      [1.02, 14.97],\n      [0.37, 14.93]\n    ],\n    [\n      [-11.51, 12.44],\n      [-11.47, 12.75],\n      [-11.55, 13.14],\n      [-11.93, 13.42],\n      [-12.12, 13.99],\n      [-12.17, 14.62]\n    ],\n    [\n      [98.55, 9.93],\n      [98.46, 10.68],\n      [98.76, 11.44],\n      [98.43, 12.03],\n      [98.51, 13.12],\n      [98.1, 13.64],\n      [97.78, 14.84],\n      [97.6, 16.1],\n      [97.16, 16.93],\n      [96.51, 16.43],\n      [95.37, 15.71],\n      [94.81, 15.8],\n      [94.19, 16.04],\n      [94.53, 17.28],\n      [94.32, 18.21],\n      [93.54, 19.37],\n      [93.66, 19.73],\n      [93.08, 19.86],\n      [92.37, 20.67]\n    ],\n    [\n      [100.12, 20.42],\n      [99.54, 20.19],\n      [98.96, 19.75],\n      [98.25, 19.71],\n      [97.8, 18.63],\n      [97.38, 18.45],\n      [97.86, 17.57],\n      [98.49, 16.84],\n      [98.9, 16.18],\n      [98.54, 15.31],\n      [98.19, 15.12],\n      [98.43, 14.62],\n      [99.1, 13.83],\n      [99.21, 13.27],\n      [99.2, 12.8],\n      [99.59, 11.89],\n      [99.04, 10.96],\n      [98.55, 9.93]\n    ],\n    [\n      [19.74, 42.69],\n      [19.3, 42.2],\n      [19.37, 41.88],\n      [19.16, 41.96],\n      [18.88, 42.28],\n      [18.45, 42.48],\n      [18.56, 42.65]\n    ],\n    [\n      [19.22, 43.52],\n      [19.48, 43.35],\n      [19.63, 43.21],\n      [19.96, 43.11],\n      [20.34, 42.9],\n      [20.26, 42.81]\n    ],\n    [\n      [87.75, 49.3],\n      [88.81, 49.47],\n      [90.71, 50.33],\n      [92.23, 50.8]\n    ],\n    [\n      [92.23, 50.8],\n      [93.1, 50.5],\n      [94.15, 50.48]\n    ],\n    [\n      [94.15, 50.48],\n      [94.82, 50.01]\n    ],\n    [\n      [94.82, 50.01],\n      [95.81, 49.98],\n      [97.26, 49.73],\n      [98.23, 50.42]\n    ],\n    [\n      [98.23, 50.42],\n      [97.83, 51.01],\n      [98.86, 52.05],\n      [99.98, 51.63],\n      [100.89, 51.52]\n    ],\n    [\n      [100.89, 51.52],\n      [102.07, 51.26],\n      [102.26, 50.51],\n      [103.68, 50.09]\n    ],\n    [\n      [103.68, 50.09],\n      [104.62, 50.28],\n      [105.89, 50.41]\n    ],\n    [\n      [105.89, 50.41],\n      [106.89, 50.27],\n      [107.87, 49.79],\n      [108.48, 49.28],\n      [109.4, 49.29],\n      [110.66, 49.13],\n      [111.58, 49.38],\n      [112.9, 49.54],\n      [114.36, 50.25],\n      [114.96, 50.14],\n      [115.49, 49.81],\n      [116.68, 49.89]\n    ],\n    [\n      [34.56, -11.52],\n      [35.31, -11.44],\n      [36.51, -11.72],\n      [36.78, -11.59]\n    ],\n    [\n      [36.78, -11.59],\n      [37.47, -11.57],\n      [37.83, -11.27],\n      [38.43, -11.29]\n    ],\n    [\n      [38.43, -11.29],\n      [39.52, -10.9],\n      [40.32, -10.32],\n      [40.48, -10.77],\n      [40.44, -11.76],\n      [40.56, -12.64],\n      [40.6, -14.2],\n      [40.78, -14.69],\n      [40.48, -15.41],\n      [40.09, -16.1],\n      [39.45, -16.72],\n      [38.54, -17.1],\n      [37.41, -17.59],\n      [36.28, -18.66],\n      [35.9, -18.84],\n      [35.2, -19.55],\n      [34.79, -19.78],\n      [34.7, -20.5],\n      [35.18, -21.25],\n      [35.37, -21.84],\n      [35.39, -22.14],\n      [35.56, -22.09],\n      [35.53, -23.07],\n      [35.37, -23.54],\n      [35.61, -23.71],\n      [35.46, -24.12],\n      [35.04, -24.48],\n      [34.22, -24.82],\n      [33.01, -25.36],\n      [32.57, -25.73],\n      [32.66, -26.15],\n      [32.92, -26.22],\n      [32.83, -26.74]\n    ],\n    [\n      [32.83, -26.74],\n      [32.07, -26.73]\n    ],\n    [\n      [32.07, -26.73],\n      [31.99, -26.29],\n      [31.84, -25.84]\n    ],\n    [\n      [31.84, -25.84],\n      [31.75, -25.48],\n      [31.93, -24.37],\n      [31.67, -23.66],\n      [31.19, -22.25]\n    ],\n    [\n      [31.19, -22.25],\n      [32.24, -21.12],\n      [32.51, -20.4],\n      [32.66, -20.3],\n      [32.77, -19.72],\n      [32.61, -19.42],\n      [32.65, -18.67],\n      [32.85, -17.98],\n      [32.85, -16.71],\n      [32.33, -16.39],\n      [31.85, -16.32],\n      [31.64, -16.07],\n      [31.17, -15.86],\n      [30.34, -15.88],\n      [30.27, -15.51]\n    ],\n    [\n      [30.27, -15.51],\n      [30.18, -14.8],\n      [33.21, -13.97]\n    ],\n    [\n      [33.21, -13.97],\n      [33.79, -14.45],\n      [34.06, -14.36],\n      [34.46, -14.61],\n      [34.52, -15.01],\n      [34.31, -15.48],\n      [34.38, -16.18],\n      [35.03, -16.8],\n      [35.34, -16.11],\n      [35.77, -15.9],\n      [35.69, -14.61],\n      [35.27, -13.89],\n      [34.91, -13.57],\n      [34.56, -13.58],\n      [34.28, -12.28],\n      [34.56, -11.52]\n    ],\n    [\n      [-12.17, 14.62],\n      [-12.83, 15.3],\n      [-13.44, 16.04],\n      [-14.1, 16.3],\n      [-14.58, 16.6],\n      [-15.14, 16.59],\n      [-15.62, 16.37],\n      [-16.12, 16.46],\n      [-16.46, 16.14]\n    ],\n    [\n      [-16.46, 16.14],\n      [-16.55, 16.67],\n      [-16.27, 17.17],\n      [-16.15, 18.11],\n      [-16.26, 19.1],\n      [-16.38, 19.59],\n      [-16.28, 20.09],\n      [-16.54, 20.57],\n      [-17.06, 21]\n    ],\n    [\n      [-17.06, 21],\n      [-16.85, 21.33],\n      [-12.93, 21.33],\n      [-13.12, 22.77],\n      [-12.87, 23.28],\n      [-11.94, 23.37],\n      [-11.97, 25.93],\n      [-8.69, 25.88],\n      [-8.68, 27.4]\n    ],\n    [\n      [33.21, -13.97],\n      [32.69, -13.71],\n      [32.99, -12.78],\n      [33.31, -12.44],\n      [33.11, -11.61],\n      [33.32, -10.8],\n      [33.49, -10.53],\n      [33.23, -9.68],\n      [32.76, -9.23]\n    ],\n    [\n      [32.76, -9.23],\n      [33.74, -9.42],\n      [33.94, -9.69]\n    ],\n    [\n      [33.94, -9.69],\n      [34.28, -10.16],\n      [34.56, -11.52]\n    ],\n    [\n      [102.14, 6.22],\n      [102.37, 6.13],\n      [102.96, 5.52],\n      [103.38, 4.86],\n      [103.44, 4.18],\n      [103.33, 3.73],\n      [103.43, 3.38],\n      [103.5, 2.79],\n      [103.85, 2.52],\n      [104.25, 1.63],\n      [104.29, 1.37],\n      [104.13, 1.27]\n    ],\n    [\n      [104.13, 1.27],\n      [103.74, 1.13],\n      [103.56, 1.19]\n    ],\n    [\n      [103.56, 1.19],\n      [102.57, 1.97],\n      [101.39, 2.76],\n      [101.27, 3.27],\n      [100.7, 3.94],\n      [100.56, 4.77],\n      [100.2, 5.31],\n      [100.31, 6.04],\n      [100.09, 6.46]\n    ],\n    [\n      [100.09, 6.46],\n      [100.26, 6.64],\n      [101.08, 6.2],\n      [101.15, 5.69],\n      [101.81, 5.81],\n      [102.14, 6.22]\n    ],\n    [\n      [109.66, 2.01],\n      [110.4, 1.66],\n      [111.17, 1.85],\n      [111.37, 2.7],\n      [111.8, 2.89],\n      [113, 3.1],\n      [113.71, 3.89],\n      [114.2, 4.53]\n    ],\n    [\n      [115.45, 5.45],\n      [116.22, 6.14],\n      [116.73, 6.92],\n      [117.13, 6.93],\n      [117.64, 6.42],\n      [117.69, 5.99],\n      [118.35, 5.71],\n      [119.18, 5.41],\n      [119.11, 5.02],\n      [118.44, 4.97],\n      [118.62, 4.48],\n      [117.88, 4.14]\n    ],\n    [\n      [16.34, -28.58],\n      [15.6, -27.82],\n      [15.21, -27.09],\n      [14.99, -26.12],\n      [14.74, -25.39],\n      [14.41, -23.85],\n      [14.39, -22.66],\n      [14.26, -22.11],\n      [13.87, -21.7],\n      [13.35, -20.87],\n      [12.83, -19.67],\n      [12.61, -19.05],\n      [11.79, -18.07],\n      [11.73, -17.3]\n    ],\n    [\n      [23.22, -17.52],\n      [24.03, -17.3],\n      [24.68, -17.35],\n      [25.08, -17.58],\n      [25.08, -17.66]\n    ],\n    [\n      [19.9, -24.77],\n      [19.89, -28.46],\n      [19, -28.97],\n      [18.46, -29.05],\n      [17.84, -28.86],\n      [17.39, -28.78],\n      [17.22, -28.36],\n      [16.82, -28.08],\n      [16.34, -28.58]\n    ],\n    [\n      [165.78, -21.08],\n      [166.6, -21.7],\n      [167.12, -22.16],\n      [166.74, -22.4],\n      [166.19, -22.13],\n      [165.47, -21.68],\n      [164.83, -21.15],\n      [164.17, -20.44],\n      [164.03, -20.11],\n      [164.46, -20.12],\n      [165.02, -20.46],\n      [165.46, -20.8],\n      [165.78, -21.08]\n    ],\n    [\n      [14.85, 22.86],\n      [15.1, 21.31]\n    ],\n    [\n      [15.1, 21.31],\n      [15.47, 21.05],\n      [15.49, 20.73]\n    ],\n    [\n      [15.49, 20.73],\n      [15.9, 20.39],\n      [15.69, 19.96],\n      [15.3, 17.93],\n      [15.25, 16.63]\n    ],\n    [\n      [15.25, 16.63],\n      [13.97, 15.68],\n      [13.54, 14.37]\n    ],\n    [\n      [13.54, 14.37],\n      [13.96, 14],\n      [13.95, 13.35],\n      [14.6, 13.33],\n      [14.5, 12.86]\n    ],\n    [\n      [14.18, 12.48],\n      [14, 12.46],\n      [13.32, 13.56],\n      [13.08, 13.6],\n      [12.3, 13.04],\n      [11.53, 13.33],\n      [10.99, 13.39],\n      [10.7, 13.25],\n      [10.11, 13.28],\n      [9.52, 12.85],\n      [9.01, 12.83],\n      [7.8, 13.34],\n      [7.33, 13.1],\n      [6.82, 13.12],\n      [6.45, 13.49],\n      [5.44, 13.87],\n      [4.37, 13.75],\n      [4.11, 13.53],\n      [3.97, 12.96],\n      [3.68, 12.55],\n      [3.61, 11.66]\n    ],\n    [\n      [8.5, 4.77],\n      [7.46, 4.41],\n      [7.08, 4.46],\n      [6.7, 4.24],\n      [5.9, 4.26],\n      [5.36, 4.89],\n      [5.03, 5.61],\n      [4.33, 6.27],\n      [3.57, 6.26],\n      [2.69, 6.26]\n    ],\n    [\n      [-85.71, 11.09],\n      [-86.06, 11.4],\n      [-86.53, 11.81],\n      [-86.75, 12.14],\n      [-87.17, 12.46],\n      [-87.67, 12.91],\n      [-87.56, 13.06],\n      [-87.39, 12.91],\n      [-87.32, 12.98]\n    ],\n    [\n      [-83.15, 15],\n      [-83.23, 14.9],\n      [-83.28, 14.68],\n      [-83.18, 14.31],\n      [-83.41, 13.97],\n      [-83.52, 13.57],\n      [-83.55, 13.13],\n      [-83.5, 12.87],\n      [-83.47, 12.42],\n      [-83.63, 12.32],\n      [-83.72, 11.89],\n      [-83.65, 11.63],\n      [-83.86, 11.37],\n      [-83.81, 11.1],\n      [-83.66, 10.94]\n    ],\n    [\n      [4.05, 51.27],\n      [3.31, 51.35],\n      [3.83, 51.62],\n      [4.71, 53.09],\n      [6.07, 53.51],\n      [6.91, 53.48]\n    ],\n    [\n      [20.65, 69.11],\n      [20.03, 69.07],\n      [19.88, 68.41],\n      [17.99, 68.57],\n      [17.73, 68.01],\n      [16.77, 68.01],\n      [16.11, 67.3],\n      [15.11, 66.19],\n      [13.56, 64.79],\n      [13.92, 64.45],\n      [13.57, 64.05],\n      [12.58, 64.07],\n      [11.93, 63.13],\n      [11.99, 61.8],\n      [12.63, 61.29],\n      [12.3, 60.12],\n      [11.47, 59.43],\n      [11.03, 58.86]\n    ],\n    [\n      [11.03, 58.86],\n      [10.36, 59.47],\n      [8.38, 58.31],\n      [7.05, 58.08],\n      [5.67, 58.59],\n      [5.31, 59.66],\n      [4.99, 61.97],\n      [5.91, 62.61],\n      [8.55, 63.45],\n      [10.53, 64.49],\n      [12.36, 65.88],\n      [14.76, 67.81],\n      [16.44, 68.56],\n      [19.18, 69.82],\n      [21.38, 70.26],\n      [23.02, 70.2],\n      [24.55, 71.03],\n      [26.37, 70.99],\n      [28.17, 71.19],\n      [31.29, 70.45],\n      [30.01, 70.19],\n      [31.1, 69.56],\n      [29.4, 69.16],\n      [28.59, 69.06]\n    ],\n    [\n      [24.72, 77.85],\n      [22.49, 77.44],\n      [20.73, 77.68],\n      [21.42, 77.94],\n      [20.81, 78.25],\n      [22.88, 78.45],\n      [23.28, 78.08],\n      [24.72, 77.85]\n    ],\n    [\n      [18.25, 79.7],\n      [21.54, 78.96],\n      [19.03, 78.56],\n      [18.47, 77.83],\n      [17.59, 77.64],\n      [17.12, 76.81],\n      [15.91, 76.77],\n      [13.76, 77.38],\n      [14.67, 77.74],\n      [13.17, 78.02],\n      [11.22, 78.87],\n      [10.44, 79.65],\n      [13.17, 80.01],\n      [13.72, 79.66],\n      [15.14, 79.67],\n      [15.52, 80.02],\n      [16.99, 80.05],\n      [18.25, 79.7]\n    ],\n    [\n      [25.45, 80.41],\n      [27.41, 80.06],\n      [25.92, 79.52],\n      [23.02, 79.4],\n      [20.08, 79.57],\n      [19.9, 79.84],\n      [18.46, 79.86],\n      [17.37, 80.32],\n      [20.46, 80.6],\n      [21.91, 80.36],\n      [22.92, 80.66],\n      [25.45, 80.41]\n    ],\n    [\n      [173.02, -40.92],\n      [173.25, -41.33],\n      [173.96, -40.93],\n      [174.25, -41.35],\n      [174.25, -41.77],\n      [173.88, -42.23],\n      [173.22, -42.97],\n      [172.71, -43.37],\n      [173.08, -43.85],\n      [172.31, -43.87],\n      [171.45, -44.24],\n      [171.19, -44.9],\n      [170.62, -45.91],\n      [169.83, -46.36],\n      [169.33, -46.64],\n      [168.41, -46.62],\n      [167.76, -46.29],\n      [166.68, -46.22],\n      [166.51, -45.85],\n      [167.05, -45.11],\n      [168.3, -44.12],\n      [168.95, -43.94],\n      [169.67, -43.56],\n      [170.52, -43.03],\n      [171.13, -42.51],\n      [171.57, -41.77],\n      [171.95, -41.51],\n      [172.1, -40.96],\n      [172.8, -40.49],\n      [173.02, -40.92]\n    ],\n    [\n      [174.61, -36.16],\n      [175.34, -37.21],\n      [175.36, -36.53],\n      [175.81, -36.8],\n      [175.96, -37.56],\n      [176.76, -37.88],\n      [177.44, -37.96],\n      [178.01, -37.58],\n      [178.52, -37.7],\n      [178.27, -38.58],\n      [177.97, -39.17],\n      [177.21, -39.15],\n      [176.94, -39.45],\n      [177.03, -39.88],\n      [176.89, -40.07],\n      [176.51, -40.6],\n      [176.01, -41.29],\n      [175.24, -41.69],\n      [175.07, -41.43],\n      [174.65, -41.28],\n      [175.23, -40.46],\n      [174.9, -39.91],\n      [173.82, -39.51],\n      [173.85, -39.15],\n      [174.57, -38.8],\n      [174.74, -38.03],\n      [174.7, -37.38],\n      [174.29, -36.71],\n      [174.32, -36.53],\n      [173.84, -36.12],\n      [173.05, -35.24],\n      [172.64, -34.53],\n      [173.01, -34.45],\n      [173.55, -35.01],\n      [174.33, -35.27],\n      [174.61, -36.16]\n    ],\n    [\n      [53.11, 16.65],\n      [52.78, 17.35],\n      [52, 19]\n    ],\n    [\n      [52, 19],\n      [55, 20],\n      [55.67, 22],\n      [55.21, 22.71]\n    ],\n    [\n      [56.4, 24.92],\n      [56.85, 24.24],\n      [57.4, 23.88],\n      [58.14, 23.75],\n      [58.73, 23.57],\n      [59.18, 22.99],\n      [59.45, 22.66],\n      [59.81, 22.53],\n      [59.81, 22.31],\n      [59.44, 21.71],\n      [59.28, 21.43],\n      [58.86, 21.11],\n      [58.49, 20.43],\n      [58.03, 20.48],\n      [57.83, 20.24],\n      [57.67, 19.74],\n      [57.79, 19.07],\n      [57.69, 18.94],\n      [57.23, 18.95],\n      [56.61, 18.57],\n      [56.51, 18.09],\n      [56.28, 17.88],\n      [55.66, 17.88],\n      [55.27, 17.63],\n      [55.27, 17.23],\n      [54.79, 16.95],\n      [54.24, 17.04],\n      [53.57, 16.71],\n      [53.11, 16.65]\n    ],\n    [\n      [56.07, 26.06],\n      [56.36, 26.4],\n      [56.49, 26.31],\n      [56.39, 25.9],\n      [56.26, 25.71]\n    ],\n    [\n      [68.18, 23.69],\n      [67.44, 23.94],\n      [67.15, 24.66],\n      [66.37, 25.43],\n      [64.53, 25.24],\n      [62.91, 25.22],\n      [61.5, 25.08]\n    ],\n    [\n      [-77.88, 7.22],\n      [-78.21, 7.51],\n      [-78.43, 8.05],\n      [-78.18, 8.32],\n      [-78.44, 8.39],\n      [-78.62, 8.72],\n      [-79.12, 9],\n      [-79.56, 8.93],\n      [-79.76, 8.58],\n      [-80.16, 8.33],\n      [-80.38, 8.3],\n      [-80.48, 8.09],\n      [-80, 7.55],\n      [-80.28, 7.42],\n      [-80.42, 7.27],\n      [-80.89, 7.22],\n      [-81.06, 7.82],\n      [-81.19, 7.65],\n      [-81.52, 7.71],\n      [-81.72, 8.11],\n      [-82.13, 8.18],\n      [-82.39, 8.29],\n      [-82.82, 8.29],\n      [-82.85, 8.07],\n      [-82.97, 8.23]\n    ],\n    [\n      [-82.55, 9.57],\n      [-82.19, 9.21],\n      [-82.21, 9],\n      [-81.81, 8.95],\n      [-81.71, 9.03],\n      [-81.44, 8.79],\n      [-80.95, 8.86],\n      [-80.52, 9.11],\n      [-79.91, 9.31],\n      [-79.57, 9.61],\n      [-79.02, 9.55],\n      [-79.06, 9.45],\n      [-78.5, 9.42],\n      [-78.06, 9.25],\n      [-77.73, 8.95],\n      [-77.35, 8.67]\n    ],\n    [\n      [-70.37, -18.35],\n      [-71.38, -17.77],\n      [-71.46, -17.36],\n      [-73.44, -16.36],\n      [-75.24, -15.27],\n      [-76.01, -14.65],\n      [-76.42, -13.82],\n      [-76.26, -13.54],\n      [-77.11, -12.22],\n      [-78.09, -10.38],\n      [-79.04, -8.39],\n      [-79.45, -7.93],\n      [-79.76, -7.19],\n      [-80.54, -6.54],\n      [-81.25, -6.14],\n      [-80.93, -5.69],\n      [-81.41, -4.74],\n      [-81.1, -4.04],\n      [-80.3, -3.4]\n    ],\n    [\n      [126.38, 8.41],\n      [126.48, 7.75],\n      [126.54, 7.19],\n      [126.2, 6.27],\n      [125.83, 7.29],\n      [125.36, 6.79],\n      [125.68, 6.05],\n      [125.4, 5.58],\n      [124.22, 6.16],\n      [123.94, 6.89],\n      [124.24, 7.36],\n      [123.61, 7.83],\n      [123.3, 7.42],\n      [122.83, 7.46],\n      [122.09, 6.9],\n      [121.92, 7.19],\n      [122.31, 8.03],\n      [122.94, 8.32],\n      [123.49, 8.69],\n      [123.84, 8.24],\n      [124.6, 8.51],\n      [124.76, 8.96],\n      [125.47, 8.99],\n      [125.41, 9.76],\n      [126.22, 9.29],\n      [126.31, 8.78],\n      [126.38, 8.41]\n    ],\n    [\n      [123.98, 10.28],\n      [123.62, 9.95],\n      [123.31, 9.32],\n      [123, 9.02],\n      [122.38, 9.71],\n      [122.59, 9.98],\n      [122.84, 10.26],\n      [122.95, 10.88],\n      [123.5, 10.94],\n      [123.34, 10.27],\n      [124.08, 11.23],\n      [123.98, 10.28]\n    ],\n    [\n      [118.5, 9.32],\n      [117.17, 8.37],\n      [117.66, 9.07],\n      [118.39, 9.68],\n      [118.99, 10.38],\n      [119.51, 11.37],\n      [119.69, 10.55],\n      [119.03, 10],\n      [118.5, 9.32]\n    ],\n    [\n      [121.88, 11.89],\n      [122.48, 11.58],\n      [123.12, 11.58],\n      [123.1, 11.17],\n      [122.64, 10.74],\n      [122, 10.44],\n      [121.97, 10.91],\n      [122.04, 11.42],\n      [121.88, 11.89]\n    ],\n    [\n      [125.5, 12.16],\n      [125.78, 11.05],\n      [125.01, 11.31],\n      [125.03, 10.98],\n      [125.28, 10.36],\n      [124.8, 10.13],\n      [124.76, 10.84],\n      [124.46, 10.89],\n      [124.3, 11.5],\n      [124.89, 11.42],\n      [124.88, 11.79],\n      [124.27, 12.56],\n      [125.23, 12.54],\n      [125.5, 12.16]\n    ],\n    [\n      [121.53, 13.07],\n      [121.26, 12.21],\n      [120.83, 12.7],\n      [120.32, 13.47],\n      [121.18, 13.43],\n      [121.53, 13.07]\n    ],\n    [\n      [121.32, 18.5],\n      [121.94, 18.22],\n      [122.25, 18.48],\n      [122.34, 18.22],\n      [122.17, 17.81],\n      [122.52, 17.09],\n      [122.25, 16.26],\n      [121.66, 15.93],\n      [121.51, 15.12],\n      [121.73, 14.33],\n      [122.26, 14.22],\n      [122.7, 14.34],\n      [123.95, 13.78],\n      [123.86, 13.24],\n      [124.18, 13],\n      [124.08, 12.54],\n      [123.3, 13.03],\n      [122.93, 13.55],\n      [122.67, 13.19],\n      [122.03, 13.78],\n      [121.13, 13.64],\n      [120.63, 13.86],\n      [120.68, 14.27],\n      [120.99, 14.53],\n      [120.69, 14.76],\n      [120.56, 14.4],\n      [120.07, 14.97],\n      [119.92, 15.41],\n      [119.88, 16.36],\n      [120.29, 16.03],\n      [120.39, 17.6],\n      [120.72, 18.51],\n      [121.32, 18.5]\n    ],\n    [\n      [155.88, -6.82],\n      [155.6, -6.92],\n      [155.17, -6.54],\n      [154.73, -5.9],\n      [154.51, -5.14],\n      [154.65, -5.04],\n      [154.76, -5.34],\n      [155.06, -5.57],\n      [155.55, -6.2],\n      [156.02, -6.54],\n      [155.88, -6.82]\n    ],\n    [\n      [151.98, -5.48],\n      [151.46, -5.56],\n      [151.3, -5.84],\n      [150.75, -6.08],\n      [150.24, -6.32],\n      [149.71, -6.32],\n      [148.89, -6.03],\n      [148.32, -5.75],\n      [148.4, -5.44],\n      [149.3, -5.58],\n      [149.85, -5.51],\n      [150, -5.03],\n      [150.14, -5],\n      [150.24, -5.53],\n      [150.81, -5.46],\n      [151.09, -5.11],\n      [151.65, -4.76],\n      [151.54, -4.17],\n      [152.14, -4.15],\n      [152.34, -4.31],\n      [152.32, -4.87],\n      [151.98, -5.48]\n    ],\n    [\n      [141, -2.6],\n      [142.74, -3.29],\n      [144.58, -3.86],\n      [145.27, -4.37],\n      [145.83, -4.88],\n      [145.98, -5.47],\n      [147.65, -6.08],\n      [147.89, -6.61],\n      [146.97, -6.72],\n      [147.19, -7.39],\n      [148.08, -8.04],\n      [148.73, -9.1],\n      [149.31, -9.07],\n      [149.27, -9.51],\n      [150.04, -9.68],\n      [149.74, -9.87],\n      [150.8, -10.29],\n      [150.69, -10.58],\n      [150.03, -10.65],\n      [149.78, -10.39],\n      [148.92, -10.28],\n      [147.91, -10.13],\n      [147.14, -9.49],\n      [146.57, -8.94],\n      [146.05, -8.07],\n      [144.74, -7.63],\n      [143.9, -7.92],\n      [143.29, -8.25],\n      [143.41, -8.98],\n      [142.63, -9.33],\n      [142.07, -9.16],\n      [141.03, -9.12]\n    ],\n    [\n      [153.14, -4.5],\n      [152.83, -4.77],\n      [152.64, -4.18],\n      [152.41, -3.79],\n      [151.95, -3.46],\n      [151.38, -3.04],\n      [150.66, -2.74],\n      [150.94, -2.5],\n      [151.48, -2.78],\n      [151.82, -3],\n      [152.24, -3.24],\n      [152.64, -3.66],\n      [153.02, -3.98],\n      [153.14, -4.5]\n    ],\n    [\n      [14.12, 53.76],\n      [14.8, 54.05],\n      [16.36, 54.51],\n      [17.62, 54.85],\n      [18.62, 54.68],\n      [18.7, 54.44],\n      [19.66, 54.43]\n    ],\n    [\n      [19.66, 54.43],\n      [20.89, 54.31],\n      [22.73, 54.33]\n    ],\n    [\n      [23.53, 51.58],\n      [24.03, 50.71],\n      [23.92, 50.42],\n      [23.43, 50.31],\n      [22.52, 49.48],\n      [22.78, 49.03],\n      [22.56, 49.09]\n    ],\n    [\n      [22.56, 49.09],\n      [21.61, 49.47],\n      [20.89, 49.33],\n      [20.42, 49.43],\n      [19.83, 49.22],\n      [19.32, 49.57],\n      [18.91, 49.44],\n      [18.85, 49.5]\n    ],\n    [\n      [-66.28, 18.51],\n      [-65.77, 18.43],\n      [-65.59, 18.23],\n      [-65.85, 17.98],\n      [-66.6, 17.98],\n      [-67.18, 17.95],\n      [-67.24, 18.37],\n      [-67.1, 18.52],\n      [-66.28, 18.51]\n    ],\n    [\n      [130.64, 42.4],\n      [130.78, 42.22],\n      [130.4, 42.28],\n      [129.97, 41.94],\n      [129.67, 41.6],\n      [129.71, 40.88],\n      [129.19, 40.66],\n      [129.01, 40.49],\n      [128.63, 40.19],\n      [127.97, 40.03],\n      [127.53, 39.76],\n      [127.5, 39.32],\n      [127.39, 39.21],\n      [127.78, 39.05],\n      [128.35, 38.61]\n    ],\n    [\n      [126.17, 37.75],\n      [125.69, 37.94],\n      [125.57, 37.75],\n      [125.28, 37.67],\n      [125.24, 37.86],\n      [124.98, 37.95],\n      [124.71, 38.11],\n      [124.99, 38.55],\n      [125.22, 38.67],\n      [125.13, 38.85],\n      [125.39, 39.39],\n      [125.32, 39.55],\n      [124.74, 39.66],\n      [124.27, 39.93]\n    ],\n    [\n      [-7.45, 37.1],\n      [-7.86, 36.84],\n      [-8.38, 36.98],\n      [-8.9, 36.87],\n      [-8.75, 37.65],\n      [-8.84, 38.27],\n      [-9.29, 38.36],\n      [-9.53, 38.74],\n      [-9.45, 39.39],\n      [-9.05, 39.76],\n      [-8.98, 40.16],\n      [-8.77, 40.76],\n      [-8.79, 41.18],\n      [-8.99, 41.54],\n      [-9.03, 41.88]\n    ],\n    [\n      [50.81, 24.75],\n      [50.74, 25.48],\n      [51.01, 26.01],\n      [51.29, 26.11],\n      [51.59, 25.8],\n      [51.61, 25.22],\n      [51.39, 24.63]\n    ],\n    [\n      [51.39, 24.63],\n      [51.11, 24.56],\n      [50.81, 24.75]\n    ],\n    [\n      [22.71, 47.88],\n      [23.14, 48.1],\n      [23.76, 47.99],\n      [24.4, 47.98],\n      [24.87, 47.74],\n      [25.21, 47.89],\n      [25.95, 47.99],\n      [26.2, 48.22],\n      [26.62, 48.22]\n    ],\n    [\n      [28.23, 45.49],\n      [28.68, 45.3],\n      [29.15, 45.46],\n      [29.6, 45.29]\n    ],\n    [\n      [29.6, 45.29],\n      [29.63, 45.04],\n      [29.14, 44.82],\n      [28.84, 44.91],\n      [28.56, 43.71]\n    ],\n    [\n      [22.66, 44.23],\n      [22.47, 44.41],\n      [22.71, 44.58],\n      [22.46, 44.7],\n      [22.15, 44.48],\n      [21.56, 44.77],\n      [21.48, 45.18],\n      [20.87, 45.42],\n      [20.76, 45.73],\n      [20.22, 46.13]\n    ],\n    [\n      [143.65, 50.75],\n      [144.65, 48.98],\n      [143.17, 49.31],\n      [142.56, 47.86],\n      [143.53, 46.84],\n      [143.51, 46.14],\n      [142.75, 46.74],\n      [142.09, 45.97],\n      [141.91, 46.81],\n      [142.02, 47.78],\n      [141.9, 48.86],\n      [142.14, 49.62],\n      [142.18, 50.95],\n      [141.59, 51.94],\n      [141.68, 53.3],\n      [142.61, 53.76],\n      [142.21, 54.23],\n      [142.65, 54.37],\n      [142.91, 53.7],\n      [143.26, 52.74],\n      [143.24, 51.76],\n      [143.65, 50.75]\n    ],\n    [\n      [19.66, 54.43],\n      [19.89, 54.87],\n      [21.27, 55.19]\n    ],\n    [\n      [-175.01, 66.58],\n      [-174.34, 66.34],\n      [-174.57, 67.06],\n      [-171.86, 66.91],\n      [-169.9, 65.98],\n      [-170.89, 65.54],\n      [-172.53, 65.44],\n      [-172.55, 64.46],\n      [-172.96, 64.25],\n      [-173.89, 64.28],\n      [-174.65, 64.63],\n      [-175.98, 64.92],\n      [-176.21, 65.36],\n      [-177.22, 65.52],\n      [-178.36, 65.39],\n      [-178.9, 65.74],\n      [-178.69, 66.11],\n      [-179.88, 65.87],\n      [-179.43, 65.4],\n      [-180, 64.98],\n      [-180, 68.96],\n      [-177.55, 68.2],\n      [-174.93, 67.21],\n      [-175.01, 66.58]\n    ],\n    [\n      [180, 70.83],\n      [178.9, 70.78],\n      [178.73, 71.1],\n      [180, 71.52],\n      [180, 70.83]\n    ],\n    [\n      [-178.69, 70.89],\n      [-180, 70.83],\n      [-180, 71.52],\n      [-179.87, 71.56],\n      [-179.02, 71.56],\n      [-177.58, 71.27],\n      [-177.66, 71.13],\n      [-178.69, 70.89]\n    ],\n    [\n      [143.6, 73.21],\n      [142.09, 73.21],\n      [140.04, 73.32],\n      [139.86, 73.37],\n      [140.81, 73.77],\n      [142.06, 73.86],\n      [143.48, 73.48],\n      [143.6, 73.21]\n    ],\n    [\n      [150.73, 75.08],\n      [149.58, 74.69],\n      [147.98, 74.78],\n      [146.12, 75.17],\n      [146.36, 75.5],\n      [148.22, 75.35],\n      [150.73, 75.08]\n    ],\n    [\n      [145.09, 75.56],\n      [144.3, 74.82],\n      [140.61, 74.85],\n      [138.96, 74.61],\n      [136.97, 75.26],\n      [137.51, 75.95],\n      [138.83, 76.14],\n      [141.47, 76.09],\n      [145.09, 75.56]\n    ],\n    [\n      [57.54, 70.72],\n      [56.94, 70.63],\n      [53.68, 70.76],\n      [53.41, 71.21],\n      [51.6, 71.47],\n      [51.46, 72.01],\n      [52.48, 72.23],\n      [52.44, 72.77],\n      [54.43, 73.63],\n      [53.51, 73.75],\n      [55.9, 74.63],\n      [55.63, 75.08],\n      [57.87, 75.61],\n      [61.17, 76.25],\n      [64.5, 76.44],\n      [66.21, 76.81],\n      [68.16, 76.94],\n      [68.85, 76.54],\n      [68.18, 76.23],\n      [64.64, 75.74],\n      [61.58, 75.26],\n      [58.48, 74.31],\n      [56.99, 73.33],\n      [55.42, 72.37],\n      [55.62, 71.54],\n      [57.54, 70.72]\n    ],\n    [\n      [131.29, 44.11],\n      [131.03, 44.97],\n      [131.88, 45.32]\n    ],\n    [\n      [131.88, 45.32],\n      [133.1, 45.14],\n      [133.77, 46.12]\n    ],\n    [\n      [133.77, 46.12],\n      [134.11, 47.21],\n      [134.5, 47.58],\n      [135.03, 48.48]\n    ],\n    [\n      [133.37, 48.18],\n      [132.51, 47.79],\n      [130.99, 47.79],\n      [130.58, 48.73]\n    ],\n    [\n      [129.4, 49.44],\n      [127.66, 49.76],\n      [127.29, 50.74]\n    ],\n    [\n      [125.07, 53.16],\n      [123.57, 53.46],\n      [122.25, 53.43]\n    ],\n    [\n      [120.73, 52.52],\n      [120.74, 51.96],\n      [120.18, 51.64],\n      [119.28, 50.58],\n      [119.29, 50.14]\n    ],\n    [\n      [105.89, 50.41],\n      [104.62, 50.28],\n      [103.68, 50.09]\n    ],\n    [\n      [103.68, 50.09],\n      [102.26, 50.51],\n      [102.07, 51.26],\n      [100.89, 51.52]\n    ],\n    [\n      [98.23, 50.42],\n      [97.26, 49.73],\n      [95.81, 49.98],\n      [94.82, 50.01]\n    ],\n    [\n      [94.15, 50.48],\n      [93.1, 50.5],\n      [92.23, 50.8]\n    ],\n    [\n      [76.89, 54.49],\n      [74.38, 53.55],\n      [73.43, 53.49]\n    ],\n    [\n      [68.17, 54.97],\n      [65.67, 54.6],\n      [65.18, 54.35]\n    ],\n    [\n      [65.18, 54.35],\n      [61.44, 54.01],\n      [60.98, 53.66]\n    ],\n    [\n      [59.64, 50.55],\n      [58.36, 51.06],\n      [56.78, 51.04],\n      [55.72, 50.62],\n      [54.53, 51.03]\n    ],\n    [\n      [46.47, 48.39],\n      [47.32, 47.72],\n      [48.06, 47.74],\n      [48.69, 47.08]\n    ],\n    [\n      [48.69, 47.08],\n      [48.59, 46.56],\n      [49.1, 46.4]\n    ],\n    [\n      [49.1, 46.4],\n      [48.65, 45.81],\n      [47.68, 45.64],\n      [46.68, 44.61],\n      [47.59, 43.66],\n      [47.49, 42.99],\n      [48.58, 41.81],\n      [47.99, 41.41]\n    ],\n    [\n      [46.4, 41.86],\n      [45.78, 42.09],\n      [45.47, 42.5]\n    ],\n    [\n      [44.54, 42.71],\n      [43.93, 42.55],\n      [43.76, 42.74],\n      [42.39, 43.22],\n      [40.92, 43.38],\n      [40.08, 43.55]\n    ],\n    [\n      [39.96, 43.43],\n      [38.68, 44.28],\n      [37.54, 44.66],\n      [36.68, 45.24],\n      [37.4, 45.4],\n      [38.23, 46.24],\n      [37.67, 46.64],\n      [39.15, 47.04],\n      [39.12, 47.26],\n      [38.22, 47.1]\n    ],\n    [\n      [38.22, 47.1],\n      [38.26, 47.55]\n    ],\n    [\n      [38.26, 47.55],\n      [38.77, 47.83],\n      [39.74, 47.9]\n    ],\n    [\n      [39.74, 47.9],\n      [39.9, 48.23],\n      [39.67, 48.78],\n      [40.08, 49.31]\n    ],\n    [\n      [40.08, 49.31],\n      [40.07, 49.6],\n      [38.59, 49.93]\n    ],\n    [\n      [38.59, 49.93],\n      [38.01, 49.92],\n      [37.39, 50.38],\n      [36.63, 50.23],\n      [35.36, 50.58]\n    ],\n    [\n      [35.36, 50.58],\n      [35.38, 50.77],\n      [35.02, 51.21]\n    ],\n    [\n      [35.02, 51.21],\n      [34.22, 51.26],\n      [34.14, 51.57],\n      [34.39, 51.77],\n      [33.75, 52.34],\n      [32.72, 52.24],\n      [32.41, 52.29]\n    ],\n    [\n      [32.41, 52.29],\n      [32.16, 52.06],\n      [31.79, 52.1],\n      [31.54, 52.74]\n    ],\n    [\n      [31.31, 53.07],\n      [31.5, 53.17],\n      [32.3, 53.13]\n    ],\n    [\n      [27.72, 57.79],\n      [27.42, 58.72],\n      [28.13, 59.3]\n    ],\n    [\n      [28.13, 59.3],\n      [27.98, 59.48],\n      [29.12, 60.03],\n      [28.07, 60.5],\n      [30.21, 61.78]\n    ],\n    [\n      [28.59, 69.06],\n      [29.4, 69.16],\n      [31.1, 69.56],\n      [32.13, 69.91],\n      [33.78, 69.3],\n      [36.51, 69.06],\n      [40.29, 67.93],\n      [41.06, 67.46],\n      [41.13, 66.79],\n      [40.02, 66.27],\n      [38.38, 66],\n      [33.92, 66.76],\n      [33.18, 66.63],\n      [34.81, 65.9],\n      [34.88, 65.44],\n      [34.94, 64.41],\n      [36.23, 64.11],\n      [37.01, 63.85],\n      [37.14, 64.33],\n      [36.54, 64.76],\n      [37.18, 65.14],\n      [39.59, 64.52],\n      [40.44, 64.76],\n      [39.76, 65.5],\n      [42.09, 66.48],\n      [43.02, 66.42],\n      [43.95, 66.07],\n      [44.53, 66.76],\n      [43.7, 67.35],\n      [44.19, 67.95],\n      [43.45, 68.57],\n      [46.25, 68.25],\n      [46.82, 67.69],\n      [45.56, 67.57],\n      [45.56, 67.01],\n      [46.35, 66.67],\n      [47.89, 66.88],\n      [48.14, 67.52],\n      [50.23, 68],\n      [53.72, 68.86],\n      [54.47, 68.81],\n      [53.49, 68.2],\n      [54.73, 68.1],\n      [55.44, 68.44],\n      [57.32, 68.47],\n      [58.8, 68.88],\n      [59.94, 68.28],\n      [61.08, 68.94],\n      [60.03, 69.52],\n      [60.55, 69.85],\n      [63.5, 69.55],\n      [64.89, 69.23],\n      [68.51, 68.09],\n      [69.18, 68.62],\n      [68.16, 69.14],\n      [68.14, 69.36],\n      [66.93, 69.45],\n      [67.26, 69.93],\n      [66.72, 70.71],\n      [66.69, 71.03],\n      [68.54, 71.93],\n      [69.2, 72.84],\n      [69.94, 73.04],\n      [72.59, 72.78],\n      [72.8, 72.22],\n      [71.85, 71.41],\n      [72.47, 71.09],\n      [72.79, 70.39],\n      [72.56, 69.02],\n      [73.67, 68.41],\n      [73.24, 67.74],\n      [71.28, 66.32],\n      [72.42, 66.17],\n      [72.82, 66.53],\n      [73.92, 66.79],\n      [74.19, 67.28],\n      [75.05, 67.76],\n      [74.47, 68.33],\n      [74.94, 68.99],\n      [73.84, 69.07],\n      [73.6, 69.63],\n      [74.4, 70.63],\n      [73.1, 71.45],\n      [74.89, 72.12],\n      [74.66, 72.83],\n      [75.16, 72.85],\n      [75.68, 72.3],\n      [75.29, 71.34],\n      [76.36, 71.15],\n      [75.9, 71.87],\n      [77.58, 72.27],\n      [79.65, 72.32],\n      [81.5, 71.75],\n      [80.61, 72.58],\n      [80.51, 73.65],\n      [82.25, 73.85],\n      [84.66, 73.81],\n      [86.82, 73.94],\n      [86.01, 74.46],\n      [87.17, 75.12],\n      [88.32, 75.14],\n      [90.26, 75.64],\n      [92.9, 75.77],\n      [93.23, 76.05],\n      [95.86, 76.14],\n      [96.68, 75.92],\n      [98.92, 76.45],\n      [100.76, 76.43],\n      [101.04, 76.86],\n      [101.99, 77.29],\n      [104.35, 77.7],\n      [106.07, 77.37],\n      [104.71, 77.13],\n      [106.97, 76.97],\n      [107.24, 76.48],\n      [108.15, 76.72],\n      [111.08, 76.71],\n      [113.33, 76.22],\n      [114.13, 75.85],\n      [113.89, 75.33],\n      [112.78, 75.03],\n      [110.15, 74.48],\n      [109.4, 74.18],\n      [110.64, 74.04],\n      [112.12, 73.79],\n      [113.02, 73.98],\n      [113.53, 73.34],\n      [113.97, 73.59],\n      [115.57, 73.75],\n      [118.78, 73.59],\n      [119.02, 73.12],\n      [123.2, 72.97],\n      [123.26, 73.74],\n      [125.38, 73.56],\n      [126.98, 73.57],\n      [128.59, 73.04],\n      [129.05, 72.4],\n      [128.46, 71.98],\n      [129.72, 71.19],\n      [131.29, 70.79],\n      [132.25, 71.84],\n      [133.86, 71.39],\n      [135.56, 71.66],\n      [137.5, 71.35],\n      [138.23, 71.63],\n      [139.87, 71.49],\n      [139.15, 72.42],\n      [140.47, 72.85],\n      [149.5, 72.2],\n      [150.35, 71.61],\n      [152.97, 70.84],\n      [157.01, 71.03],\n      [159, 70.87],\n      [159.83, 70.45],\n      [159.71, 69.72],\n      [160.94, 69.44],\n      [162.28, 69.64],\n      [164.05, 69.67],\n      [165.94, 69.47],\n      [167.84, 69.58],\n      [169.58, 68.69],\n      [170.82, 69.01],\n      [170.01, 69.65],\n      [170.45, 70.1],\n      [173.64, 69.82],\n      [175.72, 69.88],\n      [178.6, 69.4],\n      [180, 68.96],\n      [180, 64.98],\n      [179.99, 64.97],\n      [178.71, 64.53],\n      [177.41, 64.61],\n      [178.31, 64.08],\n      [178.91, 63.25],\n      [179.37, 62.98],\n      [179.49, 62.57],\n      [179.23, 62.3],\n      [177.36, 62.52],\n      [174.57, 61.77],\n      [173.68, 61.65],\n      [172.15, 60.95],\n      [170.7, 60.34],\n      [170.33, 59.88],\n      [168.9, 60.57],\n      [166.29, 59.79],\n      [165.84, 60.16],\n      [164.88, 59.73],\n      [163.54, 59.87],\n      [163.22, 59.21],\n      [162.02, 58.24],\n      [162.05, 57.84],\n      [163.19, 57.62],\n      [163.06, 56.16],\n      [162.13, 56.12],\n      [161.7, 55.29],\n      [162.12, 54.86],\n      [160.37, 54.34],\n      [160.02, 53.2],\n      [158.53, 52.96],\n      [158.23, 51.94],\n      [156.79, 51.01],\n      [156.42, 51.7],\n      [155.99, 53.16],\n      [155.43, 55.38],\n      [155.91, 56.77],\n      [156.76, 57.36],\n      [156.81, 57.83],\n      [158.36, 58.06],\n      [160.15, 59.31],\n      [161.87, 60.34],\n      [163.67, 61.14],\n      [164.47, 62.55],\n      [163.26, 62.47],\n      [162.66, 61.64],\n      [160.12, 60.54],\n      [159.3, 61.77],\n      [156.72, 61.43],\n      [154.22, 59.76],\n      [155.04, 59.14],\n      [152.81, 58.88],\n      [151.27, 58.78],\n      [151.34, 59.5],\n      [149.78, 59.66],\n      [148.54, 59.16],\n      [145.49, 59.34],\n      [142.2, 59.04],\n      [138.96, 57.09],\n      [135.13, 54.73],\n      [136.7, 54.6],\n      [137.19, 53.98],\n      [138.16, 53.76],\n      [138.8, 54.25],\n      [139.9, 54.19],\n      [141.35, 53.09],\n      [141.38, 52.24],\n      [140.6, 51.24],\n      [140.51, 50.05],\n      [140.06, 48.45],\n      [138.55, 47],\n      [138.22, 46.31],\n      [136.86, 45.14],\n      [135.52, 43.99],\n      [134.87, 43.4],\n      [133.54, 42.81],\n      [132.91, 42.8],\n      [132.28, 43.28],\n      [130.94, 42.55],\n      [130.78, 42.22],\n      [130.64, 42.4],\n      [130.63, 42.9]\n    ],\n    [\n      [105.08, 78.31],\n      [99.44, 77.92],\n      [101.26, 79.23],\n      [102.09, 79.35],\n      [102.84, 79.28],\n      [105.37, 78.71],\n      [105.08, 78.31]\n    ],\n    [\n      [51.14, 80.55],\n      [49.79, 80.42],\n      [48.89, 80.34],\n      [48.75, 80.18],\n      [47.59, 80.01],\n      [46.5, 80.25],\n      [47.07, 80.56],\n      [44.85, 80.59],\n      [46.8, 80.77],\n      [48.32, 80.78],\n      [48.52, 80.51],\n      [49.1, 80.75],\n      [50.04, 80.92],\n      [51.52, 80.7],\n      [51.14, 80.55]\n    ],\n    [\n      [99.94, 78.88],\n      [97.76, 78.76],\n      [94.97, 79.04],\n      [93.31, 79.43],\n      [92.55, 80.14],\n      [91.18, 80.34],\n      [93.78, 81.02],\n      [95.94, 81.25],\n      [97.88, 80.75],\n      [100.19, 79.78],\n      [99.94, 78.88]\n    ],\n    [\n      [30.42, -1.13],\n      [30.82, -1.7],\n      [30.76, -2.29]\n    ],\n    [\n      [30.76, -2.29],\n      [30.47, -2.41]\n    ],\n    [\n      [29.58, -1.34],\n      [29.82, -1.44],\n      [30.42, -1.13]\n    ],\n    [\n      [-17.06, 21],\n      [-17.02, 21.41],\n      [-16.98, 21.89],\n      [-16.58, 22.16],\n      [-16.26, 22.68],\n      [-16.33, 23.02],\n      [-15.99, 23.73],\n      [-15.43, 24.36],\n      [-15.09, 24.52],\n      [-14.83, 25.1],\n      [-14.8, 25.64],\n      [-14.44, 26.25],\n      [-13.78, 26.62],\n      [-13.41, 27.2],\n      [-13.26, 27.44],\n      [-13.1, 27.6]\n    ],\n    [\n      [42.78, 16.35],\n      [42.65, 16.77],\n      [42.35, 17.08],\n      [42.27, 17.47],\n      [41.75, 17.83],\n      [41.22, 18.67],\n      [40.94, 19.49],\n      [40.25, 20.17],\n      [39.8, 20.34],\n      [39.14, 21.29],\n      [39.02, 21.99],\n      [39.07, 22.58],\n      [38.49, 23.69],\n      [38.02, 24.08],\n      [37.48, 24.29],\n      [37.15, 24.86],\n      [37.21, 25.08],\n      [36.93, 25.6],\n      [36.64, 25.83],\n      [36.25, 26.57],\n      [35.64, 27.38],\n      [35.13, 28.06],\n      [34.63, 28.06],\n      [34.79, 28.61],\n      [34.83, 28.96],\n      [34.96, 29.36]\n    ],\n    [\n      [48.42, 28.55],\n      [48.81, 27.69],\n      [49.3, 27.46],\n      [49.47, 27.11],\n      [50.15, 26.69],\n      [50.21, 26.28],\n      [50.11, 25.94],\n      [50.24, 25.61],\n      [50.53, 25.33],\n      [50.66, 25],\n      [50.81, 24.75]\n    ],\n    [\n      [51.39, 24.63],\n      [51.58, 24.25]\n    ],\n    [\n      [52, 19],\n      [49.12, 18.62],\n      [48.18, 18.17],\n      [47.47, 17.12],\n      [47, 16.95],\n      [46.75, 17.28],\n      [46.37, 17.23],\n      [45.4, 17.33],\n      [45.22, 17.43],\n      [44.06, 17.41],\n      [43.79, 17.32],\n      [43.38, 17.58],\n      [43.12, 17.09],\n      [43.22, 16.67],\n      [42.78, 16.35]\n    ],\n    [\n      [33.96, 9.46],\n      [33.82, 9.48],\n      [33.84, 9.98],\n      [33.72, 10.33],\n      [33.21, 10.72],\n      [33.09, 11.44],\n      [33.21, 12.18],\n      [32.74, 12.25],\n      [32.67, 12.02],\n      [32.07, 11.97],\n      [32.31, 11.68],\n      [32.4, 11.08],\n      [31.85, 10.53],\n      [31.35, 9.81],\n      [30.84, 9.71],\n      [30, 10.29],\n      [29.62, 10.08],\n      [29.52, 9.79],\n      [29, 9.6],\n      [28.97, 9.4],\n      [27.97, 9.4],\n      [27.83, 9.6],\n      [27.11, 9.64],\n      [26.75, 9.47],\n      [26.48, 9.55],\n      [25.96, 10.14],\n      [25.79, 10.41],\n      [25.07, 10.27],\n      [24.79, 9.81],\n      [24.54, 8.92],\n      [24.19, 8.73],\n      [23.89, 8.62]\n    ],\n    [\n      [23.89, 8.62],\n      [23.81, 8.67]\n    ],\n    [\n      [22.86, 11.14],\n      [22.88, 11.38],\n      [22.51, 11.68],\n      [22.5, 12.26],\n      [22.29, 12.65],\n      [21.94, 12.59],\n      [22.04, 12.96],\n      [22.3, 13.37],\n      [22.18, 13.79],\n      [22.51, 14.09],\n      [22.3, 14.33],\n      [22.57, 14.94],\n      [23.02, 15.68],\n      [23.89, 15.61],\n      [23.84, 19.58]\n    ],\n    [\n      [36.87, 22],\n      [37.19, 21.02],\n      [36.97, 20.84],\n      [37.11, 19.81],\n      [37.48, 18.61],\n      [37.86, 18.37],\n      [38.41, 18]\n    ],\n    [\n      [36.85, 16.96],\n      [36.75, 16.29],\n      [36.32, 14.82],\n      [36.43, 14.42]\n    ],\n    [\n      [33.96, 9.58],\n      [33.96, 9.46]\n    ],\n    [\n      [33.96, 9.46],\n      [33.97, 8.68]\n    ],\n    [\n      [34.01, 4.25],\n      [33.39, 3.79],\n      [32.69, 3.79],\n      [31.88, 3.56],\n      [31.25, 3.78],\n      [30.83, 3.51]\n    ],\n    [\n      [30.83, 3.51],\n      [29.95, 4.17],\n      [29.72, 4.6]\n    ],\n    [\n      [24.57, 8.23],\n      [23.89, 8.62]\n    ],\n    [\n      [-16.71, 13.59],\n      [-17.13, 14.37],\n      [-17.63, 14.73],\n      [-17.19, 14.92],\n      [-16.7, 15.62],\n      [-16.46, 16.14]\n    ],\n    [\n      [-16.68, 12.38],\n      [-16.84, 13.15]\n    ],\n    [\n      [162.12, -10.48],\n      [162.4, -10.83],\n      [161.7, -10.82],\n      [161.32, -10.2],\n      [161.92, -10.45],\n      [162.12, -10.48]\n    ],\n    [\n      [160.85, -9.87],\n      [160.46, -9.9],\n      [159.85, -9.79],\n      [159.64, -9.64],\n      [159.7, -9.24],\n      [160.36, -9.4],\n      [160.69, -9.61],\n      [160.85, -9.87]\n    ],\n    [\n      [161.68, -9.6],\n      [161.53, -9.78],\n      [160.79, -8.92],\n      [160.58, -8.32],\n      [160.92, -8.32],\n      [161.28, -9.12],\n      [161.68, -9.6]\n    ],\n    [\n      [159.88, -8.34],\n      [159.92, -8.54],\n      [159.13, -8.11],\n      [158.59, -7.75],\n      [158.21, -7.42],\n      [158.36, -7.32],\n      [158.82, -7.56],\n      [159.64, -8.02],\n      [159.88, -8.34]\n    ],\n    [\n      [157.54, -7.35],\n      [157.34, -7.4],\n      [156.9, -7.18],\n      [156.49, -6.77],\n      [156.54, -6.6],\n      [157.14, -7.02],\n      [157.54, -7.35]\n    ],\n    [\n      [-11.44, 6.79],\n      [-11.71, 6.86],\n      [-12.43, 7.26],\n      [-12.95, 7.8],\n      [-13.12, 8.16],\n      [-13.25, 8.9]\n    ],\n    [\n      [-87.79, 13.38],\n      [-87.9, 13.15],\n      [-88.48, 13.16],\n      [-88.84, 13.26],\n      [-89.26, 13.46],\n      [-89.81, 13.52],\n      [-90.1, 13.74]\n    ],\n    [\n      [47.79, 8],\n      [46.95, 8],\n      [43.68, 9.18]\n    ],\n    [\n      [43.68, 9.18],\n      [43.3, 9.54],\n      [42.93, 10.02]\n    ],\n    [\n      [43.15, 11.46],\n      [43.47, 11.28],\n      [43.67, 10.86],\n      [44.12, 10.45],\n      [44.61, 10.44],\n      [45.56, 10.7],\n      [46.65, 10.82],\n      [47.53, 11.13],\n      [48.02, 11.19],\n      [48.38, 11.38],\n      [48.95, 11.41],\n      [48.94, 11.39],\n      [48.95, 11.41],\n      [49.27, 11.43],\n      [49.73, 11.58],\n      [50.26, 11.68],\n      [50.73, 12.02],\n      [51.11, 12.02],\n      [51.13, 11.75],\n      [51.04, 11.17],\n      [51.05, 10.64],\n      [50.83, 10.28],\n      [50.55, 9.2],\n      [50.07, 8.08],\n      [49.45, 6.8],\n      [48.59, 5.34],\n      [47.74, 4.22],\n      [46.56, 2.86],\n      [45.56, 2.05],\n      [44.07, 1.05],\n      [43.14, 0.29],\n      [42.04, -0.92],\n      [41.81, -1.45],\n      [41.59, -1.68]\n    ],\n    [\n      [19.37, 44.86],\n      [19.01, 44.86],\n      [19.39, 45.24]\n    ],\n    [\n      [19.07, 45.52],\n      [18.83, 45.91],\n      [19.6, 46.17]\n    ],\n    [\n      [-57.15, 5.97],\n      [-55.95, 5.77],\n      [-55.84, 5.95],\n      [-55.03, 6.03],\n      [-53.96, 5.76]\n    ],\n    [\n      [-54.4, 4.21],\n      [-54.01, 3.62],\n      [-54.18, 3.19],\n      [-54.27, 2.73],\n      [-54.52, 2.31]\n    ],\n    [\n      [22.56, 49.09],\n      [22.28, 48.83],\n      [22.09, 48.42]\n    ],\n    [\n      [13.72, 45.5],\n      [13.94, 45.59]\n    ],\n    [\n      [23.9, 66.01],\n      [22.18, 65.72],\n      [21.21, 65.03],\n      [21.37, 64.41],\n      [19.78, 63.61],\n      [17.85, 62.75],\n      [17.12, 61.34],\n      [17.83, 60.64],\n      [18.79, 60.08],\n      [17.87, 58.95],\n      [16.83, 58.72],\n      [16.45, 57.04],\n      [15.88, 56.1],\n      [14.67, 56.2],\n      [14.1, 55.41],\n      [12.94, 55.36],\n      [12.63, 56.31],\n      [11.79, 57.44],\n      [11.03, 58.86]\n    ],\n    [\n      [32.07, -26.73],\n      [31.87, -27.18],\n      [31.28, -27.29],\n      [30.69, -26.74],\n      [30.68, -26.4],\n      [30.95, -26.02],\n      [31.04, -25.73],\n      [31.33, -25.66],\n      [31.84, -25.84]\n    ],\n    [\n      [36, 34.64],\n      [35.91, 35.41],\n      [36.15, 35.82]\n    ],\n    [\n      [36.15, 35.82],\n      [36.42, 36.04],\n      [36.69, 36.26],\n      [36.74, 36.82],\n      [37.07, 36.62],\n      [38.17, 36.9],\n      [38.7, 36.71],\n      [39.52, 36.72],\n      [40.67, 37.09],\n      [41.21, 37.07],\n      [42.35, 37.23]\n    ],\n    [\n      [13.54, 14.37],\n      [13.97, 15.68],\n      [15.25, 16.63]\n    ],\n    [\n      [15.49, 20.73],\n      [15.47, 21.05],\n      [15.1, 21.31]\n    ],\n    [\n      [14.96, 11.56],\n      [14.89, 12.22],\n      [14.5, 12.86]\n    ],\n    [\n      [1.87, 6.14],\n      [1.06, 5.93]\n    ],\n    [\n      [102.58, 12.19],\n      [101.69, 12.65],\n      [100.83, 12.63],\n      [100.98, 13.41],\n      [100.1, 13.41],\n      [100.02, 12.31],\n      [99.48, 10.85],\n      [99.15, 9.96],\n      [99.22, 9.24],\n      [99.87, 9.21],\n      [100.28, 8.3],\n      [100.46, 7.43],\n      [101.02, 6.86],\n      [101.62, 6.74],\n      [102.14, 6.22]\n    ],\n    [\n      [100.09, 6.46],\n      [99.69, 6.85],\n      [99.52, 7.34],\n      [98.99, 7.91],\n      [98.5, 8.38],\n      [98.34, 7.79],\n      [98.15, 8.35],\n      [98.26, 8.97],\n      [98.55, 9.93]\n    ],\n    [\n      [67.83, 37.14],\n      [68.39, 38.16],\n      [68.18, 38.9],\n      [67.44, 39.14],\n      [67.7, 39.58],\n      [68.54, 39.53],\n      [69.01, 40.09],\n      [69.33, 40.73],\n      [70.67, 40.96],\n      [70.46, 40.5],\n      [70.6, 40.22],\n      [71.01, 40.24]\n    ],\n    [\n      [53.92, 37.2],\n      [53.74, 37.91],\n      [53.88, 38.95],\n      [53.1, 39.29],\n      [53.36, 39.98],\n      [52.69, 40.03],\n      [52.92, 40.88],\n      [53.86, 40.63],\n      [54.74, 40.95],\n      [54.01, 41.55],\n      [53.72, 42.12],\n      [52.92, 41.87],\n      [52.81, 41.14],\n      [52.5, 41.78]\n    ],\n    [\n      [55.97, 41.31],\n      [57.1, 41.32],\n      [56.93, 41.83],\n      [57.79, 42.17],\n      [58.63, 42.75],\n      [59.98, 42.22],\n      [60.08, 41.43],\n      [60.47, 41.22],\n      [61.55, 41.27],\n      [61.88, 41.08],\n      [62.37, 40.05],\n      [63.52, 39.36],\n      [64.17, 38.89],\n      [65.22, 38.4],\n      [66.55, 37.97],\n      [66.52, 37.36]\n    ],\n    [\n      [124.97, -8.89],\n      [125.09, -8.66],\n      [125.95, -8.43],\n      [126.64, -8.4],\n      [126.96, -8.27],\n      [127.34, -8.4],\n      [126.97, -8.67],\n      [125.93, -9.11],\n      [125.09, -9.39]\n    ],\n    [\n      [-61.68, 10.76],\n      [-61.1, 10.89],\n      [-60.89, 10.86],\n      [-60.93, 10.11],\n      [-61.77, 10],\n      [-61.95, 10.09],\n      [-61.66, 10.37],\n      [-61.68, 10.76]\n    ],\n    [\n      [8.42, 36.95],\n      [9.51, 37.35],\n      [10.21, 37.23],\n      [10.18, 36.72],\n      [11.03, 37.09],\n      [11.1, 36.9],\n      [10.6, 36.41],\n      [10.59, 35.95],\n      [10.94, 35.7],\n      [10.81, 34.83],\n      [10.15, 34.33],\n      [10.34, 33.79],\n      [10.86, 33.77],\n      [11.11, 33.29],\n      [11.49, 33.14]\n    ],\n    [\n      [44.23, 37.97],\n      [44.77, 37.17]\n    ],\n    [\n      [36.15, 35.82],\n      [35.78, 36.27],\n      [36.16, 36.65],\n      [35.55, 36.57],\n      [34.71, 36.8],\n      [34.03, 36.22],\n      [32.51, 36.11],\n      [31.7, 36.64],\n      [30.62, 36.68],\n      [30.39, 36.26],\n      [29.7, 36.14],\n      [28.73, 36.68],\n      [27.64, 36.66],\n      [27.05, 37.65],\n      [26.32, 38.21],\n      [26.8, 38.99],\n      [26.17, 39.46],\n      [27.28, 40.42],\n      [28.82, 40.46],\n      [29.24, 41.22],\n      [31.15, 41.09],\n      [32.35, 41.74],\n      [33.51, 42.02],\n      [35.17, 42.04],\n      [36.91, 41.34],\n      [38.35, 40.95],\n      [39.51, 41.1],\n      [40.37, 41.01],\n      [41.55, 41.54]\n    ],\n    [\n      [28, 42.01],\n      [28.12, 41.62],\n      [28.99, 41.3],\n      [28.81, 41.05],\n      [27.62, 41],\n      [27.19, 40.69],\n      [26.36, 40.15],\n      [26.04, 40.62],\n      [26.06, 40.82]\n    ],\n    [\n      [121.78, 24.39],\n      [121.18, 22.79],\n      [120.75, 21.97],\n      [120.22, 22.81],\n      [120.11, 23.56],\n      [120.69, 24.54],\n      [121.5, 25.3],\n      [121.95, 25],\n      [121.78, 24.39]\n    ],\n    [\n      [39.2, -4.68],\n      [38.74, -5.91],\n      [38.8, -6.48],\n      [39.44, -6.84],\n      [39.47, -7.1],\n      [39.19, -7.7],\n      [39.25, -8.01],\n      [39.19, -8.49],\n      [39.54, -9.11],\n      [39.95, -10.1],\n      [40.32, -10.32],\n      [39.52, -10.9],\n      [38.43, -11.29]\n    ],\n    [\n      [38.43, -11.29],\n      [37.83, -11.27],\n      [37.47, -11.57],\n      [36.78, -11.59]\n    ],\n    [\n      [34.56, -11.52],\n      [34.28, -10.16],\n      [33.94, -9.69]\n    ],\n    [\n      [33.94, -9.69],\n      [33.74, -9.42],\n      [32.76, -9.23]\n    ],\n    [\n      [32.76, -9.23],\n      [32.19, -8.93],\n      [31.56, -8.76],\n      [31.16, -8.59]\n    ],\n    [\n      [31.16, -8.59],\n      [30.74, -8.34],\n      [30.2, -7.08],\n      [29.62, -6.52],\n      [29.42, -5.94]\n    ],\n    [\n      [29.75, -4.45],\n      [30.12, -4.09],\n      [30.51, -3.57],\n      [30.75, -3.36],\n      [30.74, -3.03],\n      [30.53, -2.81],\n      [30.47, -2.41],\n      [30.76, -2.29]\n    ],\n    [\n      [30.42, -1.13],\n      [30.77, -1.01],\n      [31.87, -1.03],\n      [33.9, -0.95]\n    ],\n    [\n      [29.59, -0.59],\n      [29.82, -0.21],\n      [29.88, 0.6]\n    ],\n    [\n      [31.17, 2.2],\n      [30.77, 2.34],\n      [30.83, 3.51]\n    ],\n    [\n      [31.79, 52.1],\n      [32.16, 52.06],\n      [32.41, 52.29]\n    ],\n    [\n      [35.02, 51.21],\n      [35.38, 50.77],\n      [35.36, 50.58]\n    ],\n    [\n      [38.59, 49.93],\n      [40.07, 49.6],\n      [40.08, 49.31]\n    ],\n    [\n      [40.08, 49.31],\n      [39.67, 48.78],\n      [39.9, 48.23],\n      [39.74, 47.9]\n    ],\n    [\n      [39.74, 47.9],\n      [38.77, 47.83],\n      [38.26, 47.55]\n    ],\n    [\n      [38.22, 47.1],\n      [37.43, 47.02],\n      [36.76, 46.7],\n      [35.82, 46.65],\n      [34.96, 46.27],\n      [35.02, 45.65],\n      [35.51, 45.41],\n      [36.53, 45.47],\n      [36.33, 45.11],\n      [35.24, 44.94],\n      [33.88, 44.36],\n      [33.33, 44.56],\n      [33.55, 45.03],\n      [32.45, 45.33],\n      [32.63, 45.52],\n      [33.59, 45.85],\n      [33.3, 46.08],\n      [31.74, 46.33],\n      [31.68, 46.71],\n      [30.75, 46.58],\n      [30.38, 46.03],\n      [29.6, 45.29]\n    ],\n    [\n      [-53.37, -33.77],\n      [-53.81, -34.4],\n      [-54.94, -34.95],\n      [-55.67, -34.75],\n      [-56.22, -34.86],\n      [-57.14, -34.43],\n      [-57.82, -34.46],\n      [-58.43, -33.91]\n    ],\n    [\n      [-155.54, 19.08],\n      [-155.69, 18.92],\n      [-155.94, 19.06],\n      [-155.91, 19.34],\n      [-156.07, 19.7],\n      [-156.02, 19.81],\n      [-155.85, 19.98],\n      [-155.92, 20.17],\n      [-155.86, 20.27],\n      [-155.79, 20.25],\n      [-155.4, 20.08],\n      [-155.22, 19.99],\n      [-155.06, 19.86],\n      [-154.81, 19.51],\n      [-154.83, 19.45],\n      [-155.22, 19.24],\n      [-155.54, 19.08]\n    ],\n    [\n      [-156.08, 20.64],\n      [-156.41, 20.57],\n      [-156.59, 20.78],\n      [-156.7, 20.86],\n      [-156.71, 20.93],\n      [-156.61, 21.01],\n      [-156.26, 20.92],\n      [-156, 20.76],\n      [-156.08, 20.64]\n    ],\n    [\n      [-156.76, 21.18],\n      [-156.79, 21.07],\n      [-157.33, 21.1],\n      [-157.25, 21.22],\n      [-156.76, 21.18]\n    ],\n    [\n      [-157.65, 21.32],\n      [-157.71, 21.26],\n      [-157.78, 21.28],\n      [-158.13, 21.31],\n      [-158.25, 21.54],\n      [-158.29, 21.58],\n      [-158.03, 21.72],\n      [-157.94, 21.65],\n      [-157.65, 21.32]\n    ],\n    [\n      [-159.35, 21.98],\n      [-159.46, 21.88],\n      [-159.8, 22.07],\n      [-159.75, 22.14],\n      [-159.6, 22.24],\n      [-159.37, 22.21],\n      [-159.35, 21.98]\n    ],\n    [\n      [-67.14, 45.14],\n      [-66.96, 44.81],\n      [-68.03, 44.33],\n      [-69.06, 43.98],\n      [-70.12, 43.68],\n      [-70.65, 43.09],\n      [-70.81, 42.87],\n      [-70.82, 42.34],\n      [-70.49, 41.81],\n      [-70.08, 41.78],\n      [-70.18, 42.15],\n      [-69.88, 41.92],\n      [-69.97, 41.64],\n      [-70.64, 41.48],\n      [-71.12, 41.49],\n      [-71.86, 41.32],\n      [-72.29, 41.27],\n      [-72.88, 41.22],\n      [-73.71, 40.93],\n      [-72.24, 41.12],\n      [-71.94, 40.93],\n      [-73.34, 40.63],\n      [-73.98, 40.63],\n      [-73.95, 40.75],\n      [-74.26, 40.47],\n      [-73.96, 40.43],\n      [-74.18, 39.71],\n      [-74.91, 38.94],\n      [-74.98, 39.2],\n      [-75.2, 39.25],\n      [-75.53, 39.5],\n      [-75.32, 38.96],\n      [-75.07, 38.78],\n      [-75.06, 38.4],\n      [-75.38, 38.02],\n      [-75.94, 37.22],\n      [-76.03, 37.26],\n      [-75.72, 37.94],\n      [-76.23, 38.32],\n      [-76.35, 39.15],\n      [-76.54, 38.72],\n      [-76.33, 38.08],\n      [-76.99, 38.24],\n      [-76.3, 37.92],\n      [-76.26, 36.97],\n      [-75.97, 36.9],\n      [-75.87, 36.55],\n      [-75.73, 35.55],\n      [-76.36, 34.81],\n      [-77.4, 34.51],\n      [-78.05, 33.93],\n      [-78.55, 33.86],\n      [-79.06, 33.49],\n      [-79.2, 33.16],\n      [-80.3, 32.51],\n      [-80.86, 32.03],\n      [-81.34, 31.44],\n      [-81.49, 30.73],\n      [-81.31, 30.04],\n      [-80.98, 29.18],\n      [-80.54, 28.47],\n      [-80.53, 28.04],\n      [-80.06, 26.88],\n      [-80.09, 26.21],\n      [-80.13, 25.82],\n      [-80.38, 25.21],\n      [-80.68, 25.08],\n      [-81.17, 25.2],\n      [-81.33, 25.64],\n      [-81.71, 25.87],\n      [-82.24, 26.73],\n      [-82.71, 27.5],\n      [-82.86, 27.89],\n      [-82.65, 28.55],\n      [-82.93, 29.1],\n      [-83.71, 29.94],\n      [-84.1, 30.09],\n      [-85.11, 29.64],\n      [-85.29, 29.69],\n      [-85.77, 30.15],\n      [-86.4, 30.4],\n      [-87.53, 30.27],\n      [-88.42, 30.38],\n      [-89.18, 30.32],\n      [-89.59, 30.16],\n      [-89.41, 29.89],\n      [-89.43, 29.49],\n      [-89.22, 29.29],\n      [-89.41, 29.16],\n      [-89.78, 29.31],\n      [-90.15, 29.12],\n      [-90.88, 29.15],\n      [-91.63, 29.68],\n      [-92.5, 29.55],\n      [-93.23, 29.78],\n      [-93.85, 29.71],\n      [-94.69, 29.48],\n      [-95.6, 28.74],\n      [-96.59, 28.31],\n      [-97.14, 27.83],\n      [-97.37, 27.38],\n      [-97.38, 26.69],\n      [-97.33, 26.21],\n      [-97.14, 25.87],\n      [-97.53, 25.84]\n    ],\n    [\n      [-117.13, 32.54],\n      [-117.3, 33.05],\n      [-117.94, 33.62],\n      [-118.41, 33.74],\n      [-118.52, 34.03],\n      [-119.08, 34.08],\n      [-119.44, 34.35],\n      [-120.37, 34.45],\n      [-120.62, 34.61],\n      [-120.74, 35.16],\n      [-121.71, 36.16],\n      [-122.55, 37.55],\n      [-122.51, 37.78],\n      [-122.95, 38.11],\n      [-123.73, 38.95],\n      [-123.87, 39.77],\n      [-124.4, 40.31],\n      [-124.18, 41.14],\n      [-124.21, 42],\n      [-124.53, 42.77],\n      [-124.14, 43.71],\n      [-124.02, 44.62],\n      [-123.9, 45.52],\n      [-124.08, 46.86],\n      [-124.4, 47.72],\n      [-124.69, 48.18],\n      [-124.57, 48.38],\n      [-123.12, 48.04],\n      [-122.59, 47.1],\n      [-122.34, 47.36],\n      [-122.5, 48.18],\n      [-122.84, 49]\n    ],\n    [\n      [-153.01, 57.12],\n      [-154.01, 56.73],\n      [-154.52, 56.99],\n      [-154.67, 57.46],\n      [-153.76, 57.82],\n      [-153.23, 57.97],\n      [-152.56, 57.9],\n      [-152.14, 57.59],\n      [-153.01, 57.12]\n    ],\n    [\n      [-165.58, 59.91],\n      [-166.19, 59.75],\n      [-166.85, 59.94],\n      [-167.46, 60.21],\n      [-166.47, 60.38],\n      [-165.67, 60.29],\n      [-165.58, 59.91]\n    ],\n    [\n      [-171.73, 63.78],\n      [-171.11, 63.59],\n      [-170.49, 63.69],\n      [-169.68, 63.43],\n      [-168.69, 63.3],\n      [-168.77, 63.19],\n      [-169.53, 62.98],\n      [-170.29, 63.19],\n      [-170.67, 63.38],\n      [-171.55, 63.32],\n      [-171.79, 63.41],\n      [-171.73, 63.78]\n    ],\n    [\n      [-134.27, 58.86],\n      [-133.36, 58.41],\n      [-132.73, 57.69]\n    ],\n    [\n      [-130.01, 55.92],\n      [-129.98, 55.28],\n      [-130.54, 54.8],\n      [-131.09, 55.18],\n      [-131.97, 55.5],\n      [-132.25, 56.37],\n      [-133.54, 57.18],\n      [-134.08, 58.12],\n      [-135.04, 58.19],\n      [-136.63, 58.21],\n      [-137.8, 58.5],\n      [-139.87, 59.54],\n      [-140.83, 59.73],\n      [-142.57, 60.08],\n      [-143.96, 60],\n      [-145.93, 60.46],\n      [-147.11, 60.88],\n      [-148.22, 60.67],\n      [-148.02, 59.98],\n      [-148.57, 59.91],\n      [-149.73, 59.71],\n      [-150.61, 59.37],\n      [-151.72, 59.16],\n      [-151.86, 59.74],\n      [-151.41, 60.73],\n      [-150.35, 61.03],\n      [-150.62, 61.28],\n      [-151.9, 60.73],\n      [-152.58, 60.06],\n      [-154.02, 59.35],\n      [-153.29, 58.86],\n      [-154.23, 58.15],\n      [-155.31, 57.73],\n      [-156.31, 57.42],\n      [-156.56, 56.98],\n      [-158.12, 56.46],\n      [-158.43, 55.99],\n      [-159.6, 55.57],\n      [-160.29, 55.64],\n      [-161.22, 55.36],\n      [-162.24, 55.02],\n      [-163.07, 54.69],\n      [-164.79, 54.4],\n      [-164.94, 54.57],\n      [-163.85, 55.04],\n      [-162.87, 55.35],\n      [-161.8, 55.89],\n      [-160.56, 56.01],\n      [-160.07, 56.42],\n      [-158.68, 57.02],\n      [-158.46, 57.22],\n      [-157.72, 57.57],\n      [-157.55, 58.33],\n      [-157.04, 58.92],\n      [-158.19, 58.62],\n      [-158.52, 58.79],\n      [-159.06, 58.42],\n      [-159.71, 58.93],\n      [-159.98, 58.57],\n      [-160.36, 59.07],\n      [-161.36, 58.67],\n      [-161.97, 58.67],\n      [-162.05, 59.27],\n      [-161.87, 59.63],\n      [-162.52, 59.99],\n      [-163.82, 59.8],\n      [-164.66, 60.27],\n      [-165.35, 60.51],\n      [-165.35, 61.07],\n      [-166.12, 61.5],\n      [-165.73, 62.07],\n      [-164.92, 62.63],\n      [-164.56, 63.15],\n      [-163.75, 63.22],\n      [-163.07, 63.06],\n      [-162.26, 63.54],\n      [-161.53, 63.46],\n      [-160.77, 63.77],\n      [-160.96, 64.22],\n      [-161.52, 64.4],\n      [-160.78, 64.79],\n      [-161.39, 64.78],\n      [-162.45, 64.56],\n      [-162.76, 64.34],\n      [-163.55, 64.56],\n      [-164.96, 64.45],\n      [-166.43, 64.69],\n      [-166.85, 65.09],\n      [-168.11, 65.67],\n      [-166.71, 66.09],\n      [-164.47, 66.58],\n      [-163.65, 66.58],\n      [-163.79, 66.08],\n      [-161.68, 66.12],\n      [-162.49, 66.74],\n      [-163.72, 67.12],\n      [-164.43, 67.62],\n      [-165.39, 68.04],\n      [-166.76, 68.36],\n      [-166.2, 68.88],\n      [-164.43, 68.92],\n      [-163.17, 69.37],\n      [-162.93, 69.86],\n      [-161.91, 70.33],\n      [-160.93, 70.45],\n      [-159.04, 70.89],\n      [-158.12, 70.82],\n      [-156.58, 71.36],\n      [-155.07, 71.15],\n      [-154.34, 70.7],\n      [-153.9, 70.89],\n      [-152.21, 70.83],\n      [-152.27, 70.6],\n      [-150.74, 70.43],\n      [-149.72, 70.53],\n      [-147.61, 70.21],\n      [-145.69, 70.12],\n      [-144.92, 69.99],\n      [-143.59, 70.15],\n      [-142.07, 69.85],\n      [-140.99, 69.71],\n      [-140.99, 69.71],\n      [-140.99, 66],\n      [-141, 60.31],\n      [-140.01, 60.28],\n      [-139.04, 60],\n      [-138.34, 59.56]\n    ],\n    [\n      [-71.33, 11.78],\n      [-71.36, 11.54],\n      [-71.95, 11.42],\n      [-71.62, 10.97],\n      [-71.63, 10.45],\n      [-72.07, 9.87],\n      [-71.7, 9.07],\n      [-71.26, 9.14],\n      [-71.04, 9.86],\n      [-71.35, 10.21],\n      [-71.4, 10.97],\n      [-70.16, 11.38],\n      [-70.29, 11.85],\n      [-69.94, 12.16],\n      [-69.58, 11.46],\n      [-68.88, 11.44],\n      [-68.23, 10.89],\n      [-68.19, 10.55],\n      [-67.3, 10.55],\n      [-66.23, 10.65],\n      [-65.66, 10.2],\n      [-64.89, 10.08],\n      [-64.33, 10.39],\n      [-64.32, 10.64],\n      [-63.08, 10.7],\n      [-61.88, 10.72],\n      [-62.73, 10.42],\n      [-62.39, 9.95],\n      [-61.59, 9.87],\n      [-60.83, 9.38],\n      [-60.67, 8.58],\n      [-60.15, 8.6],\n      [-59.76, 8.37]\n    ],\n    [\n      [108.05, 21.55],\n      [106.72, 20.7],\n      [105.88, 19.75],\n      [105.66, 19.06],\n      [106.43, 18],\n      [107.36, 16.7],\n      [108.27, 16.08],\n      [108.88, 15.28],\n      [109.34, 13.43],\n      [109.2, 11.67],\n      [108.37, 11.01],\n      [107.22, 10.36],\n      [106.41, 9.53],\n      [105.16, 8.6],\n      [104.8, 9.24],\n      [105.08, 9.92],\n      [104.33, 10.49]\n    ],\n    [\n      [167.84, -16.47],\n      [167.52, -16.6],\n      [167.18, -16.16],\n      [167.22, -15.89],\n      [167.84, -16.47]\n    ],\n    [\n      [167.11, -14.93],\n      [167.27, -15.74],\n      [167, -15.61],\n      [166.79, -15.67],\n      [166.65, -15.39],\n      [166.63, -14.63],\n      [167.11, -14.93]\n    ],\n    [\n      [53.11, 16.65],\n      [52.39, 16.38],\n      [52.19, 15.94],\n      [52.17, 15.6],\n      [51.17, 15.18],\n      [49.57, 14.71],\n      [48.68, 14],\n      [48.24, 13.95],\n      [47.94, 14.01],\n      [47.35, 13.59],\n      [46.72, 13.4],\n      [45.88, 13.35],\n      [45.63, 13.29],\n      [45.41, 13.03],\n      [45.14, 12.95],\n      [44.99, 12.7],\n      [44.49, 12.72],\n      [44.18, 12.59],\n      [43.48, 12.64],\n      [43.22, 13.22],\n      [43.25, 13.77],\n      [43.09, 14.06],\n      [42.89, 14.8],\n      [42.6, 15.21],\n      [42.81, 15.26],\n      [42.7, 15.72],\n      [42.82, 15.91],\n      [42.78, 16.35]\n    ],\n    [\n      [29.43, -22.09],\n      [29.84, -22.1],\n      [30.32, -22.27],\n      [30.66, -22.15],\n      [31.19, -22.25]\n    ],\n    [\n      [32.83, -26.74],\n      [32.58, -27.47],\n      [32.46, -28.3],\n      [32.2, -28.75],\n      [31.52, -29.26],\n      [31.33, -29.4],\n      [30.9, -29.91],\n      [30.62, -30.42],\n      [30.06, -31.14],\n      [28.93, -32.17],\n      [28.22, -32.77],\n      [27.46, -33.23],\n      [26.42, -33.61],\n      [25.91, -33.67],\n      [25.78, -33.94],\n      [25.17, -33.8],\n      [24.68, -33.99],\n      [23.59, -33.79],\n      [22.99, -33.92],\n      [22.57, -33.86],\n      [21.54, -34.26],\n      [20.69, -34.42],\n      [20.07, -34.8],\n      [19.62, -34.82],\n      [19.19, -34.46],\n      [18.86, -34.44],\n      [18.42, -34],\n      [18.38, -34.14],\n      [18.24, -33.87],\n      [18.25, -33.28],\n      [17.93, -32.61],\n      [18.25, -32.43],\n      [18.22, -31.66],\n      [17.57, -30.73],\n      [17.06, -29.88],\n      [17.06, -29.88],\n      [16.34, -28.58]\n    ],\n    [\n      [30.27, -15.51],\n      [29.52, -15.64],\n      [28.95, -16.04],\n      [28.83, -16.39],\n      [28.47, -16.47],\n      [27.6, -17.29],\n      [27.04, -17.94],\n      [26.71, -17.96],\n      [26.38, -17.85],\n      [25.26, -17.74]\n    ],\n    [\n      [30.74, -8.34],\n      [31.16, -8.59]\n    ],\n    [\n      [-24.32, 14.85],\n      [-24.39, 14.81],\n      [-24.46, 14.83],\n      [-24.54, 14.92],\n      [-24.5, 14.97],\n      [-24.39, 15.02],\n      [-24.36, 15],\n      [-24.32, 14.92],\n      [-24.32, 14.85]\n    ],\n    [\n      [-23.2, 15.13],\n      [-23.24, 15.13],\n      [-23.28, 15.18],\n      [-23.28, 15.25],\n      [-23.24, 15.32],\n      [-23.17, 15.32],\n      [-23.13, 15.26],\n      [-23.13, 15.16],\n      [-23.2, 15.13]\n    ],\n    [\n      [-23.46, 15],\n      [-23.53, 14.9],\n      [-23.67, 14.92],\n      [-23.82, 15.07],\n      [-23.82, 15.16],\n      [-23.78, 15.23],\n      [-23.78, 15.32],\n      [-23.71, 15.32],\n      [-23.71, 15.26],\n      [-23.56, 15.13],\n      [-23.46, 15]\n    ],\n    [\n      [-22.95, 16.24],\n      [-22.84, 16.2],\n      [-22.77, 16.22],\n      [-22.7, 16.17],\n      [-22.7, 16.1],\n      [-22.74, 16.03],\n      [-22.84, 15.98],\n      [-22.92, 15.98],\n      [-22.99, 16.03],\n      [-22.92, 16.13],\n      [-22.95, 16.24]\n    ],\n    [\n      [-24.1, 16.62],\n      [-24.07, 16.57],\n      [-24.1, 16.55],\n      [-24.25, 16.58],\n      [-24.28, 16.57],\n      [-24.36, 16.48],\n      [-24.43, 16.6],\n      [-24.43, 16.65],\n      [-24.39, 16.67],\n      [-24.28, 16.64],\n      [-24.1, 16.62]\n    ],\n    [\n      [-22.92, 16.65],\n      [-22.95, 16.6],\n      [-22.99, 16.67],\n      [-23.02, 16.79],\n      [-22.95, 16.83],\n      [-22.92, 16.83],\n      [-22.92, 16.65]\n    ],\n    [\n      [-24.9, 16.81],\n      [-25.04, 16.79],\n      [-25.11, 16.83],\n      [-25.08, 16.86],\n      [-24.97, 16.91],\n      [-24.9, 16.84],\n      [-24.9, 16.81]\n    ],\n    [\n      [-25.18, 16.93],\n      [-25.29, 16.91],\n      [-25.33, 16.93],\n      [-25.33, 17],\n      [-25.36, 17.05],\n      [-25.36, 17.09],\n      [-25.15, 17.19],\n      [-25.04, 17.17],\n      [-25, 17.09],\n      [-25.04, 17.04],\n      [-25.18, 16.93]\n    ],\n    [\n      [43.76, -12.32],\n      [43.65, -12.36],\n      [43.62, -12.29],\n      [43.62, -12.25],\n      [43.69, -12.27],\n      [43.76, -12.32]\n    ],\n    [\n      [44.45, -12.1],\n      [44.52, -12.24],\n      [44.52, -12.34],\n      [44.48, -12.36],\n      [44.45, -12.34],\n      [44.37, -12.25],\n      [44.19, -12.18],\n      [44.27, -12.17],\n      [44.3, -12.18],\n      [44.37, -12.17],\n      [44.37, -12.13],\n      [44.41, -12.1],\n      [44.45, -12.1]\n    ],\n    [\n      [43.44, -11.91],\n      [43.44, -11.92],\n      [43.29, -11.85],\n      [43.22, -11.77],\n      [43.22, -11.44],\n      [43.26, -11.4],\n      [43.33, -11.39],\n      [43.37, -11.42],\n      [43.37, -11.63],\n      [43.44, -11.77],\n      [43.47, -11.87],\n      [43.44, -11.91]\n    ],\n    [\n      [57.62, -20.5],\n      [57.52, -20.52],\n      [57.37, -20.52],\n      [57.3, -20.47],\n      [57.34, -20.41],\n      [57.34, -20.34],\n      [57.37, -20.24],\n      [57.41, -20.19],\n      [57.48, -20.15],\n      [57.48, -20.07],\n      [57.55, -20.01],\n      [57.62, -20],\n      [57.73, -20.1],\n      [57.77, -20.22],\n      [57.77, -20.34],\n      [57.7, -20.38],\n      [57.7, -20.45],\n      [57.62, -20.5]\n    ],\n    [\n      [55.54, -4.7],\n      [55.54, -4.79],\n      [55.46, -4.77],\n      [55.46, -4.7],\n      [55.39, -4.67],\n      [55.36, -4.61],\n      [55.43, -4.56],\n      [55.54, -4.7]\n    ],\n    [\n      [50.6, 25.87],\n      [50.57, 25.8],\n      [50.53, 25.82],\n      [50.46, 25.96],\n      [50.46, 26.05],\n      [50.42, 26.18],\n      [50.46, 26.22],\n      [50.57, 26.24],\n      [50.53, 26.18],\n      [50.6, 26.12],\n      [50.6, 25.87]\n    ],\n    [\n      [73.39, 3.22],\n      [73.36, 3.23],\n      [73.36, 3.27],\n      [73.39, 3.28],\n      [73.43, 3.27],\n      [73.43, 3.23],\n      [73.39, 3.22]\n    ],\n    [\n      [73.5, 4.15],\n      [73.47, 4.15],\n      [73.47, 4.21],\n      [73.5, 4.22],\n      [73.5, 4.15]\n    ],\n    [\n      [169.63, 5.82],\n      [169.59, 5.85],\n      [169.63, 5.94],\n      [169.67, 5.92],\n      [169.63, 5.82]\n    ],\n    [\n      [171.07, 7.12],\n      [171.22, 7.07],\n      [171.36, 7.1],\n      [171.36, 7.09],\n      [171.25, 7.05],\n      [171.22, 7.05],\n      [171.07, 7.1],\n      [171.07, 7.12]\n    ],\n    [\n      [162.97, 5.32],\n      [162.97, 5.26],\n      [162.9, 5.3],\n      [162.93, 5.33],\n      [162.97, 5.32]\n    ],\n    [\n      [158.29, 6.81],\n      [158.25, 6.77],\n      [158.18, 6.79],\n      [158.15, 6.88],\n      [158.11, 6.9],\n      [158.11, 6.93],\n      [158.18, 6.97],\n      [158.29, 6.95],\n      [158.33, 6.88],\n      [158.29, 6.84],\n      [158.29, 6.81]\n    ],\n    [\n      [151.63, 7.33],\n      [151.59, 7.35],\n      [151.59, 7.38],\n      [151.63, 7.38],\n      [151.63, 7.33]\n    ],\n    [\n      [151.88, 7.42],\n      [151.85, 7.42],\n      [151.85, 7.45],\n      [151.88, 7.45],\n      [151.88, 7.42]\n    ],\n    [\n      [138.13, 9.5],\n      [138.13, 9.57],\n      [138.16, 9.59],\n      [138.2, 9.54],\n      [138.16, 9.5],\n      [138.13, 9.5]\n    ],\n    [\n      [166.89, -0.52],\n      [166.93, -0.5],\n      [166.97, -0.52],\n      [166.97, -0.55],\n      [166.93, -0.55],\n      [166.89, -0.55],\n      [166.89, -0.53],\n      [166.89, -0.52]\n    ],\n    [\n      [134.56, 7.36],\n      [134.53, 7.35],\n      [134.49, 7.43],\n      [134.49, 7.52],\n      [134.53, 7.59],\n      [134.6, 7.61],\n      [134.6, 7.49],\n      [134.56, 7.43],\n      [134.56, 7.36]\n    ],\n    [\n      [-171.47, -14.06],\n      [-171.76, -14.06],\n      [-171.9, -14.01],\n      [-171.94, -14.01],\n      [-172.04, -13.92],\n      [-172.08, -13.87],\n      [-172.01, -13.83],\n      [-171.86, -13.82],\n      [-171.61, -13.89],\n      [-171.58, -13.96],\n      [-171.54, -13.96],\n      [-171.47, -13.99],\n      [-171.47, -14.06]\n    ],\n    [\n      [-172.37, -13.47],\n      [-172.22, -13.57],\n      [-172.19, -13.69],\n      [-172.26, -13.82],\n      [-172.33, -13.78],\n      [-172.51, -13.82],\n      [-172.55, -13.8],\n      [-172.76, -13.59],\n      [-172.8, -13.52],\n      [-172.69, -13.54],\n      [-172.51, -13.49],\n      [-172.37, -13.47]\n    ],\n    [\n      [103.56, 1.19],\n      [103.67, 1.42],\n      [103.72, 1.46],\n      [103.86, 1.47],\n      [104, 1.42],\n      [104.08, 1.43],\n      [104.08, 1.36],\n      [104.13, 1.27]\n    ],\n    [\n      [-174.92, -21.32],\n      [-174.92, -21.46],\n      [-175, -21.39],\n      [-175, -21.35],\n      [-174.92, -21.32]\n    ],\n    [\n      [-175.18, -21.18],\n      [-175.14, -21.14],\n      [-175.1, -21.18],\n      [-175.18, -21.26],\n      [-175.21, -21.23],\n      [-175.36, -21.16],\n      [-175.32, -21.13],\n      [-175.25, -21.13],\n      [-175.18, -21.18]\n    ],\n    [\n      [-173.99, -18.64],\n      [-174.02, -18.71],\n      [-174.1, -18.64],\n      [-174.02, -18.57],\n      [-173.95, -18.59],\n      [-173.95, -18.63],\n      [-173.99, -18.64]\n    ],\n    [\n      [178.31, -8.03],\n      [178.38, -7.93],\n      [178.45, -7.97],\n      [178.38, -8.09],\n      [178.31, -8.03]\n    ],\n    [\n      [178.67, -7.46],\n      [178.7, -7.48],\n      [178.67, -7.5],\n      [178.67, -7.46]\n    ],\n    [\n      [179.14, -8.42],\n      [179.17, -8.43],\n      [179.21, -8.52],\n      [179.1, -8.59],\n      [179.1, -8.63],\n      [179.06, -8.64],\n      [179.06, -8.59],\n      [179.03, -8.52],\n      [179.1, -8.43],\n      [179.14, -8.42]\n    ],\n    [\n      [179.86, -9.35],\n      [179.86, -9.37],\n      [179.89, -9.39],\n      [179.86, -9.44],\n      [179.86, -9.42],\n      [179.82, -9.37],\n      [179.82, -9.35],\n      [179.86, -9.35]\n    ],\n    [\n      [176.98, -12.46],\n      [177.16, -12.48],\n      [177.16, -12.53],\n      [177.01, -12.51],\n      [176.98, -12.46]\n    ],\n    [\n      [177.16, -7.25],\n      [177.12, -7.18],\n      [177.16, -7.2],\n      [177.16, -7.25]\n    ],\n    [\n      [176.08, -5.64],\n      [176.11, -5.66],\n      [176.15, -5.71],\n      [176.11, -5.67],\n      [176.04, -5.64],\n      [176.08, -5.64]\n    ],\n    [\n      [-61.73, 17.04],\n      [-61.76, 16.98],\n      [-61.87, 17],\n      [-61.91, 17.05],\n      [-61.91, 17.09],\n      [-61.84, 17.16],\n      [-61.69, 17.09],\n      [-61.69, 17.05],\n      [-61.73, 17.04]\n    ],\n    [\n      [-61.76, 17.57],\n      [-61.76, 17.54],\n      [-61.87, 17.59],\n      [-61.87, 17.71],\n      [-61.76, 17.66],\n      [-61.76, 17.57]\n    ],\n    [\n      [-59.5, 13.08],\n      [-59.53, 13.06],\n      [-59.64, 13.09],\n      [-59.68, 13.15],\n      [-59.68, 13.3],\n      [-59.6, 13.3],\n      [-59.46, 13.15],\n      [-59.5, 13.08]\n    ],\n    [\n      [-61.3, 15.25],\n      [-61.4, 15.21],\n      [-61.44, 15.39],\n      [-61.51, 15.52],\n      [-61.48, 15.59],\n      [-61.48, 15.63],\n      [-61.33, 15.58],\n      [-61.3, 15.52],\n      [-61.26, 15.37],\n      [-61.3, 15.25]\n    ],\n    [\n      [-61.73, 12],\n      [-61.76, 12.04],\n      [-61.76, 12.1],\n      [-61.69, 12.23],\n      [-61.62, 12.21],\n      [-61.66, 12.05],\n      [-61.73, 12]\n    ],\n    [\n      [-62.56, 17.1],\n      [-62.59, 17.09],\n      [-62.63, 17.12],\n      [-62.63, 17.19],\n      [-62.59, 17.19],\n      [-62.56, 17.16],\n      [-62.56, 17.1]\n    ],\n    [\n      [-62.66, 17.23],\n      [-62.74, 17.28],\n      [-62.81, 17.3],\n      [-62.84, 17.33],\n      [-62.84, 17.38],\n      [-62.81, 17.4],\n      [-62.74, 17.35],\n      [-62.7, 17.28],\n      [-62.66, 17.26],\n      [-62.66, 17.23]\n    ],\n    [\n      [-60.9, 13.81],\n      [-60.97, 13.7],\n      [-61.08, 13.77],\n      [-61.08, 13.91],\n      [-61.01, 14],\n      [-60.97, 14.07],\n      [-60.94, 14.08],\n      [-60.9, 14],\n      [-60.9, 13.81]\n    ],\n    [\n      [-61.19, 13.15],\n      [-61.22, 13.13],\n      [-61.3, 13.2],\n      [-61.3, 13.29],\n      [-61.22, 13.32],\n      [-61.19, 13.35],\n      [-61.15, 13.35],\n      [-61.15, 13.2],\n      [-61.19, 13.15]\n    ],\n    [\n      [1.42, 42.59],\n      [1.49, 42.63],\n      [1.57, 42.63],\n      [1.71, 42.57],\n      [1.71, 42.52],\n      [1.67, 42.49],\n      [1.46, 42.42],\n      [1.42, 42.44],\n      [1.42, 42.49],\n      [1.39, 42.52],\n      [1.42, 42.59]\n    ],\n    [\n      [9.61, 47.06],\n      [9.56, 47.05]\n    ],\n    [\n      [9.49, 47.18],\n      [9.53, 47.27],\n      [9.58, 47.21]\n    ],\n    [\n      [14.56, 35.84],\n      [14.53, 35.8],\n      [14.42, 35.82],\n      [14.35, 35.86],\n      [14.35, 35.98],\n      [14.53, 35.87],\n      [14.56, 35.84]\n    ],\n    [\n      [7.43, 43.74],\n      [7.36, 43.72],\n      [7.36, 43.75],\n      [7.43, 43.74]\n    ],\n    [\n      [12.37, 43.93],\n      [12.44, 43.98],\n      [12.51, 43.95],\n      [12.48, 43.89],\n      [12.4, 43.89],\n      [12.37, 43.93]\n    ],\n    [\n      [-157.42, 2.02],\n      [-157.39, 1.99],\n      [-157.32, 1.97],\n      [-157.31, 1.97],\n      [-157.31, 1.96],\n      [-157.32, 1.95],\n      [-157.32, 1.95],\n      [-157.34, 1.94],\n      [-157.34, 1.93],\n      [-157.34, 1.91],\n      [-157.35, 1.86],\n      [-157.33, 1.84],\n      [-157.29, 1.82],\n      [-157.25, 1.8],\n      [-157.24, 1.78],\n      [-157.22, 1.77],\n      [-157.21, 1.77],\n      [-157.19, 1.76],\n      [-157.18, 1.75],\n      [-157.17, 1.73],\n      [-157.18, 1.72],\n      [-157.19, 1.72],\n      [-157.2, 1.72],\n      [-157.21, 1.71],\n      [-157.22, 1.71],\n      [-157.24, 1.71],\n      [-157.25, 1.71],\n      [-157.25, 1.72],\n      [-157.26, 1.73],\n      [-157.27, 1.73],\n      [-157.28, 1.75],\n      [-157.29, 1.75],\n      [-157.31, 1.75],\n      [-157.41, 1.78],\n      [-157.45, 1.8],\n      [-157.48, 1.83],\n      [-157.51, 1.85],\n      [-157.53, 1.86],\n      [-157.54, 1.86],\n      [-157.55, 1.86],\n      [-157.57, 1.86],\n      [-157.58, 1.88],\n      [-157.58, 1.91],\n      [-157.57, 1.91],\n      [-157.54, 1.93],\n      [-157.53, 1.93],\n      [-157.52, 1.93],\n      [-157.52, 1.92],\n      [-157.52, 1.92],\n      [-157.53, 1.92],\n      [-157.55, 1.9],\n      [-157.55, 1.88],\n      [-157.54, 1.87],\n      [-157.53, 1.86],\n      [-157.51, 1.87],\n      [-157.5, 1.87],\n      [-157.5, 1.86],\n      [-157.49, 1.85],\n      [-157.48, 1.85],\n      [-157.45, 1.85],\n      [-157.43, 1.84],\n      [-157.43, 1.84],\n      [-157.44, 1.85],\n      [-157.45, 1.86],\n      [-157.46, 1.86],\n      [-157.47, 1.87],\n      [-157.48, 1.88],\n      [-157.46, 1.88],\n      [-157.45, 1.88],\n      [-157.45, 1.88],\n      [-157.45, 1.9],\n      [-157.44, 1.89],\n      [-157.43, 1.88],\n      [-157.43, 1.87],\n      [-157.42, 1.86],\n      [-157.41, 1.86],\n      [-157.41, 1.87],\n      [-157.41, 1.88],\n      [-157.42, 1.9],\n      [-157.43, 1.91],\n      [-157.42, 1.92],\n      [-157.42, 1.93],\n      [-157.41, 1.92],\n      [-157.4, 1.92],\n      [-157.41, 1.94],\n      [-157.39, 1.94],\n      [-157.39, 1.93],\n      [-157.4, 1.92],\n      [-157.39, 1.91],\n      [-157.38, 1.9],\n      [-157.38, 1.91],\n      [-157.37, 1.91],\n      [-157.36, 1.92],\n      [-157.36, 1.92],\n      [-157.35, 1.93],\n      [-157.35, 1.94],\n      [-157.36, 1.95],\n      [-157.36, 1.95],\n      [-157.38, 1.96],\n      [-157.41, 1.97],\n      [-157.45, 1.99],\n      [-157.45, 2],\n      [-157.46, 2],\n      [-157.47, 2.01],\n      [-157.48, 2.02],\n      [-157.48, 2.01],\n      [-157.49, 2],\n      [-157.5, 2],\n      [-157.51, 2.01],\n      [-157.51, 2.02],\n      [-157.5, 2.03],\n      [-157.47, 2.03],\n      [-157.42, 2.02]\n    ],\n    [\n      [6.66, 0.42],\n      [6.77, 0.28],\n      [6.66, 0.12],\n      [6.53, 0.02],\n      [6.46, 0.21],\n      [6.49, 0.31],\n      [6.66, 0.42]\n    ]\n  ],\n  \"bbox\": [-180, -55.61183, 180, 83.64513]\n}\n"
  },
  {
    "path": "src/web-check-live/components/Form/Button.tsx",
    "content": "import { type ReactNode, type MouseEventHandler } from 'react';\n\nimport styled from '@emotion/styled';\nimport { keyframes } from '@emotion/react';\nimport colors from 'web-check-live/styles/colors';\nimport { type InputSize, applySize } from 'web-check-live/styles/dimensions';\n\ntype LoadState = 'loading' | 'success' | 'error';\n\ninterface ButtonProps {\n  children: ReactNode;\n  onClick?: MouseEventHandler<HTMLButtonElement>;\n  size?: InputSize,\n  bgColor?: string,\n  fgColor?: string,\n  styles?: string,\n  title?: string,\n  type?: 'button' | 'submit' | 'reset' | undefined,\n  loadState?: LoadState,\n};\n\nconst StyledButton = styled.button<ButtonProps>`\n  cursor: pointer;\n  border: none;\n  border-radius: 0.25rem;\n  font-family: PTMono;\n  box-sizing: border-box; \n  width: -moz-available;\n  display: flex;\n  justify-content: center;\n  gap: 1rem;\n  box-shadow: 3px 3px 0px ${colors.fgShadowColor};\n  &:hover {\n    box-shadow: 5px 5px 0px ${colors.fgShadowColor};\n  }\n  &:active {\n    box-shadow: -3px -3px 0px ${colors.fgShadowColor};\n  }\n  ${props => applySize(props.size)};\n  ${(props) => props.bgColor ?\n    `background: ${props.bgColor};` : `background: ${colors.primary};`\n  }\n  ${(props) => props.fgColor ?\n    `color: ${props.fgColor};` : `color: ${colors.background};`\n  }\n  ${props => props.styles}\n`;\n\n\nconst spinAnimation = keyframes`\n  0% { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n`;\nconst SimpleLoader = styled.div`\n  border: 4px solid rgba(255, 255, 255, 0.3);\n  border-radius: 50%;\n  border-top: 4px solid ${colors.background};\n  width: 1rem;\n  height: 1rem;\n  animation: ${spinAnimation} 1s linear infinite;\n`;\n\nconst Loader = (props: { loadState: LoadState }) => {\n  if (props.loadState === 'loading') return <SimpleLoader />\n  if (props.loadState === 'success') return <span>✔</span>\n  if (props.loadState === 'error') return <span>✗</span>\n  return <span></span>;\n};\n\nconst Button = (props: ButtonProps): JSX.Element => {\n  const { children, size, bgColor, fgColor, onClick, styles, title, loadState, type } = props;\n  return (\n    <StyledButton\n      onClick={onClick || (() => null) }\n      size={size}\n      bgColor={bgColor}\n      fgColor={fgColor}\n      styles={styles}\n      title={title?.toString()}\n      type={type || 'button'}\n      >\n      { loadState && <Loader loadState={loadState} /> }\n      {children}\n    </StyledButton>\n  );\n};\n\nexport default Button;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Card.tsx",
    "content": "import styled from '@emotion/styled';\n\nimport { type ReactNode } from 'react';\nimport ErrorBoundary from 'web-check-live/components/misc/ErrorBoundary';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nexport const StyledCard = styled.section<{ styles?: string}>`\n  background: ${colors.backgroundLighter};\n  color: ${colors.textColor};\n  box-shadow: 4px 4px 0px ${colors.bgShadowColor};\n  border-radius: 8px;\n  padding: 1rem;\n  position: relative;\n  margin 0.5rem;\n  max-height: 64rem;\n  overflow: auto;\n  ${props => props.styles}\n`;\n\ninterface CardProps {\n  children: ReactNode;\n  heading?: string,\n  styles?: string;\n  actionButtons?: ReactNode | undefined;\n};\n\nexport const Card = (props: CardProps): JSX.Element => {\n  const { children, heading, styles, actionButtons } = props;\n  return (\n    <ErrorBoundary title={heading}>\n      <StyledCard styles={styles}>\n        { actionButtons && actionButtons }\n        { heading && <Heading className=\"inner-heading\" as=\"h3\" align=\"left\" color={colors.primary}>{heading}</Heading> }\n        {children}\n      </StyledCard>\n    </ErrorBoundary>\n  );\n}\n\nexport default StyledCard;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Heading.tsx",
    "content": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { TextSizes } from 'web-check-live/styles/typography';\nimport type { ReactNode } from 'react';\n\ninterface HeadingProps {\n  as?: 'h1' | 'h2' | 'h3' | 'h4' | 'p';\n  align?: 'left' | 'center' | 'right';\n  color?: string;\n  size?: 'xSmall' | 'small' | 'medium' | 'large' | 'xLarge';\n  inline?: boolean;\n  children: ReactNode;\n  id?: string;\n  className?: string;\n};\n\nconst StyledHeading = styled.h1<HeadingProps>`\n  margin: 0.5rem 0;\n  text-shadow: 2px 2px 0px ${colors.bgShadowColor};\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  align-items: center;\n  font-size: ${TextSizes.medium};\n  img { // Some titles have an icon\n    width: 2.5rem;\n    border-radius: 4px;\n  }\n  a { // If a title is a link, keep title styles\n    color: inherit;\n    text-decoration: none;\n    display: flex;\n  }\n  ${props => {\n    switch (props.size) {\n      case 'xSmall': return `font-size: ${TextSizes.xSmall};`;\n      case 'small': return `font-size: ${TextSizes.small};`;\n      case 'medium': return `font-size: ${TextSizes.large};`;\n      case 'large': return `font-size: ${TextSizes.xLarge};`;\n      case 'xLarge': return `font-size: ${TextSizes.xLarge};`;\n    }\n  }};\n  ${props => {\n    switch (props.align) {\n      case 'left': return 'text-align: left;';\n      case 'right': return 'text-align: right;';\n      case 'center': return 'text-align: center; justify-content: center;';\n    }\n  }};\n  ${props => props.color ? `color: ${props.color};` : '' }\n  ${props => props.inline ? 'display: inline;' : '' }\n`;\n\nconst makeAnchor = (title: string): string => {\n  return title.toLowerCase().replace(/[^\\w\\s]|_/g, \"\").replace(/\\s+/g, \"-\");\n};\n\nconst Heading = (props: HeadingProps): JSX.Element => {\n  const { children, as, size, align, color, inline, id, className } = props;\n  return (\n    <StyledHeading as={as} size={size} align={align} color={color} inline={inline} className={className} id={id || makeAnchor((children || '')?.toString())}>\n      {children}\n    </StyledHeading>\n  );\n}\n\nexport default Heading;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Input.tsx",
    "content": "import { type InputHTMLAttributes } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { type InputSize, applySize } from 'web-check-live/styles/dimensions';\n\ntype Orientation = 'horizontal' | 'vertical';\n\ninterface Props {\n  id: string,\n  value: string,\n  name?: string,\n  label?: string,\n  placeholder?: string,\n  disabled?: boolean,\n  size?: InputSize,\n  orientation?: Orientation;\n  handleChange: (nweVal: React.ChangeEvent<HTMLInputElement>) => void,\n  handleKeyDown?: (keyEvent: React.KeyboardEvent<HTMLInputElement>) => void,\n};\n\ntype SupportedElements = HTMLInputElement | HTMLLabelElement | HTMLDivElement;\ninterface StyledInputTypes extends InputHTMLAttributes<SupportedElements> {\n  inputSize?: InputSize;\n  orientation?: Orientation;\n};\n\nconst InputContainer = styled.div<StyledInputTypes>`\n  display: flex;\n  ${props => props.orientation === 'vertical' ? 'flex-direction: column;' : ''};\n`;\n\nconst StyledInput = styled.input<StyledInputTypes>`\n  background: ${colors.background};\n  color: ${colors.textColor};\n  border: none;\n  border-radius: 0.25rem;\n  font-family: PTMono;\n  box-shadow: 3px 3px 0px ${colors.backgroundDarker};\n  &:focus {\n    outline: 1px solid ${colors.primary}\n  }\n\n  ${props => applySize(props.inputSize)};\n`;\n\nconst StyledLabel = styled.label<StyledInputTypes>`\n  color: ${colors.textColor};\n  ${props => applySize(props.inputSize)};\n  padding: 0;\n  font-size: 1.6rem;\n`;\n\nconst Input = (inputProps: Props): JSX.Element => {\n\n  const { id, value, label, placeholder, name, disabled, size, orientation, handleChange, handleKeyDown } = inputProps;\n\n  return (\n  <InputContainer orientation={orientation}>\n    { label && <StyledLabel htmlFor={id} inputSize={size}>{ label }</StyledLabel> }\n    <StyledInput\n      id={id}\n      value={value}\n      placeholder={placeholder}\n      name={name}\n      disabled={disabled}\n      onChange={handleChange}\n      inputSize={size}\n      onKeyDown={handleKeyDown || (() => {})}\n    />\n  </InputContainer>\n  );\n};\n\nexport default Input;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Modal.tsx",
    "content": "import React from 'react';\nimport type { ReactNode } from 'react';\nimport ReactDOM from 'react-dom';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport Button from 'web-check-live/components/Form/Button';\n\ninterface ModalProps {\n  children: ReactNode;\n  isOpen: boolean;\n  closeModal: () => void;\n}\n\nconst Overlay = styled.div`\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background-color: rgba(0, 0, 0, 0.5);\n  animation: fadeIn 0.5s;\n  \n  @keyframes fadeIn {\n    0% {opacity: 0;}\n    100% {opacity: 1;}\n  }\n`;\n\nconst ModalWindow = styled.div`\n  width: 80%;\n  max-width: 500px;\n  background: ${colors.backgroundLighter};\n  padding: 2rem;\n  border-radius: 12px;\n  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n  position: relative;\n  animation: appear 0.5s;\n  color: ${colors.textColor};\n  box-shadow: 4px 4px 0px ${colors.bgShadowColor};\n  max-height: 80%;\n  overflow-y: auto;\n  @keyframes appear {\n    0% {opacity: 0; transform: scale(0.9);}\n    100% {opacity: 1; transform: scale(1);}\n  }\n  pre {\n    white-space: break-spaces;\n  }\n`;\n\nconst Modal: React.FC<ModalProps> = ({ children, isOpen, closeModal }) => {\n  const handleOverlayClick = (e: React.MouseEvent<HTMLElement>) => {\n    if (e.target === e.currentTarget) {\n      closeModal();\n    }\n  };\n\n  React.useEffect(() => {\n    const handleEscPress = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') {\n        closeModal();\n      }\n    };\n\n    if (isOpen) {\n      window.addEventListener('keydown', handleEscPress);\n    }\n\n    return () => {\n      window.removeEventListener('keydown', handleEscPress);\n    };\n  }, [isOpen, closeModal]);\n\n  if (!isOpen) {\n    return null;\n  }\n\n  return ReactDOM.createPortal(\n    <Overlay onClick={handleOverlayClick}>\n      <ModalWindow>\n        {children}\n        <Button onClick={closeModal} styles=\"width: fit-content;float: right;\">Close</Button>\n      </ModalWindow>\n    </Overlay>,\n    document.body,\n  );\n};\n\nexport default Modal;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Nav.tsx",
    "content": "import styled from '@emotion/styled';\nimport type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\n\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nconst Header = styled(StyledCard)`\n  margin: 1rem auto;\n  display: flex;\n  flex-wrap: wrap;\n  align-items: baseline;\n  justify-content: space-between;\n  padding: 0.5rem 1rem;\n  align-items: center;\n  width: 95vw;\n`;\n\nconst Nav = (props: { children?: ReactNode}) => {\n  return (\n    <Header as=\"header\">\n    <Heading color={colors.primary} size=\"large\">\n      <img width=\"64\" src=\"/web-check.png\" alt=\"Web Check Icon\" />\n      <a href=\"/\" target=\"_self\">Web Check</a>\n    </Heading>\n      {props.children && props.children}\n  </Header>\n  );\n};\n\nexport default Nav;\n"
  },
  {
    "path": "src/web-check-live/components/Form/Row.tsx",
    "content": "import type { ReactNode } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nexport interface RowProps {\n  lbl: string,\n  val: string,\n  key?: string | number,\n  children?: ReactNode,\n  rowList?: RowProps[],\n  title?: string,\n  open?: boolean,\n  plaintext?: string,\n  listResults?: string[],\n}\n\nexport const StyledRow = styled.div`\n  display: flex;\n  justify-content: space-between;\n  flex-wrap: wrap;\n  padding: 0.25rem;\n  &li { border-bottom: 1px dashed ${colors.primaryTransparent} !important; }\n  &:not(:last-child) { border-bottom: 1px solid ${colors.primaryTransparent}; }\n  span.lbl { font-weight: bold; }\n  span.val {\n    max-width: 16rem;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    a {\n      color: ${colors.primary};\n    }\n  }  \n`;\n\nexport const Details = styled.details`\n  transition: all 0.2s ease-in-out;\n  summary {\n    padding-left: 1rem;\n    cursor: pointer;\n  }\n  summary:before {\n    content: \"►\";\n    position: absolute;\n    margin-left: -1rem;\n    color: ${colors.primary};\n    cursor: pointer;\n  }\n  &[open] summary:before {\n    content: \"▼\";\n  }\n`;\n\nconst SubRowList = styled.ul`\n  margin: 0;\n  padding: 0.25rem;\n  background: ${colors.primaryTransparent};\n`;\n\nconst PlainText = styled.pre`\n  background: ${colors.background};\n  width: 95%;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  border-radius: 4px;\n  padding: 0.25rem;\n`;\n\nconst List = styled.ul`\n  // background: ${colors.background};\n  width: 95%;\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  border-radius: 4px;\n  margin: 0;\n  padding: 0.25rem 0.25rem 0.25rem 1rem;\n  li {\n    // white-space: nowrap;\n    // overflow: hidden;\n    text-overflow: ellipsis;\n    list-style: circle;\n    &:first-letter{\n      text-transform: capitalize\n    }\n    &::marker {\n      color: ${colors.primary};\n    }\n  }\n`;\n\nconst isValidDate = (date: any): boolean => {\n  // Checks if a date is within reasonable range\n  const isInRange = (date: Date): boolean => {\n    return date >= new Date('1995-01-01') && date <= new Date('2030-12-31');\n  };\n\n  // Check if input is a timestamp\n  if (typeof date === 'number') {\n    const timestampDate = new Date(date);\n    return !isNaN(timestampDate.getTime()) && isInRange(timestampDate);\n  }\n\n  // Check if input is a date string\n  if (typeof date === 'string') {\n    const dateStringDate = new Date(date);\n    return !isNaN(dateStringDate.getTime()) && isInRange(dateStringDate);\n  }\n\n  // Check if input is a Date object\n  if (date instanceof Date) {\n    return !isNaN(date.getTime()) && isInRange(date);\n  }\n\n  return false;\n};\n\n\nconst formatDate = (dateString: string): string => {\n  return new Intl.DateTimeFormat('en-GB', {\n    day: 'numeric', month: 'long', year: 'numeric'\n  }).format(new Date(dateString));\n}\nconst formatValue = (value: any): string => {\n  if (isValidDate(new Date(value))) return formatDate(value);\n  if (typeof value === 'boolean') return value ? '✅' : '❌';\n  return value;\n};\n\n\nconst copyToClipboard = (text: string) => {\n  navigator.clipboard.writeText(text);\n}\n\nconst snip = (text: string, length: number = 80) => {\n  if (text.length < length) return text;\n  return `${text.substring(0, length)}...`;\n};\n\nexport const ExpandableRow = (props: RowProps) => {\n  const { lbl, val, title, rowList, open } = props;\n  return (\n    <Details open={open}>\n      <StyledRow as=\"summary\" key={`${lbl}-${val}`}>\n        <span className=\"lbl\" title={title?.toString()}>{lbl}</span>\n        <span className=\"val\" title={val?.toString()}>{val.toString()}</span>\n      </StyledRow>\n      { rowList &&\n        <SubRowList>\n          { rowList?.map((row: RowProps, index: number) => {\n            return (\n              <StyledRow as=\"li\" key={`${row.lbl}-${index}`}>\n                <span className=\"lbl\" title={row.title?.toString()}>{row.lbl}</span>\n                <span className=\"val\" title={row.val?.toString()} onClick={() => copyToClipboard(row.val)}>\n                  {formatValue(row.val)}\n                </span>\n                { row.plaintext && <PlainText>{row.plaintext}</PlainText> }\n                { row.listResults && (<List>\n                  {row.listResults.map((listItem: string, listIndex: number) => (\n                    <li key={listItem}>{snip(listItem)}</li>\n                  ))}\n                </List>)}\n              </StyledRow>\n            )\n          })}\n        </SubRowList>\n      }\n    </Details>\n  );\n};\n\nexport const ListRow = (props: { list: string[], title: string }) => {\n  const { list, title } = props;\n  return (\n  <>\n    <Heading as=\"h4\" size=\"small\" align=\"left\" color={colors.primary}>{title}</Heading>\n    { list.map((entry: string, index: number) => {\n      return (\n      <Row lbl=\"\" val=\"\" key={`${entry}-${title.toLocaleLowerCase()}-${index}`}>\n        <span>{ entry }</span>\n      </Row>\n      )}\n    )}\n  </>\n);\n}\n\nconst Row = (props: RowProps) => {\n  const { lbl, val, title, plaintext, listResults, children } = props;\n  if (children) return <StyledRow key={`${lbl}-${val}`}>{children}</StyledRow>;\n  return (\n  <StyledRow key={`${lbl}-${val}`}>\n    { lbl && <span className=\"lbl\" title={title?.toString()}>{lbl}</span> }\n    <span className=\"val\" title={val} onClick={() => copyToClipboard(val)}>\n      {formatValue(val)}\n    </span>\n    { plaintext && <PlainText>{plaintext}</PlainText> }\n    { listResults && (<List>\n      {listResults.map((listItem: string, listIndex: number) => (\n        <li key={listIndex} title={listItem}>{snip(listItem)}</li>\n      ))}\n    </List>)}\n  </StyledRow>\n  );\n};\n\nexport default Row;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Archives.tsx",
    "content": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst Note = styled.small`\nopacity: 0.5;\ndisplay: block;\nmargin-top: 0.5rem;\na {\n  color: ${colors.primary};\n}\n`;\n\nconst ArchivesCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const data = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      <Row lbl=\"First Scan\" val={data.firstScan} />\n      <Row lbl=\"Last Scan\" val={data.lastScan} />\n      <Row lbl=\"Total Scans\" val={data.totalScans} />\n      <Row lbl=\"Change Count\" val={data.changeCount} />\n      <Row lbl=\"Avg Size\" val={`${data.averagePageSize} bytes`} />\n      { data.scanFrequency?.scansPerDay > 1 ?\n        <Row lbl=\"Avg Scans Per Day\" val={data.scanFrequency.scansPerDay} /> :\n        <Row lbl=\"Avg Days between Scans\" val={data.scanFrequency.daysBetweenScans} />\n      }\n\n      <Note>\n        View historical versions of this page <a rel=\"noreferrer\" target=\"_blank\" href={`https://web.archive.org/web/*/${data.scanUrl}`}>here</a>,\n        via the Internet Archive's Wayback Machine.\n      </Note>\n    </Card>\n  );\n}\n\nexport default ArchivesCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/BlockLists.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst BlockListsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const blockLists = props.data.blocklists;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { blockLists.map((blocklist: any, blockIndex: number) => (\n        <Row\n          title={blocklist.serverIp}\n          lbl={blocklist.server}\n          val={blocklist.isBlocked ? '❌ Blocked' : '✅ Not Blocked'}\n          key={`blocklist-${blockIndex}-${blocklist.serverIp}`}\n        />\n      ))}\n    </Card>\n  );\n}\n\nexport default BlockListsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/BuiltWith.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport type { TechnologyGroup, Technology } from 'web-check-live/utils/result-processor';\nimport colors from 'web-check-live/styles/colors';\nimport Card from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nconst Outer = styled(Card)`\n  grid-row: span 2\n`;\n\nconst Row = styled.div`\n  display: flex;\n  justify-content: space-between;\n  padding: 0.25rem;\n  &:not(:last-child) { border-bottom: 1px solid ${colors.primaryTransparent}; }\n  span.lbl { font-weight: bold; }\n  span.val {\n    max-width: 200px;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst ListRow = (props: { list: Technology[], title: string }) => {\n  const { list, title } = props;\n  return (\n    <>\n      <Heading as=\"h3\" align=\"left\" color={colors.primary}>{title}</Heading>\n      { list.map((entry: Technology, index: number) => {\n        return (\n        <Row key={`${title.toLocaleLowerCase()}-${index}`}><span>{ entry.Name }</span></Row>\n        )}\n      )}\n    </>\n  );\n}\n\nconst BuiltWithCard = (props: { data: TechnologyGroup[]}): JSX.Element => {\n  // const { created, updated, expires, nameservers } = whois;\n  return (\n    <Outer>\n      <Heading as=\"h3\" align=\"left\" color={colors.primary}>Technologies</Heading>\n      { props.data.map((group: TechnologyGroup) => {\n        return (\n          <ListRow key={group.tag} title={group.tag} list={group.technologies} />\n        );\n      })}\n      {/* { created && <DataRow lbl=\"Created\" val={formatDate(created)} /> }\n      { updated && <DataRow lbl=\"Updated\" val={formatDate(updated)} /> }\n      { expires && <DataRow lbl=\"Expires\" val={formatDate(expires)} /> }\n      { nameservers && <ListRow title=\"Name Servers\" list={nameservers} /> } */}\n    </Outer>\n  );\n}\n\nexport default BuiltWithCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/CarbonFootprint.tsx",
    "content": "import { useEffect, useState } from 'react';\nimport styled from '@emotion/styled';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport colors from 'web-check-live/styles/colors';\n\nconst LearnMoreInfo = styled.p`\nfont-size: 0.8rem;\nmargin-top: 0.5rem;\nopacity: 0.75;\na { color: ${colors.primary}; }\n`;\n\nconst CarbonCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const carbons = props.data.statistics;\n  const initialUrl = props.data.scanUrl;\n\n  const [carbonData, setCarbonData] = useState<{c?: number, p?: number}>({});\n\n  useEffect(() => {\n    const fetchCarbonData = async () => {\n      try {\n        const response = await fetch(`https://api.websitecarbon.com/b?url=${encodeURIComponent(initialUrl)}`);\n        const data = await response.json();\n        setCarbonData(data);\n      } catch (error) {\n        console.error('Error fetching carbon data:', error);\n      }\n    };\n    fetchCarbonData();\n  }, [initialUrl]);\n\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { (!carbons?.adjustedBytes && !carbonData.c) && <p>Unable to calculate carbon footprint for host</p>}\n      { carbons?.adjustedBytes > 0 && <>\n        <Row lbl=\"HTML Initial Size\" val={`${carbons.adjustedBytes} bytes`} />\n        <Row lbl=\"CO2 for Initial Load\" val={`${(carbons.co2.grid.grams * 1000).toPrecision(4)} grams`} />\n        <Row lbl=\"Energy Usage for Load\" val={`${(carbons.energy * 1000).toPrecision(4)} KWg`} />\n      </>}\n      {carbonData.c && <Row lbl=\"CO2 Emitted\" val={`${carbonData.c} grams`} />}\n      {carbonData.p && <Row lbl=\"Better than average site by\" val={`${carbonData.p}%`} />}\n      <br />\n      <LearnMoreInfo>Learn more at <a href=\"https://www.websitecarbon.com/\">websitecarbon.com</a></LearnMoreInfo>\n    </Card>\n  );\n}\n\nexport default CarbonCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/ContentLinks.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\n  small { margin-top: 1rem; opacity: 0.5; }\n  a {\n    color: ${colors.textColor};\n  }\n  details {\n    // display: inline;\n    display: flex;\n    transition: all 0.2s ease-in-out;\n    h3 {\n      display: inline;\n    }\n    summary {\n      padding: 0;\n      margin: 1rem 0 0 0;\n      cursor: pointer;\n    }\n    summary:before {\n      content: \"►\";\n      position: absolute;\n      margin-left: -1rem;\n      color: ${colors.primary};\n      cursor: pointer;\n    }\n    &[open] summary:before {\n      content: \"▼\";\n    }\n  }\n`;\n\nconst getPathName = (link: string) => {\n  try {\n    const url = new URL(link);\n    return url.pathname;\n  } catch(e) {\n    return link;\n  }\n};\n\nconst ContentLinksCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const internal = props.data.internal || [];\n  const external = props.data.external || [];\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <Heading as=\"h3\" size=\"small\" color={colors.primary}>Summary</Heading>\n      <Row lbl=\"Internal Link Count\" val={internal.length} />\n      <Row lbl=\"External Link Count\" val={external.length} />\n      { internal && internal.length > 0 && (\n        <details>\n          <summary><Heading as=\"h3\" size=\"small\" color={colors.primary}>Internal Links</Heading></summary>\n          {internal.map((link: string) => (\n          <Row key={link} lbl=\"\" val=\"\">\n            <a href={link} target=\"_blank\" rel=\"noreferrer\">{getPathName(link)}</a>\n          </Row>\n        ))}\n        </details>\n      )}\n      { external && external.length > 0 && (\n        <details>\n          <summary><Heading as=\"h3\" size=\"small\" color={colors.primary}>External Links</Heading></summary>\n          {external.map((link: string) => (\n            <Row key={link} lbl=\"\" val=\"\">\n              <a href={link} target=\"_blank\" rel=\"noreferrer\">{link}</a>\n            </Row>\n          ))}\n        </details>\n      )}\n    </Card>\n  );\n}\n\nexport default ContentLinksCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Cookies.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport { ExpandableRow } from 'web-check-live/components/Form/Row';\nimport type { Cookie } from 'web-check-live/utils/result-processor';\n\nexport const parseHeaderCookies = (cookiesHeader: string[]): Cookie[] => {\n  if (!cookiesHeader || !cookiesHeader.length) return [];\n  const cookies = cookiesHeader.flatMap(cookieHeader => {\n    return cookieHeader.split(/,(?=\\s[A-Za-z0-9]+=)/).map(cookieString => {\n      const [nameValuePair, ...attributePairs] = cookieString.split('; ').map(part => part.trim());\n      const [name, value] = nameValuePair.split('=');\n      const attributes: Record<string, string> = {};\n      attributePairs.forEach(pair => {\n        const [attributeName, attributeValue = ''] = pair.split('=');\n        attributes[attributeName] = attributeValue;\n      });\n      return { name, value, attributes };\n    });\n  });\n  return cookies;\n};\n\nconst CookiesCard = (props: { data: any, title: string, actionButtons: any}): JSX.Element => {\n  const headerCookies = parseHeaderCookies(props.data.headerCookies) || [];\n  const clientCookies = props.data.clientCookies || [];\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      {\n        headerCookies.map((cookie: any, index: number) => {\n          const attributes = Object.keys(cookie.attributes).map((key: string) => {\n            return { lbl: key, val: cookie.attributes[key] }\n          });\n          return (\n            <ExpandableRow key={`cookie-${index}`} lbl={cookie.name} val={cookie.value} rowList={attributes} />\n          )\n        })\n      }\n      {\n        clientCookies.map((cookie: any) => {\n          const nameValPairs = Object.keys(cookie).map((key: string) => { return { lbl: key, val: cookie[key] }});\n          return (\n            <ExpandableRow key={`cookie-${cookie.name}`} lbl={cookie.name} val=\"\" rowList={nameValPairs} />\n          );\n        })\n      }\n    </Card>\n  );\n}\n\nexport default CookiesCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/DnsRecords.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ListRow }  from 'web-check-live/components/Form/Row';\n\nconst styles = `\n  grid-row: span 2;\n  .content {\n    max-height: 50rem;\n    overflow-x: hidden;\n    overflow-y: auto;\n  }\n`;\n\nconst DnsRecordsCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const dnsRecords = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={styles}>\n      <div className=\"content\">\n      { dnsRecords.A && <Row lbl=\"A\" val={dnsRecords.A.address} /> }\n      { dnsRecords.AAAA?.length > 0 && <ListRow title=\"AAAA\" list={dnsRecords.AAAA} /> }\n      { dnsRecords.MX?.length > 0 && <ListRow title=\"MX\" list={dnsRecords.MX} /> }\n      { dnsRecords.CNAME?.length > 0 && <ListRow title=\"CNAME\" list={dnsRecords.CNAME} /> }\n      { dnsRecords.NS?.length > 0 && <ListRow title=\"NS\" list={dnsRecords.NS} /> }\n      { dnsRecords.PTR?.length > 0 && <ListRow title=\"PTR\" list={dnsRecords.PTR} /> }\n      { dnsRecords.SOA?.length > 0 && <ListRow title=\"SOA\" list={dnsRecords.SOA} /> }\n      </div>\n    </Card>\n  );\n}\n\nexport default DnsRecordsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/DnsSec.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ExpandableRow, type RowProps } from 'web-check-live/components/Form/Row';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\n\n\nconst parseDNSKeyData = (data: string) => {\n  const dnsKey = data.split(' ');\n\n  const flags = parseInt(dnsKey[0]);\n  const protocol = parseInt(dnsKey[1]);\n  const algorithm = parseInt(dnsKey[2]);\n\n  let flagMeaning = '';\n  let protocolMeaning = '';\n  let algorithmMeaning = '';\n\n  // Flags\n  if (flags === 256) {\n    flagMeaning = 'Zone Signing Key (ZSK)';\n  } else if (flags === 257) {\n    flagMeaning = 'Key Signing Key (KSK)';\n  } else {\n    flagMeaning = 'Unknown';\n  }\n\n  // Protocol\n  protocolMeaning = protocol === 3 ? 'DNSSEC' : 'Unknown';\n\n  // Algorithm\n  switch (algorithm) {\n    case 5: \n      algorithmMeaning = 'RSA/SHA-1';\n      break;\n    case 7: \n      algorithmMeaning = 'RSASHA1-NSEC3-SHA1';\n      break;\n    case 8: \n      algorithmMeaning = 'RSA/SHA-256';\n      break;\n    case 10: \n      algorithmMeaning = 'RSA/SHA-512';\n      break;\n    case 13: \n      algorithmMeaning = 'ECDSA Curve P-256 with SHA-256';\n      break;\n    case 14: \n      algorithmMeaning = 'ECDSA Curve P-384 with SHA-384';\n      break;\n    case 15: \n      algorithmMeaning = 'Ed25519';\n      break;\n    case 16: \n      algorithmMeaning = 'Ed448';\n      break;\n    default: \n      algorithmMeaning = 'Unknown';\n      break;\n  }\n\n  return {\n    flags: flagMeaning,\n    protocol: protocolMeaning,\n    algorithm: algorithmMeaning,\n    publicKey: dnsKey[3]\n  };\n}\n\nconst getRecordTypeName = (typeCode: number): string => {\n  switch(typeCode) {\n      case 1: return 'A';\n      case 2: return 'NS';\n      case 5: return 'CNAME';\n      case 6: return 'SOA';\n      case 12: return 'PTR';\n      case 13: return 'HINFO';\n      case 15: return 'MX';\n      case 16: return 'TXT';\n      case 28: return 'AAAA';\n      case 33: return 'SRV';\n      case 35: return 'NAPTR';\n      case 39: return 'DNAME';\n      case 41: return 'OPT';\n      case 43: return 'DS';\n      case 46: return 'RRSIG';\n      case 47: return 'NSEC';\n      case 48: return 'DNSKEY';\n      case 50: return 'NSEC3';\n      case 51: return 'NSEC3PARAM';\n      case 52: return 'TLSA';\n      case 53: return 'SMIMEA';\n      case 55: return 'HIP';\n      case 56: return 'NINFO';\n      case 57: return 'RKEY';\n      case 58: return 'TALINK';\n      case 59: return 'CDS';\n      case 60: return 'CDNSKEY';\n      case 61: return 'OPENPGPKEY';\n      case 62: return 'CSYNC';\n      case 63: return 'ZONEMD';\n      default: return 'Unknown';\n  }\n}\n\nconst parseDSData = (dsData: string) => {\n  const parts = dsData.split(' ');\n  \n  const keyTag = parts[0];\n  const algorithm = getAlgorithmName(parseInt(parts[1], 10));\n  const digestType = getDigestTypeName(parseInt(parts[2], 10));\n  const digest = parts[3];\n\n  return {\n      keyTag,\n      algorithm,\n      digestType,\n      digest\n  };\n}\n\nconst getAlgorithmName = (code: number) => {\n  switch(code) {\n      case 1: return 'RSA/MD5';\n      case 2: return 'Diffie-Hellman';\n      case 3: return 'DSA/SHA1';\n      case 5: return 'RSA/SHA1';\n      case 6: return 'DSA/NSEC3/SHA1';\n      case 7: return 'RSASHA1/NSEC3/SHA1';\n      case 8: return 'RSA/SHA256';\n      case 10: return 'RSA/SHA512';\n      case 12: return 'ECC/GOST';\n      case 13: return 'ECDSA/CurveP256/SHA256';\n      case 14: return 'ECDSA/CurveP384/SHA384';\n      case 15: return 'Ed25519';\n      case 16: return 'Ed448';\n      default: return 'Unknown';\n  }\n}\n\nconst getDigestTypeName = (code: number) => {\n  switch(code) {\n      case 1: return 'SHA1';\n      case 2: return 'SHA256';\n      case 3: return 'GOST R 34.11-94';\n      case 4: return 'SHA384';\n      default: return 'Unknown';\n  }\n}\n\nconst makeResponseList = (response: any): RowProps[] => {\n  const result = [] as RowProps[];\n  if (!response) return result;\n  if (typeof response.RD === 'boolean') result.push({ lbl: 'Recursion Desired (RD)', val: response.RD });\n  if (typeof response.RA === 'boolean') result.push({ lbl: 'Recursion Available (RA)', val: response.RA });\n  if (typeof response.TC === 'boolean') result.push({ lbl: 'TrunCation (TC)', val: response.TC });\n  if (typeof response.AD === 'boolean') result.push({ lbl: 'Authentic Data (AD)', val: response.AD });\n  if (typeof response.CD === 'boolean') result.push({ lbl: 'Checking Disabled (CD)', val: response.CD });\n  return result;\n};\n\nconst makeAnswerList = (recordData: any): RowProps[] => {\n  return [\n    { lbl: 'Domain', val: recordData.name },\n    { lbl: 'Type', val: `${getRecordTypeName(recordData.type)} (${recordData.type})` },\n    { lbl: 'TTL', val: recordData.TTL },\n    { lbl: 'Algorithm', val: recordData.algorithm },\n    { lbl: 'Flags', val: recordData.flags },\n    { lbl: 'Protocol', val: recordData.protocol },\n    { lbl: 'Public Key', val: recordData.publicKey },\n    { lbl: 'Key Tag', val: recordData.keyTag },\n    { lbl: 'Digest Type', val: recordData.digestType },\n    { lbl: 'Digest', val: recordData.digest },\n  ].filter((rowData) => rowData.val && rowData.val !== 'Unknown');\n};\n\nconst DnsSecCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const dnsSec = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      {\n        ['DNSKEY', 'DS', 'RRSIG'].map((key: string, index: number) => {\n          const record = dnsSec[key];\n          return (<div key={`${key}-${index}`}>\n          <Heading as=\"h3\" size=\"small\" color={colors.primary}>{key}</Heading>\n          {(record.isFound && record.answer) && (<>\n              <Row lbl={`${key} - Present?`} val=\"✅ Yes\" />\n              {\n                record.answer.map((answer: any, index: number) => {\n                  const keyData = parseDNSKeyData(answer.data);\n                  const dsData = parseDSData(answer.data);\n                  const label = (keyData.flags && keyData.flags !== 'Unknown') ? keyData.flags : key;\n                  return (\n                  <ExpandableRow lbl={`Record #${index+1}`} val={label} rowList={makeAnswerList({ ...answer, ...keyData, ...dsData })} open />\n                );\n                })\n              }\n          </>)}\n\n            {(!record.isFound && record.response) && (\n              <ExpandableRow lbl={`${key} - Present?`} val={record.isFound ? '✅ Yes' : '❌ No'} rowList={makeResponseList(record.response)} />\n            )}\n          </div>)\n        })\n      }\n    </Card>\n  );\n}\n\nexport default DnsSecCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/DnsServer.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Row from 'web-check-live/components/Form/Row';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\n  small {\n    margin-top: 1rem;\n    opacity: 0.5;\n    display: block;\n    a { color: ${colors.primary}; }\n  }\n`;\n\nconst DnsServerCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const dnsSecurity = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {dnsSecurity.dns.map((dns: any, index: number) => {\n        return (<div key={`dns-${index}`}>\n          { dnsSecurity.dns.length > 1 && <Heading as=\"h4\" size=\"small\" color={colors.primary}>DNS Server #{index+1}</Heading> }\n          <Row lbl=\"IP Address\" val={dns.address} key={`ip-${index}`} />\n          { dns.hostname && <Row lbl=\"Hostname\" val={dns.hostname}  key={`host-${index}`} /> }\n          <Row lbl=\"DoH Support\" val={dns.dohDirectSupports ? '✅ Yes*' : '❌ No*'} key={`doh-${index}`} />\n        </div>);\n      })}\n      {dnsSecurity.dns.length > 0 && (<small>\n        * DoH Support is determined by the DNS server's response to a DoH query.\n        Sometimes this gives false negatives, and it's also possible that the DNS server supports DoH but does not respond to DoH queries.\n        If the DNS server does not support DoH, it may still be possible to use DoH by using a DoH proxy.\n      </small>)}\n    </Card>\n  );\n}\n\nexport default DnsServerCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/DomainLookup.tsx",
    "content": "\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\nspan.val {\n  &.up { color: ${colors.success}; }\n  &.down { color: ${colors.danger}; }\n}\n`;\n\nconst DomainLookupCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const domain = props.data.internicData || {};\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { domain.Domain_Name && <Row lbl=\"Registered Domain\" val={domain.Domain_Name} /> }\n      { domain.Creation_Date && <Row lbl=\"Creation Date\" val={domain.Creation_Date} /> }\n      { domain.Updated_Date && <Row lbl=\"Updated Date\" val={domain.Updated_Date} /> }\n      { domain.Registry_Expiry_Date && <Row lbl=\"Registry Expiry Date\" val={domain.Registry_Expiry_Date} /> }\n      { domain.Registry_Domain_ID && <Row lbl=\"Registry Domain ID\" val={domain.Registry_Domain_ID} /> }\n      { domain.Registrar_WHOIS_Server && <Row lbl=\"Registrar WHOIS Server\" val={domain.Registrar_WHOIS_Server} /> }\n      { domain.Registrar && <Row lbl=\"\" val=\"\">\n        <span className=\"lbl\">Registrar</span>\n        <span className=\"val\"><a href={domain.Registrar_URL || '#'}>{domain.Registrar}</a></span>\n      </Row> }\n      { domain.Registrar_IANA_ID && <Row lbl=\"Registrar IANA ID\" val={domain.Registrar_IANA_ID} /> }\n    </Card>\n  );\n}\n\nexport default DomainLookupCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Firewall.tsx",
    "content": "import styled from '@emotion/styled';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst Note = styled.small`\nopacity: 0.5;\ndisplay: block;\nmargin-top: 0.5rem;\n`;\n\nconst FirewallCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const data = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      <Row lbl=\"Firewall\" val={data.hasWaf ? '✅ Yes' : '❌ No*' } />\n      { data.waf && <Row lbl=\"WAF\" val={data.waf} /> }\n      { !data.hasWaf && (<Note>\n        *The domain may be protected with a proprietary or custom WAF which we were unable to identify automatically\n      </Note>) }\n    </Card>\n  );\n}\n\nexport default FirewallCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Headers.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport type { ReactNode } from 'react';\n\nconst HeadersCard = (props: { data: any, title: string, actionButtons: ReactNode }): JSX.Element => {\n  const headers = props.data;\n  return (\n    <Card heading={props.title} styles=\"grid-row: span 2;\" actionButtons={props.actionButtons}>\n      {\n        Object.keys(headers).map((header: string, index: number) => {\n          return (\n            <Row key={`header-${index}`} lbl={header} val={headers[header]} />\n          )\n        })\n      }      \n    </Card>\n  );\n}\n\nexport default HeadersCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/HostNames.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport type { HostNames } from 'web-check-live/utils/result-processor';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nconst Row = styled.div`\n  display: flex;\n  justify-content: space-between;\n  padding: 0.25rem;\n  &:not(:last-child) { border-bottom: 1px solid ${colors.primaryTransparent}; }\n  span:first-child { font-weight: bold; }\n`;\n\nconst HostListSection = (props: { list: string[], title: string }) => {\n  const { list, title } = props;\n  return (\n  <>\n    <Heading as=\"h4\" size=\"small\" align=\"left\" color={colors.primary}>{title}</Heading>\n    { list.map((entry: string, index: number) => {\n      return (\n      <Row key={`${title.toLocaleLowerCase()}-${index}`}><span>{ entry }</span></Row>\n      )}\n    )}\n  </>\n);\n}\n\nconst cardStyles = `\n  max-height: 50rem;\n  overflow: auto;\n`;\n\nconst HostNamesCard = (props: { data: HostNames, title: string, actionButtons: any }): JSX.Element => {\n  const hosts = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { hosts.domains.length > 0 &&\n        <HostListSection list={hosts.domains} title=\"Domains\" />\n      }\n      { hosts.hostnames.length > 0 &&\n        <HostListSection list={hosts.hostnames} title=\"Hosts\" />\n      }\n    </Card>\n  );\n}\n\nexport default HostNamesCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Hsts.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { type RowProps } from 'web-check-live/components/Form/Row';\n\nconst cardStyles = '';\n\nconst parseHeader = (headerString: string): RowProps[] => {\n  return headerString.split(';').map((part) => {\n    const trimmedPart = part.trim();\n    const equalsIndex = trimmedPart.indexOf('=');\n\n    if (equalsIndex >= 0) {\n      return {\n        lbl: trimmedPart.substring(0, equalsIndex).trim(),\n        val: trimmedPart.substring(equalsIndex + 1).trim(),\n      };\n    } else {\n      return { lbl: trimmedPart, val: 'true' };\n    }\n  });\n};\n\nconst HstsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const hstsResults = props.data;\n  const hstsHeaders = hstsResults?.hstsHeader ? parseHeader(hstsResults.hstsHeader) : [];\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {typeof hstsResults.compatible === 'boolean' && (\n        <Row lbl=\"HSTS Enabled?\" val={hstsResults.compatible ? '✅ Yes' : '❌ No'} />\n      )}\n      {hstsHeaders.length > 0 && hstsHeaders.map((header: RowProps, index: number) => {\n        return (\n          <Row lbl={header.lbl} val={header.val} key={`hsts-${index}`} />\n        );\n      })\n      }\n      {hstsResults.message && (<p>{hstsResults.message}</p>)}\n    </Card>\n  );\n}\n\nexport default HstsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/HttpSecurity.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst HttpSecurityCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const data = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      <Row lbl=\"Content Security Policy\" val={data.contentSecurityPolicy ? '✅ Yes' : '❌ No' } />\n      <Row lbl=\"Strict Transport Policy\" val={data.strictTransportPolicy ? '✅ Yes' : '❌ No' } />\n      <Row lbl=\"X-Content-Type-Options\" val={data.xContentTypeOptions ? '✅ Yes' : '❌ No' } />\n      <Row lbl=\"X-Frame-Options\" val={data.xFrameOptions ? '✅ Yes' : '❌ No' } />\n      <Row lbl=\"X-XSS-Protection\" val={data.xXSSProtection ? '✅ Yes' : '❌ No' } />\n    </Card>\n  );\n}\n\nexport default HttpSecurityCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Lighthouse.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport { ExpandableRow } from 'web-check-live/components/Form/Row';\n\nconst processScore = (percentile: number) => {\n  return `${Math.round(percentile * 100)}%`;\n}\n\ninterface Audit {\n  id: string,\n  score?: number | string,\n  scoreDisplayMode?: string,\n  title?: string,\n  description?: string,\n  displayValue?: string,\n};\n\nconst makeValue = (audit: Audit) => {\n  let score = audit.score;\n  if (audit.displayValue) {\n    score = audit.displayValue;\n  } else if (audit.scoreDisplayMode) {\n    score = audit.score === 1 ? '✅ Pass' : '❌ Fail'; \n  }\n  return score;\n};\n\nconst LighthouseCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const lighthouse = props.data;\n  const categories = lighthouse?.categories || {};\n  const audits = lighthouse?.audits || [];\n\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { Object.keys(categories).map((title: string, index: number) => {\n        const scoreIds = categories[title].auditRefs.map((ref: { id: string }) => ref.id);\n        const scoreList = scoreIds.map((id: string) => {\n          return { lbl: audits[id].title, val: makeValue(audits[id]), title: audits[id].description, key: id }\n        })\n        return (\n          <ExpandableRow\n            key={`lighthouse-${index}`}\n            lbl={title}\n            val={processScore(categories[title].score)}\n            rowList={scoreList}\n          />\n        );\n      }) }\n    </Card>\n  );\n}\n\nexport default LighthouseCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/MailConfig.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = ``;\n\nconst MailConfigCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const mailServer = props.data;\n  const txtRecords = (mailServer.txtRecords || []).join('').toLowerCase() || '';\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <Heading as=\"h3\" color={colors.primary} size=\"small\">Mail Security Checklist</Heading>\n      <Row lbl=\"SPF\" val={txtRecords.includes('spf')} />\n      <Row lbl=\"DKIM\" val={txtRecords.includes('dkim')} />\n      <Row lbl=\"DMARC\" val={txtRecords.includes('dmarc')} />\n      <Row lbl=\"BIMI\" val={txtRecords.includes('bimi')} />\n\n      { mailServer.mxRecords && <Heading as=\"h3\" color={colors.primary} size=\"small\">MX Records</Heading>}\n      { mailServer.mxRecords && mailServer.mxRecords.map((record: any, index: number) => (\n          <Row lbl=\"\" val=\"\" key={index}>\n            <span>{record.exchange}</span>\n            <span>{record.priority ? `Priority: ${record.priority}` : ''}</span>\n          </Row>\n        ))\n      }\n      { mailServer.mailServices.length > 0 && <Heading as=\"h3\" color={colors.primary} size=\"small\">External Mail Services</Heading>}\n      { mailServer.mailServices && mailServer.mailServices.map((service: any, index: number) => (\n        <Row lbl={service.provider} title={service.value} val=\"\" key={index} />\n        ))\n      }\n\n      { mailServer.txtRecords && <Heading as=\"h3\" color={colors.primary} size=\"small\">Mail-related TXT Records</Heading>}\n      { mailServer.txtRecords && mailServer.txtRecords.map((record: any, index: number) => (\n          <Row lbl=\"\" val=\"\" key={index}>\n            <span>{record}</span>\n          </Row>\n        ))\n      }\n    </Card>\n  );\n}\n\nexport default MailConfigCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/OpenPorts.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\n  small { margin-top: 1rem; opacity: 0.5; }\n`;\n\nconst OpenPortsCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const portData = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {portData.openPorts.map((port: any) => (\n          <Row key={port} lbl=\"\" val=\"\">\n            <span>{port}</span>\n          </Row>\n        )\n      )}\n      <br />\n      <small>\n        Unable to establish connections to:<br />\n        {portData.failedPorts.join(', ')}\n      </small>\n    </Card>\n  );\n}\n\nexport default OpenPortsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Rank.tsx",
    "content": "\nimport { AreaChart, Area, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\nspan.val {\n  &.up { color: ${colors.success}; }\n  &.down { color: ${colors.danger}; }\n}\n.rank-average {\n  text-align: center;\n  font-size: 1.8rem;\n  font-weight: bold;\n}\n.chart-container {\n  margin-top: 1rem;\n}\n`;\n\nconst makeRankStats = (data: {date: string, rank: number }[]) => {\n  const average = Math.round(data.reduce((acc, cur) => acc + cur.rank, 0) / data.length);\n  const today = data[0].rank;\n  const yesterday = data[1].rank;\n  const percentageChange = ((today - yesterday) / yesterday) * 100;\n  return {\n    average,\n    percentageChange\n  };\n};\n\nconst makeChartData = (data: {date: string, rank: number }[]) => {\n  return data.map((d) => {\n    return {\n      date: d.date,\n      uv: d.rank\n    };\n  });\n};\n\nfunction Chart(chartData: { date: string; uv: number; }[], data: any) {\n  return <ResponsiveContainer width=\"100%\" height={100}>\n    <AreaChart width={400} height={100} data={chartData}>\n      <defs>\n        <linearGradient id=\"colorUv\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"20%\" stopColor=\"#0f1620\" stopOpacity={0.8} />\n          <stop offset=\"80%\" stopColor=\"#0f1620\" stopOpacity={0} />\n        </linearGradient>\n      </defs>\n      <CartesianGrid strokeDasharray=\"4\" strokeWidth={0.25} verticalPoints={[50, 100, 150, 200, 250, 300, 350]} horizontalPoints={[25, 50, 75]} />\n      <Tooltip contentStyle={{ background: colors.background, color: colors.textColor, borderRadius: 4 }}\n        labelFormatter={(value) => ['Date : ', data[value].date]} />\n      <Area type=\"monotone\" dataKey=\"uv\" stroke=\"#9fef00\" fillOpacity={1} name=\"Rank\" fill={`${colors.backgroundDarker}a1`} />\n    </AreaChart>\n  </ResponsiveContainer>;\n}\n\nconst RankCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const data = props.data.ranks || [];\n  const { average, percentageChange } = makeRankStats(data);\n  const chartData = makeChartData(data);\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <div className=\"rank-average\">{data[0].rank.toLocaleString()}</div>\n      <Row lbl=\"Change since Yesterday\" val={`${percentageChange > 0 ? '+':''} ${percentageChange.toFixed(2)}%`} />\n      <Row lbl=\"Historical Average Rank\" val={average.toLocaleString()} />\n      <div className=\"chart-container\">\n        {Chart(chartData, data)}\n      </div>\n    </Card>\n  );\n}\n\nexport default RankCard;\n\n\n"
  },
  {
    "path": "src/web-check-live/components/Results/Redirects.tsx",
    "content": "import colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\n  div {\n    justify-content: flex-start;\n    align-items: baseline; \n  }\n  .arrow-thing {\n    color: ${colors.primary};\n    font-size: 1.8rem;\n    font-weight: bold;\n    margin-right: 0.5rem;\n  }\n  .redirect-count {\n    color: ${colors.textColorSecondary};\n    margin: 0;\n  }\n`;\n\nconst RedirectsCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const redirects = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { !redirects?.redirects.length && <Row lbl=\"\" val=\"No redirects\" />}\n      <p className=\"redirect-count\">\n        Followed {redirects.redirects.length}{' '}\n        redirect{redirects.redirects.length === 1 ? '' : 's'} when contacting host\n      </p>\n      {redirects.redirects.map((redirect: any, index: number) => {\n        return (\n          <Row lbl=\"\" val=\"\" key={index}>\n          <span className=\"arrow-thing\">↳</span> {redirect}\n          </Row>\n        );\n      })}\n    </Card>\n  );\n}\n\nexport default RedirectsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/RobotsTxt.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { type RowProps }  from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\n  grid-row: span 2;\n  .content {\n    max-height: 50rem;\n    overflow-y: auto;\n  }\n`;\n\nconst RobotsTxtCard = ( props: { data: { robots: RowProps[]}, title: string, actionButtons: any}): JSX.Element => {\n  const { data } = props;\n  const robots = data?.robots || [];\n\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <div className=\"content\">\n      {\n        robots.length === 0 && <p>No crawl rules found.</p>\n      }\n      {\n        robots.map((row: RowProps, index: number) => {\n          return (\n            <Row key={`${row.lbl}-${index}`} lbl={row.lbl} val={row.val} />\n          )\n        })\n      }\n      </div>\n    </Card>\n  );\n}\n\nexport default RobotsTxtCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Screenshot.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\n\nconst cardStyles = `\n  overflow: auto;\n  max-height: 50rem;\n  grid-row: span 2;\n  img {\n    border-radius: 6px;\n    width: 100%;\n    margin 0.5rem 0;;\n  }\n`;\n\nconst ScreenshotCard = (props: { data: { image?: string, data?: string, }, title: string, actionButtons: any }): JSX.Element => {\n  const screenshot = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { screenshot.image && <img src={`data:image/png;base64,${screenshot.image}`}  alt=\"Full page screenshot\" /> }\n      { (!screenshot.image && screenshot.data) && <img src={screenshot.data} alt=\"Full page screenshot\" /> }\n    </Card>\n  );\n}\n\nexport default ScreenshotCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/SecurityTxt.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { Details } from 'web-check-live/components/Form/Row';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\nsmall {\n  margin-top: 1rem;\n  opacity: 0.5;\n  display: block;\n  a { color: ${colors.primary}; }\n}\nsummary {\n  padding: 0.5rem 0 0 0.5rem !important;\n  cursor: pointer;\n  font-weight: bold;\n}\npre {\n  background: ${colors.background};\n  padding: 0.5rem 0.25rem;\n  border-radius: 4px;\n  overflow: auto;\n}\n`;\n\nconst getPagePath = (url: string): string => {\n  try {\n    return new URL(url).pathname;\n  } catch (error) {\n    return url;\n  }\n}\n\nconst SecurityTxtCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const securityTxt = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <Row lbl=\"Present\" val={securityTxt.isPresent ? '✅ Yes' : '❌ No'} />\n      { securityTxt.isPresent && (\n        <>\n        <Row lbl=\"File Location\" val={securityTxt.foundIn} />\n        <Row lbl=\"PGP Signed\" val={securityTxt.isPgpSigned ? '✅ Yes' : '❌ No'} />\n        {securityTxt.fields && Object.keys(securityTxt.fields).map((field: string, index: number) => {\n          if (securityTxt.fields[field].includes('http')) return (\n            <Row lbl=\"\" val=\"\" key={`policy-url-row-${index}`}>\n              <span className=\"lbl\">{field}</span>\n              <span className=\"val\"><a href={securityTxt.fields[field]}>{getPagePath(securityTxt.fields[field])}</a></span>\n            </Row>\n          );\n          return (\n            <Row lbl={field} val={securityTxt.fields[field]} key={`policy-row-${index}`} />\n          );\n        })}\n        <Details>\n          <summary>View Full Policy</summary>\n          <pre>{securityTxt.content}</pre>\n        </Details>\n        </>\n      )}\n      {!securityTxt.isPresent && (<small>\n        Having a security.txt ensures security researchers know how and where to safely report vulnerabilities.\n      </small>)}\n    </Card>\n  );\n}\n\nexport default SecurityTxtCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/ServerInfo.tsx",
    "content": "import type { ServerInfo } from 'web-check-live/utils/result-processor';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst ServerInfoCard = (props: { data: ServerInfo, title: string, actionButtons: any }): JSX.Element => {\n  const info = props.data;\n  const { org, asn, isp, os, ports, ip, loc, type } = info;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { org && <Row lbl=\"Organization\" val={org} /> }\n      { (isp && isp !== org) && <Row lbl=\"Service Provider\" val={isp} /> }\n      { os && <Row lbl=\"Operating System\" val={os} /> }\n      { asn && <Row lbl=\"ASN Code\" val={asn} /> }\n      { ports && <Row lbl=\"Ports\" val={ports} /> }\n      { ip && <Row lbl=\"IP\" val={ip} /> }\n      { type && <Row lbl=\"Type\" val={type} /> }\n      { loc && <Row lbl=\"Location\" val={loc} /> }\n    </Card>\n  );\n}\n\nexport default ServerInfoCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/ServerLocation.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport type { ServerLocation } from 'web-check-live/utils/result-processor';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport LocationMap from 'web-check-live/components/misc/LocationMap';\nimport Flag from 'web-check-live/components/misc/Flag';\nimport { TextSizes } from 'web-check-live/styles/typography';\nimport Row, { StyledRow } from 'web-check-live/components/Form/Row';\n\nconst cardStyles = '';\n\nconst SmallText = styled.span`\n  opacity: 0.5;\n  font-size: ${TextSizes.xSmall};\n  text-align: right;\n  display: block;\n`;\n\nconst MapRow = styled(StyledRow)`\n  padding-top: 1rem;\n  flex-direction: column;\n`;\n\nconst CountryValue = styled.span`\n  display: flex;\n  gap: 0.5rem;\n`;\n\nconst ServerLocationCard = (props: { data: ServerLocation, title: string, actionButtons: any }): JSX.Element => {\n  const location = props.data;\n  const {\n    city, region, country,\n    postCode, countryCode, coords,\n    isp, timezone, languages, currency, currencyCode,\n  } = location;\n\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      <Row lbl=\"City\" val={`${postCode}, ${city}, ${region}`} />\n      <Row lbl=\"\" val=\"\">\n        <b>Country</b>\n        <CountryValue>\n          {country}\n          { countryCode && <Flag countryCode={countryCode} width={28} /> }\n        </CountryValue>\n      </Row>\n      <Row lbl=\"Timezone\" val={timezone} />\n      <Row lbl=\"Languages\" val={languages} />\n      <Row lbl=\"Currency\" val={`${currency} (${currencyCode})`} />\n      <MapRow>\n        <LocationMap lat={coords.latitude} lon={coords.longitude} label={`Server (${isp})`} />\n        <SmallText>Latitude: {coords.latitude}, Longitude: {coords.longitude} </SmallText>\n      </MapRow>\n    </Card>\n  );\n}\n\nexport default ServerLocationCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/ServerStatus.tsx",
    "content": "\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\nspan.val {\n  &.up { color: ${colors.success}; }\n  &.down { color: ${colors.danger}; }\n}\n`;\n\nconst ServerStatusCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const serverStatus = props.data;\n  return (\n    <Card heading={props.title.toString()} actionButtons={props.actionButtons} styles={cardStyles}>\n      <Row lbl=\"\" val=\"\">\n        <span className=\"lbl\">Is Up?</span>\n        { serverStatus.isUp ? <span className=\"val up\">✅ Online</span> : <span className=\"val down\">❌ Offline</span>}\n      </Row>\n      <Row lbl=\"Status Code\" val={serverStatus.responseCode} />\n      { serverStatus.responseTime && <Row lbl=\"Response Time\" val={`${Math.round(serverStatus.responseTime)}ms`} /> }\n    </Card>\n  );\n}\n\nexport default ServerStatusCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/SiteFeatures.tsx",
    "content": "import { Card } from 'web-check-live/components/Form/Card';\nimport colors from 'web-check-live/styles/colors';\nimport Row from 'web-check-live/components/Form/Row';\nimport Heading  from 'web-check-live/components/Form/Heading';\n\nconst styles = `\n  .content {\n    max-height: 50rem;\n    overflow-y: auto;\n  }\n\n  .scan-date {\n    font-size: 0.8rem;\n    margin-top: 0.5rem;\n    opacity: 0.75;\n  }\n`;\n\nconst formatDate = (timestamp: number): string => {\n  if (isNaN(timestamp) || timestamp <= 0) return 'No Date';\n\n  const date = new Date(timestamp * 1000);\n\n  if (isNaN(date.getTime())) return 'Unknown';\n\n  const formatter = new Intl.DateTimeFormat('en-GB', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric',\n    hour: '2-digit',\n    minute: '2-digit',\n    hour12: true\n  });\n\n  return formatter.format(date);\n}\n\n\n\nconst SiteFeaturesCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const features = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={styles}>\n      <div className=\"content\">\n        { (features?.groups || []).filter((group: any) => group.categories.length > 0).map((group: any, index: number) => (\n          <div key={`${group.name}-${index}`}>\n          <Heading as=\"h4\" size=\"small\" color={colors.primary}>{group.name}</Heading>\n          { group.categories.map((category: any, subIndex: number) => (\n            // <Row lbl={category.name} val={category.live} />\n            <Row lbl=\"\" val=\"\" key={`${category.name}-${subIndex}`}>\n              <span className=\"lbl\">{category.name}</span>\n              <span className=\"val\">{category.live} Live {category.dead ? `(${category.dead} dead)` : ''}</span>\n            </Row>\n          ))\n          }\n          </div>\n        ))\n        }\n      </div>\n      <p className=\"scan-date\">Last scanned on {formatDate(features.last)}</p>\n    </Card>\n  );\n}\n\nexport default SiteFeaturesCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Sitemap.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ExpandableRow } from 'web-check-live/components/Form/Row';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\n  max-height: 50rem;\n  overflow-y: auto;\n  a {\n    color: ${colors.primary};\n  }\n  small {\n    margin-top: 1rem;\n    opacity: 0.5;\n    display: block;\n    a { color: ${colors.primary}; }\n  }\n`;\n\nconst SitemapCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const normalSiteMap = props.data.url ||  props.data.urlset?.url || null;\n  const siteMapIndex = props.data.sitemapindex?.sitemap || null;\n\n  const makeExpandableRowData = (site: any) => {\n    const results = [];\n    if (site.lastmod) { results.push({lbl: 'Last Modified', val: site.lastmod[0]}); }\n    if (site.changefreq) { results.push({lbl: 'Change Frequency', val: site.changefreq[0]}); }\n    if (site.priority) { results.push({lbl: 'Priority', val: site.priority[0]}); }\n    return results;\n  };\n\n  const getPathFromUrl = (url: string) => {\n    try {\n      const urlObj = new URL(url);\n      return urlObj.pathname;\n    } catch (e) {\n      return url;\n    }    \n  };\n\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {\n        normalSiteMap && normalSiteMap.map((subpage: any, index: number) => {\n          return (<ExpandableRow lbl={getPathFromUrl(subpage.loc[0])} key={index} val=\"\" rowList={makeExpandableRowData(subpage)}></ExpandableRow>)\n        })\n      }\n      { siteMapIndex && <p>\n        This site returns a sitemap index, which is a list of sitemaps.  \n      </p>}\n      {\n        siteMapIndex && siteMapIndex.map((subpage: any, index: number) => {\n          return (<Row lbl=\"\" val=\"\" key={index}><a href={subpage.loc[0]}>{getPathFromUrl(subpage.loc[0])}</a></Row>);\n        })\n      }\n    </Card>\n  );\n}\n\nexport default SitemapCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/SocialTags.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\n  .banner-image img {\n    width: 100%;\n    border-radius: 4px;\n    margin: 0.5rem 0;\n  }\n  .color-field {\n    border-radius: 4px;\n    &:hover {\n      color: ${colors.primary};\n    }\n  }\n`;\n\nconst OgBanner = ({ ogImage, ogUrl }: { ogImage: string; ogUrl?: string }): JSX.Element => {\n  const urlCover = ogImage.startsWith(\"/\") && ogUrl ? `${ogUrl}${ogImage}` : ogImage;\n  return (\n      <div className=\"banner-image\">\n          <img src={urlCover} alt=\"Banner\" />\n      </div>\n  );\n};\n\nconst SocialTagsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const tags = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { tags.title && <Row lbl=\"Title\" val={tags.title} /> }\n      { tags.description && <Row lbl=\"Description\" val={tags.description} /> }\n      { tags.keywords && <Row lbl=\"Keywords\" val={tags.keywords} /> }\n      { tags.canonicalUrl && <Row lbl=\"Canonical URL\" val={tags.canonicalUrl} /> }\n      { tags.themeColor && <Row lbl=\"\" val=\"\">\n        <span className=\"lbl\">Theme Color</span>\n        <span className=\"val color-field\" style={{background: tags.themeColor}}>{tags.themeColor}</span>\n      </Row> }\n      { tags.twitterSite && <Row lbl=\"\" val=\"\">\n        <span className=\"lbl\">Twitter Site</span>\n        <span className=\"val\"><a href={`https://x.com/${tags.twitterSite}`}>{tags.twitterSite}</a></span>\n      </Row> }\n      { tags.author && <Row lbl=\"Author\" val={tags.author} />}\n      { tags.publisher && <Row lbl=\"Publisher\" val={tags.publisher} />}\n      { tags.generator && <Row lbl=\"Generator\" val={tags.generator} />}\n      {tags.ogImage && <OgBanner ogImage={tags.ogImage} ogUrl={tags.ogUrl} />}\n    </Card>\n  );\n}\n\nexport default SocialTagsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/SslCert.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nconst Row = styled.div`\n  display: flex;\n  justify-content: space-between;\n  padding: 0.25rem;\n  &:not(:last-child) { border-bottom: 1px solid ${colors.primaryTransparent}; }\n  span.lbl { font-weight: bold; }\n  span.val {\n    max-width: 200px;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst formatDate = (dateString: string): string => {\n  const date = new Date(dateString);\n  const formatter = new Intl.DateTimeFormat('en-GB', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric'\n  });\n  return formatter.format(date);\n}\n\nconst DataRow = (props: { lbl: string, val: string }) => {\n  const { lbl, val } = props;\n  return (\n  <Row>\n    <span className=\"lbl\">{lbl}</span>\n    <span className=\"val\" title={val}>{val}</span>\n  </Row>\n  );\n};\n\n\nfunction getExtendedKeyUsage(oids: string[]) {\n  const oidMap: { [key: string]: string } = {\n    \"1.3.6.1.5.5.7.3.1\": \"TLS Web Server Authentication\",\n    \"1.3.6.1.5.5.7.3.2\": \"TLS Web Client Authentication\",\n    \"1.3.6.1.5.5.7.3.3\": \"Code Signing\",\n    \"1.3.6.1.5.5.7.3.4\": \"Email Protection (SMIME)\",\n    \"1.3.6.1.5.5.7.3.8\": \"Time Stamping\",\n    \"1.3.6.1.5.5.7.3.9\": \"OCSP Signing\",\n    \"1.3.6.1.5.5.7.3.5\": \"IPSec End System\",\n    \"1.3.6.1.5.5.7.3.6\": \"IPSec Tunnel\",\n    \"1.3.6.1.5.5.7.3.7\": \"IPSec User\",\n    \"1.3.6.1.5.5.8.2.2\": \"IKE Intermediate\",\n    \"2.16.840.1.113730.4.1\": \"Netscape Server Gated Crypto\",\n    \"1.3.6.1.4.1.311.10.3.3\": \"Microsoft Server Gated Crypto\",\n    \"1.3.6.1.4.1.311.10.3.4\": \"Microsoft Encrypted File System\",\n    \"1.3.6.1.4.1.311.20.2.2\": \"Microsoft Smartcard Logon\",\n    \"1.3.6.1.4.1.311.10.3.12\": \"Microsoft Document Signing\",\n    \"0.9.2342.19200300.100.1.3\": \"Email Address (in Subject Alternative Name)\",\n  };\n  const results = oids.map(oid => oidMap[oid] || oid);\n  return results.filter((item, index) => results.indexOf(item) === index);\n}\n\n\nconst ListRow = (props: { list: string[], title: string }) => {\n  const { list, title } = props;\n  return (\n  <>\n    <Heading as=\"h3\" size=\"small\" align=\"left\" color={colors.primary}>{title}</Heading>\n    { list.map((entry: string, index: number) => {\n      return (\n      <Row key={`${title.toLocaleLowerCase()}-${index}`}><span>{ entry }</span></Row>\n      )}\n    )}\n  </>\n);\n}\n\nconst SslCertCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const sslCert = props.data;\n  const { subject, issuer, fingerprint, serialNumber, asn1Curve, nistCurve, valid_to, valid_from, ext_key_usage } = sslCert;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { subject && <DataRow lbl=\"Subject\" val={subject?.CN} /> }\n      { issuer?.O && <DataRow lbl=\"Issuer\" val={issuer.O} /> }\n      { asn1Curve && <DataRow lbl=\"ASN1 Curve\" val={asn1Curve} /> }\n      { nistCurve && <DataRow lbl=\"NIST Curve\" val={nistCurve} /> }\n      { valid_to && <DataRow lbl=\"Expires\" val={formatDate(valid_to)} /> }\n      { valid_from && <DataRow lbl=\"Renewed\" val={formatDate(valid_from)} /> }\n      { serialNumber && <DataRow lbl=\"Serial Num\" val={serialNumber} /> }\n      { fingerprint && <DataRow lbl=\"Fingerprint\" val={fingerprint} /> }\n      { ext_key_usage && <ListRow title=\"Extended Key Usage\" list={getExtendedKeyUsage(ext_key_usage)} /> }\n      \n    </Card>\n  );\n}\n\nexport default SslCertCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TechStack.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nconst cardStyles = `\n  grid-row: span 2;\n  small {\n    margin-top: 1rem;\n    opacity: 0.5;\n    display: block;\n    a { color: ${colors.primary}; }\n  }\n`;\n\nconst TechStackRow = styled.div`\ntransition: all 0.2s ease-in-out;\n.r1 {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n}\nh4 {\n  margin: 0.5rem 0 0 0;\n}\n.r2 {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 0.5rem;\n}\n.tech-version {\n  opacity: 0.5;\n}\n.tech-confidence, .tech-categories {\n  font-size: 0.8rem;\n  opacity: 0.5;\n}\n.tech-confidence {\n  display: none;\n}\n.tech-description, .tech-website {\n  font-size: 0.8rem;\n  margin: 0.25rem 0;\n  font-style: italic;\n\n  display: -webkit-box;\n  -webkit-line-clamp: 3;\n  -webkit-box-orient: vertical;\n  overflow: hidden;\n  &.tech-website {\n    -webkit-line-clamp: 1;\n  }\n  a {\n    color: ${colors.primary};\n    opacity: 0.75;\n    &:hover { opacity: 1; }\n  }\n}\n.tech-icon {\n  min-width: 2.5rem;\n  border-radius: 4px;\n  margin: 0.5rem 0;\n}\n&:not(:last-child) {\n  border-bottom: 1px solid ${colors.primaryTransparent};\n}\n&:hover {\n  .tech-confidence {\n    display: block;\n  }\n  .tech-categories {\n    display: none;\n  }\n}\n`;\n\nconst TechStackCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const technologies = props.data.technologies;\n  const iconsCdn = 'https://www.wappalyzer.com/images/icons/';\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {technologies.map((tech: any, index: number) => {\n        return (\n        <TechStackRow key={`tech-stack-row-${index}`}>\n          <div className=\"r1\">\n            <Heading as=\"h4\" size=\"small\">\n              {tech.name}\n              <span className=\"tech-version\">{tech.version? `(v${tech.version})` : ''}</span>\n            </Heading>  \n            <span className=\"tech-confidence\" title={`${tech.confidence}% certain`}>Certainty: {tech.confidence}%</span>\n            <span className=\"tech-categories\">\n              {tech.categories.map((cat: any, i: number) => `${cat.name}${i < tech.categories.length - 1 ? ', ' : ''}`)}\n            </span>\n          </div>\n          <div className=\"r2\">\n            <img className=\"tech-icon\" width=\"10\" src={`${iconsCdn}${tech.icon}`} alt={tech.name} />\n            <div>\n            <p className=\"tech-description\">{tech.description}</p>\n            <p className=\"tech-website\">Learn more at: <a href={tech.website}>{tech.website}</a></p>\n            </div>\n          </div>\n          \n        </TechStackRow>\n        );\n      })}\n    </Card>\n  );\n}\n\nexport default TechStackCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/Threats.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row, { ExpandableRow } from 'web-check-live/components/Form/Row';\n\nconst Expandable = styled.details`\nmargin-top: 0.5rem;\ncursor: pointer;\nsummary::marker {\n  color: ${colors.primary};\n}\n`;\n\nconst getExpandableTitle = (urlObj: any) => {\n  let pathName = '';\n  try {\n    pathName = new URL(urlObj.url).pathname;\n  } catch(e) {}\n  return `${pathName} (${urlObj.id})`;\n}\n\nconst convertToDate = (dateString: string): string => {\n  const [date, time] = dateString.split(' ');\n  const [year, month, day] = date.split('-').map(Number);\n  const [hour, minute, second] = time.split(':').map(Number);\n  const dateObject = new Date(year, month - 1, day, hour, minute, second);\n  if (isNaN(dateObject.getTime())) {\n    return dateString;\n  }\n  return dateObject.toString();\n}\n\nconst MalwareCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const urlHaus = props.data.urlHaus || {};\n  const phishTank = props.data.phishTank || {};\n  const cloudmersive = props.data.cloudmersive || {};\n  const safeBrowsing = props.data.safeBrowsing || {};\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { safeBrowsing && !safeBrowsing.error && (\n        <Row lbl=\"Google Safe Browsing\" val={safeBrowsing.unsafe ? '❌ Unsafe' : '✅ Safe'} />\n      )}\n      { ((cloudmersive && !cloudmersive.error) || safeBrowsing?.details) && (\n        <Row lbl=\"Threat Type\" val={safeBrowsing?.details?.threatType || cloudmersive.WebsiteThreatType || 'None :)'} />\n      )}\n      { phishTank && !phishTank.error && (\n        <Row lbl=\"Phishing Status\" val={phishTank?.url0?.in_database !== 'false' ? '❌ Phishing Identified' : '✅ No Phishing Found'} />\n      )}\n      { phishTank.url0 && phishTank.url0.phish_detail_page && (\n        <Row lbl=\"\" val=\"\">\n          <span className=\"lbl\">Phish Info</span>\n          <span className=\"val\"><a href={phishTank.url0.phish_detail_page}>{phishTank.url0.phish_id}</a></span>  \n        </Row>\n      )}\n      { urlHaus.query_status === 'no_results' && <Row lbl=\"Malware Status\" val=\"✅ No Malwares Found\" />}\n      { urlHaus.query_status === 'ok' && (\n        <>\n        <Row lbl=\"Status\" val=\"❌ Malware Identified\" />\n        <Row lbl=\"First Seen\" val={convertToDate(urlHaus.firstseen)} />\n        <Row lbl=\"Bad URLs Count\" val={urlHaus.url_count} />\n        </>\n      )}\n      {urlHaus.urls && (\n        <Expandable>\n          <summary>Expand Results</summary>\n          { urlHaus.urls.map((urlResult: any, index: number) => {\n          const rows = [\n            { lbl: 'ID', val: urlResult.id },\n            { lbl: 'Status', val: urlResult.url_status },\n            { lbl: 'Date Added', val: convertToDate(urlResult.date_added) },\n            { lbl: 'Threat Type', val: urlResult.threat },\n            { lbl: 'Reported By', val: urlResult.reporter },\n            { lbl: 'Takedown Time', val: urlResult.takedown_time_seconds },\n            { lbl: 'Larted', val: urlResult.larted },\n            { lbl: 'Tags', val: (urlResult.tags || []).join(', ') },\n            { lbl: 'Reference', val: urlResult.urlhaus_reference },      \n            { lbl: 'File Path', val: urlResult.url },      \n          ];\n          return (<ExpandableRow lbl={getExpandableTitle(urlResult)} val=\"\" rowList={rows} />)\n        })}\n        </Expandable>\n      )}\n    </Card>\n  );\n}\n\nexport default MalwareCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TlsCipherSuites.tsx",
    "content": "\nimport { useState, useEffect } from 'react';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button from 'web-check-live/components/Form/Button';\nimport { ExpandableRow } from 'web-check-live/components/Form/Row';\n\nconst makeCipherSuites = (results: any) => {\n  if (!results || !results.connection_info || (results.connection_info.ciphersuite || [])?.length === 0) {\n    return [];\n  }\n  return results.connection_info.ciphersuite.map((ciphersuite: any) => {\n    return {\n      title: ciphersuite.cipher,\n      fields: [\n      { lbl: 'Code', val: ciphersuite.code },\n      { lbl: 'Protocols', val: ciphersuite.protocols.join(', ') },\n      { lbl: 'Pubkey', val: ciphersuite.pubkey },\n      { lbl: 'Sigalg', val: ciphersuite.sigalg },\n      { lbl: 'Ticket Hint', val: ciphersuite.ticket_hint },\n      { lbl: 'OCSP Stapling', val: ciphersuite.ocsp_stapling ? '✅ Enabled' : '❌ Disabled' },\n      { lbl: 'PFS', val: ciphersuite.pfs },\n      ciphersuite.curves ? { lbl: 'Curves', val: ciphersuite.curves.join(', ') } : {},\n    ]};\n  });\n};\n\nconst TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n\n  const [cipherSuites, setCipherSuites] = useState(makeCipherSuites(props.data));\n  const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);\n\n  useEffect(() => { // Update cipher suites when data changes\n    setCipherSuites(makeCipherSuites(props.data));\n  }, [props.data]);\n\n  const updateData = (id: number) => {\n    setCipherSuites([]);\n    setLoadState('loading');\n    const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;\n    fetch(fetchUrl)\n      .then((response) => response.json())\n      .then((data) => {\n        setCipherSuites(makeCipherSuites(data));\n        setLoadState('success');\n    }).catch((error) => {\n      setLoadState('error');\n    });\n  };\n  \n  const scanId = props.data?.id;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { cipherSuites.length && cipherSuites.map((cipherSuite: any, index: number) => {\n        return (\n          <ExpandableRow key={`tls-cipher-${index}`} lbl={cipherSuite.title} val=\"\" rowList={cipherSuite.fields} />\n        );\n      })}\n      { !cipherSuites.length && (\n        <div>\n          <p>No cipher suites found.<br />\n            This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.\n          </p>\n          <Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>\n        </div>\n      )}\n    </Card>\n  );\n}\n\nexport default TlsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TlsClientSupport.tsx",
    "content": "\nimport { useState, useEffect } from 'react';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button from 'web-check-live/components/Form/Button';\nimport { ExpandableRow } from 'web-check-live/components/Form/Row';\n\nconst makeClientSupport = (results: any) => {\n  if (!results?.analysis || results.analysis.length <1) return [];\n  const target = results.target;\n  const sslLabsClientSupport = (\n      results.analysis.find((a: any) => a.analyzer === 'sslLabsClientSupport')\n    ).result;\n\n  return sslLabsClientSupport.map((sup: any) => {\n    return {\n      title: `${sup.name} ${sup.platform ?  `(on ${sup.platform})`: sup.version}`,\n      value: sup.is_supported ? '✅' : '❌',\n      fields: sup.is_supported ? [\n        sup.curve ? { lbl: 'Curve', val: sup.curve } : {},\n        { lbl: 'Protocol', val: sup.protocol },\n        { lbl: 'Cipher Suite', val: sup.ciphersuite },\n        { lbl: 'Protocol Code', val: sup.protocol_code },\n        { lbl: 'Cipher Suite Code', val: sup.ciphersuite_code },\n        { lbl: 'Curve Code', val: sup.curve_code },\n      ] : [\n        { lbl: '', val: '',\n        plaintext: `The host ${target} does not support ${sup.name} `\n          +`${sup.version ?  `version ${sup.version} `: ''} `\n          + `${sup.platform ?  `on ${sup.platform} `: ''}`}\n      ],\n    };\n  });\n  \n};\n\nconst TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n\n  const [clientSupport, setClientSupport] = useState(makeClientSupport(props.data));\n  const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);\n\n  useEffect(() => {\n    setClientSupport(makeClientSupport(props.data));\n  }, [props.data]);\n\n  const updateData = (id: number) => {\n    setClientSupport([]);\n    setLoadState('loading');\n    const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;\n    fetch(fetchUrl)\n      .then((response) => response.json())\n      .then((data) => {\n        setClientSupport(makeClientSupport(data));\n        setLoadState('success');\n    }).catch(() => {\n      setLoadState('error');\n    });\n  };\n  \n  const scanId = props.data?.id;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      {clientSupport.map((support: any, index: number) => {\n        return (\n        <ExpandableRow\n          key={`tls-client-${index}`}\n          lbl={support.title}\n          val={support.value || '?'}\n          rowList={support.fields}\n        />\n      )\n      })}\n      { !clientSupport.length && (\n        <div>\n          <p>No entries available to analyze.<br />\n            This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.\n          </p>\n          <Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>\n        </div>\n      )}\n    </Card>\n  );\n}\n\nexport default TlsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TlsIssueAnalysis.tsx",
    "content": "\nimport { useState, useEffect } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button from 'web-check-live/components/Form/Button';\nimport Row, { ExpandableRow } from 'web-check-live/components/Form/Row';\n\nconst Expandable = styled.details`\nmargin-top: 0.5rem;\ncursor: pointer;\nsummary::marker {\n  color: ${colors.primary};\n}\n`;\n\nconst makeExpandableData = (results: any) => {\n  if (!results || !results.analysis || results.analysis.length === 0) {\n    return [];\n  }\n  return results.analysis.map((analysis: any) => {\n    const fields = Object.keys(analysis.result).map((label) => {\n      const lbl = isNaN(parseInt(label, 10)) ? label : '';\n      const val = analysis.result[label] || 'None';\n      if (typeof val !== 'object') {\n        return { lbl, val };\n      }\n      return { lbl, val: '', plaintext: JSON.stringify(analysis.result[label])};\n    });\n    return {\n      title: analysis.analyzer,\n      value: analysis.success ? '✅' : '❌',\n      fields,\n    };\n  });\n};\n\nconst makeResults = (results: any) => {\n  const rows: { lbl: string; val?: any; plaintext?: string; list?: string[] }[] = [];\n  if (!results || !results.analysis || results.analysis.length === 0) {\n    return rows;\n  }\n  const caaWorker = results.analysis.find((a: any) => a.analyzer === 'caaWorker');\n  if (caaWorker.result.host) rows.push({ lbl: 'Host', val: caaWorker.result.host });\n  if (typeof caaWorker.result.has_caa === 'boolean') rows.push({ lbl: 'CA Authorization', val: caaWorker.result.has_caa });\n  if (caaWorker.result.issue) rows.push({ lbl: 'CAAs allowed to Issue Certs', plaintext: caaWorker.result.issue.join('\\n') });\n\n  const mozillaGradingWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaGradingWorker')).result;\n  if (mozillaGradingWorker.grade) rows.push({ lbl: 'Mozilla Grading', val: mozillaGradingWorker.grade });\n  if (mozillaGradingWorker.gradeTrust) rows.push({ lbl: 'Mozilla Trust', val: mozillaGradingWorker.gradeTrust });\n\n  const symantecDistrust = (results.analysis.find((a: any) => a.analyzer === 'symantecDistrust')).result;\n  if (typeof symantecDistrust.isDistrusted === 'boolean') rows.push({ lbl: 'No distrusted symantec SSL?', val: !symantecDistrust.isDistrusted });\n  if (symantecDistrust.reasons) rows.push({ lbl: 'Symantec Distrust', plaintext: symantecDistrust.reasons.join('\\n') });\n\n  const top1m = (results.analysis.find((a: any) => a.analyzer === 'top1m')).result;\n  if (top1m.certificate.rank) rows.push({ lbl: 'Certificate Rank', val: top1m.certificate.rank.toLocaleString() });\n\n  const mozillaEvaluationWorker = (results.analysis.find((a: any) => a.analyzer === 'mozillaEvaluationWorker')).result;\n  if (mozillaEvaluationWorker.level) rows.push({ lbl: 'Mozilla Evaluation Level', val: mozillaEvaluationWorker.level });\n  if (mozillaEvaluationWorker.failures) {\n    const { bad, old, intermediate, modern } = mozillaEvaluationWorker.failures;\n    if (bad) rows.push({ lbl: `Critical Security Issues (${bad.length})`, list: bad });\n    if (old) rows.push({ lbl: `Compatibility Config Issues (${old.length})`, list: old });\n    if (intermediate) rows.push({ lbl: `Intermediate Issues (${intermediate.length})`, list: intermediate });\n    if (modern) rows.push({ lbl: `Modern Issues (${modern.length})`, list: modern });\n  }\n  return rows;\n};\n\nconst TlsCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n\n  const [tlsRowData, setTlsRowWata] = useState(makeExpandableData(props.data));\n  const [tlsResults, setTlsResults] = useState(makeResults(props.data));\n  const [loadState, setLoadState] = useState<undefined | 'loading' | 'success' | 'error'>(undefined);\n\n  useEffect(() => {\n    setTlsRowWata(makeExpandableData(props.data));\n    setTlsResults(makeResults(props.data));\n  }, [props.data]);\n\n  const updateData = (id: number) => {\n    setTlsRowWata([]);\n    setLoadState('loading');\n    const fetchUrl = `https://tls-observatory.services.mozilla.com/api/v1/results?id=${id}`;\n    fetch(fetchUrl)\n      .then((response) => response.json())\n      .then((data) => {\n        setTlsRowWata(makeExpandableData(data));\n        setTlsResults(makeResults(data));\n        setLoadState('success');\n    }).catch(() => {\n      setLoadState('error');\n    });\n  };\n  \n  const scanId = props.data?.id;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { tlsResults.length > 0 && tlsResults.map((row: any, index: number) => {\n        return (\n          <Row\n            lbl={row.lbl}\n            val={row.val}\n            plaintext={row.plaintext}\n            listResults={row.list}\n            key={`tls-issues-${index}`}\n          />\n        );\n      })}\n      <Expandable>\n        <summary>Full Analysis Results</summary>\n        { tlsRowData.length > 0 && tlsRowData.map((cipherSuite: any, index: number) => {\n          return (\n            <ExpandableRow lbl={cipherSuite.title} val={cipherSuite.value || '?'} rowList={cipherSuite.fields} />\n          );\n        })}\n      </Expandable>\n      { !tlsRowData.length && (\n        <div>\n          <p>No entries available to analyze.<br />\n            This sometimes happens when the report didn't finish generating in-time, you can try re-requesting it.\n          </p>\n          <Button loadState={loadState} onClick={() => updateData(scanId)}>Refetch Report</Button>\n        </div>\n      )}\n    </Card>\n  );\n}\n\nexport default TlsCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TraceRoute.tsx",
    "content": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\n\nconst RouteRow = styled.div`\n  text-align: center;\n  width: fit-content;\n  margin: 0 auto;\n  .ipName {\n    font-size: 1rem;\n  }\n`;\n\nconst RouteTimings = styled.div`\np {\n  margin: 0 auto;\n}\n.arrow {\n  font-size: 2.5rem;\n  color: ${colors.primary};\n  margin-top: -1rem;\n}\n.times {\n  font-size: 0.85rem;\n  color: ${colors.textColorSecondary};\n}\n.completed {\n  text-align: center;\n  font-weight: bold;\n}\n`;\n\nconst cardStyles = ``;\n\nconst TraceRouteCard = (props: { data: any, title: string, actionButtons: any }): JSX.Element => {\n  const traceRouteResponse = props.data;\n  const routes = traceRouteResponse.result;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      {routes.filter((x: any) => x).map((route: any, index: number) => (\n          <RouteRow key={index}>\n            <span className=\"ipName\">{Object.keys(route)[0]}</span>\n            <RouteTimings>\n              {route[Object.keys(route)[0]].map((time: any, packetIndex: number) => (\n                <p className=\"times\" key={`timing-${packetIndex}-${time}`}>\n                  { route[Object.keys(route)[0]].length > 1 && (<>Packet #{packetIndex + 1}:</>) }\n                  Took {time} ms\n                </p>\n              ))}\n              <p className=\"arrow\">↓</p>\n            </RouteTimings>\n          </RouteRow>\n        )\n      )}\n      <RouteTimings>\n        <p className=\"completed\">\n          Round trip completed in {traceRouteResponse.timeTaken} ms\n        </p>\n      </RouteTimings>\n    </Card>\n  );\n}\n\nexport default TraceRouteCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/TxtRecords.tsx",
    "content": "\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Row from 'web-check-live/components/Form/Row';\n\nconst cardStyles = `\ngrid-column: span 2;\nspan.val { max-width: 32rem !important; }\nspan { overflow: hidden; }\n`;\n\nconst TxtRecordCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => {\n  const records = props.data;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons} styles={cardStyles}>\n      { !records && <Row lbl=\"\" val=\"No TXT Records\" />}\n      {Object.keys(records).map((recordName: any, index: number) => {\n        return (\n          <Row lbl={recordName} val={records[recordName]} key={`${recordName}-${index}`} />\n        );\n      })}\n    </Card>\n  );\n}\n\nexport default TxtRecordCard;\n"
  },
  {
    "path": "src/web-check-live/components/Results/WhoIs.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport type { Whois } from 'web-check-live/utils/result-processor';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nconst Row = styled.div`\n  display: flex;\n  justify-content: space-between;\n  padding: 0.25rem;\n  &:not(:last-child) { border-bottom: 1px solid ${colors.primaryTransparent}; }\n  span.lbl { font-weight: bold; }\n  span.val {\n    max-width: 200px;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n`;\n\nconst formatDate = (dateString: string): string => {\n  const date = new Date(dateString);\n  const formatter = new Intl.DateTimeFormat('en-GB', {\n    day: 'numeric',\n    month: 'long',\n    year: 'numeric'\n  });\n  return formatter.format(date);\n}\n\nconst DataRow = (props: { lbl: string, val: string }) => {\n  const { lbl, val } = props;\n  return (\n  <Row>\n    <span className=\"lbl\">{lbl}</span>\n    <span className=\"val\" title={val}>{val}</span>\n  </Row>\n  );\n};\n\nconst ListRow = (props: { list: string[], title: string }) => {\n  const { list, title } = props;\n  return (\n  <>\n    <Heading as=\"h3\" size=\"small\" align=\"left\" color={colors.primary}>{title}</Heading>\n    { list.map((entry: string, index: number) => {\n      return (\n      <Row key={`${title.toLocaleLowerCase()}-${index}`}><span>{ entry }</span></Row>\n      )}\n    )}\n  </>\n);\n}\n\nconst WhoIsCard = (props: { data: Whois, title: string, actionButtons: any }): JSX.Element => {\n  const whois = props.data;\n  const { created, updated, expires, nameservers } = whois;\n  return (\n    <Card heading={props.title} actionButtons={props.actionButtons}>\n      { created && <DataRow lbl=\"Created\" val={formatDate(created)} /> }\n      { updated && <DataRow lbl=\"Updated\" val={formatDate(updated)} /> }\n      { expires && <DataRow lbl=\"Expires\" val={formatDate(expires)} /> }\n      { nameservers && <ListRow title=\"Name Servers\" list={nameservers} /> }\n    </Card>\n  );\n}\n\nexport default WhoIsCard;\n"
  },
  {
    "path": "src/web-check-live/components/boundaries/PageError.tsx",
    "content": "import React from 'react';\nimport styled from '@emotion/styled';\n\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Footer from 'web-check-live/components/misc/Footer';\nimport Nav from 'web-check-live/components/Form/Nav';\nimport Button from 'web-check-live/components/Form/Button';\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport { Link } from 'react-router-dom';\n\ninterface ErrorBoundaryState {\n  hasError: boolean;\n  errorCount: number;\n  errorMessage: string | null;\n}\n\ninterface ErrorBoundaryProps {\n  children: React.ReactNode;\n}\n\nconst ErrorPageContainer = styled.div`\nwidth: 95vw;\nmax-width: 1000px;\nmargin: 2rem auto;\npadding-bottom: 1rem;\nheader {\n  margin 1rem 0;\n  width: auto;\n}\nsection {\n  width: auto;\n  .inner-heading { display: none; }\n}\n`;\n\nconst HeaderLinkContainer = styled.nav`\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  a {\n    text-decoration: none;\n  }\n`;\n\nconst ErrorInner = styled(StyledCard)`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 0.5rem;\n  h3 { font-size: 6rem; }\n`;\n\nconst ErrorDetails = styled.div`\n  background: ${colors.primaryTransparent};\n  padding: 1rem;\n  border-radius: 0.5rem;\n`;\n\nconst ErrorMessageText = styled.p`\n  color: ${colors.danger};\n`;\n\nclass ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {\n  constructor(props: ErrorBoundaryProps) {\n    super(props);\n    this.state = { hasError: false, errorCount: 0, errorMessage: null };\n  }\n\n  static getDerivedStateFromError(err: Error): ErrorBoundaryState {\n    return { hasError: true, errorCount: 0, errorMessage: err.message };\n  }\n  \n\n  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n    console.error(\"Uncaught error:\", error, errorInfo);\n    console.error(\n      `%cCritical Error%c\\n\\nRoute or component failed to mount%c:%c\\n`\n      +`${this.state.errorCount < 1? 'Will attempt a page reload' : ''}. `\n      + `Error Details:\\n${error}\\n\\n${JSON.stringify(errorInfo || {})}`,\n      `background: ${colors.danger}; color:${colors.background}; padding: 4px 8px; font-size: 16px;`,\n      `font-weight: bold; color: ${colors.danger};`,\n      `color: ${colors.danger};`,\n      `color: ${colors.warning};`,\n    );\n    if (this.state.errorCount < 1) {\n      this.setState(prevState => ({ errorCount: prevState.errorCount + 1 }));\n      window.location.reload();\n    }\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        <ErrorPageContainer>\n          <Nav>\n            <HeaderLinkContainer>\n              <Link to=\"/\"><Button>Go back Home</Button></Link>\n              <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\"><Button>View on GitHub</Button></a>\n            </HeaderLinkContainer>\n          </Nav>\n          <ErrorInner>\n            <Heading as=\"h1\" size=\"medium\" color={colors.primary}>Something's gone wrong</Heading>\n            <Heading as=\"h2\" size=\"small\" color={colors.textColor}>An unexpected error occurred.</Heading>\n            <Heading as=\"h3\" size=\"large\" color={colors.textColor}>🤯</Heading>\n            <ErrorDetails>\n              <p>\n                We're sorry this happened.\n                Usually reloading the page will resolve this, but if it doesn't, please raise a bug report.\n              </p>\n              {this.state.errorMessage && (\n              <p>\n                Below is the error message we received:<br /><br />\n                <ErrorMessageText>{this.state.errorMessage}</ErrorMessageText>\n              </p>\n              )}\n            </ErrorDetails>\n            <Button onClick={() => window.location.reload()}>Reload Page</Button>\n            <a target=\"_blank\" rel=\"noreferrer\" href=\"github.com/lissy93/web-check/issues/choose\">\n              Report Issue\n            </a>\n          </ErrorInner>\n          <Footer isFixed={true} />\n        </ErrorPageContainer>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n"
  },
  {
    "path": "src/web-check-live/components/misc/ActionButtons.tsx",
    "content": "import styled from '@emotion/styled';\nimport Button from 'web-check-live/components/Form/Button';\nimport colors from 'web-check-live/styles/colors';\n\nconst ActionButtonContainer = styled.div`\n  position: absolute;\n  top: 0.25rem;\n  right: 0.25rem;\n  opacity: 0.75;\n  display: flex;\n  gap: 0.125rem;\n  align-items: baseline;\n`;\n\ninterface Action {\n    label: string;\n    icon: string;\n    onClick: () => void;\n};\n\nconst actionButtonStyles = `\n  padding: 0 0.25rem;\n  font-size: 1.25rem;\n  text-align: center;\n  width: 1.5rem;\n  height: 1.5rem;\n  color: ${colors.textColor};\n  background: none;\n  box-shadow: none;\n  transition: all 0.2s ease-in-out;\n  margin: 0;\n  display: flex;\n  align-items: center;\n  &:hover {\n    color: ${colors.primary};\n    background: ${colors.backgroundDarker};\n    box-shadow: none;\n  }\n`;\n\nconst ActionButtons = (props: { actions: any }): JSX.Element => {\n  const actions = props.actions;\n  if (!actions) return (<></>);\n  return (\n    <ActionButtonContainer>\n      { actions.map((action: Action, index: number) => \n        <Button\n          key={`action-${index}`}\n          styles={actionButtonStyles}\n          onClick={action.onClick}\n          title={action.label}>\n            {action.icon}\n        </Button>\n      )}\n    </ActionButtonContainer>\n  );\n};\n\nexport default ActionButtons;\n"
  },
  {
    "path": "src/web-check-live/components/misc/AdditionalResources.tsx",
    "content": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\n\nconst ResourceListOuter = styled.ul`\nlist-style: none;\nmargin: 0;\npadding: 1rem;\ndisplay: grid;\ngap: 0.5rem;\ngrid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));\nli a.resource-wrap {\n  display: flex;\n  flex-direction: column;\n  align-items: start;\n  gap: 0.25rem;\n  padding: 0.25rem 0.5rem;\n  background: ${colors.background};\n  border-radius: 8px;\n  text-decoration: none;\n  color: ${colors.textColor};\n  height: 100%;\n\n  transition: all 0.2s ease-in-out;\n  cursor: pointer;\n  border: none;\n  border-radius: 0.25rem;\n  font-family: PTMono;\n  box-sizing: border-box; \n  width: -moz-available;\n  box-shadow: 3px 3px 0px ${colors.backgroundDarker};\n  &:hover {\n    box-shadow: 5px 5px 0px ${colors.backgroundDarker};\n    a { opacity: 1; }\n  }\n  &:active {\n    box-shadow: -3px -3px 0px ${colors.fgShadowColor};\n  }\n}\nimg {\n  width: 2.5rem;\n  border-radius: 4px;\n  margin: 0.25rem 0.1rem 0.1rem 0.1rem;\n}\np, a {\n  margin: 0;\n}\n.resource-link {\n  color: ${colors.primary};\n  opacity: 0.75;\n  font-size: 0.9rem;\n  transition: all 0.2s ease-in-out;\n  text-decoration: underline;\n  cursor: pointer;\n}\n.resource-title {\n  font-weight: bold;\n}\n.resource-lower {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n.resource-details {\n  max-width: 20rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.1rem;\n  .resource-description {\n    color: ${colors.textColorSecondary};\n    font-size: 0.9rem;\n  }\n}\n`;\n\nconst Note = styled.small`\n  margin-top: 1rem;\n  opacity: 0.5;\n  display: block;\n  a { color: ${colors.primary}; }\n`;\n\nconst CardStyles = `\n  margin: 0 auto 1rem auto;\n  width: 95vw;\n  position: relative;\n  transition: all 0.2s ease-in-out;\n`;\n\nconst resources = [\n  {\n    title: 'Hudson Rock',\n    link: 'https://hudsonrock.com/free-tools/?=webcheck',\n    icon: 'https://i.ibb.co/0rF3rZh/logo-1-967abb2c.png',\n    description: 'Identify Infostealer infection data related to domains and emails',\n  },\n  {\n    title: 'SSL Labs Test',\n    link: 'https://ssllabs.com/ssltest/analyze.html',\n    icon: 'https://i.ibb.co/6bVL8JK/Qualys-ssl-labs.png',\n    description: 'Analyzes the SSL configuration of a server and grades it',\n    searchLink: 'https://www.ssllabs.com/ssltest/analyze.html?d={URL}',\n  },\n  {\n    title: 'Virus Total',\n    link: 'https://virustotal.com',\n    icon: 'https://i.ibb.co/dWFz0RC/Virustotal.png',\n    description: 'Checks a URL against multiple antivirus engines',\n    searchLink: 'https://www.virustotal.com/gui/search/{URL_ENCODED}',\n  },\n  {\n    title: 'Shodan',\n    link: 'https://shodan.io/',\n    icon: 'https://i.ibb.co/SBZ8WG4/shodan.png',\n    description: 'Search engine for Internet-connected devices',\n    searchLink: 'https://www.shodan.io/search/report?query={URL}',\n  },\n  {\n    title: 'Archive',\n    link: 'https://archive.org/',\n    icon: 'https://i.ibb.co/nfKMvCm/Archive-org.png',\n    description: 'View previous versions of a site via the Internet Archive',\n    searchLink: 'https://web.archive.org/web/*/{URL}',\n  },\n  {\n    title: 'URLScan',\n    link: 'https://urlscan.io/',\n    icon: 'https://i.ibb.co/cYXt8SH/Url-scan.png',\n    description: 'Scans a URL and provides information about the page',\n    searchLink: 'https://urlscan.io/search/#{URL}',\n  },\n  {\n    title: 'Sucuri SiteCheck',\n    link: 'https://sitecheck.sucuri.net/',\n    icon: 'https://i.ibb.co/K5pTP1K/Sucuri-site-check.png',\n    description: 'Checks a URL against blacklists and known threats',\n    searchLink: 'https://www.ssllabs.com/ssltest/analyze.html?d={URL}',\n  },\n  {\n    title: 'Domain Tools',\n    link: 'https://whois.domaintools.com/',\n    icon: 'https://i.ibb.co/zJfCKjM/Domain-tools.png',\n    description: 'Run a WhoIs lookup on a domain',\n    searchLink: 'https://whois.domaintools.com/{DOMAIN}',\n  },\n  {\n    title: 'NS Lookup',\n    link: 'https://nslookup.io/',\n    icon: 'https://i.ibb.co/BLSWvBv/Ns-lookup.png',\n    description: 'View DNS records for a domain',\n    searchLink: 'https://www.nslookup.io/domains/{DOMAIN}/dns-records/',\n  },\n  {\n    title: 'DNS Checker',\n    link: 'https://dnschecker.org/',\n    icon: 'https://i.ibb.co/gyKtgZ1/Dns-checker.webp',\n    description: 'Check global DNS propagation across multiple servers',\n    searchLink: 'https://dnschecker.org/#A/{DOMAIN}',\n  },\n  {\n    title: 'Censys',\n    link: 'https://search.censys.io/',\n    icon: 'https://i.ibb.co/j3ZtXzM/censys.png',\n    description: 'Lookup hosts associated with a domain',\n    searchLink: 'https://search.censys.io/search?resource=hosts&q={URL}',\n  },\n  {\n    title: 'Page Speed Insights',\n    link: 'https://developers.google.com/speed/pagespeed/insights/',\n    icon: 'https://i.ibb.co/k68t9bb/Page-speed-insights.png',\n    description: 'Checks the performance, accessibility and SEO of a page on mobile + desktop',\n    searchLink: 'https://developers.google.com/speed/pagespeed/insights/?url={URL}',\n  },\n  {\n    title: 'Built With',\n    link: 'https://builtwith.com/',\n    icon: 'https://i.ibb.co/5LXBDfD/Built-with.png',\n    description: 'View the tech stack of a website',\n    searchLink: 'https://builtwith.com/{URL}',\n  },\n  {\n    title: 'DNS Dumpster',\n    link: 'https://dnsdumpster.com/',\n    icon: 'https://i.ibb.co/DtQ2QXP/Trash-can-regular.png',\n    description: 'DNS recon tool, to map out a domain from it\\'s DNS records',\n  },\n  {\n    title: 'BGP Tools',\n    link: 'https://bgp.tools/',\n    icon: 'https://i.ibb.co/zhcSnmh/Bgp-tools.png',\n    description: 'View realtime BGP data for any ASN, Prefix or DNS',\n    searchLink: 'https://bgp.tools/dns/{URL}',\n  },\n  {\n    title: 'Similar Web',\n    link: 'https://similarweb.com/',\n    icon: 'https://i.ibb.co/9YX8x3c/Similar-web.png',\n    description: 'View approx traffic and engagement stats for a website',\n    searchLink: 'https://similarweb.com/website/{URL}',\n  },\n  {\n    title: 'Blacklist Checker',\n    link: 'https://blacklistchecker.com/',\n    icon: 'https://i.ibb.co/7ygCyz3/black-list-checker.png',\n    description: 'Check if a domain, IP or email is present on the top blacklists',\n    searchLink: 'https://blacklistchecker.com/check?input={URL}',\n  },\n  {\n    title: 'Cloudflare Radar',\n    link: 'https://radar.cloudflare.com/',\n    icon: 'https://i.ibb.co/DGZXRgh/Cloudflare.png',\n    description: 'View traffic source locations for a domain through Cloudflare',\n    searchLink: 'https://radar.cloudflare.com/domains/domain/{URL}',\n  },\n  {\n    title: 'Mozilla HTTP Observatory',\n    link: 'https://developer.mozilla.org/en-US/observatory',\n    icon: 'https://i.ibb.co/hBWh9cj/logo-mozm-5e95c457fdd1.png',\n    description: 'Assesses website security posture by analyzing various security headers and practices',\n    searchLink: 'https://developer.mozilla.org/en-US/observatory/analyze?host={URL}',\n  },\n  {\n    title: 'AbuseIPDB',\n    link: 'https://abuseipdb.com/',\n    icon: 'https://i.ibb.co/KLZncxw/abuseipdb.png',\n    description: 'Checks a website against Zscaler\\'s dynamic risk scoring engine',\n    searchLink: 'https://www.abuseipdb.com/check?query={DOMAIN}',\n  },\n  {\n    title: 'IBM X-Force Exchange',\n    link: 'https://exchange.xforce.ibmcloud.com/',\n    icon: 'https://i.ibb.co/tsTsCV5/x-force.png',\n    description: 'View shared human and machine generated threat intelligence',\n    searchLink: 'https://exchange.xforce.ibmcloud.com/url/{URL_ENCODED}',\n  },\n  {\n    title: 'URLVoid',\n    link: 'https://urlvoid.com/',\n    icon: 'https://i.ibb.co/0ZDjCDz/urlvoid-icon.png',\n    description: 'Checks a website across 30+ blocklist engines and website reputation services',\n    searchLink: 'https://urlvoid.com/scan/{DOMAIN}',\n  },\n  {\n    title: 'URLhaus',\n    link: 'https://urlhaus.abuse.ch/',\n    icon: 'https://i.ibb.co/j3QwrT8/urlhaus-logo.png',\n    description: 'Checks if the site is in URLhaus\\'s malware URL exchange',\n    searchLink: 'https://urlhaus.abuse.ch/browse.php?search={URL_ENCODED}',\n  },\n  {\n    title: 'ANY.RUN',\n    link: 'https://any.run/',\n    icon: 'https://i.ibb.co/6nLw2MC/anyrun-icon.png',\n    description: 'An interactive malware and web sandbox',\n  },\n];\n\nconst makeLink = (resource: any, scanUrl: string | undefined): string => {\n  return (scanUrl && resource.searchLink) ? resource.searchLink.replaceAll('{URL}', scanUrl.replace(/(https?:\\/\\/)?/i, '')).replaceAll('{URL_ENCODED}', encodeURIComponent(scanUrl.replace(/(https?:\\/\\/)?/i, '')).replace(/['\\.*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)).replaceAll('{DOMAIN}', scanUrl.replace(/(https?:\\/\\/)?(www.)?/i, '').replace(/(\\/.*)/i, '')) : resource.link;\n};\n\nconst AdditionalResources = (props: { url?: string }): JSX.Element => {\n  return (<Card heading=\"External Tools for Further Research\" styles={CardStyles}>\n    <ResourceListOuter>\n      {\n        resources.map((resource, index) => {\n          return (\n            <li key={index}>\n              <a className=\"resource-wrap\" target=\"_blank\" rel=\"noreferrer\" href={makeLink(resource, props.url)}>\n                <p className=\"resource-title\">{resource.title}</p>\n                <span className=\"resource-link\" onClick={()=> window.open(resource.link, '_blank')} title={`Open: ${resource.link}`}>\n                  {new URL(resource.link).hostname}\n                </span>\n                <div className=\"resource-lower\">\n                  <img src={resource.icon} alt=\"\" />\n                  <div className=\"resource-details\">\n                    <p className=\"resource-description\">{resource.description}</p>\n                  </div>\n                </div>\n              </a>\n            </li>\n          );\n        })\n      }\n    </ResourceListOuter>\n    <Note>\n      These tools are not affiliated with Web-Check. Please use them at your own risk.<br />\n      At the time of listing, all of the above were available and free to use\n      - if this changes, please report it via GitHub (<a href=\"https://github.com/lissy93/web-check\">lissy93/web-check</a>).\n    </Note>\n  </Card>);\n}\n\nexport default AdditionalResources;\n"
  },
  {
    "path": "src/web-check-live/components/misc/DocContent.tsx",
    "content": "import styled from '@emotion/styled';\nimport docs, { type Doc } from 'web-check-live/utils/docs';\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\n\nconst JobDocsContainer = styled.div`\np.doc-desc, p.doc-uses, ul {\n  margin: 0.25rem auto 1.5rem auto;\n}\nul {\n  padding: 0 0.5rem 0 1rem;\n}\nul li a {\n  color: ${colors.primary};\n}\nsummary { color: ${colors.primary};}\nh4 {\n  border-top: 1px solid ${colors.primary};\n  color: ${colors.primary};\n  opacity: 0.75;\n  padding: 0.5rem 0;\n}\n`;\n\nconst DocContent = (id: string) => {\n  const doc = docs.filter((doc: Doc) => doc.id === id)[0] || null;\n  return (\n    doc? (<JobDocsContainer>\n      <Heading as=\"h3\" size=\"medium\" color={colors.primary}>{doc.title}</Heading>\n      <Heading as=\"h4\" size=\"small\">About</Heading>\n      <p className=\"doc-desc\">{doc.description}</p>\n      <Heading as=\"h4\" size=\"small\">Use Cases</Heading>\n      <p className=\"doc-uses\">{doc.use}</p>\n      <Heading as=\"h4\" size=\"small\">Links</Heading>\n      <ul>\n        {doc.resources.map((resource: string | { title: string, link: string } , index: number) => (\n          typeof resource === 'string' ? (\n            <li id={`link-${index}`}><a target=\"_blank\" rel=\"noreferrer\" href={resource}>{resource}</a></li>\n          ) : (\n            <li id={`link-${index}`}><a target=\"_blank\" rel=\"noreferrer\" href={resource.link}>{resource.title}</a></li>\n          )\n        ))}\n      </ul>\n      <details>\n        <summary><Heading as=\"h4\" size=\"small\">Example</Heading></summary>\n        <img width=\"300\" src={doc.screenshot} alt=\"Screenshot\" />\n      </details>\n    </JobDocsContainer>)\n  : (\n    <JobDocsContainer>\n      <p>No Docs provided for this widget yet</p>\n    </JobDocsContainer>\n    ));\n};\n\nexport default DocContent;\n"
  },
  {
    "path": "src/web-check-live/components/misc/ErrorBoundary.tsx",
    "content": "import React, { Component, type ErrorInfo, type ReactNode } from \"react\";\nimport styled from '@emotion/styled';\nimport Card from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\ninterface Props {\n  children: ReactNode;\n  title?: string;\n  key?: string;\n}\n\ninterface State {\n  hasError: boolean;\n  errorMessage: string | null;\n}\n\nconst ErrorText = styled.p`\n  color: ${colors.danger};\n`;\n\nclass ErrorBoundary extends Component<Props, State> {\n  public state: State = {\n    hasError: false,\n    errorMessage: null\n  };\n\n  // Catch errors in any components below and re-render with error message\n  public static getDerivedStateFromError(error: Error): State {\n    return { hasError: true, errorMessage: error.message };\n  }\n\n\n  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n    console.error(\"Uncaught error:\", error, errorInfo);\n  }\n\n  public render() {\n    if (this.state.hasError) {\n      return (\n        <Card>\n          { this.props.title && <Heading color={colors.primary}>{this.props.title}</Heading> }\n          <ErrorText>This component errored unexpectedly</ErrorText>\n          <p>\n            Usually this happens if the result from the server was not what was expected.\n            Check the logs for more info. If you continue to experience this issue, please raise a ticket on the repository.\n          </p>\n          {\n            this.state.errorMessage &&\n            <details>\n              <summary>Error Details</summary>\n              <div>{this.state.errorMessage}</div>\n            </details>\n          }\n        </Card>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n"
  },
  {
    "path": "src/web-check-live/components/misc/FancyBackground.tsx",
    "content": "import { useEffect, useMemo } from \"react\";\n\n\nconst FancyBackground = (): JSX.Element => {\n  \n  const makeAbsolute = (elem: HTMLElement) => {\n    elem.style.position = 'absolute';\n    elem.style.top = '0';\n    elem.style.left = '0';\n  };\n\n  const maxBy = (array: any) => {\n    const chaos = 30;\n    const iteratee = (e: any) => e.field + chaos * Math.random();\n    let result;\n    if (array == null) { return result; }\n    let computed;\n    for (const value of array) {\n      const current = iteratee(value);\n      if (current != null && (computed === undefined ? current : current > computed)) {\n        computed = current;\n        result = value;\n      }\n    }\n    return result;\n  };\n  \n  const App: any = useMemo(() => [], []);\n  \n  App.setup = function () {\n\n    this.lifespan = 1000;\n    this.popPerBirth = 1;\n    this.maxPop = 300;\n    this.birthFreq = 2;\n    this.bgColor = '#141d2b';\n\n    var canvas = document.createElement('canvas');\n    canvas.width = window.innerWidth;\n    canvas.height = window.innerHeight;\n    canvas.style.opacity = '0.5';\n    makeAbsolute(canvas);\n    this.canvas = canvas;\n    const container = document.getElementById('fancy-background');\n    if (container) {\n      container.style.color = this.bgColor;\n      makeAbsolute(container);\n      container.appendChild(canvas);\n    }\n    this.ctx = this.canvas.getContext('2d');\n    this.width = this.canvas.width;\n    this.height = this.canvas.height;\n    this.dataToImageRatio = 1;\n    this.ctx.imageSmoothingEnabled = false;\n    this.ctx.webkitImageSmoothingEnabled = false;\n    this.ctx.msImageSmoothingEnabled = false;\n    this.xC = this.width / 2;\n    this.yC = this.height / 2;\n  \n    this.stepCount = 0;\n    this.particles = [];\n\n  \n    // Build grid\n    this.gridSize = 8; // Motion coords\n    this.gridSteps = Math.floor(1000 / this.gridSize);\n    this.grid = [];\n    var i = 0;\n    for (var xx = -500; xx < 500; xx += this.gridSize) {\n      for (var yy = -500; yy < 500; yy += this.gridSize) {\n        // Radial field, triangular function of r with max around r0\n        var r = Math.sqrt(xx * xx + yy * yy),\n          r0 = 100,\n          field;\n  \n        if (r < r0) field = (255 / r0) * r;\n        else if (r > r0) field = 255 - Math.min(255, (r - r0) / 2);\n  \n        this.grid.push({\n          x: xx,\n          y: yy,\n          busyAge: 0,\n          spotIndex: i,\n          isEdge:\n            xx === -500\n              ? 'left'\n              : xx === -500 + this.gridSize * (this.gridSteps - 1)\n              ? 'right'\n              : yy === -500\n              ? 'top'\n              : yy === -500 + this.gridSize * (this.gridSteps - 1)\n              ? 'bottom'\n              : false,\n          field: field,\n        });\n        i++;\n      }\n    }\n    this.gridMaxIndex = i;\n  \n    // Counters for UI\n    this.drawnInLastFrame = 0;\n    this.deathCount = 0;\n  \n    this.initDraw();\n  };\n  App.evolve = function () {\n    var time1 = performance.now();\n  \n    this.stepCount++;\n  \n    // Increment all grid ages\n    this.grid.forEach(function (e: any) {\n      if (e.busyAge > 0) e.busyAge++;\n    });\n  \n    if (\n      this.stepCount % this.birthFreq === 0 &&\n      this.particles.length + this.popPerBirth < this.maxPop\n    ) {\n      this.birth();\n    }\n    App.move();\n    App.draw();\n  \n    var time2 = performance.now();\n  \n    // Update UI\n    const elemDead = document.getElementsByClassName('dead');\n    if (elemDead && elemDead.length > 0) elemDead[0].textContent = this.deathCount;\n\n    const elemAlive = document.getElementsByClassName('alive');\n    if (elemAlive && elemAlive.length > 0) elemAlive[0].textContent = this.particles.length;\n    \n    const elemFPS = document.getElementsByClassName('fps');\n    if (elemFPS && elemFPS.length > 0) elemFPS[0].textContent = Math.round(1000 / (time2 - time1)).toString();\n    \n    const elemDrawn = document.getElementsByClassName('drawn');\n    if (elemDrawn && elemDrawn.length > 0) elemDrawn[0].textContent = this.drawnInLastFrame;\n  };\n  App.birth = function () {\n    var x, y;\n    var gridSpotIndex = Math.floor(Math.random() * this.gridMaxIndex);\n    var gridSpot = this.grid[gridSpotIndex];\n    x = gridSpot.x;\n    y = gridSpot.y;\n  \n    var particle = {\n      hue: 200, // + Math.floor(50*Math.random()),\n      sat: 95, //30 + Math.floor(70*Math.random()),\n      lum: 20 + Math.floor(40 * Math.random()),\n      x: x,\n      y: y,\n      xLast: x,\n      yLast: y,\n      xSpeed: 0,\n      ySpeed: 0,\n      age: 0,\n      ageSinceStuck: 0,\n      attractor: {\n        oldIndex: gridSpotIndex,\n        gridSpotIndex: gridSpotIndex, // Pop at random position on grid\n      },\n      name: 'seed-' + Math.ceil(10000000 * Math.random()),\n    };\n    this.particles.push(particle);\n  };\n  App.kill = function (particleName: any) {\n    const newArray = this.particles.filter(\n      (seed: any) => seed.name !== particleName\n    );\n    this.particles = [...newArray];\n  };\n  App.move = function () {\n    for (var i = 0; i < this.particles.length; i++) {\n      // Get particle\n      var p = this.particles[i];\n  \n      // Save last position\n      p.xLast = p.x;\n      p.yLast = p.y;\n  \n      // Attractor and corresponding grid spot\n      var index = p.attractor.gridSpotIndex,\n        gridSpot = this.grid[index];\n  \n      // Maybe move attractor and with certain constraints\n      if (Math.random() < 0.5) {\n        // Move attractor\n        if (!gridSpot.isEdge) {\n          // Change particle's attractor grid spot and local move function's grid spot\n          var topIndex = index - 1,\n            bottomIndex = index + 1,\n            leftIndex = index - this.gridSteps,\n            rightIndex = index + this.gridSteps,\n            topSpot = this.grid[topIndex],\n            bottomSpot = this.grid[bottomIndex],\n            leftSpot = this.grid[leftIndex],\n            rightSpot = this.grid[rightIndex];\n    \n          var maxFieldSpot = maxBy(\n            [topSpot, bottomSpot, leftSpot, rightSpot]\n          );\n  \n          var potentialNewGridSpot = maxFieldSpot;\n          if (\n            potentialNewGridSpot.busyAge === 0 ||\n            potentialNewGridSpot.busyAge > 15\n          ) {\n            p.ageSinceStuck = 0;\n            p.attractor.oldIndex = index;\n            p.attractor.gridSpotIndex = potentialNewGridSpot.spotIndex;\n            gridSpot = potentialNewGridSpot;\n            gridSpot.busyAge = 1;\n          } else p.ageSinceStuck++;\n        } else p.ageSinceStuck++;\n  \n        if (p.ageSinceStuck === 10) this.kill(p.name);\n      }\n  \n      // Spring attractor to center with viscosity\n      const k = 8, visc = 0.4;\n      var dx = p.x - gridSpot.x,\n        dy = p.y - gridSpot.y,\n        dist = Math.sqrt(dx * dx + dy * dy);\n  \n      // Spring\n      var xAcc = -k * dx,\n        yAcc = -k * dy;\n  \n      p.xSpeed += xAcc;\n      p.ySpeed += yAcc;\n  \n      // Calm the f*ck down\n      p.xSpeed *= visc;\n      p.ySpeed *= visc;\n  \n      // Store stuff in particle brain\n      p.speed = Math.sqrt(p.xSpeed * p.xSpeed + p.ySpeed * p.ySpeed);\n      p.dist = dist;\n  \n      // Update position\n      p.x += 0.1 * p.xSpeed;\n      p.y += 0.1 * p.ySpeed;\n  \n      // Get older\n      p.age++;\n  \n      // Kill if too old\n      if (p.age > this.lifespan) {\n        this.kill(p.name);\n        this.deathCount++;\n      }\n    }\n  };\n  App.initDraw = function () {\n    this.ctx.beginPath();\n    this.ctx.rect(0, 0, this.width, this.height);\n    this.ctx.fillStyle = this.bgColor;\n    this.ctx.fill();\n    this.ctx.closePath();\n  };\n  App.draw = function () {\n    this.drawnInLastFrame = 0;\n    if (!this.particles.length) return false;\n  \n    this.ctx.beginPath();\n    this.ctx.rect(0, 0, this.width, this.height);\n    this.ctx.fillStyle = this.bgColor;\n    this.ctx.fill();\n    this.ctx.closePath();\n  \n    for (var i = 0; i < this.particles.length; i++) {\n      var p = this.particles[i];\n  \n      var last = this.dataXYtoCanvasXY(p.xLast, p.yLast),\n        now = this.dataXYtoCanvasXY(p.x, p.y);\n      var attracSpot = this.grid[p.attractor.gridSpotIndex],\n        attracXY = this.dataXYtoCanvasXY(attracSpot.x, attracSpot.y);\n      var oldAttracSpot = this.grid[p.attractor.oldIndex],\n        oldAttracXY = this.dataXYtoCanvasXY(oldAttracSpot.x, oldAttracSpot.y);\n  \n      this.ctx.beginPath();\n      this.ctx.strokeStyle = '#9fef00';\n      this.ctx.fillStyle = '#9fef00';\n  \n      // Particle trail\n      this.ctx.moveTo(last.x, last.y);\n      this.ctx.lineTo(now.x, now.y);\n  \n      this.ctx.lineWidth = 1.5 * this.dataToImageRatio;\n      this.ctx.stroke();\n      this.ctx.closePath();\n  \n      // Attractor positions\n      this.ctx.beginPath();\n      this.ctx.lineWidth = 1.5 * this.dataToImageRatio;\n      this.ctx.moveTo(oldAttracXY.x, oldAttracXY.y);\n      this.ctx.lineTo(attracXY.x, attracXY.y);\n      this.ctx.arc(\n        attracXY.x,\n        attracXY.y,\n        1.5 * this.dataToImageRatio,\n        0,\n        2 * Math.PI,\n        false\n      );\n\n      this.ctx.strokeStyle = '#9fef00';\n      this.ctx.fillStyle = '#9fef00';\n\n      this.ctx.stroke();\n      this.ctx.fill();\n  \n      this.ctx.closePath();\n  \n      // UI counter\n      this.drawnInLastFrame++;\n    }\n  };\n  App.dataXYtoCanvasXY = function (x: number, y: number) {\n    var zoom = 1.6;\n    var xx = this.xC + x * zoom * this.dataToImageRatio,\n      yy = this.yC + y * zoom * this.dataToImageRatio;\n  \n    return { x: xx, y: yy };\n  };\n  \n  useEffect(() => {\n    App.setup();\n    App.draw();\n  \n    var frame = function () {\n      App.evolve();\n      requestAnimationFrame(frame);\n    };\n    frame();\n  }, [App]);\n  \n  \n\n  return (\n  <div id='fancy-background'>\n    <p><span className='dead'>0</span></p>\n    <p><span className='alive'>0</span></p>\n    <p><span className='drawn'>0</span></p>\n    <p><span className='fps'>0</span></p>\n  </div>\n  );\n}\n\nexport default FancyBackground;\n"
  },
  {
    "path": "src/web-check-live/components/misc/Flag.tsx",
    "content": "interface Props {\n  countryCode: string,\n  width: number,\n};\n\nconst Flag = ({ countryCode, width }: Props): JSX.Element => {\n\n  const getFlagUrl = (code: string, w: number = 64) => {\n    const protocol = 'https';\n    const cdn = 'flagcdn.com';\n    const dimensions = `${width}x${width * 0.75}`;\n    const country = countryCode.toLowerCase();\n    const ext = 'png';\n    return `${protocol}://${cdn}/${dimensions}/${country}.${ext}`;\n  };\n\n  return (<img src={getFlagUrl(countryCode, width)} alt={countryCode} />);\n}\n\nexport default Flag;\n"
  },
  {
    "path": "src/web-check-live/components/misc/Footer.tsx",
    "content": "import styled from '@emotion/styled';\nimport { Link } from 'react-router-dom';\nimport colors from 'web-check-live/styles/colors';\n\nconst StyledFooter = styled.footer`\n  bottom: 0;\n  width: 100%;\n  text-align: center;\n  padding: 0.5rem 0;\n  background: ${colors.backgroundDarker};\n  display: flex;\n  justify-content: space-around;\n  align-items: center;\n  align-content: center;\n  flex-wrap: wrap;\n  opacity: 0.75;\n  transition: all 0.2s ease-in-out;\n  @media (min-width: 1024px) {\n    justify-content: space-between;\n  }\n  &:hover {\n    opacity: 1;\n  }\n  span {\n    margin: 0 0.5rem;\n    text-align: center; \n  }\n`;\n\n\nconst ALink = styled.a`\n  color: ${colors.primary};\n  font-weight: bold;\n  border-radius: 4px;\n  padding: 0.1rem;\n  transition: all 0.2s ease-in-out;\n  &:hover {\n    background: ${colors.primary};\n    color: ${colors.backgroundDarker};\n    text-decoration: none;\n  }\n`;\n\nconst Footer = (props: { isFixed?: boolean }): JSX.Element => {\n  const licenseUrl = 'https://github.com/lissy93/web-check/blob/master/LICENSE';\n  const authorUrl = 'https://aliciasykes.com';\n  const githubUrl = 'https://github.com/lissy93/web-check';\n  return (\n  <StyledFooter style={props.isFixed ? {position: 'fixed'} : {}}>\n    <span>\n      View source at <ALink href={githubUrl}>github.com/lissy93/web-check</ALink>\n    </span>\n    <span>\n      <Link to=\"/about\">Web-Check</Link> is\n      licensed under <ALink href={licenseUrl}>MIT</ALink> -\n      © <ALink href={authorUrl}>Alicia Sykes</ALink> 2023\n    </span>\n  </StyledFooter>\n  );\n}\n\nexport default Footer;\n"
  },
  {
    "path": "src/web-check-live/components/misc/Loader.tsx",
    "content": "import styled from '@emotion/styled';\n\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport colors from 'web-check-live/styles/colors';\n\nconst LoaderContainer = styled(StyledCard)`\n  margin: 0 auto 1rem auto;\n  width: 95vw;\n  position: relative;\n  transition: all 0.2s ease-in-out;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: column;\n  gap: 2rem;\n  height: 50vh;\n  transition: all 0.3s ease-in-out;\n  p.loadTimeInfo {\n    text-align: center;\n    margin: 0;\n    color: ${colors.textColorSecondary};\n    opacity: 0.5;\n  }\n  &.flex {\n    display: flex;\n  }\n  &.finished {\n    height: 0;\n    overflow: hidden;\n    opacity: 0;\n    margin: 0;\n    padding: 0;\n    svg { width: 0; }\n    h4 { font-size: 0; }\n  }\n  &.hide {\n    display: none;\n  }\n`;\n\nconst StyledSvg = styled.svg`\n  width: 200px;\n  margin: 0 auto;\n  path {\n    fill: ${colors.primary};\n    &:nth-of-type(2) { opacity: 0.8; }\n    &:nth-of-type(3) { opacity: 0.5; }\n  }\n`;\n\nconst Loader = (props: { show: boolean }): JSX.Element => {\n  return (\n  <LoaderContainer className={props.show ? '' : 'finished'}>\n    <Heading as=\"h4\" color={colors.primary}>Crunching data...</Heading>\n    <StyledSvg version=\"1.1\" id=\"L7\" xmlns=\"http://www.w3.org/2000/svg\" x=\"0px\" y=\"0px\"\n      viewBox=\"0 0 100 100\" enableBackground=\"new 0 0 100 100\">\n      <path fill=\"#fff\" d=\"M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3\n        c-8.4-21.3,2-45.4,23.3-53.8L31.6,3.5z\">\n          <animateTransform \n            attributeName=\"transform\" \n            attributeType=\"XML\" \n            type=\"rotate\"\n            dur=\"2s\" \n            from=\"0 50 50\"\n            to=\"360 50 50\" \n            repeatCount=\"indefinite\" />\n      </path>\n    <path fill=\"#fff\" d=\"M42.3,39.6c5.7-4.3,13.9-3.1,18.1,2.7c4.3,5.7,3.1,13.9-2.7,18.1l4.1,5.5c8.8-6.5,10.6-19,4.1-27.7\n      c-6.5-8.8-19-10.6-27.7-4.1L42.3,39.6z\">\n          <animateTransform \n            attributeName=\"transform\" \n            attributeType=\"XML\" \n            type=\"rotate\"\n            dur=\"1s\" \n            from=\"0 50 50\"\n            to=\"-360 50 50\" \n            repeatCount=\"indefinite\" />\n      </path>\n    <path fill=\"#fff\" d=\"M82,35.7C74.1,18,53.4,10.1,35.7,18S10.1,46.6,18,64.3l7.6-3.4c-6-13.5,0-29.3,13.5-35.3s29.3,0,35.3,13.5\n      L82,35.7z\">\n          <animateTransform \n            attributeName=\"transform\" \n            attributeType=\"XML\" \n            type=\"rotate\"\n            dur=\"2s\" \n            from=\"0 50 50\"\n            to=\"360 50 50\" \n            repeatCount=\"indefinite\" />\n      </path>\n    </StyledSvg>\n    <p className=\"loadTimeInfo\">\n      It may take up-to a minute for all jobs to complete<br />\n      You can view preliminary results as they come in below\n    </p>\n  </LoaderContainer>\n  );\n}\n\nexport default Loader;\n\n\n\n"
  },
  {
    "path": "src/web-check-live/components/misc/LocationMap.tsx",
    "content": "import {\n  ComposableMap,\n  Geographies,\n  Geography,\n  Annotation,\n} from 'react-simple-maps';\n\nimport colors from 'web-check-live/styles/colors';\nimport MapFeatures from 'web-check-live/assets/data/map-features.json';\n\ninterface Props {\n  lat: number,\n  lon: number,\n  label?: string,\n};\n\nconst MapChart = (location: Props) => {\n  const { lat, lon, label } = location;\n\n  return (\n    <ComposableMap\n      projection=\"geoAzimuthalEqualArea\"\n      projectionConfig={{\n        rotate: [0, 0, 0],\n        center: [lon + 5, lat - 25],\n        scale: 200\n      }}\n    >\n      <Geographies\n        geography={MapFeatures}\n        fill={colors.backgroundDarker}\n        stroke={colors.primary}\n        strokeWidth={0.5}\n      >\n        {({ geographies }: any) =>\n          geographies.map((geo: any) => (\n            <Geography key={geo.rsmKey} geography={geo} />\n          ))\n        }\n      </Geographies>\n      <Annotation\n        subject={[lon, lat]}\n        dx={-80}\n        dy={-80}\n        connectorProps={{\n          stroke: colors.textColor,\n          strokeWidth: 3,\n          strokeLinecap: \"round\"\n        }}\n      >\n        <text x=\"-8\" textAnchor=\"end\" fill={colors.textColor} fontSize={25}>\n          {label || \"Server\"}\n        </text>\n      </Annotation>\n    </ComposableMap>\n  );\n};\n\nexport default MapChart;\n"
  },
  {
    "path": "src/web-check-live/components/misc/ProgressBar.tsx",
    "content": "import styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport Card from 'web-check-live/components/Form/Card';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport { useState, useEffect, type ReactNode } from 'react';\n\n\nconst LoadCard = styled(Card)`\n  margin: 0 auto 1rem auto;\n  width: 95vw;\n  position: relative;\n  transition: all 0.2s ease-in-out;\n  &.hidden {\n    height: 0;\n    overflow: hidden;\n    margin: 0;\n    padding: 0;\n  }\n`;\n\nconst ProgressBarContainer = styled.div`\n  width: 100%;\n  height: 0.5rem;\n  background: ${colors.bgShadowColor};\n  border-radius: 4px;\n  overflow: hidden;\n`;\n\nconst ProgressBarSegment = styled.div<{ color: string, color2: string, width: number }>`\n  height: 1rem;\n  display: inline-block;\n  width: ${props => props.width}%;\n  background: ${props => props.color};\n  background: ${props => props.color2 ?\n      `repeating-linear-gradient( 315deg, ${props.color}, ${props.color} 3px, ${props.color2} 3px, ${props.color2} 6px )`\n      : props.color};\n  transition: width 0.5s ease-in-out;\n`;\n\nconst Details = styled.details`\n  transition: all 0.2s ease-in-out;\n  summary {\n    margin: 0.5rem 0;\n    font-weight: bold;\n    cursor: pointer;\n  }\n  summary:before {\n    content: \"►\";\n    position: absolute;\n    margin-left: -1rem;\n    color: ${colors.primary};\n    cursor: pointer;\n  }\n  &[open] summary:before {\n    content: \"▼\";\n  }\n  ul {\n    list-style: none;\n    padding: 0.25rem;\n    border-radius: 4px;\n    width: fit-content;\n    li b {\n      cursor: pointer;\n    }\n    i {\n      color: ${colors.textColorSecondary};\n    }\n  }\n  p.error {\n    margin: 0.5rem 0;\n    opacity: 0.75;\n    color: ${colors.danger};\n  }\n`;\n\nconst StatusInfoWrapper = styled.div`\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  .run-status {\n    color: ${colors.textColorSecondary};\n    margin: 0;\n  }\n`;\n\nconst AboutPageLink = styled.a`\n  color: ${colors.primary};\n`;\n\nconst SummaryContainer = styled.div`\n  margin: 0.5rem 0;\n  b {\n    margin: 0;\n    font-weight: bold;\n  }\n  p {\n    margin: 0;\n    opacity: 0.75;\n  }\n  &.error-info {\n    color: ${colors.danger};\n  }\n  &.success-info {\n    color: ${colors.success};\n  }\n  &.loading-info {\n    color: ${colors.info};\n  }\n  .skipped {\n    margin-left: 0.75rem;\n    color: ${colors.warning};\n  }\n  .success {\n    margin-left: 0.75rem;\n    color: ${colors.success};\n  }\n`;\n\nconst ReShowContainer = styled.div`\n  position: relative;\n  &.hidden {\n    height: 0;\n    overflow: hidden;\n    margin: 0;\n    padding: 0;\n  }\n  button { background: none;}\n`;\n\nconst DismissButton = styled.button`\n  width: fit-content;\n  position: absolute;\n  right: 1rem;\n  bottom: 1rem;\n  background: ${colors.background};\n  color: ${colors.textColorSecondary};\n  border: none;\n  padding: 0.25rem 0.5rem;\n  border-radius: 4px;\n  font-family: PTMono;\n  cursor: pointer;\n  &:hover {\n    color: ${colors.primary};\n  }\n`;\n\nconst FailedJobActionButton = styled.button`\n  margin: 0.1rem 0.1rem 0.1rem 0.5rem;\n  background: ${colors.background};\n  color: ${colors.textColorSecondary};\n  border: none;\n  padding: 0.25rem 0.5rem;\n  border-radius: 4px;\n  font-family: PTMono;\n  cursor: pointer;\n  border: 1px solid ${colors.textColorSecondary};\n  transition: all 0.2s ease-in-out;\n  &:hover {\n    color: ${colors.primary};\n    border: 1px solid ${colors.primary};\n  } \n`;\n\nconst ErrorModalContent = styled.div`\np {\n  margin: 0;\n}\npre {\n  color: ${colors.danger};\n  &.info {\n    color: ${colors.warning};\n  }\n}\n`;\n\nexport type LoadingState = 'success' | 'loading' | 'skipped' | 'error' | 'timed-out';\n\nexport interface LoadingJob {\n  name: string,\n  state: LoadingState,\n  error?: string,\n  timeTaken?: number,\n  retry?: () => void,\n}\n\nconst jobNames = [\n  'get-ip',\n  'location',\n  'ssl',\n  'domain',\n  'quality',\n  'tech-stack',\n  'server-info',\n  'cookies',\n  'headers',\n  'dns',\n  'hosts',\n  'http-security',\n  'social-tags',\n  'trace-route',\n  'security-txt',\n  'dns-server',\n  'firewall',\n  'dnssec',\n  'hsts',\n  'threats',\n  'mail-config',\n  'archives',\n  'rank',\n  'screenshot',\n  'tls-cipher-suites',\n  'tls-security-config',\n  'tls-client-support',\n  'redirects',\n  'linked-pages',\n  'robots-txt',\n  'status',\n  'ports',\n  // 'whois',\n  'txt-records',\n  'block-lists',\n  'features',\n  'sitemap',\n  'carbon',\n] as const;\n\ninterface JobListItemProps {\n  job: LoadingJob;\n  showJobDocs: (name: string) => void;\n  showErrorModal: (name: string, state: LoadingState, timeTaken: number | undefined, error: string, isInfo?: boolean) => void;\n  barColors: Record<LoadingState, [string, string]>;\n}\n\nconst getStatusEmoji = (state: LoadingState): string => {\n  switch (state) {\n    case 'success':\n      return '✅';\n    case 'loading':\n      return '🔄';\n    case 'error':\n      return '❌';\n    case 'timed-out':\n      return '⏸️';\n    case 'skipped':\n      return '⏭️';\n    default:\n      return '❓';\n  }\n};\n\nconst JobListItem: React.FC<JobListItemProps> = ({ job, showJobDocs, showErrorModal, barColors }) => {\n  const { name, state, timeTaken, retry, error } = job;\n  const actionButton = retry && state !== 'success' && state !== 'loading' ? \n    <FailedJobActionButton onClick={retry}>↻ Retry</FailedJobActionButton> : null;\n    \n  const showModalButton = error && ['error', 'timed-out', 'skipped'].includes(state) &&\n    <FailedJobActionButton onClick={() => showErrorModal(name, state, timeTaken, error, state === 'skipped')}> \n      {state === 'timed-out' ? '■ Show Timeout Reason' : '■ Show Error'} \n    </FailedJobActionButton>;\n\n  return (\n    <li key={name}>\n      <b onClick={() => showJobDocs(name)}>{getStatusEmoji(state)} {name}</b>\n      <span style={{color: barColors[state][0]}}> ({state})</span>.\n      <i>{timeTaken && state !== 'loading' ? ` Took ${timeTaken} ms` : ''}</i>\n      {actionButton}\n      {showModalButton}\n    </li>\n  );\n};\n\n\nexport const initialJobs = jobNames.map((job: string) => {\n  return {\n    name: job,\n    state: 'loading' as LoadingState,\n    retry: () => {}\n  }\n});\n\nexport const calculateLoadingStatePercentages = (loadingJobs: LoadingJob[]): Record<LoadingState | string, number> => {\n  const totalJobs = loadingJobs.length;\n\n  // Initialize count object\n  const stateCount: Record<LoadingState, number> = {\n    'success': 0,\n    'loading': 0,\n    'timed-out': 0,\n    'error': 0,\n    'skipped': 0,\n  };\n\n  // Count the number of each state\n  loadingJobs.forEach((job) => {\n    stateCount[job.state] += 1;\n  });\n\n  // Convert counts to percentages\n  const statePercentage: Record<LoadingState, number> = {\n    'success': (stateCount['success'] / totalJobs) * 100,\n    'loading': (stateCount['loading'] / totalJobs) * 100,\n    'timed-out': (stateCount['timed-out'] / totalJobs) * 100,\n    'error': (stateCount['error'] / totalJobs) * 100,\n    'skipped': (stateCount['skipped'] / totalJobs) * 100,\n  };\n\n  return statePercentage;\n};\n\nconst MillisecondCounter = (props: {isDone: boolean}) => {\n  const { isDone } = props;\n  const [milliseconds, setMilliseconds] = useState<number>(0);\n\n  useEffect(() => {\n    let timer: NodeJS.Timeout;\n    // Start the timer as soon as the component mounts\n    if (!isDone) {\n      timer = setInterval(() => {\n        setMilliseconds(milliseconds => milliseconds + 100);\n      }, 100);\n    }\n    // Clean up the interval on unmount\n    return () => {\n      clearInterval(timer);\n    };\n  }, [isDone]); // If the isDone prop changes, the effect will re-run\n\n  return <span>{milliseconds} ms</span>;\n};\n\nconst RunningText = (props: { state: LoadingJob[], count: number }): JSX.Element => {\n  const loadingTasksCount = jobNames.length - props.state.filter((val: LoadingJob) => val.state === 'loading').length;\n  const isDone = loadingTasksCount >= jobNames.length;\n  return (\n    <p className=\"run-status\">\n    { isDone ? 'Finished in ' : `Running ${loadingTasksCount} of ${jobNames.length} jobs - ` }\n    <MillisecondCounter isDone={isDone} />\n    </p>\n  );\n};\n\nconst SummaryText = (props: { state: LoadingJob[], count: number }): JSX.Element => {\n  const totalJobs = jobNames.length;\n  let failedTasksCount = props.state.filter((val: LoadingJob) => val.state === 'error').length;\n  let loadingTasksCount = props.state.filter((val: LoadingJob) => val.state === 'loading').length;\n  let skippedTasksCount = props.state.filter((val: LoadingJob) => val.state === 'skipped').length;\n  let successTasksCount = props.state.filter((val: LoadingJob) => val.state === 'success').length;\n\n  const jobz = (jobCount: number) => `${jobCount} ${jobCount === 1 ? 'job' : 'jobs'}`;\n\n  const skippedInfo = skippedTasksCount > 0 ? (<span className=\"skipped\">{jobz(skippedTasksCount)} skipped </span>) : null;\n  const successInfo = successTasksCount > 0 ? (<span className=\"success\">{jobz(successTasksCount)} successful </span>) : null;\n  const failedInfo = failedTasksCount > 0 ? (<span className=\"error\">{jobz(failedTasksCount)} failed </span>) : null;\n\n  if (loadingTasksCount > 0) {\n    return (\n      <SummaryContainer className=\"loading-info\">\n        <b>Loading {totalJobs - loadingTasksCount} / {totalJobs} Jobs</b>\n        {skippedInfo}\n      </SummaryContainer>\n    );\n  }\n\n  if (failedTasksCount === 0) {\n    return (\n      <SummaryContainer className=\"success-info\">\n        <b>{successTasksCount} Jobs Completed Successfully</b>\n        {skippedInfo}\n      </SummaryContainer>\n    );\n  }\n\n  return (\n    <SummaryContainer className=\"error-info\">\n      {successInfo}\n      {skippedInfo}\n      {failedInfo}\n    </SummaryContainer>\n  );\n};\n\nconst ProgressLoader = (props: { loadStatus: LoadingJob[], showModal: (err: ReactNode) => void, showJobDocs: (job: string) => void }): JSX.Element => {\n  const [ hideLoader, setHideLoader ] = useState<boolean>(false);\n  const loadStatus = props.loadStatus;\n  const percentages = calculateLoadingStatePercentages(loadStatus);\n\n  const loadingTasksCount = jobNames.length - loadStatus.filter((val: LoadingJob) => val.state === 'loading').length;\n  const isDone = loadingTasksCount >= jobNames.length;\n\n  const makeBarColor = (colorCode: string): [string, string] => {\n    const amount = 10;\n    const darkerColorCode = '#' + colorCode.replace(/^#/, '').replace(\n      /../g,\n      colorCode => ('0' + Math.min(255, Math.max(0, parseInt(colorCode, 16) - amount)).toString(16)).slice(-2),\n    );\n    return [colorCode, darkerColorCode];\n  };\n\n  const barColors: Record<LoadingState | string, [string, string]> = {\n    'success': isDone ? makeBarColor(colors.primary) : makeBarColor(colors.success),\n    'loading': makeBarColor(colors.info),\n    'error': makeBarColor(colors.danger),\n    'timed-out': makeBarColor(colors.warning),\n    'skipped': makeBarColor(colors.neutral),\n  };\n\n  const showErrorModal = (name: string, state: LoadingState, timeTaken: number | undefined, error: string, isInfo?: boolean) => {\n    const errorContent = (\n      <ErrorModalContent>\n        <Heading as=\"h3\">Error Details for {name}</Heading>\n        <p>\n          The {name} job failed with an {state} state after {timeTaken} ms.\n          The server responded with the following error:\n        </p>\n        { /* If isInfo == true, then add .info className to pre */}\n        <pre className={isInfo ? 'info' : 'error'}>{error}</pre>\n      </ErrorModalContent>\n    );\n    props.showModal(errorContent);\n  };\n\n  return (\n  <>\n  <ReShowContainer className={!hideLoader ? 'hidden' : ''}>\n    <DismissButton onClick={() => setHideLoader(false)}>Show Load State</DismissButton>\n  </ReShowContainer>\n  <LoadCard className={hideLoader ? 'hidden' : ''}>\n    <ProgressBarContainer>\n      {Object.keys(percentages).map((state: string | LoadingState) =>\n        <ProgressBarSegment \n          color={barColors[state][0]} \n          color2={barColors[state][1]} \n          title={`${state} (${Math.round(percentages[state])}%)`}\n          width={percentages[state]}\n          key={`progress-bar-${state}`}\n        />\n      )}\n    </ProgressBarContainer>\n    \n    <StatusInfoWrapper>\n      <SummaryText state={loadStatus} count={loadStatus.length} />\n      <RunningText state={loadStatus} count={loadStatus.length} />\n    </StatusInfoWrapper>\n\n    <Details>\n      <summary>Show Details</summary>\n      <ul>\n        {loadStatus.map((job: LoadingJob) => (\n          <JobListItem key={job.name} job={job} showJobDocs={props.showJobDocs} showErrorModal={showErrorModal} barColors={barColors} />\n        ))}\n      </ul>\n      { loadStatus.filter((val: LoadingJob) => val.state === 'error').length > 0 &&\n        <p className=\"error\">\n          <b>Check the browser console for logs and more info</b><br />\n          It's normal for some jobs to fail, either because the host doesn't return the required info,\n          or restrictions in the lambda function, or hitting an API limit.\n        </p>}\n        <AboutPageLink href=\"/check/about\" target=\"_blank\" rel=\"noreferer\" >Learn More about Web-Check</AboutPageLink>\n    </Details>\n    <DismissButton onClick={() => setHideLoader(true)}>Dismiss</DismissButton>\n  </LoadCard>\n  </>\n  );\n}\n\n\n\nexport default ProgressLoader;\n"
  },
  {
    "path": "src/web-check-live/components/misc/SelfScanMsg.tsx",
    "content": "\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { StyledCard } from 'web-check-live/components/Form/Card';\n\nconst StyledSelfScanMsg = styled(StyledCard)`\n  margin: 0px auto 1rem;\n  width: 95vw;\n  a { color: ${colors.primary}; }\n  b { font-weight: extra-bold; }\n  span, i { opacity: 0.85; }\n  img {\n    width: 5rem;\n    float: right;\n    border-radius: 4px;\n  }\n`;\n\nconst messages = [\n  'Nice try! But scanning this app is like trying to tickle yourself. It just doesn\\'t work!',\n  'Recursive scanning detected. The universe might implode...or it might not. But let\\'s not try to find out.',\n  'Hey, stop checking us out! We\\'re blushing... 😉',\n  'Hmmm, scanning us, are you? We feel so special!',\n  'Alert! Mirror scanning detected. Trust us, we\\'re looking good 😉',\n  'We\\'re flattered you\\'re trying to scan us, but we can\\'t tickle ourselves!',\n  'Oh, inspecting the inspector, aren\\'t we? Inception much?',\n  'Just a second...wait a minute...you\\'re scanning us?! Well, that\\'s an interesting twist!',\n  'Scanning us? It\\'s like asking a mirror to reflect on itself.',\n  'Well, this is awkward... like a dog chasing its own tail!',\n  'Ah, I see you\\'re scanning this site... But alas, this did not cause an infinite recursive loop (this time)',\n];\n\nconst SelfScanMsg = () => {\n  return (\n    <StyledSelfScanMsg>\n      <img src=\"https://i.ibb.co/0tQbCPJ/test2.png\" alt=\"Self-Scan\" />\n      <b>{messages[Math.floor(Math.random() * messages.length)]}</b>\n      <br />\n      <span>\n        But if you want to see how this site is built, why not check out\n        the <a href='https://github.com/lissy93/web-check'>source code</a>?\n      </span>\n      <br />\n      <i>Do me a favour, and drop the repo a Star while you're there</i> 😉\n    </StyledSelfScanMsg>\n  );\n};\n\nexport default SelfScanMsg;\n"
  },
  {
    "path": "src/web-check-live/components/misc/ViewRaw.tsx",
    "content": "import React, { useState } from 'react';\nimport styled from '@emotion/styled';\nimport colors from 'web-check-live/styles/colors';\nimport { Card } from 'web-check-live/components/Form/Card';\nimport Button from 'web-check-live/components/Form/Button';\n\nconst CardStyles = `\nmargin: 0 auto 1rem auto;\nwidth: 95vw;\nposition: relative;\ntransition: all 0.2s ease-in-out;\ndisplay: flex;\nflex-direction: column;\na {\n  color: ${colors.primary};\n}\n.controls {\n  display: flex;\n  flex-wrap: wrap;\n  button {\n    max-width: 300px;\n  }\n}\nsmall {\n  margin-top: 0.5rem;\n  font-size: 0.8rem;\n  opacity: 0.5;\n}\n`;\n\nconst StyledIframe = styled.iframe`\n  width: calc(100% - 2rem);\n  outline: none;\n  border: none;\n  border-radius: 4px;\n  min-height: 50vh;\n  height: 100%;\n  margin: 1rem;\n  background: ${colors.background};\n`;\n\nconst ViewRaw = (props: { everything: { id: string, result: any}[] }) => {\n  const [resultUrl, setResultUrl] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n\n  const makeResults = () => {\n    const result: {[key: string]: any} = {};\n    props.everything.forEach((item: {id: string, result: any}) => {\n      result[item.id] = item.result;\n    });\n    return result;\n  };\n\n  const fetchResultsUrl = async () => {\n    const resultContent = makeResults();\n    const response = await fetch('https://jsonhero.io/api/create.json', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        title: 'web-check results',\n        content: resultContent,\n        readOnly: true,\n        ttl: 3600,\n      })\n    });\n    if (!response.ok) {\n      setError(`HTTP error! status: ${response.status}`);\n    } else {\n      setError(null);\n    }\n    await response.json().then(\n      (data) => setResultUrl(data.location)\n    )\n  };\n\n  const handleDownload = () => {\n    const blob = new Blob([JSON.stringify(makeResults(), null, 2)], { type: \"application/json\" });\n    const url = URL.createObjectURL(blob);\n    const link = document.createElement('a');\n    link.href = url;\n    link.download = 'web-check-results.json';\n    link.click();\n    URL.revokeObjectURL(url);\n  }\n  return (\n    <Card heading=\"View / Download Raw Data\" styles={CardStyles}>\n      <div className=\"controls\">\n        <Button onClick={handleDownload}>Download Results</Button>\n        <Button onClick={fetchResultsUrl}>{resultUrl ? 'Update Results' : 'View Results'}</Button>\n        { resultUrl && <Button onClick={() => setResultUrl('') }>Hide Results</Button> }\n      </div>\n      { resultUrl && !error &&\n      <>\n        <StyledIframe title=\"Results, via JSON Hero\" src={resultUrl} />\n        <small>Your results are available to view <a href={resultUrl}>here</a>.</small>\n      </>\n      }\n      { error && <p className=\"error\">{error}</p> }\n      <small>\n        These are the raw results generated from your URL, and in JSON format.\n        You can import these into your own program, for further analysis.\n      </small>\n    </Card>\n  );\n};\n\nexport default ViewRaw;\n"
  },
  {
    "path": "src/web-check-live/hooks/motherOfAllHooks.ts",
    "content": "import { useState, useEffect } from 'react';\nimport { toast } from 'react-toastify';\nimport 'react-toastify/dist/ReactToastify.css';\n\nimport type { LoadingState } from 'web-check-live/components/misc/ProgressBar';\nimport type { AddressType } from 'web-check-live/utils/address-type-checker';\nimport keys from 'web-check-live/utils/get-keys';\n\ninterface UseIpAddressProps<ResultType = any> {\n  // Unique identifier for this job type\n  jobId: string | string[];\n  // The actual fetch request\n  fetchRequest: () => Promise<ResultType>;\n  // Function to call to update the loading state in parent\n  updateLoadingJobs: (job: string | string[], newState: LoadingState, error?: string, retry?: (data?: any) => void | null, data?: any) => void;\n  addressInfo: {\n    // The hostname/ip address that we're checking\n    address: string | undefined;\n    // The type of address (e.g. url, ipv4)\n    addressType: AddressType;\n    // The valid address types for this job\n    expectedAddressTypes: AddressType[];\n  };\n}\n\ntype ResultType = any;\n\ntype ReturnType = [ResultType | undefined, (data?: any) => void];\n\nconst useMotherOfAllHooks = <ResultType = any>(params: UseIpAddressProps<ResultType>): ReturnType => {\n  // Destructure params\n  const { addressInfo, fetchRequest, jobId, updateLoadingJobs } = params;\n  const { address, addressType, expectedAddressTypes } = addressInfo;\n\n  // Build useState that will be returned\n  const [result, setResult] = useState<ResultType>();\n\n  // Fire off the HTTP fetch request, then set results and update loading / error state\n\n  const doTheFetch = () => {\n    if (keys.disableEverything) {\n      updateLoadingJobs(jobId, 'skipped', 'Web-Check is temporarily disabled. Please try again later.', reset);\n      return Promise.resolve();\n    }\n    return fetchRequest()\n    .then((res: any) => {\n      if (!res) { // No response :(\n        updateLoadingJobs(jobId, 'error', 'No response', reset);\n      } else if (res.error) { // Response returned an error message\n        if (res.error.includes(\"timed-out\")) { // Specific handling for timeout errors\n          updateLoadingJobs(jobId, 'timed-out', res.error, reset);\n        } else {\n          updateLoadingJobs(jobId, 'error', res.error, reset);\n        }\n      } else if (res.errorType && res.errorMessage) {\n        const errorMessage = `${res.errorType}\\n${res.errorMessage}\\n\\n`\n        + `This sometimes occurs on Netlify if using the free plan. You may need to upgrade to use lambda functions`;\n        updateLoadingJobs(jobId, 'error', errorMessage, reset);\n      } else if (res.skipped) { // Response returned a skipped message\n        updateLoadingJobs(jobId, 'skipped', res.skipped, reset);\n      } else { // Yay, everything went to plan :)\n        setResult(res);\n        updateLoadingJobs(jobId, 'success', '', undefined, res);\n      }\n    })\n    .catch((err) => {\n      // Something fucked up\n      updateLoadingJobs(jobId, 'error', err.error || err.message || 'Unknown error', reset);\n      throw err;\n    })\n  }\n\n  // For when the user manually re-triggers the job\n  const reset = (data: any) => {\n    // If data is provided, then update state\n    if (data && !(data instanceof Event) && !data?._reactName) {\n      setResult(data);\n    } else { // Otherwise, trigger a data re-fetch\n      updateLoadingJobs(jobId, 'loading');\n      const fetchyFetch = doTheFetch();\n      const toastOptions = {\n        pending: `Updating Data (${jobId})`,\n        success: `Completed (${jobId})`,\n        error: `Failed to update (${jobId})`,\n        skipped: `Skipped job (${jobId}), as no valid results for host`,\n      };\n      // Initiate fetch, and show progress toast\n      toast.promise(fetchyFetch, toastOptions).catch(() => {});\n    }\n  };\n\n  useEffect(() => {\n    // Still waiting for this upstream, cancel job\n    if (!address || !addressType) {\n      return;\n    }\n    // This job isn't needed for this address type, cancel job\n    if (!expectedAddressTypes.includes(addressType)) {\n      if (addressType !== 'empt') updateLoadingJobs(jobId, 'skipped');\n      return;\n    }\n\n    // Initiate the data fetching process\n    doTheFetch().catch(() => {});\n    \n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [address, addressType]);\n\n  return [result, reset];\n};\n\nexport default useMotherOfAllHooks;\n\n// I really fucking hate TypeScript sometimes....\n// Feels like a weak attempt at trying to make JavaScript less crappy,\n// when the real solution would be to just switch to a proper, typed, safe language\n// ... Either that, or I'm just really shit at it.\n"
  },
  {
    "path": "src/web-check-live/main.tsx",
    "content": "import { BrowserRouter } from \"react-router-dom\";\nimport { StaticRouter } from \"react-router-dom/server\";\nimport App from \"./App.tsx\";\n\nexport default ({ pathname }: { pathname: string }) => (\n    import.meta.env.SSR\n        ? <StaticRouter location={pathname}><App/></StaticRouter>\n        : <BrowserRouter><App/></BrowserRouter>\n)\n"
  },
  {
    "path": "src/web-check-live/styles/colors.ts",
    "content": "\nconst colors = {\n  primary: '#c1fb41',\n  primaryLighter: '#cff97a',\n  textColor: '#ffffff',\n  textColorSecondary: '#a4b1cd',\n  background: '#141517',\n  backgroundDarker: '#000000',\n  backgroundLighter: '#242525',\n  bgShadowColor: '#101010',\n  fgShadowColor: '#3f550e',\n  primaryTransparent: '#9fef0012',\n\n  // Action Colors\n  info: '#04e4f4',\n  success: '#20e253',\n  warning: '#f6f000',\n  error: '#fca016',\n  danger: '#f80363',\n  neutral: '#272f4d',\n};\n\nexport default colors;\n"
  },
  {
    "path": "src/web-check-live/styles/dimensions.ts",
    "content": "export type InputSize = 'small' | 'medium' | 'large';\n\nexport const applySize = (inputSize?: InputSize) => {\n  const sizeSpecificStyles = {\n    small: `\n      font-size: 1rem;\n      border-radius: 0.25rem;\n      padding: 0.5rem 1rem;\n      margin: 0.5rem;\n    `,\n    medium: `\n      font-size: 1.5rem;\n      border-radius: 0.25rem;\n      padding: 0.75rem 1.5rem;\n      margin: 0.5rem;\n    `,\n    large: `\n      font-size: 2rem;\n      border-radius: 0.25rem;\n      padding: 1rem 1.75rem;\n      margin: 0.5rem;\n    `,\n  };\n  switch (inputSize) {\n    case 'small': return sizeSpecificStyles.small;\n    case 'medium': return sizeSpecificStyles.medium;\n    case 'large': return sizeSpecificStyles.large;\n    default: return sizeSpecificStyles.small;\n  }\n};\n"
  },
  {
    "path": "src/web-check-live/styles/globals.tsx",
    "content": "import { Global, css } from '@emotion/react';\n\nconst GlobalStyles = () => (\n  <Global\n    styles={css`\n    @font-face {\n      font-family: PTMono;\n      font-style: normal;\n      font-weight: 400;\n      src: url('/fonts/PTMono.ttf') format('ttf');\n    }\n    body, div, a, p, span, ul, li, small, h1, h2, h3, h4, button, section {\n      font-family: PTMono;\n      color: #fff;\n    }\n    #fancy-background p span {\n      color: transparent;\n    }\n    `}\n  />\n);\n\nexport default GlobalStyles;\n"
  },
  {
    "path": "src/web-check-live/styles/index.css",
    "content": "@font-face {\n  font-family: 'PTMono';\n  src: url('/fonts/PTMono-Regular.ttf') format('truetype');\n  font-weight: normal;\n  font-style: normal;\n}\n\nhtml {\n  scroll-behavior: smooth;\n}\n\nbody {\n  margin: 0;\n  font-family: 'PTMono', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n    sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  background: #141517;\n  color: #fff;\n}\n\ncode {\n  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n    monospace;\n}\n#fancy-background { color: var(--background, #141517); }\n\n\n::selection {\n  background: var(--primary, #d6fb41);\n  color: var(--background, #141517);\n}\n\n\nsvg.rsm-svg {\n  background: #141517;\n  border-radius: 6px;\n}\n\n:root {\n  --toastify-color-dark: #111927 !important;\n  --toastify-color-info: #04e4f4 !important;\n  --toastify-color-success: #20e253 !important;\n  --toastify-color-warning: #f6f000 !important;\n  --toastify-color-error: #f80363 !important;\n}\n\n#fancy-background {\n  position: absolute;\n}\n"
  },
  {
    "path": "src/web-check-live/styles/typography.ts",
    "content": "\nexport const TextSizes = {\n  xSmall: '0.75rem',\n  small: '1rem',\n  medium: '1.5rem',\n  large: '2rem',\n  xLarge: '3rem',\n  xxLarge: '4rem',\n};\n\nexport const TextReset = `\n  font-size: ${TextSizes.small};\n  font-family: PTMono, Helvetica, Arial, sans-serif;\n  font-weight: 400;\n  font-style: normal;\n  font-stretch: normal;\n  line-height: normal;\n  letter-spacing: 0.38px;\n  margin: 0;\n  padding: 0;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n`;\n"
  },
  {
    "path": "src/web-check-live/typings/file-types.d.ts",
    "content": "declare module \"*.ttf\"\n"
  },
  {
    "path": "src/web-check-live/typings/react-simple-maps.d.ts",
    "content": "declare module 'react-simple-maps';\n"
  },
  {
    "path": "src/web-check-live/utils/address-type-checker.ts",
    "content": "/** \n * Helper functions to determine if a string is a valid web address,\n * and what type of address it is: URL, IPv4, IPv6 or none of those.\n */\n\nexport type AddressType = 'ipV4' | 'ipV6' | 'url' | 'err' | 'empt';\n\n/* Checks if a given string looks like a URL */\nconst isUrl = (value: string):boolean => {\n  var urlPattern = new RegExp('^(https?:\\\\/\\\\/)?'+ // validate protocol\n    '((([a-z\\\\d]([a-z\\\\d-]*[a-z\\\\d])*)\\\\.)+[a-z]{2,}|'+ // validate domain name\n    '((\\\\d{1,3}\\\\.){3}\\\\d{1,3}))'+ // validate OR ip (v4) address\n    '(\\\\:\\\\d+)?(\\\\/[-a-z\\\\d%_.~+]*)*'+ // validate port and path\n    '(\\\\?[;&a-z\\\\d%_.~+=-]*)?'+ // validate query string\n    '(\\\\#[-a-z\\\\d_]*)?$','i'); // validate fragment locator\n  return urlPattern.test(value);\n};\n\n// /* Checks if a given string looks like a URL */\n// const isUrl = (value: string):boolean => {\n//   const urlRegex= new RegExp(''\n//     // + /(?:(?:(https?|ftp):)?\\/\\/)/.source\n//     + /(?:([^:\\n\\r]+):([^@\\n\\r]+)@)?/.source\n//     + /(?:(?:www\\.)?([^/\\n\\r]+))/.source\n//     + /(\\/[^?\\n\\r]+)?/.source\n//     + /(\\?[^#\\n\\r]*)?/.source\n//     + /(#?[^\\n\\r]*)?/.source\n//   );\n//   return urlRegex.test(value);\n// };\n\n/* Checks if a given string looks like an IP Version 4 Address */\nconst isIpV4 = (value: string): boolean => {\n  const ipPart = '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)';\n  const ipV4Regex = new RegExp(`^${ipPart}.${ipPart}.${ipPart}.${ipPart}$`);\n  return ipV4Regex.test(value);\n};\n\n/* Checks if a given string looks like an IP Version 6 Address */\nconst isIPv6 = (value: string): boolean => {\n  const components = value.split(':');\n  \n  if ((components.length < 2 || components.length > 8) ||\n    ((components[0] !== '' || components[1] !== '')\n      && !components[0].match(/^[\\da-f]{1,4}/i))\n  ) return false;\n\n  let numberOfZeroCompressions = 0;\n  for (let i = 1; i < components.length; ++i) {\n    if (components[i] === '') {\n      ++numberOfZeroCompressions;\n      if (numberOfZeroCompressions > 1) return false;\n      continue;\n    }\n    if (!components[i].match(/^[\\da-f]{1,4}/i)) return false;\n  }\n  return true;\n};\n\nconst isShort = (value: string): boolean => {\n  return (!value || value.length <=3);\n};\n\n/* Returns the address type for a given address */\nexport const determineAddressType = (address: string | undefined): AddressType => {\n  if (!address) return 'err';\n  if (isShort(address)) return 'empt';\n  if (isUrl(address)) return 'url';\n  if (isIpV4(address)) return 'ipV4';\n  if (isIPv6(address)) return 'ipV6';\n  return 'err';\n};\n"
  },
  {
    "path": "src/web-check-live/utils/docs.ts",
    "content": "export interface Doc {\n  id: string;\n  title: string;\n  description: string;\n  use: string;\n  resources: string[] | { title: string, link: string}[];\n  screenshot?: string;\n}\n\nconst docs: Doc[] = [\n  {\n    id: \"get-ip\",\n    title: \"IP Info\",\n    description:\n      \"An IP address (Internet Protocol address) is a numerical label assigned to each device connected to a network / the internet. The IP associated with a given domain can be found by querying the Domain Name System (DNS) for the domain's A (address) record.\",\n    use: \"Finding the IP of a given server is the first step to conducting further investigations, as it allows us to probe the server for additional info. Including creating a detailed map of a target's network infrastructure, pinpointing the physical location of a server, identifying the hosting service, and even discovering other domains that are hosted on the same IP address.\",\n    resources: [\n      { title: 'Understanding IP Addresses', link: 'https://www.digitalocean.com/community/tutorials/understanding-ip-addresses-subnets-and-cidr-notation-for-networking'},\n      { title: 'IP Addresses - Wiki', link: 'https://en.wikipedia.org/wiki/IP_address'},\n      { title: 'RFC-791 Internet Protocol', link: 'https://tools.ietf.org/html/rfc791'},\n      { title: 'whatismyipaddress.com', link: 'https://whatismyipaddress.com/'},\n    ],\n  },\n  {\n    id: \"ssl\",\n    title: \"SSL Chain\",\n    description:\n    \"SSL certificates are digital certificates that authenticate the identity of a website or server, enable secure encrypted communication (HTTPS), and establish trust between clients and servers. A valid SSL certificate is required for a website to be able to use the HTTPS protocol, and encrypt user + site data in transit. SSL certificates are issued by Certificate Authorities (CAs), which are trusted third parties that verify the identity and legitimacy of the certificate holder.\",  \n    use: \"SSL certificates not only provide the assurance that data transmission to and from the website is secure, but they also provide valuable OSINT data. Information from an SSL certificate can include the issuing authority, the domain name, its validity period, and sometimes even organization details. This can be useful for verifying the authenticity of a website, understanding its security setup, or even for discovering associated subdomains or other services.\",\n    resources: [\n      { title: 'TLS - Wiki', link: 'https://en.wikipedia.org/wiki/Transport_Layer_Security'},\n      { title: 'What is SSL (via Cloudflare learning)', link: 'https://www.cloudflare.com/learning/ssl/what-is-ssl/'},\n      { title: 'RFC-8446 - TLS', link: 'https://tools.ietf.org/html/rfc8446'},\n      { title: 'SSL Checker', link: 'https://www.sslshopper.com/ssl-checker.html'},\n    ],\n    screenshot: 'https://i.ibb.co/kB7LsV1/wc-ssl.png',\n  },\n  {\n    id: \"dns\",\n    title: \"DNS Records\",\n    description:\n      \"This task involves looking up the DNS records associated with a specific domain. DNS is a system that translates human-readable domain names into IP addresses that computers use to communicate. Various types of DNS records exist, including A (address), MX (mail exchange), NS (name server), CNAME (canonical name), and TXT (text), among others.\",\n    use: \"Extracting DNS records can provide a wealth of information in an OSINT investigation. For example, A and AAAA records can disclose IP addresses associated with a domain, potentially revealing the location of servers. MX records can give clues about a domain's email provider. TXT records are often used for various administrative purposes and can sometimes inadvertently leak internal information. Understanding a domain's DNS setup can also be useful in understanding how its online infrastructure is built and managed.\",\n    resources: [\n      { title: 'What are DNS records? (via Cloudflare learning)', link: 'https://www.cloudflare.com/learning/dns/dns-records/'},\n      { title: 'DNS Record Types', link: 'https://en.wikipedia.org/wiki/List_of_DNS_record_types'},\n      { title: 'RFC-1035 - DNS', link: 'https://tools.ietf.org/html/rfc1035'},\n      { title: 'DNS Lookup (via MxToolbox)', link: 'https://mxtoolbox.com/DNSLookup.aspx'},\n    ],\n    screenshot: 'https://i.ibb.co/7Q1kMwM/wc-dns.png',\n  },\n  {\n    id: \"cookies\",\n    title: \"Cookies\",\n    description:\n      \"The Cookies task involves examining the HTTP cookies set by the target website. Cookies are small pieces of data stored on the user's computer by the web browser while browsing a website. They hold a modest amount of data specific to a particular client and website, such as site preferences, the state of the user's session, or tracking information.\",\n    use: \"Cookies can disclose information about how the website tracks and interacts with its users. For instance, session cookies can reveal how user sessions are managed, and tracking cookies can hint at what kind of tracking or analytics frameworks are being used. Additionally, examining cookie policies and practices can offer insights into the site's security settings and compliance with privacy regulations.\",\n    resources: [\n      { title: 'HTTP Cookie Docs (Mozilla)', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies' },\n      { title: 'What are Cookies (via Cloudflare Learning)', link: 'https://www.cloudflare.com/learning/privacy/what-are-cookies/' },\n      { title: 'Testing for Cookie Attributes (OWASP)', link: 'https://owasp.org/www-project-web-security-testing-guide/v42/4-Web_Application_Security_Testing/06-Session_Management_Testing/02-Testing_for_Cookies_Attributes' },\n      { title: 'RFC-6265 - Coolies', link: 'https://tools.ietf.org/html/rfc6265' },\n    ],\n    screenshot: 'https://i.ibb.co/TTQ6DtP/wc-cookies.png',\n  },\n  {\n    id: \"robots-txt\",\n    title: \"Crawl Rules\",\n    description:\n      \"Robots.txt is a file found (usually) at the root of a domain, and is used to implement the Robots Exclusion Protocol (REP) to indicate which pages should be ignored by which crawlers and bots. It's good practice to avoid search engine crawlers from over-loading your site, but should not be used to keep pages out of search results (use the noindex meta tag or header instead).\",\n    use: \"It's often useful to check the robots.txt file during an investigation, as it can sometimes disclose the directories and pages that the site owner doesn't want to be indexed, potentially because they contain sensitive information, or reveal the existence of otherwise hidden or unlinked directories. Additionally, understanding crawl rules may offer insights into a website's SEO strategies.\",\n    resources: [\n      { title: 'Google Search Docs - Robots.txt', link: 'https://developers.google.com/search/docs/advanced/robots/intro' },\n      { title: 'Learn about robots.txt (via Moz.com)', link: 'https://moz.com/learn/seo/robotstxt' },\n      { title: 'RFC-9309 -  Robots Exclusion Protocol', link: 'https://datatracker.ietf.org/doc/rfc9309/' },\n      { title: 'Robots.txt - wiki', link: 'https://en.wikipedia.org/wiki/Robots_exclusion_standard' },\n    ],\n    screenshot: 'https://i.ibb.co/KwQCjPf/wc-robots.png',\n  },\n  {\n    id: \"headers\",\n    title: \"Headers\",\n    description:\n      \"The Headers task involves extracting and interpreting the HTTP headers sent by the target website during the request-response cycle. HTTP headers are key-value pairs sent at the start of an HTTP response, or before the actual data. Headers contain important directives for how to handle the data being transferred, including cache policies, content types, encoding, server information, security policies, and more.\",\n    use: \"Analyzing HTTP headers can provide significant insights in an OSINT investigation. Headers can reveal specific server configurations, chosen technologies, caching directives, and various security settings. This information can help to determine a website's underlying technology stack, server-side security measures, potential vulnerabilities, and general operational practices.\",\n    resources: [\n      { title: 'HTTP Headers - Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers' },\n      { title: 'RFC-7231 Section 7 - Headers', link: 'https://datatracker.ietf.org/doc/html/rfc7231#section-7' },\n      { title: 'List of header response fields', link: 'https://en.wikipedia.org/wiki/List_of_HTTP_header_fields' },\n      { title: 'OWASP Secure Headers Project', link: 'https://owasp.org/www-project-secure-headers/' },\n    ],\n    screenshot: 'https://i.ibb.co/t3xcwP1/wc-headers.png',\n  },\n  {\n    id: \"quality\",\n    title: \"Quality Metrics\",\n    description:\n      \"Using Lighthouse, the Quality Metrics task measures the performance, accessibility, best practices, and SEO of the target website. This returns a simple checklist of 100 core metrics, along with a score for each category, to gauge the overall quality of a given site.\",\n    use: \"Useful for assessing a site's technical health, SEO issues, identify vulnerabilities, and ensure compliance with standards.\",\n    resources: [\n      { title: 'Lighthouse Docs', link: 'https://developer.chrome.com/docs/lighthouse/' },\n      { title: 'Google Page Speed Tools', link: 'https://developers.google.com/speed' },\n      { title: 'W3 Accessibility Tools', link: 'https://www.w3.org/WAI/test-evaluate/' },\n      { title: 'Google Search Console', link: 'https://search.google.com/search-console' },\n      { title: 'SEO Checker', link: 'https://www.seobility.net/en/seocheck/' },\n      { title: 'PWA Builder', link: 'https://www.pwabuilder.com/' },\n    ],\n    screenshot: 'https://i.ibb.co/Kqg8rx7/wc-quality.png',\n  },\n  {\n    id: \"location\",\n    title: \"Server Location\",\n    description:\n      \"The Server Location task determines the physical location of the server hosting a given website based on its IP address. This is done by looking up the IP in a location database, which maps the IP to a lat + long of known data centers and ISPs. From the latitude and longitude, it's then possible to show additional contextual info, like a pin on the map, along with address, flag, time zone, currency, etc.\",\n    use: \"Knowing the server location is a good first step in better understanding a website. For site owners this aids in optimizing content delivery, ensuring compliance with data residency requirements, and identifying potential latency issues that may impact user experience in specific geographical regions. And for security researcher, assess the risk posed by specific regions or jurisdictions regarding cyber threats and regulations.\",\n    resources: [\n      { title: 'IP Locator', link: 'https://geobytes.com/iplocator/' },\n      { title: 'Internet Geolocation - Wiki', link: 'https://en.wikipedia.org/wiki/Internet_geolocation' },\n    ],\n    screenshot: 'https://i.ibb.co/cXH2hfR/wc-location.png',\n  },\n  {\n    id: \"hosts\",\n    title: \"Associated Hosts\",\n    description:\n      \"This task involves identifying and listing all domains and subdomains (hostnames) that are associated with the website's primary domain. This process often involves DNS enumeration to discover any linked domains and hostnames, as well as looking at known DNS records.\",\n    use: \"During an investigation, understanding the full scope of a target's web presence is critical. Associated domains could lead to uncovering related projects, backup sites, development/test sites, or services linked to the main site. These can sometimes provide additional information or potential security vulnerabilities. A comprehensive list of associated domains and hostnames can also give an overview of the organization's structure and online footprint.\",\n    resources: [\n      { title: 'DNS Enumeration - Wiki', link: 'https://en.wikipedia.org/wiki/DNS_enumeration' },\n      { title: 'OWASP - Enumerate Applications on Webserver', link: 'https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/01-Information_Gathering/04-Enumerate_Applications_on_Webserver' },\n      { title: 'DNS Enumeration - DNS Dumpster', link: 'https://dnsdumpster.com/' },\n      { title: 'Subdomain Finder', link: 'https://subdomainfinder.c99.nl/' },\n    ],\n    screenshot: 'https://i.ibb.co/25j1sT7/wc-hosts.png',\n  },\n  {\n    id: \"redirects\",\n    title: \"Redirect Chain\",\n    description:\n      \"This task traces the sequence of HTTP redirects that occur from the original URL to the final destination URL. An HTTP redirect is a response with a status code that advises the client to go to another URL. Redirects can occur for several reasons, such as URL normalization (directing to the www version of the site), enforcing HTTPS, URL shorteners, or forwarding users to a new site location.\",\n    use: \"Understanding the redirect chain can be useful for several reasons. From a security perspective, long or complicated redirect chains can be a sign of potential security risks, such as unencrypted redirects in the chain. Additionally, redirects can impact website performance and SEO, as each redirect introduces additional round-trip-time (RTT). For OSINT, understanding the redirect chain can help identify relationships between different domains or reveal the use of certain technologies or hosting providers.\",\n    resources: [\n      { title: 'HTTP Redirects - MDN', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections' },\n      { title: 'URL Redirection - Wiki', link: 'https://en.wikipedia.org/wiki/URL_redirection' },\n      { title: '301 Redirects explained', link: 'https://ahrefs.com/blog/301-redirects/' },\n    ],\n    screenshot: 'https://i.ibb.co/hVVrmwh/wc-redirects.png',\n  },\n  {\n    id: \"txt-records\",\n    title: \"TXT Records\",\n    description:\n      \"TXT records are a type of DNS record that provides text information to sources outside your domain. They can be used for a variety of purposes, such as verifying domain ownership, ensuring email security, and even preventing unauthorized changes to your website.\",\n    use: \"The TXT records often reveal which external services and technologies are being used with a given domain. They may reveal details about the domain's email configuration, the use of specific services like Google Workspace or Microsoft 365, or security measures in place such as SPF and DKIM. Understanding these details can give an insight into the technologies used by the organization, their email security practices, and potential vulnerabilities.\",\n    resources: [\n      { title: 'TXT Records (via Cloudflare Learning)', link: 'https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/' },\n      { title: 'TXT Records - Wiki', link: 'https://en.wikipedia.org/wiki/TXT_record' },\n      { title: 'RFC-1464 - TXT Records', link: 'https://datatracker.ietf.org/doc/html/rfc1464' },\n      { title: 'TXT Record Lookup (via MxToolbox)', link: 'https://mxtoolbox.com/TXTLookup.aspx' },\n    ],\n    screenshot: 'https://i.ibb.co/wyt21QN/wc-txt-records.png',\n  },\n  {\n    id: \"status\",\n    title: \"Server Status\",\n    description: \"Checks if a server is online and responding to requests.\",\n    use: \"\",\n    resources: [\n    ],\n    screenshot: 'https://i.ibb.co/V9CNLBK/wc-status.png',\n  },\n  {\n    id: \"ports\",\n    title: \"Open Ports\",\n    description:\n      \"Open ports on a server are endpoints of communication which are available for establishing connections with clients. Each port corresponds to a specific service or protocol, such as HTTP (port 80), HTTPS (port 443), FTP (port 21), etc. The open ports on a server can be determined using techniques such as port scanning.\",\n    use: \"Knowing which ports are open on a server can provide information about the services running on that server, useful for understanding the potential vulnerabilities of the system, or for understanding the nature of the services the server is providing.\",\n    resources: [\n      { title: 'List of TCP & UDP Port Numbers', link: 'https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers' },\n      { title: 'NMAP - Port Scanning Basics', link: 'https://nmap.org/book/man-port-scanning-basics.html' },\n    ],\n    screenshot: 'https://i.ibb.co/F8D1hmf/wc-ports.png',\n  },\n  {\n    id: \"trace-route\",\n    title: \"Traceroute\",\n    description:\n      \"Traceroute is a network diagnostic tool used to track in real-time the pathway taken by a packet of information from one system to another. It records each hop along the route, providing details about the IPs of routers and the delay at each point.\",\n    use: \"In OSINT investigations, traceroute can provide insights about the routing paths and geography of the network infrastructure supporting a website or service. This can help to identify network bottlenecks, potential censorship or manipulation of network traffic, and give an overall sense of the network's structure and efficiency. Additionally, the IP addresses collected during the traceroute may provide additional points of inquiry for further OSINT investigation.\",\n    resources: [\n      \"https://www.cloudflare.com/learning/network-layer/what-is-traceroute/\",\n      \"https://tools.ietf.org/html/rfc1393\",\n      \"https://en.wikipedia.org/wiki/Traceroute\",\n      \"https://www.ripe.net/publications/docs/ripe-611\",\n    ],\n    screenshot: 'https://i.ibb.co/M59qgxP/wc-trace-route.png',\n  },\n  {\n    id: \"carbon\",\n    title: \"Carbon Footprint\",\n    description:\n      \"This task calculates the estimated carbon footprint of a website. It's based on the amount of data being transferred and processed, and the energy usage of the servers that host and deliver the website. The larger the website and the more complex its features, the higher its carbon footprint is likely to be.\",\n    use: \"From an OSINT perspective, understanding a website's carbon footprint doesn't directly provide insights into its internal workings or the organization behind it. However, it can still be valuable data in broader analyses, especially in contexts where environmental impact is a consideration. For example, it can be useful for activists, researchers, or ethical hackers who are interested in the sustainability of digital infrastructure, and who want to hold organizations accountable for their environmental impact.\",\n    resources: [\n      { title: 'WebsiteCarbon - Carbon Calculator', link: 'https://www.websitecarbon.com/' },\n      { title: 'The Green Web Foundation', link: 'https://www.thegreenwebfoundation.org/' },\n      { title: 'The Eco Friendly Web Alliance', link: 'https://ecofriendlyweb.org/' },\n      { title: 'Reset.org', link: 'https://en.reset.org/' },\n      { title: 'Your website is killing the planet - via Wired', link: 'https://www.wired.co.uk/article/internet-carbon-footprint' },\n    ],\n    screenshot: 'https://i.ibb.co/5v6fSyw/Screenshot-from-2023-07-29-19-07-50.png',\n  },\n  {\n    id: \"server-info\",\n    title: \"Server Info\",\n    description:\n      \"This task retrieves various pieces of information about the server hosting the target website. This can include the server type (e.g., Apache, Nginx), the hosting provider, the Autonomous System Number (ASN), and more. The information is usually obtained through a combination of IP address lookups and analysis of HTTP response headers.\",\n    use: \"In an OSINT context, server information can provide valuable clues about the organization behind a website. For instance, the choice of hosting provider could suggest the geographical region in which the organization operates, while the server type could hint at the technologies used by the organization. The ASN could also be used to find other domains hosted by the same organization.\",\n    resources: [\n      \"https://en.wikipedia.org/wiki/List_of_HTTP_header_fields\",\n      \"https://en.wikipedia.org/wiki/Autonomous_system_(Internet)\",\n      \"https://tools.ietf.org/html/rfc7231#section-7.4.2\",\n      \"https://builtwith.com/\",\n    ],\n    screenshot: 'https://i.ibb.co/Mk1jx32/wc-server.png',\n  },\n  {\n    id: \"domain\",\n    title: \"Whois Lookup\",\n    description:\n      \"This task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.\",\n    use: \"In an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.\",\n    resources: [\n      \"https://en.wikipedia.org/wiki/WHOIS\",\n      \"https://www.icann.org/resources/pages/whois-2018-01-17-en\",\n      \"https://whois.domaintools.com/\",\n    ],\n    screenshot: 'https://i.ibb.co/89WLp14/wc-domain.png',\n  },\n  {\n    id: \"whois\",\n    title: \"Domain Info\",\n    description:\n      \"This task retrieves Whois records for the target domain. Whois records are a rich source of information, including the name and contact information of the domain registrant, the domain's creation and expiration dates, the domain's nameservers, and more. The information is usually obtained through a query to a Whois database server.\",\n    use: \"In an OSINT context, Whois records can provide valuable clues about the entity behind a website. They can show when the domain was first registered and when it's set to expire, which could provide insights into the operational timeline of the entity. The contact information, though often redacted or anonymized, can sometimes lead to additional avenues of investigation. The nameservers could also be used to link together multiple domains owned by the same entity.\",\n    resources: [\n      \"https://en.wikipedia.org/wiki/WHOIS\",\n      \"https://www.icann.org/resources/pages/whois-2018-01-17-en\",\n      \"https://whois.domaintools.com/\",\n    ],\n    screenshot: 'https://i.ibb.co/89WLp14/wc-domain.png',\n  },\n  {\n    id: \"dnssec\",\n    title: \"DNS Security Extensions\",\n    description:\n      \"Without DNSSEC, it's possible for MITM attackers to spoof records and lead users to phishing sites. This is because the DNS system includes no built-in methods to verify that the response to the request was not forged, or that any other part of the process wasn’t interrupted by an attacker. The DNS Security Extensions (DNSSEC) secures DNS lookups by signing your DNS records using public keys, so browsers can detect if the response has been tampered with. Another solution to this issue is DoH (DNS over HTTPS) and DoT (DNS over TLD).\",\n    use: \"DNSSEC information provides insight into an organization's level of cybersecurity maturity and potential vulnerabilities, particularly around DNS spoofing and cache poisoning. If no DNS secururity (DNSSEC, DoH, DoT, etc) is implemented, this may provide an entry point for an attacker.\",\n    resources: [\n      \"https://dnssec-analyzer.verisignlabs.com/\",\n      \"https://www.cloudflare.com/dns/dnssec/how-dnssec-works/\",\n      \"https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions\",\n      \"https://www.icann.org/resources/pages/dnssec-what-is-it-why-important-2019-03-05-en\",\n      \"https://support.google.com/domains/answer/6147083\",\n      \"https://www.internetsociety.org/resources/deploy360/2013/dnssec-test-sites/\",\n    ],\n    screenshot: 'https://i.ibb.co/J54zVmQ/wc-dnssec.png',\n  },\n  {\n    id: \"features\",\n    title: \"Site Features\",\n    description: 'Checks which core features are present on a site. If a feature as marked as dead, that means it\\'s not being actively used at load time',\n    use: \"This is useful to understand what a site is capable of, and what technologies to look for\",\n    resources: [],\n    screenshot: 'https://i.ibb.co/gP4P6kp/wc-features.png',\n  },\n  {\n    id: \"hsts\",\n    title: \"HTTP Strict Transport Security\",\n    description: 'HTTP Strict Transport Security (HSTS) is a web security policy '\n    +'mechanism that helps protect websites against protocol downgrade attacks and '\n    + 'cookie hijacking. A website can be included in the HSTS preload list by '\n    + 'conforming to a set of requirements and then submitting itself to the list.',\n    use: `There are several reasons why it's important for a site to be HSTS enabled:\n  1. User bookmarks or manually types http://example.com and is subject to a man-in-the-middle attacker\n    HSTS automatically redirects HTTP requests to HTTPS for the target domain\n  2. Web application that is intended to be purely HTTPS inadvertently contains HTTP links or serves content over HTTP\n    HSTS automatically redirects HTTP requests to HTTPS for the target domain\n  3. A man-in-the-middle attacker attempts to intercept traffic from a victim user using an invalid certificate and hopes the user will accept the bad certificate\n    HSTS does not allow a user to override the invalid certificate message\n    `,\n    resources: [\n      'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security',\n      'https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html',\n      'https://hstspreload.org/'\n    ],\n    screenshot: 'https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png',\n  },\n  {\n    id: 'dns-server',\n    title: 'DNS Server',\n    description: 'This check determines the DNS server(s) that the requested URL / IP resolves to. Also fires off a rudimentary check to see if the DNS server supports DoH, and weather it\\'s vulnerable to DNS cache poisoning.',\n    use: '',\n    resources: [],\n    screenshot: 'https://i.ibb.co/tKpL8F9/Screenshot-from-2023-08-12-15-43-12.png',\n  },\n  {\n    id: 'tech-stack',\n    title: 'Tech Stack',\n    description: 'Checks what technologies a site is built with. '\n    + 'This is done by fetching and parsing the site, then comparing it against a bit list of RegEx maintained by Wappalyzer to identify the unique fingerprints that different technologies leave.',\n    use: 'Identifying a website\\'s tech stack aids in evaluating its security by exposing potential vulnerabilities, '\n    + 'informs competitive analyses and development decisions, and can guide tailored marketing strategies. '\n    + 'Ethical application of this knowledge is crucial to avoid harmful activities like data theft or unauthorized intrusion.',\n    resources: [\n      { title: 'Wappalyzer fingerprints', link: 'https://github.com/wappalyzer/wappalyzer/tree/master/src/technologies'},\n      { title: 'BuiltWith - Check what tech a site is using', link: 'https://builtwith.com/'},\n    ],\n    screenshot: 'https://i.ibb.co/bBQSQNz/Screenshot-from-2023-08-12-15-43-46.png',\n  },\n  {\n    id: 'sitemap',\n    title: 'Listed Pages',\n    description: 'This job finds and parses a site\\'s listed sitemap. This file lists public sub-pages on the site, which the author wishes to be crawled by search engines. Sitemaps help with SEO, but are also useful for seeing all a sites public content at a glance.',\n    use: 'Understand the structure of a site\\'s public-facing content, and for site-owners, check that you\\'re site\\'s sitemap is accessible, parsable and contains everything you wish it to.',\n    resources: [\n      { title: 'Learn about Sitemaps', link: 'https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview'},\n      { title: 'Sitemap XML spec', link: 'https://www.sitemaps.org/protocol.html'},\n      { title: 'Sitemap tutorial', link: 'https://www.conductor.com/academy/xml-sitemap/'},\n    ],\n    screenshot: 'https://i.ibb.co/GtrCQYq/Screenshot-from-2023-07-21-12-28-38.png',\n  },\n  {\n    id: 'security-txt',\n    title: 'Security.txt',\n    description: \"The security.txt file tells researchers how they can responsibly disclose any security issues found on your site. \"\n    + \"The standard was proposed in RFC 9116, and specifies that this file should include a point of contact (email address), \"\n    + \"as well as optionally other info, like a link to the security disclosure policy, PGP key, proffered language, policy expiry and more. \"\n    + \"The file should be located at the root of your domain, either at /security.txt or /.well-known/security.txt.\",\n    use: \"This is important, as without a defined point of contact a security researcher may be unable to report a critical security issue, \"\n    + \"or may use insecure or possibly public channels to do so. From an OSINT perspective, you may also glean info about a site including \"\n    + \"their posture on security, their CSAF provider, and meta data from the PGP public key.\",\n    resources: [\n      { title: 'securitytxt.org', link: 'https://securitytxt.org/'},\n      { title: 'RFC-9116 Proposal', link: 'https://datatracker.ietf.org/doc/html/rfc9116'},\n      { title: 'RFC-9116 History', link: 'https://datatracker.ietf.org/doc/rfc9116/'},\n      { title: 'Security.txt (Wikipedia)', link: 'https://en.wikipedia.org/wiki/Security.txt'},\n      { title: 'Example security.txt (Cloudflare)', link: 'https://www.cloudflare.com/.well-known/security.txt'},\n      { title: 'Tutorial for creating security.txt (Pieter Bakker)', link: 'https://pieterbakker.com/implementing-security-txt/'},\n    ],\n    screenshot: 'https://i.ibb.co/tq1FT5r/Screenshot-from-2023-07-24-20-31-21.png',\n  },\n  {\n    id: 'linked-pages',\n    title: 'Linked Pages',\n    description: 'Displays all internal and external links found on a site, identified by the href attributes attached to anchor elements.',\n    use: \"For site owners, this is useful for diagnosing SEO issues, improving the site structure, understanding how content is inter-connected. External links can show partnerships, dependencies, and potential reputation risks. \" +\n    \"From a security standpoint, the outbound links can help identify any potential malicious or compromised sites the website is unknowingly linking to. Analyzing internal links can aid in understanding the site's structure and potentially uncover hidden or vulnerable pages which are not intended to be public. \" +\n    \"And for an OSINT investigator, it can aid in building a comprehensive understanding of the target, uncovering related entities, resources, or even potential hidden parts of the site.\",\n    resources: [\n      { title: 'W3C Link Checker', link: 'https://validator.w3.org/checklink'},\n    ],\n    screenshot: 'https://i.ibb.co/LtK14XR/Screenshot-from-2023-07-29-11-16-44.png',\n  },\n  {\n    id: 'social-tags',\n    title: 'Social Tags',\n    description: 'Websites can include certain meta tags, that tell search engines and social media platforms what info to display. This usually includes a title, description, thumbnail, keywords, author, social accounts, etc.',\n    use: 'Adding this data to your site will boost SEO, and as an OSINT researcher it can be useful to understand how a given web app describes itself',\n    resources: [\n      { title: 'SocialSharePreview.com', link: 'https://socialsharepreview.com/'},\n      { title: 'The guide to social meta tags', link: 'https://css-tricks.com/essential-meta-tags-social-media/'},\n      { title: 'Web.dev metadata tags', link: 'https://web.dev/learn/html/metadata/'},\n      { title: 'Open Graph Protocol', link: 'https://ogp.me/'},\n      { title: 'Twitter Cards', link: 'https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards'},\n      { title: 'Facebook Open Graph', link: 'https://developers.facebook.com/docs/sharing/webmasters'},\n    ],\n    screenshot: 'https://i.ibb.co/4srTT1w/Screenshot-from-2023-07-29-11-15-27.png',\n  },\n  {\n    id: 'mail-config',\n    title: 'Email Configuration',\n    description: \"DMARC (Domain-based Message Authentication, Reporting & Conformance): DMARC is an email authentication protocol that works with SPF and DKIM to prevent email spoofing and phishing. It allows domain owners to specify how to handle unauthenticated mail via a published policy in DNS, and provides a way for receiving mail servers to send feedback about emails' compliance to the sender. \" +\n    \"BIMI (Brand Indicators for Message Identification): BIMI is an emerging email standard that enables organizations to display a logo in their customers' email clients automatically. BIMI ties the logo to the domain's DMARC record, providing another level of visual assurance to recipients that the email is legitimate. \" +\n    \"DKIM (DomainKeys Identified Mail): DKIM is an email security standard designed to make sure that messages were not altered in transit between the sending and recipient servers. It uses digital signatures linked to the domain of the sender to verify the sender and ensure message integrity. \" +\n    \"SPF (Sender Policy Framework): SPF is an email authentication method designed to prevent email spoofing. It specifies which mail servers are authorized to send email on behalf of a domain by creating a DNS record. This helps protect against spam by providing a way for receiving mail servers to check that incoming mail from a domain comes from a host authorized by that domain's administrators.\",\n    use: \"This information is helpful for researchers as it helps assess a domain's email security posture, uncover potential vulnerabilities, and verify the legitimacy of emails for phishing detection. These details can also provide insight into the hosting environment, potential service providers, and the configuration patterns of a target organization, assisting in investigative efforts.\",\n    resources: [\n      { title: 'Intro to DMARC, DKIM, and SPF (via Cloudflare)', link: 'https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/' },\n      { title: 'EasyDMARC Domain Scanner', link: 'https://easydmarc.com/tools/domain-scanner' },\n      { title: 'MX Toolbox', link: 'https://mxtoolbox.com/' },\n      { title: 'RFC-7208 - SPF', link: 'https://datatracker.ietf.org/doc/html/rfc7208' },\n      { title: 'RFC-6376 - DKIM', link: 'https://datatracker.ietf.org/doc/html/rfc6376' },\n      { title: 'RFC-7489 - DMARC', link: 'https://datatracker.ietf.org/doc/html/rfc7489' },\n      { title: 'BIMI Group', link: 'https://bimigroup.org/' },\n    ],\n    screenshot: 'https://i.ibb.co/yqhwx5G/Screenshot-from-2023-07-29-18-22-20.png',\n  },\n  {\n    id: 'firewall',\n    title: 'Firewall Detection',\n    description: 'A WAF or web application firewall helps protect web applications by filtering and monitoring HTTP traffic between a web application and the Internet. It typically protects web applications from attacks such as cross-site forgery, cross-site-scripting (XSS), file inclusion, and SQL injection, among others.',\n    use: 'It\\'s useful to understand if a site is using a WAF, and which firewall software / service it is using, as this provides an insight into the sites protection against several attack vectors, but also may reveal vulnerabilities in the firewall itself.',\n    resources: [\n      { title: 'What is a WAF (via Cloudflare Learning)', link: 'https://www.cloudflare.com/learning/ddos/glossary/web-application-firewall-waf/' },\n      { title: 'OWASP - Web Application Firewalls', link: 'https://owasp.org/www-community/Web_Application_Firewall' },\n      { title: 'Web Application Firewall Best Practices', link: 'https://owasp.org/www-pdf-archive/Best_Practices_Guide_WAF_v104.en.pdf' },\n      { title: 'WAF - Wiki', link: 'https://en.wikipedia.org/wiki/Web_application_firewall' },\n    ],\n    screenshot: 'https://i.ibb.co/MfcxQt2/Screenshot-from-2023-08-12-15-40-52.png',\n  },\n  {\n    id: 'http-security',\n    title: 'HTTP Security Features',\n    description: 'Correctly configured security HTTP headers adds a layer of protection against common attacks to your site. The main headers to be aware of are: '\n    + 'HTTP Strict Transport Security (HSTS): Enforces the use of HTTPS, mitigating man-in-the-middle attacks and protocol downgrade attempts. '\n    + 'Content Security Policy (CSP): Constrains web page resources to prevent cross-site scripting and data injection attacks. '\n    + 'X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content type, curbing MIME-type confusion attacks. '\n    + 'X-Frame-Options: Protects users from clickjacking attacks by controlling whether a browser should render the page in a <frame>, <iframe>, <embed>, or <object>. ',\n    use: 'Reviewing security headers is important, as it offers insights into a site\\'s defensive posture and potential vulnerabilities, enabling proactive mitigation and ensuring compliance with security best practices.',\n    resources: [\n      { title: 'OWASP Secure Headers Project', link: 'https://owasp.org/www-project-secure-headers/'},\n      { title: 'HTTP Header Cheatsheet', link: 'https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html' },\n      { title: 'content-security-policy.com', link: 'https://content-security-policy.com/' },\n      { title: 'resourcepolicy.fyi', link: 'https://resourcepolicy.fyi/' },\n      { title: 'HTTP Security Headers', link: 'https://securityheaders.com/' },\n      { title: 'Mozilla Observatory', link: 'https://observatory.mozilla.org/' },\n      { title: 'CSP Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP' },\n      { title: 'HSTS Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security' },\n      { title: 'X-Content-Type-Options Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options' },\n      { title: 'X-Frame-Options Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options' },\n      { title: 'X-XSS-Protection Docs', link: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection' },\n    ],\n    screenshot: 'https://i.ibb.co/LP05HMV/Screenshot-from-2023-08-12-15-40-28.png',\n  },\n  {\n    id: 'archives',\n    title: 'Archive History',\n    description: 'Fetches full history of archives from the Wayback machine',\n    use: 'This is useful for understanding the history of a site, and how it has changed over time. It can also be useful for finding old versions of a site, or for finding content that has been removed.',\n    resources: [\n      { title: 'Wayback Machine', link: 'https://archive.org/web/'},\n    ],\n    screenshot: 'https://i.ibb.co/nB9szT1/Screenshot-from-2023-08-14-22-31-16.png',\n  },\n  {\n    id: 'rank',\n    title: 'Global Ranking',\n    description: 'This check shows the global rank of the requested site. This is only accurate for websites which are in the top 100 million list. We\\'re using data from the Tranco project (see below), which collates the top sites on the web from Umbrella, Majestic, Quantcast, the Chrome User Experience Report and Cloudflare Radar.',\n    use: 'Knowing a websites overall global rank can be useful for understanding the scale of the site, and for comparing it to other sites. It can also be useful for understanding the relative popularity of a site, and for identifying potential trends.',\n    resources: [\n      { title: 'Tranco List', link: 'https://tranco-list.eu/' },\n      { title: 'Tranco Research Paper', link: 'https://tranco-list.eu/assets/tranco-ndss19.pdf'},\n    ],\n    screenshot: 'https://i.ibb.co/nkbczgb/Screenshot-from-2023-08-14-22-02-40.png',\n  },\n  {\n    id: 'block-lists',\n    title: 'Block Detection',\n    description: 'Checks access to the URL using 10+ of the most popular privacy, malware and parental control blocking DNS servers.',\n    use: '',\n    resources: [\n      { title: 'ThreatJammer Lists', link: 'https://threatjammer.com/osint-lists'},\n    ],\n    screenshot: 'https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png',\n  },\n  {\n    id: 'threats',\n    title: 'Malware & Phishing Detection',\n    description: 'Checks if a site appears in several common malware and phishing lists, to determine it\\'s threat level.',\n    use: 'Knowing if a site is listed as a threat by any of these services can be useful for understanding the reputation of a site, and for identifying potential trends.',\n    resources: [\n      { title: 'URLHaus', link: 'https://urlhaus-api.abuse.ch/'},\n      { title: 'PhishTank', link: 'https://www.phishtank.com/'},\n    ],\n    screenshot: 'https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png',\n  },\n  {\n    id: 'tls-cipher-suites',\n    title: 'TLS Cipher Suites',\n    description: 'These are combinations of cryptographic algorithms used by the server to establish a secure connection. It includes the key exchange algorithm, bulk encryption algorithm, MAC algorithm, and PRF (pseudorandom function).',\n    use: 'This is important info to test for from a security perspective. Because a cipher suite is only as secure as the algorithms that it contains. If the version of encryption or authentication algorithm in a cipher suite have known vulnerabilities the cipher suite and TLS connection may then vulnerable to a downgrade or other attack',\n    resources: [\n      { title: 'sslscan2 CLI', link: 'https://github.com/rbsec/sslscan' },\n      { title: 'ssl-enum-ciphers (NPMAP script)', link: 'https://nmap.org/nsedoc/scripts/ssl-enum-ciphers.html' }\n    ],\n    screenshot: 'https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png',\n  },\n  {\n    id: 'tls-security-config',\n    title: 'TLS Security Config',\n    description: 'This uses guidelines from Mozilla\\'s TLS Observatory to check the security of the TLS configuration. It checks for bad configurations, which may leave the site vulnerable to attack, as well as giving advice on how to fix. It will also give suggestions around outdated and modern TLS configs',\n    use: 'Understanding issues with a site\\'s TLS configuration will help you address potential vulnerabilities, and ensure the site is using the latest and most secure TLS configuration.',\n    resources: [],\n    screenshot: 'https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png',\n  },\n  {\n    id: 'tls-client-support',\n    title: 'TLS Handshake Simulation',\n    description: 'This simulates how different clients (browsers, operating systems) would perform a TLS handshake with the server. It helps identify compatibility issues and insecure configurations.',\n    use: '',\n    resources: [\n      { title: 'TLS Handshakes (via Cloudflare Learning)', link: 'https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/' },\n      { title: 'SSL Test (via SSL Labs)', link: 'https://www.ssllabs.com/ssltest/' },\n    ],\n    screenshot: 'https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png',\n  },\n  {\n    id: 'screenshot',\n    title: 'Screenshot',\n    description: 'This check takes a screenshot of webpage that the requested URL / IP resolves to, and displays it.',\n    use: 'This may be useful to see what a given website looks like, free of the constraints of your browser, IP, or location.',\n    resources: [],\n    screenshot: 'https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png',\n  },\n];\n\nexport const featureIntro = [\n  'When conducting an OSINT investigation on a given website or host, there are several key areas to look at. Each of these are documented below, along with links to the tools and techniques you can use to gather the relevant information.',\n  'Web-Check can automate the process of gathering this data, but it will be up to you to interpret the results and draw conclusions.',\n];\n\nexport const about = [\n`Web-Check is a powerful all-in-one tool for discovering information about a website/host.\nThe core philosophy is simple: feed Web-Check a URL and let it gather, collate, and present a broad array of open data for you to delve into.`,\n\n`The report shines a spotlight onto potential attack vectors, existing security measures,\nand the web of connections within a site's architecture.\nThe results can also help optimizing server responses, configuring redirects,\nmanaging cookies, or fine-tuning DNS records for your site.`,\n\n`So, whether you're a developer, system administrator, security researcher, penetration\ntester or are just interested in discovering the underlying technologies of a given site \n- I'm sure you'll find this a useful addition to your toolbox.`,\n];\n\nexport const license = `The MIT License (MIT)\nCopyright (c) Alicia Sykes <alicia@omg.com> \n\nPermission is hereby granted, free of charge, to any person obtaining a copy \nof this software and associated documentation files (the \"Software\"), to deal \nin the Software without restriction, including without limitation the rights \nto use, copy, modify, merge, publish, distribute, sub-license, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished \nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included install \ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANT ABILITY, FITNESS FOR A\nPARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\nHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n`;\n\nexport const supportUs = [\n  \"Web-Check is free to use without restriction.\",\n  \"All the code is open source, so you're also free to deploy your own instance, as well as fork, modify and distribute the code in both private and commercial settings.\",\n  \"Running web-check does cost me a small amount of money each month, so if you're finding the app useful, consider <a href='https://github.com/sponsors/Lissy93'>sponsoring me on GitHub</a> if you're able to. Even just $1 or $2/month would be a huge help in supporting the ongoing project running costs.\",\n  \"Otherwise, there are other ways you can help out, like submitting or reviewing a pull request to the <a href='https://github.com/Lissy93/web-check'>GitHub repo</a>, upvoting us on <a href='https://www.producthunt.com/posts/web-check'>Product Hunt</a>, or by sharing with your network.\",\n  \"But don't feel obliged to do anything, as this app (and all my other projects) will always remain 100% free and open source, and I will do my best to ensure the managed instances remain up and available for as long as possible :)\",\n];\n\nexport const fairUse = [\n  'Please use this tool responsibly. Do not use it for hosts you do not have permission to scan. Do not use it as part of a scheme to attack or disrupt services.',\n  'Requests may be rate-limited to prevent abuse. If you need to make more bandwidth, please deploy your own instance.',\n  'There is no guarantee of uptime or availability. If you need to make sure the service is available, please deploy your own instance.',\n  'Please use fairly, as excessive use will quickly deplete the lambda function credits, making the service unavailable for others (and/or empty my bank account!).',\n];\n\nexport default docs;\n"
  },
  {
    "path": "src/web-check-live/utils/get-keys.ts",
    "content": "\nconst keys = {\n  shodan: import.meta.env.REACT_APP_SHODAN_API_KEY || \"default_value_if_not_set\",\n  whoApi: import.meta.env.REACT_APP_WHO_API_KEY || \"default_value_if_not_set\",\n  disableEverything: import.meta.env.VITE_DISABLE_EVERYTHING === 'true',\n};\n// const keys = process && process.env ? {\n//   shodan: process.env.REACT_APP_SHODAN_API_KEY,\n//   whoApi: process.env.REACT_APP_WHO_API_KEY,\n// } : {\n//   shodan: import.meta.env.REACT_APP_SHODAN_API_KEY || \"default_value_if_not_set\",\n//   whoApi: import.meta.env.REACT_APP_WHO_API_KEY || \"default_value_if_not_set\",\n// };\n\nexport default keys;\n"
  },
  {
    "path": "src/web-check-live/utils/result-processor.ts",
    "content": "import type { RowProps }  from 'web-check-live/components/Form/Row';\n\nexport interface ServerLocation {\n  city: string,\n  region: string,\n  country: string,\n  postCode: string,\n  regionCode: string,\n  countryCode: string,\n  coords: {\n    latitude: number,\n    longitude: number,\n  },\n  isp: string,\n  timezone: string,\n  languages: string,\n  currency: string,\n  currencyCode: string,\n  countryDomain: string,\n  countryAreaSize: number,\n  countryPopulation: number,\n};\n\nexport interface Whois {\n  created: string,\n  expires: string,\n  updated: string,\n  nameservers: string[],\n}\n\nexport const getLocation = (response: any): ServerLocation => {\n  return {\n    city: response.city,\n    region: response.region,\n    country: response.country_name,\n    postCode: response.postal,\n    regionCode: response.region_code,\n    countryCode: response.country_code,\n    coords: {\n      latitude: response.latitude,\n      longitude: response.longitude,\n    },\n    isp: response.org,\n    timezone: response.timezone,\n    languages: response.languages,\n    currencyCode: response.currency,\n    currency: response.currency_name,\n    countryDomain: response.country_tld,\n    countryAreaSize: response.country_area,\n    countryPopulation: response.country_population,\n  };\n};\n\n\nexport interface ServerInfo {\n  org: string,\n  asn: string,\n  isp: string,\n  os?: string,\n  ip?: string,\n  ports?: string,\n  loc?: string,\n  type?: string,\n};\n\nexport const getServerInfo = (response: any): ServerInfo => {\n  return {\n    org: response.org,\n    asn: response.asn,\n    isp: response.isp,\n    os: response.os,\n    ip: response.ip_str,\n    ports: response?.ports?.toString(),\n    loc: response.city ? `${response.city}, ${response.country_name}` : '',\n    type: response.tags ? response.tags.toString() : '',\n  };\n};\n\nexport interface HostNames {\n  domains: string[],\n  hostnames: string[],\n};\n\nexport const getHostNames = (response: any): HostNames | null => {\n  const { hostnames, domains } = response;\n  if ((!domains || domains.length < 1) && (!hostnames || hostnames.length < 1)) {\n    return null;\n  }\n  const results: HostNames = {\n    domains: domains || [],\n    hostnames: hostnames || [],\n  };\n  return results;\n};\n\nexport interface ShodanResults {\n  hostnames: HostNames | null,\n  serverInfo: ServerInfo,\n}\n\nexport const parseShodanResults = (response: any): ShodanResults => {\n  return {\n    hostnames: getHostNames(response),\n    serverInfo: getServerInfo(response),\n  };\n}\n\nexport interface Technology {\n  Categories?: string[];\n  Parent?: string;\n  Name: string;\n  Description: string;\n  Link: string;\n  Tag: string;\n  FirstDetected: number;\n  LastDetected: number;\n  IsPremium: string;\n}\n\nexport interface TechnologyGroup {\n  tag: string;\n  technologies: Technology[];\n}\n\nexport const makeTechnologies = (response: any): TechnologyGroup[] => {\n  let flatArray = response.Results[0].Result.Paths\n    .reduce((accumulator: any, obj: any) => accumulator.concat(obj.Technologies), []);\n  let technologies = flatArray.reduce((groups: any, item: any) => {\n    let tag = item.Tag;\n    if (!groups[tag]) groups[tag] = [];\n    groups[tag].push(item);\n    return groups;\n  }, {});\n  return technologies;\n};\n\nexport type Cookie = {\n  name: string;\n  value: string;\n  attributes: Record<string, string>;\n};\n\nexport const parseRobotsTxt = (content: string): { robots: RowProps[] } => {\n  const lines = content.split('\\n');\n  const rules: RowProps[] = [];\n\n  lines.forEach(line => {\n    line = line.trim();  // This removes trailing and leading whitespaces\n\n    let match = line.match(/^(Allow|Disallow):\\s*(\\S*)$/i);\n    if (match) {\n      const rule: RowProps = {\n        lbl: match[1],\n        val: match[2],\n      };\n      \n      rules.push(rule);\n    } else {\n      match = line.match(/^(User-agent):\\s*(\\S*)$/i);\n      if (match) {\n        const rule: RowProps = {\n          lbl: match[1],\n          val: match[2],\n        };\n        \n        rules.push(rule);\n      }\n    }\n  });\n\n  return { robots: rules };\n}\n\nexport const applyWhoIsResults = (response: any) => {\n  if (response.status !== '0') {\n    return {\n      error: response.status_desc,\n    }\n  }\n  const whoIsResults: Whois = {\n    created: response.date_created,\n    expires: response.date_expires,\n    updated: response.date_updated,\n    nameservers: response.nameservers,\n  };\n  return whoIsResults;\n}\n\n"
  },
  {
    "path": "src/web-check-live/views/About.tsx",
    "content": "import styled from '@emotion/styled';\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\n\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Footer from 'web-check-live/components/misc/Footer';\nimport Nav from 'web-check-live/components/Form/Nav';\nimport Button from 'web-check-live/components/Form/Button';\nimport AdditionalResources from 'web-check-live/components/misc/AdditionalResources';\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport docs, { about, featureIntro, license, fairUse, supportUs } from 'web-check-live/utils/docs';\n\nconst AboutContainer = styled.div`\nwidth: 95vw;\nmax-width: 1000px;\nmargin: 2rem auto;\npadding-bottom: 1rem;\nheader {\n  margin 1rem 0;\n  width: auto;\n}\nsection {\n  width: auto;\n  .inner-heading { display: none; }\n}\n`;\n\nconst HeaderLinkContainer = styled.nav`\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  a {\n    text-decoration: none;\n  }\n`;\n\nconst Section = styled(StyledCard)`\n  margin-bottom: 2rem;\n  overflow: clip;\n  max-height: 100%;\n  section {\n    clear: both;\n  }\n  h3 {\n    font-size: 1.5rem;\n  }\n  hr {\n    border: none;\n    border-top: 1px dashed ${colors.primary};\n    margin: 1.5rem auto;\n  }\n  ul {\n    padding: 0 0 0 1rem;\n    list-style: circle;\n  }\n  a {\n    color: ${colors.primary};\n    &:visited { opacity: 0.8; }\n  }\n  pre {\n    background: ${colors.background};\n    border-radius: 4px;\n    padding: 0.5rem;\n    width: fit-content;\n  }\n  small { opacity: 0.7; }\n  .contents {\n    ul {\n      list-style: none;\n      li {\n        a {\n          // color: ${colors.textColor};\n          &:visited { opacity: 0.8; }\n        }\n        b {\n          opacity: 0.75;\n          display: inline-block;\n          width: 1.5rem;\n        }\n      }\n    }\n  }\n  .example-screenshot {\n    float: right; \n    display: inline-flex;\n    flex-direction: column;\n    clear: both;\n    max-width: 300px;\n    img {\n      float: right;\n      break-inside: avoid;\n      max-width: 300px;\n      // max-height: 30rem;\n      border-radius: 6px;\n      clear: both;\n    }\n    figcaption {\n      font-size: 0.8rem;\n      text-align: center;\n      opacity: 0.7;\n    }\n  }\n`;\n\nconst SponsorshipContainer = styled.div`\n  display: flex;\n  justify-content: space-between;\n  gap: 1rem;\n  flex-wrap: wrap;\n  align-items: center;\n  line-height: 1.5rem;\n  img {\n    border-radius: 4px;\n  }\n`;\n\nconst makeAnchor = (title: string): string => {\n  return title.toLowerCase().replace(/[^\\w\\s]|_/g, \"\").replace(/\\s+/g, \"-\");\n};\n\nconst About = (): JSX.Element => {\n  const location = useLocation();\n\n  useEffect(() => {\n    // Scroll to hash fragment if present\n    if (location.hash) {\n      // Add a small delay to ensure the page has fully rendered\n      setTimeout(() => {\n        const element = document.getElementById(location.hash.slice(1));\n        if (element) {\n          element.scrollIntoView({ behavior: 'smooth', block: 'start' });\n        }\n      }, 100);\n    }\n  }, [location]);\n\n  return (\n    <div>\n    <AboutContainer>\n      <Nav>\n        <HeaderLinkContainer>\n          <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\"><Button>View on GitHub</Button></a>\n        </HeaderLinkContainer>\n      </Nav>\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Intro</Heading>\n      <Section>\n        {about.map((para, index: number) => (\n          <p key={index}>{para}</p>\n        ))}\n        <hr />\n        <SponsorshipContainer>\n          <p>\n            Web-Check is kindly sponsored\n            by <a target=\"_blank\" rel=\"noreferrer\" href=\"https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n              Terminal Trove\n            </a>\n            <br />\n            The $HOME of all things in the terminal.\n            <br />\n            <small>\n              <a target=\"_blank\" rel=\"noreferrer\" href=\"https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n                Find your next CLI / TUI tool, and get updates to your inbox\n              </a>\n            </small>\n          </p>\n          <a target=\"_blank\" rel=\"noreferrer\" href=\"https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n            <img width=\"300\" alt=\"Terminal Trove\" src=\"https://i.ibb.co/T1KzVmR/terminal-trove-green.png\" />\n          </a>\n        </SponsorshipContainer>\n        <hr />\n        <p>\n          Web-Check is developed and maintained by <a target=\"_blank\" rel=\"noreferrer\" href=\"https://aliciasykes.com\">Alicia Sykes</a>.\n          It's licensed under the <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/Lissy93/web-check/blob/master/LICENSE\">MIT license</a>,\n          and is completely free to use, modify and distribute in both personal and commercial settings.<br />\n          Source code and self-hosting docs are available on <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\">GitHub</a>.\n          If you've found this service useful, consider <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/sponsors/Lissy93\">sponsoring me</a> from $1/month,\n          to help with the ongoing hosting and development costs.\n        </p>\n      </Section>\n      \n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Features</Heading>\n      <Section>\n        {featureIntro.map((fi: string, i: number) => (<p key={i}>{fi}</p>))}\n        <div className=\"contents\">\n        <Heading as=\"h3\" size=\"small\" id=\"#feature-contents\" color={colors.primary}>Contents</Heading>\n          <ul>\n            {docs.map((section, index: number) => (\n              <li key={index}>\n                <b>{index + 1}</b>\n                <a href={`#${makeAnchor(section.title)}`}>{section.title}</a></li>\n            ))}\n          </ul>\n          <hr />\n        </div>\n        {docs.map((section, sectionIndex: number) => (\n          <section key={section.title}>\n            { sectionIndex > 0 && <hr /> }\n            <Heading as=\"h3\" size=\"small\" id={makeAnchor(section.title)} color={colors.primary}>{section.title}</Heading>\n            {section.screenshot &&\n              <figure className=\"example-screenshot\">\n                <img className=\"screenshot\" src={section.screenshot} alt={`Example Screenshot ${section.title}`} />\n                <figcaption>Fig.{sectionIndex + 1} - Example of {section.title}</figcaption>\n              </figure> \n            }\n            {section.description && <>\n              <Heading as=\"h4\" size=\"small\">Description</Heading>\n              <p>{section.description}</p>\n            </>}\n            { section.use && <>\n              <Heading as=\"h4\" size=\"small\">Use Cases</Heading>\n              <p>{section.use}</p>\n            </>}\n            {section.resources && section.resources.length > 0 && <>\n              <Heading as=\"h4\" size=\"small\">Useful Links</Heading>\n              <ul>\n                {section.resources.map((link: string | { title: string, link: string }, linkIndx: number) => (\n                  typeof link === 'string' ? (\n                    <li key={`link-${linkIndx}`} id={`link-${linkIndx}`}><a target=\"_blank\" rel=\"noreferrer\" href={link}>{link}</a></li>\n                  ) : (\n                    <li key={`link-${linkIndx}`} id={`link-${linkIndx}`}><a target=\"_blank\" rel=\"noreferrer\" href={link.link}>{link.title}</a></li>\n                  )\n                ))}\n              </ul>\n            </>}\n          </section>\n        ))}\n      </Section>\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Deploy your own Instance</Heading>\n      <Section>\n        <p>Web-Check is designed to be easily self-hosted.</p>\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Option #1 - Netlify</Heading>\n        <p>Click the button below to deploy to Netlify</p>\n        <a target=\"_blank\" rel=\"noreferrer\" href=\"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check\">\n          <img src=\"https://www.netlify.com/img/deploy/button.svg\" alt=\"Deploy to Netlify\" />\n        </a>\n\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Option #2 - Vercel</Heading>\n        <p>Click the button below to deploy to Vercel</p>\n        <a target=\"_blank\" rel=\"noreferrer\" href=\"https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Flissy93%2Fweb-check&project-name=web-check&repository-name=web-check-fork&demo-title=Web-Check%20Demo&demo-description=Check%20out%20web-check.xyz%20to%20see%20a%20live%20demo%20of%20this%20application%20running.&demo-url=https%3A%2F%2Fweb-check.xyz&demo-image=https%3A%2F%2Fraw.githubusercontent.com%2FLissy93%2Fweb-check%2Fmaster%2F.github%2Fscreenshots%2Fweb-check-screenshot10.png\">\n          <img src=\"https://vercel.com/button\" alt=\"Deploy with Vercel\" />\n        </a>\n\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Option #3 - Docker</Heading>\n        <p>\n        A Docker container is published to <a target=\"_blank\" rel=\"noreferrer\" href=\"https://hub.docker.com/r/lissy93/web-check\">DockerHub</a>\n        <br />\n        Run this command, then open <code>localhost:3000</code>\n        <pre>docker run -p 3000:3000 lissy93/web-check</pre>\n        </p>\n\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Option #4 - Manual</Heading>\n        <pre>\n        git clone https://github.com/Lissy93/web-check.git<br />\n        cd web-check # Move into the project directory<br />\n        yarn install # Install dependencies<br />\n        yarn build # Build the app for production<br />\n        yarn serve # Start the app (API and GUI)<br />\n        </pre>\n\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Further Docs</Heading>\n        <p>\n          More detailed installation and setup instructions can be found in the\n          GitHub repository - <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check#readme\">github.com/lissy93/web-check</a>\n        </p>\n\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Configuring</Heading>\n        <p>\n          There are some optional environmental variables you can specify to give you access to some additional Web-Checks.\n          See the README for full list of options.\n        </p>\n\n        <ul>\n          <li>\n            <code>GOOGLE_CLOUD_API_KEY</code>\n            : <a target=\"_blank\" rel=\"noreferrer\" href=\"https://cloud.google.com/api-gateway/docs/authenticate-api-keys\">A Google API key</a>\n            <i> Used to return quality metrics for a site</i>\n          </li>\n          <li>\n            <code>REACT_APP_SHODAN_API_KEY</code>\n            : <a target=\"_blank\" rel=\"noreferrer\" href=\"https://account.shodan.io/\">A Shodan API key</a>\n            <i> To show associated hosts for a domain</i>\n          </li>\n          <li>\n            <code>REACT_APP_WHO_API_KEY</code>\n            : <a target=\"_blank\" rel=\"noreferrer\" href=\"https://whoapi.com/\">A WhoAPI key</a>\n            <i> Allows for more comprehensive WhoIs records</i>\n          </li>\n        </ul>\n\n      </Section>\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>API Documentation</Heading>\n      <Section>\n        {/* eslint-disable-next-line*/}\n        <p>// Coming soon...</p>\n      </Section>\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Additional Resources</Heading>\n      <AdditionalResources />\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Support Us</Heading>\n      <Section>\n        {supportUs.map((para, index: number) => (<p dangerouslySetInnerHTML={{__html: para}} />))}\n      </Section>\n\n      <Heading as=\"h2\" size=\"medium\" color={colors.primary}>Terms & Info</Heading>\n      <Section>\n      <Heading as=\"h3\" size=\"small\" color={colors.primary}>License</Heading>\n        <b>\n          <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\">Web-Check</a> is distributed under the MIT license,\n          © <a target=\"_blank\" rel=\"noreferrer\" href=\"https://aliciasykes.com\">Alicia Sykes</a> { new Date().getFullYear()}\n        </b>\n        <br />\n        <small>For more info, see <a target=\"_blank\" rel=\"noreferrer\" href=\"https://tldrlegal.com/license/mit-license\">TLDR Legal → MIT</a></small>\n        <pre>{license}</pre>\n        <hr />\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Fair Use</Heading>\n        <ul>\n          {fairUse.map((para, index: number) => (<li>{para}</li>))}\n        </ul>\n        <hr />\n        <Heading as=\"h3\" size=\"small\" color={colors.primary}>Privacy</Heading>\n        <p>\n        Analytics are used on the demo instance (via a self-hosted Plausible instance), this only records the URL you visited but no personal data.\n        There's also some basic error logging (via a self-hosted GlitchTip instance), this is only used to help me fix bugs.\n        <br />\n        <br />\n        Neither your IP address, browser/OS/hardware info, nor any other data will ever be collected or logged.\n        (You may verify this yourself, either by inspecting the source code or the using developer tools)\n        </p>\n      </Section>\n    </AboutContainer>\n    <Footer />\n    </div>\n  );\n}\n\nexport default About;\n"
  },
  {
    "path": "src/web-check-live/views/Home.tsx",
    "content": "import styled from '@emotion/styled';\nimport { type ChangeEvent, type FormEvent, useState, useEffect } from 'react';\nimport { Link, useNavigate, useLocation, type NavigateOptions } from 'react-router-dom';\n\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Input from 'web-check-live/components/Form/Input'\nimport Button from 'web-check-live/components/Form/Button';\nimport { StyledCard } from 'web-check-live/components/Form/Card';\nimport Footer from 'web-check-live/components/misc/Footer';\nimport FancyBackground from 'web-check-live/components/misc/FancyBackground';\n\nimport docs from 'web-check-live/utils/docs';\nimport colors from 'web-check-live/styles/colors';\nimport { determineAddressType } from 'web-check-live/utils/address-type-checker';\n\nconst HomeContainer = styled.section`\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  font-family: 'PTMono';\n  padding: 1.5rem 1rem 4rem 1rem;\n  footer {\n    z-index: 1;\n  }\n`;\n\nconst UserInputMain = styled.form`\n  background: ${colors.backgroundLighter};\n  box-shadow: 4px 4px 0px ${colors.bgShadowColor};\n  border-radius: 8px;\n  padding: 1rem;\n  z-index: 5;\n  margin: 1rem;\n  width: calc(100% - 2rem);\n  max-width: 60rem;\n  z-index: 2;\n`;\n\nconst SponsorCard = styled.div`\n  background: ${colors.backgroundLighter};\n  box-shadow: 4px 4px 0px ${colors.bgShadowColor};\n  border-radius: 8px;\n  padding: 1rem;\n  z-index: 5;\n  margin: 1rem;\n  width: calc(100% - 2rem);\n  max-width: 60rem;\n  z-index: 2;\n  .inner {\n    display: flex;\n    justify-content: space-between;\n    flex-wrap: wrap;\n    gap: 1rem;\n    p {\n      margin: 0.25rem 0;\n    }\n  }\n  a {\n    color: ${colors.textColor};\n  }\n  img {\n    border-radius: 0.25rem;\n    box-shadow: 2px 2px 0px ${colors.fgShadowColor};\n    transition: box-shadow 0.2s;\n    margin: 0 auto;\n    display: block;\n    width: 200px;\n    &:hover {\n      box-shadow: 4px 4px 0px ${colors.fgShadowColor};\n    }\n    &:active {\n      box-shadow: -2px -2px 0px ${colors.fgShadowColor};\n    }\n  }\n  .cta {\n    font-size: 0.78rem;\n    a { color: ${colors.primary}; }\n  }\n`;\n\n// const FindIpButton = styled.a`\n//   margin: 0.5rem;\n//   cursor: pointer;\n//   display: block;\n//   text-align: center;\n//   color: ${colors.primary};\n//   text-decoration: underline;\n// `;\n\nconst ErrorMessage = styled.p`\n  color: ${colors.danger};\n  margin: 0.5rem;\n`;\n\nconst SiteFeaturesWrapper = styled(StyledCard)`\n  margin: 1rem;\n  width: calc(100% - 2rem);\n  max-width: 60rem;\n  z-index: 2;\n  .links {\n    display: flex;\n    justify-content: center;\n    gap: 0.5rem;\n    a {\n      width: 100%;\n      button {\n        width: calc(100% - 2rem);\n      }\n    }\n    @media(max-width: 600px) {\n      flex-wrap: wrap;\n    }\n  }\n  ul {\n    -webkit-column-width: 150px;\n    -moz-column-width: 150px;\n    column-width: 150px;\n    list-style: none;\n    padding: 0 1rem;\n    font-size: 0.9rem;\n    color: ${colors.textColor};\n    li {\n      margin: 0.1rem 0;\n      text-indent: -1.2rem;\n      break-inside: avoid-column;\n    }\n    li:before {\n      content: '✓';\n      color: ${colors.primary};\n      margin-right: 0.5rem;\n    }\n  }\n  a {\n    color: ${colors.primary};\n  }\n`;\n\nconst Home = (): JSX.Element => {\n  const defaultPlaceholder = 'e.g. https://duck.com/';\n  const [userInput, setUserInput] = useState('');\n  const [errorMsg, setErrMsg] = useState('');\n  const [placeholder] = useState(defaultPlaceholder);\n  const [inputDisabled] = useState(false);\n  const navigate = useNavigate();\n\n  const location = useLocation();\n\n  /* Redirect strait to results, if somehow we land on /check?url=[] */\n  useEffect(() => {\n    const query = new URLSearchParams(location.search);\n    const urlFromQuery = query.get('url');\n    if (urlFromQuery) {\n      navigate(`/check/${encodeURIComponent(urlFromQuery)}`, { replace: true });\n    }\n  }, [navigate, location.search]);\n\n  /* Check is valid address, either show err or redirect to results page */\n  const submit = () => {\n    let address = userInput.endsWith(\"/\") ? userInput.slice(0, -1) : userInput;\n    const addressType = determineAddressType(address);\n  \n    if (addressType === 'empt') {\n      setErrMsg('Field must not be empty');\n    } else if (addressType === 'err') {\n      setErrMsg('Must be a valid URL, IPv4 or IPv6 Address');\n    } else {\n      // if the addressType is 'url' and address doesn't start with 'http://' or 'https://', prepend 'https://'\n      if (addressType === 'url' && !/^https?:\\/\\//i.test(address)) {\n        address = 'https://' + address;\n      }\n      const resultRouteParams: NavigateOptions = { state: { address, addressType } };\n      navigate(`/check/${encodeURIComponent(address)}`, resultRouteParams);\n    }\n  };\n  \n\n  /* Update user input state, and hide error message if field is valid */\n  const inputChange = (event: ChangeEvent<HTMLInputElement>) => {\n    setUserInput(event.target.value);\n    const isError = ['err', 'empt'].includes(determineAddressType(event.target.value));\n    if (!isError) setErrMsg('');\n  };\n\n  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {\n    if (event.key === 'Enter') {\n      event.preventDefault();\n      submit();\n    }\n  };\n\n  const formSubmitEvent = (event: FormEvent<HTMLFormElement>) => {\n    event.preventDefault();\n    submit();\n  }\n\n  // const findIpAddress = () => {\n  //   setUserInput('');\n  //   setPlaceholder('Looking up your IP...');\n  //   setInputDisabled(true);\n  //   fetch('https://ipapi.co/json/')\n  //     .then(function(response) {\n  //       response.json().then(jsonData => {\n  //         setUserInput(jsonData.ip);\n  //         setPlaceholder(defaultPlaceholder);\n  //         setInputDisabled(true);\n  //       });\n  //     })\n  //     .catch(function(error) {\n  //       console.log('Failed to get IP address :\\'(', error)\n  //     });\n  // };\n\n\n  return (\n    <HomeContainer>\n      <FancyBackground />\n      <UserInputMain onSubmit={formSubmitEvent}>\n        <a href=\"/\">\n          <Heading as=\"h1\" size=\"xLarge\" align=\"center\" color={colors.primary}>\n            <img width=\"64\" src=\"/web-check.png\" alt=\"Web Check Icon\" />\n            Web Check\n          </Heading>\n        </a>\n        <Input\n          id=\"user-input\"\n          value={userInput}\n          label=\"Enter a URL\"\n          size=\"large\"\n          orientation=\"vertical\"\n          name=\"url\"\n          placeholder={placeholder}\n          disabled={inputDisabled}\n          handleChange={inputChange}\n          handleKeyDown={handleKeyPress}\n        />\n        {/* <FindIpButton onClick={findIpAddress}>Or, find my IP</FindIpButton> */}\n        { errorMsg && <ErrorMessage>{errorMsg}</ErrorMessage>}\n        <Button type=\"submit\" styles=\"width: calc(100% - 1rem);\" size=\"large\" onClick={submit}>Analyze!</Button>\n      </UserInputMain>\n      <SponsorCard>\n        <Heading as=\"h2\" size=\"small\" color={colors.primary}>Sponsored by</Heading>\n        <div className=\"inner\">\n          <p>\n            <a\n              target=\"_blank\"\n              rel=\"noreferrer\"\n              href=\"https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\"\n            >\n              Terminal Trove\n            </a> - The $HOME of all things in the terminal.\n            <br />\n            <span className=\"cta\">\n              Get updates on the latest CLI/TUI tools via\n              the <a\n                target=\"_blank\"\n                rel=\"noreferrer\"\n                className=\"cta\"\n                href=\"https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\"\n                >\n                Terminal Trove newsletter\n              </a>\n            </span>\n            \n          </p>\n          <a\n            target=\"_blank\"\n            rel=\"noreferrer\"\n            href=\"https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh\">\n            <img width=\"120\" alt=\"Terminal Trove\" src=\"https://i.ibb.co/NKtYjJ1/terminal-trove-web-check.png\" />\n          </a>\n        </div>\n\n      </SponsorCard>\n      <SiteFeaturesWrapper>\n        <div className=\"features\">\n          <Heading as=\"h2\" size=\"small\" color={colors.primary}>Supported Checks</Heading>\n          <ul>\n            {docs.map((doc, index) => (<li key={index}>{doc.title}</li>))}\n            <li><Link to=\"/check/about\">+ more!</Link></li>\n          </ul>\n        </div>\n        <div className=\"links\">\n          <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\" title=\"Check out the source code and documentation on GitHub, and get support or contribute\">\n            <Button>View on GitHub</Button>\n          </a>\n          <a target=\"_blank\" rel=\"noreferrer\" href=\"https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check\" title=\"Deploy your own private or public instance of Web-Check to Netlify\">\n            <Button>Deploy your own</Button>\n          </a>\n          <Link to=\"/check/about#api-documentation\" title=\"View the API documentation, to use Web-Check programmatically\">\n            <Button>API Docs</Button>\n          </Link>\n        </div>\n      </SiteFeaturesWrapper>\n      <Footer isFixed={true} />\n    </HomeContainer>\n  );\n}\n\nexport default Home;\n"
  },
  {
    "path": "src/web-check-live/views/NotFound.tsx",
    "content": "\nimport styled from '@emotion/styled';\n\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Footer from 'web-check-live/components/misc/Footer';\nimport Nav from 'web-check-live/components/Form/Nav';\nimport Button from 'web-check-live/components/Form/Button';\nimport { StyledCard } from 'web-check-live/components/Form/Card';\n\nconst AboutContainer = styled.div`\n  width: 95vw;\n  max-width: 1000px;\n  margin: 2rem auto;\n  padding-bottom: 1rem;\n  header {\n    margin 1rem 0;\n  }\n  a {\n    color: ${colors.primary};\n  }\n  .im-drink { font-size: 6rem; }\n  header {\n    width: auto;\n    margin: 1rem;\n  }\n`;\n\nconst HeaderLinkContainer = styled.nav`\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n  a {\n    text-decoration: none;\n  }\n`;\n\nconst NotFoundInner = styled(StyledCard)`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  margin: 1rem;\n  gap: 0.5rem;\n  h2 { font-size: 8rem; }\n`;\n\n\nconst NotFound = (): JSX.Element => {\n  return (\n    <>\n    <AboutContainer>\n    <Nav />\n    <NotFoundInner>\n      <Heading as=\"h2\" size=\"large\" color={colors.primary}>404</Heading>\n      <span className=\"im-drink\">🥴</span>\n      <Heading as=\"h3\" size=\"large\" color={colors.primary}>Not Found</Heading>\n      <HeaderLinkContainer>\n        <a href=\"/\"><Button>Back to Homepage</Button></a>\n      </HeaderLinkContainer>\n      <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\">Report Issue</a>\n    </NotFoundInner>\n    </AboutContainer>\n    <Footer isFixed={true} />\n    </>\n  );\n};\n\nexport default NotFound;\n"
  },
  {
    "path": "src/web-check-live/views/Results.tsx",
    "content": "import { useState, useEffect, useCallback, type ReactNode } from 'react';\nimport { useParams, useLocation } from 'react-router-dom';\nimport styled from '@emotion/styled';\nimport { ToastContainer } from 'react-toastify';\nimport Masonry from 'react-masonry-css'\n\nimport colors from 'web-check-live/styles/colors';\nimport Heading from 'web-check-live/components/Form/Heading';\nimport Modal from 'web-check-live/components/Form/Modal';\nimport Footer from 'web-check-live/components/misc/Footer';\nimport Nav from 'web-check-live/components/Form/Nav';\nimport type { RowProps }  from 'web-check-live/components/Form/Row';\n\nimport Loader from 'web-check-live/components/misc/Loader';\nimport ErrorBoundary from 'web-check-live/components/misc/ErrorBoundary';\nimport SelfScanMsg from 'web-check-live/components/misc/SelfScanMsg';\nimport DocContent from 'web-check-live/components/misc/DocContent';\nimport ProgressBar, { type LoadingJob, type LoadingState, initialJobs } from 'web-check-live/components/misc/ProgressBar';\nimport ActionButtons from 'web-check-live/components/misc/ActionButtons';\nimport AdditionalResources from 'web-check-live/components/misc/AdditionalResources';\nimport ViewRaw from 'web-check-live/components/misc/ViewRaw';\n\nimport ServerLocationCard from 'web-check-live/components/Results/ServerLocation';\nimport ServerInfoCard from 'web-check-live/components/Results/ServerInfo';\nimport HostNamesCard from 'web-check-live/components/Results/HostNames';\nimport WhoIsCard from 'web-check-live/components/Results/WhoIs';\nimport LighthouseCard from 'web-check-live/components/Results/Lighthouse';\nimport ScreenshotCard from 'web-check-live/components/Results/Screenshot';\nimport SslCertCard from 'web-check-live/components/Results/SslCert';\nimport HeadersCard from 'web-check-live/components/Results/Headers';\nimport CookiesCard from 'web-check-live/components/Results/Cookies';\nimport RobotsTxtCard from 'web-check-live/components/Results/RobotsTxt';\nimport DnsRecordsCard from 'web-check-live/components/Results/DnsRecords';\nimport RedirectsCard from 'web-check-live/components/Results/Redirects';\nimport TxtRecordCard from 'web-check-live/components/Results/TxtRecords';\nimport ServerStatusCard from 'web-check-live/components/Results/ServerStatus';\nimport OpenPortsCard from 'web-check-live/components/Results/OpenPorts';\nimport TraceRouteCard from 'web-check-live/components/Results/TraceRoute';\nimport CarbonFootprintCard from 'web-check-live/components/Results/CarbonFootprint';\nimport SiteFeaturesCard from 'web-check-live/components/Results/SiteFeatures';\nimport DnsSecCard from 'web-check-live/components/Results/DnsSec';\nimport HstsCard from 'web-check-live/components/Results/Hsts';\nimport SitemapCard from 'web-check-live/components/Results/Sitemap';\nimport DomainLookup from 'web-check-live/components/Results/DomainLookup';\nimport DnsServerCard from 'web-check-live/components/Results/DnsServer';\nimport TechStackCard from 'web-check-live/components/Results/TechStack';\nimport SecurityTxtCard from 'web-check-live/components/Results/SecurityTxt';\nimport ContentLinksCard from 'web-check-live/components/Results/ContentLinks';\nimport SocialTagsCard from 'web-check-live/components/Results/SocialTags';\nimport MailConfigCard from 'web-check-live/components/Results/MailConfig';\nimport HttpSecurityCard from 'web-check-live/components/Results/HttpSecurity';\nimport FirewallCard from 'web-check-live/components/Results/Firewall';\nimport ArchivesCard from 'web-check-live/components/Results/Archives';\nimport RankCard from 'web-check-live/components/Results/Rank';\nimport BlockListsCard from 'web-check-live/components/Results/BlockLists';\nimport ThreatsCard from 'web-check-live/components/Results/Threats';\nimport TlsCipherSuitesCard from 'web-check-live/components/Results/TlsCipherSuites';\nimport TlsIssueAnalysisCard from 'web-check-live/components/Results/TlsIssueAnalysis';\nimport TlsClientSupportCard from 'web-check-live/components/Results/TlsClientSupport';\n\nimport keys from 'web-check-live/utils/get-keys';\nimport { determineAddressType, type AddressType } from 'web-check-live/utils/address-type-checker';\nimport useMotherHook from 'web-check-live/hooks/motherOfAllHooks';\nimport {\n  getLocation, type ServerLocation,\n  type Cookie,\n  applyWhoIsResults, type Whois,\n  parseShodanResults, type ShodanResults\n} from 'web-check-live/utils/result-processor';\n\nconst ResultsOuter = styled.div`\n  display: flex;\n  flex-direction: column;\n  .masonry-grid {\n    display: flex;\n    width: auto;\n  }\n  .masonry-grid-col section { margin: 1rem 0.5rem; }\n`;\n\nconst ResultsContent = styled.section`\n  width: 95vw;\n  display: grid;\n  grid-auto-flow: dense;\n  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));\n  gap: 1rem;\n  margin: auto;\n  width: calc(100% - 2rem);\n  padding-bottom: 1rem;\n`;\n\nconst FilterButtons = styled.div`\n  width: 95vw;\n  margin: auto;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  gap: 1rem;\n  .one-half {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n    align-items: center;\n  }\n  button, input, .toggle-filters {\n    background: ${colors.backgroundLighter};\n    color: ${colors.textColor};\n    border: none;\n    border-radius: 4px;\n    font-family: 'PTMono';\n    padding: 0.25rem 0.5rem;\n    border: 1px solid transparent;\n    transition: all 0.2s ease-in-out;\n  }\n  button, .toggle-filters {\n    cursor: pointer;\n    text-transform: capitalize;\n    box-shadow: 2px 2px 0px ${colors.bgShadowColor};\n    transition: all 0.2s ease-in-out;\n    &:hover {\n      box-shadow: 4px 4px 0px ${colors.bgShadowColor};\n      color: ${colors.primary};\n    }\n    &.selected {\n      border: 1px solid ${colors.primary};\n      color: ${colors.primary};\n    }\n  }\n  input:focus {\n    border: 1px solid ${colors.primary};\n    outline: none;\n  }\n  .clear {\n    color: ${colors.textColor};\n    text-decoration: underline;\n    cursor: pointer;\n    font-size: 0.8rem;\n    opacity: 0.8;\n  }\n  .toggle-filters  {\n    font-size: 0.8rem;\n  }\n  .control-options {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 1rem;\n    align-items: center;\n    a {\n      text-decoration: none;\n    }\n  }\n`;\n\nconst Results = (props: { address?: string } ): JSX.Element => {\n  const startTime = new Date().getTime();\n\n  const address = props.address || useParams().urlToScan || '';\n\n  const [ addressType, setAddressType ] = useState<AddressType>('empt');\n\n  const [loadingJobs, setLoadingJobs] = useState<LoadingJob[]>(initialJobs);\n  const [modalOpen, setModalOpen] = useState(false);\n  const [modalContent, setModalContent] = useState<ReactNode>(<></>);\n  const [showFilters, setShowFilters] = useState(false);\n  const [searchTerm, setSearchTerm] = useState<string>('');\n  const [tags, setTags] = useState<string[]>([]);\n\n  const clearFilters = () => {\n    setTags([]);\n    setSearchTerm('');\n  };\n  const updateTags = (tag: string) => {\n    // Remove current tag if it exists, otherwise add it\n    // setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [...tags, tag]);\n    setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [tag]);\n  };\n\n  const updateLoadingJobs = useCallback((jobs: string | string[], newState: LoadingState, error?: string, retry?: () => void, data?: any) => {\n    (typeof jobs === 'string' ? [jobs] : jobs).forEach((job: string) => {\n    const now = new Date();\n    const timeTaken = now.getTime() - startTime;\n    setLoadingJobs((prevJobs) => {\n      const newJobs = prevJobs.map((loadingJob: LoadingJob) => {\n        if (job.includes(loadingJob.name)) {\n          return { ...loadingJob, error, state: newState, timeTaken, retry };\n        }\n        return loadingJob;\n      });\n\n      const timeString = `[${now.getHours().toString().padStart(2, '0')}:`\n        +`${now.getMinutes().toString().padStart(2, '0')}:`\n        + `${now.getSeconds().toString().padStart(2, '0')}]`;\n\n\n      if (newState === 'success') {\n        console.log(\n          `%cFetch Success - ${job}%c\\n\\n${timeString}%c The ${job} job succeeded in ${timeTaken}ms`\n          + `\\n%cRun %cwindow.webCheck['${job}']%c to inspect the raw the results`,\n          `background:${colors.success};color:${colors.background};padding: 4px 8px;font-size:16px;`,\n          `font-weight: bold; color: ${colors.success};`,\n          `color: ${colors.success};`,\n          `color: #1d8242;`,`color: #1d8242;text-decoration:underline;`,`color: #1d8242;`,\n        );\n        if (!(window as any).webCheck) (window as any).webCheck = {};\n        if (data) (window as any).webCheck[job] = data;\n      }\n  \n      if (newState === 'error') {\n        console.log(\n          `%cFetch Error - ${job}%c\\n\\n${timeString}%c The ${job} job failed `\n          +`after ${timeTaken}ms, with the following error:%c\\n${error}`,\n          `background: ${colors.danger}; color:${colors.background}; padding: 4px 8px; font-size: 16px;`,\n          `font-weight: bold; color: ${colors.danger};`,\n          `color: ${colors.danger};`,\n          `color: ${colors.warning};`,\n        );\n      }\n\n      if (newState === 'timed-out') {\n        console.log(\n          `%cFetch Timeout - ${job}%c\\n\\n${timeString}%c The ${job} job timed out `\n          +`after ${timeTaken}ms, with the following error:%c\\n${error}`,\n          `background: ${colors.info}; color:${colors.background}; padding: 4px 8px; font-size: 16px;`,\n          `font-weight: bold; color: ${colors.info};`,\n          `color: ${colors.info};`,\n          `color: ${colors.warning};`,\n        );\n      }\n\n      return newJobs;\n    });\n  });\n  }, [startTime]);\n\n  const parseJson = (response: Response): Promise<any> => {\n    return new Promise((resolve) => {\n        response.json()\n          .then(data => resolve(data))\n          .catch(error => resolve(\n            { error: `Failed to get a valid response 😢\\n`\n            + 'This is likely due the target not exposing the required data, '\n            + 'or limitations in imposed by the infrastructure this instance '\n            + 'of Web Check is running on.\\n\\n'\n            + `Error info:\\n${error}`}\n          ));\n    });\n  };\n\n  const urlTypeOnly = ['url'] as AddressType[]; // Many jobs only run with these address types\n\n  const api = import.meta.env.PUBLIC_API_ENDPOINT || '/api'; // Where is the API hosted?\n  \n  // Fetch and parse IP address for given URL\n  const [ipAddress, setIpAddress] = useMotherHook({\n    jobId: 'get-ip',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/get-ip?url=${address}`)\n    .then(res => parseJson(res))\n    .then(res => res.ip),\n  });\n\n  useEffect(() => {\n    if (!addressType || addressType === 'empt') {\n      setAddressType(determineAddressType(address || ''));\n    }\n    if (addressType === 'ipV4' && address) {\n      setIpAddress(address);\n    }\n  }, [address, addressType, setIpAddress]);  \n\n  // Get IP address location info\n  const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({\n    jobId: 'location',\n    updateLoadingJobs,\n    addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },\n    fetchRequest: () => fetch(`https://ipapi.co/${ipAddress}/json/`)\n      .then(res => parseJson(res))\n      .then(res => getLocation(res)),\n  });\n\n  // Fetch and parse SSL certificate info\n  const [sslResults, updateSslResults] = useMotherHook({\n    jobId: 'ssl',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/ssl?url=${address}`).then((res) => parseJson(res)),\n  });\n\n  // Run a manual whois lookup on the domain\n  const [domainLookupResults, updateDomainLookupResults] = useMotherHook({\n    jobId: 'domain',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Fetch and parse Lighthouse performance data\n  const [lighthouseResults, updateLighthouseResults] = useMotherHook({\n    jobId: 'quality',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/quality?url=${address}`)\n      .then(res => parseJson(res))\n      .then(res => res?.lighthouseResult || { error: res.error || 'No Data' }),\n  });\n\n  // Get the technologies used to build site, using Wappalyzer\n  const [techStackResults, updateTechStackResults] = useMotherHook({\n    jobId: 'tech-stack',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get hostnames and associated domains from Shodan\n  const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({\n    jobId: ['hosts', 'server-info'],\n    updateLoadingJobs,\n    addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },\n    fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)\n      .then(res => parseJson(res))\n      .then(res => parseShodanResults(res)),\n  });\n\n  // Fetch and parse cookies info\n  const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({\n    jobId: 'cookies',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/cookies?url=${address}`)\n      .then(res => parseJson(res)),\n  });\n\n  // Fetch and parse headers\n  const [headersResults, updateHeadersResults] = useMotherHook({\n    jobId: 'headers',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/headers?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Fetch and parse DNS records\n  const [dnsResults, updateDnsResults] = useMotherHook({\n    jobId: 'dns',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/dns?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get HTTP security\n  const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({\n    jobId: 'http-security',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get social media previews, from a sites social meta tags\n  const [socialTagResults, updateSocialTagResults] = useMotherHook({\n    jobId: 'social-tags',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get trace route for a given hostname\n  const [traceRouteResults, updateTraceRouteResults] = useMotherHook({\n    jobId: 'trace-route',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get a websites listed pages, from sitemap\n  const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({\n    jobId: 'security-txt',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get the DNS server(s) for a domain, and test DoH/DoT support\n  const [dnsServerResults, updateDnsServerResults] = useMotherHook({\n    jobId: 'dns-server',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get the WAF and Firewall info for a site\n  const [firewallResults, updateFirewallResults] = useMotherHook({\n    jobId: 'firewall',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get DNSSEC info\n  const [dnsSecResults, updateDnsSecResults] = useMotherHook({\n    jobId: 'dnssec',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Check if a site is on the HSTS preload list\n  const [hstsResults, updateHstsResults] = useMotherHook({\n    jobId: 'hsts',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Check if a host is present on the URLHaus malware list\n  const [threatResults, updateThreatResults] = useMotherHook({\n    jobId: 'threats',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get mail config for server, based on DNS records\n  const [mailConfigResults, updateMailConfigResults] = useMotherHook({\n    jobId: 'mail-config',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get list of archives from the Wayback Machine\n  const [archivesResults, updateArchivesResults] = useMotherHook({\n    jobId: 'archives',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get website's global ranking, from Tranco\n  const [rankResults, updateRankResults] = useMotherHook({\n    jobId: 'rank',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Take a screenshot of the website\n  const [screenshotResult, updateScreenshotResult] = useMotherHook({\n    jobId: 'screenshot',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get TLS security info, from Mozilla Observatory\n  const [tlsResults, updateTlsResults] = useMotherHook({\n    jobId: ['tls-cipher-suites', 'tls-security-config', 'tls-client-support'],\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/tls?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Fetches URL redirects\n  const [redirectResults, updateRedirectResults] = useMotherHook({\n    jobId: 'redirects',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get list of links included in the page content\n  const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({\n    jobId: 'linked-pages',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Fetch and parse crawl rules from robots.txt\n  const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({\n    jobId: 'robots-txt',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)\n      .then(res => parseJson(res)),\n  });\n\n  // Get current status and response time of server\n  const [serverStatusResults, updateServerStatusResults] = useMotherHook({\n    jobId: 'status',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Check for open ports\n  const [portsResults, updatePortsResults] = useMotherHook({\n    jobId: 'ports',\n    updateLoadingJobs,\n    addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },\n    fetchRequest: () => fetch(`${api}/ports?url=${ipAddress}`)\n      .then(res => parseJson(res)),\n  });\n\n  // Fetch and parse domain whois results\n  const [whoIsResults, updateWhoIsResults] = useMotherHook<Whois | { error: string }>({\n    jobId: 'whois',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`https://api.whoapi.com/?domain=${address}&r=whois&apikey=${keys.whoApi}`)\n      .then(res => parseJson(res))\n      .then(res => applyWhoIsResults(res)),\n  });\n\n  // Fetches DNS TXT records\n  const [txtRecordResults, updateTxtRecordResults] = useMotherHook({\n    jobId: 'txt-records',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/txt-records?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Check site against DNS blocklists\n  const [blockListsResults, updateBlockListsResults] = useMotherHook({\n    jobId: 'block-lists',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get a websites listed pages, from sitemap\n  const [sitemapResults, updateSitemapResults] = useMotherHook({\n    jobId: 'sitemap',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/sitemap?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Fetch carbon footprint data for a given site\n  const [carbonResults, updateCarbonResults] = useMotherHook({\n    jobId: 'carbon',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),\n  });\n\n  // Get site features from BuiltWith\n  const [siteFeaturesResults, updateSiteFeaturesResults] = useMotherHook({\n    jobId: 'features',\n    updateLoadingJobs,\n    addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },\n    fetchRequest: () => fetch(`${api}/features?url=${address}`)\n    .then(res => parseJson(res))\n    .then(res => {\n      if (res.Errors && res.Errors.length > 0) {\n        return { error: `No data returned, because ${res.Errors[0].Message || 'API lookup failed'}` };\n      }\n      return res;\n    }),\n  });\n\n  /* Cancel remaining jobs after  10 second timeout */\n  useEffect(() => {\n    const checkJobs = () => {\n      loadingJobs.forEach(job => {\n        if (job.state === 'loading') {\n          updateLoadingJobs(job.name, 'timed-out');\n        }\n      });\n    };\n    const timeoutId = setTimeout(checkJobs, 10000);\n    return () => {\n      clearTimeout(timeoutId);\n    };\n  }, [loadingJobs, updateLoadingJobs]);\n\n  const makeSiteName = (address: string): string => {\n    try {\n      return new URL(address).hostname.replace('www.', '');\n    } catch (error) {\n      return address;\n    }\n  }\n\n  // A list of state sata, corresponding component and title for each card\n  const resultCardData = [\n    {\n      id: 'location',\n      title: 'Server Location',\n      result: locationResults,\n      Component: ServerLocationCard,\n      refresh: updateLocationResults,\n      tags: ['server'],\n    }, {\n      id: 'ssl',\n      title: 'SSL Certificate',\n      result: sslResults,\n      Component: SslCertCard,\n      refresh: updateSslResults,\n      tags: ['server', 'security'],\n    }, {\n      id: 'domain',\n      title: 'Domain Whois',\n      result: domainLookupResults,\n      Component: DomainLookup,\n      refresh: updateDomainLookupResults,\n      tags: ['server'],\n    }, {\n      id: 'quality',\n      title: 'Quality Summary',\n      result: lighthouseResults,\n      Component: LighthouseCard,\n      refresh: updateLighthouseResults,\n      tags: ['client'],\n    }, {\n      id: 'tech-stack',\n      title: 'Tech Stack',\n      result: techStackResults,\n      Component: TechStackCard,\n      refresh: updateTechStackResults,\n      tags: ['client', 'meta'],\n    }, {\n      id: 'server-info',\n      title: 'Server Info',\n      result: shoadnResults?.serverInfo,\n      Component: ServerInfoCard,\n      refresh: updateShodanResults,\n      tags: ['server'],\n    }, {\n      id: 'cookies',\n      title: 'Cookies',\n      result: cookieResults,\n      Component: CookiesCard,\n      refresh: updateCookieResults,\n      tags: ['client', 'security'],\n    }, {\n      id: 'headers',\n      title: 'Headers',\n      result: headersResults,\n      Component: HeadersCard,\n      refresh: updateHeadersResults,\n      tags: ['client', 'security'],\n    }, {\n      id: 'dns',\n      title: 'DNS Records',\n      result: dnsResults,\n      Component: DnsRecordsCard,\n      refresh: updateDnsResults,\n      tags: ['server'],\n    }, {\n      id: 'hosts',\n      title: 'Host Names',\n      result: shoadnResults?.hostnames,\n      Component: HostNamesCard,\n      refresh: updateShodanResults,\n      tags: ['server'],\n    }, {\n      id: 'http-security',\n      title: 'HTTP Security',\n      result: httpSecurityResults,\n      Component: HttpSecurityCard,\n      refresh: updateHttpSecurityResults,\n      tags: ['security'],\n    }, {\n      id: 'social-tags',\n      title: 'Social Tags',\n      result: socialTagResults,\n      Component: SocialTagsCard,\n      refresh: updateSocialTagResults,\n      tags: ['client', 'meta'],\n    }, {\n      id: 'trace-route',\n      title: 'Trace Route',\n      result: traceRouteResults,\n      Component: TraceRouteCard,\n      refresh: updateTraceRouteResults,\n      tags: ['server'],\n    }, {\n      id: 'security-txt',\n      title: 'Security.Txt',\n      result: securityTxtResults,\n      Component: SecurityTxtCard,\n      refresh: updateSecurityTxtResults,\n      tags: ['security'],\n    }, {\n      id: 'dns-server',\n      title: 'DNS Server',\n      result: dnsServerResults,\n      Component: DnsServerCard,\n      refresh: updateDnsServerResults,\n      tags: ['server'],\n    }, {\n      id: 'firewall',\n      title: 'Firewall',\n      result: firewallResults,\n      Component: FirewallCard,\n      refresh: updateFirewallResults,\n      tags: ['server', 'security'],\n    }, {\n      id: 'dnssec',\n      title: 'DNSSEC',\n      result: dnsSecResults,\n      Component: DnsSecCard,\n      refresh: updateDnsSecResults,\n      tags: ['security'],\n    }, {\n      id: 'hsts',\n      title: 'HSTS Check',\n      result: hstsResults,\n      Component: HstsCard,\n      refresh: updateHstsResults,\n      tags: ['security'],\n    }, {\n      id: 'threats',\n      title: 'Threats',\n      result: threatResults,\n      Component: ThreatsCard,\n      refresh: updateThreatResults,\n      tags: ['security'],\n    }, {\n      id: 'mail-config',\n      title: 'Email Configuration',\n      result: mailConfigResults,\n      Component: MailConfigCard,\n      refresh: updateMailConfigResults,\n      tags: ['server'],\n    }, {\n      id: 'archives',\n      title: 'Archive History',\n      result: archivesResults,\n      Component: ArchivesCard,\n      refresh: updateArchivesResults,\n      tags: ['meta'],\n    }, {\n      id: 'rank',\n      title: 'Global Ranking',\n      result: rankResults,\n      Component: RankCard,\n      refresh: updateRankResults,\n      tags: ['meta'],\n    }, {\n      id: 'screenshot',\n      title: 'Screenshot',\n      result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot,\n      Component: ScreenshotCard,\n      refresh: updateScreenshotResult,\n      tags: ['client', 'meta'],\n    }, {\n      id: 'tls-cipher-suites',\n      title: 'TLS Cipher Suites',\n      result: tlsResults,\n      Component: TlsCipherSuitesCard,\n      refresh: updateTlsResults,\n      tags: ['server', 'security'],\n    }, {\n      id: 'tls-security-config',\n      title: 'TLS Security Issues',\n      result: tlsResults,\n      Component: TlsIssueAnalysisCard,\n      refresh: updateTlsResults,\n      tags: ['security'],\n    }, {\n      id: 'tls-client-support',\n      title: 'TLS Handshake Simulation',\n      result: tlsResults,\n      Component: TlsClientSupportCard,\n      refresh: updateTlsResults,\n      tags: ['security'],\n    }, {\n      id: 'redirects',\n      title: 'Redirects',\n      result: redirectResults,\n      Component: RedirectsCard,\n      refresh: updateRedirectResults,\n      tags: ['meta'],\n    }, {\n      id: 'linked-pages',\n      title: 'Linked Pages',\n      result: linkedPagesResults,\n      Component: ContentLinksCard,\n      refresh: updateLinkedPagesResults,\n      tags: ['client', 'meta'],\n    }, {\n      id: 'robots-txt',\n      title: 'Crawl Rules',\n      result: robotsTxtResults,\n      Component: RobotsTxtCard,\n      refresh: updateRobotsTxtResults,\n      tags: ['meta'],\n    }, {\n      id: 'status',\n      title: 'Server Status',\n      result: serverStatusResults,\n      Component: ServerStatusCard,\n      refresh: updateServerStatusResults,\n      tags: ['server'],\n    }, {\n      id: 'ports',\n      title: 'Open Ports',\n      result: portsResults,\n      Component: OpenPortsCard,\n      refresh: updatePortsResults,\n      tags: ['server'],\n    }, {\n      id: 'whois',\n      title: 'Domain Info',\n      result: whoIsResults,\n      Component: WhoIsCard,\n      refresh: updateWhoIsResults,\n      tags: ['server'],\n    }, {\n      id: 'txt-records',\n      title: 'TXT Records',\n      result: txtRecordResults,\n      Component: TxtRecordCard,\n      refresh: updateTxtRecordResults,\n      tags: ['server'],\n    }, {\n      id: 'block-lists',\n      title: 'Block Lists',\n      result: blockListsResults,\n      Component: BlockListsCard,\n      refresh: updateBlockListsResults,\n      tags: ['security', 'meta'],\n    }, {\n      id: 'features',\n      title: 'Site Features',\n      result: siteFeaturesResults,\n      Component: SiteFeaturesCard,\n      refresh: updateSiteFeaturesResults,\n      tags: ['meta'],\n    }, {\n      id: 'sitemap',\n      title: 'Pages',\n      result: sitemapResults,\n      Component: SitemapCard,\n      refresh: updateSitemapResults,\n      tags: ['meta'],\n    }, {\n      id: 'carbon',\n      title: 'Carbon Footprint',\n      result: carbonResults,\n      Component: CarbonFootprintCard,\n      refresh: updateCarbonResults,\n      tags: ['meta'],\n    },\n  ];\n\n  const makeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {\n    const actions = [\n      { label: `Info about ${title}`, onClick: showInfo, icon: 'ⓘ'},\n      { label: `Re-fetch ${title} data`, onClick: refresh, icon: '↻'},\n    ];\n    return (\n      <ActionButtons actions={actions} />\n    );\n  };\n\n  const showInfo = (id: string) => {\n    setModalContent(DocContent(id));\n    setModalOpen(true);\n  };\n\n  const showErrorModal = (content: ReactNode) => {\n    setModalContent(content);\n    setModalOpen(true);\n  };\n  \n  return (\n    <ResultsOuter>\n      <Nav>\n      { address && \n        <Heading color={colors.textColor} size=\"medium\">\n          { addressType === 'url' && <a target=\"_blank\" rel=\"noreferrer\" href={address}><img width=\"32px\" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt=\"\" /></a> }\n          {makeSiteName(address)}\n        </Heading>\n        }\n      </Nav>\n      <ProgressBar loadStatus={loadingJobs} showModal={showErrorModal} showJobDocs={showInfo} />\n      {/* { address?.includes(window?.location?.hostname || 'web-check.xyz') && <SelfScanMsg />} */}\n      <Loader show={loadingJobs.filter((job: LoadingJob) => job.state !== 'loading').length < 5} />\n      <FilterButtons>{ showFilters ? <>\n        <div className=\"one-half\">\n        <span className=\"group-label\">Filter by</span>\n        {['server', 'client', 'meta'].map((tag: string) => (\n          <button\n            key={tag}\n            className={tags.includes(tag) ? 'selected' : ''}\n            onClick={() => updateTags(tag)}>\n              {tag}\n          </button>\n        ))}\n        {(tags.length > 0 || searchTerm.length > 0) && <span onClick={clearFilters} className=\"clear\">Clear Filters</span> }\n        </div>\n        <div className=\"one-half\">\n        <span className=\"group-label\">Search</span>\n        <input \n          type=\"text\" \n          placeholder=\"Filter Results\" \n          value={searchTerm} \n          onChange={(e) => setSearchTerm(e.target.value)}\n        />\n        <span className=\"toggle-filters\" onClick={() => setShowFilters(false)}>Hide</span>\n        </div>\n        </> : (\n          <div className=\"control-options\">\n            <span className=\"toggle-filters\" onClick={() => setShowFilters(true)}>Show Filters</span>\n            <a href=\"#view-download-raw-data\"><span className=\"toggle-filters\">Export Data</span></a>\n            <a href=\"/about\"><span className=\"toggle-filters\">Learn about the Results</span></a>\n            <a href=\"/about#additional-resources\"><span className=\"toggle-filters\">More tools</span></a>\n            <a target=\"_blank\" rel=\"noreferrer\" href=\"https://github.com/lissy93/web-check\"><span className=\"toggle-filters\">View GitHub</span></a>\n          </div>\n      ) }\n      </FilterButtons>\n      <ResultsContent>\n        <Masonry\n          breakpointCols={{ 10000: 12, 4000: 9, 3600: 8, 3200: 7, 2800: 6, 2400: 5, 2000: 4, 1600: 3, 1200: 2, 800: 1 }}\n          className=\"masonry-grid\"\n          columnClassName=\"masonry-grid-col\">\n          {\n            resultCardData\n            .map(({ id, title, result, tags, refresh, Component }, index: number) => {\n              const show = (tags.length === 0 || tags.some(tag => tags.includes(tag)))\n              && title.toLowerCase().includes(searchTerm.toLowerCase())\n              && (result && !result.error);\n              return show ? (\n                <ErrorBoundary title={title} key={`eb-${index}`}>\n                  <Component\n                    key={`${title}-${index}`}\n                    data={{...result}}\n                    title={title}\n                    actionButtons={refresh ? makeActionButtons(title, refresh, () => showInfo(id)) : undefined}\n                  />\n                </ErrorBoundary>\n            ) : null})\n          }\n          </Masonry>\n      </ResultsContent>\n      <ViewRaw everything={resultCardData} />\n      <AdditionalResources url={address} />\n      <Footer />\n      <Modal isOpen={modalOpen} closeModal={()=> setModalOpen(false)}>{modalContent}</Modal>\n      <ToastContainer limit={3} draggablePercent={60} autoClose={2500} theme=\"dark\" position=\"bottom-right\" />\n    </ResultsOuter>\n  );\n}\n\nexport default Results;\n"
  },
  {
    "path": "svelte.config.js",
    "content": "import { vitePreprocess } from '@astrojs/svelte';\n\nexport default {\n\tpreprocess: vitePreprocess(),\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"ES2020\",\n    \"moduleResolution\": \"node\",\n    \"allowImportingTsExtensions\": true,\n    \"plugins\": [\n      {\n        \"name\": \"@astrojs/ts-plugin\"\n      },\n    ],\n    \"lib\": [\n      \"DOM\",\n      \"DOM.Iterable\",\n      \"ES2020\"\n    ],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"react\",\n    \"baseUrl\": \"src\",\n    \"paths\": {\n      \"@/*\": [\"*\"],\n      \"@components/*\": [\"components/*\"],\n      \"@layouts/*\": [\"layouts/*\"],\n      \"@pages/*\": [\"pages/*\"],\n      \"@styles/*\": [\"styles/*\"],\n      \"@assets/*\": [\"assets/*\"],\n    }\n  },\n  \"include\": [\n    \"src\"\n  ]\n}\n"
  },
  {
    "path": "vercel.json",
    "content": "{\n  \"version\": 2,\n  \"routes\": [\n    {\n      \"src\": \"/api/(.*)\",\n      \"dest\": \"/api/$1.js\"\n    }\n  ],\n  \"functions\": {\n    \"api/*.js\": {\n      \"maxDuration\": 10\n    }\n  },\n  \"env\": {\n    \"PLATFORM\": \"vercel\",\n    \"CI\": \"false\",\n    \"CHROME_PATH\": \"/usr/bin/chromium\",\n    \"NODE_VERSION\": \"21.x\",\n    \"GOOGLE_CLOUD_API_KEY\": \"\",\n    \"BUILT_WITH_API_KEY\": \"\",\n    \"REACT_APP_SHODAN_API_KEY\": \"\",\n    \"REACT_APP_WHO_API_KEY\": \"\"\n  },\n  \"build\": {\n    \"env\": {\n      \"PLATFORM\": \"vercel\"\n    }\n  }\n}\n"
  },
  {
    "path": "vite.config.js",
    "content": "// vite.config.js\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\n\nexport default defineConfig({\n  plugins: [\n    react({\n      jsxImportSource: '@emotion/react',\n      babel: {\n        plugins: ['babel-plugin-styled-components'],\n      },\n    }),\n  ],\n});\n"
  }
]