2026-05-29 11:50:27 +08:00
|
|
|
import { ArrowUp } from "lucide-react";
|
|
|
|
|
import { AnimatePresence, m } from "framer-motion";
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { useI18n } from "../i18n";
|
|
|
|
|
|
|
|
|
|
const SHOW_AFTER = 400;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Floating "back to top" button. Appears once the page is scrolled past
|
|
|
|
|
* SHOW_AFTER px and smoothly returns the window to the top on click. Sits
|
|
|
|
|
* above the mobile bottom nav and clears it on larger screens.
|
|
|
|
|
*/
|
|
|
|
|
export function BackToTop() {
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const update = () => setVisible(window.scrollY > SHOW_AFTER);
|
|
|
|
|
update();
|
|
|
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
|
|
|
return () => window.removeEventListener("scroll", update);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{visible ? (
|
|
|
|
|
<m.button
|
|
|
|
|
type="button"
|
|
|
|
|
initial={{ opacity: 0, scale: 0.8, y: 8 }}
|
|
|
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
|
|
|
exit={{ opacity: 0, scale: 0.8, y: 8 }}
|
|
|
|
|
transition={{ type: "spring", stiffness: 380, damping: 26 }}
|
2026-05-29 12:49:22 +08:00
|
|
|
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
|
2026-05-30 21:30:08 +08:00
|
|
|
className="fixed bottom-[calc(84px+max(env(safe-area-inset-bottom),0px))] right-4 z-30 flex h-11 w-11 items-center justify-center rounded-full bg-ark-gold text-black shadow-lg shadow-black/40 outline-none transition hover:bg-ark-gold2 active:scale-95 focus-visible:ring-2 focus-visible:ring-ark-gold/80 focus-visible:ring-offset-2 focus-visible:ring-offset-ark-bg md:bottom-8 md:right-8"
|
2026-05-29 11:50:27 +08:00
|
|
|
aria-label={t("backToTop")}
|
|
|
|
|
title={t("backToTop")}
|
|
|
|
|
>
|
|
|
|
|
<ArrowUp className="h-5 w-5" strokeWidth={2.4} />
|
|
|
|
|
</m.button>
|
|
|
|
|
) : null}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
);
|
|
|
|
|
}
|