<?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: Henrique Ramos</title>
    <description>The latest articles on DEV Community by Henrique Ramos (@hnrq).</description>
    <link>https://dev.to/hnrq</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%2F382304%2F36f88799-4695-43fd-9bf0-07c9007fc7c1.png</url>
      <title>DEV Community: Henrique Ramos</title>
      <link>https://dev.to/hnrq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hnrq"/>
    <language>en</language>
    <item>
      <title>Building Veloren SA: An Astro blog fed by a Google Cloud Translation Pipeline</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Sat, 30 Aug 2025 15:43:50 +0000</pubDate>
      <link>https://dev.to/hnrq/building-veloren-sa-an-astro-blog-fed-by-google-cloud-translation-pipeline-150</link>
      <guid>https://dev.to/hnrq/building-veloren-sa-an-astro-blog-fed-by-google-cloud-translation-pipeline-150</guid>
      <description>&lt;p&gt;As the owner of a long-standing &lt;a href="https://veloren.net/" rel="noopener noreferrer"&gt;Veloren&lt;/a&gt; community server in South America, I had a cool idea: create a front page to keep non-English-speaking players updated on the latest devlogs. The community was missing out on the latest news from the official Veloren team since devlogs are only in English. My goal here was to get them translated to Spanish and Portuguese. Devlogs, however, are huge! If I only I could automate this...&lt;/p&gt;

&lt;p&gt;Turns out, I could!&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The pipeline is an event-based system using Google Cloud services. It works in three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion:&lt;/strong&gt; A Cloud function runs weekly via Google Cloud Scheduler. It checks the RSS feed for new devlogs, saving its HTML content into a Cloud Storage bucket. To avoid reprocessing old posts, the service first downloads a file containing a list of previously processed URLs. It then filters out any posts that have been seen before, ensuring only new content enters the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation:&lt;/strong&gt; A second Cloud Function is automatically triggered whenever a new file is saved to the ingestion bucket. It starts a batch &lt;strong&gt;Cloud Translation&lt;/strong&gt; job to convert the HTML files into Spanish and Portuguese, then saves the translated files into a second Cloud Storage bucket. To prevent concurrency errors that can occur when multiple files are uploaded at once, the function uses a unique timestamp for each job's output directory, ensuring all files are translated successfully.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishing:&lt;/strong&gt; A third Cloud Function watches the translation output bucket. When new translated files appear, it downloads them, and extracts the post's metadata from the HTML, including the title, publication date, and URL. It then combines the metadata and the raw HTML content into a single JSON file, which is saved to a third bucket. This JSON structure is perfectly suited for a content collection in Astro. The last thing it does is trigger Netlify's build hook, getting the new posts live on the website.&lt;/li&gt;
&lt;/ol&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%2F3e7ubx9fbudbck2oe150.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%2F3e7ubx9fbudbck2oe150.png" alt="Service architecture diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nx Monorepo with pnpm workspaces:&lt;/strong&gt; This setup made managing functions so much easier. It put all code in one place, simplified testing, and gave a clean way to handle deployment with GitHub Actions. Adopting a monorepo meant that services could share dependencies and tooling, which reduced boilerplate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Functions:&lt;/strong&gt; Using Cloud functions gave a simple, event-based system. It integrates seamlessly with Cloud Storage, which was key to the pipeline. This architecture also ensures that we only pay for the resources used when a function is triggered, making it extremely cost-effective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud Translation API (Batch Mode):&lt;/strong&gt; This was a perfect fit for this kind of work. It’s designed to translate large volumes of documents offline, which is far more efficient than processing files one at a time. The API's ability to handle HTML directly means we didn't have to write any complex parsing logic to preserve the original formatting, images, or links within the devlogs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Netlify Build Hooks:&lt;/strong&gt; This simple feature was the final piece of the puzzle. It gives a straightforward way to trigger a website rebuild from GCS, completing the automated loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astro:&lt;/strong&gt; The final content is converted to JSON files so Astro can consume them as a content collection. This is a powerful feature of Astro that simplifies building a static content-heavy website.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Astro Integration
&lt;/h2&gt;

