<?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: Merbin J Anselm</title>
    <description>The latest articles on DEV Community by Merbin J Anselm (@anselm94).</description>
    <link>https://dev.to/anselm94</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F158056%2F315520c5-b079-41a1-8a43-8b5700a41890.png</url>
      <title>DEV Community: Merbin J Anselm</title>
      <link>https://dev.to/anselm94</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anselm94"/>
    <language>en</language>
    <item>
      <title>🤝 Collaborative &amp; 📸 PhotoGrid Collage Maker - Built on Netlify Primitives ⚡️</title>
      <dc:creator>Merbin J Anselm</dc:creator>
      <pubDate>Sun, 12 May 2024 16:51:44 +0000</pubDate>
      <link>https://dev.to/anselm94/collaborative-photogrid-collage-maker-built-on-netlify-primitives-gam</link>
      <guid>https://dev.to/anselm94/collaborative-photogrid-collage-maker-built-on-netlify-primitives-gam</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/netlify"&gt;Netlify Dynamic Site Challenge&lt;/a&gt;: Visual Feast, Build with Blobs, Clever Caching.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Recently, I embarked on a rejuvenating journey to the picturesque &lt;a href="https://en.wikipedia.org/wiki/Thekkady"&gt;Thekkady&lt;/a&gt; region of South India, seeking respite from &lt;a href="https://www.newindianexpress.com/explainers/2024/May/12/explained-why-is-peninsular-india-reeling-under-heat-wave-and-water-crisis"&gt;the sizzling summer heat waves&lt;/a&gt;. Little did I know that this trip would inspire a unique project for the Netlify Dev Challenge. Upon my return, I found myself tasked with creating a collage of the captivating photos I had captured during my adventure. Instead of settling for the usual approach of using another online 'collage maker', I decided to take up the challenge and craft something useful – the &lt;strong&gt;Collaborative Photo Collage Maker&lt;/strong&gt;. Yes, I know, this is something bit too much for a trivial task. But it's fun to do so ;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37uhlk39i1vx3j128b30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37uhlk39i1vx3j128b30.png" alt="Collage created using PhotoGrid Collage Maker - Trip to Thekkady" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I set out to create the collaborative PhotoGrid Collage Maker, a dynamic web application that allows users to visually build &lt;strong&gt;photo collages&lt;/strong&gt; together with friends, even asynchronously. The project is a perfect fit for the Netlify Dynamic Site Challenge, showcasing the power of Netlify's platform primitives, including &lt;strong&gt;Netlify Blobs&lt;/strong&gt;, &lt;strong&gt;Netlify Image CDN&lt;/strong&gt;, and &lt;strong&gt;Netlify Cache-Control&lt;/strong&gt;. At its core, the PhotoGrid Collage Maker is a blank grid where users can select from various &lt;strong&gt;grid layouts&lt;/strong&gt;, ranging from &lt;strong&gt;2 to 8 images&lt;/strong&gt;. With a simple &lt;strong&gt;drag-and-drop interface&lt;/strong&gt;, users can &lt;strong&gt;upload&lt;/strong&gt; their photos and arrange them within the chosen layout. The beauty of this application lies in its &lt;strong&gt;collaborative&lt;/strong&gt; nature – users can share a &lt;strong&gt;unique link&lt;/strong&gt; with friends, allowing them to contribute to the collage one image at a time.&lt;/p&gt;

&lt;p&gt;But that's not all! The app also boasts intelligent &lt;strong&gt;conflict resolution&lt;/strong&gt;, ensuring that even if multiple users make changes simultaneously, their edits are seamlessly merged. Thanks to Netlify Blobs' &lt;strong&gt;'strong' consistency mode&lt;/strong&gt;, the app keeps track of any conflicts and notifies users if reconciliation is required. Once the masterpiece is complete, users can effortlessly &lt;strong&gt;download&lt;/strong&gt; their collage as a high-quality PNG image with a single click. And let's not forget about &lt;strong&gt;performance&lt;/strong&gt; – the Netlify Image CDN optimizes all uploaded images, ensuring they're served at the &lt;strong&gt;perfect resolution&lt;/strong&gt;, while Netlify's granular Cache Control (using &lt;code&gt;Netlify-CDN-Cache-Control&lt;/code&gt; &amp;amp; &lt;code&gt;Netlify-Vary&lt;/code&gt; headers) keeps the site lightning-fast.&lt;/p&gt;

