mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
Long guides and case studies now have a small `↑ Top` link at the bottom right. It appears after scrolling, returns smoothly to the page header, and moves keyboard focus back to navigation. Reduced-motion users return immediately; the native `#top` link also works without JavaScript. The link is shared by all 11 styled website pages. The README and style guide document it, and the evidence notes retain the original browser-example digest alongside its later navigation addition. Validation: - Website preparation and verification: 12 HTML pages, 195 local references, SVG XML, the six-file ZIP and the sole manual Pages publisher. - Oxfmt, `git diff --check` and `npm run check:fast` with Node.js 24.18.0. - Focused browser checks at `/` and `/rea/`: 22 routes, 21 pointer returns, six keyboard returns, five viewport widths, smooth scrolling, reduced motion, focus return and the no-JavaScript anchor. - Existing site interactions: 24 routes, 68 copy buttons, 20 comparison steps, two CSV exports, two ZIP downloads and eight viewport widths from 320px to 1440px. Code excerpts are unchanged. - Desktop and mobile rendering reviewed. Downloadable Electron source and CSV export source/data are unchanged. Publication continues through the existing manual Pages workflow after merge.
87 lines
2.8 KiB
JavaScript
87 lines
2.8 KiB
JavaScript
function initializeCopyButtons() {
|
|
document.querySelectorAll("[data-copy]").forEach((button) => {
|
|
button.addEventListener("click", async () => {
|
|
const target = document.getElementById(button.getAttribute("data-copy"));
|
|
if (target === null) return;
|
|
|
|
const status = document.getElementById("copy-status");
|
|
const content = target.textContent.trim();
|
|
const text =
|
|
target.closest(".terminal-prompt") === null
|
|
? content
|
|
: content.replace(/\s+/g, " ");
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
button.textContent = "Copied";
|
|
if (status !== null) status.textContent = "Code copied to clipboard.";
|
|
} catch {
|
|
const range = document.createRange();
|
|
range.selectNodeContents(target);
|
|
const selection = window.getSelection();
|
|
if (selection !== null) {
|
|
selection.removeAllRanges();
|
|
selection.addRange(range);
|
|
}
|
|
button.textContent = "Selected";
|
|
if (status !== null)
|
|
status.textContent = "Code selected. Use your keyboard to copy it.";
|
|
}
|
|
|
|
window.setTimeout(() => {
|
|
button.textContent = "Copy";
|
|
}, 2000);
|
|
});
|
|
});
|
|
}
|
|
|
|
initializeCopyButtons();
|
|
|
|
function initializeStepComparisons() {
|
|
document.querySelectorAll("[data-step-comparison]").forEach((comparison) => {
|
|
const buttons = comparison.querySelectorAll("[data-select-step]");
|
|
buttons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const selected = button.getAttribute("data-select-step");
|
|
buttons.forEach((candidate) => {
|
|
candidate.setAttribute("aria-pressed", String(candidate === button));
|
|
});
|
|
comparison.querySelectorAll("[data-step]").forEach((fragment) => {
|
|
fragment.classList.toggle(
|
|
"is-active",
|
|
fragment.getAttribute("data-step") === selected,
|
|
);
|
|
});
|
|
comparison.querySelectorAll("[data-step-note]").forEach((note) => {
|
|
note.hidden = note.getAttribute("data-step-note") !== selected;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
initializeStepComparisons();
|
|
|
|
function initializeBackToTop() {
|
|
const link = document.querySelector(".back-to-top");
|
|
if (link === null) return;
|
|
|
|
const updateVisibility = () => {
|
|
link.hidden = window.scrollY < 400;
|
|
};
|
|
window.addEventListener("scroll", updateVisibility, { passive: true });
|
|
updateVisibility();
|
|
|
|
link.addEventListener("click", (event) => {
|
|
event.preventDefault();
|
|
const reduceMotion = window.matchMedia(
|
|
"(prefers-reduced-motion: reduce)",
|
|
).matches;
|
|
window.scrollTo({ top: 0, behavior: reduceMotion ? "auto" : "smooth" });
|
|
document
|
|
.querySelector(".brand, .breadcrumb a")
|
|
?.focus({ preventScroll: true });
|
|
});
|
|
}
|
|
|
|
initializeBackToTop();
|