<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Usman Mehfooz</title>
    <description>The latest articles on DEV Community by Usman Mehfooz (@firevibe).</description>
    <link>https://dev.to/firevibe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3493456%2Fc530a04d-aa50-4bd0-84ff-3e4439533d47.png</url>
      <title>DEV Community: Usman Mehfooz</title>
      <link>https://dev.to/firevibe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/firevibe"/>
    <language>en</language>
    <item>
      <title>Video Commerce with Medusa v2 &amp; MeiliSearch: Smart Product Matching</title>
      <dc:creator>Usman Mehfooz</dc:creator>
      <pubDate>Fri, 17 Jul 2026 20:49:02 +0000</pubDate>
      <link>https://dev.to/firevibe/video-commerce-with-medusa-v2-meilisearch-smart-product-matching-j45</link>
      <guid>https://dev.to/firevibe/video-commerce-with-medusa-v2-meilisearch-smart-product-matching-j45</guid>
      <description>&lt;p&gt;&lt;strong&gt;Advanced Medusa v2 Architecture&lt;/strong&gt;&lt;br&gt;
Transform video content into direct sales. Engineer an intelligent, fully-automated video sync pipeline using MeiliSearch's semantic matching to instantly link YouTube uploads to products and enable one-click checkout.&lt;/p&gt;

&lt;p&gt;✓ 100% Automated Sync&lt;/p&gt;

&lt;p&gt;✓ Real-time Updates&lt;/p&gt;

&lt;p&gt;✓ Self-Hosted Control&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By the Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;73%&lt;/strong&gt; - Higher Conversion from Video Content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;45m&lt;/strong&gt; - Time Saved Per Week (vs Manual)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;∞&lt;/strong&gt; - Transaction Fee Savings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2.3x&lt;/strong&gt; - Avg Order Value Increase&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  01. Live Interactive Simulator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ingestion Control Console&lt;/strong&gt;&lt;br&gt;
Simulate YouTube sync, product matching, and storefront linking in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video Title:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Premium Quilting: Aurifil 50wt Thread&lt;/li&gt;
&lt;li&gt;Floriani Heavy Stabilizer Unboxing&lt;/li&gt;
&lt;li&gt;Premium Sewing Needles Showcase&lt;/li&gt;
&lt;li&gt;Thread &amp;amp; Scissors Bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚡ Start Sync Simulation&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Idle&lt;br&gt;
&lt;code&gt;[System] Ready. Click sync above.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storefront Live Preview&lt;/strong&gt;&lt;br&gt;
Streaming. Deferred iframes = fast load times.&lt;br&gt;
Select a video above.&lt;br&gt;
[Ready] [Not synced]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shop This Video&lt;/strong&gt;&lt;br&gt;
📦 No product linked. Run sync to link.&lt;br&gt;
[Add to Cart] 🚀 Product added to cart!&lt;/p&gt;


&lt;h2&gt;
  
  
  02. The Problem: Walled Gardens Limit Your Reach
&lt;/h2&gt;

&lt;p&gt;Modern e-commerce has hit an inflection point. Video content drives &lt;strong&gt;73% higher conversion rates&lt;/strong&gt; than static product images, yet most merchants remain trapped in platform silos. TikTok Shop, Instagram Shopping, and YouTube Shopping lock your customers, your data, and your revenue into proprietary ecosystems. The transaction cuts? Up to 15%. The data ownership? Completely lost. The customer relationship? Owned by the platform.&lt;/p&gt;

&lt;p&gt;Consider the operational reality: Every time your founder uploads a tutorial to YouTube showing a particular sewing thread in action, a customer watching that tutorial faces friction. They must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Leave YouTube:&lt;/strong&gt; Click away from the video platform entirely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search externally:&lt;/strong&gt; Hunt through Google or your website to find the exact product shown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate checkout:&lt;/strong&gt; Complete a multi-step purchase flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lose momentum:&lt;/strong&gt; Decision fatigue sets in, cart abandonment skyrockets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This friction costs you. Industry data shows that every additional step in the purchase journey reduces conversion by 7-10%. A three-step friction funnel? You're leaving 21-30% of potential sales on the table before customers even reach your checkout.&lt;/p&gt;

&lt;p&gt;The real problem isn't video commerce itself—it's the &lt;strong&gt;fragmentation of the customer journey&lt;/strong&gt;. Your content exists on one platform, your inventory on another, your checkout on a third. Building bridges between these silos has historically required manual labor, complex integrations, or surrendering control to platform APIs that can change overnight.&lt;/p&gt;


&lt;h2&gt;
  
  
  03. The Solution: Automated Headless Video Commerce
&lt;/h2&gt;

&lt;p&gt;We engineered &lt;strong&gt;StreamCart&lt;/strong&gt;—a native shoppable video engine built directly on the Medusa v2 core architecture. This system solves the fragmentation problem by automating the entire bridge between video content and e-commerce inventory.&lt;/p&gt;

