aki-info-detect 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  /*!
2
- * aki-info-detect v2.0.0
2
+ * aki-info-detect v2.0.1
3
3
  * (c) 2025 lacvietanh
4
4
  * Released under the MIT License
5
- * https://github.com/lacvietanh/akiInfoDetect.js
5
+ * https://akiinfodetect-js.pages.dev/
6
6
  */
7
7
  function parseUserAgent(ua = navigator.userAgent) {
8
8
  let os = null;
@@ -1,9 +1,9 @@
1
1
  !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).akiInfoDetect={})}(this,function(e){"use strict";
2
2
  /*!
3
- * aki-info-detect v2.0.0
3
+ * aki-info-detect v2.0.1
4
4
  * (c) 2025 lacvietanh
5
5
  * Released under the MIT License
6
- * https://github.com/lacvietanh/akiInfoDetect.js
6
+ * https://akiinfodetect-js.pages.dev/
7
7
  */function t(e=navigator.userAgent){let t=null,n="",r="",i="",o="";const a=[{test:/Edg(?:e)?\//,name:"Edge",regex:/Edg(?:e)?\/([\d.]+)/},{test:/OPR\/|Opera\//,name:"Opera",regex:/(?:OPR|Opera)\/([\d.]+)/},{test:/Firefox\//,name:"Firefox",regex:/Firefox\/([\d.]+)/,exclude:/Seamonkey/},{test:/Chrome\//,name:"Chrome",regex:/Chrome\/([\d.]+)/,exclude:/Edge|Edg|OPR|Opera/},{test:/Safari\//,name:"Safari",regex:/Version\/([\d.]+)/,exclude:/Chrome|Edge|Edg|OPR|Opera/},{test:/MSIE|Trident/,name:"IE",regex:/(?:MSIE |rv:)([\d.]+)/}];for(const{test:c,name:u,regex:l,exclude:d}of a)if(c.test(e)&&(!d||!d.test(e))){n=u;const t=e.match(l);r=(null==t?void 0:t[1])||"";break}const s=[{test:/Windows/,parse:()=>{const t=e.match(/Windows NT ([\d.]+)/),n=(null==t?void 0:t[1])||"",r={"10.0":"10",6.3:"8.1",6.2:"8",6.1:"7","6.0":"Vista",5.1:"XP"}[n]||n;return{family:`Windows ${r}`,version:r,architecture:/WOW64|Win64|x64/.test(e)?64:32}}},{test:/Mac OS X/,parse:()=>{var t;const n=e.match(/Mac OS X ([\d_.]+)/),r=(null==(t=null==n?void 0:n[1])?void 0:t.replace(/_/g,"."))||"";return{family:`macOS ${r}`,version:r,architecture:64}}},{test:/Android/,parse:()=>{const t=e.match(/Android ([\d.]+)/),n=(null==t?void 0:t[1])||"";return{family:`Android ${n}`,version:n,architecture:64}}},{test:/iPhone|iPad|iPod/,parse:()=>{var t;const n=e.match(/OS ([\d_]+)/),r=(null==(t=null==n?void 0:n[1])?void 0:t.replace(/_/g,"."))||"";return{family:`iOS ${r}`,version:r,architecture:64}}},{test:/Linux/,parse:()=>({family:"Linux",version:"",architecture:/x86_64|amd64/.test(e)?64:32})}];for(const{test:c,parse:u}of s)if(c.test(e)){t=u();break}if(/iPhone/.test(e))i="iPhone",o="Apple";else if(/iPad/.test(e))i="iPad",o="Apple";else if(/Android/.test(e)){const t=e.match(/Android[^;]+;\s*([^;)]+)/);if(t){i=t[1].trim();const e=i.match(/^(Samsung|LG|Motorola|HTC|Huawei|Xiaomi|OnePlus|Google|Sony|OPPO|Vivo|Realme)/i);o=(null==e?void 0:e[1])||""}}return{name:n,version:r,product:i,manufacturer:o,os:t,layout:/AppleWebKit/.test(e)?"WebKit":/Gecko\//.test(e)?"Gecko":/Trident/.test(e)?"Trident":""}}async function n(){var e;if(!(null==(e=navigator.userAgentData)?void 0:e.getHighEntropyValues))return{};try{return await navigator.userAgentData.getHighEntropyValues(["platform","platformVersion","architecture","model","mobile","bitness","brands","fullVersionList"])}catch{return{}}}function r(){try{const e=document.createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl");if(!t)return"No WebGL support";const n=t.getExtension("WEBGL_debug_renderer_info");return n?t.getParameter(n.UNMASKED_RENDERER_WEBGL)||"Unknown GPU":"GPU info restricted"}catch{return"GPU detection failed"}}const i={IP:"",ISP:"",country:"",lastUpdated:0,TTL:36e5};async function o(e,t=2500){const n=new AbortController,r=setTimeout(()=>n.abort(),t);try{const t=await fetch(e,{signal:n.signal});return clearTimeout(r),t.ok?await t.json():null}catch{return clearTimeout(r),null}}async function a(e=!1){if(!e&&i.IP&&Date.now()-i.lastUpdated<i.TTL)return{IP:i.IP,ISP:i.ISP,country:i.country};const t=[{url:"https://ipinfo.io/json",parse:e=>({IP:e.ip,ISP:e.org||"",country:e.country||""})},{url:"https://ipwhois.app/json/",parse:e=>({IP:e.ip,ISP:e.isp||"",country:e.country_code||""})},{url:"https://api.ipify.org?format=json",parse:e=>({IP:e.ip,ISP:"",country:""})}];for(const{url:n,parse:r}of t){const e=await o(n);if(e){const t=r(e);if(t.IP)return i.IP=t.IP,i.ISP=t.ISP||i.ISP,i.country=t.country||i.country,i.lastUpdated=Date.now(),t}}return{IP:i.IP,ISP:i.ISP,country:i.country}}async function s(e=!1){return(await a(e)).IP}async function c(e=!1){return(await a(e)).ISP}async function u(e=!1){return(await a(e)).country}function l(){const e=navigator.connection||navigator.mozConnection||navigator.webkitConnection;return e?{type:e.type||"unknown",effectiveType:e.effectiveType||"unknown",downlink:e.downlink||0,rtt:e.rtt||0,saveData:e.saveData||!1}:null}function d(e={timeout:1e4,enableHighAccuracy:!1}){return new Promise(t=>{navigator.geolocation?navigator.geolocation.getCurrentPosition(e=>t({latitude:e.coords.latitude,longitude:e.coords.longitude,accuracy:e.coords.accuracy,timestamp:e.timestamp}),()=>t(null),e):t(null)})}const g=i;function m(){var e;const{screen:t}=window;return{width:t.width,height:t.height,availWidth:t.availWidth,availHeight:t.availHeight,colorDepth:t.colorDepth,pixelRatio:window.devicePixelRatio||1,orientation:(null==(e=t.orientation)?void 0:e.type)||(window.innerWidth>window.innerHeight?"landscape-primary":"portrait-primary")}}async function p(){if(!("getBattery"in navigator))return{isCharging:!1,level:0,chargingTime:1/0,dischargingTime:1/0};try{const e=await navigator.getBattery();return{isCharging:e.charging,level:Math.round(100*e.level),chargingTime:e.chargingTime,dischargingTime:e.dischargingTime}}catch{return{isCharging:!1,level:0,chargingTime:1/0,dischargingTime:1/0}}}
8
8
  /**
9
9
  * @fileoverview aki-info-detect - Browser information detection library
@@ -0,0 +1,1074 @@
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>aki-info-detect | Device Detection Library</title>
7
+ <meta name="description" content="Lightweight JavaScript library for detecting device, browser, hardware, and network information">
8
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+ <style>
13
+ :root {
14
+ --bg-primary: #050506;
15
+ --bg-secondary: #0c0c0e;
16
+ --bg-card: #111114;
17
+ --bg-hover: #18181c;
18
+ --border: #1e1e23;
19
+ --border-light: #2a2a32;
20
+ --text-primary: #f5f5f7;
21
+ --text-secondary: #8a8a93;
22
+ --text-muted: #55555d;
23
+ --accent: #fbbf24;
24
+ --accent-secondary: #f59e0b;
25
+ --accent-dim: #a16207;
26
+ --accent-glow: rgba(251, 191, 36, 0.08);
27
+ --success: #4ade80;
28
+ --error: #f87171;
29
+ }
30
+
31
+ * { box-sizing: border-box; margin: 0; padding: 0; }
32
+
33
+ html { font-size: 15px; }
34
+
35
+ body {
36
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
37
+ background: var(--bg-primary);
38
+ color: var(--text-primary);
39
+ min-height: 100vh;
40
+ line-height: 1.6;
41
+ -webkit-font-smoothing: antialiased;
42
+ }
43
+
44
+ .bg-gradient {
45
+ position: fixed;
46
+ inset: 0;
47
+ background:
48
+ radial-gradient(ellipse 80% 50% at 20% -10%, rgba(251, 191, 36, 0.08) 0%, transparent 50%),
49
+ radial-gradient(ellipse 60% 40% at 80% 110%, rgba(245, 158, 11, 0.06) 0%, transparent 50%);
50
+ pointer-events: none;
51
+ z-index: -2;
52
+ }
53
+
54
+ .bg-grid {
55
+ position: fixed;
56
+ inset: 0;
57
+ background-image:
58
+ linear-gradient(rgba(251, 191, 36, 0.02) 1px, transparent 1px),
59
+ linear-gradient(90deg, rgba(251, 191, 36, 0.02) 1px, transparent 1px);
60
+ background-size: 80px 80px;
61
+ pointer-events: none;
62
+ z-index: -1;
63
+ }
64
+
65
+ .container {
66
+ width: 100%;
67
+ max-width: 1400px;
68
+ margin: 0 auto;
69
+ padding: 28px 20px 50px;
70
+ }
71
+
72
+ /* Header */
73
+ header {
74
+ text-align: center;
75
+ margin-bottom: 28px;
76
+ padding-bottom: 24px;
77
+ border-bottom: 1px solid var(--border);
78
+ }
79
+
80
+ .logo {
81
+ display: inline-flex;
82
+ align-items: center;
83
+ gap: 14px;
84
+ margin-bottom: 10px;
85
+ }
86
+
87
+ .logo-icon {
88
+ width: 44px;
89
+ height: 44px;
90
+ background: linear-gradient(135deg, var(--accent) 0%, var(--accent-dim) 100%);
91
+ border-radius: 12px;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ font-size: 22px;
96
+ box-shadow: 0 0 30px rgba(251, 191, 36, 0.2);
97
+ }
98
+
99
+ h1 {
100
+ font-size: 1.8rem;
101
+ font-weight: 600;
102
+ letter-spacing: -0.03em;
103
+ background: linear-gradient(135deg, var(--text-primary) 0%, var(--text-secondary) 100%);
104
+ -webkit-background-clip: text;
105
+ -webkit-text-fill-color: transparent;
106
+ }
107
+
108
+ .subtitle {
109
+ color: var(--text-secondary);
110
+ font-size: 1rem;
111
+ font-weight: 400;
112
+ margin-top: 4px;
113
+ }
114
+
115
+ .header-meta {
116
+ display: flex;
117
+ gap: 16px;
118
+ justify-content: center;
119
+ align-items: center;
120
+ margin-top: 16px;
121
+ flex-wrap: wrap;
122
+ }
123
+
124
+ .badges {
125
+ display: flex;
126
+ gap: 6px;
127
+ flex-wrap: wrap;
128
+ }
129
+ .badges img { height: 20px; opacity: 0.7; transition: opacity 0.2s; }
130
+ .badges a:hover img { opacity: 1; }
131
+
132
+ .install-box {
133
+ background: var(--bg-card);
134
+ border: 1px solid var(--border);
135
+ border-radius: 8px;
136
+ padding: 8px 14px;
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 10px;
140
+ }
141
+
142
+ .install-box code {
143
+ font-family: 'JetBrains Mono', monospace;
144
+ font-size: 0.9rem;
145
+ color: var(--accent);
146
+ }
147
+
148
+ .install-box button {
149
+ background: transparent;
150
+ border: 1px solid var(--border);
151
+ color: var(--text-muted);
152
+ padding: 5px 11px;
153
+ border-radius: 5px;
154
+ cursor: pointer;
155
+ font-size: 0.8rem;
156
+ transition: all 0.2s;
157
+ }
158
+ .install-box button:hover {
159
+ border-color: var(--accent);
160
+ color: var(--accent);
161
+ }
162
+
163
+ /* Tabs */
164
+ .tabs {
165
+ display: flex;
166
+ gap: 4px;
167
+ border-bottom: 1px solid var(--border);
168
+ margin-bottom: 24px;
169
+ }
170
+
171
+ .tab {
172
+ background: transparent;
173
+ border: none;
174
+ color: var(--text-secondary);
175
+ padding: 10px 20px;
176
+ cursor: pointer;
177
+ font-size: 0.95rem;
178
+ font-weight: 500;
179
+ transition: all 0.2s;
180
+ border-bottom: 2px solid transparent;
181
+ font-family: inherit;
182
+ }
183
+
184
+ .tab:hover { color: var(--text-primary); }
185
+ .tab.active {
186
+ color: var(--accent);
187
+ border-bottom-color: var(--accent);
188
+ }
189
+
190
+ .tab-content {
191
+ display: none;
192
+ }
193
+
194
+ .tab-content.active {
195
+ display: block;
196
+ animation: fadeIn 0.3s ease;
197
+ }
198
+
199
+ @keyframes fadeIn {
200
+ from { opacity: 0; transform: translateY(10px); }
201
+ to { opacity: 1; transform: translateY(0); }
202
+ }
203
+
204
+ /* Grid (Demo tab) */
205
+ .grid {
206
+ display: grid;
207
+ grid-template-columns: repeat(1, 1fr);
208
+ gap: 14px;
209
+ }
210
+
211
+ @media (min-width: 640px) {
212
+ .grid { grid-template-columns: repeat(2, 1fr); }
213
+ }
214
+
215
+ @media (min-width: 1024px) {
216
+ .grid { grid-template-columns: repeat(3, 1fr); }
217
+ }
218
+
219
+ /* Cards */
220
+ .card {
221
+ background: var(--bg-card);
222
+ border: 1px solid var(--border);
223
+ border-radius: 10px;
224
+ overflow: hidden;
225
+ transition: all 0.25s ease;
226
+ position: relative;
227
+ }
228
+
229
+ .card::before {
230
+ content: '';
231
+ position: absolute;
232
+ top: 0;
233
+ left: 0;
234
+ right: 0;
235
+ height: 1px;
236
+ background: linear-gradient(90deg, transparent, rgba(251, 191, 36, 0.3), transparent);
237
+ opacity: 0;
238
+ transition: opacity 0.25s;
239
+ }
240
+
241
+ .card:hover {
242
+ border-color: var(--border-light);
243
+ transform: translateY(-2px);
244
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
245
+ }
246
+
247
+ .card:hover::before { opacity: 1; }
248
+
249
+ .card-header {
250
+ padding: 14px 16px 12px;
251
+ border-bottom: 1px solid var(--border);
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 11px;
255
+ }
256
+
257
+ .card-icon {
258
+ width: 30px;
259
+ height: 30px;
260
+ background: var(--accent-glow);
261
+ border: 1px solid rgba(251, 191, 36, 0.2);
262
+ border-radius: 7px;
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ font-size: 14px;
267
+ }
268
+
269
+ .card-title {
270
+ font-size: 0.95rem;
271
+ font-weight: 500;
272
+ color: var(--text-primary);
273
+ }
274
+
275
+ .card-body { padding: 4px 0; }
276
+
277
+ .row {
278
+ display: flex;
279
+ justify-content: space-between;
280
+ align-items: center;
281
+ padding: 9px 16px;
282
+ border-bottom: 1px solid var(--border);
283
+ transition: background 0.15s;
284
+ }
285
+ .row:last-child { border-bottom: none; }
286
+ .row:hover { background: var(--bg-hover); }
287
+
288
+ .label {
289
+ font-size: 0.9rem;
290
+ color: var(--text-secondary);
291
+ font-weight: 400;
292
+ }
293
+
294
+ .value {
295
+ font-family: 'JetBrains Mono', monospace;
296
+ font-size: 0.85rem;
297
+ color: var(--text-primary);
298
+ text-align: right;
299
+ max-width: 60%;
300
+ word-break: break-word;
301
+ }
302
+ .value.loading { color: var(--accent); }
303
+ .value.error { color: var(--error); }
304
+ .value.success { color: var(--success); }
305
+ .value.muted { color: var(--text-muted); }
306
+
307
+ .actions {
308
+ padding: 12px 16px;
309
+ display: flex;
310
+ gap: 8px;
311
+ }
312
+
313
+ button {
314
+ background: transparent;
315
+ border: 1px solid var(--border);
316
+ color: var(--text-secondary);
317
+ padding: 8px 14px;
318
+ border-radius: 6px;
319
+ cursor: pointer;
320
+ font-size: 0.85rem;
321
+ font-weight: 500;
322
+ transition: all 0.2s;
323
+ font-family: inherit;
324
+ }
325
+ button:hover {
326
+ border-color: var(--accent);
327
+ color: var(--accent);
328
+ background: var(--accent-glow);
329
+ }
330
+ button:active { transform: scale(0.98); }
331
+
332
+ .output {
333
+ grid-column: 1 / -1;
334
+ background: var(--bg-card);
335
+ border: 1px solid var(--border);
336
+ border-radius: 10px;
337
+ margin-top: 10px;
338
+ overflow: hidden;
339
+ }
340
+
341
+ .output-header {
342
+ padding: 12px 16px;
343
+ border-bottom: 1px solid var(--border);
344
+ display: flex;
345
+ align-items: center;
346
+ justify-content: space-between;
347
+ }
348
+
349
+ .output-title {
350
+ font-size: 0.9rem;
351
+ font-weight: 500;
352
+ color: var(--text-secondary);
353
+ display: flex;
354
+ align-items: center;
355
+ gap: 8px;
356
+ }
357
+
358
+ .output-body {
359
+ padding: 14px 16px;
360
+ max-height: 280px;
361
+ overflow: auto;
362
+ }
363
+
364
+ pre {
365
+ font-family: 'JetBrains Mono', monospace;
366
+ font-size: 0.8rem;
367
+ line-height: 1.7;
368
+ color: var(--text-muted);
369
+ white-space: pre-wrap;
370
+ word-break: break-word;
371
+ }
372
+
373
+ /* Documentation styles */
374
+ .docs-content {
375
+ max-width: 900px;
376
+ }
377
+
378
+ .docs-content h2 {
379
+ font-size: 1.5rem;
380
+ margin-bottom: 12px;
381
+ color: var(--accent);
382
+ font-weight: 600;
383
+ }
384
+
385
+ .docs-content h3 {
386
+ font-size: 1.15rem;
387
+ margin: 20px 0 10px;
388
+ color: var(--text-primary);
389
+ font-weight: 500;
390
+ }
391
+
392
+ .docs-content p {
393
+ margin-bottom: 12px;
394
+ color: var(--text-secondary);
395
+ font-size: 0.95rem;
396
+ line-height: 1.7;
397
+ }
398
+
399
+ .docs-content ul {
400
+ margin: 10px 0 20px 20px;
401
+ color: var(--text-secondary);
402
+ }
403
+
404
+ .docs-content li {
405
+ margin: 6px 0;
406
+ font-size: 0.95rem;
407
+ }
408
+
409
+ .docs-content code {
410
+ background: var(--bg-card);
411
+ border: 1px solid var(--border);
412
+ padding: 2px 6px;
413
+ border-radius: 4px;
414
+ font-family: 'JetBrains Mono', monospace;
415
+ font-size: 0.85rem;
416
+ color: var(--accent);
417
+ }
418
+
419
+ .docs-content pre {
420
+ background: var(--bg-card);
421
+ border: 1px solid var(--border);
422
+ padding: 14px;
423
+ border-radius: 8px;
424
+ margin: 12px 0 20px;
425
+ overflow-x: auto;
426
+ }
427
+
428
+ .docs-content pre code {
429
+ background: none;
430
+ border: none;
431
+ padding: 0;
432
+ color: var(--text-muted);
433
+ }
434
+
435
+ .note-box {
436
+ background: linear-gradient(135deg, rgba(251, 191, 36, 0.04) 0%, rgba(251, 191, 36, 0.02) 100%);
437
+ border: 1px solid rgba(251, 191, 36, 0.15);
438
+ border-radius: 8px;
439
+ padding: 14px 16px;
440
+ margin: 16px 0;
441
+ }
442
+
443
+ .note-box strong {
444
+ color: var(--accent);
445
+ font-size: 0.9rem;
446
+ }
447
+
448
+ .feature-grid {
449
+ display: grid;
450
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
451
+ gap: 12px;
452
+ margin: 20px 0;
453
+ }
454
+
455
+ .feature-item {
456
+ background: var(--bg-card);
457
+ border: 1px solid var(--border);
458
+ border-radius: 8px;
459
+ padding: 12px;
460
+ transition: border-color 0.2s;
461
+ }
462
+
463
+ .feature-item:hover { border-color: var(--border-light); }
464
+
465
+ .feature-item strong {
466
+ color: var(--accent);
467
+ font-size: 0.9rem;
468
+ }
469
+
470
+ /* Footer */
471
+ footer {
472
+ text-align: center;
473
+ margin-top: 32px;
474
+ padding-top: 20px;
475
+ border-top: 1px solid var(--border);
476
+ }
477
+
478
+ .footer-links {
479
+ display: flex;
480
+ gap: 24px;
481
+ justify-content: center;
482
+ margin-bottom: 10px;
483
+ }
484
+
485
+ .footer-links a {
486
+ color: var(--text-secondary);
487
+ text-decoration: none;
488
+ font-size: 0.9rem;
489
+ font-weight: 500;
490
+ transition: color 0.2s;
491
+ display: flex;
492
+ align-items: center;
493
+ gap: 6px;
494
+ }
495
+ .footer-links a:hover { color: var(--accent); }
496
+
497
+ .footer-credit {
498
+ font-size: 0.85rem;
499
+ color: var(--text-muted);
500
+ }
501
+ .footer-credit a { color: var(--accent); text-decoration: none; }
502
+
503
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
504
+ ::-webkit-scrollbar-track { background: transparent; }
505
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
506
+ ::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
507
+
508
+ @media (max-width: 639px) {
509
+ html { font-size: 14px; }
510
+ .container { padding: 20px 14px 40px; }
511
+ header { margin-bottom: 20px; padding-bottom: 20px; }
512
+ h1 { font-size: 1.5rem; }
513
+ .header-meta { flex-direction: column; gap: 12px; }
514
+ .tabs { overflow-x: auto; }
515
+ .tab { padding: 10px 16px; font-size: 0.9rem; }
516
+ }
517
+ </style>
518
+ </head>
519
+ <body>
520
+ <div class="bg-gradient"></div>
521
+ <div class="bg-grid"></div>
522
+
523
+ <div class="container">
524
+ <header>
525
+ <div class="logo">
526
+ <div class="logo-icon">⚡</div>
527
+ <h1>aki-info-detect</h1>
528
+ </div>
529
+ <p class="subtitle">Lightweight browser, device & network detection library</p>
530
+
531
+ <div class="header-meta">
532
+ <div class="install-box">
533
+ <code>npm i aki-info-detect</code>
534
+ <button onclick="copyInstall()">Copy</button>
535
+ </div>
536
+ <div class="badges">
537
+ <a href="https://www.npmjs.com/package/aki-info-detect"><img src="https://img.shields.io/npm/v/aki-info-detect?color=f59e0b&label=npm&style=flat-square" alt="npm"></a>
538
+ <a href="https://bundlephobia.com/package/aki-info-detect"><img src="https://img.shields.io/bundlephobia/minzip/aki-info-detect?color=22c55e&label=gzip&style=flat-square" alt="size"></a>
539
+ <a href="https://github.com/lacvietanh/akiInfoDetect.js"><img src="https://img.shields.io/github/stars/lacvietanh/akiInfoDetect.js?style=flat-square&color=8b5cf6" alt="stars"></a>
540
+ </div>
541
+ </div>
542
+ </header>
543
+
544
+ <!-- Tabs -->
545
+ <div class="tabs">
546
+ <button class="tab active" data-tab="demo">Demo</button>
547
+ <button class="tab" data-tab="docs">Documentation</button>
548
+ <button class="tab" data-tab="api">API Reference</button>
549
+ </div>
550
+
551
+ <!-- Demo Tab -->
552
+ <div class="tab-content active" id="demo">
553
+ <div class="note-box">
554
+ <strong>⚠️ Server Headers Required:</strong> For deep hardware detection via Client Hints API, configure:
555
+ <code>Accept-CH: Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness</code>
556
+ </div>
557
+
558
+ <div class="grid">
559
+ <!-- Browser -->
560
+ <div class="card">
561
+ <div class="card-header">
562
+ <div class="card-icon">🌐</div>
563
+ <div class="card-title">Browser</div>
564
+ </div>
565
+ <div class="card-body">
566
+ <div class="row"><span class="label">Name</span><span class="value" id="browser">—</span></div>
567
+ <div class="row"><span class="label">Product</span><span class="value" id="product">—</span></div>
568
+ <div class="row"><span class="label">Mobile</span><span class="value" id="isMobile">—</span></div>
569
+ <div class="row"><span class="label">Language</span><span class="value" id="language">—</span></div>
570
+ </div>
571
+ </div>
572
+
573
+ <!-- OS -->
574
+ <div class="card">
575
+ <div class="card-header">
576
+ <div class="card-icon">💻</div>
577
+ <div class="card-title">Operating System</div>
578
+ </div>
579
+ <div class="card-body">
580
+ <div class="row"><span class="label">Name</span><span class="value" id="osName">—</span></div>
581
+ <div class="row"><span class="label">Version</span><span class="value" id="osVersion">—</span></div>
582
+ <div class="row"><span class="label">Platform</span><span class="value" id="osString">—</span></div>
583
+ </div>
584
+ </div>
585
+
586
+ <!-- CPU -->
587
+ <div class="card">
588
+ <div class="card-header">
589
+ <div class="card-icon">⚙️</div>
590
+ <div class="card-title">Processor</div>
591
+ </div>
592
+ <div class="card-body">
593
+ <div class="row"><span class="label">CPU</span><span class="value" id="cpu">—</span></div>
594
+ <div class="row"><span class="label">Cores</span><span class="value" id="cpuCore">—</span></div>
595
+ <div class="row"><span class="label">Architecture</span><span class="value" id="arch">—</span></div>
596
+ </div>
597
+ </div>
598
+
599
+ <!-- Memory & GPU -->
600
+ <div class="card">
601
+ <div class="card-header">
602
+ <div class="card-icon">🎮</div>
603
+ <div class="card-title">Memory & Graphics</div>
604
+ </div>
605
+ <div class="card-body">
606
+ <div class="row"><span class="label">RAM</span><span class="value" id="ram">—</span></div>
607
+ <div class="row"><span class="label">GPU</span><span class="value" id="gpu">—</span></div>
608
+ <div class="row"><span class="label">Manufacturer</span><span class="value" id="manufacturer">—</span></div>
609
+ </div>
610
+ </div>
611
+
612
+ <!-- Battery -->
613
+ <div class="card">
614
+ <div class="card-header">
615
+ <div class="card-icon">🔋</div>
616
+ <div class="card-title">Battery</div>
617
+ </div>
618
+ <div class="card-body">
619
+ <div class="row"><span class="label">Level</span><span class="value" id="batteryLevel">—</span></div>
620
+ <div class="row"><span class="label">Charging</span><span class="value" id="charging">—</span></div>
621
+ </div>
622
+ </div>
623
+
624
+ <!-- Screen -->
625
+ <div class="card">
626
+ <div class="card-header">
627
+ <div class="card-icon">📱</div>
628
+ <div class="card-title">Display</div>
629
+ </div>
630
+ <div class="card-body">
631
+ <div class="row"><span class="label">Resolution</span><span class="value" id="resolution">—</span></div>
632
+ <div class="row"><span class="label">Pixel Ratio</span><span class="value" id="pixelRatio">—</span></div>
633
+ <div class="row"><span class="label">Color Depth</span><span class="value" id="colorDepth">—</span></div>
634
+ </div>
635
+ </div>
636
+
637
+ <!-- Connection -->
638
+ <div class="card">
639
+ <div class="card-header">
640
+ <div class="card-icon">📶</div>
641
+ <div class="card-title">Connection</div>
642
+ </div>
643
+ <div class="card-body">
644
+ <div class="row"><span class="label">Type</span><span class="value" id="connType">—</span></div>
645
+ <div class="row"><span class="label">Downlink</span><span class="value" id="downlink">—</span></div>
646
+ <div class="row"><span class="label">RTT</span><span class="value" id="rtt">—</span></div>
647
+ </div>
648
+ </div>
649
+
650
+ <!-- Network -->
651
+ <div class="card">
652
+ <div class="card-header">
653
+ <div class="card-icon">🌍</div>
654
+ <div class="card-title">Network</div>
655
+ </div>
656
+ <div class="card-body">
657
+ <div class="row"><span class="label">IP</span><span class="value loading" id="ip">Click fetch</span></div>
658
+ <div class="row"><span class="label">ISP</span><span class="value" id="isp">—</span></div>
659
+ <div class="row"><span class="label">Country</span><span class="value" id="country">—</span></div>
660
+ </div>
661
+ <div class="actions">
662
+ <button onclick="fetchNetwork()">Fetch Network</button>
663
+ </div>
664
+ </div>
665
+
666
+ <!-- Location -->
667
+ <div class="card">
668
+ <div class="card-header">
669
+ <div class="card-icon">📍</div>
670
+ <div class="card-title">Location</div>
671
+ </div>
672
+ <div class="card-body">
673
+ <div class="row"><span class="label">Latitude</span><span class="value" id="lat">—</span></div>
674
+ <div class="row"><span class="label">Longitude</span><span class="value" id="lng">—</span></div>
675
+ <div class="row"><span class="label">Accuracy</span><span class="value" id="accuracy">—</span></div>
676
+ </div>
677
+ <div class="actions">
678
+ <button onclick="fetchLocation()">Get Location</button>
679
+ </div>
680
+ </div>
681
+
682
+ <!-- Raw Output -->
683
+ <div class="output">
684
+ <div class="output-header">
685
+ <div class="output-title">
686
+ <span>📋</span>
687
+ <span>Raw JSON Output</span>
688
+ </div>
689
+ <button onclick="copyJSON()">Copy</button>
690
+ </div>
691
+ <div class="output-body">
692
+ <pre id="rawOutput">Loading...</pre>
693
+ </div>
694
+ </div>
695
+ </div>
696
+ </div>
697
+
698
+ <!-- Documentation Tab -->
699
+ <div class="tab-content" id="docs">
700
+ <div class="docs-content">
701
+ <h2>Features</h2>
702
+ <div class="feature-grid">
703
+ <div class="feature-item">
704
+ <strong>🌐 Browser Detection</strong>
705
+ <p style="font-size: 0.85rem; margin-top: 4px;">Name, version, rendering engine</p>
706
+ </div>
707
+ <div class="feature-item">
708
+ <strong>💻 OS Detection</strong>
709
+ <p style="font-size: 0.85rem; margin-top: 4px;">Windows, macOS, Linux, iOS, Android</p>
710
+ </div>
711
+ <div class="feature-item">
712
+ <strong>⚙️ Hardware Info</strong>
713
+ <p style="font-size: 0.85rem; margin-top: 4px;">CPU cores, RAM, GPU, Apple Silicon</p>
714
+ </div>
715
+ <div class="feature-item">
716
+ <strong>🌍 Network Info</strong>
717
+ <p style="font-size: 0.85rem; margin-top: 4px;">Public IP, ISP, country (cached)</p>
718
+ </div>
719
+ <div class="feature-item">
720
+ <strong>📱 Screen Info</strong>
721
+ <p style="font-size: 0.85rem; margin-top: 4px;">Resolution, pixel ratio, orientation</p>
722
+ </div>
723
+ <div class="feature-item">
724
+ <strong>🔋 Battery Status</strong>
725
+ <p style="font-size: 0.85rem; margin-top: 4px;">Charging state, level percentage</p>
726
+ </div>
727
+ </div>
728
+
729
+ <h2>Installation</h2>
730
+
731
+ <h3>Package Manager</h3>
732
+ <pre><code>npm install aki-info-detect
733
+ # or
734
+ yarn add aki-info-detect
735
+ # or
736
+ pnpm add aki-info-detect</code></pre>
737
+
738
+ <h3>CDN (ES Module)</h3>
739
+ <pre><code>&lt;script type="module"&gt;
740
+ import akiInfoDetect from 'https://unpkg.com/aki-info-detect/dist/aki-info-detect.js';
741
+ const info = await akiInfoDetect();
742
+ console.log(info);
743
+ &lt;/script&gt;</code></pre>
744
+
745
+ <h3>CDN (UMD)</h3>
746
+ <pre><code>&lt;script src="https://unpkg.com/aki-info-detect/dist/aki-info-detect.umd.cjs"&gt;&lt;/script&gt;
747
+ &lt;script&gt;
748
+ akiInfoDetect().then(info => console.log(info));
749
+ &lt;/script&gt;</code></pre>
750
+
751
+ <h2>Quick Start</h2>
752
+ <pre><code>import akiInfoDetect from 'aki-info-detect';
753
+
754
+ // Get all info
755
+ const info = await akiInfoDetect();
756
+ console.log(info.browser); // "Chrome 120.0"
757
+ console.log(info.os.name); // "macOS"
758
+ console.log(info.CPU); // "Apple M3 Pro"
759
+
760
+ // Tree-shakeable imports
761
+ import { detectBrowser, detectOS } from 'aki-info-detect';
762
+ const browser = detectBrowser();
763
+ const os = detectOS();</code></pre>
764
+
765
+ <h2>Server Configuration</h2>
766
+ <p>For deep hardware detection, configure your server to send Client Hints headers:</p>
767
+
768
+ <h3>Express.js</h3>
769
+ <pre><code>app.use((req, res, next) => {
770
+ res.setHeader('Accept-CH',
771
+ 'Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, ' +
772
+ 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness'
773
+ );
774
+ next();
775
+ });</code></pre>
776
+
777
+ <h3>Nginx</h3>
778
+ <pre><code>add_header Accept-CH "Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness";</code></pre>
779
+
780
+ <h3>Vite</h3>
781
+ <pre><code>// vite.config.js
782
+ export default {
783
+ server: {
784
+ headers: {
785
+ 'Accept-CH': 'Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, ...'
786
+ }
787
+ }
788
+ };</code></pre>
789
+
790
+ <h2>Bundle Size</h2>
791
+ <ul>
792
+ <li><strong>ES Module:</strong> 14.85 kB</li>
793
+ <li><strong>UMD:</strong> 9.38 kB</li>
794
+ <li><strong>Gzipped:</strong> ~3.8 kB</li>
795
+ </ul>
796
+
797
+ <h2>Browser Support</h2>
798
+ <p>All modern browsers with ES2020+ support:</p>
799
+ <ul>
800
+ <li>Chrome/Edge 90+</li>
801
+ <li>Firefox 88+</li>
802
+ <li>Safari 14+</li>
803
+ <li>Opera 76+</li>
804
+ </ul>
805
+ </div>
806
+ </div>
807
+
808
+ <!-- API Reference Tab -->
809
+ <div class="tab-content" id="api">
810
+ <div class="docs-content">
811
+ <h2>API Reference</h2>
812
+
813
+ <h3>Default Export: <code>akiInfoDetect()</code></h3>
814
+ <p>Returns a Promise that resolves to an object containing all detected information.</p>
815
+ <pre><code>const info = await akiInfoDetect();</code></pre>
816
+
817
+ <h3>Return Object</h3>
818
+ <pre><code>{
819
+ // Browser info
820
+ browser: string, // "Chrome 120.0"
821
+ product: string, // "Gecko"
822
+ manufacturer: string, // "Google Inc."
823
+ isMobile: boolean, // true/false
824
+ language: string, // "en-US"
825
+
826
+ // OS info
827
+ os: {
828
+ name: string, // "macOS"
829
+ version: string, // "14.1.2"
830
+ string: string // "macOS 14.1.2"
831
+ },
832
+
833
+ // Hardware
834
+ CPU: string, // "Apple M3 Pro"
835
+ CPUCore: number, // 12
836
+ arch: string, // "arm64"
837
+ RAM: number, // 16 (GB)
838
+ GPU: string, // "Apple M3 Pro"
839
+
840
+ // Battery
841
+ battery: {
842
+ isCharging: boolean, // true/false
843
+ level: number // 0-100
844
+ },
845
+
846
+ // Methods
847
+ getScreen: () => Object,
848
+ getConnection: () => Object,
849
+ getIP: () => Promise&lt;string&gt;,
850
+ getISP: () => Promise&lt;string&gt;,
851
+ getCountry: () => Promise&lt;string&gt;,
852
+ getLocation: () => Promise&lt;Object&gt;
853
+ }</code></pre>
854
+
855
+ <h3>Named Exports</h3>
856
+
857
+ <h4><code>detectBrowser()</code></h4>
858
+ <p>Returns browser name and version.</p>
859
+ <pre><code>import { detectBrowser } from 'aki-info-detect';
860
+ const browser = detectBrowser(); // "Chrome 120.0"</code></pre>
861
+
862
+ <h4><code>detectOS()</code></h4>
863
+ <p>Returns OS information.</p>
864
+ <pre><code>import { detectOS } from 'aki-info-detect';
865
+ const os = detectOS();
866
+ // { name: "macOS", version: "14.1.2", string: "macOS 14.1.2" }</code></pre>
867
+
868
+ <h4><code>getHardwareInfo()</code></h4>
869
+ <p>Returns CPU, GPU, RAM information (async).</p>
870
+ <pre><code>import { getHardwareInfo } from 'aki-info-detect';
871
+ const hw = await getHardwareInfo();
872
+ // { CPU: "...", cores: 12, arch: "...", RAM: 16, GPU: "..." }</code></pre>
873
+
874
+ <h4><code>getNetworkInfo()</code></h4>
875
+ <p>Returns network information with caching (async).</p>
876
+ <pre><code>import { getNetworkInfo } from 'aki-info-detect';
877
+ const net = await getNetworkInfo();
878
+ // { ip: "...", isp: "...", country: "..." }</code></pre>
879
+
880
+ <h4><code>getScreen()</code></h4>
881
+ <p>Returns screen dimensions and properties.</p>
882
+ <pre><code>import { getScreen } from 'aki-info-detect';
883
+ const screen = getScreen();
884
+ // { width: 1920, height: 1080, pixelRatio: 2, ... }</code></pre>
885
+
886
+ <h4><code>getBattery()</code></h4>
887
+ <p>Returns battery status (async).</p>
888
+ <pre><code>import { getBattery } from 'aki-info-detect';
889
+ const battery = await getBattery();
890
+ // { isCharging: true, level: 85 }</code></pre>
891
+
892
+ <h3>Caching</h3>
893
+ <p>Network requests (IP, ISP, Country) are cached for 1 hour to reduce API calls.</p>
894
+ <pre><code>// First call: fetches from API
895
+ const ip1 = await info.getIP();
896
+
897
+ // Within 1 hour: returns cached value
898
+ const ip2 = await info.getIP();</code></pre>
899
+
900
+ <h3>Error Handling</h3>
901
+ <pre><code>try {
902
+ const info = await akiInfoDetect();
903
+ console.log(info.browser);
904
+ } catch (error) {
905
+ console.error('Detection failed:', error);
906
+ }</code></pre>
907
+
908
+ <h3>TypeScript Support</h3>
909
+ <p>Full TypeScript declarations included.</p>
910
+ <pre><code>import akiInfoDetect, { type DetectionResult } from 'aki-info-detect';
911
+
912
+ const info: DetectionResult = await akiInfoDetect();</code></pre>
913
+ </div>
914
+ </div>
915
+
916
+ <footer>
917
+ <div class="footer-links">
918
+ <a href="https://github.com/lacvietanh/akiInfoDetect.js">
919
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
920
+ GitHub
921
+ </a>
922
+ <a href="https://www.npmjs.com/package/aki-info-detect">
923
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"/></svg>
924
+ npm
925
+ </a>
926
+ <a href="https://bundlephobia.com/package/aki-info-detect">
927
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>
928
+ Size
929
+ </a>
930
+ </div>
931
+ <p class="footer-credit">MIT License · Made with ⚡ by <a href="https://github.com/lacvietanh">lacvietanh</a></p>
932
+ </footer>
933
+ </div>
934
+
935
+ <script type="module">
936
+ // Tab switching
937
+ document.querySelectorAll('.tab').forEach(tab => {
938
+ tab.addEventListener('click', () => {
939
+ const targetId = tab.dataset.tab;
940
+
941
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
942
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
943
+
944
+ tab.classList.add('active');
945
+ document.getElementById(targetId).classList.add('active');
946
+ });
947
+ });
948
+
949
+ // Library loading
950
+ const isDev = location.port === '3000';
951
+ const module = isDev
952
+ ? await import('/src/index.js')
953
+ : await import('./aki-info-detect.js');
954
+
955
+ const akiInfoDetect = module.default;
956
+ let info = null;
957
+ let rawData = null;
958
+
959
+ async function init() {
960
+ try {
961
+ info = await akiInfoDetect();
962
+
963
+ document.getElementById('browser').textContent = info.browser || '—';
964
+ document.getElementById('product').textContent = info.product || '—';
965
+ document.getElementById('isMobile').textContent = info.isMobile ? 'Yes' : 'No';
966
+ document.getElementById('language').textContent = info.language || '—';
967
+
968
+ document.getElementById('osName').textContent = info.os?.name || '—';
969
+ document.getElementById('osVersion').textContent = info.os?.version || '—';
970
+ document.getElementById('osString').textContent = info.os?.string || '—';
971
+
972
+ document.getElementById('cpu').textContent = info.CPU || '—';
973
+ document.getElementById('cpuCore').textContent = info.CPUCore ? `${info.CPUCore} cores` : '—';
974
+ document.getElementById('arch').textContent = info.arch || '—';
975
+
976
+ document.getElementById('ram').textContent = info.RAM ? `${info.RAM} GB` : '—';
977
+ document.getElementById('gpu').textContent = info.GPU || '—';
978
+ document.getElementById('manufacturer').textContent = info.manufacturer || '—';
979
+
980
+ if (info.battery?.level > 0) {
981
+ document.getElementById('batteryLevel').textContent = `${info.battery.level}%`;
982
+ document.getElementById('charging').textContent = info.battery.isCharging ? 'Yes ⚡' : 'No';
983
+ } else {
984
+ document.getElementById('batteryLevel').textContent = 'N/A';
985
+ document.getElementById('batteryLevel').className = 'value muted';
986
+ document.getElementById('charging').textContent = 'N/A';
987
+ document.getElementById('charging').className = 'value muted';
988
+ }
989
+
990
+ const screen = info.getScreen();
991
+ document.getElementById('resolution').textContent = `${screen.width} × ${screen.height}`;
992
+ document.getElementById('pixelRatio').textContent = `${screen.pixelRatio}x`;
993
+ document.getElementById('colorDepth').textContent = `${screen.colorDepth}-bit`;
994
+
995
+ const conn = info.getConnection();
996
+ document.getElementById('connType').textContent = conn.type !== 'unknown' ? conn.type : '—';
997
+ document.getElementById('downlink').textContent = conn.downlink ? `${conn.downlink} Mbps` : '—';
998
+ document.getElementById('rtt').textContent = conn.rtt ? `${conn.rtt} ms` : '—';
999
+
1000
+ rawData = { ...info };
1001
+ delete rawData.getIP;
1002
+ delete rawData.getISP;
1003
+ delete rawData.getCountry;
1004
+ delete rawData.getConnection;
1005
+ delete rawData.getScreen;
1006
+ delete rawData.getLocation;
1007
+ rawData.screen = screen;
1008
+ rawData.connection = conn;
1009
+ document.getElementById('rawOutput').textContent = JSON.stringify(rawData, null, 2);
1010
+
1011
+ } catch (err) {
1012
+ document.getElementById('rawOutput').textContent = 'Error: ' + err.message;
1013
+ console.error(err);
1014
+ }
1015
+ }
1016
+
1017
+ window.fetchNetwork = async function() {
1018
+ if (!info) return;
1019
+ document.getElementById('ip').className = 'value loading';
1020
+ document.getElementById('ip').textContent = 'Fetching...';
1021
+ try {
1022
+ const [ip, isp, country] = await Promise.all([
1023
+ info.getIP(),
1024
+ info.getISP(),
1025
+ info.getCountry()
1026
+ ]);
1027
+ document.getElementById('ip').className = 'value';
1028
+ document.getElementById('ip').textContent = ip || '—';
1029
+ document.getElementById('isp').textContent = isp || '—';
1030
+ document.getElementById('country').textContent = country || '—';
1031
+ } catch (err) {
1032
+ document.getElementById('ip').className = 'value error';
1033
+ document.getElementById('ip').textContent = 'Error';
1034
+ }
1035
+ };
1036
+
1037
+ window.fetchLocation = async function() {
1038
+ if (!info) return;
1039
+ document.getElementById('lat').className = 'value loading';
1040
+ document.getElementById('lat').textContent = 'Getting...';
1041
+ try {
1042
+ const loc = await info.getLocation();
1043
+ document.getElementById('lat').className = 'value';
1044
+ document.getElementById('lat').textContent = loc.latitude?.toFixed(6) || '—';
1045
+ document.getElementById('lng').textContent = loc.longitude?.toFixed(6) || '—';
1046
+ document.getElementById('accuracy').textContent = loc.accuracy ? `±${loc.accuracy}m` : '—';
1047
+ } catch (err) {
1048
+ document.getElementById('lat').className = 'value error';
1049
+ document.getElementById('lat').textContent = 'Denied';
1050
+ document.getElementById('lng').textContent = '—';
1051
+ document.getElementById('accuracy').textContent = '—';
1052
+ }
1053
+ };
1054
+
1055
+ window.copyInstall = function() {
1056
+ navigator.clipboard.writeText('npm install aki-info-detect');
1057
+ const btn = document.querySelector('.install-box button');
1058
+ btn.textContent = 'Copied!';
1059
+ setTimeout(() => btn.textContent = 'Copy', 1500);
1060
+ };
1061
+
1062
+ window.copyJSON = function() {
1063
+ if (rawData) {
1064
+ navigator.clipboard.writeText(JSON.stringify(rawData, null, 2));
1065
+ const btn = document.querySelector('.output button');
1066
+ btn.textContent = 'Copied!';
1067
+ setTimeout(() => btn.textContent = 'Copy', 1500);
1068
+ }
1069
+ };
1070
+
1071
+ init();
1072
+ </script>
1073
+ </body>
1074
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aki-info-detect",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Lightweight JavaScript library for detecting device, browser, hardware, and network information with UMD/ESM support",
5
5
  "author": "lacvietanh",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/lacvietanh/akiInfoDetect.js/issues"
13
13
  },
14
- "homepage": "https://github.com/lacvietanh/akiInfoDetect.js#readme",
14
+ "homepage": "https://akiinfodetect-js.pages.dev/",
15
15
  "keywords": [
16
16
  "device-detection",
17
17
  "browser-detection",