<?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: James Moro</title>
    <description>The latest articles on DEV Community by James Moro (@jamesrmoro).</description>
    <link>https://dev.to/jamesrmoro</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%2F282314%2F32142cf2-81a5-4f09-a522-08216f5d9caf.jpeg</url>
      <title>DEV Community: James Moro</title>
      <link>https://dev.to/jamesrmoro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jamesrmoro"/>
    <language>en</language>
    <item>
      <title>🔍 ArtExplorer - An Interactive and Inspiring Way to Explore Works of Art</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Mon, 28 Jul 2025 03:28:20 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/artexplorer-an-interactive-and-inspiring-way-to-explore-works-of-art-4bjb</link>
      <guid>https://dev.to/jamesrmoro/artexplorer-an-interactive-and-inspiring-way-to-explore-works-of-art-4bjb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/algolia-2025-07-09"&gt;Algolia MCP Server Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;ArtExplorer&lt;/strong&gt; is an interactive application that uses Algolia's search power to make the Met Museum's art collection more accessible, visual, and enjoyable to explore.&lt;/p&gt;

&lt;p&gt;The idea is to offer an experience where users can type in themes, artists, or styles (e.g., “Van Gogh”, “nature”, “Japanese painting”) and instantly view relevant works with organized images and metadata.&lt;/p&gt;

&lt;p&gt;The interface also features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔍 &lt;strong&gt;Interactive filter with artist autocomplete&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;⚡️ &lt;strong&gt;Instant search powered by Algolia InstantSearch.js&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything was built using &lt;strong&gt;HTML, CSS, and vanilla JavaScript&lt;/strong&gt;, with no frameworks.&lt;/p&gt;




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

&lt;p&gt;🔗 &lt;a href="https://artexplorer.jamesrmoro.me" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/jamesrmoro/artexplorer" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;br&gt;
🎥 Presentation Video&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/sjAnekRLOzk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Utilized the Algolia MCP Server
&lt;/h2&gt;

&lt;p&gt;The Algolia MCP Server was used as a real-time search engine to index and query artworks from the &lt;a href="https://metmuseum.github.io/" rel="noopener noreferrer"&gt;Metropolitan Museum of Art Collection API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s how it was structured:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Node.js Seed Script&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetches data from the Met Museum public API&lt;/li&gt;
&lt;li&gt;Filters and transforms relevant fields (title, artist, period, image, style)&lt;/li&gt;
&lt;li&gt;Indexes into Algolia’s &lt;code&gt;artworks&lt;/code&gt; index with &lt;code&gt;objectID&lt;/code&gt; and structured fields&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frontend with InstantSearch.js&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;searchBox&lt;/code&gt;: free-text search with a smart placeholder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refinementList&lt;/code&gt;: artist filter with autocomplete&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hits&lt;/code&gt;: responsive grid view displaying artwork cards&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;searchFunction&lt;/code&gt;: prevents rendering without a search query&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ✅ What I Learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How to transform a raw API into a rich and fast search experience using Algolia&lt;/li&gt;
&lt;li&gt;Implementation of &lt;code&gt;searchFunction&lt;/code&gt; for full control over rendering&lt;/li&gt;
&lt;li&gt;Smart filtering using &lt;code&gt;refinementList&lt;/code&gt; and &lt;code&gt;searchable: true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;How to ensure accessibility, responsiveness, and a smooth experience without frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎯 Challenges Faced
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Met Museum API doesn’t provide full bulk data — required individual ID fetches&lt;/li&gt;
&lt;li&gt;Handling null/missing fields required careful validation before indexing&lt;/li&gt;
&lt;li&gt;Some visual fields (like title and image) needed fallbacks to avoid breaking rendering&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;ArtExplorer is proof of how Algolia can turn large datasets into intuitive and delightful discovery experiences - even without heavy frameworks.&lt;/p&gt;

&lt;p&gt;Thanks to the Algolia and DEV.to teams for this inspiring challenge!&lt;/p&gt;




&lt;h3&gt;
  
  
  Credits
&lt;/h3&gt;

&lt;p&gt;Created by &lt;a href="https://dev.to/jamesrmoro"&gt;@jamesrmoro&lt;/a&gt;&lt;br&gt;
Data provided by &lt;a href="https://metmuseum.github.io/" rel="noopener noreferrer"&gt;The Met Museum Collection API&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>algoliachallenge</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>🎨 Office Coolors: A Colorful Intranet for Creative Productivity</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sun, 27 Jul 2025 22:44:22 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/office-coolors-a-colorful-intranet-for-creative-productivity-4aic</link>
      <guid>https://dev.to/jamesrmoro/office-coolors-a-colorful-intranet-for-creative-productivity-4aic</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend/axero"&gt;Frontend Challenge: Office Edition sponsored by Axero, Holistic Webdev: Office Space&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;Office Coolors&lt;/strong&gt; is a creative intranet designed for illustrators, visual artists, and anyone who prefers a more visual and interactive approach to organizing tasks.&lt;/p&gt;

&lt;p&gt;The main idea is to turn task completion into a visually rewarding process: each completed task unlocks a color that can be used to paint parts of an &lt;strong&gt;interactive SVG drawing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s like a digital artistic workspace where motivation grows as your progress brings color to your creations.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Key Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Choose a themed workspace: &lt;strong&gt;Nature&lt;/strong&gt;, &lt;strong&gt;Office&lt;/strong&gt;, &lt;strong&gt;Beach&lt;/strong&gt;, or &lt;strong&gt;Trailer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Visual tasks unlock exclusive colors&lt;/li&gt;
&lt;li&gt;Interactive SVG painting with visual feedback (color explosion effect)&lt;/li&gt;
&lt;li&gt;Dark mode with persistent storage&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Bounce&lt;/em&gt; animations and custom sound effects&lt;/li&gt;
&lt;li&gt;Local storage to save your progress&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;🖥️ Try the live demo:&lt;br&gt;
&lt;a href="https://office.jamesrmoro.me" rel="noopener noreferrer"&gt;https://office.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📁 View the source code on GitHub:&lt;br&gt;
&lt;a href="https://github.com/jamesrmoro/office-coolors" rel="noopener noreferrer"&gt;https://github.com/jamesrmoro/office-coolors&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📺 Watch in action:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/e3F9_-taRz4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




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

&lt;p&gt;The idea behind the project came from this simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What if completing each task felt like coloring a piece of a drawing?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As both an artist and a front-end developer, I wanted to explore this fusion of &lt;strong&gt;productivity, aesthetics, and gamification&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Key Design Decisions:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;strong&gt;SVGs as a base for coloring&lt;/strong&gt; gave users artistic freedom. Colors are only unlocked after task completion — turning progress into a reward.&lt;/li&gt;
&lt;li&gt;Tasks are grouped into "floors," like sections of a creative building.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;ambient sounds, colorful particles, and visual themes&lt;/strong&gt;, the experience becomes fun and immersive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛠️ Technologies Used:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;HTML, CSS, and vanilla JavaScript&lt;/li&gt;
&lt;li&gt;SVG manipulation via DOM&lt;/li&gt;
&lt;li&gt;Persistent progress using &lt;code&gt;localStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/tsparticles/confetti" rel="noopener noreferrer"&gt;tsparticles/confetti&lt;/a&gt; for celebration animations&lt;/li&gt;
&lt;li&gt;Sound effects via &lt;code&gt;Audio API&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dark mode with memory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bounce&lt;/code&gt; animations for modals&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Office Coolors&lt;/strong&gt; is more than just a task tool — it’s a digital space where &lt;strong&gt;art and progress go hand in hand&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Perfect for illustrators and creatives looking for a more visual and engaging way to stay productive. Each task completed brings your canvas to life.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✨ Feel free to explore, customize, or expand this project to suit your own creative workflow!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Thanks to Axero and DEV for encouraging imagination, customization, and creativity in the digital workspace. 🎨&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>💾 Save What You Search: A Chrome Extension Created with Runner H</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sat, 05 Jul 2025 19:25:46 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/save-what-you-search-a-chrome-extension-created-with-runner-h-3ga4</link>
      <guid>https://dev.to/jamesrmoro/save-what-you-search-a-chrome-extension-created-with-runner-h-3ga4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/runnerh"&gt;Runner H "AI Agent Prompting" Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I created a Chrome extension called &lt;strong&gt;SideSave&lt;/strong&gt;, designed especially for content professionals, SEOs, and competitive analysis.&lt;/p&gt;

&lt;p&gt;The idea came from my own workflow writing search-optimized content, where I often need to analyze &lt;em&gt;how competitors position themselves&lt;/em&gt; in Google's sponsored results.&lt;/p&gt;

