hyperinstant 1.0.1 → 1.0.2

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.
package/README.md CHANGED
@@ -1,42 +1,27 @@
1
- # HyperInstant v1.0.1
1
+ # HyperInstant v1.0.2
2
2
 
3
- **HyperInstant** accelerates navigation across any website by intelligently prefetching (and when safe, prerendering) the *right* pages at the *right* time.
4
-
5
- ### Key features
6
- - **Adaptive engine** (network-aware budgets + intent scoring)
7
- - **Safe by default** (auto-deny rules for cart/checkout/account/actions)
8
- - **Platform adapters** (auto: Shopify / WooCommerce / nopCommerce)
9
- - **Local stats** (hit-rate) + optional debug overlay
10
-
11
- ---
12
-
13
- ## Quick install (CDN)
3
+ v1.0.2 focuses on **real-world speed**:
4
+ - **Prerender only on high-confidence intent** (mousedown/touchstart), not on hover
5
+ - **Lower overhead scheduler** (capped queue + dedupe; no heavy sorting)
6
+ - **Safer auto-deny** (less over-blocking)
7
+ - **Optional auto-tuning** (OFF by default)
14
8
 
9
+ ## CDN
15
10
  ```html
16
11
  <script>
17
12
  window.HyperInstant = {
18
- mode: "balanced", // safe | balanced | aggressive
19
- adapter: "auto", // auto | shopify | woocommerce | nopcommerce | generic
20
- observe: "intent", // intent | intent+viewport
21
- hoverDelay: 65,
13
+ mode: "balanced",
14
+ adapter: "auto",
22
15
  debug: false
23
16
  };
24
17
  </script>
25
- <script src="https://unpkg.com/hyperinstant@1.0.1/dist/hyperinstant.min.js" defer></script>
18
+ <script src="https://unpkg.com/hyperinstant@1.0.2/dist/hyperinstant.min.js" defer></script>
26
19
  ```
27
20
 
28
- ## Recommended deny additions for eCommerce (optional)
29
- You can add project-specific entries (strings are matched by `.includes()`):
30
-
21
+ ## “Very fast” preset (nopCommerce)
31
22
  ```js
32
- deny: ["/cart", "/checkout", "/account", "add-to-cart"]
23
+ mode: "aggressive",
24
+ adapter: "nopcommerce",
25
+ hoverDelay: 35,
26
+ enablePrerender: false
33
27
  ```
