Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions build/check-bundle-size.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,8 @@ const BUDGET_BYTES: Record<string, number> = {
// bundle without adding a UI framework or runtime dependency.
// Raised from 54 kB for the in-app lighting colour picker: the SV panel,
// hue bar, drag thumbs, swatch, and hex field in the dedicated Lighting tab.
".css": 55_500,
// Raised from 55.5 kB for the standalone Mouse Check diagnostics page.
".css": 61_500,
// Raised from 280 kB for the production Logitech onboard-profile codec,
// guarded flash editor, verification exporter, upstream Finalmouse driver,
// the dedicated Viper Mini protocol driver, Viper V3 sleep/low-power plus
Expand All@@ -28,7 +29,9 @@ const BUDGET_BYTES: Record<string, number> = {
// registry now bundles the Cobra HID client, its extended-matrix lighting
// payloads, and the Cobra picker filter alongside the other Razer drivers.
// Protocol tests and preview fixtures remain unbundled.
".js": 435_000,
// Raised from 435 kB for Mouse Check's HID inventory UI and reuse of the
// protocol package's validated Razer request/response codec.
".js": 444_000,
};

const ASSETS = join("dist", "assets");
Expand Down
21 changes: 21 additions & 0 deletions check.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#09090b" />
<link rel="icon" href="/favicon.ico?v=2" sizes="32x32" />
<link rel="icon" type="image/png" href="/favicon-32.png?v=2" sizes="32x32" />
<link rel="icon" type="image/svg+xml" href="/favicon-dark.svg?v=2" sizes="any" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<meta name="robots" content="index, follow" />
<title>Mouse Check — OpenMouse HID Diagnostics</title>
<meta name="description" content="Check if your gaming mouse works with WebHID in the browser, or if it requires a native driver." />
</head>
<body>
<div id="check-app"></div>
<script type="module" src="/src/check.ts"></script>
</body>
</html>
307 changes: 307 additions & 0 deletions src/check.css
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
:root {
color: #f1f1f3;
background: #09090b;
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-synthesis: none;
}

* { box-sizing: border-box; }
body { margin: 0; min-width: 320px; background: #09090b; }
button { font: inherit; cursor: pointer; color: inherit; }
a { color: inherit; text-decoration: none; }
button:focus-visible, a:focus-visible { outline: 2px solid #69d28d; outline-offset: 3px; }

.check-shell {
width: min(100% - 2rem, 860px);
margin: 0 auto;
padding: 2rem 0 4rem;
}

/* Header */
.check-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-bottom: 3rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid #212125;
}
.check-wordmark {
font-size: 1rem;
font-weight: 600;
letter-spacing: -.025em;
}
.check-wordmark span {
color: #55555b;
font-weight: 400;
}
.check-back {
color: #77777c;
font-size: .75rem;
transition: color .15s ease;
}
.check-back:hover { color: #f1f1f3; }

/* Hero */
.check-hero { margin-bottom: 2rem; }
.check-hero .overline {
margin: 0 0 .6rem;
color: #69d28d;
font-family: "JetBrains Mono", monospace;
font-size: .62rem;
letter-spacing: .1em;
}
.check-hero h1 {
margin: 0 0 .75rem;
font-size: clamp(1.9rem, 4vw, 2.8rem);
font-weight: 500;
letter-spacing: -.04em;
line-height: .96;
}
.check-hero p {
max-width: 560px;
margin: 0;
color: #85858a;
font-size: .88rem;
line-height: 1.6;
}

/* Browser compat */
.compat-banner {
display: flex;
align-items: center;
gap: .75rem;
padding: .75rem 1rem;
border-radius: 9px;
margin-bottom: 1.75rem;
font-size: .78rem;
}
.compat-banner.ok { border: 1px solid rgb(105 210 141 / 25%); background: rgb(105 210 141 / 7%); color: #a3e6bc; }
.compat-banner.warn { border: 1px solid rgb(255 174 100 / 25%); background: rgb(255 174 100 / 7%); color: #ffb97a; }
.compat-banner-dot { width: .45rem; height: .45rem; flex: 0 0 auto; border-radius: 50%; }
.compat-banner.ok .compat-banner-dot { background: #69d28d; box-shadow: 0 0 0 3px rgb(105 210 141 / 15%); }
.compat-banner.warn .compat-banner-dot { background: #ffb97a; }

/* Scan button */
.scan-row {
display: flex;
align-items: center;
gap: .75rem;
margin-bottom: 2rem;
}
.scan-button {
padding: .7rem 1.25rem;
border: 1px solid #69d28d;
border-radius: 9px;
background: #69d28d;
color: #07120b;
font-size: .8rem;
font-weight: 650;
transition: filter .15s ease, transform .15s ease;
}
.scan-button:hover { filter: brightness(1.08); transform: translateY(-1px); }
.scan-button:disabled { cursor: wait; opacity: .6; transform: none; }
.scan-note { color: #55555b; font-size: .72rem; }

/* Empty / status */
.check-empty {
padding: 3rem 0;
color: #55555b;
font-size: .82rem;
text-align: center;
}

/* Results */
.results-list { display: grid; gap: .75rem; }

.device-card {
padding: 1.1rem 1.25rem;
border: 1px solid #252529;
border-radius: 12px;
background: #111113;
}
.device-card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: .9rem;
}
.device-card-identity strong {
display: block;
margin-bottom: .25rem;
font-size: .9rem;
font-weight: 550;
}
.device-card-identity small {
color: #55555b;
font-family: "JetBrains Mono", monospace;
font-size: .6rem;
letter-spacing: .05em;
}
.verdict-badge {
flex: 0 0 auto;
padding: .22rem .55rem;
border-radius: 999px;
font-family: "JetBrains Mono", monospace;
font-size: .58rem;
font-weight: 700;
letter-spacing: .07em;
white-space: nowrap;
}
.verdict-full { border: 1px solid rgb(105 210 141 / 30%); background: rgb(105 210 141 / 10%); color: #69d28d; }
.verdict-partial { border: 1px solid rgb(255 210 100 / 30%); background: rgb(255 210 100 / 8%); color: #ffd264; }
.verdict-limited { border: 1px solid rgb(255 174 100 / 30%); background: rgb(255 174 100 / 8%); color: #ffb97a; }
.verdict-blocked { border: 1px solid rgb(255 110 110 / 25%); background: rgb(255 110 110 / 8%); color: #ff8e8e; }
.verdict-unknown { border: 1px solid #2f2f34; background: #161618; color: #77777c; }

/* Interfaces table */
.iface-section {
margin-top: .75rem;
padding-top: .75rem;
border-top: 1px solid #1e1e22;
}
.iface-label {
margin-bottom: .45rem;
color: #55555b;
font-family: "JetBrains Mono", monospace;
font-size: .58rem;
letter-spacing: .09em;
}
.iface-row {
display: flex;
align-items: center;
gap: .65rem;
padding: .38rem 0;
border-bottom: 1px solid #1a1a1e;
color: #b3b3b7;
font-size: .72rem;
}
.iface-row:last-child { border-bottom: 0; }
.iface-page {
flex: 0 0 auto;
min-width: 110px;
color: #77777c;
font-family: "JetBrains Mono", monospace;
font-size: .62rem;
}
.iface-type { flex: 1; }
.iface-reports {
color: #4d4d52;
font-family: "JetBrains Mono", monospace;
font-size: .6rem;
}

/* TX test results (Razer) */
.tx-section {
margin-top: .75rem;
padding-top: .75rem;
border-top: 1px solid #1e1e22;
}
.tx-label {
margin-bottom: .5rem;
color: #55555b;
font-family: "JetBrains Mono", monospace;
font-size: .58rem;
letter-spacing: .09em;
}
.tx-row {
display: flex;
align-items: center;
gap: .75rem;
padding: .3rem 0;
font-size: .72rem;
}
.tx-id {
min-width: 36px;
font-family: "JetBrains Mono", monospace;
font-size: .65rem;
font-weight: 500;
}
.tx-ok { color: #69d28d; }
.tx-fail { color: #4d4d52; }
.tx-fw { margin-left: auto; color: #55555b; font-family: "JetBrains Mono", monospace; font-size: .62rem; }

/* Open result */
.open-result {
display: flex;
align-items: center;
gap: .5rem;
margin-top: .65rem;
padding: .45rem .65rem;
border-radius: 7px;
background: #0d0d0f;
color: #77777c;
font-size: .7rem;
}
.open-result.ok { color: #a3e6bc; }
.open-result.err { color: #ff8e8e; }
.open-dot {
width: .35rem;
height: .35rem;
flex: 0 0 auto;
border-radius: 50%;
background: currentColor;
}

/* Copy section */
.copy-section {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin-top: 1.75rem;
padding-top: 1.25rem;
border-top: 1px solid #1a1a1e;
}
.copy-section p {
margin: 0;
color: #55555b;
font-size: .72rem;
}
.copy-button {
padding: .55rem .9rem;
border: 1px solid #2f2f34;
border-radius: 8px;
background: #161618;
color: #c5c5c9;
font-size: .72rem;
font-weight: 550;
transition: border-color .15s ease, color .15s ease;
}
.copy-button:hover { border-color: #55555b; color: #f1f1f3; }
.copy-button.copied { border-color: rgb(105 210 141 / 40%); color: #69d28d; }

/* Scanning indicator */
.scanning-state {
padding: 2rem 0;
color: #55555b;
font-size: .78rem;
text-align: center;
animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: .5; }
50% { opacity: 1; }
}

/* Section count */
.results-heading {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.results-heading span {
color: #55555b;
font-family: "JetBrains Mono", monospace;
font-size: .62rem;
letter-spacing: .08em;
}

@media (max-width: 600px) {
.check-shell { padding: 1.25rem 0 3rem; }
.device-card-head { flex-direction: column; gap: .5rem; }
.copy-section { flex-direction: column; align-items: flex-start; }
}
Loading