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

Møt Aurora – Elektrisk, kjører selv og kun 162 kilo!

Oslo Motor Show fikk være til stede på avdukingen av årets RACECAR – konseptbilen til Revolve NTNU. Årets bil heter Aurora, er elektrisk og selvkjørende og veier bare 162 kilo! Det er den 10. bilen som er bygd av Revolveprosjektet, og skal nå ut og konkurrere internasjonalt om de gjeveste pokalene i Formula student. Vi tar av oss hatten for gjengen av dedikerte studenter, ønsker dem lykke til i konkurransen, og håper å få vise bilen på Oslo Motor Show 2022!

Alle foto: Revolve NTNU

Les mer om Aurora og Revolve NTNU her