import { useRef, useEffect, useState, useMemo, useId } from 'react'; import './CurvedLoop.css'; const CurvedLoop = ({ marqueeText = '1', speed = 2, className, curveAmount = 400, direction = 'left', interactive = true }) => { const text = useMemo(() => { const hasTrailing = /\s|\u00A0$/.test(marqueeText); return (hasTrailing ? marqueeText.replace(/\s+$/, '') : marqueeText) + '\u00A0'; }, [marqueeText]); const measureRef = useRef(null); const textPathRef = useRef(null); const pathRef = useRef(null); const [spacing, setSpacing] = useState(0); const [offset, setOffset] = useState(0); const uid = useId(); const pathId = `curve-${uid}`; const pathD = `M-100,40 Q500,${40 + curveAmount} 1540,40`; const dragRef = useRef(false); const lastXRef = useRef(0); const dirRef = useRef(direction); const velRef = useRef(0); const textLength = spacing; const totalText = textLength ? Array(Math.ceil(1800 / textLength) + 2) .fill(text) .join('') : text; const ready = spacing > 0; useEffect(() => { if (measureRef.current) setSpacing(measureRef.current.getComputedTextLength()); }, [text, className]); useEffect(() => { if (!spacing) return; if (textPathRef.current) { const initial = -spacing; textPathRef.current.setAttribute('startOffset', initial + 'px'); setOffset(initial); } }, [spacing]); useEffect(() => { if (!spacing || !ready) return; let frame = 0; const step = () => { if (!dragRef.current && textPathRef.current) { const delta = dirRef.current === 'right' ? speed : -speed; const currentOffset = parseFloat(textPathRef.current.getAttribute('startOffset') || '0'); let newOffset = currentOffset + delta; const wrapPoint = spacing; if (newOffset <= -wrapPoint) newOffset += wrapPoint; if (newOffset > 0) newOffset -= wrapPoint; textPathRef.current.setAttribute('startOffset', newOffset + 'px'); setOffset(newOffset); } frame = requestAnimationFrame(step); }; frame = requestAnimationFrame(step); return () => cancelAnimationFrame(frame); }, [spacing, speed, ready]); const onPointerDown = e => { if (!interactive) return; dragRef.current = true; lastXRef.current = e.clientX; velRef.current = 0; e.target.setPointerCapture(e.pointerId); }; const onPointerMove = e => { if (!interactive || !dragRef.current || !textPathRef.current) return; const dx = e.clientX - lastXRef.current; lastXRef.current = e.clientX; velRef.current = dx; const currentOffset = parseFloat(textPathRef.current.getAttribute('startOffset') || '0'); let newOffset = currentOffset + dx; const wrapPoint = spacing; if (newOffset <= -wrapPoint) newOffset += wrapPoint; if (newOffset > 0) newOffset -= wrapPoint; textPathRef.current.setAttribute('startOffset', newOffset + 'px'); setOffset(newOffset); }; const endDrag = () => { if (!interactive) return; dragRef.current = false; dirRef.current = velRef.current > 0 ? 'right' : 'left'; }; const cursorStyle = interactive ? (dragRef.current ? 'grabbing' : 'grab') : 'auto'; return (
{text} {ready && ( {totalText} )}
); }; export default CurvedLoop;
Åpningstider Fre 10.00 – 19.00 | Lør 10.00 – 19.00 | Søn 10.00 – 17.00

Kulturarv på luftfjæring

Sture Balsbergs Amazon Kombi ble bygget i det stille og slo ned som en bombe på Vallåkratreffet. Nå har du sjansen til å se den live på Oslo Motor Show. Sture beskriver bilen som en forvaltet kulturarv og har vært ekstremt nøye med at alt skal kunne tilbakestilles til orginalen, eller helst ti ganger bedre enn orginalen. Luftfjæring og orginalfelger ombygd til splitfelger er kronen på verket som er Stures perfekte Amazon.

Foto: Mattias Larsson