// 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. ); // 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); 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 (triggered) return; const sy = window.scrollY; if (sy > 20 && sy < window.innerHeight * 0.7) { triggered = true; window.scrollTo({ top: window.innerHeight, behavior: 'smooth' }); } }; 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 });