&lt;p&gt;I wrote the whole application in AstroJS with Svelte Islands. Though I initially started in SvelteKit but was hit with this &lt;a href="https://answers.netlify.com/t/netlify-blobs-accessing-from-function-inside-sveltekit-app-requiring-explicit-token-siteid-deployid/107200/16"&gt;infamous issue&lt;/a&gt;, but later switched to AstroJS as the &lt;a href="https://docs.astro.build/en/guides/integrations-guide/netlify/"&gt;Netlify adapter&lt;/a&gt; already supports Netlify Functions v2. I know, switching from SvelteKit used as an SPA/SSR framework to AstroJS as an MPA framework does not make sense at first sight, but I realised once after this implementation, that was the right choice to demonstrate the platform primitives for the challenge. And for the effort they both have a lot of similarities, and AstroJS can support multiple UI frameworks as islands, the porting was a lot easier.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anselm94"&gt;
        anselm94
      &lt;/a&gt; / &lt;a href="https://github.com/anselm94/netlify-challenge-collagemaker"&gt;
        netlify-challenge-collagemaker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Photo Collage Maker for Netlify Dev Challenge
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Collaborative PhotoGrid Maker&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A collaborative PhotoGrid collage maker written in AstroJS, submitted for the &lt;a href="https://dev.to/challenges/netlify" rel="nofollow"&gt;Dev.to Netlify Challenge&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Get Started&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Clone this repository&lt;/li&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Start local server via Netlify CLI&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/anselm94/netlify-challenge-collagemaker./LICENSE"&gt;MIT License&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anselm94/netlify-challenge-collagemaker"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;p&gt;&lt;a href="https://photogrider.netlify.app/" class="ltag_cta ltag_cta--branded"&gt;Try Demo - photogrider.netlify.app&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Let me walkthrough you for a short demo of user-facing functionalities and how Netlify Platform Primitives enables them with its technical capabilities using GIFs.&lt;/p&gt;

&lt;p&gt;a. Open the website. You'll be greeted with a homepage&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dx2oka51vy5juh2uoip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8dx2oka51vy5juh2uoip.png" alt="Home page" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;b. Then click on 'Create a Photogrid' action to start creating a photogrid&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqzdcb0c1or3dxlpbep9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnqzdcb0c1or3dxlpbep9.gif" alt="Create a photogrid" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;c. You are presented with an empty grid with drag-drop zones for 2 images. For adding more images, select a layout from the list to add even more upto 8 images in different layouts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnib9ni9jxycuixljtuzr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnib9ni9jxycuixljtuzr.gif" alt="Switch layouts" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;d. You can drag and drop images into these drag-drop zones to upload them&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuya8x5v74q8plvp0ugdc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuya8x5v74q8plvp0ugdc.gif" alt="Drag and drop" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;e. Accidentally selected the wrong image? Fear not, just delete it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsnccn7ucrmcyq2r7me9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsnccn7ucrmcyq2r7me9.gif" alt="Delete image" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;f. Next to the fun part, you can share your photogrid with your friends. What if your friend made an edit while you just edited? Sorry your changes will be lost, but no worries, you can always try again at the now synced version&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foy1qbtprmx02obt1gnxm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foy1qbtprmx02obt1gnxm.gif" alt="Collaboration" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;g. Happy with your edit? You can now download your photogrid collage as a PNG image&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrph9q1vkgngiua8lxbk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrph9q1vkgngiua8lxbk.gif" alt="Download image" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Primitives
&lt;/h2&gt;

