PageSourceSearch

https://openmuseum.tw/auth-assets-7q2k9x4m/auth.js

js openmuseum.tw collected 2026-10-02 10:30:41 UTC 22,990 bytes, 332 lines download raw bytes

1/* auth.js — ASCDC WAF 認證閘前端(完整版,套版自 auth-ui-mockup)。權威源:GitLab dockerfile/auth-www-nginx。
2   功能:i18n 16 語、深淺主題、字型選擇器(依語系/OS,只用本機既有字型)、背景切換(4 款,<link> 切換免 CSP)、
3        版型切換(split/portrait/stage)、可拖曳分隔線、顯示/隱藏密碼、記住帳號、左側廣告輪播(自動播+進度條+箭頭+swipe)。
4   ❌ 不攔截表單送出(真表單 = PHP POST /validUser);無假 captcha/假成功/demo。
5   🔴 pre-auth 邊界:CSP 維持 style-src/font-src 'self',全頁零外連字型(無 Google Fonts);背景用同源 <link> 切換。 */
6(function () {
7  "use strict";
8  var LS = { lang: "ascdc.lang", theme: "ascdc.theme", account: "ascdc.account", remember: "ascdc.remember", bg: "ascdc.bg.v3", layout: "ascdc.layout.v3", font: "ascdc.font", formw: "ascdc.formw" };
9  var I18N = null, META = null, STR = null, LANG = null, adsUpdate = null;
10  var AB = "/auth-assets";
11  function lsGet(k){try{return window.localStorage.getItem(k);}catch(e){return null;}}
12  function lsSet(k,v){try{window.localStorage.setItem(k,v);}catch(e){}}
13  function lsDel(k){try{window.localStorage.removeItem(k);}catch(e){}}
14
15  function loadI18N() {
16    return fetch(AB + "/i18n.json", { credentials: "same-origin" })
17      .then(function (r) { if (!r.ok) throw new Error("i18n " + r.status); return r.json(); })
18      .catch(function () { return window.__I18N__ || null; });
19  }
20  function canonLang(tag) {
21    if (!tag) return null;
22    tag = String(tag).toLowerCase();
23    if (tag === "zh" || tag.indexOf("zh-tw") === 0 || tag.indexOf("zh-hant") === 0 || tag.indexOf("zh-hk") === 0 || tag.indexOf("zh-mo") === 0) return "zh-Hant";
24    if (tag.indexOf("zh-cn") === 0 || tag.indexOf("zh-hans") === 0 || tag.indexOf("zh-sg") === 0) return "zh-Hans";
25    if (tag.indexOf("zh") === 0) return "zh-Hant";
26    if (tag === "tl" || tag.indexOf("tl-") === 0 || tag.indexOf("fil") === 0) return "fil";
27    var codes = Object.keys(I18N.strings);
28    for (var i = 0; i < codes.length; i++) { if (codes[i].toLowerCase() === tag) return codes[i]; }
29    var two = tag.slice(0, 2);
30    for (var j = 0; j < codes.length; j++) { if (codes[j].indexOf("-") < 0 && codes[j].toLowerCase() === two) return codes[j]; }
31    return null;
32  }
33  function pickLang() {
34    var docLang = canonLang(document.documentElement.getAttribute("lang"));
35    if (docLang && I18N.strings[docLang]) return docLang;
36    var saved = lsGet(LS.lang);
37    if (saved && I18N.strings[saved]) return saved;
38    var nav = canonLang(navigator.language || "en");
39    if (nav && I18N.strings[nav]) return nav;
40    return META.fallback || "en";
41  }
42  function applyLang(code) {
43    LANG = code; STR = I18N.strings[code] || I18N.strings[META.fallback || "en"];
44    var info = (META.languages || []).filter(function (l) { return l.code === code; })[0] || { dir: "ltr" };
45    document.documentElement.lang = code;
46    document.documentElement.dir = info.dir || "ltr";
47    document.querySelectorAll("[data-i18n]").forEach(function (el) { var v = STR[el.dataset.i18n]; if (v != null) el.textContent = v; });
48    document.querySelectorAll("[data-i18n-ph]").forEach(function (el) { var v = STR[el.dataset.i18nPh]; if (v != null) el.placeholder = v; });
49    document.querySelectorAll("[data-i18n-aria]").forEach(function (el) { var v = STR[el.dataset.i18nAria]; if (v != null) el.setAttribute("aria-label", v); });
50    var sel = document.getElementById("langSelect"); if (sel) sel.value = code;
51    if (STR.brand) document.title = STR.brand;       // 切語言即時更新分頁標題
52    refreshFonts();                                  // 切語言 → 依新語系重建字型清單
53    if (adsUpdate) adsUpdate();
54  }
55  function buildLang() {
56    var sel = document.getElementById("langSelect"); if (!sel) return;
57    sel.innerHTML = (META.languages || []).map(function (l) { return '<option value="' + l.code + '">' + l.name + "</option>"; }).join("");
58    sel.value = LANG;
59    sel.addEventListener("change", function () {
60      lsSet(LS.lang, sel.value);
61      // SameSite=Lax(非 Strict):跨站 SSO(id.ascdc.tw→*.openmuseum.tw)導回 /validUser 是頂層 GET,
62      // Strict 會扣住 AUTH_LANG 使語系重置回預設。與 AUTH session cookie 的 SameSite 策略一致。
63      document.cookie = "AUTH_LANG=" + encodeURIComponent(sel.value) + "; Path=/; Max-Age=31536000; SameSite=Lax; Secure";
64      applyLang(sel.value);
65    });
66  }
67
68  function sysDark() { return window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches; }
69  function initTheme() {
70    var saved = lsGet(LS.theme);
71    if (saved === "dark" || saved === "light") document.documentElement.setAttribute("data-theme", saved);
72    var btn = document.getElementById("themeToggle"); if (!btn) return;
73    btn.addEventListener("click", function () {
74      var cur = document.documentElement.getAttribute("data-theme") || (sysDark() ? "dark" : "light");
75      var next = cur === "dark" ? "light" : "dark";
76      document.documentElement.setAttribute("data-theme", next);
77      lsSet(LS.theme, next);
78    });
79  }
80
81  /* ---------- 背景切換(4 款;同源 <link> 切換,CSP style-src 'self' 允許)---------- */
82  var BG_ORDER = ["aurora", "ivory", "orbs", "blueprint"];
83  var BG_NAMES = { aurora: "極光紗 Aurora", ivory: "宣紙墨韻 Ivory", orbs: "靜謐光暈 Orbs", blueprint: "藍曬格網 Blueprint" };
84  function applyBg(key) {
85    var link = document.getElementById("bg-link");
86    if (key === "aurora") { if (link && link.parentNode) link.parentNode.removeChild(link); }   // 預設 aurora 在 auth.css
87    else {
88      if (!link) { link = document.createElement("link"); link.id = "bg-link"; link.rel = "stylesheet"; document.head.appendChild(link); }
89      link.href = AB + "/bg-" + key + ".css";
90    }
91    document.documentElement.setAttribute("data-bg", key);
92  }
93  function initBg() {
94    var controls = document.querySelector(".controls"); if (!controls) return;
95    var saved = lsGet(LS.bg);
96    var cur = (saved && BG_ORDER.indexOf(saved) >= 0) ? saved : BG_ORDER[0];
97    applyBg(cur);
98    var btn = document.createElement("button");
99    btn.className = "theme-toggle"; btn.type = "button"; btn.id = "bgToggle";
99 btn.setAttribute("aria-label", "Background");
100    btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M3 15l5-5 4 4 3-3 6 6"/><circle cx="8.5" cy="8.5" r="1.4"/></svg>';
101    btn.title = "背景 / Background: " + (BG_NAMES[cur] || cur);
102    btn.addEventListener("click", function () {
103      var i = BG_ORDER.indexOf(document.documentElement.getAttribute("data-bg") || BG_ORDER[0]);
104      var next = BG_ORDER[(i + 1) % BG_ORDER.length];
105      lsSet(LS.bg, next); applyBg(next); btn.title = "背景 / Background: " + (BG_NAMES[next] || next);
106    });
107    controls.appendChild(btn);
108  }
109
110  /* ---------- 字型選擇器(依語系+OS,只用使用者本機既有字型)----------
111     pre-auth 認證閘邊界:CSP 維持 style-src/font-src 'self',絕不外連載字型(無 Google Fonts)。
112     涵蓋各語系原生字型(Win 正黑/雅黑/游ゴシック/Malgun/Leelawadee、Mac 蘋方/Hiragino/AppleSD/Thonburi);
113     fonts.js 中標 web: 的開源字型一律忽略不顯示、不載入。 */
114  function detectOS() {
115    var p = ((navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || navigator.userAgent || "").toLowerCase();
116    if (p.indexOf("win") >= 0) return "win";
117    if (p.indexOf("mac") >= 0 || p.indexOf("iphone") >= 0 || p.indexOf("ipad") >= 0 || p.indexOf("ios") >= 0) return "mac";
118    if (p.indexOf("android") >= 0) return "android";
119    if (p.indexOf("linux") >= 0) return "linux";
120    return "other";
121  }
122  var fontSel = null, fontOS = "other";
123  function fontMap() { try { return JSON.parse(lsGet(LS.font) || "{}") || {}; } catch (e) { return {}; } }
124  function applyFont(id) {
125    var o = (window.__FONTS__ || []).filter(function (x) { return x.id === id; })[0];
126    var rs = document.documentElement.style;
127    // system / 不存在 / 任何 web 字型 → 一律回系統字,絕不外連載字型(pre-auth 邊界)
128    if (!o || id === "system" || o.web) { rs.removeProperty("--font"); rs.removeProperty("--font-display"); return; }
129    rs.setProperty("--font", o.stack); rs.setProperty("--font-display", o.stack);
130  }
131  function fLabel(k, fb) { return (STR && STR[k]) || fb; }
132  function buildFontOptions() {
133    if (!fontSel) return;
134    var scripts = (window.__FONT_LANG_SCRIPTS__ || {})[LANG] || ["latin"];
135    var list = window.__FONTS__ || [];
136    fontSel.innerHTML = "";
137    var ogSys = document.createElement("optgroup"); ogSys.label = fLabel("font_system", "系統");
138    var ogOS = document.createElement("optgroup"); ogOS.label = fLabel("font_builtin", "本機字型");
139    function opt(id, txt) { var e = document.createElement("option"); e.value = id; e.textContent = txt; return e; }
140    list.forEach(function (o) {
141      if (o.id === "system") { ogSys.appendChild(opt("system", fLabel("font_default", "系統預設"))); return; }
142      if (o.web) return;                                  // 開源網路字型不顯示(pre-auth 不外連)
143      if (scripts.indexOf(o.script) < 0) return;
144      if (o.os === fontOS) ogOS.appendChild(opt(o.id, o.label));
145    });
146    fontSel.appendChild(ogSys);
147    if (ogOS.children.length) fontSel.appendChild(ogOS);
148    var saved = fontMap()[LANG] || "system", ok = false;
149    for (var i = 0; i < fontSel.options.length; i++) if (fontSel.options[i].value === saved) ok = true;
150    if (!ok) saved = "system";
151    fontSel.value = saved; applyFont(saved);
152  }
153  function refreshFonts() { if (fontSel) buildFontOptions(); }
154  function initFonts() {
155    if (!(window.__FONTS__ || []).length) return;
156    var controls = document.querySelector(".controls"); if (!controls) return;
157    fontOS = detectOS();
158    var wrap = document.createElement("div"); wrap.className = "lang-switch font-switch";
159    fontSel = document.createElement("select"); fontSel.id = "fontSelect"; fontSel.setAttribute("aria-label", "Font");
160    wrap.appendChild(fontSel);
161    var langWrap = controls.querySelector(".lang-switch");
162    controls.insertBefore(wrap, langWrap ? langWrap.nextSibling : controls.firstChild);
163    buildFontOptions();
164    fontSel.addEventListener("change", function () {
165      var m = fontMap(); m[LANG] = fontSel.value; lsSet(LS.font, JSON.stringify(m)); applyFont(fontSel.value);
166    });
167  }
168
169  /* ---------- 版型切換(split/portrait/stage;CSS 在 auth.css)---------- */
170  var LAYOUT_ORDER = ["split", "portrait", "stage"];
171  var LAYOUT_NAMES = { split: "雙欄 Split", portrait: "直幅 Portrait", stage: "報頭 Stage" };
172  function initLayout() {
173    if (!document.querySelector(".scene")) return;
174    var controls = document.querySelector(".controls"); if (!controls) return;
175    var saved = lsGet(LS.layout);
176    var cur = (saved && LAYOUT_ORDER.indexOf(saved) >= 0) ? saved : (document.body.getAttribute("data-layout") || LAYOUT_ORDER[0]);
177    document.body.setAttribute("data-layout", cur);
178    var btn = document.createElement("button");
179    btn.className = "theme-toggle"; btn.type = "button"; btn.id = "layoutToggle"; btn.setAttribute("aria-label", "Layout");
180    btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="18" rx="1.5"/><rect x="13" y="3" width="8" height="8" rx="1.5"/><rect x="13" y="14" width="8" height="7" rx="1.5"/></svg>';
181    btn.title = "版型 / Layout: " + (LAYOUT_NAMES[cur] || cur);
182    btn.addEventListener("click", function () {
183      var i = LAYOUT_ORDER.indexOf(document.body.getAttribute("data-layout") || LAYOUT_ORDER[0]);
184      var next = LAYOUT_ORDER[(i + 1) % LAYOUT_ORDER.length];
185      document.body.setAttribute("data-layout", next);
186      lsSet(LS.layout, next); btn.title = "版型 / Layout: " + (LAYOUT_NAMES[next] || next);
187      window.dispatchEvent(new Event("resize"));
188    });
189    controls.appendChild(btn);
190  }
191
192  /* ---------- 可拖曳分隔線(電腦版調整右側表單寬;雙擊還原)---------- */
193  function initSplitter() {
194    var scene = document.querySelector(".scene"); if (!scene || scene.querySelector(".split-handle")) return;
195    var saved = parseInt(lsGet(LS.formw), 10);
196    if (saved) document.documentElement.style.setProperty("--form-w", saved + "px");
197    var h = document.createElement("div"); h.className = "split-handle";
198    h.setAttribute("role", "separator"); h.setAttribute("aria-label", "拖曳調整寬度,雙擊還原");
199    scene.appendChild(h);
200    var dragging = false;
201    function clampW(w) { var sw = scene.getBoundingClientRect().width; return Math.max(360, Math.min(Math.min(760, sw - 380), w)); }
202    h.addEventListener("pointerdown", function (e) { dragging = true; h.classList.add("dragging"); try { h.setPointerCapture(e.pointerId); } catch (_) {} document.body.style.userSelect = "none"; e.preventDefault(); });
203    h.addEventListener("pointermove", function (e) { if (!dragging) return; var w = clampW(scene.getBoundingClientRect().right - e.clientX); document.documentElement.style.setProperty("--form-w", w + "px"); window.dispatchEvent(new Event("resize")); });
204    function end() { if (!dragging) return; dragging = false; h.classList.remove("dragging"); document.body.style.userSelect = ""; var px = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--form-w"), 10); if (px) lsSet(LS.formw, px); }
205    h.addEventListener("pointerup", end); h.addEventListener("pointercancel", end);
206    h.addEventListener("dblclick", function () { document.documentElement.style.removeProperty("--form-w"); lsDel(LS.formw); });
207  }
208
209  function initForm() {
210    document.querySelectorAll(".show-pw, .toggle-pw").forEach(function (b) {
211      b.addEventListener("click", function () {
212        var inp = b.parentNode.querySelector("input[type=password],input[type=text][data-pw]");
213        if (!inp) return;
214        var show = inp.type === "password";
215        inp.type = show ? "text" : "password"; inp.setAttribute("data-pw", "1");
216        b.classList.toggle("on", show); b.setAttribute("aria-pressed", show ? "true" : "false");
217      });
218    });
219    var acc = document.getElementById("account"), rem = document.getElementById("remember");
220    if (acc && rem) {
221      if (lsGet(LS.remember) === "1") { rem.checked = true; acc.value = lsGet(LS.account) || acc.value; }
222      var form = acc.form;
223      if (form) form.addEventListener("submit", function () {
224        if (rem.checked) { lsSet(LS.remember, "1"); lsSet(LS.account, acc.value); }
225        else { lsDel(LS.remember); lsDel(LS.account); }
226      });
227    }
228  }
229
230  /* ---------- 左側廣告輪播 ---------- */
231  function initAds() {
232    var data = window.__SHOWCASE__; if (!data || !data.items || !data.items.length) return;
233    var brand = document.querySelector(".scene-brand"); if (!brand || brand.querySelector(".sc-rail")) return;
234    var rail = document.createElement("div"); rail.className = "sc-rail";
235    var track = document.createElement("div"); track.className = "sc-strip";
236    var MAX_SHOW = 10, picked = data.items.slice();   // 每次最多隨機抽 10 則(Fisher–Yates)
237    if (picked.length > MAX_SHOW) {
238      for (var sh = picked.length - 1; sh > 0; sh--) { var jj = Math.floor(Math.random() * (sh + 1)); var tp = picked[sh]; picked[sh] = picked[jj]; picked[jj] = tp; }
239      picked = picked.slice(0, MAX_SHOW);
240    }
241    picked.forEach(function (it, i) {
242      var a = document.createElement("a"); a.className = "sc-slide" + (i === 0 ? " active" : "");
243      a.href = it.link; a.target = "_blank"; a.rel = "noopener noreferrer"; a.dataset.idx = data.items.indexOf(it);
244      a.innerHTML =
245        '<span class="sc-cover" data-bg="' + it.img + '"></span>' +
246        '<span class="sc-photo" data-bg="' + it.img + '"></span>' +
247        '<img class="sc-om" src="' + (it.logo || data.om_logo) + '" alt="">' +
248        '<span class="sc-teaser"></span>' +
249        '<span class="sc-meta"><span class="sc-cat"></span><span class="sc-title"></span><span class="sc-sub"></span></span>';
250      track.appendChild(a);
251    });
252    var dots = document.createElement("div"); dots.className = "sc-dots";
253    picked.forEach(function (_, i) { var d = document.createElement("button"); d.type = "button"; d.className = "sc-dot" + (i === 0 ? " on" : ""); d.setAttribute("aria-label", "ad " + (i + 1)); dots.appendChild(d); });
254    var prog = document.createElement("div"); prog.className = "sc-progress"; var pbar = document.createElement("span"); pbar.className = "sc-progress-bar"; prog.appendChild(pbar);
255    var nav = document.createElement("div"); nav.className = "sc-nav";
256    var prev = document.createElement("button"); prev.type = "button"; prev.className = "sc-nav-btn sc-prev"; prev.setAttribute("aria-label", "prev");
257    prev.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 6 9 12 15 18"/></svg>';
258    var next = document.createElement("button"); next.type = "button"; next.className = "sc-nav-btn sc-next"; next.setAttribute("aria-label", "next");
259    next.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>';
260    nav.appendChild(prev); nav.appendChild(next);
261    var eb = document.createElement("a"); eb.className = "sc-eyebrow"; eb.href = data.site || "https://ascdc.sinica.edu.tw/databases"; eb.target = "_blank"; eb.rel = "noopener noreferrer";
262    eb.innerHTML = '<span data-i18n="ad_eyebrow">' + ((STR && STR.ad_eyebrow) || "") + '</span><span class="x">ascdc.sinica.edu.tw ↗</span>';
263    rail.appendChild(track); rail.appendChild(eb); rail.appendChild(nav); rail.appendChild(prog); rail.appendChild(dots); brand.appendChild(rail);
264    var slides = track.querySelectorAll(".sc-slide"), ds = dots.querySelectorAll(".sc-dot"), idx = 0, timer = null;
265
266    function pickTr(it) { var t = it.tr || {}; return t[LANG] || t[META.fallback || "en"] || t["en"] || t["zh-Hant"] || {}; }
267    function catText(it) { var m = { museum: "ad_cat_museum", curation: "ad_cat_curation", database: "ad_cat_database", platform: "ad_cat_platform", exhibition: "ad_cat_exhibition" }; var k = m[it.catKey] || "ad_cat_database"; return (STR && STR[k]) || it.catKey || ""; }
268    function updateAdsLang() {
269      for (var i = 0; i < slides.length; i++) {
270        var it = data.items[+slides[i].dataset.idx]; if (!it) continue;
271        var tr = pickTr(it), s = slides[i];
272        var c = s.querySelector(".sc-cat"); if (c) c.textContent = catText(it);
273        var t = s.querySelector(".sc-title"); if (t) t.textContent = tr.title || "";
274        var u = s.querySelector(".sc-sub"); if (u) u.textContent = tr.sub || "";
275        var k = s.querySelector(".sc-teaser"); if (k) k.textContent = tr.teaser || "";
276      }
277    }
278    updateAdsLang(); adsUpdate = updateAdsLang;
279
280    var ratios = [];
281    function lazyLoad(i) {
282      slides[i].querySelectorAll("[data-bg]").forEach(function (el) { el.style.backgroundImage = "url('" + el.getAttribute("data-bg") + "')"; el.removeAttribute("data-bg"); });
283      if (ratios[i] == null) { ratios[i] = 0; var im = new Image(); im.onload = function () { ratios[i] = im.naturalHeight ? im.naturalWi
283dth / im.naturalHeight : 1; applyMode(i); }; im.src = picked[i].img; }
284    }
285    function panelRatio() { var r = rail.getBoundingClientRect(); return r.height ? r.width / r.height : 1; }
286    function applyMode(i) {
287      var rt = ratios[i]; if (!rt) return;
288      var pr = panelRatio(), o = rt > pr ? rt / pr : pr / rt;
289      var m = o <= 1.5 ? "m-cover" : (rt > pr ? "m-panx" : "m-pany");
290      var ph = slides[i].querySelector(".sc-photo");
291      if (ph && !ph.classList.contains(m)) { ph.classList.remove("m-cover", "m-panx", "m-pany"); ph.classList.add(m); }
292    }
293    function layoutAds() { for (var i = 0; i < slides.length; i++) applyMode(i); }
294    var raf; window.addEventListener("resize", function () { if (raf) cancelAnimationFrame(raf); raf = requestAnimationFrame(layoutAds); });
295
296    var reduced = window.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches;
297    function restartProgress() {
298      if (reduced) return;
299      pbar.style.animation = "none"; void pbar.offsetWidth; pbar.style.animation = "sc-progress 5s linear forwards";
300    }
301    function go(n) {
302      idx = (n + slides.length) % slides.length; lazyLoad(idx); lazyLoad((idx + 1) % slides.length);
303      slides.forEach(function (s, i) { s.classList.toggle("active", i === idx); });
304      ds.forEach(function (d, i) { d.classList.toggle("on", i === idx); });
305      restartProgress();
306    }
307    function start() { stop(); if (!reduced) { timer = setInterval(function () { go(idx + 1); }, 5000); restartProgress(); } }
308    function stop() { if (timer) { clearInterval(timer); timer = null; } }
309    ds.forEach(function (d, i) { d.addEventListener("click", function (e) { e.preventDefault(); go(i); start(); }); });
310    prev.addEventListener("click", function (e) { e.preventDefault(); go(idx - 1); start(); });
311    next.addEventListener("click", function (e) { e.preventDefault(); go(idx + 1); start(); });
312    var sx = 0, sy = 0, swiping = false, swiped = false;
313    rail.addEventListener("pointerdown", function (e) { sx = e.clientX; sy = e.clientY; swiping = true; swiped = false; });
314    rail.addEventListener("pointermove", function (e) { if (swiping && Math.abs(e.clientX - sx) > 10 && Math.abs(e.clientX - sx) > Math.abs(e.clientY - sy)) swiped = true; });
315    rail.addEventListener("pointerup", function (e) { if (!swiping) return; swiping = false; var dx = e.clientX - sx, dy = e.clientY - sy; if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy)) { go(dx < 0 ? idx + 1 : idx - 1); start(); } });
316    rail.addEventListener("pointercancel", function () { swiping = false; });
317    rail.addEventListener("click", function (e) { if (swiped) { e.preventDefault(); e.stopPropagation(); swiped = false; } }, true);
318    rail.addEventListener("mouseenter", stop); rail.addEventListener("mouseleave", start);
319    rail.addEventListener("focusin", stop); rail.addEventListener("focusout", start);
320    lazyLoad(0); if (!reduced) start();
321  }
322
323  function boot() {
324    initTheme(); initForm(); initBg(); initLayout(); initSplitter();
325    loadI18N().then(function (j) {
326      if (!j) { console.error("i18n load failed; form still usable"); return; }
327      I18N = j; META = j._meta; LANG = pickLang();
328      applyLang(LANG); buildLang(); initFonts(); initAds();
329    }).catch(function (e) { console.error("i18n error; form still usable", e); });
330  }
331  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot); else boot();
332})();

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.