Vite 8 vanilla page for omarchycn.zacharyzhang.com: kami palette on a 12-col Swiss grid, self-hosted Fusion Pixel zh_hans (OFL shipped), GSAP ScrollSmoother/ScrollTrigger with reduced-motion opt-out, reicon icons, ISO download plus one-click convert command copy, real installer screenshot. shots.mjs is the rendered-state harness; deploy is npx wrangler deploy from site/ (custom domain + workers.dev). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hLK3wsDuKVAC37GgDqg6H
96 lines
2.9 KiB
JavaScript
96 lines
2.9 KiB
JavaScript
import { gsap } from "gsap";
|
|
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
|
import { ScrollSmoother } from "gsap/ScrollSmoother";
|
|
import { Download, Copy, Check } from "reicon";
|
|
|
|
const ICONS = { Download, Copy, Check };
|
|
const ICON_OPTS = { size: 18, strokeWidth: 1.5 };
|
|
|
|
for (const el of document.querySelectorAll(".i[data-icon]")) {
|
|
const icon = ICONS[el.dataset.icon];
|
|
if (!icon) throw new Error(`unknown icon: ${el.dataset.icon}`);
|
|
el.replaceChildren(icon(ICON_OPTS));
|
|
}
|
|
|
|
/* Copy the convert command */
|
|
const copyBtn = document.getElementById("copy-btn");
|
|
const copyText = copyBtn.querySelector(".copy-text");
|
|
const copyIcon = copyBtn.querySelector(".i");
|
|
copyText.setAttribute("aria-live", "polite");
|
|
let copyTimer;
|
|
let copySeq = 0;
|
|
|
|
copyBtn.addEventListener("click", async () => {
|
|
const cmd = document.getElementById("cmd-text").textContent.trim();
|
|
const seq = ++copySeq;
|
|
let ok = true;
|
|
try {
|
|
await navigator.clipboard.writeText(cmd);
|
|
} catch (err) {
|
|
console.error("clipboard write failed", err);
|
|
ok = false;
|
|
}
|
|
// Only the latest click may touch the feedback state
|
|
if (seq !== copySeq) return;
|
|
clearTimeout(copyTimer);
|
|
copyBtn.classList.remove("done", "fail");
|
|
copyBtn.classList.add(ok ? "done" : "fail");
|
|
copyIcon.replaceChildren(ok ? Check(ICON_OPTS) : Copy(ICON_OPTS));
|
|
copyText.textContent = ok ? "已复制" : "复制失败";
|
|
copyTimer = setTimeout(() => {
|
|
copyBtn.classList.remove("done", "fail");
|
|
copyIcon.replaceChildren(Copy(ICON_OPTS));
|
|
copyText.textContent = "复制";
|
|
}, 2000);
|
|
});
|
|
|
|
/* Grid overlay: G key */
|
|
document.addEventListener("keydown", (e) => {
|
|
if (e.key !== "g" && e.key !== "G") return;
|
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
if (/^(input|textarea|select)$/i.test(e.target.tagName)) return;
|
|
document.body.classList.toggle("grid-on");
|
|
});
|
|
|
|
/* Motion: smooth scrolling + reveals, skipped under reduced motion */
|
|
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
|
|
|
|
ScrollSmoother.create({
|
|
wrapper: "#smooth-wrapper",
|
|
content: "#smooth-content",
|
|
smooth: 1,
|
|
});
|
|
|
|
const enter = [".hero .eyebrow", ".hero h1 .line", ".hero .lede", ".hero .actions", ".hero .meta"];
|
|
gsap.set(enter, { y: 24, autoAlpha: 0 });
|
|
document.fonts.ready.then(() => {
|
|
gsap.to(enter, {
|
|
y: 0,
|
|
autoAlpha: 1,
|
|
duration: 0.7,
|
|
ease: "power2.out",
|
|
stagger: 0.09,
|
|
});
|
|
});
|
|
|
|
// Document-tail elements never reach the 85% line, so they fire on entry
|
|
const reveals = [
|
|
[".shot", "top 85%"],
|
|
[".feat", "top 85%"],
|
|
[".verify > *", "top 92%"],
|
|
[".foot > *", "top bottom"],
|
|
];
|
|
for (const [sel, start] of reveals) {
|
|
for (const el of gsap.utils.toArray(sel)) {
|
|
gsap.from(el, {
|
|
y: 24,
|
|
autoAlpha: 0,
|
|
duration: 0.6,
|
|
ease: "power2.out",
|
|
scrollTrigger: { trigger: el, start },
|
|
});
|
|
}
|
|
}
|
|
}
|