diff --git a/explorer/css/aaguids-explorer.css b/explorer/css/aaguids-explorer.css
new file mode 100644
index 0000000..f1663f3
--- /dev/null
+++ b/explorer/css/aaguids-explorer.css
@@ -0,0 +1,127 @@
+html,
+body {
+ margin: 0;
+}
+
+body {
+ margin: 0;
+ padding: 15px;
+ font-family: sans-serif;
+ background-color: whitesmoke;
+}
+
+h1 {
+ font-weight: bold;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ width: 100%;
+ margin: -15px 0 15px -15px;
+ padding: 15px;
+ background-color: lightsteelblue;
+ border-bottom: 1px solid black;
+ box-shadow: 0 2px 2px 1px #888;
+}
+
+
+h2 {
+ width: 100%;
+ margin: 10px 0 10px -15px;
+ padding: 5px;
+ background-color: lightgray;
+ border-bottom: 1px solid black;
+ border-top: 1px solid rgb(186, 186, 186);
+ box-shadow: 0 2px 2px 1px #888;
+}
+
+/* for 480px or less */
+@media screen and (max-width: 480px) {
+ h1 {
+ font-size: 24px;
+ }
+
+ h2 {
+ font-size: 18px;
+ }
+}
+
+pre {
+ white-space: pre-wrap;
+ /* css-3 */
+ white-space: -moz-pre-wrap;
+ /* Mozilla, since 1999 */
+ white-space: -pre-wrap;
+ /* Opera 4-6 */
+ white-space: -o-pre-wrap;
+ /* Opera 7 */
+ word-wrap: break-word;
+ /* Internet Explorer 5.5+ */
+}
+
+#about {
+ float: right;
+}
+
+#about img {
+ max-width: 32px;
+}
+
+#main,
+#loading-error {
+ display: none;
+ margin: 10px;
+}
+
+#aaguids {
+ border-collapse: collapse;
+}
+
+#aaguids td,
+#aaguids th {
+ border: black 1px solid;
+ padding: 10px;
+ vertical-align: top;
+}
+
+a {
+ color: cadetblue;
+ text-decoration: none;
+}
+
+#aaguids td.icon {
+ text-align: center;
+}
+
+#aaguids img {
+ max-width: 100px;
+ max-width: 50;
+}
+
+#aaguids td.dark,
+#aaguids th.dark {
+ background-color: #555;
+ color: white;
+}
+
+#aaguids th {
+ background-color: #DDD;
+}
+
+#aaguids th:last-of-type {
+ background-color: #333;
+}
+
+#aaguids th:nth-of-type(1) {
+ min-width: 19em;
+}
+#aaguids th:nth-of-type(2) {
+ min-width: 20em;
+}
+#filter {
+ width: 80%;
+}
+footer {
+ color: grey;
+ font-size: 80%;
+ text-align: left;
+ margin-top: 10px;
+}
\ No newline at end of file
diff --git a/explorer/img/favicon.ico b/explorer/img/favicon.ico
new file mode 100644
index 0000000..0a29c54
Binary files /dev/null and b/explorer/img/favicon.ico differ
diff --git a/explorer/img/github-mark.svg b/explorer/img/github-mark.svg
new file mode 100644
index 0000000..37fa923
--- /dev/null
+++ b/explorer/img/github-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/explorer/index.html b/explorer/index.html
new file mode 100644
index 0000000..a20b40b
--- /dev/null
+++ b/explorer/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Passkey AAGUIDs Explorer
+
+
+
+
+
+
+ Passkey AAGUIDs Explorer
+
+
+
+
+
+
+
+ Loading...
+
+
Error
+ Failed to load the AAGUIDs data :(
+
+
+
+
+
+
diff --git a/explorer/js/aaguids-explorer.js b/explorer/js/aaguids-explorer.js
new file mode 100644
index 0000000..b542962
--- /dev/null
+++ b/explorer/js/aaguids-explorer.js
@@ -0,0 +1,97 @@
+'use strict';
+
+function imageTag(src) {
+ return src ? "" : "";
+}
+
+function $(query) {
+ return document.querySelector(query);
+}
+
+function $toggle(query, show) {
+ return $(query).style.display = (show ? 'block' : 'none');
+}
+
+function $$(query) {
+ return document.querySelectorAll(query);
+}
+
+function appendRow(table, html) {
+ const row = document.createElement("tr");
+ row.innerHTML = html;
+ table.appendChild(row);
+}
+
+document.onreadystatechange = async () => {
+ if (document.readyState == "complete") {
+
+ try {
+ let file, switchJsonUrl, switchJsonText;
+ if (location.search === "?combined") {
+ file = "combined_aaguid.json";
+ switchJsonUrl = ".";
+ switchJsonText = "Exclude MDS authenticators"
+ } else {
+ file = "aaguid.json";
+ switchJsonUrl = "./?combined";
+ switchJsonText = "Include MDS authenticators"
+ }
+ $("#switch-json").setAttribute("href", switchJsonUrl);
+ $("#switch-json").innerText = switchJsonText;
+
+ const response = await fetch("../" + file);
+
+ const json = await response.json();
+
+ $toggle("#loading", false);
+ $toggle("#main", true);
+ const table = $("#aaguids");
+ appendRow(table, `
+
+ AAGUID
|
+ Name
+
+ X
+ |
+ Icon light |
+ Icon dark |
+
+ `);
+
+ for (const aaguid in json) {
+ if (Object.hasOwnProperty.call(json, aaguid)) {
+ appendRow(table, `
+
+ ${aaguid} |
+ ${json[aaguid].name} |
+ ${imageTag(json[aaguid].icon_light)} |
+ ${imageTag(json[aaguid].icon_dark)} |
+
+ `);
+ }
+ }
+
+ function applyFilter(filter) {
+ $$("#aaguids tr").forEach( (row) => {
+ const name = row.querySelector("td.name")?.innerText?.toLowerCase();
+ const show = !filter || (!name || name.indexOf(filter.toLowerCase()) >= 0);
+ row.style.display = (show ? 'table-row' : 'none');
+ });
+ }
+
+ $("#filter").addEventListener("keyup", () => {
+ applyFilter($("#filter").value);
+ });
+
+ $("#clear-filter").addEventListener("click", (event) => {
+ $("#filter").value = "";
+ applyFilter();
+ event.preventDefault();
+ });
+
+ } catch (err) {
+ $toggle("#loading-error", true);
+ console.error(err);
+ }
+ }
+}