&lt;p&gt;One of the coolest parts of this project was using Astro's Content Collections to pull in the remote JSON data. Instead of manually downloading and managing files, I configured Astro to do it all during the build process.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;src/content.config.ts&lt;/code&gt; file tells Astro where to find the content and what to do with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro:content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FileMetadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google-cloud/storage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;he&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;he&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;blog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VELOREN_ARTICLES_URL&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;data&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;mediaLink&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;FileMetadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaLink&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;he&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;allowUnsafeSymbols&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="na"&gt;language&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;source_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&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;pt-br&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="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;blog&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The loader function uses fetch and await to get a list of all the JSON files from my Cloud Storage bucket.
&lt;/li&gt;
&lt;li&gt;It then iterates over the list, fetching the content of each JSON file.
&lt;/li&gt;
&lt;li&gt;A z.object from the Zod library acts as a schema to validate the incoming data, ensuring every post has a title, date, content, and the correct language.&lt;/li&gt;
&lt;li&gt;The cleaned-up and validated data is then returned as a collection of blog posts. Astro automatically handles the rest, creating static pages for each post during the build process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach gives me the best of both worlds: a dynamic, automated content pipeline and a fast, static-first website.&lt;/p&gt;
&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;The final result can be found &lt;a href="https://veloren.net.br" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The complete source code for my project is available on GitHub. You can explore the project structure, the TypeScript code for each function, and the GitHub Actions workflow that automates the deployment.&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/hnrq" rel="noopener noreferrer"&gt;
        hnrq
      &lt;/a&gt; / &lt;a href="https://github.com/hnrq/veloren-translate" rel="noopener noreferrer"&gt;
        veloren-translate
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GCP infrastructure to translate TWIV posts to pt-BR and ES
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&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/hnrq" rel="noopener noreferrer"&gt;
        hnrq
      &lt;/a&gt; / &lt;a href="https://github.com/hnrq/veloren-website" rel="noopener noreferrer"&gt;
        veloren-website
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Veloren South America website
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://app.netlify.com/projects/velorensa/deploys" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/18645533e4bf6bd640f910d16a2b70dc1305b406c650a1bdfbdafa8483a18168/68747470733a2f2f6170692e6e65746c6966792e636f6d2f6170692f76312f6261646765732f37643361356332622d393031312d343731382d613365372d3262363639343463313433612f6465706c6f792d737461747573" alt="Netlify Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Veloren South America&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This is the Front-end for my &lt;a href="https://github.com/hnrq/veloren-translate" rel="noopener noreferrer"&gt;veloren-translate&lt;/a&gt; project. It uses Astro for building pages and is available &lt;a href="https://veloren.net.br" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hnrq/veloren-website" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Also, feel free to join the server at anytime at &lt;strong&gt;play.veloren.net.br&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>astro</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creating a Slide repository with Astro and Reveal.js</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Thu, 27 Feb 2025 13:00:00 +0000</pubDate>
      <link>https://dev.to/hnrq/at-last-revealjs-multi-deck-page-powered-by-astro-2ki</link>
      <guid>https://dev.to/hnrq/at-last-revealjs-multi-deck-page-powered-by-astro-2ki</guid>
      <description>&lt;p&gt;Reveal.js is a powerful tool for creating beautiful slide presentations using web technologies. However, managing individual repositories for each presentation becomes increasingly difficult as the collection expands.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; - the modern framework for content-driven websites. Astro delivers zero-JS-by-default, &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Islands Architecture&lt;/a&gt; for partial hydration, file-based routing, TS and multi-framework (React, Vue, Svelte, etc.) support. These features make it a perfect tool for what we'll be building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;We'll create an Astro website that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Centralizes all Reveal.js presentations in one place&lt;/li&gt;
&lt;li&gt;Provides a main page listing all available slides&lt;/li&gt;
&lt;li&gt;Supports multiple UI frameworks for interactive demos&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let's Get Started!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create an Astro Project
&lt;/h3&gt;

&lt;p&gt;First, let's scaffold a new Astro project with &lt;code&gt;pnpm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm create astro@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create a Reveal.js Layout
&lt;/h3&gt;

&lt;p&gt;We'll create a reusable layout component for our slides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
export interface Props {
    title: "string;"
    authors: string[];
    description?: string;
}
import Layout from "./BaseLayout.astro";
import "reveal.js/dist/reveal.css";
import "reveal.js/plugin/highlight/monokai.css";

const { title, authors, description } = Astro.props;
---

{/* See Layout at https://github.com/hnrq/slides/blob/main/src/layouts/SlideLayout.astro */}
&lt;span class="nt"&gt;&amp;lt;Layout&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Fragment&lt;/span&gt; &lt;span class="na"&gt;slot=&lt;/span&gt;&lt;span class="s"&gt;"head"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {description &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{description}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;}
    {authors.map((author) =&amp;gt; &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;{author}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;)}
  &lt;span class="nt"&gt;&amp;lt;/Fragment&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"reveal"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"slides"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;slot&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Layout&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Reveal&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reveal.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Highlight&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reveal.js/plugin/highlight/highlight.esm.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;deck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Reveal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Highlight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;deck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Creating Individual Slides
&lt;/h3&gt;

&lt;p&gt;Each slide is an &lt;code&gt;.astro&lt;/code&gt; file in &lt;code&gt;src/slides&lt;/code&gt; with frontmatter metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import CSSPropertyDemo from "./components/CSSPropertyDemo.svelte";

export const title = "CSS Flexbox";
export const authors = ["Henrique Ramos"];
export const publishedAt = "2025-01-27";
export const description = "Do you even flex?";
export const draft = true;
---

&lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;What is Flexbox?&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;One-dimensional layout model&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Distributes space along a single direction&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Powerful alignment capabilities&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Leveraging Astro Islands
&lt;/h3&gt;

&lt;p&gt;One of the coolest features of Astro is being any UI framework for interactive components. For example, we can create a &lt;a href="https://github.com/hnrq/slides/blob/main/src/slides/flexbox/components/CSSPropertyDemo.svelte" rel="noopener noreferrer"&gt;Svelte component&lt;/a&gt; for demonstrating CSS properties and &lt;a href="https://github.com/hnrq/slides/blob/967ad59394b54f4e1c14e852d17e7c17d402e43f/src/slides/flexbox/index.astro#L30" rel="noopener noreferrer"&gt;append it to the slide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Building the Homepage
&lt;/h3&gt;

