1import { onMount, onCleanup } from "solid-js";23interface RingProps {4 target: () => HTMLElement | null;5}67export function Ring(props: RingProps) {8 let svgEl: SVGSVGElement | undefined;9 let rectEl: SVGRectElement | undefined;10 let fadeTimer: ReturnType<typeof setTimeout>;1112 function calibrate() {13 const el = props.target();14 if (!el || !svgEl || !rectEl) return;15 const { width, height } = el.getBoundingClientRect();16 const r = 12;17 const perim = 2 * (width + height);18 svgEl.setAttribute("viewBox", `0 0 ${width} ${height}`);19 svgEl.setAttribute("width", String(width));20 svgEl.setAttribute("height", String(height));21 rectEl.setAttribute("width", String(width));22 rectEl.setAttribute("height", String(height));23 rectEl.setAttribute("rx", String(r));24 rectEl.setAttribute("ry", String(r));25 rectEl.style.setProperty("--perim", String(perim));26 rectEl.style.strokeDasharray = String(perim);27 rectEl.style.strokeDashoffset = String(perim);28 }2930 function trigger() {31 if (!rectEl) return;32 rectEl.style.animation = "none";33 void (rectEl as unknown as HTMLElement).offsetWidth;34 rectEl.style.animation = "ringDraw 380ms ease-out both";35 clearTimeout(fadeTimer);36 fadeTimer = setTimeout(beginFade, 380 + 1200);37 }3839 function beginFade() {40 if (!rectEl) return;41 rectEl.style.transition = "opacity 600ms var(--retreat)";42 rectEl.style.opacity = "0";43 }4445 onMount(() => {46 calibrate();47 const ro = new ResizeObserver(calibrate);48 const el = props.target();49 if (el) ro.observe(el);50 onCleanup(() => ro.disconnect());51 });5253 (window as any).__peachRingTrigger = trigger;5455 return (56 <svg57 ref={svgEl}58 style={{ position: "absolute", inset: 0, "pointer-events": "none", "z-index": 10 }}59 >60 <style>{`61 @keyframes ringDraw {62 0% { stroke-dashoffset: var(--perim); opacity: 0; }63 60% { stroke-dashoffset: calc(var(--perim) * -0.035); opacity: 0.85; }64 78% { stroke-dashoffset: calc(var(--perim) * 0.008); }65 100% { stroke-dashoffset: 0; opacity: 0.85; }66 }67 `}</style>68 <rect69 ref={rectEl}70 x="0"71 y="0"72 fill="none"73 stroke="var(--ac)"74 stroke-width="1"75 style={{76 filter: "drop-shadow(0 0 5px color-mix(in srgb, var(--peach-accent) 80%, transparent))",77 transform: "rotate(-90deg)",78 "transform-origin": "50% 50%",79 opacity: "0",80 }}81 />82 </svg>83 );84}