&lt;p&gt;The problem is that these ads — although rich in &lt;em&gt;copywriting&lt;/em&gt;, &lt;em&gt;links&lt;/em&gt;, persuasive hooks, and keywords — disappear quickly. Just by refreshing the search or waiting a few minutes, the same ad may no longer appear.&lt;br&gt;&lt;br&gt;
This makes analysis and later study difficult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SideSave solves this by saving ads with title, link, snippet, and a clear visual marker that it's a sponsored result.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  How I Used Runner H
&lt;/h2&gt;

&lt;p&gt;I used Runner H as a tool to automatically and accurately generate the extension code.&lt;br&gt;&lt;br&gt;
With a well-structured prompt, I was able to generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A V3-compatible &lt;code&gt;manifest.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content.js&lt;/code&gt; scripts that dynamically detect Google results
&lt;/li&gt;
&lt;li&gt;Ad detection based on text like "Sponsored"
&lt;/li&gt;
&lt;li&gt;A floating “➕ Save to SideSave” button with smart behavior
&lt;/li&gt;
&lt;li&gt;A fixed sidebar with a flat/striped style
&lt;/li&gt;
&lt;li&gt;Persistent storage using &lt;code&gt;chrome.storage.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Communication between popup and content script via &lt;code&gt;chrome.runtime.sendMessage&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this was generated with the help of the AI in Runner, without me needing to manually code everything.&lt;br&gt;&lt;br&gt;
Automation saved me hours of fine-tuning and accelerated the prototyping process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case &amp;amp; Impact
&lt;/h2&gt;

&lt;p&gt;The main use case for this extension is for &lt;strong&gt;digital marketers, content creators, copywriters, and SEO specialists&lt;/strong&gt; who want to study how ads are structured on search pages — especially when &lt;strong&gt;creating campaigns, optimized landing pages, or analyzing competitors&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With SideSave, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save and classify sponsored ads for future reference
&lt;/li&gt;
&lt;li&gt;Compare headline and description copy used in competitor campaigns
&lt;/li&gt;
&lt;li&gt;Identify link patterns used in paid ads
&lt;/li&gt;
&lt;li&gt;Store ideas for your own strategic content creation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The impact is clear: &lt;strong&gt;it makes analytical and creative work easier by keeping key insights readily accessible in the browser.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Social Love
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Youtube&lt;/strong&gt;: &lt;a href="https://youtu.be/WoncvzT_bUI" rel="noopener noreferrer"&gt;https://youtu.be/WoncvzT_bUI&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;X&lt;/strong&gt;: &lt;a href="https://x.com/jamesrmoro/status/1941583769053372422" rel="noopener noreferrer"&gt;https://x.com/jamesrmoro/status/1941583769053372422&lt;/a&gt;&lt;/p&gt;




</description>
      <category>devchallenge</category>
      <category>runnerhchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>🎮 From Prompt to Play: My Game Made with Amazon Q CLI</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sun, 22 Jun 2025 03:07:19 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/from-prompt-to-play-my-game-made-with-amazon-q-cli-3clb</link>
      <guid>https://dev.to/jamesrmoro/from-prompt-to-play-my-game-made-with-amazon-q-cli-3clb</guid>
      <description>&lt;p&gt;Recently, I saw an announcement right here on dev.to about the &lt;strong&gt;Build Games with Amazon Q CLI&lt;/strong&gt; challenge promoted by AWS. The idea is simple: use Amazon Q's AI to build a game, share how you did it, and if you're among the first 2,000 valid participants, you get an official Amazon Q t-shirt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Decided to Join
&lt;/h2&gt;

&lt;p&gt;I had already done some tests with Amazon Q before, so when I saw this challenge, I realized it was the perfect opportunity to turn an experiment into something more fun — and still walk away with a new t-shirt in the end. In fact, I had already participated in a previous initiative and shared my experience in &lt;a href="https://dev.to/jamesrmoro/the-status-rebellion-an-epic-game-about-http-codes-1p66"&gt;this article&lt;/a&gt; here on dev.to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using the Amazon Q Extension in VS Code
&lt;/h2&gt;

&lt;p&gt;Instead of using just the terminal, I chose to use the &lt;strong&gt;official Amazon Q extension for Visual Studio Code&lt;/strong&gt;, which makes the experience much smoother. With it, you can talk directly to the AI assistant inside the editor, request code snippets, tweak specific parts, and even get performance improvement suggestions for your game.&lt;/p&gt;

&lt;p&gt;Everything was super fluid — I switched between the code and Q's chat directly in VS Code, making the process very productive.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Game I Built
&lt;/h2&gt;

&lt;p&gt;I went for a &lt;strong&gt;classic arcade-style game&lt;/strong&gt;, with a spaceship, GitHub commits as enemies, and a retro vibe. Everything was built using &lt;strong&gt;HTML, CSS, and JavaScript&lt;/strong&gt;, generating code with support from Amazon Q directly inside Visual Studio Code.&lt;/p&gt;

&lt;p&gt;💡 &lt;em&gt;I chose this style because it's perfect for testing movement, collision, scoring, and visual effects — and it's lightweight enough to run in any browser.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Game Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Player movement using arrow keys (left and right)&lt;/li&gt;
&lt;li&gt;Shooting with the space bar&lt;/li&gt;
&lt;li&gt;"Enemies" represented by GitHub commits&lt;/li&gt;
&lt;li&gt;Real-time scoring&lt;/li&gt;
&lt;li&gt;Explosions, visual effects, and shooting sound&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;The game is simple, but quite satisfying for something built almost entirely with AI support. And best of all: I learned a lot through the process without getting stuck in repetitive steps.&lt;/p&gt;

&lt;p&gt;📺 Watch the gameplay in action:&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/DZLbiHaarqQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;👉 Want to try it yourself?&lt;br&gt;&lt;br&gt;
The game is desktop-only:&lt;br&gt;
🎮 &lt;a href="https://commits-invaders.jamesrmoro.me" rel="noopener noreferrer"&gt;https://commits-invaders.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a game with AI was a surprisingly fun experience. The Amazon Q extension for VS Code made a huge difference, bringing contextual suggestions directly into the editor and speeding up development.&lt;/p&gt;

&lt;p&gt;🎮 #AmazonQCLI #BuildGamesChallenge #AmazonQDevCLI&lt;/p&gt;

</description>
      <category>amazonqcli</category>
      <category>gamedev</category>
      <category>buildgameschallenge</category>
      <category>amazonqdevcli</category>
    </item>
    <item>
      <title>🕹️ History of Game Consoles – A Visual Timeline Powered by Storyblok</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Thu, 19 Jun 2025 22:00:54 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/history-of-game-consoles-a-visual-timeline-powered-by-storyblok-2i0h</link>
      <guid>https://dev.to/jamesrmoro/history-of-game-consoles-a-visual-timeline-powered-by-storyblok-2i0h</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/storyblok"&gt;Storyblok Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;My project is a &lt;strong&gt;visual timeline of video game consoles&lt;/strong&gt;, where each card displays the name, release year, image, and description of a classic console.&lt;/p&gt;

&lt;p&gt;The goal was to create a responsive and visually engaging experience, &lt;strong&gt;without needing to update the code every time I wanted to add or edit consoles&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All content is powered by &lt;strong&gt;Storyblok&lt;/strong&gt;, which serves as a headless CMS to feed the timeline with console data.&lt;br&gt;&lt;br&gt;
I used Swiper.js to build the visual carousel and enable smooth slide transitions.&lt;/p&gt;


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

&lt;p&gt;&lt;strong&gt;Storyblok Space:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://app.storyblok.com/#/me/spaces/342145/stories/0/0/689686892" rel="noopener noreferrer"&gt;https://app.storyblok.com/#/me/spaces/342145/stories/0/0/689686892&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Repository:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/jamesrmoro/timeline" rel="noopener noreferrer"&gt;https://github.com/jamesrmoro/timeline&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://timeline.jamesrmoro.me/game" rel="noopener noreferrer"&gt;https://timeline.jamesrmoro.me/game&lt;/a&gt;&lt;br&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%2Fzsp5tdyvhma82nir7z5g.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%2Fzsp5tdyvhma82nir7z5g.png" alt="Domain is custom for personal"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The domain is custom for personal use, with public mode enabled for viewing.&lt;/em&gt;&lt;/p&gt;

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




&lt;h2&gt;
  
  
  🛠 Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HTML + CSS + Vanilla JavaScript
