<?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: Eoin McGrath</title>
    <description>The latest articles on DEV Community by Eoin McGrath (@eoinmcg).</description>
    <link>https://dev.to/eoinmcg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1836780%2F0910e6bc-964a-4ecc-920c-aa25a1824b3e.png</url>
      <title>DEV Community: Eoin McGrath</title>
      <link>https://dev.to/eoinmcg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eoinmcg"/>
    <language>en</language>
    <item>
      <title>Walktober</title>
      <dc:creator>Eoin McGrath</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:19:42 +0000</pubDate>
      <link>https://dev.to/eoinmcg/walktober-5e30</link>
      <guid>https://dev.to/eoinmcg/walktober-5e30</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Walktober is a small reason to go outside. Photograph what you notice, complete quests, and fill your scrapbook with the season. Then put your phone away.&lt;/p&gt;
&lt;/blockquote&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxygqru4nj9jwgohsb45.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzxygqru4nj9jwgohsb45.png" alt="Screenshot of Walktober homepage" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a pic of something notable on your autumnal ramblings. It is then converted into a sticker and added into your scrapbook.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://walktober.eoinmcgrath.com" rel="noopener noreferrer"&gt;🍂 walktober.eoinmcgrath.com&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/eoinmcg/walktober" rel="noopener noreferrer"&gt;💻️ github.com/eoinmcg/walktober&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Everything runs in the browser, with no server and no API calls, and it works offline once the files are cached. All the AI is open-weight and runs through &lt;a href="https://en.wikipedia.org/wiki/Open_Neural_Network_Exchange" rel="noopener noreferrer"&gt;ONNX&lt;/a&gt; Runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cutout and sticker: U²-Net-P&lt;/strong&gt;&lt;br&gt;
The image is separated from its background by U²-Net-P, a small (about 4.5 MB, Apache-2.0) salient-object-detection network. It runs through onnxruntime-web (MIT) on WebAssembly inside a Web Worker, so the UI stays responsive. The worker decodes the photo, resizes it to 320×320, normalises it, runs the model, and sends back a soft mask. The main thread turns that mask into alpha. The sticker (rounded white border, thin black outline, drop shadow) is plain canvas code on top of that alpha. It involves no extra model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classification and matching:&lt;/strong&gt; &lt;a href="https://huggingface.co/transformers/v4.8.0/model_doc/clip.html" rel="noopener noreferrer"&gt;CLIP&lt;/a&gt; via transformers.js&lt;br&gt;
In order to make things interesting I decided to add a number of &lt;a href="https://github.com/eoinmcg/walktober/blob/main/public/data/quests.json" rel="noopener noreferrer"&gt;quests&lt;/a&gt;. Basically, take a pic of an acorn, mushroom etc. This requires a way of matching the sticker to the quest. I first tried MobileNetV2, which is lightweight but only has around 1000 ImageNet classes. This simply didn't cut it when attempting to classify images. After some experimentation, I settled on CLIP (Xenova/clip-vit-base-patch32, an ONNX conversion of OpenAI’s MIT-licensed model) through @huggingface/transformers. CLIP embeds images and text into the same vector space, so the cutout can be compared against any text description by similarity, without retraining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text embeddings:&lt;/strong&gt; precomputed at build time&lt;br&gt;
scripts/build-text-embeddings.ts loads CLIP’s text encoder in Node (AutoTokenizer plus CLIPTextModelWithProjection) and embeds every quest from quests.json once. It writes the vectors to public/embeddings/text-embeddings.json, rounded to 5 decimals to keep the file small. At runtime the browser loads only the vision encoder and compares the cutout’s embedding with those stored vectors. The text encoder never ships to the phone, which saves download size and memory. The build script and the runtime must use the same CLIP model, or the vectors live in different spaces and the similarities mean nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Purely Local &amp;amp; Offline-First
&lt;/h3&gt;

&lt;p&gt;The core mandate of Walktober was 100% on-device execution. A closed-source API couldn't satisfy this requirement. Because model files and WebAssembly runtimes are served as standard static assets, the app operates without reliance on external servers. Photos are processed locally - ensuring privacy, instant response times, and uninterrupted outdoor use without requiring cell coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Freedom
&lt;/h3&gt;

