PageSourceSearch

https://hackertyper.com/script.js

js hackertyper.com collected 2026-10-03 20:09:35 UTC 43,331 bytes, 1,388 lines download raw bytes

1// Ctrl+<letter> sponsor slots. One table, one handler (Typer.openShortcut):
2// add or retire a slot here, never in the keydown chain. Chrome, Edge and
3// Firefox keep Ctrl+T, Ctrl+N and Ctrl+W for themselves, so those letters can
4// never reach the page - DocuTyper lives on Ctrl+D for that reason.
5// "tab: true" opens an ordinary tab; the rest open the classic 1200x800 window.
6// Ctrl+C / Cmd+C is reserved for real copying (see addText) - never put a slot on C.
7var SHORTCUTS = {
8  A: { url: "https://angel.co/l/2xHh3M", title: "Find Start Up Jobs Near You" },
9  B: { url: "https://belkin.evyy.net/ORAXJK", title: "Belkin: Tech Made Easy" },
10  D: { url: "https://docutyper.com/?utm_source=ht-key", title: "DocuTyper", tab: true },
11  E: { url: "https://visualsitesearch.com", title: "vss" },
12  // find-dental-plans.com does not answer on https (checked 2026-09-04).
13  F: { url: "http://find-dental-plans.com?t202id=53208", title: "find-dental-plans" },
14  G: { url: "https://robinhood.com/creditcard/?referral_code=e4748b98", title: "Enlisted" },
15  J: {
16    url: "https://202.redirexit.com/tracking202/redirect/rtr.php?t202id=13559&t202kw=ogx1-key",
17    title: "Opera GX: The Browser For Gamers",
18  },
19  L: { url: "https://lovable.dev/invite/2796cac3-b3ca-4dc0-bcc8-5d1b0f07ff0b", title: "Lovable" },
20  // O for "market Open". The browser's Ctrl+O file dialog is suppressed by the
21  // blanket preventDefault at the end of the keydown handler, same as Ctrl+S/P/D.
22  O: { url: "https://bellhour.com/?utm_source=ht-key", title: "BellHour", tab: true },
23  P: {
24    url: "https://202.redirexit.com/tracking202/redirect/dl.php?t202id=63448&t202kw=wowd1-key",
25    title: "World Of Warships",
26  },
27  R: { url: "https://tech.ck.page", title: "Develop a Career in Tech!" },
28  S: { url: "https://stargateasi.com/?utm_source=ht-key", title: "Stargate ASI", tab: true },
29};
30
31var Typer = {
32  text: null,
33  accessCountimer: null,
34  index: 0, // current cursor position
35  speed: 3, // speed of the Typer
36  file: "code.txt", //file, must be setted
37  accessCount: 0, //times alt is pressed for Access Granted
38  deniedCount: 0, //times caps is pressed for Access Denied
39  filesList: null, // loaded files list
40  databaseData: null, // loaded database data
41  init: function () {
42    // inizialize Hacker Typer
43    accessCountimer = setInterval(function () {
44      Typer.updLstChr();
45    }, 500); // inizialize timer for blinking cursor
46
47    // Embed the code directly for local testing
48    Typer.text = `
49
50struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
51
52struct group_info *groups_alloc(int gidsetsize){
53
54	struct group_info *group_info;
55
56	int nblocks;
57
58	int i;
59
60
61
62	nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
63
64	/* Make sure we always allocate at least one indirect block pointer */
65
66	nblocks = nblocks ? : 1;
67
68	group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
69
70	if (!group_info)
71
72		return NULL;
73
74	group_info->ngroups = gidsetsize;
75
76	group_info->nblocks = nblocks;
77
78	atomic_set(&group_info->usage, 1);
79
80
81
82	if (gidsetsize <= NGROUPS_SMALL)
83
84		group_info->blocks[0] = group_info->small_block;
85
86	else {
87
88		for (i = 0; i < nblocks; i++) {
89
90			gid_t *b;
91
92			b = (void *)__get_free_page(GFP_USER);
93
94			if (!b)
95
96				goto out_undo_partial_alloc;
97
98			group_info->blocks[i] = b;
99
100		}
101
102	}
103
104	return group_info;
105
106
107
108out_undo_partial_alloc:
109
110	while (--i >= 0) {
111
112		free_page((unsigned long)group_info->blocks[i]);
113
114	}
115
116	kfree(group_info);
117
118return NULL;
119
120}
121
122
123
124EXPORT_SYMBOL(groups_alloc);
125
126
127
128void groups_free(struct group_info *group_info)
129
130{
131
132	if (group_info->blocks[0] != group_info->small_block) {
133
134		int i;
135
136		for (i = 0; i < group_info->nblocks; i++)
137
138			free_page((unsigned long)group_info->blocks[i]);
139
140	}
141
142	kfree(group_info);
143
144}
145
146
147
148EXPORT_SYMBOL(groups_free);
149
150
151
152/* export the group_info to a user-space array */
153
154static int groups_to_user(gid_t __user *grouplist,
155
156			  const struct group_info *group_info)
157
158{
159
160	int i;
161
162	unsigned int count = group_info->ngroups;
163
164
165
166	for (i = 0; i < group_info->nblocks; i++) {
167
168		unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
169
170		unsigned int len = cp_count * sizeof(*grouplist);
171
172
173
174		if (copy_to_user(grouplist, group_info->blocks[i], len))
175
176			return -EFAULT;
177
178
179
180		grouplist += NGROUPS_PER_BLOCK;
181
182		count -= cp_count;
183
184	}
185
186	return 0;
187
188}
189
190
191
192/* fill a group_info from a user-space array - it must be allocated already */
193
194static int groups_from_user(struct group_info *group_info,
195
196    gid_t __user *grouplist)
197
198{
199
200	int i;
201
202	unsigned int count = group_info->ngroups;
203
204
205
206	for (i = 0; i < group_info->nblocks; i++) {
207
208		unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
209
210		unsigned int len = cp_count * sizeof(*grouplist);
211
212
213
214		if (copy_from_user(group_info->blocks[i], grouplist, len))
215
216			return -EFAULT;
217
218
219
220		grouplist += NGROUPS_PER_BLOCK;
221
222		count -= cp_count;
223
224	}
225
226	return 0;
227
228}
229
230
231
232/* a simple Shell sort */
233
234static void groups_sort(struct group_info *group_info)
235
236{
237
238	int base, max, stride;
239
240	int gidsetsize = group_info->ngroups;
241
242
243
244	for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
245
246		; /* nothing */
247
248	stride /= 3;
249
250
251
252	while (stride) {
253
254		max = gidsetsize - stride;
255
256		for (base = 0; base < max; base++) {
257
258			int left = base;
259
260			int right = left + stride;
261
262			gid_t tmp = GROUP_AT(group_info, right);
263
264
265
266			while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
267
268				GROUP_AT(group_info, right) =
269
270				    GROUP_AT(group_info, left);
271
272				right = left;
273
274				left -= stride;
275
276			}
277
278			GROUP_AT(group_info, right) = tmp;
279
280		}
281
282		stride /= 3;
283
284	}
285
286}
287
288
289
290/* a simple bsearch */
291
292int groups_search(const struct group_info *group_info, gid_t grp)
293
294{
295
296	unsigned int left, right;
297
298
299
300	if (!group_info)
301
302		return 0;
303
304
305
306	left = 0;
307
308	right = group_info->ngroups;
309
310	while (left < right) {
311
312		unsigned int mid = (left+right)/2;
313
314		if (grp > GROUP_AT(group_info, mid))
315
316			left = mid + 1;
317
318		else if (grp < GROUP_AT(group_info, mid))
319
320			right = mid;
321
322		else
323
324			return 1;
325
326	}
327
328	return 0;
329
330}
331
332
333
334/**
335
336 * set_groups - Change a group subscription in a set of credentials
337
338 * @new: The newly prepared set of credentials to alter
339
340 * @group_info: The group list to install
341
342 *
343
344 * Validate a group subscription and, if valid, insert it into a set
345
346 * of credentials.
347
348 */
349
350int set_groups(struct cred *new, struct group_info *group_info)
351
352{
353
354	put_group_info(new->group_info);
355
356	groups_sort(group_info);
357
358	get_group_info(group_info);
359
360	new->group_info = group_info;
361
362	return 0;
363
364}
365
366
367
368EXPORT_SYMBOL(set_groups);
369
370
371
372/**
373
374 * set_current_groups - Change current's group subscription
375
376 * @group_info: The group list to impose
377
378 *
379
380 * Validate a group subscription and, if valid, impose it upon current's task
381
382 * security record.
383
384 */
385
386int set_current_groups(struct group_info *group_info)
387
388{
389
390	struct cred *new;
391
392	int ret;
393
394
395
396	new = prepare_creds();
397
398	if (!new)
399
400		return -ENOMEM;
401
402
403
404	ret = set_groups(new, group_info);
405
406	if (ret < 0) {
407
408		abort_creds(new);
409
410		return ret;
411
412	}
413
414
415
416	return commit_creds(new);
417
418}
419
420
421
422EXPORT_SYMBOL(set_current_groups);
423
424
425
426SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
427
428{
429
430	const struct cred *cred = current_cred();
431
432	int i;
433
434
435
436	if (gidsetsize < 0)
437
438		return -EINVAL;
439
440
441
442	/* no need to grab task_lock here; it cannot change */
443
444	i = cred->group_info->ngroups;
445
446	if (gidsetsize) {
447
448		if (i > gidsetsize) {
449
450			i = -EINVAL;
451
452			goto out;
453
454		}
455
456		if (groups_to_user(grouplist, cred->group_info)) {
457
458			i = -EFAULT;
459
460			goto out;
461
462		}
463
464	}
465
466out:
467
468	return i;
469
470}
471
472
473
474/*
475
476 *	SMP: Our groups are copy-on-write. We can set them safely
477
478 *	without another task interfering.
479
480 */
481
482
483
484SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
485
486{
487
488	struct group_info *group_info;
489
490	int retval;
491
492
493
494	if (!nsown_capable(CAP_SETGID))
495
496		return -EPERM;
497
498	if ((unsigned)gidsetsize > NGROUPS_MAX)
499
500		return -EINVAL;
501
502
503
504	group_info = groups_alloc(gidsetsize);
505
506	if (!group_info)
507
508		return -ENOMEM;
509
510	retval = groups_from_user(group_info, grouplist);
511
512	if (retval) {
513
514		put_group_info(group_info);
515
516		return retval;
517
518	}
519
520
521
522	retval = set_current_groups(group_info);
523
524	put_group_info(group_info);
525
526
527
528	return retval;
529
530}
531
532
533
534/*
535
536 * Check whether we're fsgid/egid or in the supplemental group..
537
538 */
539
540int in_group_p(gid_t grp)
541
542{
543
544	const struct cred *cred = current_cred();
545
546	int retval = 1;
547
548
549
550	if (grp != cred->fsgid)
551
552		retval = groups_search(cred->group_info, grp);
553
554	return retval;
555
556}
557
558
559
560EXPORT_SYMBOL(in_group_p);
561
562
563
564int in_egroup_p(gid_t grp)
565
566{
567
568	const struct cred *cred = current_cred();
569
570	int retval = 1;
571
572
573
574	if (grp != cred->egid)
575
576		retval = groups_search(cred->group_info, grp);
577
578	return retval;
579
580}`;
581
582    // Also try to fetch code.txt for production use
583    fetch(Typer.file)
584      .then((response) => response.text())
585      .then((data) => {
586        if (data && data.length > 100) {
587          Typer.text = data;
588        }
589      })
590      .catch(() => {
591        // Use embedded fallback
592      });
593
594    // Load files list
595    fetch("files.txt")
596      .then((response) => response.text())
597      .then((data) => {
598        Typer.filesList = data.split('\n').filter(line => line.trim());
599      })
600      .catch(() => {
601        // Fallback files list
602        Typer.filesList = [
603          "/etc/passwd", "/etc/shadow", "/home/admin/.ssh/id_rsa", 
604          "/var/log/auth.log", "/root/.bash_history", "/etc/sudoers",
605          "/var/www/html/admin.php", "/tmp/exploit.sh", "/usr/bin/sudo",
606          "/home/user/documents/passwords.txt"
607        ];
608      });
609
610    // Load database data
611    fetch("database.txt")
612      .then((response) => response.text())
613      .then((data) => {
614        Typer.databaseData = data.split('\n').filter(line => line.trim());
615      })
616      .catch(() => {
617        // Fallback database data
618        Typer.databaseData = [
619          "admin:$2b$12$xyz789abc123:[email protected]:1",
620          "root:$2b$12$def456ghi789:[email protected]:0",
621          "jsmith:$2b$12$mno012pqr345:[email protected]:1001"
622        ];
623      });
624  },
625
626  content: function () {
627    return document.querySelector("#console").innerHTML; // get console content
628  },
629
630  write: function (str) {
631    // append to console content
632    document.querySelector("#console").innerHTML =
633      document.querySelector("#console").innerHTML + str;
634    return false;
635  },
636
637  makeAccess: function () {
638    //create Access Granted popUp
639    Typer.hidepop(); // hide all popups
640    Typer.accessCount = 0; //reset count
641    document.querySelector("#gran").style.display = "block";
642    return false;
643  },
644
645  makeDenied: function () {
646    //create Access Denied popUp
647    Typer.hidepop(); // hide all popups
648    Typer.deniedCount = 0; //reset count
649    document.querySelector("#deni").style.display = "block";
650    return false;
651  },
652
653  hidepop: function () {
654    // hide pop ups, and stop whatever the last effect was still doing
655    Typer.clearFx();
656    document.querySelector("#deni").style.display = "none";
657    document.querySelector("#gran").style.display = "none";
658    document.querySelector("#breach").style.display = "none";
659    document.querySelector("#camera").style.display = "none";
660    document.querySelector("#gps").style.display = "none";
661    document.querySelector("#filesystem").style.display = "none";
662    document.querySelector("#database").style.display = "none";
663  },
664
665  trackShortcut: function (label, url, via) {
666    // One event for every sponsor slot, keyboard or footer, so GTM can count
667    // clicks per slot. Fails silently: measurement never blocks the click.
668    try {
669      window.dataLayer = window.dataLayer || [];
670      window.dataLayer.push({ event: "sponsor_click", slot: label, slot_url: url, via: via });
671      if (typeof gtag !== "undefined") {
672        gtag("event", "sponsor_click", { slot: label, via: via });
673      }
674    } catch (e) {}
675  },
676
677  openShortcut: function (letter, via) {
678    var slot = SHORTCUTS[letter];
679    if (!slot) return false;
680    Typer.trackShortcut("Ctrl+" + letter + " " + slot.title, slot.url, via);
681    if (slot.tab) {
682      window.open(slot.url, "_blank");
683    } else {
684      window.open(slot.url, slot.title, "height=800,width=1200,menubar=1,status=1,scrollbars=1");
685    }
686    return true;
687  },
688
689  trackHackerEffect: function (action) {
690    // Track hacker effects usage in Google Analytics
691    try {
692      // Google Tag Manager / GA4 tracking
693      if (typeof gtag !== 'undefined') {
694        gtag('event', action, {
695          'event_category': 'Hacker_Effects'
696        });
697      }
698    } catch (e) {
699      // Fail silently if analytics not loaded
700      console.log('Analytics tracking failed:', e);
701    }
702  },
703
704  // Effect timers. Every timeout and interval an effect starts is registered
705  // here and cleared by hidepop (Escape, or the next effect), so a re-triggered
706  // effect never stacks on its previous run and nothing keeps ticking behind a
707  // hidden popup. Before this, each Shift+3 press added another GPS loop that
708  // ran for the life of the page.
709  fxTimers: [],
710  fxTimeout: function (fn, ms) { var id = setTimeout(fn, ms); Typer.fxTimers.push(id); return id; },
711  fxInterval: function (fn, ms) { var id = setInterval(fn, ms); Typer.fxTimers.push(id); return id; },
712  clearFx: function () {
713    Typer.fxTimers.forEach(function (id) { clearTimeout(id); clearInterval(id); });
714    Typer.fxTimers = [];
715  },
716
717  // Phase strings the effects write while they run. Locale pages carry them
718  // as effects.phases in their i18n table; the static Portuguese page and the
719  // English root use these. {n} is the count.
720  fxPhases: {
721    en: {
722      threatLevel: "Threat level:",
723      critical: "CRITICAL",
724      protocols: "⚠️ SECURITY PROTOCOLS ACTIVATED",
725      cameraLabel: "📷 Camera:",
726      initializing: "INITIALIZING",
727      active: "ACTIVE",
728      acquiring: "📱 Acquiring satellite lock",
729      locked: "📱 Satellite lock acquired",
730      tracking: "🛰️ TRACKING ACTIVE",
731      found: "Found:",
732      scanComplete: "📁 Scan complete. {n} files indexed.",
733      connected: "Connected to MySQL server",
734      executing: "Executing query",
735      streaming: "Streaming results",
736      completed: "✓ Query completed. {n} rows returned."
737    },
738    pt: {
739      threatLevel: "Nível de ameaça:",
740      critical: "CRÍTICO",
741      protocols: "⚠️ PROTOCOLOS DE SEGURANÇA ATIVADOS",
742      cameraLabel: "📷 Câmera:",
743      initializing: "INICIALIZANDO",
744      active: "ATIVA",
745      acquiring: "📱 Obtendo sinal de satélite",
746      locked: "📱 Sinal de satélite obtido",
747      tracking: "🛰️ RASTREAMENTO ATIVO",
748      found: "Encontrado:",
749      scanComplete: "📁 Varredura concluída. {n} arquivos indexados.",
750      connected: "Conectado ao servidor MySQL",
751      executing: "Executando consulta",
752      streaming: "Transmitindo resultados",
753      completed: "✓ Consulta concluída. {n} linhas retornadas."
754    }
755  },
756  fxText: function (key, n) {
757    var lang = (document.documentElement.lang || "en").toLowerCase().split("-")[0];
758    var table = null;
759    try {
760      if (typeof translations !== "undefined" && translations[lang] && translations[lang].effects) {
761        table = translations[lang].effects.phases;
762      }
763    } catch (e) {}
764    var s = (table && table[key]) || (Typer.fxPhases[lang] || {})[key] || Typer.fxPhases.en[key] || "";
765    return s.replace("{n}", n);
766  },
767  // Status lines are rewritten as an effect runs; remember what the page
768  // shipped so a second run starts from the beginning, not from the end.
769  fxReset: function (id) {
770    var el = document.getElementById(id);
771    if (!el) return null;
772    if (el.getAttribute("data-initial") === null) el.setAttribute("data-initial", el.innerHTML);
773    el.innerHTML = el.getAttribute("data-initial");
774    return el;
775  },
776  fxScroll: function (id) { var el = document.getElementById(id); if (el) el.scrollTop = el.scrollHeight; },
777  fxCoord: function (value, positive, negative) {
778    return Math.abs(value).toFixed(4) + "° " + (value >= 0 ? positive : negative);
779  },
780
781  makeSystemBreach: function () {
782    Typer.hidepop();
783    document.querySelector("#breach").style.display = "block";
784    Typer.trackHackerEffect("system_breach");
785    var status = Typer.fxReset("breach-status");
786    if (!status) return false;
787    Typer.fxTimeout(function () {
788      status.innerHTML = Typer.fxText("threatLevel") + " <span style='color:#FF6666'>" + Typer.fxText("critical") + "</span>";
789    }, 3000);
790    Typer.fxTimeout(function () { status.textContent = Typer.fxText("protocols"); }, 5000);
791    return false;
792  },
793
794  makeCameraAccess: function () {
795    Typer.hidepop();
796    document.querySelector("#camera").style.display = "block";
797    Typer.trackHackerEffect("camera_access");
798    var status = Typer.fxReset("camera-status");
799    var wave = document.getElementById("soundwave-display");
800    if (wave) wave.style.display = "none";
801    if (!status) return false;
802    Typer.fxTimeout(function () {
803      status.innerHTML = Typer.fxText("cameraLabel") + " <span style='color:#4a90e2'>" + Typer.fxText("initializing") + "</span>";
804    }, 2000);
805    Typer.fxTimeout(function () {
806      status.innerHTML = Typer.fxText("cameraLabel") + " <span style='color:#00FF00'>" + Typer.fxText("active") + "</span>";
807      if (wave) wave.style.display = "flex";
808    }, 4000);
809    return false;
810  },
811
812  makeGpsTracking: function () {
813    Typer.hidepop();
814    document.querySelector("#gps").style.display = "block";
815    Typer.trackHackerEffect("gps_tracking");
816
817    var cities = [
818      { lat: 37.7749, lng: -122.4194 }, { lat: 40.7128, lng: -74.0060 }, { lat: 51.5074, lng: -0.1278 },
819      { lat: 48.8566, lng: 2.3522 }, { lat: 35.6762, lng: 139.6503 }, { lat: -33.8688, lng: 151.2093 },
820      { lat: 55.7558, lng: 37.6173 }, { lat: 39.9042, lng: 116.4074 }, { lat: 52.5200, lng: 13.4050 },
821      { lat: 41.9028, lng: 12.4964 }, { lat: 19.4326, lng: -99.1332 }, { lat: -23.5505, lng: -46.6333 },
822      { lat: 28.6139, lng: 77.2090 }, { lat: 1.3521, lng: 103.8198 }, { lat: 25.2048, lng: 55.2708 }
823    ];
824    var city = cities[Math.floor(Math.random() * cities.length)];
825    var lat = city.lat + (Math.random() - 0.5) * 0.02;
826    var lng = city.lng + (Math.random() - 0.5) * 0.02;
827    var latEl = document.getElementById("lat"), lngEl = document.getElementById("lng");
828    var accEl = document.getElementById("accuracy");
829    var status = Typer.fxReset("gps-status");
830    // Hemisphere follows the sign: Sydney reads S and E, not a negative N.
831    function paint(spread) {
832      lat += (Math.random() - 0.5) * spread;
833      lng += (Math.random() - 0.5) * spread;
834      if (latEl) latEl.textContent = Typer.fxCoord(lat, "N", "S");
835      if (lngEl) lngEl.textContent = Typer.fxCoord(lng, "E", "W");
836      if (accEl) accEl.textContent = "±" + (Math.floor(Math.random() * 6) + 2) + "m";
837    }
838    paint(0);
839    var updates = 0;
840    var lock = Typer.fxInterval(function () {
841      paint(0.002);
842      updates++;
843      if (updates === 1 && status) status.textContent = Typer.fxText("locked");
844      if (updates >= 8) {
845        clearInterval(lock);
846        if (status) status.innerHTML = "<span style='color:#00FF00'>" + Typer.fxText("tracking") + "</span>";
847        var drift = function () {
848          paint(0.001);
849          Typer.fxTimeout(drift, Math.random() * 5000 + 5000);
850        };
851        Typer.fxTimeout(drift, 5000);
852      }
853    }, 1000);
854    return false;
855  },
856
857  makeFileSystem: function () {
858    Typer.hidepop();
859    var box = document.querySelector("#filesystem");
860    box.style.display = "block";
861    Typer.trackHackerEffect("file_scanner");
862    box.classList.add("scan-effect");
863    var list = Typer.fxReset("file-list");
864    if (!list) return false;
865    box.scrollTop = 0;
866
867    var files;
868    if (Typer.filesList && Typer.filesList.length > 0) {
869      var want = Math.min(Math.floor(Math.random() * 6) + 10, Typer.filesList.length);
870      var pool = Typer.filesList.slice();
871      files = [];
872      while (files.length < want) files.push(pool.splice(Math.floor(Math.random() * pool.length), 1)[0]);
873    } else {
874      files = [
875        "/etc/passwd", "/etc/shadow", "/home/admin/.ssh/id_rsa",
876        "/var/log/auth.log", "/root/.bash_history", "/etc/sudoers",
877        "/var/www/html/admin.php", "/tmp/exploit.sh", "/usr/bin/sudo",
878        "/home/user/documents/passwords.txt"
879      ];
880    }
881    var i = 0;
882    var scan = Typer.fxInterval(function () {
883      var line = document.createElement("p");
884      if (i < files.length) {
885        line.style.color = "#FFFF00";
886        line.textContent = Typer.fxText("found") + " " + files[i++];
887      } else {
888        clearInterval(scan);
889        line.style.color = "#00FF00";
890        line.textContent = Typer.fxText("scanComplete", files.length);
891        box.classList.remove("scan-effect");
892      }
893      list.appendChild(line);
894      Typer.fxScroll("filesystem");
895    }, 800);
896    return false;
897  },
898
899  makeDatabaseQuery: function () {
900    Typer.hidepop();
901    var box = document.querySelector("#database");
902    box.style.display = "block";
903    Typer.trackHackerEffect("database_query");
904    var status = Typer.fxReset("db-status");
905    var results = document.getElementById("db-results");
906    if (!status || !results) return false;
907    status.style.display = "block";
908    results.style.display = "none";
909    results.innerHTML = "";
910    box.scrollTop = 0;
911
912    Typer.fxTimeout(function () { status.textContent = Typer.fxText("connected"); }, 2000);
913    Typer.fxTimeout(function () { status.innerHTML = Typer.fxText("executing") + "<span class='loading-dots'></span>"; }, 3500);
914    Typer.fxTimeout(function () {
915      status.innerHTML = Typer.fxText("streaming") + "<span class='loading-dots'></span>";
916      results.style.display = "block";
917      var max = Math.floor(Math.random() * 8) + 5;
918      var n = 0;
919      var add = function (text, color) {
920        var line = document.createElement("p");
921        line.style.color = color;
922        line.textContent = text;
923        results.appendChild(line);
924        Typer.fxScroll("database");
925      };
926      add("mysql> SELECT username,email,uid FROM users WHERE active=1;", "#00FFFF");
927      var stream = Typer.fxInterval(function () {
928        if (n < max) {
929          var row = null;
930          if (Typer.databaseData && Typer.databaseData.length > 0) {
931            var entry = Typer.databaseData[Math.floor(Math.random() * Typer.databaseData.length)].split(":");
932            if (entry.length >= 4) row = entry[0] + " | " + entry[2] + " | " + entry[3];
933          }
934          if (!row) {
935            var users = ["admin", "root", "jsmith", "sarah_connor", "neo_anderson"];
936            var u = users[Math.floor(Math.random() * users.length)];
937            row = u + " | " + u + "@system.com | " + (1000 + n);
938          }
939          add(row, "#FFFFFF");
940          n++;
941        } else {
942          clearInterval(stream);
943          status.style.display = "none";
944          add(Typer.fxText("completed", n), "#00FF00");
945        }
946      }, 600);
947    }, 5000);
948    return false;
949  },
950
951  addText: function (key) {
952    //Main function to add the code
953    if ((key.ctrlKey || key.metaKey) && key.keyCode == 67) {
954      // Ctrl+C / Cmd+C: people copy the code for real. Let the browser handle it -
955      // no typing, no preventDefault. (Slot C was "Buy Bitcoin Here" until 2026-09-06.)
956      try {
957        window.dataLayer = window.dataLayer || [];
958        window.dataLayer.push({ event: "copy_key" });
959      } catch (e) {}
960      return;
961    }
962    if (key.keyCode == 18) {
963      // key 18 = alt key
964      Typer.accessCount++; //increase counter
965      if (Typer.accessCount >= 3) {
966        // if it's presed 3 times
967        Typer.makeAccess(); // make access popup
968      }
969    } else if (key.keyCode == 20) {
970      // key 20 = caps lock
971      Typer.deniedCount++; // increase counter
972      if (Typer.deniedCount >= 3) {
973        // if it's pressed 3 times
974        Typer.makeDenied(); // make denied popup
975      }
976    } else if (key.keyCode == 27) {
977      // key 27 = esc key
978      Typer.hidepop(); // hide all popups
979    } else if (key.keyCode == 49 && key.shiftKey) {
980      // key 1 + shift = ! key (system breach)
981      Typer.makeSystemBreach();
982    } else if (key.keyCode == 50 && key.shiftKey) {
983      // key 2 + shift = @ key (camera access)
984      Typer.makeCameraAccess();
985    } else if (key.keyCode == 51 && key.shiftKey) {
986      // key 3 + shift = # key (GPS tracking)
987      Typer.makeGpsTracking();
988    } else if (key.keyCode == 52 && key.shiftKey) {
989      // key 4 + shift = $ key (file system)
990      Typer.makeFileSystem();
991    } else if (key.keyCode == 53 && key.shiftKey) {
992      // key 5 + shift = % key (database query)
993      Typer.makeDatabaseQuery();
994    } else if (
995      (key.ctrlKey == true || key.metaKey == true) &&
996      key.keyCode == "72"
997    ) {
998      // key 72 = H key show help
999      document.getElementById("settings").style.display = "block";
1000      document.querySelector(".subscribe-section").style.display = "none";
1001    } else if (
1002      (key.ctrlKey == true || key.metaKey == true) &&
1003      key.keyCode == "77"
1004    ) {
1005      // key 77 = M key show menu
1006      document.querySelector(".subscribe-section").style.display = "block";
1007    } else if (key.ctrlKey == true && SHORTCUTS[String.fromCharCode(key.keyCode)]) {
1008      // Ctrl + a letter from the SHORTCUTS table opens that sponsor slot.
1009      Typer.openShortcut(String.fromCharCode(key.keyCode), "key");
1010    } else if (Typer.text) {
1011      // otherway if text is loaded
1012      var cont = Typer.content(); // get the console content
1013      if (cont.substring(cont.length - 1, cont.length) == "|")
1014        // if the last char is the blinking cursor
1015        document.querySelector("#console").innerHTML = document
1016          .querySelector("#console")
1017          .innerHTML.substring(0, cont.length - 1); // remove it before adding the text
1018      if (key.keyCode != 8) {
1019        // if key is not backspace
1020        Typer.index += Typer.speed; // add to the index the speed
1021      } else {
1022        if (Typer.index > 0)
1023          // else if index is not less than 0
1024          Typer.index -= Typer.speed; //     remove speed for deleting text
1025      }
1026      // var text = $('<div/>').text(Typer.text.substring(0, Typer.index)).html(); // parse the text for stripping html enities
1027      var text = Typer.text.substring(0, Typer.index);
1028      var rtn = new RegExp("\n", "g"); // newline regex
1029      var rts = new RegExp("\\s", "g"); // whitespace regex
1030      var rtt = new RegExp("\\t", "g"); // tab regex
1031
1032      document.querySelector("#console").innerHTML = text
1033        .replace(rtn, "<br/>")
1034        .replace(rtt, "&nbsp;&nbsp;&nbsp;&nbsp;")
1035        .replace(rts, "&nbsp;");
1036      // replace newline chars with br, tabs with 4 space and blanks with an html blank
1037      // Scroll to keep content visible above the footer
1038      let footerHeight = 40;
1039      if (window.innerWidth <= 480) {
1040        footerHeight = 60;
1041      } else if (window.innerWidth <= 768) {
1042        footerHeight = 50;
1043      }
1044      
1045      const windowHeight = window.innerHeight;
1046      const console = document.querySelector("#console");
1047      const consoleBottom = console.getBoundingClientRect().bottom;
1048      
1049      if (consoleBottom > (windowHeight - footerHeight - 20)) {
1050        window.scrollBy(0, 50);
1051      }
1052    }
1053    if (key.preventDefault && key.keyCode != 122) {
1054      // prevent F11(fullscreen) from being blocked
1055      key.preventDefault();
1056    }
1057    if (key.keyCode != 122) {
1058      // otherway prevent keys default behavior
1059      key.returnValue = false;
1060    }
1061  },
1062
1063  updLstChr: function () {
1064    // blinking cursor
1065    var cont = this.content(); // get console
1066    if (cont.substring(cont.length - 1, cont.length) == "|")
1067      // if last char is the cursor
1068      document.querySelector("#console").innerHTML = document
1069        .querySelector("#console")
1070        .innerHTML.substring(0, cont.length - 1);
1071    else this.write("|"); // else write it
1072  },
1073};
1074
1075Typer.init();
1076
1077document.addEventListener("touchstart", (e) => {
1078  if (e.target.id === "subscribe-btn") return;
1079  document.getElementById("settings").style.display = "none";
1080  if (e.touches && e.touches.length >= 3) {
1081    // Three fingers down is the phone's Alt-Alt-Alt.
1082    Typer.makeAccess();
1083    return;
1084  }
1085  Typer.addText(e); //Capture the tap event for mobile and call the addText, this is executed on page load
1086});
1087
1088document.addEventListener("keydown", (e) => {
1089  document.getElementById("settings").style.display = "none";
1090  document.querySelector(".subscribe-section").style.display = "none";
1091  Typer.addText(e); //Capture the keydown event and call the addText, this is executed on page load
1092});
1093
1094document.querySelector("#showhelp").addEventListener("click", (e) => {
1095  document.getElementById("settings").style.display = "block";
1096  var element = document.getElementById("subscribe-section");
1097  if (element) {
1098    element.style.display = "none";
1099  }
1100});
1101
1102document.querySelector("#showmenu").addEventListener("click", (e) => {
1103  document.getElementById("settings").style.display = "none";
1104  var element = document.getElementById("subscribe-section");
1105  if (element) {
1106    element.style.display = "block";
1107  }
1108});
1109
1110document.querySelector(".settings-btn").addEventListener("click", (e) => {
1111  document.getElementById("settings").style.display = "none";
1112});
1113
1114// Footer Stargate link A/B. The label is the only variable; assignment is per
1115// visitor, sticky in localStorage, 50/50. The variant rides on utm_campaign,
1116// which stargateasi.com already records as an attribution facet, so "runs
1117// started by campaign tag" on its dashboard is the readout. utm_source stays
1118// "ht" so the existing series is unbroken. Ctrl+S (ht-key) is not in the test.
1119// Locale pages take the label from their i18n table; root and br from the
1120// link's data-variant-* attributes.
1121var FOOTER_VARIANTS = { a: "footer-stargate", b: "footer-survive" };
1122(function () {
1123  var link = document.getElementById("stargate-link");
1124  var label = document.getElementById("stargate-label");
1125  if (!link || !label) return;
1126  var v = null;
1127  try { v = localStorage.getItem("htFooterVariant"); } catch (e) {}
1128  if (v !== "a" && v !== "b") {
1129    v = Math.random() < 0.5 ? "a" : "b";
1130    try { localStorage.setItem("htFooterVariant", v); } catch (e) {}
1131  }
1132  var lang = document.documentElement.lang;
1133  var t = (typeof translations !== "undefined" && translations[lang] && translations[lang].ui) || null;
1134  var text = v === "b"
1135    ? (t && t.stargateAlt) || link.getAttribute("data-variant-b")
1136    : (t && t.stargate) || link.getAttribute("data-variant-a");
1137  if (text) label.textContent = text;
1138  label.setAttribute("data-i18n", v === "b" ? "ui.stargateAlt" : "ui.stargate");
1139  try {
1140    var url = new URL(link.href);
1141    url.searchParams.set("utm_campaign", FOOTER_VARIANTS[v]);
1142    link.href = url.toString();
1143  } catch (e) {}
1144  try {
1145    window.dataLayer = window.dataLayer || [];
1146    window.dataLayer.push({ event: "footer_variant", footer_variant: FOOTER_VARIANTS[v] });
1147  } catch (e) {}
1148})();
1149
1150// Footer sponsor links and the Access Granted promo report through the same
1151// event as the keyboard slots, so one GTM report covers every way in.
1152document.querySelectorAll("#menu a[target='_blank'], #gran .granted-promo a").forEach(function (link) {
1153  link.addEventListener("click", function () {
1154    Typer.trackShortcut(link.textContent.replace(/\s+/g, " ").trim(), link.href, "footer");
1155  });
1156});
1157
1158// Share from the Access Granted popup: the native share sheet where there is
1159// one, otherwise copy a tagged link to this page and say so. No third party.
1160(function () {
1161  var share = document.getElementById("granted-share");
1162  if (!share) return;
1163  var label = share.textContent;
1164  share.addEventListener("click", function (e) {
1165    e.preventDefault();
1166    e.stopPropagation();
1167    var canonical = document.querySelector('link[rel="canonical"]');
1168    var url = (canonical ? canonical.href : "https://hackertyper.com/") + "?utm_source=share";
1169    var heading = document.querySelector("#gran h2");
1170    var text = (heading ? heading.textContent.trim() : "ACCESS GRANTED") + " - hackertyper.com";
1171    try {
1172      window.dataLayer = window.dataLayer || [];
1173      window.dataLayer.push({ event: "share_click", via: "granted", method: navigator.share ? "native" : "copy" });
1174    } catch (err) {}
1175    if (navigator.share) {
1176      navigator.share({ title: "Hacker Typer", text: text, url: url }).catch(function () {});
1177      return;
1178    }
1179    if (navigator.clipboard && navigator.clipboard.writeText) {
1180      navigator.clipboard.writeText(url).then(function () {
1181        share.textContent = share.getAttribute("data-copied") || "Link copied";
1182        setTimeout(function () { share.textContent = label; }, 2000);
1183      }, function () {});
1184    }
1185  });
1186})();
1187
1188// Mobile footer: a shell prompt line. On narrow touch screens the keyboard
1189// badges mean nothing and the five-cell footer overflows the viewport, so it
1190// becomes  [?] [≡]  $ run survive-the-ai▌  - two fixed controls and one
1191// sponsor slot shown as a ghost autosuggestion that backspaces and retypes
1192// the next slot every few seconds: the site's own typing effect, not a banner
1193// fade. Slots come from the footer links, so the A/B label, the locale text
1194// and the tracking carry over. Stargate alternates with every other slot
1195// (Stargate, DocuTyper, Stargate, Lovable, ...) and holds longest. Rotation
1196// pauses while a popup is up or the tab is hidden, and never starts under
1197// prefers-reduced-motion. Every fully typed slot pushes sponsor_impression,
1198// so tap rate per slot is taps over the times it was actually on screen.
1199// [fx] opens a command list for the effects that need Shift or Caps Lock on
1200// a keyboard, named from the help panel so locale pages get their own words.
1201// Desktop keeps the badge footer; the bar only exists below PROMPT_BAR.query.
1202// Two breakpoints, not one. The footer is swapped for the bar wherever it no
1203// longer fits - measured at 822px with six slots - while the popup and console
1204// rules below stay phone-only.
1205var PROMPT_BAR = {
1206  query: "(max-width: 830px)",
1207  phoneQuery: "(max-width: 640px)",
1208  holdMs: { first: 8000, rest: 5000 },
1209  typeMs: 45,
1210  eraseMs: 25,
1211};
1212(function () {
1213  var footer = document.getElementById("menu");
1214  if (!footer || !window.matchMedia) return;
1215  var links = Array.prototype.slice.call(footer.querySelectorAll("a[target='_blank']"));
1216  if (!links.length) return;
1217
1218  var slots = links.map(function (a) {
1219    var label = a.textContent.replace(/\s+/g, " ").replace(/Ctrl\+\w\s*/, "").trim();
1220    var slug = label.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-|-$/g, "");
1221    var lead = /stargateasi\.com/.test(a.href);
1222    return { label: label, url: a.href, command: (lead ? "run " : "open ") + slug, lead: lead };
1223  });
1224  // Play order: the lead slot between every other slot, so it is on screen
1225  // half the time and never more than one slot away.
1226  var lead = slots.filter(function (s) { return s.lead; })[0] || slots[0];
1227  var others = slots.filter(function (s) { return s !== lead; });
1228  var sequence = [];
1229  others.forEach(function (s) { sequence.push(lead, s); });
1230  if (!sequence.length) sequence = [lead];
1231
1232  var style = document.createElement("style");
1233  style.textContent = [
1234    ".prompt-bar { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;",
1235    "  align-items: center; gap: 10px; height: 44px; padding: 0 12px env(safe-area-inset-bottom);",
1236    "  background: #000; border-top: 1px solid #00FF00; color: #00FF00;",
1237    "  font-family: monospace; font-size: 15px; line-height: 44px; }",
1238    ".prompt-key { background: #00FF00; color: #000; border: 0; padding: 0 3px; margin: 0;",
1239    "  font: inherit; line-height: 20px; cursor: pointer; }",
1240    ".prompt-line { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden;",
1241    "  background: transparent; color: #00FF00; text-decoration: none; }",
1242    ".prompt-ghost { opacity: 0.65; }",
1243    ".fx-menu { display: none; position: fixed; left: 0; right: 0; bottom: 44px; z-index: 41;",
1244    "  padding: 6px 12px 10px; background: #000; border-top: 1px solid #00FF00; color: #00FF00;",
1245    "  font-family: monospace; font-size: 15px; line-height: 1.2; }",
1246    ".fx-menu .fx-title { opacity: 0.65; padding: 4px 0 6px; }",
1247    ".fx-menu button { display: block; width: 100%; text-align: left; background: transparent;",
1248    "  color: #00FF00; border: 0; padding: 7px 0; margin: 0; font: inherit; cursor: pointer; }",
1249    ".fx-menu button .prompt-key { display: inline-block; min-width: 3.2em; text-align: center; margin-right: 8px; }",
1250    ".prompt-cursor { animation: prompt-blink 1s steps(1) infinite; }",
1251    "@keyframes prompt-blink { 50% { opacity: 0; } }",
1252    "@media " + PROMPT_BAR.query + " { .prompt-bar { display: flex; } #menu { display: none; } }",
1253    "@media " + PROMPT_BAR.phoneQuery + " {",
1254    "  .systemBreach { width: auto; left: 12px; right: 12px; margin-left: 0; font-size: 1.3em; padding: 18px; }",
1255    "  .cameraAccess, .gpsTracking, .fileSystem, .databaseQuery { width: auto; left: 12px; right: 12px; }",
1256    "  .databaseQuery { bottom: 60px; }",
1257    // Clip sideways overflow at the viewport (html), never on body: the base
1258    // rule gives body height:100%, and a non-visible overflow there turns body
1259    // into its own scroller, so window.scrollBy in addText stops following the text.
1260    "  html { overflow-x: hidden; }",
1261    "  #console { max-width: 100%; overflow-wrap: anywhere; word-break: break-all; }",
1262    "}",
1263    "@media (prefers-reduced-motion: reduce) { .prompt-cursor { animation: none; } }",
1264  ].join("\n");
1265  document.head.appendChild(style);
1266
1267  var bar = document.createElement("div");
1268  bar.id = "prompt-bar";
1269  bar.className = "prompt-bar";
1270  bar.innerHTML =
1271    '<button type="button" class="prompt-key" id="prompt-help" aria-label="Help">[?]</button>' +
1272    '<button type="button" class="prompt-key" id="prompt-menu" aria-label="Menu">[≡]</button>' +
1273    '<button type="button" class="prompt-key" id="prompt-fx" aria-label="Effects" aria-expanded="false">[fx]</button>' +
1274    '<a class="prompt-line" id="prompt-line" href="#" rel="noopener">' +
1275    '<span class="prompt-ps1">$</span> <span class="prompt-ghost" id="prompt-ghost"></span>' +
1276    '<span class="prompt-cursor">▌</span></a>';
1277  document.body.appendChild(bar);
1278
1279  var ghost = document.getElementById("prompt-ghost");
1280  var line = document.getElementById("prompt-line");
1281  var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1282  var idx = 0, timer = null, running = false, current = sequence[0];
1283
1284  function popupOpen() {
1285    return ["gran", "deni", "breach", "camera", "gps", "filesystem", "database", "settings", "subscribe-section", "fx-menu"]
1286      .some(function (id) { var el = document.getElementById(id); return el && el.style.display === "block"; });
1287  }
1288  function impression(slot) {
1289    try {
1290      window.dataLayer = window.dataLayer || [];
1291      window.dataLayer.push({ event: "sponsor_impression", slot: slot.label, slot_url: slot.url, via: "prompt" });
1292    } catch (e) {}
1293  }
1294  function typeTo(text, done) {
1295    var i = 0;
1296    ghost.textContent = "";
1297    (function step() {
1298      if (i < text.length) { ghost.textContent = text.slice(0, ++i); timer = setTimeout(step, PROMPT_BAR.typeMs); }
1299      else done();
1300    })();
1301  }
1302  function erase(done) {
1303    (function step() {
1304      var t = ghost.textContent;
1305      if (t.length) { ghost.textContent = t.slice(0, -1); timer = setTimeout(step, PROMPT_BAR.eraseMs); }
1306      else done();
1307    })();
1308  }
1309  function schedule(ms) { clearTimeout(timer); timer = setTimeout(next, ms); }
1310  function next() {
1311    if (!window.matchMedia(PROMPT_BAR.query).matches) { running = false; return; }
1312    if (document.hidden || popupOpen()) { schedule(1000); return; }
1313    erase(function () {
1314      idx = (idx + 1) % sequence.length;
1315      current = sequence[idx];
1316      line.href = current.url;
1317      typeTo(current.command, function () {
1318        impression(current);
1319        schedule(current.lead ? PROMPT_BAR.holdMs.first : PROMPT_BAR.holdMs.rest);
1320      });
1321    });
1322  }
1323  function start() {
1324    if (running) return;
1325    running = true;
1326    idx = 0;
1327    current = sequence[0];
1328    ghost.textContent = current.command;
1329    line.href = current.url;
1330    impression(current);
1331    if (!reduced && sequence.length > 1) schedule(PROMPT_BAR.holdMs.first);
1332  }
1333  var mq = window.matchMedia(PROMPT_BAR.query);
1334  if (mq.matches) start();
1335  if (mq.addEventListener) mq.addEventListener("change", function (e) { if (e.matches) start(); });
1336
1337  // The effects menu. Names come from the help panel lines "! (Shift+1) = X"
1338  // (already translated on locale pages) and the two popup headings.
1339  var effects = [];
1340  Array.prototype.forEach.call(document.querySelectorAll("#settings p"), function (p) {
1341    var m = p.textContent.trim().match(/^(\S)\s*\(Shift\+([1-5])\)\s*=\s
1341*(.+)$/);
1342    if (m) effects.push({ key: m[1], name: m[3].trim(), run: ["makeSystemBreach", "makeCameraAccess", "makeGpsTracking", "makeFileSystem", "makeDatabaseQuery"][Number(m[2]) - 1] });
1343  });
1344  var granH2 = document.querySelector("#gran h2"), deniH2 = document.querySelector("#deni h2");
1345  effects.push({ key: "ok", name: granH2 ? granH2.textContent.trim() : "ACCESS GRANTED", run: "makeAccess" });
1346  effects.push({ key: "x", name: deniH2 ? deniH2.textContent.trim() : "ACCESS DENIED", run: "makeDenied" });
1347  effects.push({ key: "esc", name: "clear", run: "hidepop" });
1348
1349  var fxMenu = document.createElement("div");
1350  fxMenu.id = "fx-menu";
1351  fxMenu.className = "fx-menu";
1352  fxMenu.setAttribute("role", "menu");
1353  fxMenu.innerHTML = '<div class="fx-title">$ fx --list</div>' + effects.map(function (fx, i) {
1354    return '<button type="button" role="menuitem" data-fx="' + i + '"><span class="prompt-key">[' + fx.key + ']</span>' + fx.name.replace(/</g, "&lt;") + "</button>";
1355  }).join("");
1356  document.body.appendChild(fxMenu);
1357  var fxKey = document.getElementById("prompt-fx");
1358  function setFx(open) {
1359    fxMenu.style.display = open ? "block" : "none";
1360    fxKey.setAttribute("aria-expanded", open ? "true" : "false");
1361  }
1362  fxKey.addEventListener("click", function () { setFx(fxMenu.style.display !== "block"); });
1363  fxMenu.addEventListener("click", function (e) {
1364    var b = e.target.closest ? e.target.closest("button[data-fx]") : null;
1365    if (!b) return;
1366    var fx = effects[Number(b.getAttribute("data-fx"))];
1367    setFx(false);
1368    if (fx && typeof Typer[fx.run] === "function") Typer[fx.run]();
1369  });
1370  document.addEventListener("click", function (e) {
1371    if (fxMenu.style.display === "block" && !fxMenu.contains(e.target) && e.target !== fxKey) setFx(false);
1372  });
1373  fxMenu.addEventListener("touchstart", function (e) { e.stopPropagation(); });
1374
1375  // A tap on the bar is a tap on the bar, not a keystroke for the typer.
1376  bar.addEventListener("touchstart", function (e) { e.stopPropagation(); });
1377  document.getElementById("prompt-help").addEventListener("click", function () {
1378    var h = document.getElementById("showhelp"); if (h) h.click();
1379  });
1380  document.getElementById("prompt-menu").addEventListener("click", function () {
1381    var m = document.getElementById("showmenu"); if (m) m.click();
1382  });
1383  line.addEventListener("click", function (e) {
1384    e.preventDefault();
1385    Typer.trackShortcut(current.label, current.url, "prompt");
1386    window.open(current.url, "_blank");
1387  });
1388})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.