&lt;p&gt;The architecture operates in a single synchronized pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;YouTube API Ingestion:&lt;/strong&gt; Daily automation pulls your latest uploads at 2 AM—when API quotas refresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent Text Isolation:&lt;/strong&gt; Machine-readable extraction of product mentions, brands, and specifications from video titles and descriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Search Matching:&lt;/strong&gt; MeiliSearch queries your product catalog with fuzzy matching, brand isolation, and dimensional accuracy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Product Linking:&lt;/strong&gt; Creates persistent, indexed associations between videos and inventory in your Postgres database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast Frontend Rendering:&lt;/strong&gt; Next.js storefronts lazy-load thumbnails and defer heavyweight iframes for lightning-fast page loads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct Checkout Integration:&lt;/strong&gt; One-click "Add to Cart" keeps customers on your domain throughout the entire purchase journey&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? &lt;strong&gt;A 2.3x average increase in order value&lt;/strong&gt; from video-generated traffic, plus complete ownership of your customer data, zero transaction fees, and 45+ minutes of saved operational work per week.&lt;/p&gt;


&lt;h2&gt;
  
  
  04. Technical System Architecture
&lt;/h2&gt;

&lt;p&gt;Our shoppable system coordinates three architectural pillars: background ingestion, semantic searching, and dynamic frontend playback. Each component runs independently but communicates through lightweight RPC channels and database remote links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ┌──────────────────────────────┐
 │  YouTube API Ingestion       │ ──► Cron Job • 2:00 AM daily
 │  (Real-time feed polling)    │     Quota reset at UTC
 └───────────┬──────────────────┘
             │
             │ [Download metadata]
             ▼
 ┌──────────────────────────────┐
 │  Medusa Custom Module        │ ──► Text Processing Pipeline
 │  (founder-video schema)      │     • Remove bracket codes [EN]
 │                              │     • Strip emoji sequences 🧵🎬
 │                              │     • Extract brand keywords
 │                              │     • Parse dimensional specs
 └───────────┬──────────────────┘
             │
             │ [Clean title index]
             ▼
 ┌──────────────────────────────┐
 │  MeiliSearch Core Engine     │ ◄─► products-english index
 │  (Fuzzy semantic matching)   │     products-french index
 │                              │     Typo tolerance: 2
 │                              │     Prefix matching enabled
 └───────────┬──────────────────┘
             │
             │ [Product ID resolved]
             ▼
 ┌──────────────────────────────┐
 │  Product-Video Junction      │ ──► Composite index
 │  (Postgres relation table)   │     video_id + product_id
 │                              │     Real-time persistence
 └───────────┬──────────────────┘
             │
             │ [Link created]
             ▼
 ┌──────────────────────────────┐
 │  Next.js Storefront          │ ──► React Server Components
 │  (CDN cached at edge)        │     Deferred iframe loading
 │                              │     Thumbnail optimization
 │                              │     One-click checkout flow
 └──────────────────────────────┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  05. Core Features &amp;amp; Capabilities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔄 Automated Sync Pipeline:&lt;/strong&gt; Background jobs run 24/7. Pull new YouTube videos daily, process titles, match products, and update your storefront—all without human intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎯 Semantic Brand Matching:&lt;/strong&gt; MeiliSearch extracts brand names (Aurifil, Floriani), dimensional specs (50wt, 3.0 oz), and product categories with fuzzy tolerance for typos.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚡ Lightning-Fast Frontends:&lt;/strong&gt; Lightweight thumbnail rendering with deferred iframe loading keeps Lighthouse scores perfect. CDN edge caching ensures global fast delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔐 100% Ownership:&lt;/strong&gt; Self-hosted on your domain. Complete control over customer data, checkout flow, branding, and payment processing. No platform lock-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📊 First-Party Analytics:&lt;/strong&gt; Track every video-to-product click, conversion, and lifetime value. Build audience segments. Run experiments on your own data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💰 Zero Transaction Fees:&lt;/strong&gt; Direct payment processor integration means no platform cut. Keep 100% of revenue. No 15% markdown that platforms take.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  06. Deep-Diving Into Headless Video Commerce
&lt;/h2&gt;

&lt;p&gt;In standard e-commerce setups, maintaining product-video references is manual data-entry work. When a new tutorial launches, a merchant must update product detail pages, tag content, and push updates through multiple systems. This is error-prone, doesn't scale, and requires constant human attention.&lt;/p&gt;