&lt;p&gt;Open weights allow developers to reshape system architecture rather than merely call remote endpoints. By leveraging CLIP's decoupled vision and text encoders, I offloaded text encoding to build time. Furthermore, using open weights meant being able to swap U²-Net-P (4.7 MB) for larger alternatives or replace MobileNet with CLIP freely. Such decisions aren't possible with black-box APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debuggability &amp;amp; Inspectability
&lt;/h3&gt;

&lt;p&gt;Open source makes edge cases solvable. When the classifier threw an unexpected JSON parse error, I diagnosed it directly by inspecting the &lt;code&gt;transformers.js&lt;/code&gt; source code. I discovered that the library treated image classification as a multimodal task by looking for a tokenizer file; my development server was catching missing requests and returning an HTML fallback page with a &lt;code&gt;200&lt;/code&gt; HTTP status code, triggering the JSON error. Understanding this permitted a clean workaround building the pipeline directly from its individual component parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seamless Composition
&lt;/h3&gt;

&lt;p&gt;U²-Net, CLIP, ONNX Runtime, &lt;code&gt;transformers.js&lt;/code&gt;, and Nuxt originate from distinct maintainers, yet seamlessly interoperate through open standards (like ONNX) and permissive open-source licenses. Each piece remains independently swappable—such as introducing a plant-specific classifier later—without rewriting the core application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;General Category&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Why I built a local-first PDF toolkit using Nuxt and OPFS</title>
      <dc:creator>Eoin McGrath</dc:creator>
      <pubDate>Tue, 06 Oct 2026 09:25:51 +0000</pubDate>
      <link>https://dev.to/eoinmcg/why-i-built-a-local-first-pdf-toolkit-using-nuxt-and-opfs-54k3</link>
      <guid>https://dev.to/eoinmcg/why-i-built-a-local-first-pdf-toolkit-using-nuxt-and-opfs-54k3</guid>
      <description>&lt;p&gt;Every time you want to compress an NDA, sign a lease agreement, or compare contract drafts online, mainstream tools (like Smallpdf or ILovePDF) ask you to do the exact same thing: &lt;strong&gt;upload your sensitive document to their remote server.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even when services use encryption in transit and promise to delete files after a limited period, uploading confidential documents to a remote server still means entrusting those documents to a third-party service. &lt;strong&gt;Encryption in transit protects the transfer; it doesn't eliminate the need to trust the system that receives and processes the document.&lt;/strong&gt; Furthermore, depending on the type of files and your use case (e.g., legal or medical), uploading them to a third-party service may not be an option due to privacy, confidentiality, or regulatory requirements.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;&lt;a href="https://beepdf.app" rel="noopener noreferrer"&gt;beePDF&lt;/a&gt;&lt;/strong&gt;, a 100% client-side Progressive Web App (PWA) where document processing happens entirely in the browser and documents are never uploaded to a remote server.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of how it works, why I chose &lt;strong&gt;OPFS&lt;/strong&gt; coupled with IndexedDB, and the architectural tradeoffs of building local first document tools on the modern web platform.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Nuxt (Vue 4, TypeScript, Vite)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PWA &amp;amp; Offline Support:&lt;/strong&gt; &lt;code&gt;@vite-pwa/nuxt&lt;/code&gt; + Service Workers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Memory File Processing:&lt;/strong&gt; &lt;code&gt;pdf-lib&lt;/code&gt;, &lt;code&gt;PDF.js&lt;/code&gt;, native Canvas API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Persistence:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system" rel="noopener noreferrer"&gt;Origin Private File System&lt;/a&gt; (&lt;code&gt;navigator.storage.getDirectory()&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling &amp;amp; UI:&lt;/strong&gt; &lt;a href="https://picocss.com/" rel="noopener noreferrer"&gt;Pico CSS&lt;/a&gt;. Yes, I'm a dinosaur that prefers to write minimal CSS rather than reaching for tailwind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having previously worked with Next.js, I have to say that &lt;a href="https://nuxt.com/" rel="noopener noreferrer"&gt;Nuxt&lt;/a&gt; is a breath of fresh air. If you haven't already taken it for a spin, I cannot recommend it highly enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Why &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system" rel="noopener noreferrer"&gt;OPFS&lt;/a&gt; (Origin Private File System) Changed Everything
&lt;/h2&gt;

