<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta name="description" content="Rafael Marin is a digital product designer based in Brooklyn, NY" />
    <meta name="keywords" content="user experience design, user interface design, usability, interaction design, design, product design, Work & Co, Airbnb" />
    <meta name="google-site-verification" content="qvCafYwHZ4o4WxlhpqOoHl7aZ7aW3t16qSC3Rj58Sak" />
    <title>Rafael Marin - Digital product designer based in Brooklyn, NY</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-color: #fcfbf9;
            --text-color: #333;
        }
        .dark-mode {
            --bg-color: #1a1a1a;
            --text-color: #f0f0f0;
        }

        @media (prefers-color-scheme: dark) {
            body:not(.light-mode-forced) {
                background-color: var(--bg-color);
                color: var(--text-color);
                a {
                background-color: rgba(255,255,255,0);
                }
                a:hover {
                    background-color: rgba(255,255,255,0.15);
                }
            }
            
            
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            background-color: var(--bg-color);
            color: var(--text-color);
            overflow: hidden; 
            font-family: 'DM Sans', sans-serif;
            font-weight: 400; 
            height: 100vh;
            width: 100vw;
            user-select: none;
            -webkit-user-select: none;
            transition: background-color 0.5s ease;
        }

        #canvas-container {
            position: relative;
            width: 100%;
            height: 100%;
        }

        .word {
            position: absolute;
            /* Adjusted clamp for better fit on mobile with 3 lines */
            font-size: clamp(1.5rem, 4vw, 4.5rem); 
            line-height: 1;
            letter-spacing: clamp(-0.05rem, 1vw, -0.15rem);
            /* Color defaults to CSS variable --text-color */
            color: var(--text-color);
            cursor: default;
            white-space: nowrap;
            will-change: transform, color; /* Optimize for animation */
            padding: 5px; /* Tiny padding for hitbox buffer */
            
            /* Use CSS variables for position so we can combine translate and scale */
            transform: translate(var(--x), var(--y));
            
            /* Add transitions for smooth intro and physics movement */
            transition: 
                transform 0.12s ease, 
                color 0.3s ease, 
                opacity 800ms ease-out, 
                filter 800ms ease-out;
        }

        /* Initial Hidden/Blurred/Scaled State for Staggered Intro */
        .word.js-hidden-init {
            opacity: 0;
            transform: translate(var(--x), var(--y)) scale(0.4);
            filter: blur(5px);
        }

        a {
            text-decoration: none;
            color: var(--text-color);
            font-weight: 300;
            font-size: 14px;
            background-color: rgba(0,0,0,0);
            padding: 10px;
            display: inline-block;
            border-radius: 12px;
            transition: 150ms all cubic-bezier(0.25, 0, 0.5, 1);
            opacity: 0.5;
        }

        a:hover {
            text-decoration: none;
            background-color: rgba(0,0,0,0.1);
        }

        a:active {
            transform: scale(0.95);
        }

        #contact {
            position: fixed;
            top: 10px;
            right: 10px;
        }


    </style>