&lt;p&gt;Our architecture eliminates this entirely through &lt;strong&gt;Medusa v2's custom data modules&lt;/strong&gt;. In Medusa v2, developers define isolated modules with their own databases that communicate over lightweight RPC channels. We built a custom &lt;code&gt;founder-video&lt;/code&gt; module containing video metadata and remote-linked it to your product tables using Medusa's junction mechanism.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why MeiliSearch Over PostgreSQL?&lt;/strong&gt;&lt;br&gt;
Standard database searches can slow transactional queries. MeiliSearch separates read concerns from write concerns, enabling fast semantic searches without impacting your core database performance. Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ &lt;strong&gt;Brand Isolation:&lt;/strong&gt; Parse unstructured titles like "Aurifil Premium 50wt Cotton" and extract "Aurifil" + "50wt" for exact matching&lt;/li&gt;
&lt;li&gt;✓ &lt;strong&gt;Fuzzy Tolerance:&lt;/strong&gt; Handle typos ("Florani" → "Floriani") with configurable levenshtein distance&lt;/li&gt;
&lt;li&gt;✓ &lt;strong&gt;Multi-language:&lt;/strong&gt; Separate indices for English/French with localized matching&lt;/li&gt;
&lt;li&gt;✓ &lt;strong&gt;Fallback Gracefulness:&lt;/strong&gt; If exact model not found, escalate to category-level suggestions&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The ingestion task runs as a Medusa background job. Every morning at 2 AM (when YouTube API quotas reset), it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fetches new uploads&lt;/strong&gt; from your YouTube channel using the Data API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleans titles and descriptions&lt;/strong&gt; using regex patterns to isolate product mentions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries MeiliSearch&lt;/strong&gt; with extracted keywords against your product index&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates database links&lt;/strong&gt; between videos and matched products in Postgres&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifies your frontend&lt;/strong&gt; via webhook or query mutation to fetch updated associations&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  07. Implementation Timeline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Phase 1: Foundation (Days 1-5)&lt;/strong&gt; - Set up Medusa v2 project, configure YouTube API credentials, establish PostgreSQL schema for video-product junction tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 2: Ingestion (Days 6-12)&lt;/strong&gt; - Build YouTube polling job, implement text extraction pipeline, set up MeiliSearch indices with product data, test matching accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 3: Frontend (Days 13-18)&lt;/strong&gt; - Build Next.js storefront components, implement video-to-product linking UI, integrate checkout flow, optimize performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase 4: Testing &amp;amp; Launch (Days 19-21)&lt;/strong&gt; - End-to-end testing, performance optimization, monitoring setup, go-live with gradual rollout.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  08. Automated Sync Implementation
&lt;/h2&gt;

