Skip to content

Commit 054f975

Browse files
authored
Merge pull request #413 from Girish0902/DIV
Device Information Viewer
2 parents d86f1c0 + 5d28fad commit 054f975

6 files changed

Lines changed: 370 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Device Information Viewer
2+
3+
A hardware diagnostics dashboard using native Web APIs, built with vanilla HTML5, CSS3, and JavaScript (ES6+).
4+
5+
## Features
6+
7+
- **OS & Browser Detection** — Parses `navigator.userAgent` to identify Windows/macOS/Linux/Android/iOS and Chrome/Firefox/Safari/Edge. Reads `navigator.hardwareConcurrency` for CPU thread count.
8+
- **Screen & Viewport** — Live tracking of screen dimensions, viewport size, device pixel ratio, color depth, and orientation via `resize`/`orientationchange` event listeners with neon flash indicators.
9+
- **Battery API** — Reads `navigator.getBattery()` for charge level (with color-coded fill bar), charging state, and discharge time. Updates live via `chargingchange`/`levelchange` events.
10+
- **Network API** — Reads `navigator.connection` for effective type (4G, WiFi, Ethernet) and downlink speed with live change listener.
11+
- **Clipboard Export** — Gathers all diagnostics into a structured JSON object and copies via `navigator.clipboard.writeText()` with a green toast confirmation.
12+
- **Live Clock** — Updates the client timestamp every second.
13+
14+
## UI Theme
15+
16+
Hardware engineering diagnostic matrix: `#05060b` backdrop, glassmorphic cards, neon cyan data values, auto-fit responsive grid.
17+
18+
## Usage
19+
20+
Open `index.html`. All probes fire on boot. Click **Trigger Hardware Probe Refreshes** to re-poll all values. Click **Copy Diagnostic Digest JSON to Clipboard** to export.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Device Information Viewer</title>
7+
<link rel="stylesheet" href="style.css">
8+
</head>
9+
<body>
10+
<div id="app">
11+
<div id="topBar">
12+
<div id="brand">Device Diagnostics</div>
13+
<div class="tel-item"><span class="tel-label">Integrity</span><span id="telIntegrity" class="tel-value status-ok">Nominal</span></div>
14+
<div class="tel-item"><span class="tel-label">Engine</span><span id="telEngine" class="tel-value"></span></div>
15+
<div class="tel-item"><span class="tel-label">Time</span><span id="telTime" class="tel-value"></span></div>
16+
</div>
17+
18+
<div id="grid">
19+
<div class="card"><span class="card-title">Operating System</span><span id="dOS" class="card-value"></span></div>
20+
<div class="card"><span class="card-title">Browser</span><span id="dBrowser" class="card-value"></span></div>
21+
<div class="card"><span class="card-title">CPU Threads</span><span id="dCPU" class="card-value"></span></div>
22+
<div class="card"><span class="card-title">Screen</span><span id="dScreen" class="card-value"></span></div>
23+
<div class="card"><span class="card-title">Viewport</span><span id="dViewport" class="card-value"></span></div>
24+
<div class="card"><span class="card-title">Pixel Ratio</span><span id="dDPR" class="card-value"></span></div>
25+
<div class="card"><span class="card-title">Color Depth</span><span id="dColor" class="card-value"></span></div>
26+
<div class="card"><span class="card-title">Orientation</span><span id="dOrientation" class="card-value"></span></div>
27+
<div class="card" id="batteryCard"><span class="card-title">Battery</span>
28+
<div class="battery-row"><span id="bLevel" class="batt-val"></span><div class="batt-bar"><div id="bFill" class="batt-fill"></div></div></div>
29+
<div class="batt-sub"><span id="bCharging"></span> · <span id="bDischarge"></span></div>
30+
</div>
31+
<div class="card"><span class="card-title">Network</span><span id="dNetType" class="card-value"></span><span id="dNetSpeed" class="card-sub"></span></div>
32+
<div class="card"><span class="card-title">Platform Arch</span><span id="dArch" class="card-value"></span></div>
33+
</div>
34+
35+
<div id="footer">
36+
<button id="refreshBtn">Trigger Hardware Probe Refresh</button>
37+
<button id="copyBtn">Copy Diagnostic Digest JSON to Clipboard</button>
38+
</div>
39+
40+
<div id="toast" class="hidden"></div>
41+
</div>
42+
43+
<script src="script.js"></script>
44+
</body>
45+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"title": "Device Information Viewer",
3+
"description": "A hardware diagnostics dashboard that reads OS, browser, CPU threads, screen/viewport geometry, battery status, network info via native Web APIs. Features live resize tracking, clipboard JSON export, and a dark cybernetic UI.",
4+
"author": { "name": "Girish Madarkar", "github": "Girish0902" },
5+
"tags": ["device-info", "diagnostics", "hardware", "battery-api", "network-api", "vanilla-js"],
6+
"entry": "index.html",
7+
"thumbnail": "thumbnail.svg"
8+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
(function () {
2+
/* ---- Elements ---- */
3+
var telIntegrity = document.getElementById('telIntegrity');
4+
var telEngine = document.getElementById('telEngine');
5+
var telTime = document.getElementById('telTime');
6+
var dOS = document.getElementById('dOS');
7+
var dBrowser = document.getElementById('dBrowser');
8+
var dCPU = document.getElementById('dCPU');
9+
var dScreen = document.getElementById('dScreen');
10+
var dViewport = document.getElementById('dViewport');
11+
var dDPR = document.getElementById('dDPR');
12+
var dColor = document.getElementById('dColor');
13+
var dOrientation = document.getElementById('dOrientation');
14+
var bLevel = document.getElementById('bLevel');
15+
var bFill = document.getElementById('bFill');
16+
var bCharging = document.getElementById('bCharging');
17+
var bDischarge = document.getElementById('bDischarge');
18+
var dNetType = document.getElementById('dNetType');
19+
var dNetSpeed = document.getElementById('dNetSpeed');
20+
var dArch = document.getElementById('dArch');
21+
var refreshBtn = document.getElementById('refreshBtn');
22+
var copyBtn = document.getElementById('copyBtn');
23+
var toast = document.getElementById('toast');
24+
25+
/* ---- Clock ---- */
26+
function updateClock() {
27+
telTime.textContent = new Date().toLocaleTimeString();
28+
}
29+
30+
/* ---- UA parsing ---- */
31+
function parseOS(ua) {
32+
ua = ua.toLowerCase();
33+
if (ua.indexOf('windows nt 10') !== -1) return 'Windows 10/11';
34+
if (ua.indexOf('windows nt 6.3') !== -1) return 'Windows 8.1';
35+
if (ua.indexOf('windows nt 6.1') !== -1) return 'Windows 7';
36+
if (ua.indexOf('windows') !== -1) return 'Windows';
37+
if (ua.indexOf('mac os x') !== -1) {
38+
var m = ua.match(/mac os x ([\d_]+)/);
39+
return 'macOS ' + (m ? m[1].replace(/_/g, '.') : '');
40+
}
41+
if (ua.indexOf('android') !== -1) return 'Android';
42+
if (ua.indexOf('linux') !== -1) return 'Linux';
43+
if (ua.indexOf('iphone') !== -1 || ua.indexOf('ipad') !== -1) return 'iOS';
44+
return 'Unknown';
45+
}
46+
47+
function parseBrowser(ua) {
48+
ua = ua.toLowerCase();
49+
if (ua.indexOf('edg/') !== -1 || ua.indexOf('edge/') !== -1) return 'Edge';
50+
if (ua.indexOf('opr/') !== -1 || ua.indexOf('opera') !== -1) return 'Opera';
51+
if (ua.indexOf('chrome/') !== -1 && ua.indexOf('safari') !== -1) return 'Chrome';
52+
if (ua.indexOf('firefox/') !== -1) return 'Firefox';
53+
if (ua.indexOf('safari/') !== -1 && ua.indexOf('chrome') === -1) return 'Safari';
54+
return 'Unknown';
55+
}
56+
57+
function getEngine() {
58+
var ua = navigator.userAgent.toLowerCase();
59+
if (ua.indexOf('edg/') !== -1) return 'EdgeHTML/Blink';
60+
if (ua.indexOf('chrome/') !== -1) return 'Blink/V8';
61+
if (ua.indexOf('firefox/') !== -1) return 'Gecko/SpiderMonkey';
62+
if (ua.indexOf('safari/') !== -1 && ua.indexOf('chrome') === -1) return 'WebKit/JavaScriptCore';
63+
return 'Unknown';
64+
}
65+
66+
function getArch() {
67+
var ua = navigator.userAgent.toLowerCase();
68+
if (ua.indexOf('x64') !== -1 || ua.indexOf('wow64') !== -1) return 'x86-64';
69+
if (ua.indexOf('arm64') !== -1 || ua.indexOf('aarch64') !== -1) return 'ARM64';
70+
if (ua.indexOf('arm') !== -1) return 'ARM';
71+
if (navigator.platform) {
72+
if (navigator.platform.indexOf('Win64') !== -1) return 'x86-64';
73+
if (navigator.platform.indexOf('Win32') !== -1) return 'x86';
74+
}
75+
return 'Unknown';
76+
}
77+
78+
/* ---- Screen ---- */
79+
function updateScreen() {
80+
dScreen.textContent = screen.width + '\u00D7' + screen.height;
81+
dViewport.textContent = window.innerWidth + '\u00D7' + window.innerHeight;
82+
dDPR.textContent = window.devicePixelRatio.toFixed(2);
83+
dColor.textContent = screen.colorDepth + '-bit';
84+
var orient = screen.orientation ? screen.orientation.type : (window.innerWidth > window.innerHeight ? 'landscape' : 'portrait');
85+
dOrientation.textContent = orient;
86+
}
87+
88+
/* ---- Battery ---- */
89+
function setupBattery() {
90+
if (!navigator.getBattery) {
91+
bLevel.textContent = 'N/A'; bFill.style.width = '0%';
92+
bCharging.textContent = 'API unavailable'; return;
93+
}
94+
navigator.getBattery().then(function (bat) {
95+
function update() {
96+
var lvl = Math.round(bat.level * 100);
97+
bLevel.textContent = lvl + '%';
98+
bFill.style.width = lvl + '%';
99+
bFill.style.background = lvl < 20 ? '#ef4444' : lvl < 50 ? '#f59e0b' : '#10b981';
100+
bCharging.textContent = bat.charging ? '\u26A1 Charging' : '\uD83D\uDD0B Discharging';
101+
bDischarge.textContent = bat.charging
102+
? (bat.chargingTime === Infinity ? 'calculating\u2026' : Math.round(bat.chargingTime / 60) + ' min to full')
103+
: (bat.dischargingTime === Infinity ? 'not discharging' : Math.round(bat.dischargingTime / 60) + ' min remaining');
104+
}
105+
update();
106+
bat.addEventListener('chargingchange', update);
107+
bat.addEventListener('levelchange', update);
108+
});
109+
}
110+
111+
/* ---- Network ---- */
112+
function updateNetwork() {
113+
var conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
114+
if (conn) {
115+
dNetType.textContent = conn.effectiveType ? conn.effectiveType.toUpperCase() : 'Unknown';
116+
dNetSpeed.textContent = (conn.downlink || '?') + ' Mbps';
117+
conn.addEventListener('change', function () {
118+
dNetType.textContent = conn.effectiveType ? conn.effectiveType.toUpperCase() : 'Unknown';
119+
dNetSpeed.textContent = (conn.downlink || '?') + ' Mbps';
120+
});
121+
} else {
122+
dNetType.textContent = 'API unavailable';
123+
dNetSpeed.textContent = '';
124+
}
125+
}
126+
127+
/* ---- Full probe ---- */
128+
function probe() {
129+
var ua = navigator.userAgent;
130+
dOS.textContent = parseOS(ua);
131+
dBrowser.textContent = parseBrowser(ua);
132+
dCPU.textContent = (navigator.hardwareConcurrency || '?') + ' logical cores';
133+
dArch.textContent = getArch();
134+
telEngine.textContent = getEngine();
135+
updateScreen();
136+
updateNetwork();
137+
updateClock();
138+
telIntegrity.textContent = 'Nominal';
139+
telIntegrity.className = 'tel-value status-ok';
140+
}
141+
142+
/* ---- Copy JSON ---- */
143+
function copyDigest() {
144+
var digest = {
145+
os: dOS.textContent,
146+
browser: dBrowser.textContent,
147+
engine: telEngine.textContent,
148+
cpu: dCPU.textContent,
149+
architecture: dArch.textContent,
150+
screen: dScreen.textContent,
151+
viewport: dViewport.textContent,
152+
pixelRatio: dDPR.textContent,
153+
colorDepth: dColor.textContent,
154+
orientation: dOrientation.textContent,
155+
battery: bLevel.textContent,
156+
charging: bCharging.textContent,
157+
networkType: dNetType.textContent,
158+
networkSpeed: dNetSpeed.textContent,
159+
timestamp: new Date().toISOString(),
160+
};
161+
162+
var text = JSON.stringify(digest, null, 2);
163+
if (navigator.clipboard && navigator.clipboard.writeText) {
164+
navigator.clipboard.writeText(text).then(function () {
165+
showToast('\u2705 Diagnostic JSON copied to clipboard');
166+
}).catch(function () {
167+
fallbackCopy(text);
168+
});
169+
} else {
170+
fallbackCopy(text);
171+
}
172+
}
173+
174+
function fallbackCopy(text) {
175+
var ta = document.createElement('textarea');
176+
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
177+
document.body.appendChild(ta); ta.select();
178+
try { document.execCommand('copy'); showToast('\u2705 Diagnostic JSON copied to clipboard'); } catch (e) { showToast('\u274C Failed to copy'); }
179+
document.body.removeChild(ta);
180+
}
181+
182+
function showToast(msg) {
183+
toast.textContent = msg;
184+
toast.className = 'show';
185+
setTimeout(function () { toast.className = 'hidden'; }, 2500);
186+
}
187+
188+
/* ---- Events ---- */
189+
window.addEventListener('resize', function () {
190+
updateScreen();
191+
/* subtle flash: add/remove a class */
192+
dViewport.style.transition = 'color 0.15s';
193+
dViewport.style.color = '#ff2a5f';
194+
setTimeout(function () { dViewport.style.color = ''; }, 200);
195+
});
196+
197+
window.addEventListener('orientationchange', function () {
198+
setTimeout(updateScreen, 300);
199+
});
200+
201+
refreshBtn.addEventListener('click', function () {
202+
probe();
203+
setupBattery();
204+
});
205+
206+
copyBtn.addEventListener('click', copyDigest);
207+
208+
/* ---- Boot ---- */
209+
probe();
210+
setupBattery();
211+
setInterval(updateClock, 1000);
212+
})();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
2+
html,body{height:100%;background:#05060b;color:#e2e8f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;font-size:13px;-webkit-font-smoothing:antialiased;overflow-x:hidden}
3+
#app{min-height:100vh;display:flex;flex-direction:column;gap:10px;padding:14px;max-width:960px;margin:0 auto;position:relative}
4+
5+
#topBar{display:flex;align-items:center;gap:8px 18px;padding:10px 16px;background:rgba(255,255,255,0.01);border:1px solid rgba(255,255,255,0.03);border-radius:10px;backdrop-filter:blur(12px);flex-wrap:wrap}
6+
#brand{font-size:13px;font-weight:700;color:#00f0ff;white-space:nowrap;margin-right:auto}
7+
.tel-item{display:flex;align-items:center;gap:5px;font-size:10px}
8+
.tel-label{color:#475569;text-transform:uppercase;letter-spacing:0.3px}
9+
.tel-value{font-weight:700;font-family:'SF Mono','Consolas',monospace;font-size:10px;color:#00f0ff}
10+
.tel-value.status-ok{color:#10b981}
11+
12+
#grid{flex:1;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:8px}
13+
14+
.card{background:rgba(255,255,255,0.01);border:1px solid rgba(255,255,255,0.03);border-radius:8px;padding:14px 16px;backdrop-filter:blur(12px);display:flex;flex-direction:column;gap:6px;transition:opacity 0.3s}
15+
.card-title{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;color:#475569}
16+
.card-value{font-size:15px;font-weight:700;font-family:'SF Mono','Consolas',monospace;color:#00f0ff;word-break:break-all}
17+
.card-sub{font-size:11px;font-family:monospace;color:#64748b}
18+
19+
.battery-row{display:flex;align-items:center;gap:8px}
20+
.batt-val{font-size:22px;font-weight:700;font-family:monospace;color:#00f0ff;min-width:40px}
21+
.batt-bar{flex:1;height:6px;background:rgba(255,255,255,0.04);border-radius:4px;overflow:hidden}
22+
.batt-fill{height:100%;width:0%;background:#10b981;border-radius:4px;transition:width 0.3s}
23+
.batt-sub{font-size:10px;color:#64748b;font-family:monospace}
24+
25+
#footer{display:flex;gap:8px;flex-wrap:wrap}
26+
#footer button{flex:1;min-width:120px;padding:9px 14px;border-radius:6px;font-size:10px;font-weight:600;cursor:pointer;font-family:inherit;border:1px solid rgba(255,255,255,0.03);background:rgba(255,255,255,0.02);color:#e2e8f0;transition:all 0.12s}
27+
#footer button:hover{border-color:#00f0ff;color:#00f0ff}
28+
29+
#toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%);padding:10px 24px;border-radius:6px;font-size:12px;font-weight:600;z-index:100;transition:opacity 0.3s;pointer-events:none}
30+
#toast.hidden{opacity:0}
31+
#toast.show{opacity:1;background:rgba(16,185,129,0.1);border:1px solid rgba(16,185,129,0.2);color:#34d399}
32+
33+
@media (max-width:480px){
34+
#app{padding:8px;gap:6px}
35+
#topBar{gap:4px 10px;padding:8px 10px}
36+
#brand{width:100%}
37+
.card-value{font-size:13px}
38+
}

0 commit comments

Comments
 (0)