&lt;p&gt;Hope you enjoyed the demo. Now, onto the technical details. As said in the introduction, this submission focuses on all 3 prompts - Visual Feast, Build with Blobs, Clever Caching. Let me walk you through how I leveraged the Netlify Platform Primitives.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Netlify Blobs
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Usecase 1: Data structure - Metadata + Images
&lt;/h4&gt;

&lt;p&gt;At the heart of this application lies Netlify Blobs storage solution, to persist both the metadata and images associated with each photogrid. For every photogrid created, a metadata object and up to seven image entries are stored in Netlify Blobs. The keys are &lt;a href="https://www.npmjs.com/package/xksuid"&gt;K-sortable UIDs&lt;/a&gt; enabling UIDs to be sorted in chronological order.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;key&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ksuid}/metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;{metadata}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ksuid}/image-0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;{image-file-0}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ksuid}/image-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;{image-file-1}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Usecase 2: Wiki-style collaboration
&lt;/h4&gt;

&lt;p&gt;The metadata object contains essential details such as the grid ID, last modified timestamp, layout, and an array of image IDs.&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;type&lt;/span&gt; &lt;span class="nx"&gt;GridMetadata&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// grid id - ksuid based&lt;/span&gt;
  &lt;span class="nl"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// to enable collaboration&lt;/span&gt;
  &lt;span class="nl"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&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-2&lt;/span&gt;&lt;span class="dl"&gt;"&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-3&lt;/span&gt;&lt;span class="dl"&gt;"&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-4&lt;/span&gt;&lt;span class="dl"&gt;"&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-5&lt;/span&gt;&lt;span class="dl"&gt;"&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-6&lt;/span&gt;&lt;span class="dl"&gt;"&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-7&lt;/span&gt;&lt;span class="dl"&gt;"&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-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&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="c1"&gt;// array containing image-ids. Initially contains 8 null values.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Netlify Blobs' 'strong' consistency mode plays a crucial role in enabling real-time collaboration. After every action, the changes are committed back to the Netlify Blob storage, along with the &lt;code&gt;lastModified&lt;/code&gt; timestamp. For every client-side action, the last known &lt;code&gt;lastModified&lt;/code&gt; timestamp is sent from the web client and compared with the entry in the storage. If the client's &lt;code&gt;lastModified&lt;/code&gt; is older, the operation is discarded; otherwise, the entry is updated, ensuring seamless synchronization across multiple users.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Netlify Image CDN
&lt;/h3&gt;

&lt;p&gt;The photogrid is a CSS Image Grid. For each of the images in the grid, the width &amp;amp; height of the image differs between the selected layout. The absolute image size is required along with how the image is spanned across in the grid. Below is how the layout info looks like&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;type&lt;/span&gt; &lt;span class="nx"&gt;LAYOUTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// grid-2, grid-3, etc.&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// width of image in the cell in px&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// height of image in the cell in px&lt;/span&gt;
      &lt;span class="nl"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// css grid colspan of the cell in the grid&lt;/span&gt;
      &lt;span class="nl"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// css grid rowspan of the cell in the grid&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;
  View full layout metadata
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LAYOUTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&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="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&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="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&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;grid-2&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-3&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-4&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-5&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-6&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-7&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;350&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-8&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;cells&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;550&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;550&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;colspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;rowspan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vp7x89mbqbpu4ofjawl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vp7x89mbqbpu4ofjawl.gif" alt="Switching layout" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To leverage the power of the Netlify Image CDN to deliver optimized and responsive images tailored to each user's device and selected layout, the application utilizes the &lt;a href="https://unpic.pics/"&gt;unpic&lt;/a&gt; library, which has &lt;a href="https://unpic.pics/lib/#supported-cdn-apis"&gt;out-of-the-box support for Netlify Image CDN&lt;/a&gt; and generates &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images"&gt;responsive image tags&lt;/a&gt; with ease.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Actual Unpic Svelte configuration:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
  &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"h-full w-full"&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// parameter - image url -&amp;gt; /.netlify/images?url=/{gridId}/image/{imageId}&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// should be loaded with high priority and interactive on load&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"constrained"&lt;/span&gt; &lt;span class="c1"&gt;// parameter - layout&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// parameter - max width&lt;/span&gt;
  &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// parameter - max height&lt;/span&gt;
  &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Photogrid image - {i}"&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Rendered HTML code:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-full w-full"&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/.netlify/images?w=700&amp;amp;h=350&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}"&lt;/span&gt;
  &lt;span class="na"&gt;priority=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;
  &lt;span class="na"&gt;layout=&lt;/span&gt;&lt;span class="s"&gt;"constrained"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Photogrid image - 2"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"eager"&lt;/span&gt;
  &lt;span class="na"&gt;fetchpriority=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"
    /.netlify/images?w=640&amp;amp;h=320&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}   640w,
    /.netlify/images?w=700&amp;amp;h=350&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}   700w,
    /.netlify/images?w=750&amp;amp;h=375&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}   750w,
    /.netlify/images?w=828&amp;amp;h=414&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}   828w,
    /.netlify/images?w=960&amp;amp;h=480&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId}   960w,
    /.netlify/images?w=1080&amp;amp;h=540&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId} 1080w,
    /.netlify/images?w=1280&amp;amp;h=640&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId} 1280w,
    /.netlify/images?w=1400&amp;amp;h=700&amp;amp;fit=cover&amp;amp;url=/{gridId}/image/{imageId} 1400w
  "&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 700px) 700px, 100vw"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"object-fit: cover; max-width: 700px; max-height: 350px; aspect-ratio: 2 / 1; width: 100%;"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures that each image in the grid is rendered at the appropriate size and resolution, optimizing performance and providing a seamless visual experience for users across various devices and screen sizes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Netlify Cache Control
