"use client"; import { useEffect, useId, useState } from "react"; import { useReducedMotion } from "motion/react"; type TeaGlassFillProps = { /** Colour of the tea. */ tea?: string; /** Colour of the foam on top. */ foam?: string; /** 1 is the default pace; 2 is twice as fast. */ speed?: number; /** Read out by screen readers. */ label?: string; className?: string; }; const clamp = (v: number) => Math.min(1, Math.max(0, v)); const span = (p: number, a: number, b: number) => clamp((p - a) / (b - a)); const ease = (t: number) => t * t * (3 - 2 * t); const lerp = (a: number, b: number, t: number) => a + (b - a) * t; const BASE = 204; // Inside floor of the glass. const FULL = 0.86; const TILT = 24; // Kettle tilt in degrees at full pour. const REST = 0.42; // The frame shown before the clock starts and for reduced motion. const FLUTES = [-3, -2, -1, 0, 1, 2, 3]; const BUBBLES = [ [-0.62, 1.6], [-0.3, 2.2], [0.05, 1.5], [0.38, 2], [0.68, 1.4], ]; /* TeaGlassFill: a steel kettle pours a long thin stream into a fluted cutting-chai glass. It fills, foams, steams, then slides off the counter and an empty glass slides in. */ export function TeaGlassFill({ tea = "#a9622b", foam = "#ecd2a6", speed = 1, label = "Loading", className = "" }: TeaGlassFillProps) { const reduce = useReducedMotion(); const id = useId().replace(/:/g, ""); const [clock, setClock] = useState(REST); useEffect(() => { if (reduce) return; const seconds = 4.6 / speed; const start = performance.now() - REST * seconds * 1000; let raf = 0; const tick = (now: number) => { setClock(((now - start) / (seconds * 1000)) % 1); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [reduce, speed]); const p = reduce ? REST : clock; const tilt = ease(span(p, 0, 0.07)) * (1 - ease(span(p, 0.66, 0.74))); const fill = p >= 0.92 ? 0 : 1 - (1 - span(p, 0.07, 0.68)) ** 1.3; const slide = -214 * ease(span(p, 0.84, 0.92)) + 214 * (1 - ease(span(p, 0.92, 1))) * Number(p >= 0.92); const surface = BASE - fill * FULL * 92; const half = lerp(27, 38, (BASE - surface) / 92); const foamHigh = 2 + span(fill, 0.1, 0.7) * 5; // Where the spout tip sits at this tilt; the kettle turns round (178, 66). const turn = (-TILT * tilt * Math.PI) / 180; const tipX = 178 + -58 * Math.cos(turn) + 14 * Math.sin(turn); const tipY = 66 + -58 * Math.sin(turn) - 14 * Math.cos(turn); const streamOn = span(p, 0.06, 0.09) > 0 && p < 0.74; const reach = lerp(tipY, surface, ease(span(p, 0.06, 0.1))); const tail = lerp(tipY, surface, span(p, 0.66, 0.73)); const sway = Math.sin(p * 90) * 1.6; const steam = span(p, 0.58, 0.72) * (1 - span(p, 0.84, 0.88)); return (