&lt;p&gt;When building a local-first web app that handles binary documents, local persistence becomes a major bottleneck:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LocalStorage:&lt;/strong&gt; Limited to ~5MB-10MB and synchronous text-only storage completely useless for binary PDFs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IndexedDB:&lt;/strong&gt; Excellent for structured metadata and smaller binary objects, but less natural for treating large documents as persistent files with efficient byte-level access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File System Access API:&lt;/strong&gt; Great when an app needs to work directly with files in the user's filesystem, but access requires user permission and is subject to browser security restrictions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Enter OPFS
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Origin Private File System (OPFS)&lt;/strong&gt; provides a private, origin-scoped filesystem designed for persistent file-like data, with low-level byte access and optimized in-place writes. It also offers synchronous access handles inside Web Workers for performance-sensitive workloads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Accessing the local OPFS root directory&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDirectory&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;draftHandle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFileHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;working-draft.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;create&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;// Writing binary data stream directly to local disk&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;draftHandle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createWritable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdfArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why OPFS is crucial for PDF processing:
&lt;/h2&gt;

&lt;p&gt;Zero Permission Prompts: Because the directory is private to &lt;a href="https://beepdf.app" rel="noopener noreferrer"&gt;https://beepdf.app&lt;/a&gt;, the browser doesn't spam the user with permission modals.&lt;/p&gt;

&lt;p&gt;Blazing Fast Disk I/O: OPFS provides direct byte-level file streams. In Web Workers, you can even use createSyncAccessHandle() for near-native, synchronous file read/write operations.&lt;/p&gt;