&lt;/h3&gt;

&lt;p&gt;To further enhance performance, I implemented Netlify's granular Cache Control. The application has 1 GET API endpoints serving the images by reading from the Netlify Blob and 2 server-side rendered pages (home page and dynamic page for each photogrid), which are cached for 24 hours on the CDN, improving load times and reducing server load.&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="nx"&gt;Astro&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Netlify-CDN-Cache-Control&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;public, max-age=0, s-maxage=86400, must-revalidate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// cache for 24 hours in the cdn, but not in the browser.&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, there is a catch. Since the photogrid image is a server-side rendered page that changes with every update (i.e. on uploading an image, deleting an image or selecting a layout), a more granular approach is required. This is where Netlify's granular cache control comes into play. The cache objects are key-values cached in the edge networks. For every action, the URL includes query parameters &lt;code&gt;?lastmodified=${lastModified}&amp;amp;hasconflict={true|false}&lt;/code&gt;. By utilizing the &lt;code&gt;Netlify-Vary&lt;/code&gt; header to instruct the CDN to vary the cache object key based on these query parameters, the application ensures that the CDN cache is used to its fullest potential while still serving the most up-to-date content.&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="nx"&gt;Astro&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Netlify-Vary&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;query=lastmodified|hasconflict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Additional Netlify Platform Primitives
&lt;/h3&gt;

