Skip to content
Open
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
150 changes: 1 addition & 149 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -136,155 +136,7 @@
<div class="bottom-row">
<button class="btn-reset" @click="reset">↺ Reset All</button>
</div>

</div>

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mouse Button Fault Tester</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.21/vue.global.prod.min.js"></script>
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Bebas+Neue&family=DM+Sans:wght@300;400;500;600&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>

<body>
<div id="app" :data-theme="theme">

<!-- TOP BAR -->
<div class="topbar">
<div>
<div class="brand-title">Mouse Fault</div>
<div class="brand-sub">Button health checker</div>
</div>
<button class="theme-toggle" @click="toggleTheme">
<span class="theme-icon">{{ theme === 'dark' ? '☀️' : '🌙' }}</span>
{{ theme === 'dark' ? 'Light' : 'Dark' }}
</button>
</div>

<!-- ARENA -->
<div class="arena-wrap">
<div class="arena" :class="arenaClass" @mousedown.prevent="onMousedown" @contextmenu.prevent>

<div class="a-icon">{{ aIcon }}</div>
<div class="a-hint">{{ aHint }}</div>
<div class="a-last" v-if="aLast">{{ aLast }}</div>

<!-- ghost banner -->
<div class="ghost-banner" v-if="showBanner">
⚡ Ghost double-click detected on {{ bannerBtn }} — {{ bannerGap }}ms gap
</div>

<!-- ripples -->
<div v-for="r in ripples" :key="r.id" class="ripple" :style="{width:r.s+'px',height:r.s+'px',top:r.y+'px',left:r.x+'px',
marginLeft:(-r.s/2)+'px',marginTop:(-r.s/2)+'px',background:r.c}">
</div>
</div>
</div>

<!-- HERO CARDS -->
<div class="cards">
<div v-for="b in btns" :key="b.id" class="card" :class="cardBorder(b)">
<div class="card-dot" :class="dotClass(b)"></div>
<div class="card-label">{{ b.name }} Button</div>
<div class="card-verdict" :class="vClass(b)">{{ verdict(b) }}</div>
<div class="card-stat">
<template v-if="b.clicks === 0">no data yet</template>
<template v-else>
{{ b.ghosts }} ghost / {{ b.clicks }} click{{ b.clicks !== 1 ? 's' : '' }}
</template>
</div>
</div>
</div>

<!-- EVENT LOG -->
<div class="collapsible">
<button class="c-toggle" @click="logOpen = !logOpen">
<span>Event Log <span style="font-weight:400;opacity:.5">({{ log.length }})</span></span>
<span class="arrow" :class="{open:logOpen}">▼</span>
</button>
<div class="c-body" :class="{open:logOpen}">
<div class="log-list">
<div v-if="!log.length" class="log-empty">No events yet — click anywhere in the box above.</div>
<div v-for="e in log" :key="e.id" class="log-row" :class="{rb:e.ghost}">
<span class="lr-time">{{ e.time }}</span>
<span class="lr-btn">{{ e.btn }}</span>
<span class="lr-type" :class="e.ghost ? 't-g' : 't-s'">{{ e.ghost ? '⚡ Ghost' : 'Single'
}}</span>
<span class="lr-gap">{{ e.gap }}</span>
</div>
</div>
</div>
</div>

<!-- DETAILED STATS -->
<div class="collapsible">
<button class="c-toggle" @click="statsOpen = !statsOpen">
<span>Detailed Stats</span>
<span class="arrow" :class="{open:statsOpen}">▼</span>
</button>
<div class="c-body" :class="{open:statsOpen}">
<div class="stats-grid">
<div>
<div class="sg-label">Total Clicks</div>
<div class="sg-val">{{ totalClicks }}</div>
</div>
<div>
<div class="sg-label">Ghost Double-Clicks</div>
<div class="sg-val" :style="{color: totalGhosts > 0 ? 'var(--bad)' : 'var(--text)'}">{{
totalGhosts }}</div>
</div>
<div>
<div class="sg-label">Overall Fault Rate</div>
<div class="sg-val">{{ faultRate }}</div>
</div>
<div>
<div class="sg-label">Fastest Ghost Gap</div>
<div class="sg-val">{{ fastestGhost }}</div>
</div>
</div>
</div>
</div>

<!-- SETTINGS -->
<div class="collapsible">
<button class="c-toggle" @click="settingsOpen = !settingsOpen">
<span>Settings</span>
<span class="arrow" :class="{open:settingsOpen}">▼</span>
</button>
<div class="c-body" :class="{open:settingsOpen}">
<div class="settings-body">
<div>
<div class="sl">
<span>Ghost detection threshold</span>
<span class="sl-val">{{ threshold }}ms</span>
</div>
<input type="range" min="80" max="600" step="10" v-model.number="threshold">
<div class="sl-desc">Two clicks arriving within this window count as a ghost fault. Windows
OS
default is 500ms; most hardware faults produce gaps under 200ms. Recommended: 250–350ms.
</div>
</div>
</div>
</div>
</div>

<div class="bottom-row">
<button class="btn-reset" @click="reset">↺ Reset All</button>
</div>

</div>

<script src="script.js"></script>
</body>

</html>
<script src="script.js"></script>
</body>

</html>