&lt;/li&gt;
&lt;li&gt;Swiper.js for the responsive carousel
&lt;/li&gt;
&lt;li&gt;Storyblok as CMS&lt;/li&gt;
&lt;li&gt;Deployed via Vercel&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧩 How I Used Storyblok
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Created a component called &lt;code&gt;consoles&lt;/code&gt; with fields: &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;image&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inside the &lt;code&gt;game&lt;/code&gt; page, I used a &lt;code&gt;consoles&lt;/code&gt; block to store all items&lt;/li&gt;
&lt;li&gt;Used the &lt;strong&gt;Storyblok CDN public API&lt;/strong&gt; to load data on the frontend without a backend&lt;/li&gt;
&lt;li&gt;Content can be edited visually, and updates are reflected in real-time&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🤖 AI Integration
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This project does not use any AI integration.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Learnings and Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I learned how to integrate &lt;strong&gt;Storyblok with a pure frontend project&lt;/strong&gt;, no frameworks involved&lt;/li&gt;
&lt;li&gt;Understood how to build a &lt;strong&gt;modular and scalable structure&lt;/strong&gt; for visual content&lt;/li&gt;
&lt;li&gt;I’m happy with the result and see potential to apply this structure to timelines of books, movies, biographies, or historical events&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>devchallenge</category>
      <category>storyblokchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>🎉 Celebrating Festa Junina: Brazilian Tradition in Color and Motion</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sat, 07 Jun 2025 22:25:38 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/celebrating-festa-junina-brazilian-tradition-in-color-and-motion-1f2k</link>
      <guid>https://dev.to/jamesrmoro/celebrating-festa-junina-brazilian-tradition-in-color-and-motion-1f2k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2025-06-04"&gt;Frontend Challenge - June Celebrations, CSS Art: June Celebrations&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;In Brazil, June is marked by “Festa Junina,” a vibrant celebration filled with colors, traditional foods, lively music, and lots of joy. So, I decided to create a Festa Junina scene entirely in CSS, taking this opportunity to share a bit of Brazilian culture with the global community.&lt;/p&gt;

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

&lt;p&gt;🎉 &lt;a href="https://festa-junina-2025.jamesrmoro.me" rel="noopener noreferrer"&gt;https://festa-junina-2025.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://festa-junina-2025.jamesrmoro.me" rel="noopener noreferrer"&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%2Fgj93kapyjpj2yn2fich6.png" alt="Celebrating Festa Junina: Brazilian Tradition in Color and Motion" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Since 2019, I’ve made a habit of creating Christmas scenes using only HTML and CSS, documenting the entire process year after year in this &lt;a href="https://www.youtube.com/watch?v=3g4pNs_JHw8&amp;amp;list=PLL-5LVs-8oQLV0HYYttoQ5PEuItVKDOU7" rel="noopener noreferrer"&gt;playlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I always wondered where I could share these creations more broadly—so I was thrilled to see that this month’s Frontend Challenge theme is “celebration.” I decided to bring Festa Junina to the challenge and share a bit of this tradition.&lt;/p&gt;

&lt;p&gt;For this Festa Junina scene, I added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fixed side menu with information about Festa Junina.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clickable stalls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Food&lt;/strong&gt;: Discover the main traditional dishes with images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Games&lt;/strong&gt;: See the most classic Festa Junina games.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correio Elegante&lt;/strong&gt;: traditional messages from the celebration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Floating accordion: click to hear a typical tune that brings Festa Junina to life.&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
    </item>
    <item>
      <title>🏠 Smart Real Estate: Delivering Instant Sales Updates with Postmark, Chrome Extensions &amp; IoT Integration</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sun, 01 Jun 2025 07:22:37 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/smart-real-estate-delivering-instant-sales-updates-with-postmark-chrome-extensions-iot-bll</link>
      <guid>https://dev.to/jamesrmoro/smart-real-estate-delivering-instant-sales-updates-with-postmark-chrome-extensions-iot-bll</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In the fast-paced real estate market, instantly notifying the team about new sales is essential for engagement and collaboration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I created an integrated digital ecosystem for construction companies and real estate agencies, combining:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧩 &lt;strong&gt;Chrome Extension&lt;/strong&gt; for quickly registering sales
&lt;/li&gt;
&lt;li&gt;📧 &lt;strong&gt;Postmark&lt;/strong&gt; for reliable transactional email delivery
&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Supabase&lt;/strong&gt; as a real-time scalable backend and database
&lt;/li&gt;
&lt;li&gt;🔔 &lt;strong&gt;PWA (Progressive Web App)&lt;/strong&gt; for push notifications and multi-platform engagement
&lt;/li&gt;
&lt;li&gt;🔥 &lt;strong&gt;IoT Integration (ESP32 Wi-Fi)&lt;/strong&gt; for physical notifications in the office&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The focus is on &lt;strong&gt;real-time, frictionless communication&lt;/strong&gt;, with Postmark as the pillar for transactional messages, and Supabase ensuring data is always available, regardless of device or browser.&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%2Fe3066hh6d13trlywnrev.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%2Fe3066hh6d13trlywnrev.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




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

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




&lt;h2&gt;
  
  
  📁 Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jamesrmoro" rel="noopener noreferrer"&gt;
        jamesrmoro
      &lt;/a&gt; / &lt;a href="https://github.com/jamesrmoro/light-estate-extension" rel="noopener noreferrer"&gt;
        light-estate-extension
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;🏙️ Light Estate&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Smart Real Estate Dashboard – Instantly track apartment sales, notify your team, and manage all projects in the cloud.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/jamesrmoro/light-estate-extension/main/assets/images/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjamesrmoro%2Flight-estate-extension%2Fmain%2Fassets%2Fimages%2Fcover.png" alt="Light Estate UI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jamesrmoro/light-estate-extension/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/08cef40a9105b6526ca22088bc514fbfdbc9aac1ddbf8d4e6c750e3a88a44dca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667" alt="License: MIT"&gt;&lt;/a&gt;
&lt;a href="https://supabase.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d87da8e3c67a3c78bf21b3e7f678d6aed424d8d2a2beb589bcd83980f32ec970/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6261636b656425323062792d53757061626173652d3365636638652e737667" alt="Supabase"&gt;&lt;/a&gt;
&lt;a href="https://vercel.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7015516519ae874ab75537283bc75f86b3d46386ed994093a3790a1180913164/68747470733a2f2f76657263656c2e636f6d2f627574746f6e" alt="Deploy on Vercel"&gt;&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Sales Tracking&lt;/strong&gt; – Visual building model, click apartments to register sales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant Notifications&lt;/strong&gt; – Email and Push notifications for your team using Postmark and webhooks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome Extension Integration&lt;/strong&gt; – Register new sales directly from your browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Management&lt;/strong&gt; – Add and manage team emails per project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project Configs&lt;/strong&gt; – Support for multiple real estate projects, with custom floors and apartments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-first&lt;/strong&gt; – Built on Supabase, deployable anywhere (Vercel, Netlify, your server).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile Friendly&lt;/strong&gt; – Works on all devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://youtu.be/Cc812fVl26I" title="Watch the full demo on YouTube" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9b6ecfb8b6b82bf738a0a2a47ab444479eb0c4c8ae53dd8a83cfb041233ac912/68747470733a2f2f696d672e796f75747562652e636f6d2f76692f436338313266566c3236492f6d617872657364656661756c742e6a7067" alt="Watch the demo on YouTube"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;▶️ &lt;strong&gt;Click the image above to watch the full demo on YouTube!&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://youtu.be/Cc812fVl26I" rel="nofollow noopener noreferrer"&gt;https://youtu.be/Cc812fVl26I&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;See the extension in action, real-time sales, notifications, and project management in under 5 minutes.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Setup&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clone the repository&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/jamesrmoro/light-estate-extension.git
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; light-estate-extension&lt;/pre&gt;

&lt;/div&gt;
&lt;ol start="2"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure your Supabase credentials&lt;/strong&gt;&lt;br&gt;
Open the file…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jamesrmoro/light-estate-extension" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  ⚙️ How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Chrome Extension: Sales Trigger
&lt;/h3&gt;

&lt;p&gt;The user registers sales directly from the browser, through a simple and intuitive interface.  &lt;/p&gt;

&lt;p&gt;Besides the fields for sale data, it is possible to register and manage the list of emails for collaborators who should be notified.  &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%2Fk0a7xtp0tsbof2b19d8v.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%2Fk0a7xtp0tsbof2b19d8v.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That way, &lt;strong&gt;whenever an admin or agent records a new sale&lt;/strong&gt;, all registered emails instantly receive both a Postmark email and a PWA notification.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Supabase: Cloud Database &amp;amp; Synchronization
&lt;/h3&gt;

