Ring.tsx

2.5 KB · raw

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}