// Typing intro + reusable wordmark logo for WithDev site. // Wordmark logo — small, for nav. Matches the SVG: "withdev" with purple dot. const WordmarkLogo = ({ height = 26 }) => ( withdev. ); // Smooth scroll with controlled duration. const introSmoothScrollTo = (targetY, duration = 1400) => { const startY = window.scrollY; const distance = targetY - startY; if (Math.abs(distance) < 4) return; const startTime = performance.now(); const easeInOutCubic = (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2); const step = (now) => { const elapsed = now - startTime; const t = Math.min(1, elapsed / duration); window.scrollTo(0, startY + distance * easeInOutCubic(t)); if (t < 1) requestAnimationFrame(step); }; requestAnimationFrame(step); }; // Full-screen intro that types "withdev." with a blinking purple cursor. // After typing completes, the cursor blinks and a "↓ role" hint appears. // Auto-scrolls to the next section as soon as the user scrolls even a little. const TypingIntro = () => { const FULL = 'withdev.'; const [typed, setTyped] = React.useState(''); const [done, setDone] = React.useState(false); const [hintDismissed, setHintDismissed] = React.useState(false); React.useEffect(() => { if (typed.length < FULL.length) { const t = setTimeout(() => { setTyped(FULL.slice(0, typed.length + 1)); }, 200); return () => clearTimeout(t); } else { const t = setTimeout(() => setDone(true), 600); return () => clearTimeout(t); } }, [typed]); // Auto-snap: once typing is done, any small scroll smoothly takes the // user to the V23 section below. React.useEffect(() => { if (!done) return; let triggered = false; const handler = () => { if (window.scrollY > window.innerHeight * 0.6) setHintDismissed(true); if (triggered) return; const sy = window.scrollY; if (sy > 20 && sy < window.innerHeight * 0.7) { triggered = true; introSmoothScrollTo(window.innerHeight, 1400); } }; window.addEventListener('scroll', handler, { passive: true }); return () => window.removeEventListener('scroll', handler); }, [done]); const renderTyped = () => { const out = []; for (let i = 0; i < typed.length; i++) { const ch = typed[i]; out.push( {ch} ); } return out; }; return (
{/* Wordmark being typed */}
{renderTyped()} {/* Cursor — solid while typing, blinks when done */}
{/* Scroll hint */}
role para continuar
); }; // The "" tag-style logo. Used in the V23 nav and footer. const TagLogo = ({ height = 24 }) => ( {'<'} WithDev {'/>'} ); // Typing headline that triggers when scrolled into view. // Pass `text` with `\n` for line breaks. Renders a blinking purple cursor at the end. const TypingHeadline = ({ text, speed = 55, style = {}, ...rest }) => { const ref = React.useRef(null); const [started, setStarted] = React.useState(false); const [typed, setTyped] = React.useState(''); React.useEffect(() => { if (!ref.current) return; const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setStarted(true); obs.disconnect(); } }, { threshold: 0.4 }); obs.observe(ref.current); return () => obs.disconnect(); }, []); React.useEffect(() => { if (!started || typed.length >= text.length) return; const t = setTimeout(() => setTyped(text.slice(0, typed.length + 1)), speed); return () => clearTimeout(t); }, [started, typed, text, speed]); const done = typed.length >= text.length; const parts = typed.split('\n'); return (

{parts.map((p, i) => ( {i > 0 &&
} {p}
))} {/* Cursor */}

); }; Object.assign(window, { WordmarkLogo, TagLogo, TypingIntro, TypingHeadline });