(function () {
"use strict";
var RELEASE_API = "https://api.github.com/repos/T-1234567890/terminal-info/releases/latest";
var VERSION_CACHE_KEY = "tinfo-version";
var VERSION_CACHE_TTL = 10 * 60 * 1000;
var LANGUAGE_KEY = "tinfo-language";
var currentLanguage = "en";
function normalizeVersion(tag) {
if (!tag || typeof tag !== "string") return null;
var value = tag.trim();
return value || null;
}
function readVersionCache() {
try {
var raw = window.localStorage.getItem(VERSION_CACHE_KEY);
if (!raw) return null;
var parsed = JSON.parse(raw);
if (!parsed || typeof parsed.version !== "string" || typeof parsed.cachedAt !== "number") {
return null;
}
if (Date.now() - parsed.cachedAt > VERSION_CACHE_TTL) {
return null;
}
return parsed;
} catch (_err) {
return null;
}
}
function writeVersionCache(version, releaseUrl) {
try {
window.localStorage.setItem(
VERSION_CACHE_KEY,
JSON.stringify({
version: version,
releaseUrl: releaseUrl || null,
cachedAt: Date.now()
})
);
} catch (_err) {
}
}
function updateVersionDisplays(version, releaseUrl) {
if (!version) return;
document.querySelectorAll("[data-stable-version]").forEach(function (node) {
node.textContent = version;
if (releaseUrl && node.tagName === "A") {
node.setAttribute("href", releaseUrl);
}
});
}
function getLocalizedText(node, lang) {
if (!node || !node.dataset) return null;
if (lang === "zh" && node.dataset.zh) return node.dataset.zh;
if (node.dataset.en) return node.dataset.en;
return null;
}
function updateLanguageButtons(lang) {
document.querySelectorAll("[data-lang-btn]").forEach(function (btn) {
btn.classList.toggle("active", btn.getAttribute("data-lang-btn") === lang);
btn.setAttribute("aria-pressed", btn.getAttribute("data-lang-btn") === lang ? "true" : "false");
});
}
function applyLanguage(lang) {
currentLanguage = lang === "zh" ? "zh" : "en";
document.documentElement.lang = currentLanguage === "zh" ? "zh-CN" : "en";
document.querySelectorAll("[data-en][data-zh]").forEach(function (node) {
var next = getLocalizedText(node, currentLanguage);
if (typeof next === "string") {
node.textContent = next;
}
});
document.querySelectorAll("[data-lang-content]").forEach(function (node) {
node.hidden = node.getAttribute("data-lang-content") !== currentLanguage;
});
var heroCopyBtn = document.getElementById("hero-copy-btn");
if (heroCopyBtn) {
heroCopyBtn.title = currentLanguage === "zh" ? "复制安装命令" : "Copy install command";
}
updateLanguageButtons(currentLanguage);
if (document.getElementById("demo-info")) {
var activeTab = document.querySelector(".demo-tab.active");
if (activeTab) {
renderInfo(activeTab.getAttribute("data-demo"));
}
}
try {
window.localStorage.setItem(LANGUAGE_KEY, currentLanguage);
} catch (_err) {
}
}
function initLanguageToggle() {
var saved = null;
try {
saved = window.localStorage.getItem(LANGUAGE_KEY);
} catch (_err) {
saved = null;
}
applyLanguage(saved === "zh" ? "zh" : "en");
document.querySelectorAll("[data-lang-btn]").forEach(function (btn) {
btn.addEventListener("click", function () {
applyLanguage(btn.getAttribute("data-lang-btn"));
});
});
}
function initSectionReveal() {
var nodes = document.querySelectorAll(".section, .stats-strip, .site-footer, [data-reveal]");
if (!nodes.length) return;
if (!("IntersectionObserver" in window)) {
nodes.forEach(function (node) {
node.classList.add("is-visible");
});
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.12,
rootMargin: "0px 0px -24px 0px"
});
nodes.forEach(function (node) {
observer.observe(node);
});
}
function animateValue(el, nextText) {
if (!el || el.textContent === nextText) return;
el.style.opacity = "0.35";
el.style.transform = "translateY(5px)";
window.setTimeout(function () {
el.textContent = nextText;
el.style.opacity = "1";
el.style.transform = "translateY(0)";
}, 220);
}
function pad(value) {
return String(value).padStart(2, "0");
}
function padEnd(value, width) {
var text = String(value);
if (text.length >= width) return text.slice(0, width);
return text + new Array(width - text.length + 1).join(" ");
}
function formatBoxRows(title, rows, options) {
var opts = options || {};
var width = opts.width || 34;
var labelWidth = opts.labelWidth || 10;
var innerWidth = width;
function border(left, fill, right) {
return left + new Array(innerWidth + 1).join(fill) + right;
}
function contentLine(text) {
return "│" + padEnd(text, innerWidth) + "│";
}
var centeredTitle = title;
var leftPad = Math.max(0, Math.floor((innerWidth - title.length) / 2));
centeredTitle = new Array(leftPad + 1).join(" ") + title;
centeredTitle = padEnd(centeredTitle, innerWidth);
var lines = [
border("┌", "─", "┐"),
contentLine(centeredTitle),
border("├", "─", "┤")
];
rows.forEach(function (row) {
var label = padEnd(row.label + ":", labelWidth);
var text = " " + label + " " + row.value;
lines.push(contentLine(text));
});
lines.push(border("└", "─", "┘"));
return lines;
}
function renderBoxLine(line) {
return '<div class="term-line"><span class="term-box">' + escHtml(line) + "</span></div>";
}
function heroRowsFromState(state) {
return [
{ label: "Location", value: "Tokyo" },
{ label: "Weather", value: "Clear sky, 20.3°C" },
{ label: "Time", value: state.time },
{ label: "Network", value: "143.xxx.x.xx" },
{ label: "CPU", value: state.cpu },
{ label: "Memory", value: state.memory + " used" },
{ label: "Timers", value: state.timer },
{ label: "Reminders", value: state.reminder }
];
}
function renderHeroTerminal(state) {
var body = document.getElementById("hero-term-body");
if (!body) return;
var lines = formatBoxRows("Terminal Info", heroRowsFromState(state), {
width: 44,
labelWidth: 10
});
var html = '<div class="term-line"><span class="term-ps">$</span><span class="term-cmd">tinfo</span></div>';
lines.forEach(function (line) {
html += renderBoxLine(line);
});
html += '<div class="term-line" style="height:0.6rem"></div>';
html += '<div class="term-line"><span class="term-out">Press q or Ctrl+C to exit.</span></div>';
body.innerHTML = html;
}
function initHeroTerminalUpdates() {
var body = document.getElementById("hero-term-body");
if (!body) return;
var cpuSeries = ["28.4%", "31.2%", "35.6%", "42.1%", "51.8%"];
var memorySeries = [
"16.2 GiB / 24.0 GiB",
"16.3 GiB / 24.0 GiB",
"16.1 GiB / 24.0 GiB",
"16.2 GiB / 24.0 GiB"
];
var cpuIndex = 0;
var memoryIndex = 0;
var reminderTarget = Date.now() + 12 * 60 * 1000;
var timerRemaining = 24 * 60 + 52;
var state = {
time: "2026-03-28 20:45:44",
cpu: cpuSeries[cpuIndex],
memory: memorySeries[memoryIndex],
timer: formatCountdown(timerRemaining),
reminder: formatReminder(reminderTarget)
};
renderHeroTerminal(state);
window.setInterval(function () {
var now = new Date();
state.time = [
now.getFullYear(),
"-",
pad(now.getMonth() + 1),
"-",
pad(now.getDate()),
" ",
pad(now.getHours()),
":",
pad(now.getMinutes()),
":",
pad(now.getSeconds())
].join("");
if (timerRemaining > 0) {
timerRemaining -= 1;
}
state.timer = formatCountdown(timerRemaining);
state.reminder = formatReminder(reminderTarget);
renderHeroTerminal(state);
}, 1000);
window.setInterval(function () {
cpuIndex = (cpuIndex + 1) % cpuSeries.length;
state.cpu = cpuSeries[cpuIndex];
renderHeroTerminal(state);
}, 4200);
window.setInterval(function () {
memoryIndex = (memoryIndex + 1) % memorySeries.length;
state.memory = memorySeries[memoryIndex];
renderHeroTerminal(state);
}, 6200);
}
async function loadStableVersion() {
var cached = readVersionCache();
if (cached) {
updateVersionDisplays(cached.version, cached.releaseUrl);
}
if (!window.fetch) {
return cached ? cached.version : null;
}
try {
var response = await window.fetch(RELEASE_API, {
headers: {
Accept: "application/vnd.github+json"
}
});
if (!response.ok) {
throw new Error("GitHub release lookup failed");
}
var payload = await response.json();
var version = normalizeVersion(payload && payload.tag_name);
var releaseUrl = payload && payload.html_url ? String(payload.html_url) : null;
if (!version) {
throw new Error("Latest release tag missing");
}
updateVersionDisplays(version, releaseUrl);
writeVersionCache(version, releaseUrl);
return version;
} catch (_err) {
if (cached) {
updateVersionDisplays(cached.version, cached.releaseUrl);
return cached.version;
}
return null;
}
}
var demos = {
dashboard: {
title: "Dashboard",
command: "tinfo",
info: {
heading: "Instant system overview",
headingZh: "即时系统总览",
bullets: [
"Shows location, weather, time, network, CPU, and memory in one view",
"Timers and reminders appear directly in the main summary",
"Useful as a quick status check when you open the tool",
],
bulletsZh: [
"在一个视图中显示位置、天气、时间、网络、CPU 和内存",
"计时器和提醒会直接出现在主摘要里",
"适合在打开工具时快速查看当前状态"
]
},
lines: [
{
type: "box",
title: "Terminal Info",
width: 44,
labelWidth: 10,
rows: [
{ label: "Location", value: "Tokyo" },
{ label: "Weather", value: "Clear sky, 20.3°C" },
{ label: "Time", value: "__DYNAMIC_TIME__" },
{ label: "Network", value: "143.xxx.x.xx" },
{ label: "CPU", value: "19.3%" },
{ label: "Memory", value: "16.2 GiB / 24.0 GiB used" },
{ label: "Timers", value: "Timer: 00:24:52 remaining" },
{ label: "Reminders", value: "⏳ break in 12 min" }
]
},
{ type: "blank" },
{ type: "out", cls: "term-output", text: "Press q or Ctrl+C to exit." }
],
},
chat: {
title: "AI Chat",
liveSession: true,
info: {
heading: "Simple terminal AI chat",
headingZh: "简单的终端 AI 聊天",
bullets: [
"Interactive AI chat in the terminal",
"Shows basic markdown rendering in the chat output",
"The prompt always shows provider and model",
"OpenRouter is recommended for broad model access"
],
bulletsZh: [
"直接在终端中进行交互式 AI 聊天",
"展示聊天输出中的基础 Markdown 渲染",
"提示符会始终显示提供商和模型",
"推荐使用 OpenRouter 来访问更多模型"
]
},
lines: [
{ type: "dynamic-chat" }
]
},
weather: {
title: "Weather",
command: "tinfo weather now",
info: {
heading: "Real-time weather",
headingZh: "实时天气",
bullets: [
"Shows the current conditions in a compact boxed view",
"Includes temperature, wind, and humidity",
"Useful when you want a quick weather check without leaving the terminal",
],
bulletsZh: [
"以紧凑的盒式视图显示当前天气",
"包含温度、风速和湿度",
"适合在不离开终端时快速查看天气"
]
},
lines: [
{
type: "box",
title: "Tokyo Weather",
width: 38,
labelWidth: 12,
rows: [
{ label: "Location", value: "Tokyo" },
{ label: "Weather", value: "Clear sky" },
{ label: "Temperature", value: "20.3°C" },
{ label: "Wind", value: "3.1 m/s" },
{ label: "Humidity", value: "61%" }
]
}
],
},
diagnostic: {
title: "Diagnostic",
command: "tinfo diagnostic network",
info: {
heading: "Network diagnostics",
headingZh: "网络诊断",
bullets: [
"Checks DNS, ping, HTTP reachability, and Cloudflare connectivity",
"Also reports public IP, local IP, and ISP details",
"Useful when you want a fast connectivity check from the CLI",
],
bulletsZh: [
"检查 DNS、ping、HTTP 可达性和 Cloudflare 连通性",
"同时报告公网 IP、本地 IP 和运营商信息",
"适合在 CLI 中快速检查连接状态"
]
},
lines: [
{ type: "check", key: " DNS resolution ", val: "ok", good: true },
{ type: "check", key: " External ping ", val: "26 ms", good: true },
{ type: "check", key: " HTTP reachability ", val: "ok", good: true },
{ type: "check", key: " Cloudflare ping ", val: "14 ms", good: true },
{ type: "kv", key: " Public IP ", val: "143.xxx.x.xx" },
{ type: "kv", key: " Local IP ", val: "192.168.x.xx" },
{ type: "kv", key: " ISP ", val: "Example ISP" },
{ type: "blank" },
{ type: "out", cls: "good", text: " All network checks passed." },
],
},
productivity: {
title: "Productivity",
command: "tinfo timer start 25m",
info: {
heading: "Built-in productivity",
headingZh: "内置效率工具",
bullets: [
"Starts a countdown timer from the command line",
"Shows the remaining time in a compact live view",
"Useful for focused work sessions directly in the terminal",
],
bulletsZh: [
"可以直接从命令行启动倒计时",
"以紧凑的实时视图显示剩余时间",
"适合在终端中进行专注工作"
]
},
lines: [
{ type: "out", cls: "term-output", text: "Started countdown timer for 25m 0s." },
{ type: "blank" },
{
type: "box",
title: "Terminal Info Timer",
width: 27,
labelWidth: 7,
rows: [
{ label: "Timer", value: "__DYNAMIC_TIMER__" }
]
},
{ type: "blank" },
{ type: "out", cls: "term-output", text: "Press q or Ctrl+C to exit." }
],
},
plugin: {
title: "Plugin",
command: "tinfo plugin install news",
info: {
heading: "Plugin management",
headingZh: "插件管理",
bullets: [
"The install command resolves a plugin name and places the binary in the managed plugin directory",
"Installed plugins become available as first-class terminal commands",
"Useful for extending the CLI without modifying the core tool",
],
bulletsZh: [
"安装命令会解析插件名称,并将二进制文件放入受管理的插件目录",
"安装后的插件会作为一等终端命令提供使用",
"适合在不修改核心工具的情况下扩展 CLI"
]
},
lines: [
{ type: "out", cls: "good", text: "Installed plugin 'news' at /Users/you/.terminal-info/plugins/docker/tinfo-news." },
],
},
};
function renderInfo(key) {
var d = demos[key];
if (!d) return;
var info = d.info;
var heading = currentLanguage === "zh" && info.headingZh ? info.headingZh : info.heading;
var bullets = currentLanguage === "zh" && info.bulletsZh ? info.bulletsZh : info.bullets;
var html = '<h3>' + heading + '</h3>';
if (bullets && bullets.length) {
html += '<ul>';
for (var i = 0; i < bullets.length; i++) {
html += '<li>' + bullets[i] + '</li>';
}
html += '</ul>';
}
var el = document.getElementById("demo-info");
if (el) el.innerHTML = html;
}
function buildLines(key) {
var d = demos[key];
if (!d) return "";
var html = "";
if (!d.liveSession) {
html += '<div class="term-line"><span class="term-ps">$</span><span class="term-cmd" id="demo-cmd-text"></span><span class="term-cursor" id="demo-cursor"></span></div>';
}
html += '<div id="demo-output" style="opacity:0;transition:opacity 0.3s">';
for (var i = 0; i < d.lines.length; i++) {
var l = d.lines[i];
if (l.type === "blank") {
html += '<div class="term-line" style="height:0.6rem"></div>';
} else if (l.type === "dynamic-chat") {
html += '<div data-dynamic-box="chat"></div>';
} else if (l.type === "box") {
if (
l.rows &&
l.rows.some(function (row) {
return row.value === "__DYNAMIC_TIME__";
})
) {
html += '<div data-dynamic-box="dashboard"></div>';
continue;
}
if (
l.rows &&
l.rows.length === 1 &&
l.rows[0].label === "Timer" &&
l.rows[0].value === "__DYNAMIC_TIMER__"
) {
html += '<div data-dynamic-box="timer"></div>';
continue;
}
var boxLines = formatBoxRows(l.title, l.rows, {
width: l.width,
labelWidth: l.labelWidth
});
for (var j = 0; j < boxLines.length; j++) {
html += renderBoxLine(boxLines[j]);
}
} else if (l.type === "out") {
html += '<div class="term-line"><span class="term-out ' + (l.cls || "") + '">' + escHtml(l.text) + '</span></div>';
} else if (l.type === "chat-prompt") {
html += '<div class="term-line"><span class="term-cmd">' + escHtml(l.text) + '</span></div>';
} else if (l.type === "kv") {
html += '<div class="term-line"><span class="term-key">' + escHtml(l.key) + '</span><span class="term-val">' + escHtml(l.val) + '</span></div>';
} else if (l.type === "check") {
var cls = l.good ? "good" : "bad";
html += '<div class="term-line"><span class="term-key">' + escHtml(l.key) + '</span><span class="term-out ' + cls + '">\u2713 ' + escHtml(l.val) + '</span></div>';
}
}
html += '</div>';
if (!d.liveSession) {
html += '<div id="demo-end-prompt" style="display:none;margin-top:0.5rem"><div class="term-line"><span class="term-ps">$</span><span class="term-cursor"></span></div></div>';
}
return html;
}
function escHtml(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
var typeTimer = null;
var showTimer = null;
var demoValueTimer = null;
var demoClockTimer = null;
function clearTimers() {
if (typeTimer) clearTimeout(typeTimer);
if (showTimer) clearTimeout(showTimer);
if (demoValueTimer) clearInterval(demoValueTimer);
if (demoClockTimer) clearInterval(demoClockTimer);
typeTimer = null;
showTimer = null;
demoValueTimer = null;
demoClockTimer = null;
}
function formatDateTime(now) {
return [
now.getFullYear(),
"-",
pad(now.getMonth() + 1),
"-",
pad(now.getDate()),
" ",
pad(now.getHours()),
":",
pad(now.getMinutes()),
":",
pad(now.getSeconds())
].join("");
}
function formatCountdown(totalSeconds) {
var hours = Math.floor(totalSeconds / 3600);
var minutes = Math.floor((totalSeconds % 3600) / 60);
var seconds = totalSeconds % 60;
return pad(hours) + ":" + pad(minutes) + ":" + pad(seconds) + " remaining";
}
function formatReminder(targetTime) {
var diffMs = Math.max(0, targetTime - Date.now());
var remainingMinutes = Math.ceil(diffMs / 60000);
if (remainingMinutes <= 0) {
return "break due now";
}
if (remainingMinutes === 1) {
return "break in 1 min";
}
return "break in " + remainingMinutes + " min";
}
function startProductivityDemo() {
var output = document.getElementById("demo-output");
if (!output) return;
var remaining = 25 * 60;
function render() {
var lines = formatBoxRows("Terminal Info Timer", [
{ label: "Timer", value: formatCountdown(remaining) }
], {
width: 27,
labelWidth: 7
});
var html = "";
for (var i = 0; i < lines.length; i++) {
html += renderBoxLine(lines[i]);
}
output.querySelectorAll('[data-dynamic-box="timer"]').forEach(function (node) {
node.remove();
});
var wrapper = document.createElement("div");
wrapper.setAttribute("data-dynamic-box", "timer");
wrapper.innerHTML = html;
output.appendChild(wrapper);
}
render();
demoValueTimer = window.setInterval(function () {
if (remaining > 0) {
remaining -= 1;
} else {
clearInterval(demoValueTimer);
demoValueTimer = null;
var endPrompt = document.getElementById("demo-end-prompt");
if (endPrompt) {
endPrompt.style.display = "block";
}
}
render();
}, 1000);
}
function startDashboardDemo() {
var output = document.getElementById("demo-output");
if (!output) return;
var cpuSeries = ["19.3%", "22.1%", "24.8%", "21.7%"];
var memorySeries = [
"16.2 GiB / 24.0 GiB used",
"16.3 GiB / 24.0 GiB used",
"16.1 GiB / 24.0 GiB used",
"16.2 GiB / 24.0 GiB used"
];
var reminderTarget = Date.now() + 12 * 60 * 1000;
var timerRemaining = 24 * 60 + 52;
var cpuIndex = 0;
function render() {
var now = new Date();
var lines = formatBoxRows("Terminal Info", [
{ label: "Location", value: "Tokyo" },
{ label: "Weather", value: "Clear sky, 20.3°C" },
{ label: "Time", value: formatDateTime(now) },
{ label: "Network", value: "143.xxx.x.xx" },
{ label: "CPU", value: cpuSeries[cpuIndex] },
{ label: "Memory", value: memorySeries[cpuIndex] },
{ label: "Timers", value: formatCountdown(timerRemaining) },
{ label: "Reminders", value: formatReminder(reminderTarget) }
], {
width: 44,
labelWidth: 10
});
var html = "";
for (var i = 0; i < lines.length; i++) {
html += renderBoxLine(lines[i]);
}
output.querySelectorAll('[data-dynamic-box="dashboard"]').forEach(function (node) {
node.remove();
});
var wrapper = document.createElement("div");
wrapper.setAttribute("data-dynamic-box", "dashboard");
wrapper.innerHTML = html;
output.insertBefore(wrapper, output.firstChild);
}
render();
demoClockTimer = window.setInterval(function () {
cpuIndex = (cpuIndex + 1) % cpuSeries.length;
if (timerRemaining > 0) {
timerRemaining -= 1;
}
render();
}, 1000);
}
function renderChatLine(text, cls) {
return '<div class="term-line"><span class="' + (cls || "term-out") + '">' + escHtml(text) + '</span></div>';
}
function renderChatPrompt(text) {
return '<div class="term-line"><span class="term-cmd">' + escHtml(text) + '</span></div>';
}
function renderMarkdownHeading(text) {
return '<div class="term-line"><span class="term-md-heading">' + escHtml(text) + '</span></div>';
}
function renderMarkdownBullet(text) {
return '<div class="term-line"><span class="term-md-bullet">' + escHtml(text) + '</span></div>';
}
function renderMarkdownCodeLine(prefix, codeText, suffix) {
var html = '<div class="term-line">';
if (prefix) {
html += '<span class="term-out">' + escHtml(prefix) + '</span>';
}
html += '<span class="term-md-code">' + escHtml(codeText) + '</span>';
if (suffix) {
html += '<span class="term-out">' + escHtml(suffix) + '</span>';
}
html += '</div>';
return html;
}
function startChatDemo() {
var output = document.getElementById("demo-output");
if (!output) return;
var modelOptions = [
"openai/gpt-5.4-pro",
"anthropic/claude-4.6-sonnet",
"google/gemini-3.1-pro-preview",
"Custom model..."
];
var modelIndex = 0;
var phase = 0;
function render() {
var html = "";
html += renderChatLine("Using provider: OpenRouter", "term-out");
html += renderChatLine("Type 'exit' or 'quit' to leave.", "term-out");
html += renderChatLine("Tip: /provider switch provider · /model switch model", "term-out");
html += '<div class="term-line" style="height:0.6rem"></div>';
html += renderChatPrompt("[OpenRouter · openai/gpt-5.4-pro] > Give me a short release note in markdown.");
html += '<div class="term-line" style="height:0.6rem"></div>';
html += renderChatLine("AI:", "term-cmd");
html += renderMarkdownHeading("Release note");
html += renderMarkdownBullet("Added live agent approvals");
html += renderMarkdownBullet("Simplified the chat prompt");
html += renderMarkdownCodeLine("Use ", "cargo check --workspace", " before shipping");
html += '<div class="term-line" style="height:0.6rem"></div>';
html += renderChatPrompt("[OpenRouter · openai/gpt-5.4-pro] > /model");
html += renderChatLine("Select a model with t/t.", "term-out");
for (var i = 0; i < modelOptions.length; i++) {
var prefix = i === modelIndex ? "› " : " ";
html += renderChatLine(prefix + modelOptions[i], "term-out");
}
output.querySelectorAll('[data-dynamic-box="chat"]').forEach(function (node) {
node.remove();
});
var wrapper = document.createElement("div");
wrapper.setAttribute("data-dynamic-box", "chat");
wrapper.innerHTML = html;
output.appendChild(wrapper);
}
render();
demoValueTimer = window.setInterval(function () {
if (phase < 4) {
modelIndex = (modelIndex + 1) % modelOptions.length;
phase += 1;
} else {
modelIndex = 0;
}
render();
}, 1100);
}
function typeCommand(text, el, cursor, callback) {
var i = 0;
el.textContent = "";
function step() {
if (i < text.length) {
el.textContent += text[i];
i++;
typeTimer = setTimeout(step, 38 + Math.random() * 20);
} else {
if (cursor) cursor.style.display = "none";
if (callback) showTimer = setTimeout(callback, 300);
}
}
step();
}
function runDemo(key) {
var d = demos[key];
if (!d) return;
clearTimers();
var body = document.getElementById("demo-term-body");
if (!body) return;
body.innerHTML = buildLines(key);
var cmdEl = document.getElementById("demo-cmd-text");
var cursor = document.getElementById("demo-cursor");
var output = document.getElementById("demo-output");
var endPrompt = document.getElementById("demo-end-prompt");
if (d.liveSession) {
if (output) {
output.style.opacity = "1";
}
if (key === "chat") {
startChatDemo();
}
return;
}
if (!cmdEl) return;
typeCommand(d.command, cmdEl, cursor, function () {
if (output) {
output.style.opacity = "1";
}
if (endPrompt && key !== "dashboard" && key !== "productivity") {
showTimer = setTimeout(function () {
endPrompt.style.display = "block";
}, 200);
}
if (key === "dashboard") {
startDashboardDemo();
}
if (key === "productivity") {
startProductivityDemo();
}
});
}
function initDemoTabs() {
var tabs = document.querySelectorAll(".demo-tab");
if (!tabs.length) return;
function activate(key) {
tabs.forEach(function (t) {
var active = t.getAttribute("data-demo") === key;
t.classList.toggle("active", active);
t.setAttribute("aria-selected", active ? "true" : "false");
});
renderInfo(key);
runDemo(key);
}
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
activate(tab.getAttribute("data-demo"));
});
});
activate("dashboard");
}
function initInstallTabs() {
var tabs = document.querySelectorAll(".install-tab");
var contents = document.querySelectorAll(".install-content");
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
var key = tab.getAttribute("data-install");
tabs.forEach(function (t) { t.classList.remove("active"); });
contents.forEach(function (c) { c.classList.remove("active"); });
tab.classList.add("active");
var target = document.getElementById("install-" + key);
if (target) target.classList.add("active");
});
});
}
function copyText(text, btn) {
if (!navigator.clipboard) {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
flashBtn(btn);
return;
}
navigator.clipboard.writeText(text).then(function () {
flashBtn(btn);
});
}
function flashBtn(btn) {
var original = getLocalizedText(btn, currentLanguage) || btn.textContent;
btn.textContent = currentLanguage === "zh" ? "已复制" : "Copied!";
btn.classList.add("copied");
setTimeout(function () {
btn.textContent = original;
btn.classList.remove("copied");
}, 2000);
}
function initCopyButtons() {
var heroBtn = document.getElementById("hero-copy-btn");
var heroCmd = document.getElementById("hero-install-cmd");
if (heroBtn && heroCmd) {
heroBtn.addEventListener("click", function () {
copyText(heroCmd.getAttribute("data-copy") || heroCmd.textContent.trim(), heroBtn);
});
}
document.querySelectorAll(".code-copy").forEach(function (btn) {
btn.addEventListener("click", function () {
var text = btn.getAttribute("data-copy") || "";
copyText(text, btn);
});
});
}
function initAvailabilityLogos() {
document.querySelectorAll(".logo-link .logo-icon").forEach(function (img) {
var link = img.closest(".logo-link");
if (!link) return;
function showFallback() {
img.style.display = "none";
link.classList.remove("has-icon");
}
function showIcon() {
link.classList.add("has-icon");
}
if (img.complete && img.naturalWidth > 0) {
showIcon();
} else if (img.complete) {
showFallback();
} else {
img.addEventListener("load", showIcon, { once: true });
img.addEventListener("error", showFallback, { once: true });
}
});
}
function initHamburger() {
var btn = document.getElementById("hamburger");
var nav = document.getElementById("site-nav");
if (!btn || !nav) return;
btn.addEventListener("click", function () {
var open = nav.classList.toggle("open");
btn.setAttribute("aria-expanded", open ? "true" : "false");
});
nav.querySelectorAll("a").forEach(function (link) {
link.addEventListener("click", function () {
nav.classList.remove("open");
});
});
}
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(function (a) {
a.addEventListener("click", function (e) {
var id = a.getAttribute("href").slice(1);
var target = document.getElementById(id);
if (target) {
e.preventDefault();
var offset = 70; var top = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({ top: top, behavior: "smooth" });
}
});
});
}
document.addEventListener("DOMContentLoaded", function () {
initLanguageToggle();
loadStableVersion();
initSectionReveal();
initHeroTerminalUpdates();
initDemoTabs();
initInstallTabs();
initCopyButtons();
initAvailabilityLogos();
initHamburger();
initSmoothScroll();
});
})();