&lt;p&gt;All sales are stored in &lt;strong&gt;Supabase&lt;/strong&gt;, which combines the robustness of PostgreSQL with modern real-time APIs.&lt;br&gt;&lt;br&gt;
I chose this platform precisely because of the easy integration with both the Chrome extension and the PWA:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows you to centralize all sales data, notifications, and collaborators in one place, accessible via API.&lt;/li&gt;
&lt;li&gt;Ensures any update made on one device is instantly reflected for everyone, with no infrastructure hassle.&lt;/li&gt;
&lt;li&gt;Comes with ready-to-use authentication, flexible access rules, and great documentation, accelerating development and scalability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thus, the experience of managing, querying, and notifying about sales becomes truly seamless, modern, and accessible across multiple devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Postmark: Transactional Email Engine
&lt;/h3&gt;

&lt;p&gt;Postmark is at the heart of communication for this ecosystem, ensuring that everyone involved receives information about new sales quickly, securely, and with a personal touch.&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%2F2txfl7rkzs2t4z9mp449.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%2F2txfl7rkzs2t4z9mp449.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beyond simple sending, you can create &lt;strong&gt;advanced HTML templates&lt;/strong&gt;, incorporating company branding, digital contracts, QR Codes for e-signatures, and dynamic information about each sale. This makes messages professional, engaging, and ready for a variety of business scenarios.&lt;/p&gt;

&lt;p&gt;Another major Postmark differentiator is its &lt;strong&gt;webhook&lt;/strong&gt; functionality.&lt;br&gt;&lt;br&gt;
Webhooks allow you to receive, in real time, events from Postmark itself, such as delivery confirmation, email opens, clicks, and sending failures.  &lt;/p&gt;

&lt;p&gt;With this, developers can automate processes, feed dashboards, update internal systems, and even trigger new actions whenever the status of a transactional email changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. PWA: Push Notifications
&lt;/h3&gt;

&lt;p&gt;In addition to the Chrome extension, the platform also works as a &lt;strong&gt;PWA (Progressive Web App)&lt;/strong&gt;, taking the user experience to the next level.&lt;/p&gt;

&lt;p&gt;The PWA offers the best of both worlds: it can be installed on desktop or mobile, works offline, and has performance close to a native app, but with all the flexibility of a web application.&lt;/p&gt;

&lt;p&gt;One of the great advantages of this approach is the ability to &lt;strong&gt;send real-time push notifications&lt;/strong&gt;. As soon as a sale is recorded, all connected collaborators — regardless of device or OS — immediately receive a push notification, keeping the entire team engaged and informed, even outside the main system.&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%2Fattk97r3uavif3ob12n9.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%2Fattk97r3uavif3ob12n9.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With the integration between push notifications and Postmark, the user is absolutely certain they have received an important email:&lt;/strong&gt; as soon as the push notification appears, they know that a new transactional email (via Postmark) has landed in their inbox, making it impossible to miss critical information about new sales.&lt;/p&gt;

&lt;p&gt;In daily practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The agent can be in the field and still instantly know about updates.&lt;/li&gt;
&lt;li&gt;The manager tracks sales performance without needing to open reports or access the system.&lt;/li&gt;
&lt;li&gt;The team celebrates every new win together, creating a more connected and motivated environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a modern, efficient, and truly omnichannel experience for real estate sales and management teams, with all the delivery reliability of Postmark and the convenience of push notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. IoT Integration (ESP32 Example)
&lt;/h3&gt;

&lt;p&gt;To make everything more fun, I built — following the same digital layout — a physical mockup of a building, easily produced with a 3D printer.  &lt;/p&gt;

&lt;p&gt;I used an &lt;strong&gt;addressable WS2812 LED strip&lt;/strong&gt; (commonly known as "NeoPixel") to represent the windows: &lt;strong&gt;each LED corresponds to an apartment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a sale is registered in the system, the corresponding window’s LED lights up, creating a physical, playful, and impactful visualization for the team to track sales in real time.&lt;/p&gt;

&lt;p&gt;This prototype can be replicated and customized according to the number of apartments/floors, serving as an excellent engagement piece for offices, events, and fairs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Chrome Extension (JavaScript)&lt;/li&gt;
&lt;li&gt;Node.js (API/backend)&lt;/li&gt;
&lt;li&gt;Supabase (database, authentication, real-time API)&lt;/li&gt;
&lt;li&gt;Postmark (email delivery)&lt;/li&gt;
&lt;li&gt;ESP32 (hardware integration)&lt;/li&gt;
&lt;li&gt;PWA (push notifications and offline support)&lt;/li&gt;
&lt;li&gt;(Optional: Firebase or other push service)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;My experience with Postmark exceeded expectations and was essential in creating a truly robust and professional product.&lt;/p&gt;

&lt;p&gt;Customizing the templates made it easy to standardize email visual identity, while Postmark's ultra-high deliverability guaranteed maximum confidence: important notifications and updates land &lt;strong&gt;directly in the recipient’s inbox&lt;/strong&gt;, with no risk of getting lost in spam or junk folders.&lt;/p&gt;

&lt;p&gt;This means essential information about each sale always arrives in the right place at the right time, strengthening communication, engagement, and efficiency for the sales team.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>✨ Challenge Feed: Real-Time Programming Challenges in One Place</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Wed, 21 May 2025 16:52:49 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/challenge-feed-real-time-programming-challenges-in-one-place-20bh</link>
      <guid>https://dev.to/jamesrmoro/challenge-feed-real-time-programming-challenges-in-one-place-20bh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/brightdata-2025-05-07"&gt;Bright Data AI Web Access Hackathon&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I created &lt;strong&gt;Challenge Feed&lt;/strong&gt;, a web application where you can discover the latest programming challenges from several popular platforms—all in one place. Developers often need to visit several sites and browse multiple pages to find new challenges. My goal was to simplify this process, making it faster, more centralized, and engaging.&lt;/p&gt;

&lt;p&gt;Challenge Feed collects challenges from different sources, stores and manages this data using &lt;strong&gt;Bright Data&lt;/strong&gt;, and displays everything in a modern interface with features like saving, liking, and viewing highlights in a stories format. Whether you’re looking for hackathons, code jams, or online competitions, you’ll now find it all in one place.&lt;/p&gt;




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

&lt;p&gt;You can try Challenge Feed live here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://challenge-feed.jamesrmoro.me" rel="noopener noreferrer"&gt;https://challenge-feed.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📺 &lt;strong&gt;Watch video:&lt;/strong&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/TlBO2Sjga58"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time feed with programming challenges from jams, hackathons, and online competitions&lt;/li&gt;
&lt;li&gt;Save and like challenges (using Appwrite for user sessions)&lt;/li&gt;
&lt;li&gt;Highlights in stories format, to view images and links&lt;/li&gt;
&lt;li&gt;Integrated AI-powered chat assistant (powered by together.ai) to help users discover and discuss challenges&lt;/li&gt;
&lt;li&gt;Responsive layout for mobile and desktop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, I’ve used only two example sources to power the Challenge Feed. These platforms were initially chosen for their relevance and diversity of programming challenges. However, the idea is to expand the project and integrate other sites in the future, making the feed even more complete and comprehensive for the developer community.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Used Bright Data's Infrastructure
&lt;/h2&gt;

&lt;p&gt;Bright Data’s Collector automatically gathers programming challenges from the sites I select, capturing opportunities on platforms that usually don’t appear in traditional feeds.&lt;/p&gt;

&lt;p&gt;Even on protected sites or those with dynamic content loading, Bright Data allows access to the necessary content to power the feed, overcoming common blocks found in traditional scraping.&lt;/p&gt;

&lt;p&gt;The data comes already structured, ready to be stored in Appwrite (an open source backend that handles authentication, database, and real-time data management) and displayed in the app, all in real time and at scale.&lt;/p&gt;

&lt;p&gt;What’s amazing is that Bright Data simulates a real user browsing the site—clicking, scrolling, and interacting with JavaScript elements when necessary—making it possible to capture challenges even on fully dynamic pages.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Improvements
&lt;/h2&gt;

&lt;p&gt;To keep the feed fast and scalable, I implemented some important optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Efficient Data Fetching:&lt;/strong&gt; Data is only fetched from Bright Data when truly needed, reducing processing and unnecessary requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized Database Reads:&lt;/strong&gt; Challenges are grouped and indexed in Appwrite, enabling fast queries and filters, as well as efficient pagination.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stack and Deployment
&lt;/h3&gt;