&lt;p&gt;In addition to the core primitives mentioned above, the PhotoGrid Collage Maker leverages several other Netlify offerings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Netlify Functions&lt;/strong&gt;: The project is built using the AstroJS framework, which compiles the application to Netlify Functions, ensuring seamless deployment and serverless execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify CLI&lt;/strong&gt;: The Netlify CLI simplified the entire development workflow, enabling local testing and integration of Netlify Blobs and the Netlify Image CDN, providing an exceptional developer experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify Site Deploys&lt;/strong&gt;: The deployment process is streamlined with a simple &lt;code&gt;git push&lt;/code&gt; command, leveraging Netlify's seamless CI/CD workflow.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>netlifychallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Fun, Beautiful, Safe, Printable 'Story Cards' for Kids with Cloudflare AI</title>
      <dc:creator>Merbin J Anselm</dc:creator>
      <pubDate>Fri, 12 Apr 2024 19:40:23 +0000</pubDate>
      <link>https://dev.to/anselm94/fun-beautiful-printable-story-cards-for-kids-with-cloudflare-ai-3fbf</link>
      <guid>https://dev.to/anselm94/fun-beautiful-printable-story-cards-for-kids-with-cloudflare-ai-3fbf</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-cloudflare-ai-challenge-3000-in-prizes-5f99"&gt;Cloudflare AI Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;An interactive AI-powered "&lt;strong&gt;Story Cards&lt;/strong&gt;" Maker specifically designed for children (we were once a kid too 😉). This fun and user-friendly web app helps kids craft their own narrative adventures in the size of a postcard, complete with beautiful illustrations. The Story Card can be translated, re-styled with illustrations with a variety of children's storybook themes and even can be downloaded and printed. In the age of AI, the safety of generated content is a priority - the illustrations and the story generated are vetted for safety moderation using AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are 'Story Cards'?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.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%2Ffs140efj3wygrmctf6a4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ffs140efj3wygrmctf6a4.png" alt="Story Card 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2ne6o15dnmtrmro7jf1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2ne6o15dnmtrmro7jf1s.png" alt="Story Card 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F7wxpu2n10xqdfimq6d66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F7wxpu2n10xqdfimq6d66.png" alt="Story Card 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Story Card = &lt;strong&gt;Story&lt;/strong&gt; in a &lt;strong&gt;Post Card&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For my newborn boy, I recently bought illustrated storybooks with 365 stories each. I really loved how the stories were written with a precise theme in a small paragraph with just one illustration. I was wondering at the same time, if I can have each of the stories printed on a card, which I can shuffle later and not read all those stories in order. Thus, the idea of 'Story Card' was born.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎒 &lt;strong&gt;Choose Your Adventure&lt;/strong&gt;: Kids can select from a range of captivating story elements, including &lt;em&gt;genre&lt;/em&gt; (fairy tale, superhero, mystery), &lt;em&gt;character&lt;/em&gt; (unicorn, robot, prince), &lt;em&gt;setting&lt;/em&gt; (castle, candy land, jungle), &lt;em&gt;tone&lt;/em&gt; (funny, adventurous, magical), and &lt;em&gt;theme&lt;/em&gt; (friendship, bravery, imagination).&lt;/li&gt;