&lt;p&gt;Since Astro doesn't yet support &lt;code&gt;.astro&lt;/code&gt; content collections, we'll create a utility to load and validate our slides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AstroInstance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;astro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;arktype&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Opts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;astroPageType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft?&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;boolean&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getAstroPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;AstroInstance&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Opts&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... implementation&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;getAstroPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create a slides getter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string[]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Slide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AstroInstance&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getSlides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;getAstroPages&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Slide&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Slide&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@slides/**/index.astro&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;@slides/*.astro&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;eager&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="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;schema&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;Finally, implement the homepage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;---
import { getSlides } from "@utils/getSlides";

const slides = getSlides()
  .filter(({ draft }) =&amp;gt; !draft)
  .sort((c1, c2) =&amp;gt; (c1.title &amp;gt; c2.title ? -1 : 1));
---

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Slides&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Here you can find a list of all available slides:&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
{
  slides.map((slide) =&amp;gt; (
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;{`/${slide.id}`}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;{slide.title}&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{slide.description}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;small&amp;gt;&lt;/span&gt;{new Date(slide.publishedAt).toLocaleDateString()}&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
  ))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Future Improvements
&lt;/h2&gt;

&lt;p&gt;Here are some ideas to improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make Reveal.js plugins configurable through props&lt;/li&gt;
&lt;li&gt;Improve code block highlighting in client-side components&lt;/li&gt;
&lt;li&gt;Add presentation themes and customization options&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;By combining Astro with Reveal.js, we've created a modern, maintainable system for managing presentations, allowing us to centralize and enhance our slides with Interactivity Islands.&lt;/p&gt;

&lt;p&gt;What do you think? Share your thoughts and ideas in the comments below!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://slides.hnrq.dev" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt; and &lt;a href="https://github.com/hnrq/slides" rel="noopener noreferrer"&gt;Code&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Note: This implementation is a starting point - feel free to customize and extend it based on your needs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>revealjs</category>
    </item>
    <item>
      <title>Using XState to coordinate Three.js character animations</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Fri, 27 Sep 2024 20:39:42 +0000</pubDate>
      <link>https://dev.to/hnrq/using-xstate-to-coordinate-threejs-character-animations-p5k</link>
      <guid>https://dev.to/hnrq/using-xstate-to-coordinate-threejs-character-animations-p5k</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is not a definitive guide, improvements and feedback are more than welcome!&lt;/p&gt;

&lt;p&gt;When creating a character with a complex animation flow, it is a common practice to use a &lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine" rel="noopener noreferrer"&gt;finite-state machine&lt;/a&gt;. Popular game engines, such as Unity and Unreal, have built-in solutions to handle state machines in order to queue animation clips. When using JS, however, developers can pick from a multitude of libraries, including XState.&lt;/p&gt;

&lt;p&gt;XState is a JS/TS library for state machine modelling and orchestration. It's easy to get started with, but its learning curve gets steeper quickly. Therefore, getting this done was a journey searching through forums, documentations and trial and error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Create a character that walks, runs and dances, sits down and does push-ups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Three.js&lt;/li&gt;
&lt;li&gt;XState&lt;/li&gt;
&lt;li&gt;A Humanoid model&lt;/li&gt;
&lt;li&gt;Ten individual animations:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regular animations:&lt;/strong&gt; Dancing, Idle, Running, Pushing-up, Sitting and Walking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State-Transition animations&lt;/strong&gt;: Idle to Push-up, Push-up to Idle, Sit to Stand and Stand to Sit&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Relevant XState Concepts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://stately.ai/docs/states" rel="noopener noreferrer"&gt;State&lt;/a&gt;: The machine's status or mode.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stately.ai/docs/transitions" rel="noopener noreferrer"&gt;Events and Transitions&lt;/a&gt;: Event is a signal that causes a transition. Transition is a change from one state to another.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stately.ai/docs/actions" rel="noopener noreferrer"&gt;Actions&lt;/a&gt;: It's an effect fired when a transition happens, be it on entry or exit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stately.ai/docs/context" rel="noopener noreferrer"&gt;Context&lt;/a&gt;: Data stored in a state machine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Assumptions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The character should start from Idle state&lt;/li&gt;
&lt;li&gt;The character should be able to walk, run, do push-ups and sit down from Idle&lt;/li&gt;
&lt;li&gt;Regular animations should run in loop, until another action is fired&lt;/li&gt;
&lt;li&gt;All State-Transition animations should be uninterruptable&lt;/li&gt;
&lt;li&gt;"Idle to Push-up" and "Stand to Sit" should run once before looping "Pushing-up" and "Sitting", respectively, effectively starts&lt;/li&gt;
&lt;li&gt;When transitioning from "Pushing-up" or "Sitting", "Push-up to Idle" and "Stand to Sit" should run once&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Character model and animations were loaded using Three.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Machine
&lt;/h3&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%2Frld7ph7ad0bfjr48myjw.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%2Frld7ph7ad0bfjr48myjw.png" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;
Mermaid diagram of the state machine. A more detailed version can be seen at &lt;a href="https://stately.ai/registry/editor/d947654c-2a61-4870-bc38-d34a5224ea00?machineId=b2a3771b-2530-4ce7-9f71-380eee380537" rel="noopener noreferrer"&gt;Stately.ai&lt;/a&gt;.



&lt;h4&gt;
  
  
  State Machine Context
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;currentAction&lt;/code&gt;: The Animation to be played&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;velocity&lt;/code&gt;: How fast should the model move&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up State-Transition animations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idle to Push-up&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="s1"&gt;Push-up to Idle&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="s1"&gt;Sit to Stand&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="s1"&gt;Stand to Sit&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="s1"&gt;Open Door&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HumanoidActions&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LoopOnce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// important for triggering finished event&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;actionName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;clampWhenFinished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;
  
  
  Playing an uninterruptable action
&lt;/h4&gt;

&lt;p&gt;In order to play an uninterruptable action, a promise actor will be created. It reads the action to be played from input and adds an event listener to &lt;code&gt;mixer&lt;/code&gt; that will be triggered once the action is finished.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;playUninterruptableAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;fromPromise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HumanoidActions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;playAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;action&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;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;mixer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finished&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="nx"&gt;mixer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finished&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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;h3&gt;
  
  
  Instancing and Listening to State Machine changes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setupHumanoidMachine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;crossfadeMixer&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentAction&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;previousSnapshot&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentAction&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="nx"&gt;crossfadeMixer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentAction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;previousSnapshot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controls
&lt;/h3&gt;

&lt;p&gt;In this case, &lt;a href="https://github.com/hnrq/hnrq/blob/main/src/js/world/Humanoid/controls.ts" rel="noopener noreferrer"&gt;PressControls&lt;/a&gt; were implemented. Every time the user presses anywhere in the screen, an event of type &lt;code&gt;Walk&lt;/code&gt; or &lt;code&gt;Run&lt;/code&gt;, depending on how far away from the character it clicked, will be sent to the actor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HumanoidControls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;mesh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;intersectionObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Events&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWpyYnE5ZnFkazdkZmV4b2kyNHF1eGR0ZzZyaXAybTRyY2s1cjFjMCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/X91rRAWWEgVQvUSr7l/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWpyYnE5ZnFkazdkZmV4b2kyNHF1eGR0ZzZyaXAybTRyY2s1cjFjMCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/X91rRAWWEgVQvUSr7l/giphy.gif" width="480" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hnrq.dev" rel="noopener noreferrer"&gt;Live demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hnrq/hnrq" rel="noopener noreferrer"&gt;Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Create a React Tooltip component using Popover API</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Thu, 27 Jun 2024 18:39:31 +0000</pubDate>
      <link>https://dev.to/hnrq/create-a-react-tooltip-component-using-popover-api-155o</link>
      <guid>https://dev.to/hnrq/create-a-react-tooltip-component-using-popover-api-155o</guid>
      <description>&lt;p&gt;Since April 2024, Popover API works major browsers on their &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API#browser_compatibility" rel="noopener noreferrer"&gt;latest versions&lt;/a&gt;. This API allows developers to display popover content on top of other page content.&lt;/p&gt;

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

&lt;p&gt;For this small snippet, I've used JS Popover API (&lt;code&gt;HTMLElement.showPopover&lt;/code&gt; &amp;amp; &lt;code&gt;HTMLElement.hidePopover&lt;/code&gt;) and &lt;a href="https://floating-ui.com/" rel="noopener noreferrer"&gt;Floating UI&lt;/a&gt; for Tooltip positioning. Check it out: &lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/hnrq_/embed/bGyJOMK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>html</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Typing Component Events in Svelte</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Wed, 30 Aug 2023 03:22:42 +0000</pubDate>
      <link>https://dev.to/hnrq/typing-component-events-in-svelte-4fjb</link>
      <guid>https://dev.to/hnrq/typing-component-events-in-svelte-4fjb</guid>
      <description>&lt;p&gt;Svelte allows the creation of custom events for a component. When using it with TypeScript, it is also possible to add types that makes it easier to know which event can be listened to and its details.&lt;/p&gt;

&lt;h2&gt;
  
  
  The event dispatcher
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createEventDispatcher&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ComponentEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dispatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createEventDispatcher&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComponentEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onToggle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle&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;onEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The event listener
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$lib/components/Component.svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ComponentEvents&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&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;handleEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ComponentEvents&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="c1"&gt;// ...};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>svelte</category>
      <category>beginners</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Extending a HTML Element in a Svelte Component using TypeScript</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Thu, 16 Feb 2023 03:13:24 +0000</pubDate>
      <link>https://dev.to/hnrq/extending-a-html-element-in-a-svelte-component-using-typescript-2fk6</link>
      <guid>https://dev.to/hnrq/extending-a-html-element-in-a-svelte-component-using-typescript-2fk6</guid>
      <description>&lt;p&gt;When using Svelte with TypeScript, all properties of a component should be typed. Props are declared by exporting a &lt;code&gt;let&lt;/code&gt;. However, when creating a superset of an HTML element, one wouldn't want to extensively declare all props. Here's a solution for this:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputAttributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte/elements&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;$$Props&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;HTMLInputAttributes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>svelte</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Conditional Wrap in React</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Wed, 22 Jun 2022 19:39:46 +0000</pubDate>
      <link>https://dev.to/hnrq/conditional-wrap-in-react-1p1e</link>
      <guid>https://dev.to/hnrq/conditional-wrap-in-react-1p1e</guid>
      <description>&lt;p&gt;If you ever played around with React, you probably found a situation where you'd need to conditionally wrap a component. If it matches some condition, it should be rendered inside a given tag, if not, leave it as is. Here's a small snippet for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WrapProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;if&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;wrapperProps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;NonNullable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FC&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WrapProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;wrapperProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wrapperProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&amp;gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Wrap&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt; &lt;span class="na"&gt;wrapperProps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-testid&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="s1"&gt;wrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Wrapped text&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Wrap&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This component uses &lt;code&gt;React.createElement&lt;/code&gt; because it allows dynamic component creation. It means that, instead of providing a function like &lt;code&gt;(children) =&amp;gt; &amp;lt;p&amp;gt;{children}&amp;lt;/p&amp;gt;&lt;/code&gt; for Wrap, it is possible to pass a React Component instance or a HTML node name.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Solid + GSAP: The superhero we need</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Fri, 17 Jun 2022 21:24:44 +0000</pubDate>
      <link>https://dev.to/hnrq/solid-gsap-the-superhero-we-need-577j</link>
      <guid>https://dev.to/hnrq/solid-gsap-the-superhero-we-need-577j</guid>
      <description>&lt;p&gt;Have you ever created a page and felt like it lacks the &lt;strong&gt;SAUCE&lt;/strong&gt;? In my opinion, that drip usually comes from micro-interactions (small interactions with the sole purpose of delighting users). There are many libraries around for creating them, but my favorite one is GSAP, which allows creating performant and complex animations easily.&lt;/p&gt;

&lt;p&gt;When using GSAP with React, however, the integration doesn't feel right: creating a Tween/Timeline and storing it in a &lt;code&gt;React.useRef()&lt;/code&gt;, adding &lt;code&gt;.to()&lt;/code&gt;'s and &lt;code&gt;.from()&lt;/code&gt;'s inside a &lt;code&gt;React.useEffect()&lt;/code&gt; looks strange to me. This is needed in order to prevent re-declaring on every render, which would re-trigger animations every time something changes in the DOM tree.&lt;/p&gt;

&lt;p&gt;This is where SolidJS is brought into play. It looks like React, but has a main difference. according to its &lt;a href="https://www.solidjs.com/docs/latest#component-apis" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every Component executes once and it is the Hooks and bindings that execute many times as their dependencies update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means no &lt;code&gt;useRef&lt;/code&gt; needed for storing a timeline. In Solid you may simply declare it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gsap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;paused&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it won't be re-declared on every render. Very cool, eh? This approach is useful for most libraries thatinteract with the DOM to add transitions and animations (&lt;a href=""&gt;ScrollReveal.js&lt;/a&gt;, for example).&lt;/p&gt;

&lt;h3&gt;
  
  
  Animating with GSAP in Solid.js
&lt;/h3&gt;

&lt;p&gt;When adding an animation to a timeline, GSAP needs to know which element should be animated. This can be done using an element reference. There are two ways of getting a ref in Solid:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pass a ref variable to a JSX element and add the animation inside &lt;code&gt;onMount&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;gsap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// TODO: animation&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;containerRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Or, my favorite one, using custom &lt;strong&gt;directives&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// code goes here&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;animation&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom directives are syntax sugar over ref, which allows us to easily attach multiple directives to a single element. A directive is a function that receives an Element and an Accessor as arguments, that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hoverAnimation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_accessor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gsap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// properties&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseenter&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mouseleave&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&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;And may then be added to any element like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nc"&gt;App &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;hoverAnimation&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="na"&gt;hoverAnimation&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An element can have multiple directives, and a directive can be used in multiple elements, making it possible to create GSAP animations that can be re-used throughout your app. &lt;/p&gt;

&lt;p&gt;Check out this live demo of an object that changes it background color on hover:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/solidjs-rainbow-box-wlysf4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>gsap</category>
      <category>solidjs</category>
    </item>
    <item>
      <title>To be or not to be a Fullstack Dev?</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Wed, 15 Jun 2022 18:07:51 +0000</pubDate>
      <link>https://dev.to/hnrq/to-be-or-not-to-be-a-fullstack-dev-41de</link>
      <guid>https://dev.to/hnrq/to-be-or-not-to-be-a-fullstack-dev-41de</guid>
      <description>&lt;p&gt;One of the hardest things for me is to define my level of knowledge in something. Nowadays I consider myself a Front-end developer because is the area I know the most and I'm most interested at. However, I'm also able to work on back-end side (Not the same knowledge, but it's sufficient most of the time).&lt;/p&gt;

&lt;p&gt;I feel like being Fullstack is like being good at Back-end and Front-end, while specializing in one of those areas, means diving deep into them, while having shallow knowledge of the other. Also, it looks like that "deep" understanding isn't needed most of the time, so probably a Fullstack would be good enough in 95% of the cases.&lt;/p&gt;

&lt;p&gt;This got me thinking: is it better, career wise, to be a Fullstack developer? Or is specialization the way to go?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>beginners</category>
      <category>career</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>How's your relationship with StackOverflow?</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Thu, 09 Jun 2022 23:50:44 +0000</pubDate>
      <link>https://dev.to/hnrq/hows-your-relationship-with-stackoverflow-4j5a</link>
      <guid>https://dev.to/hnrq/hows-your-relationship-with-stackoverflow-4j5a</guid>
      <description>&lt;p&gt;Every time I discover a new technology, I like to make a silly small project with it. This time, I created &lt;a href="https://discovr-ashen.vercel.app/" rel="noopener noreferrer"&gt;Discovr&lt;/a&gt; or, how I described it: "Just an excuse to learn SolidJS". The Tech Stack was TypeScript, SolidJS, Vite, Vitest and Testing Library. To sum up: I wanted to learn new things.&lt;/p&gt;

&lt;p&gt;While dropping characters into VSCode, I, of course, ran into issues. My gut feeling was google it and open the first SO or Doc entry, and so I did and... No answers. In the end I got over the problem the hard way: reading source code and muddling through, something I don't do too often.&lt;/p&gt;

&lt;p&gt;Everything went well. But at some point I considered leaving the project aside and coming back once there was a tutorial, answer or doc entry for my problem (which goes against the initial purpose of exploring new technologies). At the end, I got myself thinking if I was too used to having answers handed on a silver platter instead of digging for answers.&lt;/p&gt;

&lt;p&gt;What do you guys think? Is StackOverflow-dependency a thing? Does using it too much affect learning? What's your opinion about tutorials and "ready-to-use" answers? Personally, I feel like they surely help a lot in the beginning, but as you start digging deeper into something, it won't suffice anymore&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Aprendendo JS no Android</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Mon, 09 May 2022 23:27:19 +0000</pubDate>
      <link>https://dev.to/hnrq-br/aprendendo-js-no-android-1bdl</link>
      <guid>https://dev.to/hnrq-br/aprendendo-js-no-android-1bdl</guid>
      <description>&lt;p&gt;Vi um Tweet esses dias sobre como é dificil aprender a programar quando não se tem um computador. Acredite ou não, esta é a realidade para muitas pessoas ao redor do mundo. Seja por preço ou facilidade de uso, celulares são muito mais comuns que PCs e Laptops. No Brasil, por exemplo, um &lt;a href="https://www.gov.br/mcom/pt-br/noticias/2021/abril/pesquisa-mostra-que-82-7-dos-domicilios-brasileiros-tem-acesso-a-internet" rel="noopener noreferrer"&gt;celular pode ser encontrado em 99.5% das casas com acesso à internet, enquanto um computador só pode ser encontrado em 45.1%&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quando se trata de programação, a esmagadora maioria das ferramentas só estão disponíveis para computadores. Isso se torna um obstáculo para os que querem aprender, mas não possuem uma máquina. Neste texto, busco sugerir algumas ferramentas para facilitar o aprendizado de JavaScript, e algumas outras ferramentas comuns em TI, utilizando um celular Android.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acode
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://acode.foxdebug.com/" rel="noopener noreferrer"&gt;Acode&lt;/a&gt; É um editor de código para Android. Ele possui &lt;a href="https://pt.wikipedia.org/wiki/Autocompletar" rel="noopener noreferrer"&gt;&lt;em&gt;auto-completar&lt;/em&gt; (&lt;em&gt;Autocomplete&lt;/em&gt;)&lt;/a&gt; e &lt;a href="https://pt.wikipedia.org/wiki/Realce_de_sintaxe" rel="noopener noreferrer"&gt;&lt;em&gt;realce de sintaxe&lt;/em&gt; (&lt;em&gt;Syntax Highlighting&lt;/em&gt;)&lt;/a&gt; que funcionam muito bem. Além disso, é possível executar arquivos HTML com um toque, facilitando a visualização das alterações.&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%2Fme53pz8cw4a57tnd5p5c.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%2Fme53pz8cw4a57tnd5p5c.png" alt="Foto do Acode editor em um Tablet e um smartphone" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Codepen
&lt;/h2&gt;

&lt;p&gt;Ao iniciar o aprendizado de JS, ter um &lt;a href="https://pt.wikipedia.org/wiki/Ambiente_de_desenvolvimento_integrado" rel="noopener noreferrer"&gt;IDE&lt;/a&gt; que funcione de cara é ótimo. Para novatos, configurar um editor de código pode ser uma tarefa difícil. É aí que &lt;a href="https://codepen.io" rel="noopener noreferrer"&gt;Codepen&lt;/a&gt; entra em jogo: Ele não só permite brincar com JavaScript no console, mas também possui editores de HTML e CSS, além de uma aba 'Result' &lt;strong&gt;&lt;em&gt;live reload&lt;/em&gt;&lt;/strong&gt; (As alterações aparecem automaticamente sem ter que recarregar toda a página).&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/hnrq_/embed/QWawYQE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Termux
&lt;/h2&gt;

&lt;p&gt;Após dar início na jornada de programador, é interessante aprender como utilizar um terminal. &lt;a href="https://termux.com/" rel="noopener noreferrer"&gt;Termux&lt;/a&gt; é um terminal de comandos Unix que permite a instalação e uso de vários softwares de linha de comando. &lt;/p&gt;

&lt;p&gt;Estar familiarizado com um terminal é uma habilidade util para toda especialização de TI, mas é especialmente necessária àqueles que trabalham com &lt;a href="https://pt.wikipedia.org/wiki/DevOps" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt; ou Infraestrutura. No último caso, o Termux será útil mesmo se tiver um computador (Digo isso como alguém que hosteia um servidor de &lt;a href="https://veloren.net" rel="noopener noreferrer"&gt;Veloren&lt;/a&gt; e precisa usar SSH constantemente para fazer atualizações e checar se tudo está funcionando corretamente).&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%2Fh80mpstg6srtw25tcdwm.png" class="article-body-image-wrapper"&gt;&lt;img alt="Foto do aplicativo Termux" 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%2Fh80mpstg6srtw25tcdwm.png" width="610" height="1200"&gt;&lt;/a&gt;&lt;br&gt;Caso vocês ainda não se conheçam: Terminal, usuário. Usuário, Terminal.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;Quando o assunto é &lt;a href="https://pt.wikipedia.org/wiki/Sistema_de_controle_de_vers%C3%B5es" rel="noopener noreferrer"&gt;Sistemas de Controle de Versionamento&lt;/a&gt;, Git é a ferramenta mais popular. Ela permite que os desenvolvedores organizem seu trabalho e consigam colaborar em um mesmo projeto em paralelo. Em um PC, os editores de código normalmente automatizam os comandos, mas no Android será necessário ter um terminal, como o Termux, e aprender como utilizar comandos do git como &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt; e &lt;code&gt;checkout&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;Se o Git é o número 1 em Sistemas de Controle de Versionamento, GitHub é o serviço em nuvem número 1 para Git. Ele permite reportar erros no código, revisões e, mais importante, ele mantém o repositório na nuvem caso seu telefone &lt;a href="https://twitter.com/SrBixcoito2/status/1521986189200347136" rel="noopener noreferrer"&gt;exploda do nada&lt;/a&gt;. A Microsoft tem feito um ótimo trabalho no &lt;a href="https://github.com/mobile" rel="noopener noreferrer"&gt;aplicativo do GitHub&lt;/a&gt;: Ele possui a maioria das funcionalidades disponíveis no aplicativo Desktop. É possível Editar arquivos, submeter, aprovar e comentar &lt;a href="https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests" rel="noopener noreferrer"&gt;&lt;em&gt;pull requests&lt;/em&gt;&lt;/a&gt;, tudo direto do &lt;em&gt;smartphone&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvaf5q2qouic511qfso6k.jpeg" class="article-body-image-wrapper"&gt;&lt;img alt="Fotos do aplicativo do GitHub" 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%2Fvaf5q2qouic511qfso6k.jpeg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Não vou mentir, o modo claro queima meus olhos.



&lt;h2&gt;
  
  
  Keyboards
&lt;/h2&gt;

&lt;p&gt;Fazer as coisas usando &lt;em&gt;touchscreen&lt;/em&gt; é bem mais difícil para mim do que digitar e navegar em um computador. No entanto, percebi que meu irmão mais novo se dá muito bem jogando no celular, enquanto eu não consigo nem acertar um alvo parado no &lt;em&gt;Free Fire&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Por viver no Brasil, compro muitas coisas no AliExpress. Lá é possível encontrar diversos teclados Bluetooth baratos (Uns 50 reais). Ainda assim, pode ser caro para quem recebe um salário mínimo. Não é necessário, mas é um acessório que vale a pena investir, porque acelera as coisas.&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%2Fzezd6eftz4kjzix6yzon.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="Um teclado sem fio, padrão INTL acima. Abaixo, um mouse sem fio" 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%2Fzezd6eftz4kjzix6yzon.jpg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusão
&lt;/h2&gt;

&lt;p&gt;Hoje em dia, a mentalidade &lt;a href="https://blog.apiki.com/mobile-first-o-conceito-e-sua-aplicabilidade/" rel="noopener noreferrer"&gt;mobile-first&lt;/a&gt; trouxe muitas ferramentas de desenvolvimento para telefones Android. As ferramentas e aplicativos disponíveis hoje, como as mostradas acima, devem ser suficiente para alguém que quer aprender JS. No entanto, ao começar a se aprofundar mais em programação e tópicos mais complexos (Digamos que você começa a gostar de DevOps, por exemplo), você provavelmente se sairá muito melhor com um &lt;em&gt;Desktop&lt;/em&gt;/&lt;em&gt;Laptop&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Learning JS on Android</title>
      <dc:creator>Henrique Ramos</dc:creator>
      <pubDate>Mon, 09 May 2022 23:27:18 +0000</pubDate>
      <link>https://dev.to/hnrq/learning-js-on-android-58eg</link>
      <guid>https://dev.to/hnrq/learning-js-on-android-58eg</guid>
      <description>&lt;p&gt;One of these days I saw a tweet about how hard it is to learn programming when you don't have a computer. Believe it or not, that's a reality for a lot of people around the globe. Whether because of price or ease of use, cellphones are much more common than PCs and Laptops. Taking Brazil as an example, a &lt;a href="https://www.gov.br/mcom/pt-br/noticias/2021/abril/pesquisa-mostra-que-82-7-dos-domicilios-brasileiros-tem-acesso-a-internet" rel="noopener noreferrer"&gt;smartphone can be found in 99.5% of the households that have access to the internet, while a computer can only be found in 45.1%&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When talking about programming, the overwhelming majority of tools are computer-only. Hence, not having a workstation becomes a hurdle for those who want to dip their toes into IT. In this text, I'll suggest some tools to facilitate JavaScript and some other common IT tools using a cellphone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acode
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://acode.foxdebug.com/" rel="noopener noreferrer"&gt;Acode&lt;/a&gt; is an Android Code Editor. It has &lt;a href="https://en.wikipedia.org/wiki/Autocomplete" rel="noopener noreferrer"&gt;autocomplete&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Syntax_highlighting" rel="noopener noreferrer"&gt;syntax highlighting&lt;/a&gt; for HTML, CSS and JS that works surprisingly well. Also, it allows to run HTML files within a tap and this makes it way easier to preview recent changes.&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%2Fme53pz8cw4a57tnd5p5c.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%2Fme53pz8cw4a57tnd5p5c.png" alt="Screenshot of Acode editor on a Tablet and a smartphone" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Codepen
&lt;/h2&gt;

&lt;p&gt;When starting to learn JS, having an out-of-the-box functional &lt;a href="https://en.wikipedia.org/wiki/Integrated_development_environment" rel="noopener noreferrer"&gt;IDE&lt;/a&gt; will come in handy. For newbies, setting up a Code Editor can be really hard. That's where &lt;a href="https://codepen.io" rel="noopener noreferrer"&gt;Codepen&lt;/a&gt; comes into play: Not only it allows you to play around with JavaScript in the console, but it also has HTML and CSS editors, plus a result tab with &lt;strong&gt;live reload&lt;/strong&gt; (Changes on code are instantly seen on the results, you don't have to refresh the whole page).&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/hnrq_/embed/QWawYQE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Termux
&lt;/h2&gt;

&lt;p&gt;After kicking-off a programmer journey, one may want to start learning how to use a terminal. &lt;a href="https://termux.com/" rel="noopener noreferrer"&gt;Termux&lt;/a&gt; is an Android terminal and Linux environment that allows installing a bunch of command-line softwares.&lt;/p&gt;

&lt;p&gt;Being used to a Unix terminal is a useful skill in every IT specialization, but it's especially required for those who want to work with &lt;a href="https://en.wikipedia.org/wiki/DevOps" rel="noopener noreferrer"&gt;DevOps&lt;/a&gt; or IT infrastructure. In the latter case, Termux will be useful even when having a workstation (saying that as someone who hosts a &lt;a href="https://veloren.net" rel="noopener noreferrer"&gt;Veloren&lt;/a&gt; server and constantly needs to SSH into the machine for updates and health-checks). &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%2Fh80mpstg6srtw25tcdwm.png" class="article-body-image-wrapper"&gt;&lt;img alt="Screenshot of the Termux app" 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%2Fh80mpstg6srtw25tcdwm.png" width="610" height="1200"&gt;&lt;/a&gt;&lt;br&gt;In case you haven't met yet: Terminal, user. User, terminal.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;When it comes to &lt;a href="https://en.wikipedia.org/wiki/Version_control" rel="noopener noreferrer"&gt;Version Control System&lt;/a&gt;, Git is the most popular tool around. It allows developers to organize their work and also collaborate in a same project in parallel. On PC, Code Editors usually automate Git commands, but on Android having a terminal, like termux, and learning how to use git commands like &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt; and &lt;code&gt;checkout&lt;/code&gt; will be necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;If Git is the #1 Version Control System, GitHub is the #1 cloud service for Git. It allows code issues reporting, code-reviewing and, most importantly, it will keeps the repository on the cloud if your cellphone suddenly &lt;a href="https://twitter.com/SrBixcoito2/status/1521986189200347136" rel="noopener noreferrer"&gt;explodes&lt;/a&gt;. Microsoft has been doing a great job on the &lt;a href="https://github.com/mobile" rel="noopener noreferrer"&gt;GitHub app&lt;/a&gt;: It has most of the features available on GitHub desktop. Edit files, submit, approve and comment on &lt;a href="https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests" rel="noopener noreferrer"&gt;pull requests&lt;/a&gt;, everything from your smartphone.&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%2Fvaf5q2qouic511qfso6k.jpeg" class="article-body-image-wrapper"&gt;&lt;img alt="Screenshots of the GitHub App" 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%2Fvaf5q2qouic511qfso6k.jpeg" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
Not gonna lie, the light mode burns my eyes.



&lt;h2&gt;
  
  
  Keyboards
&lt;/h2&gt;

&lt;p&gt;Doing things in a touchscreen is way harder for me than typing and browsing around a computer. However, I've noticed that my younger brother is a proficient phone gamer, while I can't even hit a standing target when playing Free Fire.&lt;/p&gt;

&lt;p&gt;Since I live in Brazil, I usually buy things on AliExpress. They sell a bunch of cheap bluetooth keyboards (Around 8 dollars). It could still be pricey for those who get a minimum wage in developing countries. It's not a must, but an accessory worth investing in, since it really speed things up.&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%2Fzezd6eftz4kjzix6yzon.jpg" class="article-body-image-wrapper"&gt;&lt;img alt="A wireless INTL keyboard with a wireless mouse below it" 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%2Fzezd6eftz4kjzix6yzon.jpg" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom-line
&lt;/h2&gt;

&lt;p&gt;Nowadays, mobile-first mentality brought much more development tools to Android devices. The tools and apps available today, such as the ones shown above, should be enough for someone who wants to learn JS. However, once you start digging deep into programming and some other complex topics (Let's say you get a taste for DevOps, for example), you'll likely be much better with a Desktop/Laptop. &lt;/p&gt;

</description>
      <category>learning</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