&lt;p&gt;Challenge Feed was built using a modern JavaScript ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js (React)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend &amp;amp; Database:&lt;/strong&gt; Appwrite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time data collection:&lt;/strong&gt; Bright Data (MCP)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversational AI:&lt;/strong&gt; together.ai&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting &amp;amp; Deployment:&lt;/strong&gt; Vercel, with a custom domain configured directly on the platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vercel made continuous deployment and custom domain setup extremely simple, allowing the app to be published in just a few minutes, production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using Bright Data made integrating and automating the Challenge Feed much easier—it made collecting and updating data from different sources simple and straightforward. &lt;/p&gt;

&lt;p&gt;Have you ever joined a challenge, hackathon, or jam? Share your experience in the comments!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>brightdatachallenge</category>
      <category>ai</category>
      <category>webdata</category>
    </item>
    <item>
      <title>⚔️ The Status Rebellion: An Epic Game About HTTP Codes</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sat, 10 May 2025 11:02:11 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/the-status-rebellion-an-epic-game-about-http-codes-1p66</link>
      <guid>https://dev.to/jamesrmoro/the-status-rebellion-an-epic-game-about-http-codes-1p66</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/aws-amazon-q-v2025-04-30"&gt;Amazon Q Developer "Quack The Code" Challenge&lt;/a&gt;: That's Entertainment!&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I created a game using &lt;strong&gt;HTML&lt;/strong&gt;, &lt;strong&gt;CSS&lt;/strong&gt;, and &lt;strong&gt;JavaScript&lt;/strong&gt; called &lt;strong&gt;The Status Rebellion&lt;/strong&gt;, with the goal of teaching HTTP status codes in a fun, interactive, and visual way.&lt;/p&gt;

&lt;p&gt;The player must drag cards representing each status into a central trap, where unique effects are triggered based on the meaning of each code.&lt;br&gt;&lt;br&gt;
During the game, players can &lt;strong&gt;test their knowledge with 14 examples of HTTP codes&lt;/strong&gt;, having only &lt;strong&gt;3 lives&lt;/strong&gt; — or, if they prefer, they can &lt;strong&gt;skip a card&lt;/strong&gt; and move on to the next.&lt;/p&gt;

&lt;p&gt;The game features a retro look, soundtrack, animations, and simple mechanics that make learning more engaging.&lt;/p&gt;

&lt;p&gt;To complement the project, I created a video that &lt;strong&gt;tells the story behind the game&lt;/strong&gt; and shows how tools like Amazon Q Developer can &lt;strong&gt;greatly help with the coding side&lt;/strong&gt;, while design focuses on creating interactive visual experiences. This separation speeds up the transformation of ideas into reality.&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%2Fblnbmmga1cb44hv995le.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%2Fblnbmmga1cb44hv995le.png" alt="The Status Rebellion"&gt;&lt;/a&gt;&lt;/p&gt;


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

&lt;p&gt;🕸️ &lt;strong&gt;Play now:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://the-status-rebellion.jamesrmoro.me" rel="noopener noreferrer"&gt;https://the-status-rebellion.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📺 &lt;strong&gt;Watch video:&lt;/strong&gt;&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/5uhJxm6deO4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  📁 Code Repository
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jamesrmoro" rel="noopener noreferrer"&gt;
        jamesrmoro
      &lt;/a&gt; / &lt;a href="https://github.com/jamesrmoro/the-status-rebellion" rel="noopener noreferrer"&gt;
        the-status-rebellion
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A retro-inspired HTML game to learn HTTP status codes like never before.
    &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;⚔️ HTTP - The Status Rebellion&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/93275f70aedf7567c5493fea7f4fc1fe8742d8af0e75671321a6047f712e5f4b/68747470733a2f2f7468652d7374617475732d726562656c6c696f6e2e6a616d6573726d6f726f2e6d652f696d616765732f636f7665722d6769746875622e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/93275f70aedf7567c5493fea7f4fc1fe8742d8af0e75671321a6047f712e5f4b/68747470733a2f2f7468652d7374617475732d726562656c6c696f6e2e6a616d6573726d6f726f2e6d652f696d616765732f636f7665722d6769746875622e706e67" alt="Game HTTP - The Status Rebellion"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A fun interactive game where you must return the HTTP status codes to their original universes. Drag each card into the HTTP TRAP in the center and observe the unique effects of each status.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;ℹ️ About the Game&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;HTTP The Status Rebellion is a game inspired by the true guardians of the internet - HTTP status codes. The game features six different status codes (200, 301, 302, 403, 404, and 500) that you need to send back to their respective universes by dragging them into the HTTP TRAP.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🎮 Play Now&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;👉 &lt;a href="https://the-status-rebellion.jamesrmoro.me" rel="nofollow noopener noreferrer"&gt;Play the game&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📺 Watch the Demo&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;👉 &lt;a href="https://youtu.be/5uhJxm6deO4" rel="nofollow noopener noreferrer"&gt;Watch the video&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;ul&gt;
&lt;li&gt;Interactive drag-and-drop gameplay&lt;/li&gt;
&lt;li&gt;Unique animations and sound effects for each status code&lt;/li&gt;
&lt;li&gt;Responsive design that works on both desktop and mobile devices&lt;/li&gt;
&lt;li&gt;Beautiful space-themed background with stars, shooting stars, and nebulas&lt;/li&gt;
&lt;li&gt;Retro console that displays game progress&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🧪 Technologies&lt;/h2&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jamesrmoro/the-status-rebellion" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;





&lt;h2&gt;
  
  
  ⚙️ How I Used Amazon Q Developer
&lt;/h2&gt;

&lt;p&gt;I used &lt;strong&gt;Amazon Q Developer&lt;/strong&gt; directly within &lt;strong&gt;Visual Studio Code&lt;/strong&gt; as a coding assistant throughout the entire development of the game.&lt;/p&gt;

&lt;p&gt;I started by installing the official extension and, on the very first command, the AI asked for permission to edit files. From that point on, the experience became smooth and collaborative.&lt;/p&gt;

&lt;p&gt;I described the general idea of the game and started requesting features in parts, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;animated starry sky&lt;/strong&gt; using only CSS and JS
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;drag and drop system&lt;/strong&gt;, with collision detection between card and trap
&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;custom modal&lt;/strong&gt; with animations and ESC key to close
&lt;/li&gt;
&lt;li&gt;Integration with the &lt;strong&gt;Swiper.js library&lt;/strong&gt; for card navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each feature, I asked Amazon Q to deliver a &lt;strong&gt;functional base&lt;/strong&gt; — and from there, I added my personal touch to the &lt;strong&gt;visual identity&lt;/strong&gt;, using my front-end knowledge to adjust colors, fonts, textures, icons, sound effects, and the desired retro style.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧩 Conclusion
&lt;/h2&gt;

&lt;p&gt;Amazon Q was essential for speeding up development and supporting me when doubts or technical blocks arose. The experience was like &lt;strong&gt;having a coding partner available at all times&lt;/strong&gt;, responding in natural language and helping turn the idea into reality.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>awschallenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🏰 Castle of Keys: API-First Access Control in a Physical-Digital Game</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sat, 03 May 2025 22:17:12 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/castle-of-keys-api-first-access-control-in-a-physical-digital-game-2f70</link>
      <guid>https://dev.to/jamesrmoro/castle-of-keys-api-first-access-control-in-a-physical-digital-game-2f70</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/permit_io"&gt;Permit.io Authorization Challenge&lt;/a&gt;: API-First Authorization Reimagined&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
What I Built

&lt;ul&gt;
&lt;li&gt;How It Works (Step-by-Step Flow)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Demo&lt;/li&gt;

&lt;li&gt;My Journey&lt;/li&gt;

&lt;li&gt;

API-First Authorization

