1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8" /> 6
6<script> 7 // Self-heal: when an /assets/* script fails to load (typically CDN 8 // cache poisoning where the path returns text/html SPA fallback 9 // during deploy transitions, or a stale chunk hash from cached 10 // index.html), force-refetch the asset bypassing disk cache then 11 // reload the page. Capped at 2 attempts per session via 12 // sessionStorage to prevent infinite reload loops. Must be inline 13 // and first in <head> so the listener is registered before any 14 // module starts loading. 15 (function () { 16 var KEY = '__reader_heal_count'; 17 var healed = false; 18 // Fire-and-forget telemetry to /error-report. keepalive lets the 19 // POST complete after the location.reload that follows. JWT is 20 // attached if present so user_id gets correlated server-side. 21 function reportHeal(assetUrl, attempt, action) { 22 try { 23 var token = null; 24 try { token = localStorage.getItem('easeverse_token'); } catch (_) {} 25 var headers = { 'Content-Type': 'application/json' }; 26 if (token) headers['Authorization'] = 'Bearer ' + token; 27 fetch('https://api.easeverse.ink/error-report', { 28 method: 'POST', 29 headers: headers, 30 body: JSON.stringify({ 31 client: 'reader', 32 level: action === 'give-up' ? 'warn' : 'info', 33 message: action === 'give-up' 34 ? 'self-heal limit reached, gave up at attempt ' + attempt 35 : 'self-heal triggered (attempt ' + attempt + ')', 36 url: location.href, 37 metadata: { 38 source: 'self-heal', 39 action: action, 40 path: assetUrl, 41 }, 42 }), 43 keepalive: true, 44 }).catch(function () {}); 45 } catch (_e) {} 46 } 47 window.addEventListener('error', function (e) { 48 if (healed) return; 49 var t = e && e.target; 50 if (!t) return; 51 if (t.tagName !== 'SCRIPT' && t.tagName !== 'LINK') return; 52 var url = t.src || t.href || ''; 53 if (url.indexOf('/assets/') === -1) return; 54 var count = parseInt(sessionStorage.getItem(KEY) || '0', 10); 55 if (count >= 2) { 56 console.warn('[reader] heal limit reached, giving up'); 57 reportHeal(url, count, 'give-up'); 58 return; 59 } 60 healed = true; 61 var nextAttempt = count + 1; 62 sessionStorage.setItem(KEY, String(nextAttempt)); 63 console.warn('[reader] asset load failure, refreshing cache:', url); 64 reportHeal(url, nextAttempt, 'trigger'); 65 var done = function () { window.location.reload(); }; 66 try { 67 fetch(url, { cache: 'reload', credentials: 'same-origin' }) 68 .then(done, done); 69 } catch (_e) { 70 done(); 71 } 72 }, true); 73 window.addEventListener('load', function () { 74 setTimeout(function () { 75 if (!healed) sessionStorage.removeItem(KEY); 76 }, 3000); 77 }); 78 })(); 79 </script>
79 80 <link rel="icon" type="image/jpeg" href="/EaseVerse_Reader_ClassicBlue_Depth_v3.jpg?v=1" sizes="32x32" /> 81 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> 82 <!-- Mobile-optimized signal for Xiaomi/Huawei in-app browsers â without 83 these, those browsers force-scale fonts < 12px and break rem layout. --> 84 <meta name="HandheldFriendly" content="true" /> 85 <meta name="MobileOptimized" content="width" /> 86 <!-- Static first-paint fallback; JS replaces document.title with the 87 language-specific value in src/lib/i18n.js on module load. --> 88 <meta name="description" content="EaseVerse Reader ï½ Multilingual Deep Reading" /> 89 <title>EaseVerse Reader ï½ Multilingual Deep Reading</title> 90 91 <!-- Preload fonts --> 92 <link rel="preconnect" href="https://fonts.googleapis.com"> 93 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 94 <link 95 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&family=Literata:ital,opsz,wght@0,7..72,200..900;1,7..72,200..900&family=Source+Sans+3:wght@300;400;500;600&family=Roboto:wght@300;400;500;700&family=Poppins:wght@300;400;500;600&family=Mulish:wght@300;400;500;600;700&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=PT+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" 96 rel="stylesheet"> 97 <!-- Do NOT add crossorigin="anonymous" here. Google's gsi/client endpoint 98 returns no Access-Control-Allow-Origin header, so the browser would 99 block the script entirely (ERR_FAILED) and the Sign-In button would 100 never render. The cost is that any gsi-internal errors arrive as 101 opaque "Script error." with no stack â handled (filtered) in 102 src/lib/error-reporter.js to keep that noise out of the DB. --> 103
103<script src="https://accounts.google.com/gsi/client" async></script>
103 104
104<script type="module" crossorigin src="/assets/index-CGi0bt1A.js"></script>
104 105 <link rel="modulepreload" crossorigin href="/assets/react-D88zDUjx.js"> 106 <link rel="modulepreload" crossorigin href="/assets/epub-iBTXOzSI.js"> 107 <link rel="stylesheet" crossorigin href="/assets/index-BPp4UNtk.css"> 108</head> 109 110<body> 111 <div id="root"></div>
112<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"9ea5289a55fd4c5c8f88b225f468fe34","r":1,"spa":2}' crossorigin="anonymous"></script>
112 113</body> 114 115</html>
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.