&lt;li&gt;🌟 &lt;strong&gt;Surprise Me!&lt;/strong&gt;: Click the "Surprise Me" button for a completely random story combination, leading to unexpected and delightful narrative twists!&lt;/li&gt;
&lt;li&gt;📇 &lt;strong&gt;Bring Your Story to Life&lt;/strong&gt;: Once the elements are chosen, the AI conjures up a captivating story title, narrative content, and even an eye-catching illustration that perfectly complements the adventure.&lt;/li&gt;
&lt;li&gt;🌏 &lt;strong&gt;Translate for the World&lt;/strong&gt;: Translate your story title and content into various languages, broadening the child's horizons and sparking an interest in foreign languages.&lt;/li&gt;
&lt;li&gt;💅 &lt;strong&gt;Style the Adventure&lt;/strong&gt;: Restyle the illustrations in a variety of storybook themes. Choose from a variety of options to create a truly personalized story card.&lt;/li&gt;
&lt;li&gt;🦺 &lt;strong&gt;Safe for Kids&lt;/strong&gt;: The illustration and the story content are vetted for safety classification using AI&lt;/li&gt;
&lt;li&gt;💖 &lt;strong&gt;Download and Share&lt;/strong&gt;: Once you're happy with your masterpiece, download the story card as a beautiful postcard-style image, ready to print, share with friends, or treasure forever!&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;a href="https://cf-challenge-ai-storycard.pages.dev/create" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;AI-Powered Story Card Maker - Demo&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Journey 1: Create a 'Story Card'&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Frspjwkowvw9bkzr6e1gg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Frspjwkowvw9bkzr6e1gg.gif" alt="Create a Story card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Journey 2: Translate the story&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Frwhrc68erx0z2w5675m1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Frwhrc68erx0z2w5675m1.gif" alt="Translate the story"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Journey 3: Re-style the illustration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fmp7w87od6k0r2rkedxv9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fmp7w87od6k0r2rkedxv9.gif" alt="Re-style the illustration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;a. &lt;strong&gt;Surprise Me!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fhwq3o15p08x2260w3b6w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhwq3o15p08x2260w3b6w.gif" alt="Surprise me"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;b. &lt;strong&gt;Safety Moderation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F22e8msi9jtg6sdfcd1ip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F22e8msi9jtg6sdfcd1ip.png" alt="Content Safety"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;c. &lt;strong&gt;Download Story Card&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F5ihezqk2t6bo6zkd1h87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F5ihezqk2t6bo6zkd1h87.png" alt="Download story card"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;d. &lt;strong&gt;Link to Story Card&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The story cards once generated, will have a unique link to access and download and will be available for 24 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;p&gt;This AI-powered Story Card Maker is built as a &lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;SvelteKit&lt;/a&gt; application with Typescript. Using &lt;a href="https://flowbite-svelte.com/" rel="noopener noreferrer"&gt;Flowbite Svelte&lt;/a&gt; component library, the whole application was laid out. The layout for the Story Card (emulating the size of a postcard - 4" x 3") is created as an HTML Canvas using &lt;a href="http://fabricjs.com/" rel="noopener noreferrer"&gt;Fabric.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, the SvelteKit app is deployed in Cloudflare Pages with &lt;a href="https://developers.cloudflare.com/r2/" rel="noopener noreferrer"&gt;Cloudflare R2&lt;/a&gt; (for storing illustrations), &lt;a href="https://developers.cloudflare.com/kv/" rel="noopener noreferrer"&gt;Cloudflare KV&lt;/a&gt; (for storing story metadata) and &lt;a href="https://developers.cloudflare.com/workers-ai/" rel="noopener noreferrer"&gt;Cloudflare AI&lt;/a&gt; (obvious choice for LLM and other GenAI models)&lt;/p&gt;

&lt;p&gt;Finally, this is my first experience building a full web app with SvelteKit and deploying it on Cloudflare Pages&lt;/p&gt;