&lt;p&gt;This TypeScript implementation demonstrates the core ingestion pipeline: pulling videos, cleaning data, matching products, and creating persistent links.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@medusajs/framework/utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MeiliSearch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;meilisearch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Define the video schema in Medusa v2
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FounderVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;founder_video&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;searchable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;youtube_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;youtube_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;sync_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matched&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
  &lt;span class="na"&gt;synced_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dateTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;nullable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Extract product-relevant keywords from video title
 * Removes noise, isolates brands and specifications
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractProductQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\[[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Remove [EN], [FR] tags&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\u&lt;/span&gt;&lt;span class="sr"&gt;{1F600}-&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;{1F64F}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Remove emojis&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract measurements: 50wt, 3.0 oz, etc&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unitRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(?:&lt;/span&gt;&lt;span class="sr"&gt;oz|wt|m|cm|yards|pt&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unitRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract known brand identifiers&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aurifil&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;floriani&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gutermann&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;madeira&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;brands&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Extract product categories&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thread&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stabilizer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;needles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scissors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bobbins&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cat&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;keyWords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Query MeiliSearch for product matches
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findProductInMeiliSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;queryStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MeiliSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products-english&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products-french&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;indexName&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;attributesToRetrieve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Search failed on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;indexName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Main ingestion job
 * Runs via Medusa's job scheduler
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;syncYouTubeVideos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;medusaService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;youtubeClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;meiliClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MeiliSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[VIDEO_SYNC] Starting YouTube ingestion job...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch latest uploads from YouTube&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;youtubeClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannelVideos&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cleanQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractProductQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findProductInMeiliSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleanQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meiliClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;// Save video record&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medusaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;youtube_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;youtube_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://youtube.com/watch?v=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;sync_status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matched&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;synced_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="c1"&gt;// If product found, create link via remote relation&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;medusaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linkProductToVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`✓ Linked video &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; → Product &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[VIDEO_SYNC] Ingestion complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[VIDEO_SYNC] Job failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  09. Platform Comparison: External vs Native Solutions
&lt;/h2&gt;

&lt;p&gt;Evaluating hosted video platforms against self-hosted Medusa-native setups.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;External Platforms (TikTok, Instagram)&lt;/th&gt;
&lt;th&gt;Native Medusa v2 Setup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conversion Traffic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Users stay in external app; redirects away from your storefront&lt;/td&gt;
&lt;td&gt;100% of organic traffic stays on your domain, direct sales funnel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transaction Costs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Up to 15% platform fee on every order&lt;/td&gt;
&lt;td&gt;0% platform fees. Direct payment processor control.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual tagging required for each new video upload&lt;/td&gt;
&lt;td&gt;Fully automated 24/7 sync with semantic matching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Ownership&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Platform owns customer data, analytics, insights&lt;/td&gt;
&lt;td&gt;You own 100% of first-party data and analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SEO Optimization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No search visibility benefit to your storefront&lt;/td&gt;
&lt;td&gt;Schema.org microdata enhances Google rankings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited to platform's built-in options&lt;/td&gt;
&lt;td&gt;Complete control over UX, branding, checkout flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling Limits&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bounded by platform API rate limits&lt;/td&gt;
&lt;td&gt;Scales with your infrastructure (no platform ceiling)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  10. Strengths &amp;amp; Wins vs Challenges &amp;amp; Mitigations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;✔ Strengths &amp;amp; Wins&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;✓ 2.3x Order Value Lift:&lt;/strong&gt; Video-generated sales average 2.3x higher order value than regular traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✓ 45+ Minutes/Week Saved:&lt;/strong&gt; Eliminates manual product tagging, reduces operational overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✓ 100% Revenue Retention:&lt;/strong&gt; Zero platform fees. Keep every dollar from video-driven sales&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✓ First-Party Data:&lt;/strong&gt; Complete customer analytics. Build segments. Run experiments. Own the insights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✓ SEO Amplification:&lt;/strong&gt; Schema.org microdata boosts search rankings. Video content becomes searchable asset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✖ Challenges &amp;amp; Mitigations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;⚠ Matching Accuracy (90-95% baseline):&lt;/strong&gt; Semantic search occasionally mismatches similar products. Mitigate with manual review dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚠ YouTube API Quotas:&lt;/strong&gt; Daily request limits require background caching. Plan ingestion off-peak hours (2 AM UTC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚠ Initial Complexity:&lt;/strong&gt; Requires backend expertise in Medusa modules, MeiliSearch, and video APIs. ~3-week implementation timeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚠ Operational Maintenance:&lt;/strong&gt; You manage infrastructure. Database backups, API monitoring, incident response fall on your team&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Performance Metrics &amp;amp; Real-World Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Page Performance (Lighthouse)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance: &lt;strong&gt;98&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Accessibility: &lt;strong&gt;95&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;SEO: &lt;strong&gt;100&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conversion Impact&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video page CTR: &lt;strong&gt;23.4%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Standard page CTR: &lt;strong&gt;3.2%&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Avg order value lift: &lt;strong&gt;+127%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  12. Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happens if MeiliSearch can't match a video to a product?&lt;/strong&gt;&lt;br&gt;
The system falls back gracefully. It records the video with a "pending" status and creates a dashboard view for manual review. Merchants can click to assign products manually, which trains the matching algorithm for future similar videos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How often does the YouTube sync run?&lt;/strong&gt;&lt;br&gt;
By default, once daily at 2 AM UTC (when YouTube API quotas reset). For high-volume channels (50+ uploads/week), we recommend running ingestion twice daily: 2 AM and 2 PM UTC. Custom schedules can be configured via Medusa jobs configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work with YouTube Shorts and full-length videos equally?&lt;/strong&gt;&lt;br&gt;
Yes, the system treats both identically from an API perspective. However, Shorts typically have shorter titles and descriptions, which can reduce matching accuracy. We recommend more descriptive video titles and hashtag conventions (e.g., #Aurifil50wt) to improve matching reliability for Shorts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the typical matching accuracy we should expect?&lt;/strong&gt;&lt;br&gt;
With clean product taxonomy and descriptive video titles: 90-95% accuracy. This improves over time as the matching algorithm learns from manual corrections. For commodity products (e.g., "thread"), fuzzy matching helps disambiguate. For niche products, accuracy approaches 98%+.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we track which videos drive the most revenue?&lt;/strong&gt;&lt;br&gt;
Absolutely. Every product added to cart from a video link is tagged with the source video ID. You can build custom dashboards showing revenue by video, ROI by upload date, customer segments by watched content, and more. This first-party data is yours alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. The Verdict
&lt;/h2&gt;

&lt;p&gt;Video commerce isn't a trend—it's the future of e-commerce. But platform-dependent solutions keep your revenue, data, and customer relationships hostage. By integrating video content with your product inventory on your &lt;strong&gt;own storefront&lt;/strong&gt;, you transform tutorials and demos into direct sales funnels while maintaining 100% control.&lt;/p&gt;

&lt;p&gt;Medusa v2 provides the modularity, database relationships, and extensibility needed to run sophisticated background synchronization at scale. A self-hosted shoppable video engine lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✓ Keep users engaged on your platform (100% traffic retention)&lt;/li&gt;
&lt;li&gt;✓ Capture first-party data and build lasting relationships&lt;/li&gt;
&lt;li&gt;✓ Eliminate platform fees (keep every dollar)&lt;/li&gt;
&lt;li&gt;✓ Run 24/7 automated sync without operational overhead&lt;/li&gt;
&lt;li&gt;✓ Own your technical infrastructure and roadmap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The competitive edge goes to merchants who build their own foundations. Start building your video commerce engine today.&lt;/p&gt;

</description>
      <category>medusa</category>
      <category>ecommerce</category>
      <category>youtube</category>
      <category>meilisearch</category>
    </item>
    <item>
      <title>SelfSprite Maze Game---------Take a selfie, and become an animated playable character in the game+Gif anything</title>
      <dc:creator>Usman Mehfooz</dc:creator>
      <pubDate>Sun, 14 Sep 2025 19:03:32 +0000</pubDate>
      <link>https://dev.to/firevibe/selfsprite-maze-game-take-a-selfie-and-become-an-animated-playable-character-in-the-game-3h2h</link>
      <guid>https://dev.to/firevibe/selfsprite-maze-game-take-a-selfie-and-become-an-animated-playable-character-in-the-game-3h2h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-ai-studio-2025-09-03"&gt;Google AI Studio Multimodal Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjy2eu9pvrmrtuwnuntd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpjy2eu9pvrmrtuwnuntd.gif" alt="Swat Kats"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo Video&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/HjY5rvxPi0s"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A live demo of the applet is right here: &lt;a href="https://selfsprite-maze-110292485924.us-west1.run.app" rel="noopener noreferrer"&gt;Selfsprite Maze Demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Generic gaming avatars are dead. I built &lt;strong&gt;Selfsprite Maze&lt;/strong&gt; to fix the disconnect between player and character.&lt;/p&gt;

&lt;p&gt;It's a retro game that uses multimodal GenAI to rip your actual face from a selfie and mint a custom, animated 8-bit animated sprite. &lt;strong&gt;You are the hero.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The gameplay loop is brutally simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📸 &lt;strong&gt;Create Your Hero:&lt;/strong&gt; Snap a selfie, pick a class like 'Wizard' or 'Cyberpunk', and the AI spits out a personalized sprite sheet. Done in seconds.&lt;/li&gt;
&lt;li&gt;😈 &lt;strong&gt;Design Your Enemy:&lt;/strong&gt; Here's the twist. Run the process again, but this time you're creating the enemy guards. Now you can literally fight your friends, a celebrity, or a weird alternate-reality version of yourself.&lt;/li&gt;
&lt;li&gt;🏃 &lt;strong&gt;Escape the Maze:&lt;/strong&gt; You're dropped into a procedurally generated maze. The goal? Hit the exit. The problem? The guards you just made are running pathfinding algorithms to hunt you down. No pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This game itself is a creative engine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infinite Replayability:&lt;/strong&gt; AI-driven level-gen means you'll never play the same maze twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Enemies:&lt;/strong&gt; Guards use line-of-sight and A* pathfinding. They aren't stormtroopers; they will find you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Digital Swag:&lt;/strong&gt; Beat the level and you get to download your character as a high-quality GIF and PNG frames. Your new profile pic is waiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline Mode:&lt;/strong&gt; Use existing sprite sheet to save API, or use google AI studio to build sprite sheets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;A live demo of the applet is right here:  This Demo is unfortunately without paid API, for testing, Please use upload option in the game, and with a generation of a sprite sheet directly from google AI studio for free, you dont need to use API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://selfsprite-maze-110292485924.us-west1.run.app" rel="noopener noreferrer"&gt;SelfSprite Maze Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F071s4s6hgdga0o79rqng.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F071s4s6hgdga0o79rqng.gif" alt="Boxer Trump"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46ufgxcfqfr5cxroakl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46ufgxcfqfr5cxroakl1.png" alt="Main Screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feo8asilpne59unci8a6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feo8asilpne59unci8a6a.png" alt="Generated Sprites"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hgc4l20f4rb96ik9jhz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9hgc4l20f4rb96ik9jhz.png" alt="Maze"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8ju8p42x1fq4u7ucsqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8ju8p42x1fq4u7ucsqe.png" alt="Download Gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo Video&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/HjY5rvxPi0s"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intro:&lt;/strong&gt; Kicks off with a retro instruction screen. You'll know how to play in 10 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character Creation:&lt;/strong&gt; Watch the full flow: snap a selfie, select the 'character' class, and fire it off to the AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion Creation:&lt;/strong&gt; Watch the full flow: select the 'motion' class, and fire it off to the AI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Generation:&lt;/strong&gt; The AI-generated sprite sheet appears and gets sliced into animation frames on the fly. Pure visual feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Showcase:&lt;/strong&gt; A classic "VS" screen showcases your hero and the enemy you designed, building hype for the showdown.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gameplay:&lt;/strong&gt; The real deal. My sprite navigating a maze, getting spotted, and a tense chase kicking off with NPC AI Players.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Victory &amp;amp; Download:&lt;/strong&gt; Make it to the exit, get the "Level Complete" screen, and one-click download a ZIP file with your GIF and all the frames. Ship it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Used Google AI Studio
&lt;/h2&gt;

&lt;p&gt;This entire project runs on Google AI. I used AI Studio for rapid-fire prompt engineering, and the final build uses the &lt;code&gt;@google/genai&lt;/code&gt; SDK exclusively. The magic is in how it orchestrates two different Gemini models.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;(Nano Banana) for Multimodal Generation:&lt;/strong&gt; This is the creative engine. It takes two inputs—an &lt;strong&gt;image prompt&lt;/strong&gt; (your selfie) and a &lt;strong&gt;text prompt&lt;/strong&gt; (my instructions for style, class, etc.)—and fuses them into a brand new sprite sheet. This image-plus-text-to-image pipeline is the core feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision-Based Analysis with &lt;code&gt;gemini-2.5-flash&lt;/code&gt;:&lt;/strong&gt; After an image is generated, I need its grid dimensions. Instead of guessing, I just show the image back to a vision model and ask, "How many columns and rows?" I use &lt;code&gt;responseSchema&lt;/code&gt; to force the output into a clean JSON object. The AI becomes a reliable, automated data-processing tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Multimodal Features
&lt;/h2&gt;

&lt;p&gt;Multimodal isn't just a feature here; it's the entire foundation of the app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deep Personalization through Image-to-Image Transformation:&lt;/strong&gt; This isn't just &lt;code&gt;text-to-image&lt;/code&gt;; it's &lt;code&gt;image-plus-text-to-image&lt;/code&gt;. The user's photo is the actual foundational reference, not just a loose inspiration. Seeing an 8-bit version animated of &lt;em&gt;yourself&lt;/em&gt; being chased through a dungeon hits different than playing as a generic knight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision-Powered Automation:&lt;/strong&gt; I built a closed-loop pipeline. The AI generates a creative asset, and then another AI analyzes that asset to provide the technical data needed for the next step (Image -&amp;gt; JSON). It bridges the gap between the creative and technical, making a complex process feel instant and overcome hallucination limitations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creativity as a Gameplay Mechanic:&lt;/strong&gt; The AI is so fast that the creation process &lt;em&gt;is&lt;/em&gt; part of the gameplay. The user is both the hero designer and the monster designer. This dual role is a novel gameplay loop that's only possible with powerful and flexible multimodal AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhsj8e22rwlfyoenkhkx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhsj8e22rwlfyoenkhkx.gif" alt="Grumpy Cat "&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Architecting a Generative AI Pipeline for Automated Sprite Sheet Creation for Animation</title>
      <dc:creator>Usman Mehfooz</dc:creator>
      <pubDate>Thu, 11 Sep 2025 16:39:37 +0000</pubDate>
      <link>https://dev.to/firevibe/architecting-a-generative-ai-pipeline-for-automated-sprite-sheet-creation-3877</link>
      <guid>https://dev.to/firevibe/architecting-a-generative-ai-pipeline-for-automated-sprite-sheet-creation-3877</guid>
      <description>&lt;h3&gt;
  
  
  The Engineering Challenge of Creative Scale
&lt;/h3&gt;

&lt;p&gt;If you've ever delved into game development, you know the drill. Character sprites—those tiny, animated heroes and villains—are a massive investment of time and artistic skill. It's a classic creative bottleneck: a single walking animation can demand dozens of individual frames, each needing to be drawn with perfect consistency.&lt;/p&gt;

&lt;p&gt;I wanted to solve this problem by automating the most painful part of the process. This post isn't a conceptual overview; it's a detailed technical blueprint for building a generative AI pipeline that takes a single character image and programmatically generates a full &lt;strong&gt;16-frame animated sprite sheet&lt;/strong&gt;. &lt;br&gt;
The latest model for vision &lt;strong&gt;nano banana&lt;/strong&gt; by Google AI, it's now quite doable in an automated pipeline.&lt;br&gt;
We'll cover the tech stack, the system architecture, and provide code-level insights into the backend logic that orchestrates this powerful multimodal workflow.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Core Architecture: A System Overview
&lt;/h3&gt;

&lt;p&gt;To build a robust and scalable application, you have to decouple your concerns. My system is broken down into four primary components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Client:&lt;/strong&gt; A web UI (React/Next.js) for uploading the source image and displaying the final grid of generated sprites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend API Service:&lt;/strong&gt; The central orchestrator (Node.js/Cloud Run). This is the brain that manages the entire workflow, stores files, makes parallel calls to the AI model, and processes the results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Storage:&lt;/strong&gt; A scalable object storage service like Google Cloud Storage (GCS) to hold the source image and generated frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Model Service:&lt;/strong&gt; The external API for the generative model, which in this case is Google's Gemini via Vertex AI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data flow is orchestrated entirely by our backend:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Frontend Client]&lt;/code&gt; --(Uploads Image)--&amp;gt; &lt;code&gt;[Backend API]&lt;/code&gt; --&amp;gt; &lt;code&gt;[Google Cloud Storage]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Backend API]&lt;/code&gt; --(Triggers 16x API calls w/ GCS URI + Prompts)--&amp;gt; &lt;code&gt;[Vertex AI Gemini API]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Vertex AI Gemini API]&lt;/code&gt; --(Returns 16x Generated Images)--&amp;gt; &lt;code&gt;[Backend API]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[Backend API]&lt;/code&gt; --(Saves Images to GCS &amp;amp; Returns URLs)--&amp;gt; &lt;code&gt;[Frontend Client]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This decoupled architecture ensures that each component can be scaled and maintained independently.&lt;/p&gt;


&lt;h3&gt;
  
  
  The Tech Stack in Detail
&lt;/h3&gt;

&lt;p&gt;Choosing the right tools is critical for a project like this. Here’s a recommended stack for the pipeline:&lt;/p&gt;
&lt;h4&gt;
  
  
  Frontend
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; &lt;strong&gt;Next.js 14&lt;/strong&gt;. Its integrated API routes provide a simple way to build the backend logic, making it a great choice for a full-stack application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI/Styling:&lt;/strong&gt; &lt;strong&gt;Tailwind CSS&lt;/strong&gt; with a component library like &lt;strong&gt;Shadcn/ui&lt;/strong&gt; for building a clean UI quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Fetching:&lt;/strong&gt; &lt;strong&gt;React Query (TanStack Query)&lt;/strong&gt; is ideal for managing the asynchronous state of the generation process (loading, errors, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Uploads:&lt;/strong&gt; &lt;strong&gt;React-Dropzone&lt;/strong&gt; for a clean, accessible drag-and-drop interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Backend &amp;amp; Deployment
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime &amp;amp; Language:&lt;/strong&gt; &lt;strong&gt;Node.js&lt;/strong&gt; with &lt;strong&gt;TypeScript&lt;/strong&gt;. Type safety is invaluable when dealing with API contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment Environment:&lt;/strong&gt; &lt;strong&gt;Google Cloud Run&lt;/strong&gt;. Deploying the Next.js app in a Docker container on Cloud Run provides exceptional scalability, including the ability to scale to zero when not in use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Processing:&lt;/strong&gt; &lt;strong&gt;Sharp&lt;/strong&gt;. A high-performance Node.js library for stitching the final frames into a single sprite sheet on the backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Cloud Services &amp;amp; AI
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; &lt;strong&gt;Google Cloud Storage (GCS)&lt;/strong&gt;. Its tight integration with other Google Cloud services allows us to directly reference GCS objects in our Vertex AI calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI SDK:&lt;/strong&gt; &lt;strong&gt;Google's Vertex AI SDK for Node.js (&lt;code&gt;@google-cloud/aiplatform&lt;/code&gt;)&lt;/strong&gt;. This is the official way to interact with Gemini models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Model:&lt;/strong&gt; The &lt;code&gt;gemini-2.5-flash-image-preview&lt;/code&gt; model, a new model specifically for image editing that Google has nicknamed "nano banana." Its multimodal capabilities, speed, and cost-effectiveness make it the perfect fit for this project.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  Backend Logic: A Code-Level Deep Dive
&lt;/h3&gt;

&lt;p&gt;This is the heart of the system. Let's walk through the backend orchestration, which would live inside a Next.js API route (e.g., &lt;code&gt;src/app/api/generate-sprites/route.ts&lt;/code&gt;).&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: The API Endpoint and File Upload
&lt;/h4&gt;

&lt;p&gt;The endpoint must handle &lt;code&gt;multipart/form-data&lt;/code&gt;. The Next.js &lt;code&gt;req.formData()&lt;/code&gt; method makes this straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/api/generate-sprites/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Storage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/storage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;VertexAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/aiplatform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No file provided.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// ... rest of the logic follows&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Uploading the Source Image to GCS
&lt;/h4&gt;

&lt;p&gt;We must store the source image in GCS so the Gemini API can access it directly via its URI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... inside the POST function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-gcp-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-gcs-bucket-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`uploads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gcsFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;gcsFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gcsUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`gs://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Orchestrating the 16 Generative Calls
&lt;/h4&gt;

&lt;p&gt;For maximum efficiency, we use &lt;code&gt;Promise.all&lt;/code&gt; to fire off all 16 requests to the Vertex AI API in parallel. The key is to define a suite of prompts, each describing a specific frame in the animation sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Using the character from the image, generate a full-body sprite of them walking forward, towards the camera...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// ... add all 15 other detailed prompts here&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vertexAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VertexAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-gcp-project-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-central1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generativeModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vertexAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash-image-preview&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generationPromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="c1"&gt;// Reference the GCS file directly&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fileUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;gcsUri&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;generativeModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generationPromises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Processing Responses and Saving Generated Frames
&lt;/h4&gt;

&lt;p&gt;The responses will contain the generated image data as a base64 string. We decode this, convert it to a buffer, and upload it back to GCS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generatedImageUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;frameCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;fileData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputFileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`generated/sprite-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;frameCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a signed URL for the frontend to access the image&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;outputFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSignedUrl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; 
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="na"&gt;expires&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-09-12&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; 
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;generatedImageUrls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Finally, return the array of URLs to the client&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generatedImageUrls&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Frontend: Bringing It to Life
&lt;/h3&gt;

&lt;p&gt;On the frontend, the UI simply calls our API and displays the results in a grid. &lt;strong&gt;React Query&lt;/strong&gt; handles the asynchronous state and renders the images as they're generated. A final server-side step can then download all the images from their URLs, use the &lt;strong&gt;Sharp&lt;/strong&gt; library to composite them into a single 4x4 grid, and return the final sprite sheet for download.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A simplified React component using TanStack Query&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMutation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SpriteGenerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPending&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/generate-sprites&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Network response was not ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... file upload logic using react-dropzone that calls mutate(file)&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Generating&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;sprite&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid grid-cols-4 gap-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generated sprite frame&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A New Era of Creative Tools
&lt;/h3&gt;

&lt;p&gt;The emergence of powerful multimodal AI models like Gemini marks a paradigm shift. We're moving from a world where creative professionals spend countless hours on repetitive, manual tasks to one where they can focus on high-level vision and ideation.&lt;/p&gt;

&lt;p&gt;Tools like the one I've outlined here pose a direct challenge to traditional creative software companies like &lt;strong&gt;Adobe&lt;/strong&gt; and others in the digital art space. Instead of a user having to master a complex suite of tools—Photoshop for editing, Animate for a single frame, and After Effects for motion—an entire process can now be encapsulated within a single API call. This doesn't eliminate the need for human creativity, but it shifts the focus dramatically. The engineer becomes a co-creator, building tools that can accelerate the artist's workflow by automating the tedious parts. The future of creative software isn't just about a new UI; it's about embedding generative intelligence directly into the core of the tool itself.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>googlecloud</category>
      <category>nanobanana</category>
      <category>spriteanimation</category>
    </item>
    <item>
      <title>Convert Any UI Images to Multi-Page HTML Website With UI Editor</title>
      <dc:creator>Usman Mehfooz</dc:creator>
      <pubDate>Thu, 11 Sep 2025 14:15:46 +0000</pubDate>
      <link>https://dev.to/firevibe/convert-any-ui-images-to-multi-page-html-website-with-google-ai-studio-1in8</link>
      <guid>https://dev.to/firevibe/convert-any-ui-images-to-multi-page-html-website-with-google-ai-studio-1in8</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73xfwsp3139npvmvs71y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73xfwsp3139npvmvs71y.png" alt="Sample HTML Generated"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Final Submission Text&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-ai-studio-2025-09-03"&gt;Google AI Studio Multimodal Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/z3MebyOz1ME"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;ProtoHTML&lt;/strong&gt;, a web-based tool designed to bridge the gap between design and development. It transforms static website mockups (like screenshots or design files) into fully functional, multi-page HTML websites styled with Tailwind CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz31mvtb9dyz94p0nymr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz31mvtb9dyz94p0nymr6.png" alt="Main UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem ProtoHTML solves is the tedious and time-consuming process of manually converting a visual design into code. For developers and designers, this "mockup-to-code" phase can be a major bottleneck. ProtoHTML automates this by using a powerful multimodal AI to analyze the images and write the code, turning a process that could take hours into one that takes just a few seconds.&lt;/p&gt;

&lt;p&gt;Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Page Site Generation:&lt;/strong&gt; Upload multiple image mockups at once to generate a complete website structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-Powered Code Generation:&lt;/strong&gt; Leverages the &lt;strong&gt;gemini-2.5-flash-image-preview&lt;/strong&gt; model to produce clean, semantic HTML and Tailwind CSS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Editable Previews:&lt;/strong&gt; Instantly preview the generated pages and edit text content directly in the browser, with the underlying code updating in real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Click Export:&lt;/strong&gt; Package the entire multi-page website into a single, downloadable &lt;code&gt;.zip&lt;/code&gt; file, ready for immediate deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;USED FREE API KEY SO IT MAY NOT BE WORKING &lt;/p&gt;

&lt;p&gt;You can try the live application here: &lt;strong&gt;&lt;a href="https://ai-multi-page-architect-626025278302.us-west1.run.app/" rel="noopener noreferrer"&gt;https://ai-multi-page-architect-626025278302.us-west1.run.app/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a video walkthrough of the application in action:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/z3MebyOz1ME"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The application has a clean UI for uploading mockups and editing the results. The AI generates clean HTML and CSS as the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Google AI Studio
&lt;/h2&gt;

&lt;p&gt;Google AI Studio was the complete development environment for building and iterating on ProtoHTML. The core of the application is powered by the &lt;strong&gt;gemini-2.5-flash-image-preview&lt;/strong&gt; model (affectionately known as 'nano banana'), used during the free trial period on Sept 6-7. This model was the perfect choice for its powerful and fast multimodal capabilities.&lt;/p&gt;

&lt;p&gt;The key to getting high-quality, consistent output was &lt;strong&gt;prompt engineering&lt;/strong&gt;. I crafted a detailed &lt;code&gt;systemInstruction&lt;/code&gt; that sets the persona for the AI as an "expert senior frontend developer" and provides a strict set of rules it must follow. These rules dictate everything from the output format (raw HTML only) to technical requirements like including the Tailwind CSS CDN link, using semantic HTML5 tags, and implementing responsive design patterns.&lt;/p&gt;

&lt;p&gt;Each API call is a multimodal request, sending both the visual image data and a concise text prompt (e.g., &lt;code&gt;Based on the provided image, generate the complete HTML file for the "About Us" page now.&lt;/code&gt;) to the Gemini model. This combination allows the AI to understand both the visual layout from the image and the specific context for the page from the text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multimodal Features
&lt;/h2&gt;

&lt;p&gt;The primary multimodal feature of ProtoHTML is &lt;strong&gt;Image-to-Code Generation&lt;/strong&gt;. The application takes a visual input (a webpage mockup) and translates it into a structured, textual output (a complete HTML file with Tailwind CSS classes).&lt;/p&gt;

&lt;p&gt;This functionality fundamentally enhances the user experience in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accelerates Prototyping:&lt;/strong&gt; It dramatically reduces the friction between a visual idea and a functional prototype. Users can go from a set of static images to an interactive, multi-page website in minutes, allowing for rapid iteration and feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empowers Non-Coders:&lt;/strong&gt; Designers or project managers can bring their visions to life without needing to write a single line of code, making web development more accessible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creates a Tangible Feedback Loop:&lt;/strong&gt; The most powerful part of the experience is the immediate connection between the visual input and the interactive output. Seeing your static mockup rendered as a live, editable webpage in the "Preview &amp;amp; Edit" tab is a powerful "wow" moment. It makes the AI's "understanding" of the image tangible and gives the user immediate control to refine the result.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>googleaichallenge</category>
      <category>ai</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
