<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged gsp</title>
<link>https://coderlegion.com/tag/gsp</link>
<description>Powered by Question2Answer</description>
<item>
<title>Immersive Gallery: Mapping the MET API to Dynamic Soundscapes</title>
<link>https://coderlegion.com/11132/immersive-gallery-mapping-the-met-api-to-dynamic-soundscapes</link>
<description>&lt;p&gt;The last time I created an online museum experience, it kinda felt off. You walk from room to room, wait for things to load, and look at static images. It's not &lt;em&gt;bad&lt;/em&gt;, but it lacks vibes.&lt;/p&gt;
&lt;p&gt;So I decided to try a different approach: build something that felt less like a museum and more like a stream of consciousness. What if you could scrub through art history the way you scrub through a YouTube video?&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;Gogh With The Flow&lt;/strong&gt; :)&lt;/p&gt;
&lt;p&gt;&lt;iframe height=&quot;600&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;TIME-TRAVELING ART GALLERY&quot; src=&quot;https://codepen.io/notbigmuzzy/embed/XJKBdPw?default-tab=result&quot; frameborder=&quot;no&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The Metropolitan Museum of Art has an incredible &lt;a rel=&quot;nofollow&quot; href=&quot;https://metmuseum.github.io/&quot;&gt;Open Access API&lt;/a&gt;. It’s a treasure trove of human history. But APIs have rate limits, and fetching data for 1450, 1451, 1452... as fast as a user can scroll would instantly hit a wall (or just be incredibly slow).&lt;/p&gt;
&lt;p&gt;I wanted the experience to be fluid. No &quot;Loading...&quot; text. No spinners. Just flow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;To solve this, I employed a &quot;hybrid&quot; strategy:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pre-indexed &quot;Shards&quot;:&lt;/strong&gt; I scraped the collection to build lightweight JSON files for every year (e.g., &lt;code&gt;1889.json&lt;/code&gt;, &lt;code&gt;1450.json&lt;/code&gt;). These contain just the valid Object IDs for that year. They are tiny and load instantly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On-Demand Detail:&lt;/strong&gt; When the dialer settles on a year, the app grabs a random selection of IDs from that shard and fetches the images.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lazy High-Res:&lt;/strong&gt; We load optimized thumbnails first. Only when you click to &quot;enter&quot; a painting do we ping the API for the massive, high-resolution file, allowing you to zoom in and see the individual brushstrokes.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;GSAP&lt;/strong&gt; does the heavy lifting regarding animations. DOM manipulation is heavy but by using GSAP's &lt;code&gt;InertiaPlugin&lt;/code&gt; and careful tweening, the transition between years—fading out the old era and drifting in the new—happens without layout trashing or &quot;ghosting&quot; ( &lt;em&gt;as much as I could pull it off :)&lt;/em&gt; )&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example here showing how 'paralax' panels are handles with minimal perf impact&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// The &quot;Time Travel&quot; Engine
const updatePanels = () =&amp;gt; {
    // Calculate how fast we are scrubbing
    const scrollPos = dialer.scrollLeft;
    const delta = scrollPos - lastScrollPos;
    
    panels.forEach(panel =&amp;gt; {
        // Create parallax depth: background moves slower, 
        // the &quot;window&quot; moves counter to create focus
        let speedMultiplier = panel.className.includes('panel-window') ? -1.5 : 
                              panel.className.includes('panel-further') ? 0.25 : 0.5;

        // Apply momentum without layout thrashing
        let currentX = gsap.getProperty(panel, 'x') || 0;
        currentX += delta * speedMultiplier;

        // Infinite Loop Logic: 
        // If a panel goes off-screen, warp it to the other side
        const screenPos = panel.offsetLeft + currentX;
        if (screenPos &amp;gt; viewportWidth + 200) {
            currentX -= viewportWidth + 400;
        } else if (screenPos &amp;lt; -200) {
            currentX += viewportWidth + 400;
        }

        // Use transforms for 60fps performance
        gsap.set(panel, { x: currentX });
    });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;Visuals are only half the story. To truly sell the time travel aspect, the soundscape shifts with you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1400s:&lt;/strong&gt; Pre-Renaissance choral echoes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1600s:&lt;/strong&gt; Baroque strings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1800s:&lt;/strong&gt; Romantic orchestral swells&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1900s:&lt;/strong&gt; Early variations of impressionistic sound&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The audio player is set up so it cross-fades tracks as you traverse centuries and even handles &quot;radio&quot; logic, automatically queuing up a new random track from the current era when one finishes&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If it sounds interesting, go ahead and give it a spin. &lt;strong&gt;1889&lt;/strong&gt; is a good year ( but me personally, I'm into baroque heavily, didn't even realize it until debugging this thing :) )&lt;/p&gt;
&lt;p&gt;Live Demo - &lt;a rel=&quot;nofollow&quot; href=&quot;https://notbigmuzzy.github.io/goghwiththeflow/&quot;&gt;https://notbigmuzzy.github.io/goghwiththeflow/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Source Code - &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/notbigmuzzy/goghwiththeflow&quot;&gt;https://github.com/notbigmuzzy/goghwiththeflow&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Powered by the &lt;a rel=&quot;nofollow&quot; href=&quot;https://metmuseum.github.io/&quot;&gt;Metropolitan Museum of Art API&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/11132/immersive-gallery-mapping-the-met-api-to-dynamic-soundscapes</guid>
<pubDate>Thu, 05 Feb 2026 12:38:19 +0000</pubDate>
</item>
</channel>
</rss>