Spaces:
Running
Running
Download index.html from fliarbi/web-technology-explorer: direct link, hf CLI and curl.
- Browser
- Download file 23.1 kB
-
https://huggingface.co/spaces/fliarbi/web-technology-explorer/resolve/main/index.html
- Command line
-
hf download hf://spaces/fliarbi/web-technology-explorer/index.html
-
curl -L -o index.html https://huggingface.co/spaces/fliarbi/web-technology-explorer/resolve/main/index.html
23.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Web Technology Explorer</title> | |
| <meta name="description" content="Technology adoption on the web (top 300 technologies by category), Core Web Vitals pass rates by CMS and platform, TLD breakdowns and schema.org structured-data types, May 2025 vs May 2026. Source: HTTP Archive / Chrome UX Report."> | |
| <link rel="stylesheet" href="common.css"> | |
| <style> | |
| .note { color: var(--muted); font-size: .86rem; } | |
| .method h2 { font-size: 1.1rem; margin: 4px 0 8px; } | |
| .method h3 { font-size: .95rem; margin: 14px 0 4px; } | |
| .method p, .method li { color: var(--muted); font-size: .9rem; } | |
| .method ul { padding-left: 18px; margin: 4px 0; } | |
| .rowflex { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; margin-bottom: 8px; } | |
| .rowflex label { display: flex; flex-direction: column; font-size: .8rem; color: var(--muted); gap: 4px; min-width: 170px; flex: 1; } | |
| .src { font-size: .86rem; color: var(--muted); } | |
| td .pos { color: var(--good); } td .neg { color: #dc2626; } | |
| </style> | |
| <script src="https://cdn.jsdelivr.net/npm/plotly.js-dist-min@2.35.2/plotly.min.js"></script> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <header> | |
| <h1>🌐 Web Technology Explorer</h1> | |
| <p>What do websites run, and how fast are they for real users? Explore the <b>top 300 web technologies</b> by category, how adoption changed | |
| between the <b>May 2025 and May 2026</b> crawls, <b>Core Web Vitals</b> pass rates by CMS and platform, country and generic <b>TLDs</b>, | |
| and the most common <b>schema.org structured-data types</b>. Aggregates only, computed by Fliar BI.</p> | |
| <p class="src">Source: <b>HTTP Archive / Chrome UX Report (public web crawl data)</b>, CC BY 4.0. <span id="covline"></span></p> | |
| </header> | |
| <div class="panel kpi" id="kpi"><div class="loading">Loading the aggregate tables…</div></div> | |
| <div class="panel" id="tabbox"> | |
| <div class="tabs"><button>Technology adoption</button><button>By popularity</button><button>Core Web Vitals by platform</button><button>TLDs & countries</button><button>Structured data</button></div> | |
| <div data-pane> | |
| <div class="rowflex"> | |
| <label>Category<select id="cat"></select></label> | |
| <label>Search technology<input type="search" id="q" placeholder="e.g. React, Shopify, Cloudflare"></label> | |
| <label>Sort by<select id="sort"><option value="share">Share of sites (2026)</option><option value="change_pp">Change since 2025 (pp)</option><option value="lift">Top-1M vs all sites</option><option value="cwv_pass_rate">CWV pass rate</option></select></label> | |
| </div> | |
| <div id="f_tech" class="chart" style="min-height:520px"></div> | |
| <div id="t_tech"></div> | |
| <p class="note">The table shows the first 60 matches; use the category filter or search to reach any of the 300 technologies. All 300 are in the aggregate dataset.</p> | |
| </div> | |
| <div data-pane> | |
| <div class="rowflex"><label>Technologies (up to 6)<select id="rk_tech" multiple size="6"></select></label></div> | |
| <div id="f_rank" class="chart" style="min-height:420px"></div> | |
| <p class="note">Popularity buckets come from the Chrome UX Report rank of each origin (top 10k = the 10,000 most visited origins). A technology above its line in the top buckets is favoured by large sites.</p> | |
| <div id="f_stack" class="chart"></div> | |
| </div> | |
| <div data-pane> | |
| <div class="rowflex"> | |
| <label>Crawl<select id="cwv_crawl"></select></label> | |
| <label>Minimum sites with field data<select id="cwv_min"><option>1000</option><option selected>10000</option><option>50000</option></select></label> | |
| <label>Category<select id="cwv_cat"></select></label> | |
| </div> | |
| <div id="f_cwv" class="chart" style="min-height:560px"></div> | |
| <div id="t_cwv"></div> | |
| </div> | |
| <div data-pane> | |
| <div class="rowflex"> | |
| <label>TLD type<select id="tld_kind"><option value="country code">Country-code TLDs</option><option value="generic">Generic TLDs</option><option value="__all__">All</option></select></label> | |
| <label>Metric<select id="tld_metric"><option value="cwv_pass_rate">Core Web Vitals pass rate</option><option value="jsonld_rate">JSON-LD structured data</option><option value="leading_platform_share">Leading platform share</option><option value="share">Share of all sites</option><option value="tech">Share using a technology…</option></select></label> | |
| <label>Technology (for the last metric)<select id="tld_tech"></select></label> | |
| </div> | |
| <div id="f_tld" class="chart" style="min-height:520px"></div> | |
| <div id="t_tld"></div> | |
| </div> | |
| <div data-pane> | |
| <div id="f_sd" class="chart" style="min-height:560px"></div> | |
| <div id="t_sd"></div> | |
| </div> | |
| </div> | |
| <div class="panel method" id="method"></div> | |
| <footer> | |
| Source: <b>HTTP Archive / Chrome UX Report (public web crawl data)</b>, CC BY 4.0. Aggregates: licence <b>CC BY-NC 4.0</b>, attribution "Fliar BI" | |
| (<a href="https://fliarbi.com" target="_blank">fliarbi.com</a>) and the upstream sources. | |
| Tables: <a href="https://huggingface.co/datasets/fliarbi/web-technology-trends-aggregates" target="_blank">fliarbi/web-technology-trends-aggregates</a> · | |
| <a href="https://fliarbi.com/datasets/" target="_blank">fliarbi.com/datasets</a> · | |
| <a href="https://github.com/Busineessfree/open-company-datasets" target="_blank">GitHub index</a> · | |
| <a href="mailto:b2b@fliarbi.com">b2b@fliarbi.com</a> | |
| </footer> | |
| </div> | |
| <script type="module"> | |
| import { parquetReadObjects } from "https://cdn.jsdelivr.net/npm/hyparquet@1.31.2/+esm"; | |
| import { ALL, groupBy, renderTable, baseLayout, plot, css, esc, int, PALETTE, tabs, sum } from "./common.js"; | |
| const $ = (id) => document.getElementById(id); | |
| async function load(name) { | |
| const res = await fetch(`data/${name}.parquet`); | |
| if (!res.ok) throw new Error(`${name}: HTTP ${res.status}`); | |
| const rows = await parquetReadObjects({ file: await res.arrayBuffer() }); | |
| for (const r of rows) for (const k in r) if (typeof r[k] === "bigint") r[k] = Number(r[k]); | |
| return rows; | |
| } | |
| const p1 = (v) => (Number.isFinite(v) ? `${(v * 100).toFixed(1)}%` : "–"); | |
| const pp = (v) => (Number.isFinite(v) ? `<span class="${v >= 0 ? "pos" : "neg"}">${v >= 0 ? "+" : ""}${v.toFixed(1)}</span>` : "–"); | |
| const month = (c) => new Date(c + "T00:00:00Z").toLocaleString("en-US", { month: "short", year: "numeric", timeZone: "UTC" }); | |
| const BUCKET = (b) => b.slice(3); | |
| let D = {}, ST, LATEST, PREV; | |
| function techTab() { | |
| const cat = $("cat").value, q = $("q").value.trim().toLowerCase(), key = $("sort").value; | |
| let rows = D.tech.filter((r) => (cat === ALL || r.categories.split("; ").includes(cat)) && (!q || r.technology.toLowerCase().includes(q))); | |
| rows = rows.map((r) => ({ ...r, lift: r.share ? r.share_top1m / r.share : NaN })); | |
| rows.sort((a, b) => (Number.isFinite(b[key]) ? b[key] : -1e9) - (Number.isFinite(a[key]) ? a[key] : -1e9)); | |
| const top = rows.slice(0, 25).reverse(); | |
| plot($("f_tech"), [ | |
| { type: "bar", orientation: "h", name: month(PREV), y: top.map((r) => r.technology), x: top.map((r) => r.share_2025 * 100), marker: { color: "#94a3b8" } }, | |
| { type: "bar", orientation: "h", name: month(LATEST), y: top.map((r) => r.technology), x: top.map((r) => r.share * 100), marker: { color: css("--accent") }, | |
| text: top.map((r) => `${(r.share * 100).toFixed(1)}%`), textposition: "outside", cliponaxis: false }, | |
| ], baseLayout(`Share of sites using each technology${cat === ALL ? "" : " · " + cat}`, { barmode: "group", height: Math.max(420, 22 * top.length + 120), | |
| xaxis: { ticksuffix: "%", gridcolor: css("--line") }, yaxis: { automargin: true }, margin: { l: 10, r: 40, t: 46, b: 40 } })); | |
| renderTable($("t_tech"), [ | |
| { key: "rank", label: "#", num: true }, { key: "technology", label: "technology" }, { key: "category", label: "category" }, | |
| { key: "s", label: `sites ${month(LATEST)}`, num: true }, { key: "s0", label: month(PREV), num: true }, { key: "d", label: "Δ pp", num: true, html: true }, | |
| { key: "t1", label: "top 1M sites", num: true }, { key: "cs", label: "company sites*", num: true }, { key: "cwv", label: "CWV pass", num: true }, | |
| ], rows.slice(0, 60).map((r) => ({ rank: r.rank, technology: r.technology, category: r.category, s: p1(r.share), s0: p1(r.share_2025), | |
| d: pp(r.change_pp), t1: p1(r.share_top1m), cs: p1(r.share_company_sites), cwv: p1(r.cwv_pass_rate) }))); | |
| } | |
| function rankTab() { | |
| const sel = [...$("rk_tech").selectedOptions].map((o) => o.value).slice(0, 6); | |
| const buckets = [...new Set(D.rank.map((r) => r.rank_bucket))].sort(); | |
| const traces = []; | |
| sel.forEach((t, i) => { | |
| for (const [crawl, dash] of [[LATEST, "solid"], [PREV, "dot"]]) { | |
| const m = new Map(D.rank.filter((r) => r.technology === t && r.crawl === crawl).map((r) => [r.rank_bucket, r.share_in_bucket])); | |
| traces.push({ type: "scatter", mode: "lines+markers", name: `${t} (${month(crawl)})`, x: buckets.map(BUCKET), y: buckets.map((b) => (m.get(b) ?? 0) * 100), | |
| line: { color: PALETTE[i % PALETTE.length], dash }, showlegend: true }); | |
| } | |
| }); | |
| plot($("f_rank"), traces, baseLayout("Share of sites using the technology, by site popularity (solid 2026, dotted 2025)", { height: 420, | |
| yaxis: { ticksuffix: "%", gridcolor: css("--line"), rangemode: "tozero" }, legend: { orientation: "h", y: -0.2, font: { size: 10 } }, margin: { l: 50, r: 10, t: 46, b: 110 } })); | |
| } | |
| function stackChart() { | |
| const traces = [LATEST, PREV].map((c, i) => { | |
| const r = D.stack.filter((x) => x.crawl === c && x.n_tech <= 60).sort((a, b) => a.n_tech - b.n_tech); | |
| const tot = sum(D.stack.filter((x) => x.crawl === c).map((x) => x.pages)); | |
| return { type: "scatter", mode: "lines", name: month(c), x: r.map((x) => x.n_tech), y: r.map((x) => x.pages / tot * 100), line: { color: i ? "#94a3b8" : css("--accent"), width: 2 } }; | |
| }); | |
| plot($("f_stack"), traces, baseLayout("How many technologies a site runs (share of sites)", { height: 340, xaxis: { title: { text: "detected technologies per site" }, gridcolor: css("--line") }, | |
| yaxis: { ticksuffix: "%", gridcolor: css("--line") } })); | |
| } | |
| function cwvTab() { | |
| const crawl = $("cwv_crawl").value, min = +$("cwv_min").value, cat = $("cwv_cat").value; | |
| const prev = new Map(D.cwv.filter((r) => r.crawl === PREV).map((r) => [r.technology, r.cwv_pass_rate])); | |
| const rows = D.cwv.filter((r) => r.crawl === crawl && r.cwv_pages >= min && (cat === ALL || r.category === cat)).sort((a, b) => b.cwv_pass_rate - a.cwv_pass_rate); | |
| const top = rows.slice(0, 30).reverse(); | |
| plot($("f_cwv"), [ | |
| { type: "bar", orientation: "h", name: "LCP good", y: top.map((r) => r.technology), x: top.map((r) => r.lcp_good_rate * 100), marker: { color: PALETTE[1] }, visible: "legendonly" }, | |
| { type: "bar", orientation: "h", name: "CLS good", y: top.map((r) => r.technology), x: top.map((r) => r.cls_good_rate * 100), marker: { color: PALETTE[2] }, visible: "legendonly" }, | |
| { type: "bar", orientation: "h", name: "INP good", y: top.map((r) => r.technology), x: top.map((r) => r.inp_good_rate * 100), marker: { color: PALETTE[3] }, visible: "legendonly" }, | |
| { type: "bar", orientation: "h", name: "passes all Core Web Vitals", y: top.map((r) => r.technology), x: top.map((r) => r.cwv_pass_rate * 100), marker: { color: css("--accent") }, | |
| text: top.map((r) => `${(r.cwv_pass_rate * 100).toFixed(0)}%`), textposition: "outside", cliponaxis: false }, | |
| ], baseLayout(`Core Web Vitals pass rate by platform, ${month(crawl)} (click legend for LCP / CLS / INP)`, { barmode: "group", height: Math.max(420, 22 * top.length + 140), | |
| xaxis: { ticksuffix: "%", range: [0, 105], gridcolor: css("--line") }, yaxis: { automargin: true }, margin: { l: 10, r: 30, t: 46, b: 40 } })); | |
| renderTable($("t_cwv"), [ | |
| { key: "technology", label: "platform" }, { key: "category", label: "category" }, { key: "n", label: "sites with field data", num: true }, | |
| { key: "pass", label: "pass all", num: true }, { key: "d", label: crawl === LATEST ? "Δ pp vs 2025" : "", num: true, html: true }, | |
| { key: "lcp", label: "LCP good", num: true }, { key: "cls", label: "CLS good", num: true }, { key: "inp", label: "INP good", num: true }, | |
| ], rows.map((r) => ({ technology: r.technology, category: r.category, n: int(r.cwv_pages), pass: p1(r.cwv_pass_rate), | |
| d: crawl === LATEST && prev.has(r.technology) ? pp((r.cwv_pass_rate - prev.get(r.technology)) * 100) : "", | |
| lcp: p1(r.lcp_good_rate), cls: p1(r.cls_good_rate), inp: p1(r.inp_good_rate) }))); | |
| } | |
| function tldTab() { | |
| const kind = $("tld_kind").value, metric = $("tld_metric").value, tech = $("tld_tech").value; | |
| $("tld_tech").disabled = metric !== "tech"; | |
| let rows = D.tld.filter((r) => r.crawl === LATEST && r.tld !== "other" && (kind === ALL || r.kind === kind)); | |
| const techShare = new Map(D.ttld.filter((r) => r.technology === tech).map((r) => [r.tld, r.share_in_tld])); | |
| const val = (r) => (metric === "tech" ? techShare.get(r.tld) ?? 0 : r[metric]); | |
| rows = rows.map((r) => ({ ...r, v: val(r) })).filter((r) => Number.isFinite(r.v)).sort((a, b) => b.v - a.v); | |
| const top = rows.slice(0, 35).reverse(); | |
| const label = { cwv_pass_rate: "Core Web Vitals pass rate", jsonld_rate: "Sites with JSON-LD", leading_platform_share: "Share of the leading platform", share: "Share of all sites", tech: `Sites using ${tech}` }[metric]; | |
| plot($("f_tld"), [{ type: "bar", orientation: "h", y: top.map((r) => `.${r.tld}${r.country ? " · " + r.country : ""}`), x: top.map((r) => r.v * 100), | |
| marker: { color: css("--accent") }, text: top.map((r) => `${(r.v * 100).toFixed(1)}%`), textposition: "outside", cliponaxis: false, | |
| hovertext: top.map((r) => `${int(r.pages)} sites · leading platform ${r.leading_platform || "–"}`), hoverinfo: "text+x" }], | |
| baseLayout(`${label} by TLD, ${month(LATEST)}`, { height: Math.max(420, 20 * top.length + 120), xaxis: { ticksuffix: "%", gridcolor: css("--line") }, yaxis: { automargin: true }, margin: { l: 10, r: 40, t: 46, b: 40 } })); | |
| const prev = new Map(D.tld.filter((r) => r.crawl === PREV).map((r) => [r.tld, r])); | |
| renderTable($("t_tld"), [ | |
| { key: "t", label: "TLD" }, { key: "country", label: "country" }, { key: "n", label: "sites", num: true }, { key: "sh", label: "share of web", num: true }, | |
| { key: "cwv", label: "CWV pass", num: true }, { key: "d", label: "Δ pp", num: true, html: true }, { key: "jl", label: "JSON-LD", num: true }, | |
| { key: "lp", label: "leading platform" }, { key: "lps", label: "its share", num: true }, | |
| ], rows.map((r) => ({ t: "." + r.tld, country: r.country, n: int(r.pages), sh: p1(r.share), cwv: p1(r.cwv_pass_rate), | |
| d: prev.has(r.tld) ? pp((r.cwv_pass_rate - prev.get(r.tld).cwv_pass_rate) * 100) : "–", jl: p1(r.jsonld_rate), lp: r.leading_platform || "–", lps: p1(r.leading_platform_share) }))); | |
| } | |
| function sdTab() { | |
| const top = D.sd.slice(0, 30).reverse(); | |
| plot($("f_sd"), [ | |
| { type: "bar", orientation: "h", name: month(PREV), y: top.map((r) => r.sd_type), x: top.map((r) => r.share_2025 * 100), marker: { color: "#94a3b8" } }, | |
| { type: "bar", orientation: "h", name: month(LATEST), y: top.map((r) => r.sd_type), x: top.map((r) => r.share * 100), marker: { color: css("--accent") } }, | |
| ], baseLayout("schema.org types declared on sites (share of sites)", { barmode: "group", height: 760, xaxis: { ticksuffix: "%", gridcolor: css("--line") }, yaxis: { automargin: true }, margin: { l: 10, r: 20, t: 46, b: 40 } })); | |
| renderTable($("t_sd"), [{ key: "i", label: "#", num: true }, { key: "sd_type", label: "@type" }, { key: "s", label: `sites ${month(LATEST)}`, num: true }, | |
| { key: "s0", label: month(PREV), num: true }, { key: "d", label: "Δ pp", num: true, html: true }, { key: "t1", label: "top 1M sites", num: true }], | |
| D.sd.slice(0, 100).map((r, i) => ({ i: i + 1, sd_type: r.sd_type, s: p1(r.share), s0: p1(r.share_2025), d: pp(r.change_pp), t1: p1(r.share_top1m) }))); | |
| } | |
| function kpis() { | |
| const agg = (c) => { const r = D.ov.filter((x) => x.crawl === c); return { pages: sum(r.map((x) => x.pages)), cwv: sum(r.map((x) => x.cwv_pages)), good: sum(r.map((x) => x.cwv_good)), jl: sum(r.map((x) => x.jsonld_pages)) }; }; | |
| const a = agg(LATEST), b = agg(PREV); | |
| const fastest = D.tech.filter((r) => r.share >= 0.02).sort((x, y) => y.change_pp - x.change_pp)[0]; | |
| const wp = D.tech.find((r) => r.technology === "WordPress"); | |
| $("kpi").innerHTML = `<span class="big">${int(a.pages)}</span> sites crawled (${month(LATEST)}) · <b>${p1(a.good / a.cwv)}</b> pass Core Web Vitals (${p1(b.good / b.cwv)} in ${month(PREV)}) | |
| · <b>${p1(a.jl / a.pages)}</b> use JSON-LD | |
| <div class="sub">${wp ? `WordPress on ${p1(wp.share)} of sites (${pp(wp.change_pp)} pp)` : ""} · biggest riser among technologies on ≥2% of sites: <b>${esc(fastest.technology)}</b> (${pp(fastest.change_pp)} pp) · | |
| field data (CrUX) for ${p1(a.cwv / a.pages)} of sites</div>`; | |
| } | |
| function method() { | |
| const c = ST.coverage; | |
| $("covline").textContent = `${int(ST.pages[LATEST])} sites (${month(LATEST)}) and ${int(ST.pages[PREV])} (${month(PREV)}); ${c.shards_answering} of ${c.shards_total} database shards answered.`; | |
| const nComp = ST.company_sites_compared; | |
| $("method").innerHTML = `<h2>Methodology and coverage</h2> | |
| <h3>Data</h3> | |
| <ul><li>Source: <b>HTTP Archive / Chrome UX Report (public web crawl data)</b>, mobile crawls of ${month(PREV)} and ${month(LATEST)}. One row = one crawled root page ("site"). HTTP Archive and Chrome UX Report data are CC BY 4.0.</li> | |
| <li>Technologies come from HTTP Archive's open-source detection rules (patterns on page code, headers and scripts). The category is the first category of the rule. ${int(ST.technologies_detected_latest)} technologies were detected in ${month(LATEST)}; this page keeps the 300 most common.</li> | |
| <li><b>Company sites*</b> = share of the ${int(nComp)} company websites in Fliar BI's <a href="https://huggingface.co/datasets/fliarbi/website-technology-stack" target="_blank">website technology stack dataset</a> that run the technology (matched by name; blank when the name differs).</li></ul> | |
| <h3>Core Web Vitals</h3> | |
| <ul><li>Chrome UX Report p75 values for phones: good = LCP ≤ 2.5 s, CLS ≤ 0.1, INP ≤ 200 ms. A site passes when LCP and CLS are good and INP is good or not reported.</li> | |
| <li>Rates use only sites with field data. Popular sites are more likely to have it, so platform comparisons mix speed with audience size.</li></ul> | |
| <h3>Other definitions</h3> | |
| <ul><li>Structured-data types = distinct <code>@type</code> values in JSON-LD (nested types such as ListItem included) or schema.org microdata on the page.</li> | |
| <li>TLD = last label of the domain (co.uk counts as .uk). Country-code TLDs are not the same as the site's country.</li> | |
| <li>Year-on-year changes mix real adoption with detection-rule updates between crawls; a very large jump for one technology can come from a new or changed rule.</li></ul> | |
| <h3>Coverage</h3> | |
| <ul><li>${c.shards_answering} of ${c.shards_total} database shards answered when the tables were built (missing: ${esc(c.missing_shards.join(", ") || "none")}). Domains are sharded by hash, so the gap is an even random sample: shares and rates are unbiased, absolute counts cover about ${Math.round(c.shards_answering / c.shards_total * 100)}% of the crawl.</li> | |
| <li>The crawl covers origins known to the Chrome UX Report (sites with real Chrome traffic), not every registered domain.</li></ul> | |
| <h3>Data and related datasets</h3> | |
| <ul><li>Aggregate tables (CC BY-NC 4.0, upstream CC BY 4.0): <a href="https://huggingface.co/datasets/fliarbi/web-technology-trends-aggregates" target="_blank">fliarbi/web-technology-trends-aggregates</a></li> | |
| <li>Company websites: <a href="https://huggingface.co/datasets/fliarbi/website-technology-stack" target="_blank">fliarbi/website-technology-stack</a> · <a href="https://huggingface.co/datasets/fliarbi/open-company-database" target="_blank">fliarbi/open-company-database</a> · <a href="https://huggingface.co/datasets/fliarbi/google-play-apps-2026" target="_blank">fliarbi/google-play-apps-2026</a></li> | |
| <li>All datasets: <a href="https://fliarbi.com/datasets/" target="_blank">fliarbi.com/datasets</a> · <a href="https://github.com/Busineessfree/open-company-datasets" target="_blank">github.com/Busineessfree/open-company-datasets</a> · contact <a href="mailto:b2b@fliarbi.com">b2b@fliarbi.com</a></li></ul>`; | |
| } | |
| async function main() { | |
| const names = { tech: "technologies_top300", rank: "technology_by_rank", ttld: "technology_by_tld", cwv: "cwv_by_platform", tld: "tlds", sd: "structured_data_types", ov: "overview_by_rank", stack: "stack_size" }; | |
| const loaded = await Promise.all(Object.values(names).map(load)); | |
| Object.keys(names).forEach((k, i) => (D[k] = loaded[i])); | |
| ST = await (await fetch("data/build_stats.json")).json(); | |
| [PREV, LATEST] = ST.crawls; | |
| D.tech.sort((a, b) => a.rank - b.rank); | |
| const cats = [...groupBy(D.tech.flatMap((r) => r.categories.split("; ").filter(Boolean).map((c) => ({ c }))), (x) => x.c).entries()].sort((a, b) => b[1].length - a[1].length); | |
| $("cat").innerHTML = `<option value="${ALL}">All categories (${D.tech.length})</option>` + cats.map(([c, g]) => `<option value="${esc(c)}">${esc(c)} (${g.length})</option>`).join(""); | |
| $("rk_tech").innerHTML = D.tech.map((r) => `<option value="${esc(r.technology)}">${esc(r.technology)}</option>`).join(""); | |
| for (const t of ["React", "WordPress", "Shopify", "Next.js"]) { const o = [...$("rk_tech").options].find((x) => x.value === t); if (o) o.selected = true; } | |
| $("cwv_crawl").innerHTML = [LATEST, PREV].map((c) => `<option value="${c}">${month(c)}</option>`).join(""); | |
| const cwvCats = [...new Set(D.cwv.map((r) => r.category))].sort(); | |
| $("cwv_cat").innerHTML = `<option value="${ALL}">All</option>` + cwvCats.map((c) => `<option>${esc(c)}</option>`).join(""); | |
| const tt = [...new Set(D.ttld.map((r) => r.technology))]; | |
| $("tld_tech").innerHTML = D.tech.filter((r) => tt.includes(r.technology)).map((r) => `<option>${esc(r.technology)}</option>`).join(""); | |
| $("tld_tech").value = "WordPress"; | |
| D.sd.sort((a, b) => b.pages - a.pages); | |
| kpis(); method(); | |
| tabs($("tabbox")); | |
| document.querySelectorAll(".tabs button").forEach((b) => b.addEventListener("click", () => setTimeout(() => | |
| document.querySelectorAll("[data-pane]:not([hidden]) .js-plotly-plot").forEach((e) => window.Plotly.Plots.resize(e)), 30))); | |
| techTab(); rankTab(); stackChart(); cwvTab(); tldTab(); sdTab(); | |
| for (const id of ["cat", "sort"]) $(id).addEventListener("change", techTab); | |
| $("q").addEventListener("input", techTab); | |
| $("rk_tech").addEventListener("change", rankTab); | |
| for (const id of ["cwv_crawl", "cwv_min", "cwv_cat"]) $(id).addEventListener("change", cwvTab); | |
| for (const id of ["tld_kind", "tld_metric", "tld_tech"]) $(id).addEventListener("change", tldTab); | |
| } | |
| main().catch((e) => { $("kpi").innerHTML = `<div class="warn">Could not load the data: ${esc(e.message)}. Please reload.</div>`; console.error(e); }); | |
| </script> | |
| </body> | |
| </html> | |