&lt;p&gt;Storage Quotas &amp;amp; Cleanup: Files persist across browser reloads (so you don't lose your working document if you accidentally close the tab), but automatically honor the browser's origin storage quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ How the Processing Pipeline Works
&lt;/h2&gt;

&lt;p&gt;When you drag and drop a PDF into beePDF:&lt;/p&gt;

&lt;p&gt;Local Intake: The browser reads the File object into an ArrayBuffer in memory.&lt;/p&gt;

&lt;p&gt;OPFS: The document is immediately stored in the local OPFS workspace directory.&lt;/p&gt;

&lt;p&gt;Meta data and a very small thumbnail are stored in indexDB. This also makes searching and tagging trivial.&lt;/p&gt;

&lt;p&gt;Web Worker Offloading: Tasks like rendering page thumbnails, extracting plain text / Markdown, or running word-level diffs are offloaded to Web Workers to keep the UI running at 60 FPS.&lt;/p&gt;

&lt;p&gt;Client-Side Export: Modifying or signing a document creates a fresh Uint8Array in memory, which triggers a direct client download via a URL.createObjectURL Blob.&lt;/p&gt;

&lt;p&gt;Airplane Mode Test: You can load beePDF, turn off your Wi-Fi or disconnect your internet completely, and merge, split, compress, or sign documents offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Challenges &amp;amp; Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Modern browsers provide a remarkably rich set of capabilities, many of which can be accessed by web applications through standardized Web APIs. Building pure client-side file tools, however, isn't without its limits:&lt;/p&gt;

&lt;p&gt;Browser Memory Ceilings: Processing massive 500MB+ scanned PDFs inside browser JS can hit V8 heap memory limits on lower-end devices.&lt;/p&gt;

&lt;p&gt;Main Thread Blocking: Heavy image compression or canvas manipulation must be offloaded to Web Workers; otherwise, rendering high-DPI canvases will freeze the UI thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  💬 Let's Discuss!
&lt;/h2&gt;

&lt;p&gt;I’d love to get feedback from the DEV community!&lt;/p&gt;

&lt;p&gt;Have you used OPFS in production yet? How does it compare to IndexedDB for your use cases?&lt;/p&gt;

&lt;p&gt;What client-side tools or WASM integrations would you like to see next?&lt;/p&gt;

&lt;p&gt;Check out the live app here: &lt;a href="https://beepdf.app" rel="noopener noreferrer"&gt;https://beepdf.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>nuxt</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Frontend Challenge: World Asteroid Day, 30th June</title>
      <dc:creator>Eoin McGrath</dc:creator>
      <pubDate>Mon, 23 Jun 2025 19:05:43 +0000</pubDate>
      <link>https://dev.to/eoinmcg/frontend-challenge-world-asteroid-day-30th-june-l7c</link>
      <guid>https://dev.to/eoinmcg/frontend-challenge-world-asteroid-day-30th-june-l7c</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, Perfect Landing: June Celebrations&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A celebration of World Asteroid Day, 30th June&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://eoinmcgrath.com/asteroid-day/" rel="noopener noreferrer"&gt;🌌 View live here&lt;/a&gt;
&lt;/h3&gt;

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

&lt;p&gt;I've wanted to learn Tailwind CSS for a while, so this challenge was the perfect opportunity to expand my skills. Space has always fascinated me, so choosing &lt;strong&gt;World Asteroid Day&lt;/strong&gt; as my inspiration was a natural fit. My goal was to create an educational and inspiring site for a younger audience, which led to this interactive landing page celebrating the day.&lt;/p&gt;

&lt;p&gt;It Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☄️ Real asteroid field&lt;/li&gt;
&lt;li&gt;🚀 Space travel&lt;/li&gt;
&lt;li&gt;ℹ️ Interesting facts&lt;/li&gt;
&lt;li&gt;❓ Interactive quiz&lt;/li&gt;
&lt;li&gt;🧑‍🎓 Downloadable personalized certificate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tech used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;React with typescript&lt;/li&gt;
&lt;li&gt;R3F for animated asteroid field&lt;/li&gt;
&lt;li&gt;Tailwind CSS for styling&lt;/li&gt;
&lt;li&gt;Google Gemini (free plan) for image generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had a blast creating this landing page, and I hope you enjoy it too!&lt;/p&gt;

&lt;p&gt;MIT license&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/eoinmcg/asteroid-day" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy4xyg7r0u1qkqj1l4fn.webp" 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%2Fqy4xyg7r0u1qkqj1l4fn.webp" alt="Ceres - the largest asteroid in the asteroid belt" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>css</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Code a mobile friendly game with LittleJS</title>
      <dc:creator>Eoin McGrath</dc:creator>
      <pubDate>Fri, 28 Feb 2025 11:55:22 +0000</pubDate>
      <link>https://dev.to/eoinmcg/code-a-mobile-friendly-game-with-littlejs-tutorial-2829</link>
      <guid>https://dev.to/eoinmcg/code-a-mobile-friendly-game-with-littlejs-tutorial-2829</guid>
      <description>&lt;p&gt;Hey all! I hacked together a super simple game last weekend, which I thought lent itself to a tutorial.&lt;br&gt;
So, I also wrote up a tutorial, which can be found here:&lt;br&gt;
&lt;a href="https://eoinmcg.github.io/little-ski/tutorial.html" rel="noopener noreferrer"&gt;https://eoinmcg.github.io/little-ski/tutorial.html&lt;/a&gt;&lt;br&gt;
and the game: &lt;br&gt;
&lt;a href="https://eoinmcg.github.io/little-ski" rel="noopener noreferrer"&gt;https://eoinmcg.github.io/little-ski&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If anyone has the time and inclination to check out the tutorial, I'd really appreciate any feedback!&lt;br&gt;
Thanks!&lt;/p&gt;

&lt;p&gt;p.s. the github repo: &lt;a href="https://github.com/eoinmcg/little-ski/" rel="noopener noreferrer"&gt;https://github.com/eoinmcg/little-ski/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fepr9znxlyu1r0gsqrjow.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%2Fepr9znxlyu1r0gsqrjow.png" alt="Title Screen" width="320" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zi9m3dd9j5ia7fonukt.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%2F4zi9m3dd9j5ia7fonukt.png" alt="Game play" width="320" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Mountains</title>
      <dc:creator>Eoin McGrath</dc:creator>
      <pubDate>Fri, 02 Aug 2024 15:24:07 +0000</pubDate>
      <link>https://dev.to/eoinmcg/mountains-jeb</link>
      <guid>https://dev.to/eoinmcg/mountains-jeb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2024-07-24"&gt;Frontend Challenge v24.07.24&lt;/a&gt;, CSS Art: Recreation.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;As developers we can spend too long in front of our screens.&lt;br&gt;
Take a break. Look up!&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://eoinmcgrath.com/mountains/" rel="noopener noreferrer"&gt;See it in action.&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I expanded my knowledge of CSS animations, that's for sure.&lt;br&gt;
On previous projects, I've only scratched the surface of what can be achieved with CSS animations. This exercise give me a chance to experiment. And most of all, have fun ;)&lt;/p&gt;

</description>
      <category>frontendchallenge</category>
      <category>devchallenge</category>
      <category>css</category>
    </item>
  </channel>
</rss>
