(function () {
const data = window.CASE_DATA;
const timeline = document.querySelector("#timeline");
const filters = document.querySelector("#filters");
let activeFilter = "all";
let timelineView = "compact";
let editing = false;
const editStoreKey = "saigon-leverage-edits-v1";
const themeStoreKey = "saigon-leverage-theme";
document.querySelector("#episodeLabel").textContent = `CASE TIMELINE · ${data.episode}`;
document.querySelector("#heroEyebrow").textContent = data.eyebrow;
document.querySelector("#caseTitle").innerHTML = `${data.title.split(":")[0]}:
${data.title.split(":").slice(1).join(":")}`;
document.querySelector("#heroDeck").textContent = data.deck;
document.querySelector("#heroDeck").dataset.edit = "hero-deck";
document.querySelector("#heroMetrics").innerHTML = data.metrics.map((metric) => `
${metric.value}${metric.label}
`).join("");
document.querySelector("#participantsGrid").innerHTML = data.participants.map((party) => `
${party.mark}${party.name}
${party.role}
${party.stance}
${party.asset}
`).join("");
function applyTimelineView() {
timeline.classList.toggle("compact", timelineView === "compact");
document.querySelectorAll("[data-view]").forEach((button) => {
const active = button.dataset.view === timelineView;
button.classList.toggle("active", active);
button.setAttribute("aria-pressed", String(active));
});
}
function renderFilters() {
filters.innerHTML = data.categories.map((item) => `
`).join("");
}
function renderTimeline() {
const visible = activeFilter === "all" ? data.events : data.events.filter((event) => event.category === activeFilter);
timeline.innerHTML = visible.map((event, position) => `
${event.date}${event.index}
`).join("");
bindCards();
applyTimelineView();
restoreEdits();
if (editing) setEditing(true);
}
function categoryLabel(id) {
return data.categories.find((item) => item.id === id)?.label || id;
}
function bindCards() {
document.querySelectorAll(".event-card").forEach((card) => {
card.addEventListener("click", () => {
if (editing) return;
const expanded = card.getAttribute("aria-expanded") === "true";
card.setAttribute("aria-expanded", String(!expanded));
card.closest(".event").classList.toggle("open", !expanded);
});
});
}
filters.addEventListener("click", (event) => {
const button = event.target.closest("[data-filter]");
if (!button) return;
activeFilter = button.dataset.filter;
renderFilters();
renderTimeline();
});
document.querySelector(".view-switch").addEventListener("click", (event) => {
const button = event.target.closest("[data-view]");
if (!button) return;
timelineView = button.dataset.view;
applyTimelineView();
});
document.querySelector("#chapterNav").innerHTML = data.chapters.map((chapter) => {
const target = data.events.find((event) => event.chapter === chapter.id)?.id;
return `${chapter.label.slice(0, 2)}${chapter.label.slice(3)}`;
}).join("");
document.querySelector("#modelGrid").innerHTML = data.model.map((item) => `
${item.num}${item.title}
${item.subtitle}${item.body}
`).join("");
function storedEdits() {
try { return JSON.parse(localStorage.getItem(editStoreKey)) || {}; } catch (_) { return {}; }
}
function restoreEdits() {
const saved = storedEdits();
document.querySelectorAll("[data-edit]").forEach((element) => {
if (saved[element.dataset.edit] !== undefined) element.innerHTML = saved[element.dataset.edit];
});
}
function saveEdit(element) {
const saved = storedEdits();
saved[element.dataset.edit] = element.innerHTML;
localStorage.setItem(editStoreKey, JSON.stringify(saved));
}
function setEditing(next) {
editing = next;
document.body.classList.toggle("is-editing", editing);
const editButton = document.querySelector("#editToggle");
editButton.querySelector(".icon-glyph").textContent = editing ? "✓" : "✎";
editButton.setAttribute("aria-label", editing ? "完成编辑" : "编辑页面内容");
editButton.title = editing ? "完成编辑" : "编辑页面内容";
document.querySelectorAll("[data-edit]").forEach((element) => {
element.contentEditable = editing ? "true" : "false";
element.spellcheck = editing;
});
}
document.addEventListener("input", (event) => {
const editable = event.target.closest("[data-edit]");
if (editing && editable) saveEdit(editable);
});
document.querySelector("#editToggle").addEventListener("click", () => setEditing(!editing));
document.querySelector("#resetEdits").addEventListener("click", () => {
if (!confirm("确定恢复全部原稿吗?当前编辑内容将被清除。")) return;
localStorage.removeItem(editStoreKey);
location.reload();
});
function setTheme(theme) {
document.documentElement.dataset.theme = theme;
const themeButton = document.querySelector("#themeToggle");
const nextTheme = theme === "dark" ? "浅色" : "深色";
themeButton.querySelector(".icon-glyph").textContent = theme === "dark" ? "☼" : "☾";
themeButton.setAttribute("aria-label", `切换至${nextTheme}模式`);
themeButton.title = `切换至${nextTheme}模式`;
document.querySelectorAll("img[data-dark-src]").forEach((image) => {
image.src = theme === "dark" ? image.dataset.darkSrc : image.dataset.lightSrc;
});
localStorage.setItem(themeStoreKey, theme);
}
document.querySelector("#themeToggle").addEventListener("click", () => {
setTheme(document.documentElement.dataset.theme === "dark" ? "light" : "dark");
});
document.querySelector("#copyLink").addEventListener("click", async (event) => {
const button = event.currentTarget;
try {
await navigator.clipboard.writeText(location.href);
button.classList.add("is-copied");
button.querySelector(".icon-glyph").textContent = "✓";
button.setAttribute("aria-label", "链接已复制");
button.title = "链接已复制";
setTimeout(() => {
button.classList.remove("is-copied");
button.querySelector(".icon-glyph").textContent = "⤴";
button.setAttribute("aria-label", "复制当前页面链接");
button.title = "复制当前页面链接";
}, 1600);
} catch (_) {
button.querySelector(".icon-glyph").textContent = "!";
button.setAttribute("aria-label", "复制失败");
button.title = "复制失败";
}
});
function updateProgress() {
const story = document.querySelector("#story");
const start = story.offsetTop;
const distance = Math.max(story.offsetHeight - innerHeight, 1);
const percent = Math.max(0, Math.min(100, ((scrollY - start) / distance) * 100));
document.querySelector("#progressBar").style.width = `${percent}%`;
document.querySelector("#progressText").textContent = `${Math.round(percent)}%`;
}
addEventListener("scroll", updateProgress, { passive: true });
renderFilters();
renderTimeline();
restoreEdits();
setTheme(localStorage.getItem(themeStoreKey) || "dark");
updateProgress();
})();