</head>
<body>

    <div id="canvas-container">
        <div class="word js-hidden-init" data-initial-color="#FFB7B2">Rafael</div>
        <div class="word js-hidden-init" data-initial-color="#FFDAC1">Marin</div>
        <div class="word js-hidden-init" data-initial-color="#E2F0CB">is</div>
        <div class="word js-hidden-init" data-initial-color="#B5EAD7">a</div>
        <div class="word js-hidden-init" data-initial-color="#F8B195">designer</div>
        <div class="word js-hidden-init" data-initial-color="#F67280">in</div>
        <div class="word js-hidden-init" data-initial-color="#C06C84">Brooklyn</div>
    </div>

    <script>
        // --- Configuration ---
        const PASTEL_COLORS = [
            '#FF6961', '#FFB347', '#FFD1DC', '#FFAC78', '#FDFD96', 
            '#C6E2E9', '#77DD77', '#836953', '#DDA0DD', '#B5EAD7', 
            '#FF69B4', '#00CED1', '#ADD8E6', '#E0BBE4', '#98FB98', 
            '#F08080', '#DAA520', '#FFA07A', '#B0C4DE', '#D8BFD8'
        ];
        
        const SPEED = 0.8; 
        const FADE_DURATION = 800;
        const STAGGER_DELAY = 150;
        const BASE_GAP = 15; // Base spacing in pixels
        
        const container = document.getElementById('canvas-container');
        const wordElements = Array.from(document.querySelectorAll('.word'));
        
        // Physics state
        let words = [];
        let isMoving = false;
        let shouldBeMoving = false; 
        let animationFrameId;
        let recenterFrameId;
        let startTimeout;

        // Media query object to track system preference
        const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');

        class Word {
            constructor(element) {
                this.el = element;
                this.width = 0;
                this.height = 0;
                this.x = 0;
                this.y = 0;
                this.targetX = 0; 
                this.targetY = 0;
                this.vx = 0;
                this.vy = 0;
                this.mass = 1; 
                this.colorIndex = 0;
                
                this.measure();
            }

            measure() {
                const rect = this.el.getBoundingClientRect();
                this.width = rect.width;
                this.height = rect.height;
            }

            setPosition(x, y) {
                this.x = x;
                this.y = y;
                // Set positions using CSS variables for combined transform
                this.el.style.setProperty('--x', `${x}px`);
                this.el.style.setProperty('--y', `${y}px`);
                // Ensure the transform property itself is only translate() during motion/final state
                this.el.style.transform = `translate(${x}px, ${y}px)`;
            }

            setColor(hex) {
                // Set color inline when moving
                this.el.style.color = hex;
            }
            
            clearColor() {
                // Clear inline style to let CSS variable take over (for paused state)
                this.el.style.color = ''; 
            }

            pickRandomColor() {
                let newColor = PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)];
                this.setColor(newColor);
            }
        }

        // --- Theme Management (System Preference Only) ---
        function applySystemTheme(isDark) {
            if (isDark) {
                document.body.classList.add('dark-mode');
            } else {
                document.body.classList.remove('dark-mode');
            }
            // Recenter words to ensure proper color/layout update after theme change
            if (!isMoving && words.length > 0) {
                 centerWords(false);
            }
        }
        
        function initializeThemeFromSystem() {
            // Apply initial theme based on system preference
            applySystemTheme(darkModeQuery.matches);

            // Add listener for future system theme changes
            darkModeQuery.addEventListener('change', (e) => {
                applySystemTheme(e.matches);
            });
        }
        // --- Initialization ---

        function init() {
            // 1. Initialize Theme based on system preference
            initializeThemeFromSystem();

            // 2. Create Word objects
            words = wordElements.map(el => new Word(el));
            
            // 3. Calculate and snap initial positions
            centerWords(true);

            // Auto-start is intended
            shouldBeMoving = true;

            // 4. Start the staggered fade-in animation
            startStaggeredIntro();
            
            // 5. Set Event Listeners
            window.addEventListener('resize', handleResize);
            // Click anywhere on the document to toggle animation
            document.addEventListener('click', toggleAnimation); 
            document.body.style.cursor = 'pointer'; 
        }
        
        function startStaggeredIntro() {
            words.forEach((word, index) => {
                const delay = index * STAGGER_DELAY;
                
                setTimeout(() => {
                    word.el.classList.remove('js-hidden-init');
                }, delay);
            });

            const totalIntroTime = (words.length - 1) * STAGGER_DELAY + FADE_DURATION + 500;
            
            startTimeout = setTimeout(() => {
                startAnimation();
            }, totalIntroTime);
        }

        function startAnimation() {
            if (isMoving) return;
            isMoving = true;
            assignRandomVelocities();
            
            // Clear colors when starting to move, allowing physics to set random colors
            words.forEach(word => word.clearColor()); 
            
            loop();
        }

        function toggleAnimation(e) {
            clearTimeout(startTimeout);

            if (isMoving) {
                // User wants to pause
                shouldBeMoving = false;
                isMoving = false;
                cancelAnimationFrame(animationFrameId); 
                
                centerWords(false); // Snap to centered layout and clear inline colors
                recenterLoop(); 
            } else {
                // User wants to restart
                shouldBeMoving = true;
                cancelAnimationFrame(recenterFrameId); 
                startAnimation();
            }
        }

        function centerWords(setImmediately = false) {
            if (words.length === 0) return;
            
            const screenW = window.innerWidth;
            const screenH = window.innerHeight;
            let dynamicGap;
            
            // When pausing, reset word color to default theme text color (by clearing inline style)
            if (!isMoving) {
                words.forEach(word => word.clearColor());
            }

            // Check breakpoint for layout mode
            if (screenW <= 1100) {
                // Smaller screens: space them out by -3% of viewport width
                const reduction = screenW * 0.03;
                dynamicGap = Math.max(0, BASE_GAP - reduction); 
                words.forEach(w => w.measure()); // Remeasure before layout calculation
                layoutStacked(screenW, screenH, dynamicGap);
            } else {
                // Large screens: space them out by +4% of viewport width
                const addition = screenW * 0.04;
                dynamicGap = BASE_GAP + addition;
                words.forEach(w => w.measure()); // Remeasure before layout calculation
                layoutSingleLine(screenW, screenH, dynamicGap);
            }

            if (setImmediately) {
                words.forEach(word => {
                    word.setPosition(word.targetX, word.targetY);
                });
            }
        }

        function layoutSingleLine(screenW, screenH, intendedGap) {
            const MAX_WIDTH_PERCENT = 0.75; 
            const maxAllowedWidth = screenW * MAX_WIDTH_PERCENT;
            const N = words.length;

            let sumWordWidths = words.reduce((acc, word) => acc + word.width, 0);
            
            // 1. Calculate the required width using the INTENDED (dynamic) gap
            let totalWidthRequired = sumWordWidths + (N - 1) * intendedGap;

            let finalGap = intendedGap;
            
            if (totalWidthRequired > maxAllowedWidth) {
                // If the INTENDED layout exceeds 75%, compress the gap to fit
                const totalGapSpace = maxAllowedWidth - sumWordWidths; 
                
                if (N > 1 && totalGapSpace > 0) {
                    // Redistribute the available gap space across N-1 gaps.
                    finalGap = totalGapSpace / (N - 1);
                } else {
                    // Emergency: If totalGapSpace is zero or negative, set gap to 0 
                    finalGap = Math.max(0, totalGapSpace / (N - 1));
                }
            } 
            
            // Calculate starting position based on the final width
            const finalContentWidth = sumWordWidths + (N - 1) * finalGap;
            const startX = (screenW - finalContentWidth) / 2;
            
            let currentX = startX;
            const centerY = (screenH / 2) - (words[0].height / 2);

            words.forEach(word => {
                word.targetX = currentX;
                word.targetY = centerY;
                // Use the calculated (or original) gap for positioning
                currentX += word.width + finalGap; 
            });
        }

        function layoutStacked(screenW, screenH, gap) {
            const lines = [
                [0, 1, 2, 3],             
                [4, 5, 6]            
            ];

            const lineHeightMultiplier = 1.2;
            const singleLineHeight = words[0].height;
            const totalBlockHeight = (lines.length * singleLineHeight) + ((lines.length - 1) * (singleLineHeight * 0.2)); 
            
            let currentY = (screenH - totalBlockHeight) / 2;

            lines.forEach((lineIndices) => {
                let lineWidth = 0;
                lineIndices.forEach((index, i) => {
                    lineWidth += words[index].width;
                    // Use the dynamic 'gap' here
                    if (i < lineIndices.length - 1) lineWidth += gap;
                });

                let currentX = (screenW - lineWidth) / 2;

                lineIndices.forEach((index) => {
                    const word = words[index];
                    word.targetX = currentX;
                    word.targetY = currentY;
                    currentX += word.width + gap;
                });

                currentY += singleLineHeight * lineHeightMultiplier;
            });
        }

        function assignRandomVelocities() {
            words.forEach(word => {
                const angle = Math.random() * Math.PI * 2;
                word.vx = Math.cos(angle) * SPEED;
                word.vy = Math.sin(angle) * SPEED;
            });
        }

        function handleResize() {
            isMoving = false;
            cancelAnimationFrame(animationFrameId);
            cancelAnimationFrame(recenterFrameId);
            clearTimeout(startTimeout); 
            
            requestAnimationFrame(() => {
                words.forEach(w => w.measure());
                centerWords(true); 
                
                if (shouldBeMoving) {
                    startTimeout = setTimeout(() => {
                        startAnimation();
                    }, 1000);
                }
            });
        }

        // --- Animation Loops ---

        function recenterLoop() {
            let allCentered = true;
            const ease = 0.12; 
            const snapDistance = 0.5; 

            words.forEach(word => {
                const dx = word.targetX - word.x;
                const dy = word.targetY - word.y;
                const distance = Math.sqrt(dx * dx + dy * dy);

                if (distance > snapDistance) {
                    allCentered = false;
                    word.x += dx * ease;
                    word.y += dy * ease;
                } else {
                    word.x = word.targetX;
                    word.y = word.targetY;
                }

                word.setPosition(word.x, word.y);
            });

            if (!allCentered) {
                recenterFrameId = requestAnimationFrame(recenterLoop);
            } else {
                cancelAnimationFrame(recenterFrameId);
            }
        }

        // --- Physics Engine ---

        function checkWallCollisions(word) {
            const screenW = window.innerWidth;
            const screenH = window.innerHeight;
            let hit = false;

            // Right Wall
            if (word.x + word.width >= screenW) {
                word.vx = -Math.abs(word.vx);
                word.x = screenW - word.width;
                hit = true;
            }
            // Left Wall
            else if (word.x <= 0) {
                word.vx = Math.abs(word.vx);
                word.x = 0;
                hit = true;
            }

            // Bottom Wall
            if (word.y + word.height >= screenH) {
                word.vy = -Math.abs(word.vy);
                word.y = screenH - word.height;
                hit = true;
            }
            // Top Wall
            else if (word.y <= 0) {
                word.vy = Math.abs(word.vy);
                word.y = 0;
                hit = true;
            }

            if (hit) {
                word.pickRandomColor(); 
            }
        }

        function checkObjectCollisions() {
            for (let i = 0; i < words.length; i++) {
                for (let j = i + 1; j < words.length; j++) {
                    const w1 = words[i];
                    const w2 = words[j];

                    if (rectIntersect(w1, w2)) {
                        resolveCollision(w1, w2);
                    }
                }
            }
        }

        function rectIntersect(r1, r2) {
            return !(r2.x > r1.x + r1.width || 
                     r2.x + r2.width < r1.x || 
                     r2.y > r1.y + r1.height || 
                     r2.y + r2.height < r1.y);
        }

        function resolveCollision(w1, w2) {
            // 1. Change Colors
            w1.pickRandomColor();
            w2.pickRandomColor();

            // 2. Resolve Overlap (prevent sticking)
            const c1x = w1.x + w1.width / 2;
            const c1y = w1.y + w1.height / 2;
            const c2x = w2.x + w2.width / 2;
            const c2y = w2.y + w2.height / 2;

            const dx = c2x - c1x;
            const dy = c2y - c1y;

            const overlapX = (w1.width/2 + w2.width/2) - Math.abs(dx);
            const overlapY = (w1.height/2 + w2.height/2) - Math.abs(dy);

            if (overlapX < overlapY) {
                // Collision on X axis
                if (dx > 0) { 
                    w1.x -= overlapX / 2;
                    w2.x += overlapX / 2;
                } else {
                    w1.x += overlapX / 2;
                    w2.x -= overlapX / 2;
                }
                const tempVx = w1.vx;
                w1.vx = w2.vx;
                w2.vx = tempVx;

            } else {
                // Collision on Y axis
                if (dy > 0) {
                    w1.y -= overlapY / 2;
                    w2.y += overlapY / 2;
                } else {
                    w1.y += overlapY / 2;
                    w2.y -= overlapY / 2;
                }
                const tempVy = w1.vy;
                w1.vy = w2.vy;
                w2.vy = tempVy;
            }
        }

        function loop() {
            if (!isMoving) return;

            words.forEach(word => {
                word.x += word.vx;
                word.y += word.vy;

                checkWallCollisions(word);
                
                word.el.style.transform = `translate(${word.x}px, ${word.y}px)`;
            });

            checkObjectCollisions();

            animationFrameId = requestAnimationFrame(loop);
        }

        // Start initialization after the fonts are ready
        document.fonts.ready.then(init);

    </script>
    <h1 style="display: none;">Rafael Marin is a designer based in Brooklyn, NY.</h1>
    <a id="contact" href="http://linkedin.com/in/rafaelmarin" target="_blank">Contact</a>
</body>
</html>