&lt;p&gt;The complete source is available here under the MIT license&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/anselm94" rel="noopener noreferrer"&gt;
        anselm94
      &lt;/a&gt; / &lt;a href="https://github.com/anselm94/cf-challenge-ai-storycard" rel="noopener noreferrer"&gt;
        cf-challenge-ai-storycard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Create Stories in a card with illustrations using AI. Created as part of Cloudflare AI Dev Challenge
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;AI Story Card (&lt;a href="https://dev.to/challenges/cloudflare" rel="nofollow"&gt;#CloudflareAIDevChallenge&lt;/a&gt;)&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Create Stories in a card with illustrations using AI. Created as part of &lt;a href="https://dev.to/challenges/cloudflare" rel="nofollow"&gt;Cloudflare AI Dev Challenge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Blog Post&lt;/strong&gt; - &lt;a href="https://dev.to/anselm94/fun-beautiful-printable-story-cards-for-kids-with-cloudflare-ai-3fbf" rel="nofollow"&gt;https://dev.to/anselm94/fun-beautiful-printable-story-cards-for-kids-with-cloudflare-ai-3fbf&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project won Cloudflare AI Challenge under 'Multiple Models' prize category winner - &lt;a href="https://dev.to/devteam/congrats-to-the-cloudflare-ai-challenge-winners-25lm" rel="nofollow"&gt;Dev.to Announcement Post&lt;/a&gt; 😉&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt; - &lt;a href="https://cf-challenge-ai-storycard.pages.dev" rel="nofollow noopener noreferrer"&gt;https://cf-challenge-ai-storycard.pages.dev&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a Story Card&lt;/strong&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/storycard-create.gif"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fstorycard-create.gif" alt="Create storycard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Translate the Story&lt;/strong&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/storycard-translate.gif"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fstorycard-translate.gif" alt="Translate storycard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Restyle the illustration&lt;/strong&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/storycard-restyle.gif"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fstorycard-restyle.gif" alt="Restyle storycard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Surprise Me&lt;/strong&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/storycard-surpriseme.gif"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fstorycard-surpriseme.gif" alt="Surprise me"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Download Story Card&lt;/strong&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/download-storycard.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fdownload-storycard.png" alt="Download storycard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Safe for Kids&lt;/strong&gt;
If any inappropriate content is detected, the story card will be marked as "unsafe" with a blurred preview image
&lt;a rel="noopener noreferrer" href="https://github.com/anselm94/cf-challenge-ai-storycard./docs/storycard-safety-classification.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fanselm94%2Fcf-challenge-ai-storycard.%2Fdocs%2Fstorycard-safety-classification.png" alt="Safety Classification"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collaborate with friends&lt;/strong&gt;
The url for a storycard is valid for a day. Comeback, edit and download it anytime within 24 hours.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Cloudflare AI Models used:&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Text Generation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/mistral-7b-instruct-v0.2/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Generation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/stable-diffusion-xl-base-1.0/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;stable-diffusion-xl-base-1.0&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/m2m100-1.2b/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;m2m100-1.2b&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image to Text&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/uform-gen2-qwen-500m/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Content Moderation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/llamaguard-7b-awq/" rel="nofollow noopener noreferrer"&gt;&lt;code&gt;llamaguard-7b-awq&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Developing&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Once you've created a project and installed dependencies with &lt;code&gt;npm install&lt;/code&gt; (or &lt;code&gt;pnpm install&lt;/code&gt; or…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/anselm94/cf-challenge-ai-storycard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;The Cloudflare Dev AI Challenge sparked a creative fire! Inspired by my child's illustrated storybook, I envisioned a collection of story cards – short, engaging narratives that could be printed, shuffled and read in any order.&lt;/p&gt;

&lt;p&gt;Here's how I brought this idea to life:&lt;/p&gt;

&lt;h3&gt;
  
  
  Inspiration &amp;amp; Design:
&lt;/h3&gt;

&lt;p&gt;Cloudflare's open AI models, powerful for focused tasks, seemed perfect for crafting short stories. Contrasting them with larger LLMs, I saw their potential for creative writing. Thus, generating short stories with illustrations, presented as printable story cards, became my challenge entry.&lt;/p&gt;

&lt;p&gt;First, I sketched the story card layout using Affinity Designer, focusing on child-friendly fonts and a layout reminiscent of a storybook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Web App:
&lt;/h3&gt;

&lt;p&gt;SvelteKit, with its smooth developer experience, was a joy to use.  Since I envisioned multiple features, Cloudflare KV and R2 became natural choices for data storage, with SvelteKit's adapters making integration effortless. Finally, Wrangler CLI helped me deploy the application with ease.&lt;/p&gt;

&lt;h3&gt;
  
  
  Harnessing AI Power:
&lt;/h3&gt;

&lt;p&gt;The story card maker utilizes five core AI tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text Generation&lt;/strong&gt;: The &lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt; LLM model generates the story title, content, and illustration caption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Generation&lt;/strong&gt;: The &lt;code&gt;stable-diffusion-xl-base-1.0&lt;/code&gt; model creates illustrations and allows for style variations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation&lt;/strong&gt;: The &lt;code&gt;m2m100-1.2b&lt;/code&gt; model translates the story content into various languages (limited to Latin character sets due to font usage constraints).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image to Text&lt;/strong&gt;: The &lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt; model writes a detailed precise description of the generated illustration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Content Moderation&lt;/strong&gt;: The &lt;code&gt;llamaguard-7b-awq&lt;/code&gt; model moderates the story content as well as the illustration description, to be 'safe' or 'unsafe' for children.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Takeaways and Future Aspirations:
&lt;/h3&gt;

&lt;p&gt;This challenge was a fantastic learning experience, helping me transform an idea into a working application in a short timeframe.  Looking ahead, I'm excited to expand this project with new features and generate even more story card varieties for my son and others.  On the technical side, I identified limitations with certain models, like character limits for translation. Exploring solutions like breaking down content for translation in chunks could be a future avenue.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'm Proud Of:
&lt;/h3&gt;

&lt;p&gt;Overall, I'm most proud of creating a user-friendly and engaging application that combines the power of AI with the magic of storytelling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple Models and/or Triple Task Types&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application currently fits into both types.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiple models per task&lt;/strong&gt;: The first journey - 'Create a story card' utilises the following 2 models for 3 steps in order&lt;/p&gt;

&lt;p&gt;a. &lt;a href="https://developers.cloudflare.com/workers-ai/models/mistral-7b-instruct-v0.2/" rel="noopener noreferrer"&gt;&lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt;&lt;/a&gt; - Generates the story title and content for the provided theme, genre, character, location etc.&lt;/p&gt;

&lt;p&gt;b. &lt;a href="https://developers.cloudflare.com/workers-ai/models/mistral-7b-instruct-v0.2/" rel="noopener noreferrer"&gt;&lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt;&lt;/a&gt; (same model) - Based on the generated story, identifies one primary scene, for which the illustration is to be generated. &lt;/p&gt;

&lt;p&gt;c. &lt;a href="https://developers.cloudflare.com/workers-ai/models/stable-diffusion-xl-base-1.0/" rel="noopener noreferrer"&gt;&lt;code&gt;stable-diffusion-xl-base-1.0&lt;/code&gt;&lt;/a&gt; - Based on the scene description, a text-to-image prompt is constructed to generate the illustration in a particular style.&lt;/p&gt;

&lt;p&gt;d. &lt;a href="https://developers.cloudflare.com/workers-ai/models/uform-gen2-qwen-500m/" rel="noopener noreferrer"&gt;&lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt;&lt;/a&gt; - Then, then a detailed description of the image is generated.&lt;/p&gt;

&lt;p&gt;e. &lt;a href="https://developers.cloudflare.com/workers-ai/models/llamaguard-7b-awq/" rel="noopener noreferrer"&gt;&lt;code&gt;llamaguard-7b-awq&lt;/code&gt;&lt;/a&gt; - Finally, both the story content (title + content) as well as the illustration description are moderated for safety.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Triple Task&lt;/strong&gt;: The AI models across 5 task types were utilized.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Text Generation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/mistral-7b-instruct-v0.2/" rel="noopener noreferrer"&gt;&lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt;&lt;/a&gt; - For story title and content generation, text-to-image prompt (scene) generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Generation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/stable-diffusion-xl-base-1.0/" rel="noopener noreferrer"&gt;&lt;code&gt;stable-diffusion-xl-base-1.0&lt;/code&gt;&lt;/a&gt; - For illustration generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/m2m100-1.2b/" rel="noopener noreferrer"&gt;&lt;code&gt;m2m100-1.2b&lt;/code&gt;&lt;/a&gt; - For translating story title and content into multiple languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image to Text&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/uform-gen2-qwen-500m/" rel="noopener noreferrer"&gt;&lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt;&lt;/a&gt; - For understanding the content in the generated illustration, to be vetted by an LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text Content Moderation&lt;/strong&gt; - &lt;a href="https://developers.cloudflare.com/workers-ai/models/llamaguard-7b-awq/" rel="noopener noreferrer"&gt;&lt;code&gt;llamaguard-7b-awq&lt;/code&gt;&lt;/a&gt; - For moderating the illustration description and the story title &amp;amp; content.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Edit&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;mistral-7b-instruct-v0.1&lt;/code&gt; model to &lt;code&gt;mistral-7b-instruct-v0.2&lt;/code&gt;, since I was facing issue soon after the post was published&lt;/li&gt;
&lt;li&gt;Added the 'Safety Classification' feature, since this is essential for AI-generated text and image content. Moreover, for an application focussed on generating content for kids, it's an absolute need. Add the usage of &lt;code&gt;uform-gen2-qwen-500m&lt;/code&gt; &amp;amp; &lt;code&gt;llamaguard-7b-awq&lt;/code&gt; for content moderation.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudflarechallenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>generativeai</category>
    </item>
  </channel>
</rss>