34
-
35
- ## Debug
36
- Set `debug: true` to enable:
37
- - `window.HyperInstantStats` (stats API)
38
- - optional mini overlay (`debugOverlay: true`)
39
-
40
- ---
41
-
42
- MIT License.
@@ -1,300 +1,389 @@
1
- /*! HyperInstant v1.0.1 | MIT | Putia Web */
2
- (function(){
1
+ /*! HyperInstant v1.0.2 | MIT | Putia Web */
2
+ (function () {
3
3
  if (window.__HYPERINSTANT_LOADED__) return;
4
4
  window.__HYPERINSTANT_LOADED__ = true;
5
5
 
6
- var cfg = window.HyperInstant || {};
7
-
8
- function clamp(n,a,b){ return Math.max(a, Math.min(b,n)); }
9
- function isSameOrigin(url){ try{ return new URL(url, location.href).origin === location.origin; }catch(e){ return false; } }
10
- function normalizeUrl(url){ try{ var u=new URL(url, location.href); u.hash=""; return u.toString(); }catch(e){ return ""; } }
11
- function getConn(){ var c=navigator.connection||navigator.mozConnection||navigator.webkitConnection; return { saveData:!!(c&&c.saveData), effectiveType:(c&&c.effectiveType)?String(c.effectiveType):"unknown", downlink:(c&&typeof c.downlink==="number")?c.downlink:null, rtt:(c&&typeof c.rtt==="number")?c.rtt:null }; }
12
- function supportsPrerender(){ try{ return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender"); }catch(e){ return false; } }
13
- function inNav(a){ try{ return !!a.closest("nav, header, [role='navigation'], .header, .site-header"); }catch(e){ return false; } }
14
-
15
- function applyModeDefaults(c){
16
- var mode = String(c.mode||"safe").toLowerCase();
17
- var out = Object.assign({}, c);
18
- if (mode==="aggressive"){ out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 2; out.observe = out.observe ?? "intent+viewport"; }
19
- else if (mode==="balanced"){ out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 1; out.observe = out.observe ?? "intent"; }
20
- else { out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.observe = out.observe ?? "intent"; }
21
- out.hoverDelay = out.hoverDelay ?? 65;
6
+ var userCfg = window.HyperInstant || {};
7
+ var VERSION = "1.0.2";
8
+
9
+ function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
10
+ function isSameOrigin(url) {
11
+ try { return new URL(url, location.href).origin === location.origin; }
12
+ catch (e) { return false; }
13
+ }
14
+ function normalizeUrl(url) {
15
+ try { var u = new URL(url, location.href); u.hash = ""; return u.toString(); }
16
+ catch (e) { return ""; }
17
+ }
18
+ function supportsPrerender() {
19
+ try {
20
+ return "relList" in HTMLLinkElement.prototype &&
21
+ HTMLLinkElement.prototype.relList &&
22
+ HTMLLinkElement.prototype.relList.supports &&
23
+ HTMLLinkElement.prototype.relList.supports("prerender");
24
+ } catch (e) { return false; }
25
+ }
26
+ function getConn() {
27
+ var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
28
+ return {
29
+ saveData: !!(c && c.saveData),
30
+ effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown",
31
+ downlink: (c && typeof c.downlink === "number") ? c.downlink : null,
32
+ rtt: (c && typeof c.rtt === "number") ? c.rtt : null
33
+ };
34
+ }
35
+ function inNav(a) {
36
+ try { return !!a.closest("nav, header, [role='navigation'], .header, .site-header"); }
37
+ catch (e) { return false; }
38
+ }
39
+
40
+ function applyModeDefaults(cfg) {
41
+ var mode = String(cfg.mode || "safe").toLowerCase();
42
+ var out = Object.assign({}, cfg);
43
+
44
+ // v1.0.2: prerender is intentionally conservative to avoid slowing pages
45
+ if (mode === "aggressive") {
46
+ out.maxPrefetch = out.maxPrefetch ?? 10;
47
+ out.maxPrerender = out.maxPrerender ?? 1;
48
+ out.hoverDelay = out.hoverDelay ?? 35;
49
+ } else if (mode === "balanced") {
50
+ out.maxPrefetch = out.maxPrefetch ?? 6;
51
+ out.maxPrerender = out.maxPrerender ?? 0;
52
+ out.hoverDelay = out.hoverDelay ?? 65;
53
+ } else {
54
+ out.maxPrefetch = out.maxPrefetch ?? 4;
55
+ out.maxPrerender = out.maxPrerender ?? 0;
56
+ out.hoverDelay = out.hoverDelay ?? 75;
57
+ }
58
+
59
+ out.adapter = out.adapter ?? "auto";
60
+ out.observe = out.observe ?? "intent";
61
+ out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"];
62
+ out.deny = out.deny ?? [];
22
63
  out.debug = !!out.debug;
23
64
  out.debugOverlay = !!out.debugOverlay;
24
- out.budget = out.budget ?? "auto";
25
- out.events = out.events ?? ["mouseover","touchstart","mousedown","focusin"];
26
- out.deny = out.deny ?? [];
65
+
66
+ // new in 1.0.2
67
+ out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender;
68
+ out.autoTune = !!out.autoTune; // OFF by default
69
+ out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200);
70
+
27
71
  return out;
28
72
  }
29
73
 
30
- function hasMeta(name){ return !!document.querySelector('meta[name="'+name+'"]'); }
31
- function shopifyAdapter(c){
32
- var deny=new Set([].concat(c.deny||[]));
33
- ["/cart","/checkout","/account","/account/login","/account/register","/account/logout","/search","cart/add","cart/change","cart/update","?variant=","variant=","sections="].forEach(function(x){ deny.add(x); });
34
- return Object.assign({}, c, {deny:Array.from(deny)});
74
+ function hasMeta(name) { return !!document.querySelector('meta[name="' + name + '"]'); }
75
+
76
+ function shopifyAdapter(cfg) {
77
+ var deny = new Set([].concat(cfg.deny || []));
78
+ ["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search",
79
+ "cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x) { deny.add(x); });
80
+ return Object.assign({}, cfg, { deny: Array.from(deny) });
35
81
  }
36
- function wooAdapter(c){
37
- var deny=new Set([].concat(c.deny||[]));
38
- ["/cart","/checkout","/my-account","add-to-cart","wc-ajax","remove_item","update_cart","apply_coupon"].forEach(function(x){ deny.add(x); });
39
- return Object.assign({}, c, {deny:Array.from(deny)});
82
+
83
+ function wooAdapter(cfg) {
84
+ var deny = new Set([].concat(cfg.deny || []));
85
+ ["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x) { deny.add(x); });
86
+ return Object.assign({}, cfg, { deny: Array.from(deny) });
40
87
  }
41
- function nopAdapter(c){
42
- var deny=new Set([].concat(c.deny||[]));
43
- ["/shoppingcart","/checkout","/login","/register","/logout","/customer","addproducttocart","updateshoppingcart","updatecart","deleteitem","/wishlist","/compareproducts","/search","?q="].forEach(function(x){ deny.add(x); });
44
- return Object.assign({}, c, {deny:Array.from(deny)});
88
+
89
+ function nopAdapter(cfg) {
90
+ var deny = new Set([].concat(cfg.deny || []));
91
+ ["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts",
92
+ "/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x) { deny.add(x); });
93
+ return Object.assign({}, cfg, { deny: Array.from(deny) });
45
94
  }
46
- function detectAdapter(name){
47
- name = String(name||"auto").toLowerCase();
48
- if (name==="shopify") return shopifyAdapter;
49
- if (name==="woocommerce") return wooAdapter;
50
- if (name==="nopcommerce") return nopAdapter;
51
- if (name==="generic") return function(x){ return x; };
52
-
53
- try{ if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter; }catch(e){}
54
- try{ if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body&&String(document.body.className||"").includes("woocommerce"))) return wooAdapter; }catch(e){}
55
- try{ if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter; }catch(e){}
56
- return function(x){ return x; };
95
+
96
+ function detectAdapter(name) {
97
+ name = String(name || "auto").toLowerCase();
98
+ if (name === "shopify") return shopifyAdapter;
99
+ if (name === "woocommerce") return wooAdapter;
100
+ if (name === "nopcommerce") return nopAdapter;
101
+ if (name === "generic") return function (c) { return c; };
102
+
103
+ try { if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter; } catch (e) { }
104
+ try { if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter; } catch (e) { }
105
+ try { if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter; } catch (e) { }
106
+ return function (c) { return c; };
57
107
  }
58
108
 
59
- var AUTO_KEYWORDS=["logout","logoff","signin","sign-in","login","register","account","checkout","cart","basket","order","orders","pay","payment","add-to-cart","addtocart","addproducttocart","remove","update","change","delete","wc-ajax","admin-ajax","ajax","sections=","variant=","token","returnurl","redirect"];
60
- var AUTO_PATHS=["/checkout","/cart","/shoppingcart","/account","/my-account","/logout","/login","/register"];
61
- var AUTO_PARAMS=["add","remove","update","change","delete","token","variant","sections","returnurl","redirect"];
109
+ // Safer auto-deny (avoid broad keywords like "order" that can hit benign pages)
110
+ var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"];
111
+ var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"];
112
+ var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="];
62
113
 
63
- function shouldDeny(url, c){
114
+ function shouldDeny(url, cfg) {
64
115
  if (!url) return true;
65
116
  if (!isSameOrigin(url)) return true;
117
+
66
118
  var u = String(url).toLowerCase();
67
119
 
68
- var denyList = c.deny || [];
69
- for (var i=0;i<denyList.length;i++){ var r=denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true; }
120
+ var denyList = cfg.deny || [];
121
+ for (var i = 0; i < denyList.length; i++) {
122
+ var r = denyList[i];
123
+ if (r && u.includes(String(r).toLowerCase())) return true;
124
+ }
70
125
 
71
- try{
126
+ try {
72
127
  var p = new URL(url, location.href).pathname.toLowerCase();
73
- for (var j=0;j<AUTO_PATHS.length;j++){
74
- var ap=AUTO_PATHS[j];
75
- if (p===ap || p.startsWith(ap+"/")) return true;
128
+ for (var j = 0; j < AUTO_PATH_PREFIX.length; j++) {
129
+ var ap = AUTO_PATH_PREFIX[j];
130
+ if (p === ap || p.startsWith(ap + "/")) return true;
76
131
  }
77
- }catch(e){}
132
+ } catch (e) { }
78
133
 
79
- for (var k=0;k<AUTO_KEYWORDS.length;k++){ if (u.includes(AUTO_KEYWORDS[k])) return true; }
134
+ for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true;
80
135
 
81
- try{
136
+ try {
82
137
  var uu = new URL(url, location.href);
83
138
  var sp = uu.searchParams;
84
- for (var q=0;q<AUTO_PARAMS.length;q++){ if (sp.has(AUTO_PARAMS[q])) return true; }
85
- }catch(e){}
139
+ for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;
140
+ } catch (e) { }
86
141
 
87
142
  return false;
88
143
  }
89
144
 
90
- function Stats(){
91
- this.startedAt=Date.now();
92
- this.seen=0; this.denied=0; this.enqueued=0;
93
- this.prefetched=0; this.prerendered=0;
94
- this.clicked=0; this.hit=0;
95
- this._loaded=new Map();
145
+ function Stats() {
146
+ this.startedAt = Date.now();
147
+ this.seen = 0;
148
+ this.denied = 0;
149
+ this.enqueued = 0;
150
+ this.prefetched = 0;
151
+ this.prerendered = 0;
152
+ this.clicked = 0;
153
+ this.hit = 0;
154
+ this._loaded = new Set();
96
155
  }
97
- Stats.prototype.markLoaded=function(url,type){
98
- if(!this._loaded.has(url)){
99
- this._loaded.set(url,{type:type,t:Date.now()});
100
- if(type==="prerender") this.prerendered++; else this.prefetched++;
101
- }
156
+ Stats.prototype.isLoaded = function (url) { return this._loaded.has(url); };
157
+ Stats.prototype.markLoaded = function (url, type) {
158
+ if (this._loaded.has(url)) return;
159
+ this._loaded.add(url);
160
+ if (type === "prerender") this.prerendered++; else this.prefetched++;
102
161
  };
103
- Stats.prototype.isLoaded=function(url){ return this._loaded.has(url); };
104
- Stats.prototype.markClick=function(url){ this.clicked++; if(this._loaded.has(url)) this.hit++; };
105
- Object.defineProperty(Stats.prototype,"hitRate",{ get:function(){ var d=this.prefetched+this.prerendered; return d? (this.hit/d):0; } });
106
- Stats.prototype.snapshot=function(){
107
- return { version:"1.0.1", uptimeSec:Math.round((Date.now()-this.startedAt)/1000), seen:this.seen, denied:this.denied, enqueued:this.enqueued, prefetched:this.prefetched, prerendered:this.prerendered, clicked:this.clicked, hit:this.hit, hitRate:Number(this.hitRate.toFixed(3)) };
162
+ Stats.prototype.markClick = function (url) { this.clicked++; if (this._loaded.has(url)) this.hit++; };
163
+ Object.defineProperty(Stats.prototype, "hitRate", { get: function () { var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0; } });
164
+ Stats.prototype.snapshot = function () {
165
+ return {
166
+ version: VERSION,
167
+ uptimeSec: Math.round((Date.now() - this.startedAt) / 1000),
168
+ seen: this.seen,
169
+ denied: this.denied,
170
+ enqueued: this.enqueued,
171
+ prefetched: this.prefetched,
172
+ prerendered: this.prerendered,
173
+ clicked: this.clicked,
174
+ hit: this.hit,
175
+ hitRate: Number(this.hitRate.toFixed(3))
176
+ };
108
177
  };
109
178
 
110
- function createScheduler(c, stats){
111
- var queue=[];
112
- var inFlight=new Set();
113
- var loaded=new Set();
114
- var usedPrefetch=0, usedPrerender=0;
179
+ function createScheduler(cfg, stats) {
180
+ var q = [];
181
+ var qSet = new Set();
182
+ var inFlight = new Set();
183
+ var usedPrefetch = 0;
184
+ var usedPrerender = 0;
185
+ var prerenderSupported = supportsPrerender();
115
186
 
116
- function computeBudgets(){
117
- if (c.budget && typeof c.budget==="object"){
118
- return { maxPrefetch: c.budget.maxPrefetch ?? c.maxPrefetch, maxPrerender: c.budget.maxPrerender ?? c.maxPrerender };
119
- }
120
- var ci=getConn();
121
- var mp=c.maxPrefetch, mr=c.maxPrerender;
122
- if (ci.saveData){ mr=0; mp=Math.min(mp,2); }
123
- if (String(ci.effectiveType).includes("2g")){ mr=0; mp=Math.min(mp,2); }
124
- if (String(ci.effectiveType).includes("3g")){ mr=Math.min(mr,0); mp=Math.min(mp,4); }
125
- if (typeof ci.rtt==="number" && ci.rtt>300){ mr=0; mp=Math.min(mp,3); }
126
- if (typeof ci.downlink==="number" && ci.downlink<1.2){ mr=0; mp=Math.min(mp,3); }
127
- mp=clamp(mp,0,12); mr=clamp(mr,0,3);
128
- if (!supportsPrerender()) mr=0;
129
- return { maxPrefetch: mp, maxPrerender: mr };
187
+ function budgets() {
188
+ var mp = cfg.maxPrefetch;
189
+ var mr = cfg.maxPrerender;
190
+
191
+ if (!cfg.enablePrerender || !prerenderSupported) mr = 0;
192
+
193
+ var ci = getConn();
194
+ if (ci.saveData) { mr = 0; mp = Math.min(mp, 2); }
195
+ if (String(ci.effectiveType).includes("2g")) { mr = 0; mp = Math.min(mp, 2); }
196
+ if (String(ci.effectiveType).includes("3g")) { mr = 0; mp = Math.min(mp, 4); }
197
+ if (typeof ci.rtt === "number" && ci.rtt > 350) { mr = 0; mp = Math.min(mp, 3); }
198
+ if (typeof ci.downlink === "number" && ci.downlink < 1.2) { mr = 0; mp = Math.min(mp, 3); }
199
+
200
+ mp = clamp(mp, 0, 12);
201
+ mr = clamp(mr, 0, 3);
202
+
203
+ return { mp: mp, mr: mr };
130
204
  }
131
205
 
132
- function enqueue(rawUrl, score){
133
- var url=normalizeUrl(rawUrl);
134
- if(!url) return;
206
+ function enqueue(rawUrl, score, signal) {
207
+ var url = normalizeUrl(rawUrl);
208
+ if (!url) return;
209
+ if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return;
210
+
211
+ if (q.length >= cfg.maxQueue) {
212
+ var minIdx = 0;
213
+ for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i;
214
+ var dropped = q.splice(minIdx, 1)[0];
215
+ if (dropped) qSet.delete(dropped.url);
216
+ }
217
+
135
218
  stats.enqueued++;
136
- queue.push({url:url,score:score,t:Date.now()});
137
- queue.sort(function(a,b){ return b.score-a.score; });
219
+ q.push({ url: url, score: score, signal: signal, t: Date.now() });
220
+ qSet.add(url);
138
221
  pump();
139
222
  }
140
223
 
141
- function pump(){
142
- var b=computeBudgets();
143
- while(queue.length){
144
- var n=queue.shift();
145
- var url=n.url, score=n.score;
146
- if(loaded.has(url) || stats.isLoaded(url)) continue;
147
- if(inFlight.has(url)) continue;
224
+ function pump() {
225
+ var b = budgets();
148
226
 
149
- var wantPrerender = b.maxPrerender>usedPrerender && score>=90;
150
- var wantPrefetch = b.maxPrefetch>usedPrefetch;
227
+ function takeBest() {
228
+ if (!q.length) return null;
229
+ var bestIdx = 0;
230
+ for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i;
231
+ var item = q.splice(bestIdx, 1)[0];
232
+ if (item) qSet.delete(item.url);
233
+ return item;
234
+ }
151
235
 
152
- if(!wantPrerender && !wantPrefetch) break;
236
+ while (q.length) {
237
+ var item = takeBest();
238
+ if (!item) break;
153
239
 
154
- var type = wantPrerender ? "prerender" : "prefetch";
155
- if(type==="prerender") usedPrerender++; else usedPrefetch++;
240
+ var url = item.url;
241
+ var score = item.score;
242
+ var signal = item.signal;
243
+
244
+ if (stats.isLoaded(url) || inFlight.has(url)) continue;
245
+
246
+ var canPrefetch = usedPrefetch < b.mp;
247
+ var canPrerender = usedPrerender < b.mr;
248
+
249
+ // v1.0.2: prerender only on HIGH confidence signals (not hover)
250
+ var highIntent = (signal === "mousedown" || signal === "touchstart");
251
+ var doPrerender = canPrerender && highIntent && score >= 110;
252
+
253
+ if (!doPrerender && !canPrefetch) break;
254
+
255
+ var type = doPrerender ? "prerender" : "prefetch";
256
+ if (type === "prerender") usedPrerender++; else usedPrefetch++;
156
257
 
157
258
  inFlight.add(url);
158
- var link=document.createElement("link");
159
- link.rel=type;
160
- link.href=url;
161
- link.as="document";
162
- link.fetchPriority = (score>=90) ? "high" : (score>=70 ? "auto" : "low");
163
-
164
- link.onload=function(){
165
- loaded.add(url);
166
- inFlight.delete(url);
167
- stats.markLoaded(url,type);
168
- };
169
- link.onerror=function(){ inFlight.delete(url); };
259
+ var link = document.createElement("link");
260
+ link.rel = type;
261
+ link.href = url;
262
+ link.as = "document";
263
+ link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low");
264
+
265
+ (function (u, t) {
266
+ link.onload = function () { inFlight.delete(u); stats.markLoaded(u, t); };
267
+ link.onerror = function () { inFlight.delete(u); };
268
+ })(url, type);
269
+
170
270
  document.head.appendChild(link);
171
271
  }
172
272
  }
173
273
 
174
- window.addEventListener("pageshow", function(){ usedPrefetch=0; usedPrerender=0; });
274
+ window.addEventListener("pageshow", function () { usedPrefetch = 0; usedPrerender = 0; });
175
275
 
176
276
  return { enqueue: enqueue };
177
277
  }
178
278
 
179
- function createTuner(c, stats){
180
- var intentCount=0;
181
- function maybeTune(){
182
- var total = stats.prefetched + stats.prerendered;
183
- if(total<12) return;
184
- var hr = stats.hitRate;
185
- if (hr>=0.35) c.hoverDelay = clamp((c.hoverDelay||65)-10, 25, 140);
186
- else if (hr<=0.15) c.hoverDelay = clamp((c.hoverDelay||65)+15, 25, 180);
187
- }
188
- return {
189
- onEnqueue:function(){ intentCount++; if(intentCount%20===0) maybeTune(); },
190
- onClick:function(){}
191
- };
192
- }
193
-
194
- function maybeOverlay(c, stats){
195
- if(!c.debug || !c.debugOverlay) return;
196
- var el=document.createElement("div");
197
- el.style.cssText="position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px";
198
- function render(){
199
- var s=stats.snapshot();
200
- el.innerHTML="<div style='font-weight:700;margin-bottom:6px'>HyperInstant "+s.version+"</div>"+
201
- "<div>Loaded: <b>"+(s.prefetched+s.prerendered)+"</b> (P:"+s.prefetched+" R:"+s.prerendered+")</div>"+
202
- "<div>Hit-rate: <b>"+Math.round(s.hitRate*100)+"%</b></div>"+
203
- "<div>HoverDelay: <b>"+c.hoverDelay+"ms</b></div>"+
204
- "<div style='opacity:.8;margin-top:6px'>Clicks: "+s.clicked+" • Seen: "+s.seen+"</div>";
279
+ function maybeOverlay(cfg, stats) {
280
+ if (!cfg.debug || !cfg.debugOverlay) return;
281
+ var el = document.createElement("div");
282
+ el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px";
283
+ function render() {
284
+ var s = stats.snapshot();
285
+ el.innerHTML =
286
+ "<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" +
287
+ "<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" +
288
+ "<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" +
289
+ "<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" +
290
+ "<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";
205
291
  }
206
292
  render();
207
- document.addEventListener("DOMContentLoaded", function(){ document.body.appendChild(el); });
293
+ document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(el); });
208
294
  setInterval(render, 1200);
209
295
  }
210
296
 
211
- function eligibleLink(target){
297
+ function eligibleLink(target) {
212
298
  var a = target && target.closest ? target.closest("a[href]") : null;
213
- if(!a || !a.href) return null;
214
- var rel = String(a.getAttribute("rel")||"").toLowerCase();
215
- if(rel.includes("nofollow")) return null;
216
- if(a.getAttribute("data-no-instant")!==null) return null;
217
- if(a.getAttribute("data-hyperinstant")==="off") return null;
218
- if(a.target && a.target!=="_self") return null;
299
+ if (!a || !a.href) return null;
300
+
301
+ var rel = String(a.getAttribute("rel") || "").toLowerCase();
302
+ if (rel.includes("nofollow")) return null;
303
+ if (a.getAttribute("data-no-instant") !== null) return null;
304
+ if (a.getAttribute("data-hyperinstant") === "off") return null;
305
+ if (a.target && a.target !== "_self") return null;
306
+
219
307
  return a;
220
308
  }
221
309
 
222
- function score(signal, a){
223
- var s=60;
224
- if(signal==="mousedown") s=100;
225
- else if(signal==="touchstart") s=96;
226
- else if(signal==="focusin") s=86;
227
- else if(signal==="mouseover") s=72;
228
- else if(signal==="viewport") s=40;
229
- if(inNav(a)) s+=8;
230
- if(String(a.getAttribute("data-hyperinstant-priority")||"").toLowerCase()==="high") s+=12;
231
- return Math.min(110,s);
310
+ function score(signal, a) {
311
+ var s = 60;
312
+ if (signal === "mousedown") s = 120;
313
+ else if (signal === "touchstart") s = 118;
314
+ else if (signal === "focusin") s = 98;
315
+ else if (signal === "mouseover") s = 82;
316
+
317
+ if (inNav(a)) s += 8;
318
+ if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12;
319
+ return Math.min(130, s);
320
+ }
321
+
322
+ function createTuner(cfg, stats) {
323
+ var intentCount = 0;
324
+ function maybeTune() {
325
+ var total = stats.prefetched + stats.prerendered;
326
+ if (total < 16) return;
327
+ var hr = stats.hitRate;
328
+
329
+ if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120);
330
+ else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);
331
+ }
332
+ return { onEnqueue: function () { intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune(); }, onClick: function () { } };
232
333
  }
233
334
 
234
- function startObservers(c, scheduler, stats, tuner){
235
- var hoverTimer=null;
335
+ function start(cfg, scheduler, stats, tuner) {
336
+ var hoverTimer = null;
337
+
338
+ function onIntent(e) {
339
+ var a = eligibleLink(e.target);
340
+ if (!a) return;
236
341
 
237
- function onIntent(e){
238
- var a=eligibleLink(e.target);
239
- if(!a) return;
240
- var url=a.href;
342
+ var url = a.href;
241
343
  stats.seen++;
242
- if(shouldDeny(url,c)){ stats.denied++; return; }
243
344
 
244
- var sig=e.type;
245
- var sc=score(sig,a);
345
+ if (shouldDeny(url, cfg)) { stats.denied++; return; }
346
+
347
+ var sig = e.type;
348
+ var sc = score(sig, a);
349
+
246
350
  clearTimeout(hoverTimer);
247
- var delay = (sig==="mouseover") ? c.hoverDelay : 0;
248
- hoverTimer=setTimeout(function(){
249
- scheduler.enqueue(url, sc);
351
+ var delay = (sig === "mouseover") ? cfg.hoverDelay : 0;
352
+
353
+ hoverTimer = setTimeout(function () {
354
+ scheduler.enqueue(url, sc, sig);
250
355
  tuner.onEnqueue();
251
356
  }, delay);
252
357
  }
253
358
 
254
- function onClick(e){
255
- var a=eligibleLink(e.target);
256
- if(!a) return;
257
- stats.markClick(a.href);
359
+ function onClick(e) {
360
+ var a = eligibleLink(e.target);
361
+ if (!a) return;
362
+ stats.markClick(normalizeUrl(a.href));
258
363
  tuner.onClick();
259
364
  }
260
365
 
261
- (c.events||["mouseover","touchstart","mousedown","focusin"]).forEach(function(evt){
262
- document.addEventListener(evt, onIntent, {passive:true, capture:true});
366
+ (cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt) {
367
+ document.addEventListener(evt, onIntent, { passive: true, capture: true });
263
368
  });
264
- document.addEventListener("click", onClick, {passive:true, capture:true});
265
-
266
- if (String(c.observe||"intent").toLowerCase().includes("viewport") && "IntersectionObserver" in window) {
267
- var io=new IntersectionObserver(function(entries){
268
- entries.forEach(function(ent){
269
- if(!ent.isIntersecting) return;
270
- var a=eligibleLink(ent.target);
271
- if(!a) return;
272
- var url=a.href;
273
- if(shouldDeny(url,c)) return;
274
- scheduler.enqueue(url, score("viewport", a));
275
- io.unobserve(ent.target);
276
- });
277
- }, {root:null, rootMargin:"200px 0px", threshold:0.01});
278
-
279
- function scan(){
280
- document.querySelectorAll("a[data-hyperinstant-viewport='on'][href]").forEach(function(a){ io.observe(a); });
281
- }
282
- scan();
283
- var mo=new MutationObserver(function(){ scan(); });
284
- mo.observe(document.documentElement, {childList:true, subtree:true});
285
- }
369
+ document.addEventListener("click", onClick, { passive: true, capture: true });
286
370
  }
287
371
 
288
- // init
289
- var c = applyModeDefaults(cfg);
290
- var adapter = detectAdapter(c.adapter);
291
- c = adapter(c);
372
+ var cfg = applyModeDefaults(userCfg);
373
+ cfg = detectAdapter(cfg.adapter)(cfg);
292
374
 
293
375
  var stats = new Stats();
294
- var tuner = createTuner(c, stats);
295
- var scheduler = createScheduler(c, stats);
376
+ var tuner = createTuner(cfg, stats);
377
+ var scheduler = createScheduler(cfg, stats);
378
+
379
+ window.HyperInstant = window.HyperInstant || {};
380
+ window.HyperInstant.version = VERSION;
381
+
382
+ if (cfg.debug) {
383
+ window.HyperInstantStats = stats;
384
+ console.log("[HyperInstant] v" + VERSION + " loaded", { mode: cfg.mode, adapter: cfg.adapter });
385
+ }
296
386
 
297
- if (c.debug) { window.HyperInstantStats = stats; console.log("[HyperInstant] v1.0.1 loaded", c); }
298
- startObservers(c, scheduler, stats, tuner);
299
- maybeOverlay(c, stats);
387
+ start(cfg, scheduler, stats, tuner);
388
+ maybeOverlay(cfg, stats);
300
389
  })();
@@ -1 +1 @@
1
- /*! HyperInstant v1.0.1 | MIT | Putia Web */ (function(){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var cfg = window.HyperInstant ||{}; function clamp(n,a,b){return Math.max(a, Math.min(b,n));} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;}catch(e){return false;}} function normalizeUrl(url){try{var u=new URL(url, location.href); u.hash=""; return u.toString();}catch(e){return "";}} function getConn(){var c=navigator.connection||navigator.mozConnection||navigator.webkitConnection; return{saveData:!!(c&&c.saveData), effectiveType:(c&&c.effectiveType)?String(c.effectiveType):"unknown", downlink:(c&&typeof c.downlink==="number")?c.downlink:null, rtt:(c&&typeof c.rtt==="number")?c.rtt:null};} function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");}catch(e){return false;}} function inNav(a){try{return !!a.closest("nav, header, [role='navigation'], .header, .site-header");}catch(e){return false;}} function applyModeDefaults(c){var mode = String(c.mode||"safe").toLowerCase(); var out = Object.assign({}, c); if (mode==="aggressive"){out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 2; out.observe = out.observe ?? "intent+viewport";} else if (mode==="balanced"){out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 1; out.observe = out.observe ?? "intent";} else{out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.observe = out.observe ?? "intent";} out.hoverDelay = out.hoverDelay ?? 65; out.debug = !!out.debug; out.debugOverlay = !!out.debugOverlay; out.budget = out.budget ?? "auto"; out.events = out.events ?? ["mouseover","touchstart","mousedown","focusin"]; out.deny = out.deny ?? []; return out;} function hasMeta(name){return !!document.querySelector('meta[name="'+name+'"]');} function shopifyAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/cart","/checkout","/account","/account/login","/account/register","/account/logout","/search","cart/add","cart/change","cart/update","?variant=","variant=","sections="].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function wooAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/cart","/checkout","/my-account","add-to-cart","wc-ajax","remove_item","update_cart","apply_coupon"].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function nopAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/shoppingcart","/checkout","/login","/register","/logout","/customer","addproducttocart","updateshoppingcart","updatecart","deleteitem","/wishlist","/compareproducts","/search","?q="].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function detectAdapter(name){name = String(name||"auto").toLowerCase(); if (name==="shopify") return shopifyAdapter; if (name==="woocommerce") return wooAdapter; if (name==="nopcommerce") return nopAdapter; if (name==="generic") return function(x){return x;}; try{if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter;}catch(e){} try{if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body&&String(document.body.className||"").includes("woocommerce"))) return wooAdapter;}catch(e){} try{if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter;}catch(e){} return function(x){return x;};} var AUTO_KEYWORDS=["logout","logoff","signin","sign-in","login","register","account","checkout","cart","basket","order","orders","pay","payment","add-to-cart","addtocart","addproducttocart","remove","update","change","delete","wc-ajax","admin-ajax","ajax","sections=","variant=","token","returnurl","redirect"]; var AUTO_PATHS=["/checkout","/cart","/shoppingcart","/account","/my-account","/logout","/login","/register"]; var AUTO_PARAMS=["add","remove","update","change","delete","token","variant","sections","returnurl","redirect"]; function shouldDeny(url, c){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url).toLowerCase(); var denyList = c.deny || []; for (var i=0;i<denyList.length;i++){var r=denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true;} try{var p = new URL(url, location.href).pathname.toLowerCase(); for (var j=0;j<AUTO_PATHS.length;j++){var ap=AUTO_PATHS[j]; if (p===ap || p.startsWith(ap+"/")) return true;}}catch(e){} for (var k=0;k<AUTO_KEYWORDS.length;k++){if (u.includes(AUTO_KEYWORDS[k])) return true;} try{var uu = new URL(url, location.href); var sp = uu.searchParams; for (var q=0;q<AUTO_PARAMS.length;q++){if (sp.has(AUTO_PARAMS[q])) return true;}}catch(e){} return false;} function Stats(){this.startedAt=Date.now(); this.seen=0; this.denied=0; this.enqueued=0; this.prefetched=0; this.prerendered=0; this.clicked=0; this.hit=0; this._loaded=new Map();} Stats.prototype.markLoaded=function(url,type){if(!this._loaded.has(url)){this._loaded.set(url,{type:type,t:Date.now()}); if(type==="prerender") this.prerendered++; else this.prefetched++;}}; Stats.prototype.isLoaded=function(url){return this._loaded.has(url);}; Stats.prototype.markClick=function(url){this.clicked++; if(this._loaded.has(url)) this.hit++;}; Object.defineProperty(Stats.prototype,"hitRate",{get:function(){var d=this.prefetched+this.prerendered; return d? (this.hit/d):0;}}); Stats.prototype.snapshot=function(){return{version:"1.0.1", uptimeSec:Math.round((Date.now()-this.startedAt)/1000), seen:this.seen, denied:this.denied, enqueued:this.enqueued, prefetched:this.prefetched, prerendered:this.prerendered, clicked:this.clicked, hit:this.hit, hitRate:Number(this.hitRate.toFixed(3))};}; function createScheduler(c, stats){var queue=[]; var inFlight=new Set(); var loaded=new Set(); var usedPrefetch=0, usedPrerender=0; function computeBudgets(){if (c.budget && typeof c.budget==="object"){return{maxPrefetch: c.budget.maxPrefetch ?? c.maxPrefetch, maxPrerender: c.budget.maxPrerender ?? c.maxPrerender};} var ci=getConn(); var mp=c.maxPrefetch, mr=c.maxPrerender; if (ci.saveData){mr=0; mp=Math.min(mp,2);} if (String(ci.effectiveType).includes("2g")){mr=0; mp=Math.min(mp,2);} if (String(ci.effectiveType).includes("3g")){mr=Math.min(mr,0); mp=Math.min(mp,4);} if (typeof ci.rtt==="number" && ci.rtt>300){mr=0; mp=Math.min(mp,3);} if (typeof ci.downlink==="number" && ci.downlink<1.2){mr=0; mp=Math.min(mp,3);} mp=clamp(mp,0,12); mr=clamp(mr,0,3); if (!supportsPrerender()) mr=0; return{maxPrefetch: mp, maxPrerender: mr};} function enqueue(rawUrl, score){var url=normalizeUrl(rawUrl); if(!url) return; stats.enqueued++; queue.push({url:url,score:score,t:Date.now()}); queue.sort(function(a,b){return b.score-a.score;}); pump();} function pump(){var b=computeBudgets(); while(queue.length){var n=queue.shift(); var url=n.url, score=n.score; if(loaded.has(url) || stats.isLoaded(url)) continue; if(inFlight.has(url)) continue; var wantPrerender = b.maxPrerender>usedPrerender && score>=90; var wantPrefetch = b.maxPrefetch>usedPrefetch; if(!wantPrerender && !wantPrefetch) break; var type = wantPrerender ? "prerender" : "prefetch"; if(type==="prerender") usedPrerender++; else usedPrefetch++; inFlight.add(url); var link=document.createElement("link"); link.rel=type; link.href=url; link.as="document"; link.fetchPriority = (score>=90) ? "high" : (score>=70 ? "auto" : "low"); link.onload=function(){loaded.add(url); inFlight.delete(url); stats.markLoaded(url,type);}; link.onerror=function(){inFlight.delete(url);}; document.head.appendChild(link);}} window.addEventListener("pageshow", function(){usedPrefetch=0; usedPrerender=0;}); return{enqueue: enqueue};} function createTuner(c, stats){var intentCount=0; function maybeTune(){var total = stats.prefetched + stats.prerendered; if(total<12) return; var hr = stats.hitRate; if (hr>=0.35) c.hoverDelay = clamp((c.hoverDelay||65)-10, 25, 140); else if (hr<=0.15) c.hoverDelay = clamp((c.hoverDelay||65)+15, 25, 180);} return{onEnqueue:function(){intentCount++; if(intentCount%20===0) maybeTune();}, onClick:function(){}};} function maybeOverlay(c, stats){if(!c.debug || !c.debugOverlay) return; var el=document.createElement("div"); el.style.cssText="position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px"; function render(){var s=stats.snapshot(); el.innerHTML="<div style='font-weight:700;margin-bottom:6px'>HyperInstant "+s.version+"</div>"+ "<div>Loaded: <b>"+(s.prefetched+s.prerendered)+"</b> (P:"+s.prefetched+" R:"+s.prerendered+")</div>"+ "<div>Hit-rate: <b>"+Math.round(s.hitRate*100)+"%</b></div>"+ "<div>HoverDelay: <b>"+c.hoverDelay+"ms</b></div>"+ "<div style='opacity:.8;margin-top:6px'>Clicks: "+s.clicked+" • Seen: "+s.seen+"</div>";} render(); document.addEventListener("DOMContentLoaded", function(){document.body.appendChild(el);}); setInterval(render, 1200);} function eligibleLink(target){var a = target && target.closest ? target.closest("a[href]") : null; if(!a || !a.href) return null; var rel = String(a.getAttribute("rel")||"").toLowerCase(); if(rel.includes("nofollow")) return null; if(a.getAttribute("data-no-instant")!==null) return null; if(a.getAttribute("data-hyperinstant")==="off") return null; if(a.target && a.target!=="_self") return null; return a;} function score(signal, a){var s=60; if(signal==="mousedown") s=100; else if(signal==="touchstart") s=96; else if(signal==="focusin") s=86; else if(signal==="mouseover") s=72; else if(signal==="viewport") s=40; if(inNav(a)) s+=8; if(String(a.getAttribute("data-hyperinstant-priority")||"").toLowerCase()==="high") s+=12; return Math.min(110,s);} function startObservers(c, scheduler, stats, tuner){var hoverTimer=null; function onIntent(e){var a=eligibleLink(e.target); if(!a) return; var url=a.href; stats.seen++; if(shouldDeny(url,c)){stats.denied++; return;} var sig=e.type; var sc=score(sig,a); clearTimeout(hoverTimer); var delay = (sig==="mouseover") ? c.hoverDelay : 0; hoverTimer=setTimeout(function(){scheduler.enqueue(url, sc); tuner.onEnqueue();}, delay);} function onClick(e){var a=eligibleLink(e.target); if(!a) return; stats.markClick(a.href); tuner.onClick();} (c.events||["mouseover","touchstart","mousedown","focusin"]).forEach(function(evt){document.addEventListener(evt, onIntent,{passive:true, capture:true});}); document.addEventListener("click", onClick,{passive:true, capture:true}); if (String(c.observe||"intent").toLowerCase().includes("viewport") && "IntersectionObserver" in window){var io=new IntersectionObserver(function(entries){entries.forEach(function(ent){if(!ent.isIntersecting) return; var a=eligibleLink(ent.target); if(!a) return; var url=a.href; if(shouldDeny(url,c)) return; scheduler.enqueue(url, score("viewport", a)); io.unobserve(ent.target);});},{root:null, rootMargin:"200px 0px", threshold:0.01}); function scan(){document.querySelectorAll("a[data-hyperinstant-viewport='on'][href]").forEach(function(a){io.observe(a);});} scan(); var mo=new MutationObserver(function(){scan();}); mo.observe(document.documentElement,{childList:true, subtree:true});}} // init var c = applyModeDefaults(cfg); var adapter = detectAdapter(c.adapter); c = adapter(c); var stats = new Stats(); var tuner = createTuner(c, stats); var scheduler = createScheduler(c, stats); if (c.debug){window.HyperInstantStats = stats; console.log("[HyperInstant] v1.0.1 loaded", c);} startObservers(c, scheduler, stats, tuner); maybeOverlay(c, stats);})();
1
+ /*! HyperInstant v1.0.2 | MIT | Putia Web */ (function (){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var userCfg = window.HyperInstant ||{}; var VERSION = "1.0.2"; function clamp(n, a, b){return Math.max(a, Math.min(b, n));} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;} catch (e){return false;}} function normalizeUrl(url){try{var u = new URL(url, location.href); u.hash = ""; return u.toString();} catch (e){return "";}} function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");} catch (e){return false;}} function getConn(){var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return{saveData: !!(c && c.saveData), effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown", downlink: (c && typeof c.downlink === "number") ? c.downlink : null, rtt: (c && typeof c.rtt === "number") ? c.rtt : null};} function inNav(a){try{return !!a.closest("nav, header, [role='navigation'], .header, .site-header");} catch (e){return false;}} function applyModeDefaults(cfg){var mode = String(cfg.mode || "safe").toLowerCase(); var out = Object.assign({}, cfg); // v1.0.2: prerender is intentionally conservative to avoid slowing pages if (mode === "aggressive"){out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 1; out.hoverDelay = out.hoverDelay ?? 35;} else if (mode === "balanced"){out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 65;} else{out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 75;} out.adapter = out.adapter ?? "auto"; out.observe = out.observe ?? "intent"; out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"]; out.deny = out.deny ?? []; out.debug = !!out.debug; out.debugOverlay = !!out.debugOverlay; // new in 1.0.2 out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender; out.autoTune = !!out.autoTune; // OFF by default out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200); return out;} function hasMeta(name){return !!document.querySelector('meta[name="' + name + '"]');} function shopifyAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search", "cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function wooAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function nopAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts", "/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function detectAdapter(name){name = String(name || "auto").toLowerCase(); if (name === "shopify") return shopifyAdapter; if (name === "woocommerce") return wooAdapter; if (name === "nopcommerce") return nopAdapter; if (name === "generic") return function (c){return c;}; try{if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter;} catch (e){} try{if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter;} catch (e){} try{if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter;} catch (e){} return function (c){return c;};} // Safer auto-deny (avoid broad keywords like "order" that can hit benign pages) var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"]; var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"]; var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="]; function shouldDeny(url, cfg){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url).toLowerCase(); var denyList = cfg.deny || []; for (var i = 0; i < denyList.length; i++){var r = denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true;} try{var p = new URL(url, location.href).pathname.toLowerCase(); for (var j = 0; j < AUTO_PATH_PREFIX.length; j++){var ap = AUTO_PATH_PREFIX[j]; if (p === ap || p.startsWith(ap + "/")) return true;}} catch (e){} for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true; try{var uu = new URL(url, location.href); var sp = uu.searchParams; for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;} catch (e){} return false;} function Stats(){this.startedAt = Date.now(); this.seen = 0; this.denied = 0; this.enqueued = 0; this.prefetched = 0; this.prerendered = 0; this.clicked = 0; this.hit = 0; this._loaded = new Set();} Stats.prototype.isLoaded = function (url){return this._loaded.has(url);}; Stats.prototype.markLoaded = function (url, type){if (this._loaded.has(url)) return; this._loaded.add(url); if (type === "prerender") this.prerendered++; else this.prefetched++;}; Stats.prototype.markClick = function (url){this.clicked++; if (this._loaded.has(url)) this.hit++;}; Object.defineProperty(Stats.prototype, "hitRate",{get: function (){var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0;}}); Stats.prototype.snapshot = function (){return{version: VERSION, uptimeSec: Math.round((Date.now() - this.startedAt) / 1000), seen: this.seen, denied: this.denied, enqueued: this.enqueued, prefetched: this.prefetched, prerendered: this.prerendered, clicked: this.clicked, hit: this.hit, hitRate: Number(this.hitRate.toFixed(3))};}; function createScheduler(cfg, stats){var q = []; var qSet = new Set(); var inFlight = new Set(); var usedPrefetch = 0; var usedPrerender = 0; var prerenderSupported = supportsPrerender(); function budgets(){var mp = cfg.maxPrefetch; var mr = cfg.maxPrerender; if (!cfg.enablePrerender || !prerenderSupported) mr = 0; var ci = getConn(); if (ci.saveData){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("2g")){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("3g")){mr = 0; mp = Math.min(mp, 4);} if (typeof ci.rtt === "number" && ci.rtt > 350){mr = 0; mp = Math.min(mp, 3);} if (typeof ci.downlink === "number" && ci.downlink < 1.2){mr = 0; mp = Math.min(mp, 3);} mp = clamp(mp, 0, 12); mr = clamp(mr, 0, 3); return{mp: mp, mr: mr};} function enqueue(rawUrl, score, signal){var url = normalizeUrl(rawUrl); if (!url) return; if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return; if (q.length >= cfg.maxQueue){var minIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i; var dropped = q.splice(minIdx, 1)[0]; if (dropped) qSet.delete(dropped.url);} stats.enqueued++; q.push({url: url, score: score, signal: signal, t: Date.now()}); qSet.add(url); pump();} function pump(){var b = budgets(); function takeBest(){if (!q.length) return null; var bestIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i; var item = q.splice(bestIdx, 1)[0]; if (item) qSet.delete(item.url); return item;} while (q.length){var item = takeBest(); if (!item) break; var url = item.url; var score = item.score; var signal = item.signal; if (stats.isLoaded(url) || inFlight.has(url)) continue; var canPrefetch = usedPrefetch < b.mp; var canPrerender = usedPrerender < b.mr; // v1.0.2: prerender only on HIGH confidence signals (not hover) var highIntent = (signal === "mousedown" || signal === "touchstart"); var doPrerender = canPrerender && highIntent && score >= 110; if (!doPrerender && !canPrefetch) break; var type = doPrerender ? "prerender" : "prefetch"; if (type === "prerender") usedPrerender++; else usedPrefetch++; inFlight.add(url); var link = document.createElement("link"); link.rel = type; link.href = url; link.as = "document"; link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low"); (function (u, t){link.onload = function (){inFlight.delete(u); stats.markLoaded(u, t);}; link.onerror = function (){inFlight.delete(u);};})(url, type); document.head.appendChild(link);}} window.addEventListener("pageshow", function (){usedPrefetch = 0; usedPrerender = 0;}); return{enqueue: enqueue};} function maybeOverlay(cfg, stats){if (!cfg.debug || !cfg.debugOverlay) return; var el = document.createElement("div"); el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px"; function render(){var s = stats.snapshot(); el.innerHTML = "<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" + "<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" + "<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" + "<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" + "<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";} render(); document.addEventListener("DOMContentLoaded", function (){document.body.appendChild(el);}); setInterval(render, 1200);} function eligibleLink(target){var a = target && target.closest ? target.closest("a[href]") : null; if (!a || !a.href) return null; var rel = String(a.getAttribute("rel") || "").toLowerCase(); if (rel.includes("nofollow")) return null; if (a.getAttribute("data-no-instant") !== null) return null; if (a.getAttribute("data-hyperinstant") === "off") return null; if (a.target && a.target !== "_self") return null; return a;} function score(signal, a){var s = 60; if (signal === "mousedown") s = 120; else if (signal === "touchstart") s = 118; else if (signal === "focusin") s = 98; else if (signal === "mouseover") s = 82; if (inNav(a)) s += 8; if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12; return Math.min(130, s);} function createTuner(cfg, stats){var intentCount = 0; function maybeTune(){var total = stats.prefetched + stats.prerendered; if (total < 16) return; var hr = stats.hitRate; if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120); else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);} return{onEnqueue: function (){intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune();}, onClick: function (){}};} function start(cfg, scheduler, stats, tuner){var hoverTimer = null; function onIntent(e){var a = eligibleLink(e.target); if (!a) return; var url = a.href; stats.seen++; if (shouldDeny(url, cfg)){stats.denied++; return;} var sig = e.type; var sc = score(sig, a); clearTimeout(hoverTimer); var delay = (sig === "mouseover") ? cfg.hoverDelay : 0; hoverTimer = setTimeout(function (){scheduler.enqueue(url, sc, sig); tuner.onEnqueue();}, delay);} function onClick(e){var a = eligibleLink(e.target); if (!a) return; stats.markClick(normalizeUrl(a.href)); tuner.onClick();} (cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt){document.addEventListener(evt, onIntent,{passive: true, capture: true});}); document.addEventListener("click", onClick,{passive: true, capture: true});} var cfg = applyModeDefaults(userCfg); cfg = detectAdapter(cfg.adapter)(cfg); var stats = new Stats(); var tuner = createTuner(cfg, stats); var scheduler = createScheduler(cfg, stats); window.HyperInstant = window.HyperInstant ||{}; window.HyperInstant.version = VERSION; if (cfg.debug){window.HyperInstantStats = stats; console.log("[HyperInstant] v" + VERSION + " loaded",{mode: cfg.mode, adapter: cfg.adapter});} start(cfg, scheduler, stats, tuner); maybeOverlay(cfg, stats);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperinstant",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Adaptive, safe and intelligent instant navigation engine for any website.",
5
5
  "main": "dist/hyperinstant.min.js",
6
6
  "module": "dist/hyperinstant.js",
@@ -20,12 +20,5 @@
20
20
  "navigation"
21
21
  ],
22
22
  "author": "Putia Web",
23
- "license": "MIT",
24
- "repository": {
25
- "type": "git",
26
- "url": "https://example.com/putiaweb/hyperinstant"
27
- },
28
- "bugs": {
29
- "url": "https://example.com/putiaweb/hyperinstant/issues"
30
- }
23
+ "license": "MIT"
31
24
  }