&lt;ul&gt;
&lt;li&gt;ESP32 Code (Arduino)&lt;/li&gt;
&lt;li&gt;PHP Proxy for Permit.io (ESP32 Integration)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Castle of Keys&lt;/strong&gt; is an interactive, real-world access control game inspired by medieval castles and powered by externalized authorization using &lt;a href="https://www.permit.io/" rel="noopener noreferrer"&gt;Permit.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Players assume roles like King, Cook, or Servant and attempt to access different levels of a castle. Access is controlled by external policies — not hardcoded logic.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔄 How It Works (Step-by-Step Flow)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The player presents an RFID card to the reader connected to the ESP32.&lt;/li&gt;
&lt;li&gt;The ESP32 reads the UID and sends a request to &lt;code&gt;proxy.php&lt;/code&gt;, including the UID and desired action (e.g., &lt;code&gt;access_floor_2&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;proxy.php&lt;/code&gt; script securely forwards the request to the Permit.io API using a private token.&lt;/li&gt;
&lt;li&gt;Permit.io returns whether the access is allowed (&lt;code&gt;"allow": true&lt;/code&gt;) or not.&lt;/li&gt;
&lt;li&gt;The ESP32 interprets the response and activates the corresponding LED:

&lt;ul&gt;
&lt;li&gt;✅ Green = Access Granted&lt;/li&gt;
&lt;li&gt;❌ Red = Access Denied&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In parallel, the ESP32 sends the log to &lt;code&gt;log.php&lt;/code&gt; — used for visual display or debugging in the web interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;log.php&lt;/code&gt; is not required for Permit.io to function. It’s used to display access attempts inside the game UI.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;👉 Watch the full walkthrough demo (Web + Hardware):&lt;/p&gt;

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

&lt;p&gt;🎮 Or test the web version (no hardware required): &lt;strong&gt;&lt;a href="https://castleofkeys.jamesrmoro.me" rel="noopener noreferrer"&gt;Try the Castle of Keys (Web Demo)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 My Journey
&lt;/h2&gt;

&lt;p&gt;This project started as an idea to bring access control to life through gamification. I wanted to create something more than just a UI button or a permission table — something tangible, physical, and interactive. That’s why I decided to integrate real hardware like RFID readers and, soon, biometric sensors, to make the experience both fun and immersive. &lt;/p&gt;




&lt;h2&gt;
  
  
  🔐 API-First Authorization
&lt;/h2&gt;

&lt;p&gt;Below is the actual &lt;code&gt;.ino&lt;/code&gt; code running on my ESP32. It reads an RFID card using the RC522 module, sends the UID to a secure PHP proxy, and receives a real-time authorization decision from Permit.io.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔌 ESP32 Code (Arduino)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;WiFi.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;HTTPClient.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SPI.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;MFRC522.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ssid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_WIFI_NAME"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"YOUR_WIFI_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;permitUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/api/proxy.php"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// your private endpoint&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;logUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://example.com/app/log.php"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cp"&gt;#define RST_PIN 22
#define SS_PIN  21
&lt;/span&gt;
&lt;span class="n"&gt;MFRC522&lt;/span&gt; &lt;span class="nf"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SS_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RST_PIN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PCD_Init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connecting to WiFi"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;WiFi connected!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PICC_IsNewCardPresent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PICC_ReadCardSerial&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&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="n"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;i&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rfid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uidByte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;HEX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Card detected: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Make Permit.io authorization request&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WiFi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;WL_CONNECTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;HTTPClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permitUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;tenant&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;devs&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;},&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:{}}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;httpResponseCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&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="n"&gt;httpResponseCode&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="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Permit.io response: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:true"&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"✅ Access granted"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;accessGranted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// digitalWrite(GREEN_LED, HIGH);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"❌ Access denied"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// digitalWrite(RED_LED, HIGH);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error on sending request: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpResponseCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Send access log&lt;/span&gt;
    &lt;span class="n"&gt;HTTPClient&lt;/span&gt; &lt;span class="n"&gt;logHttp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;logHttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;logHttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;String&lt;/span&gt; &lt;span class="n"&gt;logPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;uid&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accessGranted&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"granted"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"denied"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;logHttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logPayload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;logHttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Prevent repeated scans&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP Proxy for Permit.io (ESP32 Integration)
&lt;/h3&gt;

&lt;p&gt;To keep your &lt;strong&gt;Permit.io API key safe&lt;/strong&gt;, I created a simple &lt;code&gt;proxy.php&lt;/code&gt; file. The ESP32 sends a request to this PHP script instead of calling Permit.io directly.&lt;/p&gt;

&lt;p&gt;Here’s the full code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// proxy.php - Receives data from ESP32 and checks permission via Permit.io&lt;/span&gt;

&lt;span class="c1"&gt;// Read the incoming JSON from ESP32&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'php://input'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Prepare the payload for Permit.io&lt;/span&gt;
&lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="s2"&gt;"user"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"key"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;// UID from RFID card&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="s2"&gt;"action"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Example: "access_floor_1"&lt;/span&gt;
  &lt;span class="s2"&gt;"resource"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"type"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"document"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Replace with your configured resource type&lt;/span&gt;
    &lt;span class="s2"&gt;"tenant"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"devs"&lt;/span&gt;      &lt;span class="c1"&gt;// Tenant ID (if applicable)&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="s2"&gt;"context"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;stdClass&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Additional context (empty for now)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Send the request to Permit.io&lt;/span&gt;
&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"https://api.permit.io/v2/allowed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"Authorization: Bearer YOUR_PERMIT_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Replace with your Permit.io API key&lt;/span&gt;
  &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$http_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Return the result back to the ESP32&lt;/span&gt;
&lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$http_code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Conclusion
&lt;/h3&gt;

&lt;p&gt;This project shows how easy it is to integrate real-world hardware with Permit.io. With just a few lines of code and external policies, you can control access securely — without hardcoding anything.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>permitchallenge</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>Make Your Game Faster with IMG + Alibaba Cloud CDN</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Sat, 26 Apr 2025 21:02:50 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/make-your-game-faster-with-img-alibaba-cloud-cdn-5cho</link>
      <guid>https://dev.to/jamesrmoro/make-your-game-faster-with-img-alibaba-cloud-cdn-5cho</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://int.alibabacloud.com/m/1000402443/" rel="noopener noreferrer"&gt;Alibaba Cloud&lt;/a&gt; Challenge: &lt;a href="https://dev.to/challenges/alibaba"&gt;Build a Web Game&lt;/a&gt;.&lt;/em&gt;*&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What I Built&lt;/li&gt;
&lt;li&gt;Live Demo link&lt;/li&gt;
&lt;li&gt;
Alibaba Cloud Services Implementation

&lt;ul&gt;
&lt;li&gt;Using the CDN to reduce latency&lt;/li&gt;
&lt;li&gt;Image processing via URL&lt;/li&gt;
&lt;li&gt;Automating with JS in the Game&lt;/li&gt;
&lt;li&gt;Creating Custom Styles in the Alibaba Console&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Game Development Highlights&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;Container Lost: Balance on the High Seas&lt;/strong&gt; is my third robot-themed game.&lt;/p&gt;

&lt;p&gt;Since this is my third project in the series, I was motivated to dive even deeper into Alibaba Cloud's features right from the start — especially focusing on performance and delivery optimizations.&lt;/p&gt;

&lt;p&gt;This time, the robot XR-77 — built for maintenance and combat — wakes up inside a drifting container after a shipwreck.. Deranged consoles, insane sound systems, and hostile drones also fell into the waters. XR-77 must keep his balance on the high seas, dodging falling objects and collecting power-ups to survive the storm — all while trapped in a container filled with uncontrollable products.&lt;/p&gt;

&lt;p&gt;🖼️ &lt;strong&gt;Screenshots:&lt;/strong&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%2Ful8l3pt6x8nu8br3eimp.jpg" 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%2Ful8l3pt6x8nu8br3eimp.jpg" alt="Gameplay" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo link
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Play now:&lt;/strong&gt;&lt;br&gt;
👉 &lt;a href="https://container-lost.jamesrmoro.me" rel="noopener noreferrer"&gt;https://container-lost.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Alibaba Cloud Services Implementation
&lt;/h2&gt;

&lt;p&gt;When I started working on my third robot-themed game, &lt;strong&gt;"Container Lost"&lt;/strong&gt;, one of my main concerns was loading time — especially on mobile connections. Since it's a static game with lots of images (sprites, backgrounds, HUD, effects), I knew I had to optimize everything I could.&lt;/p&gt;

&lt;p&gt;That's when I started exploring &lt;strong&gt;Alibaba Cloud's tools&lt;/strong&gt;, and ended up discovering two features that completely transformed the project’s performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Global CDN&lt;/strong&gt; integrated with OSS
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;image processing (IMG)&lt;/strong&gt; feature directly in the URL&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Using the CDN to reduce latency
&lt;/h3&gt;

&lt;p&gt;The first step was to enable the &lt;strong&gt;CDN on my OSS bucket&lt;/strong&gt;. This made all the assets (JS, images, sounds, etc.) get distributed across servers worldwide, improving response times.&lt;/p&gt;

&lt;p&gt;With the CDN, I also set up custom cache rules, like making the &lt;code&gt;index.html&lt;/code&gt; expire every 1 minute — not typical for static sites, but super useful during active development when I need to push quick updates and test changes in real-time.&lt;/p&gt;
&lt;h3&gt;
  
  
  Image processing via URL
&lt;/h3&gt;

&lt;p&gt;After enabling the CDN, I discovered a feature called &lt;strong&gt;IMG&lt;/strong&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%2For4qdb1e1qiebyhqp5b2.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%2For4qdb1e1qiebyhqp5b2.png" alt="Image description" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OSS panel on Alibaba Cloud showing the image processing section (IMG) — This is where you can configure custom styles to resize, convert, and optimize images directly via URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basically, you can add parameters directly in the image URL, like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://container-lost.jamesrmoro.me/assets/images/bg.jpg?x-oss-process=image/resize,m_fill,w_320,h_480/format,webp/quality,q_75&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With that, I can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize images for mobile devices&lt;/li&gt;
&lt;li&gt;Automatically convert them to WebP&lt;/li&gt;
&lt;li&gt;Reduce file size without editing or duplicating anything&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Automating with JS in the Game
&lt;/h3&gt;

&lt;p&gt;Since the WebP format offers better compression without losing quality, I wrote a simple script to apply this format to all images automatically — using the CDN URL with dynamic IMG parameters:&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;cdn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://container-lost.jamesrmoro.me/assets/images&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;ossSuffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?x-oss-process=image/format,webp&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;imagePaths&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;bg.jpg&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;shield.png&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;life.png&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;battery.png&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;combo.png&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;game.png&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;tv.png&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;drone.png&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;phone.png&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;sound.png&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;rain-1.png&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;rain-2.png&lt;/span&gt;&lt;span class="dl"&gt;"&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;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;cdn&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;name&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;ossSuffix&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;p&gt;With this, all images are served in WebP format, reducing file size without the need to duplicate files or modify anything on the backend. The conversion happens directly in the URL, thanks to Alibaba Cloud’s &lt;code&gt;x-oss-process&lt;/code&gt; feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Custom Styles in the Alibaba Console
&lt;/h3&gt;

&lt;p&gt;Alibaba Cloud allows you to create reusable image styles directly in the OSS panel, which makes it much easier to generate clean and consistent URLs in your code.&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%2Fs1cbkvsn3ygm68qq3si3.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%2Fs1cbkvsn3ygm68qq3si3.png" alt="Alibaba Cloud" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;IMG Parameter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;webp-only&lt;/td&gt;
&lt;td&gt;image/format,webp/quality,q_85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mobile&lt;/td&gt;
&lt;td&gt;image/resize,m_fill,w_320,h_480/format,webp/quality,q_75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tablet&lt;/td&gt;
&lt;td&gt;image/resize,m_fill,w_768,h_1024/format,webp/quality,q_80&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://container-lost.jamesrmoro.me/assets/images/bg.jpg?x-oss-process=style/mobile&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In my case, I created three simple styles with easy-to-remember names that I can call directly in the URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Development Highlights
&lt;/h2&gt;

&lt;p&gt;To handle responsive images efficiently, I used CSS variables (&lt;code&gt;:root&lt;/code&gt;) with Alibaba Cloud’s &lt;code&gt;x-oss-process&lt;/code&gt;. This lets me serve optimized WebP images directly via CSS, switching between styles based on screen size.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--intro-desktop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("https://container-lost.jamesrmoro.me/assets/images/intro-desktop.png?x-oss-process=image/format,webp")&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--intro-mobile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url("https://container-lost.jamesrmoro.me/assets/images/intro-mobile.png?x-oss-process=image/format,webp")&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;h4&gt;
  
  
  1. Background image of the title screen (desktop version)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;601px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.titleBackground&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--intro-desktop&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;code&gt;var(--intro-desktop)&lt;/code&gt; loads an optimized webp image for larger screens using &lt;code&gt;x-oss-process&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Background image of the title screen (mobile version)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.titleBackground&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--intro-mobile&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;code&gt;var(--intro-mobile)&lt;/code&gt; ensures mobile devices carry a lighter version of the background image automatically.&lt;/p&gt;

&lt;p&gt;The game was built entirely with &lt;strong&gt;CreateJS&lt;/strong&gt;, which I used to handle canvas rendering, sprite animations, real-time updates, and interactions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌊 Animated SVG waves and continuous rain effects
&lt;/li&gt;
&lt;li&gt;⚡ Explosions, sparks, and lightning with dynamic visual control
&lt;/li&gt;
&lt;li&gt;🛡️ Creative power-ups: shield, battery, explosive combo
&lt;/li&gt;
&lt;li&gt;🎮 Movement with easing, animated sprites, and direction flipping
&lt;/li&gt;
&lt;li&gt;📱 Mobile-first design with both touch and keyboard controls
&lt;/li&gt;
&lt;li&gt;📳 On mobile, the robot vibrates when hitting the edges of the container
&lt;/li&gt;
&lt;li&gt;💾 High Score system using &lt;code&gt;localStorage&lt;/code&gt;, with persistent scoring
&lt;/li&gt;
&lt;li&gt;🧊 Rust effect if the robot remains idle for 10 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I already expected a CDN to help, but what really made the difference was the ability to automate image delivery for each screen size — without plugins, without creating multiple image versions, and without setting up a backend.&lt;/p&gt;

&lt;p&gt;If you're building static games (or visually heavy websites), it's definitely worth exploring these features. In my case, I reduced mobile loading time by over 40% with just these optimizations.&lt;/p&gt;

</description>
      <category>alibabachallenge</category>
      <category>devchallenge</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Automatic Game Deployment on Alibaba Cloud Using GitHub Actions</title>
      <dc:creator>James Moro</dc:creator>
      <pubDate>Mon, 21 Apr 2025 21:01:25 +0000</pubDate>
      <link>https://dev.to/jamesrmoro/automatic-game-deployment-on-alibaba-cloud-using-github-actions-59pe</link>
      <guid>https://dev.to/jamesrmoro/automatic-game-deployment-on-alibaba-cloud-using-github-actions-59pe</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://int.alibabacloud.com/m/1000402443/" rel="noopener noreferrer"&gt;Alibaba Cloud&lt;/a&gt; Challenge: &lt;a href="https://dev.to/challenges/alibaba"&gt;Build a Web Game&lt;/a&gt;.&lt;/em&gt;*&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What I Built&lt;/li&gt;
&lt;li&gt;Live Demo link&lt;/li&gt;
&lt;li&gt;
Alibaba Cloud Services Implementation

&lt;ul&gt;
&lt;li&gt;Create Secrets on GitHub&lt;/li&gt;
&lt;li&gt;Create the Automatic Deploy Workflow&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Game Development Highlights&lt;/li&gt;

&lt;li&gt;

Conclusion &lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;In this article, you will learn how to set up an automatic deployment workflow to publish static websites directly to Alibaba Cloud (OSS) using GitHub Actions — without needing to use the local command line with &lt;code&gt;ossutil&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;a href="https://www.alibabacloud.com/" rel="noopener noreferrer"&gt;Alibaba Cloud&lt;/a&gt; account
&lt;/li&gt;
&lt;li&gt;An OSS bucket created and configured as a static website
&lt;/li&gt;
&lt;li&gt;A project with a &lt;code&gt;public/&lt;/code&gt; folder containing your files (e.g., &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;assets/&lt;/code&gt;, etc.)
&lt;/li&gt;
&lt;li&gt;A GitHub repository&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Live Demo Link
&lt;/h2&gt;

&lt;p&gt;👉 Play now: &lt;a href="https://arena-magnobot.jamesrmoro.me" rel="noopener noreferrer"&gt;https://arena-magnobot.jamesrmoro.me&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jamesrmoro" rel="noopener noreferrer"&gt;
        jamesrmoro
      &lt;/a&gt; / &lt;a href="https://github.com/jamesrmoro/arena-magnobot" rel="noopener noreferrer"&gt;
        arena-magnobot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;Arena Magnobot&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Arena Magnobot is a fast-paced, retro-style online game where you control a magnetic robot in a futuristic arena. Your mission is to dodge dangerous goods, collect valuable product power-ups, and survive as long as possible.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/f2c789fd20fb0c3155d8a82099042ff10538889a38cf7fb35961e5b205d90131/68747470733a2f2f6172656e612d6d61676e6f626f742e6a616d6573726d6f726f2e6d652f6173736574732f696d616765732f636f7665722e6a7067"&gt;&lt;img src="https://camo.githubusercontent.com/f2c789fd20fb0c3155d8a82099042ff10538889a38cf7fb35961e5b205d90131/68747470733a2f2f6172656e612d6d61676e6f626f742e6a616d6573726d6f726f2e6d652f6173736574732f696d616765732f636f7665722e6a7067" alt="Game running in browser"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A high-speed retro arena game where you control a magnetic robot and dodge dangerous products while collecting power-ups — built entirely with JavaScript and HTML5 Canvas!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Playable online &amp;amp; mobile-friendly&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Built with &lt;a href="https://createjs.com/" rel="nofollow noopener noreferrer"&gt;CreateJS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deployed automatically to &lt;strong&gt;Alibaba Cloud OSS&lt;/strong&gt; using &lt;strong&gt;GitHub Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Supports multiple languages (PT 🇧🇷 / EN 🇺🇸 / ZH 🇨🇳)&lt;/li&gt;
&lt;li&gt;PWA ready — install the game on your device and play offline!&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;About this project&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains the full source code and deployment workflow for &lt;strong&gt;Arena Magnobot&lt;/strong&gt;, a retro-inspired arena-style web game developed for the &lt;a href="https://dev.to/challenges/alibaba" rel="nofollow"&gt;Alibaba Cloud Web Game Challenge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the game, you command a magnetic robot that must collect power-ups and avoid hazardous products in…&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/jamesrmoro/arena-magnobot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  ☁️ Alibaba Cloud Services Implementation
&lt;/h2&gt;

&lt;p&gt;I've broken this tutorial down into 2 quick and easy steps to help you deploy your static content using Alibaba Cloud OSS with GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create Secrets on GitHub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In your GitHub repository:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New repository secret&lt;/strong&gt; and create &lt;strong&gt;one at a time&lt;/strong&gt; the following secrets:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&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;OSS_ENDPOINT&lt;/td&gt;
&lt;td&gt;e.g., &lt;code&gt;oss-eu-west-1.aliyuncs.com&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSS_BUCKET&lt;/td&gt;
&lt;td&gt;Your bucket name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSS_KEY_ID&lt;/td&gt;
&lt;td&gt;Your AccessKeyId&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSS_KEY_SECRET&lt;/td&gt;
&lt;td&gt;Your AccessKeySecret&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;For each secret, do the following:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Settings &amp;gt; Secrets and variables &amp;gt; Actions
&lt;/li&gt;
&lt;li&gt;Click "New repository secret"
&lt;/li&gt;
&lt;li&gt;Fill in the fields like this:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1st secret – &lt;code&gt;OSS_ENDPOINT&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: OSS_ENDPOINT
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: oss-eu-west-1.aliyuncs.com (or your bucket’s endpoint)
&lt;/li&gt;
&lt;li&gt;Click "Add secret"&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%2Fb35ixzoi2jc1sy24uo5e.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%2Fb35ixzoi2jc1sy24uo5e.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2nd secret – &lt;code&gt;OSS_BUCKET&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: OSS_BUCKET
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: your-bucket-name
&lt;/li&gt;
&lt;li&gt;Click "Add secret"&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%2Fpgqs4xqcp1mgy2qotxkn.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%2Fpgqs4xqcp1mgy2qotxkn.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3rd secret – &lt;code&gt;OSS_KEY_ID&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: OSS_KEY_ID
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: your Alibaba Cloud AccessKeyId
&lt;/li&gt;
&lt;li&gt;Click "Add secret"&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%2Fbwu3921hu4b3kxcwdr8g.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%2Fbwu3921hu4b3kxcwdr8g.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4th secret – &lt;code&gt;OSS_KEY_SECRET&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: OSS_KEY_SECRET
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret&lt;/strong&gt;: your Alibaba Cloud AccessKeySecret
&lt;/li&gt;
&lt;li&gt;Click "Add secret"&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%2Fbflpqs1sv71gjfzajt03.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%2Fbflpqs1sv71gjfzajt03.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the four secrets, move on to the next step to create the automatic deploy workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create the Automatic Deploy Workflow
&lt;/h3&gt;

&lt;p&gt;Now that your secrets are ready, it's time to tell GitHub Actions &lt;strong&gt;what to do&lt;/strong&gt; every time you push changes to your code.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Create the workflow file
&lt;/h4&gt;

&lt;p&gt;Inside your project, create the following folder structure (if it doesn’t exist yet):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside this folder, create a file named:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Paste the following content into &lt;code&gt;deploy.yml&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This script tells GitHub Actions to run a deployment process every time a push is made to the &lt;code&gt;main&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Alibaba OSS&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download and configure ossutil&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -O https://gosspublic.alicdn.com/ossutil/1.7.16/ossutil64&lt;/span&gt;
          &lt;span class="s"&gt;chmod +x ossutil64&lt;/span&gt;
          &lt;span class="s"&gt;./ossutil64 config \&lt;/span&gt;
            &lt;span class="s"&gt;-e ${{ secrets.OSS_ENDPOINT }} \&lt;/span&gt;
            &lt;span class="s"&gt;-i ${{ secrets.OSS_KEY_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;-k ${{ secrets.OSS_KEY_SECRET }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to OSS&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;./ossutil64 cp -r public/ oss://${{ secrets.OSS_BUCKET }}/ --force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this workflow does&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Checks out your latest code from the main branch.&lt;/li&gt;
&lt;li&gt;Step 2: Downloads ossutil, the CLI tool used to interact with Alibaba OSS.&lt;/li&gt;
&lt;li&gt;Step 3: Configures ossutil using your secrets (already set in Step 1).&lt;/li&gt;
&lt;li&gt;Step 4: Uploads all files from the public/ folder to your OSS bucket, replacing the previous content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this file is added and committed, any push to the main branch will automatically trigger the deployment process.&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%2F3didkg27ghjchqm8q5pd.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%2F3didkg27ghjchqm8q5pd.png" alt="GitHub Actions workflow running"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The GitHub Actions workflow running the automatic deployment to Alibaba Cloud OSS.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🎮 Game Development Highlights
&lt;/h2&gt;

&lt;p&gt;This is my second game submission for the same &lt;a href="https://dev.to/challenges/alibaba"&gt;Alibaba Cloud challenge&lt;/a&gt;, and this time I decided to take a different approach. Instead of focusing solely on gameplay and retro aesthetics, I explored modern web technologies by implementing the game as a PWA (Progressive Web App).&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%2Ff2yhljbja14fvgz2fawj.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%2Ff2yhljbja14fvgz2fawj.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to enhancing the mobile experience with features like home screen installation and offline support, this was a strategic choice: in the future, the same game could be published on the Play Store using TWA (Trusted Web Activity), without the need to rewrite the code in another language.&lt;/p&gt;

&lt;p&gt;You can check out my first game &lt;a href="https://dev.to/jamesrmoro/how-to-host-a-game-on-alibaba-cloud-using-oss-and-ossutil-5el6"&gt;here&lt;/a&gt; on the &lt;strong&gt;Alibaba Cloud challenge&lt;/strong&gt; page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This project was developed to demonstrate the capabilities of web technologies in a lightweight, fully browser-based gaming environment. Key highlights of the implementation include:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;✨ &lt;strong&gt;Progressive Web App (PWA)&lt;/strong&gt;: Playable instantly from your browser with offline support and installable on mobile devices.&lt;/li&gt;
&lt;li&gt;🧱 &lt;strong&gt;Canvas-based gameplay&lt;/strong&gt;: Developed entirely with CreateJS to deliver smooth 2D animations and real-time interactions.&lt;/li&gt;
&lt;li&gt;📲 &lt;strong&gt;Responsive controls&lt;/strong&gt;: Designed for all devices — keyboard support for desktops and tap-based input for touchscreens.&lt;/li&gt;
&lt;li&gt;🔊 &lt;strong&gt;Dynamic audio&lt;/strong&gt;: Background music and sound effects enhance immersion, with a simple toggle for sound control.&lt;/li&gt;
&lt;li&gt;📐 &lt;strong&gt;Orientation guidance&lt;/strong&gt;: Automatically detects mobile landscape mode and prompts users to rotate their device for the best experience.&lt;/li&gt;
&lt;li&gt;🏆 &lt;strong&gt;Local high score tracking&lt;/strong&gt;: Challenge yourself to improve, as your highest score is saved directly in the browser.&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;Internationalization built-in&lt;/strong&gt;: Language selection right at the start — currently supporting Portuguese, English, and Chinese.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Every time you push to the main branch, GitHub Actions will automatically handle deployment to Alibaba Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and configure ossutil – the official CLI for interacting with Alibaba OSS&lt;/li&gt;
&lt;li&gt;Upload all files from the public/ folder to your designated bucket&lt;/li&gt;
&lt;li&gt;Deploy your static website live on Alibaba Cloud OSS – no manual steps required!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can track the full process via the Actions tab in your GitHub repository.&lt;/p&gt;

</description>
      <category>alibabachallenge</category>
      <category>devchallenge</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
