aditya0103's picture
Tasks 5-7: eval harness, FastAPI backend, Paper & Ink UI- src/eval/ — precision/recall/F1 harness with type-aware comparators,micro/macro F1, CSV + markdown reports, --model benchmark flag- src/api/ — FastAPI backend with /extract, /schemas, /health,request-ID middleware, typed error envelope, injectable extractor- ui/ — Vite + React + TS + Tailwind + Motion + React Three FiberPaper & Ink editorial UI with 3D paper hero, dark/light mode,confidence inkwell, wax-stamp metrics, kinetic typography- 95 passing tests (up from 54); UI is a separate npm workspace
557ab38
Raw
History Blame Contribute Delete
2.5 kB
/**
* CustomCursor — a soft ink dot that follows the mouse with spring lag.
* On hoverable elements (marked with data-cursor="focus"), the dot expands.
* Hidden on touch devices.
*
* The spring gives it that "ink meeting paper" resistance instead of a
* hard pixel-perfect follow.
*/
import { motion, useMotionValue, useSpring } from "motion/react";
import { useEffect, useState } from "react";
const SPRING = { damping: 30, stiffness: 400, mass: 0.6 };
export function CustomCursor() {
const x = useMotionValue(-100);
const y = useMotionValue(-100);
const sx = useSpring(x, SPRING);
const sy = useSpring(y, SPRING);
const [focused, setFocused] = useState(false);
const [visible, setVisible] = useState(false);
useEffect(() => {
if (typeof window === "undefined") return;
// Bail on coarse pointers (mobile / touch) — no custom cursor.
if (window.matchMedia("(hover: none)").matches) return;
setVisible(true);
const onMove = (e: MouseEvent) => {
x.set(e.clientX);
y.set(e.clientY);
};
const onOver = (e: MouseEvent) => {
const el = e.target as HTMLElement;
const wantsFocus = !!el.closest(
'a, button, [role="button"], input, textarea, [data-cursor="focus"]'
);
setFocused(wantsFocus);
};
const onLeave = () => setVisible(false);
const onEnter = () => setVisible(true);
window.addEventListener("mousemove", onMove);
window.addEventListener("mouseover", onOver);
document.addEventListener("mouseleave", onLeave);
document.addEventListener("mouseenter", onEnter);
return () => {
window.removeEventListener("mousemove", onMove);
window.removeEventListener("mouseover", onOver);
document.removeEventListener("mouseleave", onLeave);
document.removeEventListener("mouseenter", onEnter);
};
}, [x, y]);
if (!visible) return null;
return (
<>
<motion.div
aria-hidden
style={{
x: sx,
y: sy,
translateX: "-50%",
translateY: "-50%",
}}
className="pointer-events-none fixed left-0 top-0 z-[200] mix-blend-difference"
>
<motion.div
animate={{
width: focused ? 44 : 10,
height: focused ? 44 : 10,
opacity: focused ? 0.65 : 0.95,
}}
transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
className="rounded-full bg-white"
/>
</motion.div>
</>
);
}