1import "./chunk.T7SNOZFH.js"; 2import "./chunk.V2LBPZHK.js"; 3
4// src/js/parts/animate.js 5var HeroAnimate = class { 6 constructor(el) { 7 this.el = el; 8 this.canvas = document.createElement("canvas"); 9 this.canvas.style.cssText = "display:block;width:100%;height:100%"; 10 this.el.appendChild(this.canvas); 11 this.ctx = this.canvas.getContext("2d"); 12 this.animId = null; 13 this.w = 0; 14 this.h = 0; 15 this.PARTICLE_COUNT = 60; 16 this.CONNECT_DIST = 150; 17 this.DROP_COUNT = 30; 18 this.particles = []; 19 this.drops = []; 20 this.hexNodes = []; 21 this.scanY = 0; 22 this.featurePhrases = [ 23 "Terminal", 24 "SSH", 25 "SFTP", 26 "Telnet", 27 "Serialport", 28 "RDP", 29 "VNC", 30 "Spice", 31 "FTP", 32 "Multi-platform", 33 "Multi-language", 34 "AI assistant", 35 "MCP", 36 "Deep link", 37 "Zmodem", 38 "Trzsz", 39 "SSH tunnel", 40 "Quick commands", 41 "Themes", 42 "Sync bookmarks", 43 "Proxy", 44 "Transparent window", 45 "\u7EC8\u7AEF", 46 "\u6587\u4EF6\u7BA1\u7406", 47 "\u591A\u5E73\u53F0", 48 "\u591A\u8BED\u8A00", 49 "\u5FEB\u6377\u952E", 50 "\u8FDC\u7A0B\u7F16\u8F91", 51 "\u5BC6\u7801\u767B\u5F55", 52 "\u5BC6\u5319\u767B\u5F55", 53 "\u96A7\u9053", 54 "\u4EE3\u7406\u670D\u52A1\u5668", 55 "\u5FEB\u6377\u547D\u4EE4", 56 "\u4E3B\u9898", 57 "\u540C\u6B65\u6570\u636E", 58 "\u5FEB\u901F\u8F93\u5165", 59 "AI\u52A9\u624B", 60 "\u6DF1\u5EA6\u94FE\u63A5", 61 "\u900F\u660E\u7A97\u53E3", 62 "\u80CC\u666F\u56FE\u7247", 63 "\u6587\u4EF6\u4F20\u8F93" 64 ]; 65 this._resize = this.resize.bind(this); 66 this.init(); 67 } 68 init() { 69 this.resize(); 70 this.animate(); 71 window.addEventListener("resize", this._resize); 72 } 73 resize() { 74 const dpr = window.devicePixelRatio || 1; 75 this.w = this.el.offsetWidth; 76 this.h = this.el.offsetHeight; 77 this.canvas.width = this.w * dpr; 78 this.canvas.height = this.h * dpr; 79 this.canvas.style.width = this.w + "px"; 80 this.canvas.style.height = this.h + "px"; 81 this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 82 this.initParticles(); 83 this.initDrops(); 84 this.initHexNodes(); 85 } 86 initParticles() { 87 this.particles = []; 88 for (let i = 0; i < this.PARTICLE_COUNT; i++) { 89 this.particles.push({ 90 x: Math.random() * this.w, 91 y: Math.random() * this.h, 92 vx: (Math.random() - 0.5) * 0.6, 93 vy: (Math.random() - 0.5) * 0.6, 94 r: Math.random() * 2 + 1, 95 opacity: Math.random() * 0.5 + 0.2 96 }); 97 } 98 } 99 initDrops() { 100 this.drops = []; 101 for (let i = 0; i < this.DROP_COUNT; i++) { 102 this.drops.push({ 103 x: Math.random() * this.w, 104 y: Math.random() * this.h * -1 - Math.random() * 300, 105 speed: Math.random() * 0.8 + 0.3, 106 phrase: this.featurePhrases[Math.floor(Math.random() * this.featurePhrases.length)], 107 opacity: Math.random() * 0.1 + 0.04, 108 fontSize: Math.random() * 4 + 11 109 }); 110 } 111 } 112 initHexNodes() { 113 this.hexNodes = []; 114 for (let i = 0; i < 8; i++) { 115 this.hexNodes.push({ 116 x: Math.random() * this.w, 117 y: Math.random() * this.h, 118 vx: (Math.random() - 0.5) * 0.3, 119 vy: (Math.random() - 0.5) * 0.3, 120 size: Math.random() * 12 + 8, 121 rotation: Math.random() * Math.PI * 2, 122 rotSpeed: (Math.random() - 0.5) * 0.01, 123 opacity: Math.random() * 0.08 + 0.03 124 }); 125 } 126 } 127 drawGrid() { 128 const ctx = this.ctx; 129 const gridSize = 60; 130 ctx.strokeStyle = "rgba(37, 99, 235, 0.04)"; 131 ctx.lineWidth = 0.5; 132 for (let x = 0; x < this.w; x += gridSize) { 133 ctx.beginPath(); 134 ctx.moveTo(x, 0); 135 ctx.lineTo(x, this.h); 136 ctx.stroke(); 137 } 138 for (let y = 0; y < this.h; y += gridSize) { 139 ctx.beginPath(); 140 ctx.moveTo(0, y); 141 ctx.lineTo(this.w, y); 142 ctx.stroke(); 143 } 144 } 145 drawParticles() { 146 const ctx = this.ctx; 147 for (const p of this.particles) { 148 p.x += p.vx; 149 p.y += p.vy; 150 if (p.x < 0 || p.x > this.w) p.vx *= -1; 151 if (p.y < 0 || p.y > this.h) p.vy *= -1; 152 ctx.beginPath(); 153 ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); 154 ctx.fillStyle = "rgba(37, 99, 235, " + p.opacity + ")"; 155 ctx.fill(); 156 } 157 for (let i = 0; i < this.particles.length; i++) { 158 for (let j = i + 1; j < this.particles.length; j++) { 159 const dx = this.particles[i].x - this.particles[j].x; 160 const dy = this.particles[i].y - this.particles[j].y; 161 const dist = Math.sqrt(dx * dx + dy * dy); 162 if (dist < this.CONNECT_DIST) {
163 const alpha = (1 - dist / this.CONNECT_DIST) * 0.15; 164 ctx.beginPath(); 165 ctx.moveTo(this.particles[i].x, this.particles[i].y); 166 ctx.lineTo(this.particles[j].x, this.particles[j].y); 167 ctx.strokeStyle = "rgba(37, 99, 235, " + alpha + ")"; 168 ctx.lineWidth = 0.8; 169 ctx.stroke(); 170 } 171 } 172 } 173 } 174 drawMatrixRain() { 175 const ctx = this.ctx; 176 ctx.textBaseline = "top"; 177 for (const drop of this.drops) { 178 ctx.font = "500 " + drop.fontSize + 'px "SF Mono", "Fira Code", monospace'; 179 ctx.fillStyle = "rgba(37, 99, 235, " + drop.opacity + ")"; 180 ctx.fillText(drop.phrase, drop.x, drop.y); 181 drop.y += drop.speed; 182 if (drop.y > this.h + 20) { 183 drop.y = Math.random() * -300 - 50; 184 drop.x = Math.random() * this.w; 185 drop.phrase = this.featurePhrases[Math.floor(Math.random() * this.featurePhrases.length)]; 186 } 187 } 188 } 189 drawHex(x, y, size, rotation, opacity) { 190 const ctx = this.ctx; 191 ctx.beginPath(); 192 for (let i = 0; i < 6; i++) { 193 const angle = rotation + Math.PI / 3 * i; 194 const px = x + size * Math.cos(angle); 195 const py = y + size * Math.sin(angle); 196 if (i === 0) ctx.moveTo(px, py); 197 else ctx.lineTo(px, py); 198 } 199 ctx.closePath(); 200 ctx.strokeStyle = "rgba(37, 99, 235, " + opacity + ")"; 201 ctx.lineWidth = 1; 202 ctx.stroke(); 203 } 204 drawHexNodes() { 205 for (const node of this.hexNodes) { 206 node.x += node.vx; 207 node.y += node.vy; 208 node.rotation += node.rotSpeed; 209 if (node.x < -50 || node.x > this.w + 50) node.vx *= -1; 210 if (node.y < -50 || node.y > this.h + 50) node.vy *= -1; 211 this.drawHex(node.x, node.y, node.size, node.rotation, node.opacity); 212 } 213 } 214 drawScanLine() { 215 this.scanY += 0.5; 216 if (this.scanY > this.h) this.scanY = 0; 217 const grad = this.ctx.createLinearGradient(0, this.scanY - 30, 0, this.scanY + 30); 218 grad.addColorStop(0, "rgba(37, 99, 235, 0)"); 219 grad.addColorStop(0.5, "rgba(37, 99, 235, 0.03)"); 220 grad.addColorStop(1, "rgba(37, 99, 235, 0)"); 221 this.ctx.fillStyle = grad; 222 this.ctx.fillRect(0, this.scanY - 30, this.w, 60); 223 } 224 animate() { 225 this.ctx.clearRect(0, 0, this.w, this.h); 226 this.drawGrid(); 227 this.drawScanLine(); 228 this.drawMatrixRain(); 229 this.drawHexNodes(); 230 this.drawParticles(); 231 this.animId = requestAnimationFrame(() => this.animate()); 232 } 233 destroy() { 234 cancelAnimationFrame(this.animId); 235 window.removeEventListener("resize", this._resize); 236 this.canvas.remove(); 237 } 238}; 239
240// src/js/parts/os-highlight.js 241var KEYWORDS = [ 242 "Windows 7", 243 "macOS 10+", 244 "PowerPC 64-bit little-endian", 245 "PowerPC 64-Bit-Little-Endian", 246 "old-world", 247 "new-world", 248 "LoongArch", 249 "riscv64", 250 "RISC-V", 251 "RISC V", 252 "ppc64le", 253 "PowerPC", 254 "HarmonyOS", 255 "Windows", 256 "macOS", 257 "Android", 258 "Ubuntu 18", 259 "Ubuntu", 260 "Kylin", 261 "UOS", 262 "Linux", 263 "iOS", 264 // CJK tokens (zh-cn / zh-tw) 265 "\u94F6\u6CB3\u9E92\u9E9F", 266 "\u7EDF\u4FE1", 267 "\u9F99\u82AF", 268 "\u9E92\u9E9F", 269 "\u65B0\u65E7\u4E16\u754C" 270]; 271var EMOJI = [ 272 { re: /windows 7/i, emoji: "\u{1FA9F}" }, 273 { re: /windows/i, emoji: "\u{1FA9F}" }, 274 { re: /macos/i, emoji: "\u{1F34E}" }, 275 { re: /^mac$/i, emoji: "\u{1F34E}" }, 276 { re: /linux|ubuntu|uos|kylin|éºéº|ç»ä¿¡/i, emoji: "\u{1F427}" }, 277 { re: /android/i, emoji: "\u{1F916}" }, 278 { re: /harmony/i, emoji: "\u{1F338}" }, 279 { re: /ios/i, emoji: "\u{1F4F1}" }, 280 { re: /loongarch|é¾è¯|old-world|new-world|æ°æ§ä¸ç/i, emoji: "\u{1F409}" }, 281 { re: /risc/i, emoji: "\u{1F9EC}" }, 282 { re: /ppc|powerpc/i, emoji: "\u2699\uFE0F" } 283]; 284function emojiFor(text) { 285 for (const m of EMOJI) { 286 if (m.re.test(text)) return m.emoji; 287 } 288 return "\u{1F4BB}"; 289} 290function tabFor(text) { 291 const t = text.toLowerCase(); 292 if (t.includes("windows")) return "windows"; 293 if (t.includes("macos") || t === "mac" || t.includes("mac os")) return "mac"; 294 if (t.includes("android")) return "android"; 295 if (t.includes("harmony")) return "harmony"; 296 if (t.includes("ios")) return "ios"; 297 return "linux"; 298} 299function escapeRe(s) { 300 return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); 301} 302var PATTERN = KEYWORDS.slice().sort((a, b) => b.length - a.length).map(escapeRe).join("|"); 303var RE = new RegExp("(" + PATTERN + ")", "gi"); 304function escapeHtml(s) { 305 return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); 306} 307function jumpToDownload(tab) { 308 const btn = document.querySelector('.download-tab-btn[data-tab="' + tab + '"]'); 309 if (btn) btn.click(); 310 const anchor = document.getElementById("downloads"); 311 if (anchor) { 312 anchor.scrollIntoView({ behavior: "smooth", block: "start" }); 313 } else { 314 window.location.hash = "#downloads"; 315 } 316} 317function setActive(chip, on) { 318 if (on) { 319 const orig = chip.dataset.orig || chip.textContent; 320 if (!chip.querySelector(".os-emoji")) { 321 const em = document.createElement("span"); 322 em.className = "os-emoji"; 323 em.setAttribute("aria-hidden", "true"); 324 em.textContent = emojiFor(orig); 325 chip.insertBefore(em, chip.firstChild); 326 } 327 if (!chip.title) chip.title = "See " + orig + " downloads \u2193"; 328 chip.classList.remove("os-pop"); 329 chip.getBoundingClientRect(); 330 chip.classList.add("os-active", "os-pop"); 331 } else { 332 chip.classList.remove("os-active", "os-pop"); 333 chip.removeAttribute("title"); 334 const em = chip.querySelector(".os-emoji"); 335 if (em) em.remove(); 336 } 337} 338function initOsHighlight() { 339 const el = document.querySelector(".hero-supports"); 340 if (!el || el.dataset.osAnimated) return; 341 const raw = el.textContent; 342 if (!raw || !RE.test(raw)) return; 343 RE.lastIndex = 0; 344 el.dataset.osAnimated = "1"; 345 el.innerHTML = escapeHtml(raw).replace(RE, function(m) { 346 const tab = tabFor(m); 347 return '<span class="os-chip" data-tab="' + tab + '" data-orig="' + escapeHtml(m) + '">' + escapeHtml(m) + "</span>"; 348 }); 349 const chips = Array.from(el.querySelectorAll(".os-chip")); 350 if (chips.length === 0) return; 351 chips.forEach(function(chip) { 352 chip.addEventListener("click", function() { 353 if (!chip.classList.contains("os-active")) return; 354 jumpToDownload(chip.dataset.tab || "linux"); 355 }); 356 }); 357 const reduceMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; 358 if (reduceMotion) { 359 return; 360 } 361 let current = -1; 362 let timer = null; 363 let hover = false; 364 let inView = true; 365 function pickNext() { 366 if (chips.length === 1) return 0; 367 let next = Math.floor(Math.random() * chips.length); 368 if (next === current) next = (next + 1 + Math.floor(Math.random() * (chips.length - 1))) % chips.length; 369 return next; 370 } 371 function spotlight() { 372 if (current >= 0 && chips[current]) setActive(chips[current], false); 373 current = pickNext(); 374 const chip = chips[current]; 375 if (chip) setActive(chip, true); 376 } 377 function schedule() { 378 clearTimeout(timer); 379 timer = setTimeout(function() { 380 if (!hover && inView && !document.hidden) spotlight(); 381 schedule(); 382 }, 900 + Math.random() * 1e3); 383 } 384 el.addEventListener("mouseenter", function() { 385 hover = true; 386 }); 387 el.addEventListener("mouseleave", function() { 388 hover = false;
389 }); 390 if (window.IntersectionObserver) { 391 const io = new IntersectionObserver(function(entries) { 392 inView = entries[0].isIntersecting; 393 }, { threshold: 0.1 }); 394 io.observe(el); 395 } 396 setTimeout(spotlight, 600); 397 schedule(); 398} 399if (document.readyState === "loading") { 400 document.addEventListener("DOMContentLoaded", initOsHighlight); 401} else { 402 initOsHighlight(); 403} 404
405// src/js/parts/hero.js 406var heroEl = document.getElementById("hero-anim"); 407if (heroEl) { 408 try { 409 heroEl.__heroAnimate = new HeroAnimate(heroEl); 410 } catch (e) { 411 console.warn("[hero] animate init failed", e); 412 } 413} 414var logo = document.querySelector(".hero-logo"); 415if (logo) { 416 import("./chunk.C7LACAKJ.js").then(function(m) { 417 const { default: Hero3DLogo } = m; 418 try { 419 logo.__hero3DLogo = new Hero3DLogo(logo, { modelUrl: "/electerm.glb" }); 420 } catch (e) { 421 console.warn("[hero-3d-logo] init failed", e); 422 } 423 }).catch(function(err) { 424 console.warn("[hero-3d-logo] init failed, keeping PNG logo", err); 425 }); 426} 427
428// src/js/parts/download.js 429function detectOS() { 430 const p = navigator.platform.toLowerCase(); 431 const u = navigator.userAgent.toLowerCase(); 432 if (p.includes("mac") || u.includes("mac")) return "mac"; 433 if (p.includes("win") || u.includes("windows")) return "windows"; 434 if (p.includes("linux") || u.includes("linux")) return "linux"; 435 if (p.includes("android") || u.includes("android")) return "android"; 436 return "windows"; 437} 438function switchTab(os) { 439 document.querySelectorAll(".download-tab-btn").forEach(function(b) { 440 b.classList.toggle("active", b.getAttribute("data-tab") === os); 441 }); 442 document.querySelectorAll(".download-panel").forEach(function(p) { 443 p.classList.toggle("active", p.getAttribute("data-tab") === os); 444 }); 445} 446function switchArch(btn, arch) { 447 const container = btn.closest(".arch-bar").parentElement; 448 container.querySelectorAll(".arch-btn").forEach(function(b) { 449 b.classList.toggle("active", b.getAttribute("data-arch") === arch); 450 }); 451 container.querySelectorAll(".arch-panel").forEach(function(p) { 452 p.classList.toggle("active", p.getAttribute("data-arch") === arch); 453 }); 454} 455function isAndroidDevice() { 456 const u = navigator.userAgent.toLowerCase(); 457 const p = (navigator.platform || "").toLowerCase(); 458 return p.includes("android") || u.includes("android"); 459} 460var qrLibLoaded = false; 461function loadQrLib(cb) { 462 if (qrLibLoaded) { 463 cb(); 464 return; 465 } 466 if (window.qrcode) { 467 qrLibLoaded = true; 468 cb(); 469 return; 470 } 471 const s = document.createElement("script"); 472 s.src = "/js/qrcode-generator.min.js"; 473 s.onload = function() { 474 qrLibLoaded = true; 475 cb(); 476 }; 477 s.onerror = function() { 478 cb(new Error("Failed to load QR library")); 479 }; 480 document.head.appendChild(s); 481} 482function showQrModal(url, source) { 483 const modal = document.getElementById("androidQrModal"); 484 const codeEl = document.getElementById("androidQrCode"); 485 const urlEl = document.getElementById("androidQrUrl"); 486 const dlEl = document.getElementById("androidQrDownload"); 487 const srcEl = document.getElementById("androidQrSource"); 488 if (!modal) return; 489 urlEl.textContent = url; 490 dlEl.href = url; 491 if (srcEl && source) srcEl.textContent = source; 492 codeEl.innerHTML = '<div class="android-qr-loading">Generating QR code\u2026</div>'; 493 modal.classList.add("active"); 494 modal.setAttribute("aria-hidden", "false"); 495 loadQrLib(function(err) { 496 if (err) { 497 codeEl.innerHTML = '<div class="android-qr-error">Failed to generate QR code</div>'; 498 return; 499 } 500 try { 501 const qr = window.qrcode(0, "M"); 502 qr.addData(url); 503 qr.make(); 504 codeEl.innerHTML = qr.createSvgTag({ 505 cellSize: 4, 506 margin: 4, 507 scalable: true, 508 alt: "QR code for " + url 509 }); 510 } catch (e) { 511 codeEl.innerHTML = '<div class="android-qr-error">Failed to generate QR code</div>'; 512 } 513 }); 514} 515function hideQrModal() { 516 const modal = document.getElementById("androidQrModal"); 517 if (modal) { 518 modal.classList.remove("active"); 519 modal.setAttribute("aria-hidden", "true"); 520 } 521} 522function copyAltCmd(btn) { 523 const item = btn.closest(".alt-install-item"); 524 const code = item && item.querySelector(".alt-install-code code"); 525 if (!code) return; 526 const text = code.textContent; 527 const done = function() { 528 const old = btn.textContent; 529 btn.textContent = btn.dataset.copied; 530 setTimeout(function() { 531 btn.textContent = old; 532 }, 1500); 533 }; 534 if (navigator.clipboard && navigator.clipboard.writeText) { 535 navigator.clipboard.writeText(text).then(done, function() { 536 fallbackCopy(text); 537 done(); 538 }); 539 } else { 540 fallbackCopy(text); 541 done(); 542 } 543} 544function fallbackCopy(text) { 545 const ta = document.createElement("textarea"); 546 ta.value = text; 547 ta.style.position = "fixed"; 548 ta.style.opacity = "0"; 549 document.body.appendChild(ta); 550 ta.select(); 551 try { 552 document.execCommand("copy"); 553 } catch (e) { 554 } 555 document.body.removeChild(ta); 556} 557function firstPresentTab() { 558 const b = document.querySelector(".download-tab-btn"); 559 return b ? b.getAttribute("data-tab") : "windows"; 560} 561document.addEventListener("DOMContentLoaded", function() { 562 const detected = detectOS(); 563 const detectedPanel = document.querySelector('.download-panel[data-tab="' + detected + '"]'); 564 switchTab(detectedPanel ? detected : firstPresentTab()); 565 document.querySelectorAll(".download-tab-btn").forEach(function(b) { 566 b.addEventListener("click", function() { 567 switchTab(this.getAttribute("data-tab")); 568 }); 569 }); 570 document.querySelectorAll(".arch-bar").forEach(function(bar) { 571 bar.querySelectorAll(".arch-btn").forEach(function(b) { 572 b.addEventListener("click", function() { 573 switchArch(this, this.getAttribute("data-arch")); 574 }); 575 }); 576 }); 577 document.querySelectorAll(".alt-copy").forEach(function(b) { 578 b.addEventListener("click", function() { 579 copyAltCmd(this); 580 }); 581 }); 582 if (!isAndroidDevice()) { 583 const qrModal = document.getElementById("androidQrModal"); 584 if (qrModal) { 585 const androidPanel = document.querySelector('.download-panel[data-tab="android"]'); 586 if (androidPanel) { 587 androidPanel.querySelectorAll(".download-card-link").forEach(function(link) { 588 link.addEventListener("click", function(e) { 589 e.preventDefault(); 590 showQrModal(this.href, this.textContent.trim()); 591 }); 592 }); 593 } 594 const closeBtn = document.getElementById("androidQrClose"); 595 if (closeBtn) closeBtn.addEventListener("click", hideQrModal);
596 const overlay = document.querySelector(".android-qr-overlay"); 597 if (overlay) overlay.addEventListener("click", hideQrModal); 598 document.addEventListener("keydown", function(e) { 599 if (e.key === "Escape") hideQrModal(); 600 }); 601 } 602 } 603}); 604
605// src/js/parts/demo.js 606function switchMode(mode) { 607 const wrapper = document.querySelector(".demo-wrapper"); 608 if (!wrapper) return; 609 wrapper.classList.toggle("mobile", mode === "mobile"); 610 document.querySelectorAll(".demo-switch-btn").forEach(function(b) { 611 const active = b.getAttribute("data-mode") === mode; 612 b.classList.toggle("active", active); 613 b.setAttribute("aria-selected", active ? "true" : "false"); 614 }); 615} 616document.addEventListener("DOMContentLoaded", function() { 617 document.querySelectorAll(".demo-switch-btn").forEach(function(b) { 618 b.addEventListener("click", function() { 619 switchMode(this.getAttribute("data-mode")); 620 }); 621 }); 622});
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.