<?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>Forem: Min</title>
    <description>The latest articles on Forem by Min (@choisohan).</description>
    <link>https://forem.com/choisohan</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%2F352432%2Fd8404643-3e7e-4aab-acf6-b40fbf227b86.png</url>
      <title>Forem: Min</title>
      <link>https://forem.com/choisohan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/choisohan"/>
    <language>en</language>
    <item>
      <title>Welcome to Sunset Avenue</title>
      <dc:creator>Min</dc:creator>
      <pubDate>Wed, 09 Jul 2025 22:28:11 +0000</pubDate>
      <link>https://forem.com/choisohan/welcome-to-sunset-avenue-1b69</link>
      <guid>https://forem.com/choisohan/welcome-to-sunset-avenue-1b69</guid>
      <description>

&lt;p&gt;What if I could live in a beautiful town with all my favorite people as neighbors?&lt;br&gt;
Would’ve been nice if someone told me earlier that dream’s basically impossible.&lt;br&gt;
(Seriously, have you seen the property market? It’s wild.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sunave.netlify.app/" rel="noopener noreferrer"&gt;🏠Sunset Avenue&lt;/a&gt; is my way of holding onto that vibrant childhood dream.&lt;br&gt;
Each 3D house syncs with an online calendar — so you can see how vibrant everyone’s life is, no matter where they are.&lt;/p&gt;

&lt;p&gt;In this post, I’ll quickly walk through how I built it.&lt;br&gt;
No heavy tech talk — just a simple, fun read.&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Structure Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework(Library)&lt;/strong&gt; : React&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3D scene&lt;/strong&gt; : Three.js via React Three Fiber&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar Data Fetching&lt;/strong&gt; : ical format with ical.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Hosting&lt;/strong&gt; : Glitch.io&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To start a JavaScript project, you need a &lt;strong&gt;code editor&lt;/strong&gt; like VS Code.&lt;br&gt;
Then, install &lt;strong&gt;Node.js&lt;/strong&gt; to use the &lt;code&gt;npm&lt;/code&gt; command for installing libraries.&lt;br&gt;
Optionally, &lt;strong&gt;Git&lt;/strong&gt; helps track code changes over time.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In coding, a library is like a set of pre-made ingredients you can use in your project.&lt;br&gt;
As a developer, you can choose the right libraries and mix them as you want.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react.js three.js react-three-fiber ical.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;React renders 2D website elements like layouts and buttons.&lt;br&gt;
Three.js renders 3D graphics in the browser.&lt;br&gt;
React Three Fiber integrates Three.js smoothly into React.&lt;br&gt;
ical.js parses iCal calendar data into easy-to-use JSON.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Go Fetch!
&lt;/h2&gt;

&lt;p&gt;When I start a new project, the first thing I tackle is what I know the least.&lt;br&gt;
If any part turns out to be impossible, the whole effort feels pointless.&lt;br&gt;
For this project, that tricky part was fetching the calendar data.&lt;/p&gt;

&lt;p&gt;When you develop something, there are two sides: frontend and backend.&lt;br&gt;
&lt;strong&gt;The frontend&lt;/strong&gt; is what you and other users see and interact with.&lt;br&gt;
&lt;strong&gt;The backend&lt;/strong&gt; is the hidden hand managing data across different servers.&lt;/p&gt;

&lt;p&gt;Fetching calendar data from online requires some backend work.&lt;br&gt;
For this, I used &lt;strong&gt;Glitch&lt;/strong&gt; to host a free backend server.&lt;br&gt;
I created a new project on Glitch and added the following code to the bottom of &lt;code&gt;server.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/fetch-ical&lt;/span&gt;&lt;span class="dl"&gt;'&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;icalData&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// iCal data as plain text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&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="nx"&gt;icalData&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;    &lt;span class="c1"&gt;// Send it back as JSON&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// handle error here&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;What’s happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Glitch server accesses the URL sent in &lt;code&gt;req.body.url&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It fetches some iCal data from that address&lt;/li&gt;
&lt;li&gt;It wraps the data in JSON and sends it back to the frontend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, this only runs when a request is made.&lt;br&gt;
Here’s what the frontend can send to trigger it:&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%2Fx6umlbs26p8c2ejcbvxt.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%2Fx6umlbs26p8c2ejcbvxt.png" alt="google calendar offers ical url" width="652" height="163"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;icalUrl&lt;/span&gt; &lt;span class="p"&gt;};&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;myGlitchURL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/fetch-ical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&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="s1"&gt;Content-Type&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestData&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;data&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;// Yay, data arrived!&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the biggest downsides of using iCal is that you can’t request &lt;strong&gt;just a little bit of data&lt;/strong&gt; — you have to fetch the whole thing.&lt;br&gt;
It’s kind of like shopping at Costco as a bachelor: you don’t really need that much of everything, but that’s the only size available.&lt;/p&gt;

&lt;p&gt;If I had used something like the &lt;strong&gt;Google Calendar API&lt;/strong&gt;, this could’ve been solved.&lt;br&gt;
I’d be able to fetch only the data I need, and even set up webhooks to trigger when events are created or deleted — meaning the frontend could stay updated in real time.&lt;/p&gt;

&lt;p&gt;But unfortunately, I was running out of time. So I went with the bulk option.&lt;br&gt;
Big bag of data it is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤷‍♀️Why Calendar?&lt;/strong&gt;&lt;br&gt;
There used to be a &lt;strong&gt;routine builder&lt;/strong&gt; in an older version of this project (a couple of years ago).&lt;br&gt;
Each house acted like a routine you’d set: open the curtains, close the curtains — that kind of thing.&lt;br&gt;
I ended up scrapping the whole feature after spending way too long on it… because the more I built it, the more it just turned into a regular calendar.&lt;br&gt;
😂 You live and learn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  2. Paint Sunset
&lt;/h2&gt;



&lt;p&gt;I wasn’t really a sunset person. I never quite understood why people would suddenly stand up and stare at the sky like something magical was happening.&lt;br&gt;
But one day, I was just sitting on the grass near my home, and out of nowhere, I felt this strange, unexplainable bond with a bunch of strangers — just from watching the same sunset together.&lt;/p&gt;

&lt;p&gt;And in that moment, I wished I could share that same feeling with people I actually care about, even if they’re on the other side of the world.&lt;br&gt;
(But I knew I couldn’t. And that made me a little sad.)&lt;/p&gt;

&lt;p&gt;The name of this project came from that moment.&lt;/p&gt;

&lt;p&gt;The sunset is a key part of this project — I had to get it right.&lt;br&gt;
I wanted the sky to look a little different every day, so I built it using a procedural method.&lt;/p&gt;



&lt;p&gt;First, I created some random shapes in Photoshop and turned them into tangent normals.&lt;br&gt;
Then, I &lt;strong&gt;distorted&lt;/strong&gt; the cloud image using Perlin noise to give it some organic motion.&lt;br&gt;
After that, the image gets &lt;strong&gt;repainted&lt;/strong&gt; with new colors.&lt;/p&gt;

&lt;p&gt;For this recoloring process, I used another map as a sampler:&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%2Fchoisohan.github.io%2Fmedia%2F2025_sunave%2FskyColors.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%2Fchoisohan.github.io%2Fmedia%2F2025_sunave%2FskyColors.png" width="353" height="652"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;    &lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;cloudShadow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;texture2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;uSkyColorMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uTime&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="n"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;cloudHighlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;texture2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;uSkyColorMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uTime&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="n"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;cloudColored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cloudShadow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cloudHighlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cloudsMixed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how I sample the color using UV coordinates.&lt;br&gt;
These sampled colors are then used in various shaders across the scene — on houses, trees, the ocean… basically anything that needs to feel like it’s part of the same sky.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. House Designer
&lt;/h2&gt;



&lt;p&gt;I imagined endless possibilities for designing the houses — all kinds of shapes, styles, and details.&lt;br&gt;
But the truth is, the more options you offer, the more pressure the user feels.&lt;br&gt;
So I decided to keep it simple: &lt;strong&gt;one house geometry, a few color choices.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keeping each house as a single mesh also reduces render calls, which helps with performance.&lt;br&gt;
Sometimes less really is more — both for the user and the GPU.&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%2Fwinsbquo2yia7iwj7ufg.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%2Fwinsbquo2yia7iwj7ufg.png" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Grid System
&lt;/h2&gt;



&lt;p&gt;The grid shown in the town editor mode is baked directly into the 3D file as a renderable mesh.&lt;br&gt;
That’s because the town is hilly — it needs its own custom grid geometry that follows the surface properly.&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%2F3842pusul8m6qs0o0hom.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%2F3842pusul8m6qs0o0hom.png" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the FBX file is loaded, it stores the transform data (position and rotation) of each grid cell in an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gridTransforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nx"&gt;_fbxFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_child&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;_child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid&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;pos&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;Vector3&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;_child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWorldPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&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;rot&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;Euler&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;quat&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;Quaternion&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;_child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWorldQuaternion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;rot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setFromQuaternion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;XYZ&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;cellTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rot&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;gridTransforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cellTransform&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;Then, when rendering a house, the system looks up the right transform from that array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;House&lt;/span&gt; &lt;span class="nx"&gt;property&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;cellNumb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="c1"&gt;// This finds the corresponding transform from the grid array&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Custom Pixelation Effect
&lt;/h2&gt;

&lt;p&gt;I didn’t originally plan to render this in a pixel art style.&lt;br&gt;
But I’ve always liked the old-school, low-key vibe of pixel art — and it felt like a natural fit for the mood of Sunset Avenue.&lt;br&gt;
There’s something about it that gives more weight and solidity to the scene than smooth 3D rendering does — kind of like turning a watercolor into thick acrylic paint.&lt;/p&gt;

&lt;p&gt;For post-processing effects, I used the &lt;a href="https://www.npmjs.com/package/postprocessing" rel="noopener noreferrer"&gt;postprocessing&lt;/a&gt; library — a common choice in Three.js projects.&lt;br&gt;
It’s great for things like color adjustment and bloom.&lt;br&gt;
It even has a built-in pixelation effect, but the result wasn’t quite what I wanted.&lt;/p&gt;

&lt;p&gt;Take a look at the right side of the comparison below — you’ll notice some strange tiling in the sky gradient.&lt;br&gt;
That’s called &lt;a href="https://upload.wikimedia.org/wikipedia/commons/9/9a/Colour_banding_example01.png" rel="noopener noreferrer"&gt;color banding&lt;/a&gt;, and it’s a common issue when compressing images (like tiny GIFs) or using low-res color sampling.&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%2F7cpz9p5gemanraz4svut.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%2F7cpz9p5gemanraz4svut.png" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The default pixelation effect samples every block evenly, regardless of how small the color difference is — which makes gradients look chunky and artificial.&lt;br&gt;
Unfortunately, I couldn’t tweak it enough to fix that.&lt;/p&gt;

&lt;p&gt;But then I stumbled upon &lt;a href="https://blenderartists.org/t/can-i-somehow-set-up-a-sharp-low-resolution/1323775/5" rel="noopener noreferrer"&gt;this article&lt;/a&gt;, which helped me get the exact look I was after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;mainImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;inputColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;outputColor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Calculate pixel grid size based on pixelSize and resolution&lt;/span&gt;
  &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;dxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixelSize&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;pixelSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;vec2&lt;/span&gt; &lt;span class="n"&gt;pixelUv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kt"&gt;vec4&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;texture2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tDiffuse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixelUv&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;The key here is &lt;code&gt;float offset = 1.5/pixelSize;&lt;/code&gt;&lt;br&gt;
That offset shifts the sample point away from the block edge and closer to the center — helping reduce color banding in gradients.&lt;br&gt;
If the offset is too small, you’ll sample near edges, causing artifacts.&lt;br&gt;
If it’s too large, you’ll drift too far into the block, possibly distorting the grid and stretching colors slightly.&lt;/p&gt;

&lt;p&gt;1.5 turns out to be a sweet spot — smooth gradients, preserved pixelation, and no weird tiling.&lt;/p&gt;

&lt;p&gt;PS. Thank you,Matías Fernández and power of internet community :D&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Sort Events
&lt;/h2&gt;

&lt;p&gt;When event data arrives, each one needs to be duplicated based on its repetition rule.&lt;br&gt;
These rules follow the RRULE format — things like daily, weekly, or from a certain date to another.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;code&gt;FREQ=WEEKLY;BYDAY=TU;UNTIL=20251230&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To decode that into something usable, I used the rrule JavaScript library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RRule&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;rrule&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;evtRRule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`FREQ=WEEKLY;BYDAY=TU;UNTIL=20251230`&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;RRule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evtRRule&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;options&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;rule&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;RRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&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;DateFromNow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;days&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="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;days&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;recentOccurrences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DateFromNow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;DateFromNow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;10&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;It’s important to limit the range using &lt;code&gt;between()&lt;/code&gt; —&lt;br&gt;
if the rule creates infinite events, you don’t want to generate thousands at once.&lt;br&gt;
Your app (and your sanity) will thank you.&lt;/p&gt;

&lt;p&gt;Once you’ve expanded all the events, sort them by their start time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;evts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startMoment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startMoment&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤪Fun Neighbours&lt;/strong&gt;&lt;br&gt;
To fill up the town, I needed more houses — and more calendars.&lt;br&gt;
So I copied iCal data from my own Google Calendar, saved it as a .json file in the project folder, and started to get creative.&lt;/p&gt;


&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/calendars/bs.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="c1"&gt;// Fetching directly from the public folder, no backend needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;One of the "residents" is めんこいラーメン, a ramen shop that occasionally gets caught up in awkward situations involving the owner’s ex-wife.&lt;br&gt;
Local drama… now in your browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  7. Control Time
&lt;/h2&gt;

&lt;p&gt;One variable that needs to be shared across multiple components is &lt;code&gt;time&lt;/code&gt;.&lt;br&gt;
It drives everything — from how shaders animate to determining which calendar event is currently active for each house.&lt;/p&gt;

&lt;p&gt;I created the time variable as a &lt;strong&gt;React context&lt;/strong&gt;, which acts as a global variable within the app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TimestampContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createContext&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;UpdateTimestampContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createContext&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;function&lt;/span&gt; &lt;span class="nf"&gt;EnvProvider&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTimestamp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;valueOf&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UpdateTimestampContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setTimestamp&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TimestampContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;timestamp&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;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/TimestampContext.Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/UpdateTimestampContext.Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timestamp is updated every minute using an interval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&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;updateTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// update every one minute&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;updateTimestamp&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, whenever the timestamp updates, a few things get triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;House&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTimestamp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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;// 1. Update all material shaders' uTime&lt;/span&gt;
    &lt;span class="nx"&gt;materials&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;mat&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;mat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Get the current calendar event based on time&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pastEvents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endMoment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;moment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setCurrentEvent&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="nx"&gt;pastEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  9. Lastly
&lt;/h2&gt;

&lt;p&gt;This project isn’t perfect yet, but I’m happy to share the progress I’ve made so far.&lt;/p&gt;

&lt;p&gt;I still have plenty of ideas I’d love to add — more features, polish, maybe even a few surprises.&lt;br&gt;
But time’s a bit limited these days, so I’ll be contributing in small bits when I can.&lt;/p&gt;

&lt;p&gt;Anyway, thank you so much for reading!&lt;br&gt;
If you have any questions, thoughts, or just want to chat — I’d be happy to hear from you.&lt;/p&gt;

</description>
      <category>threejs</category>
      <category>webgl</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Working alone is so exhausting so I created my own assistant</title>
      <dc:creator>Min</dc:creator>
      <pubDate>Tue, 15 Mar 2022 02:18:47 +0000</pubDate>
      <link>https://forem.com/choisohan/working-alone-is-so-exhausting-so-i-created-my-own-assistant-4mki</link>
      <guid>https://forem.com/choisohan/working-alone-is-so-exhausting-so-i-created-my-own-assistant-4mki</guid>
      <description>&lt;p&gt;Working alone is great... but... I am so tired of dealing with all of these.. these shitty uninspiring, repetitive, tedious tasks...&lt;/p&gt;

&lt;p&gt;I didn't realize how many emails I had to reply to and how many invoices I had to make on my own. How many tasks do I have to manage and schedule?!&lt;/p&gt;

&lt;p&gt;I sometimes freaked out alone because I was too busy, and sometimes I freaked out because suddenly there's nothing to do..&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Console.log( my_life )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I need something stabilizes me all the time no matter what happens to me.  Maybe I need a manager. Manage me! (Funny to say that because I left work to not be managed by others.LOL)&lt;/p&gt;

&lt;p&gt;For a sec, I think about hiring somebody, but I don't have money for that.(of course)&lt;/p&gt;

&lt;p&gt;So here is my attempt to create my personal assistant with discord and notion API and, what I learned from this project.&lt;/p&gt;

&lt;p&gt;If there is anything incorrect, please let me know. I'd love to learn from you!&lt;/p&gt;




&lt;h2&gt;
  
  
  1. I learned NLP
&lt;/h2&gt;

&lt;p&gt;The first thing in my head to create a chatbot is how a chatbot analyze the intention of my text. People say the same thing in various forms of expression&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How are you?&lt;/li&gt;
&lt;li&gt;How's it going?&lt;/li&gt;
&lt;li&gt;What's up?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can write the code like &lt;code&gt;if( text.includes('how are you') )&lt;/code&gt; for every expression, but how inefficient would it be? This is the time when NLP is useful.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Natural language processing (NLP) is &lt;strong&gt;the ability of a computer program to understand human language as it is spoken and written&lt;/strong&gt; -- referred to as natural language. It is a component of artificial intelligence (AI). NLP has existed for more than 50 years and has roots in the field of linguistics. from &lt;a href="https://www.techtarget.com/searchenterpriseai/definition/natural-language-processing-NLP#:~:text=Natural%20language%20processing%20(NLP)%20is,in%20the%20field%20of%20linguistics." rel="noopener noreferrer"&gt;techtarget.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;While I still don't fully understand how NLP works, my understanding is so far like this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A developer submits a set of sentences future users might send&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There are three types of variables A user can assign for each text.&lt;br&gt;
(1) intension: The most important part of the sentence. Only one can be assigned for one sentence. ex) I need a new iPhone! -&amp;gt; Request_add_wishlist&lt;br&gt;
(2) Entities: This is a smaller assembly piece, basically a critical keyword to define the intention. ex) I need shopping! -&amp;gt; need, shopping.&lt;br&gt;
(3) Traits : ( I'll ignore this part for now )&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An NLP program analyzes what kind of entity composition is for specific intension.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next time a user send complete new text, a trained NLP will spit out what a user means.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Among multiple different services to provide NLP, I chose to use wit.ai service, which is an open-source service developed by Facebook, Meta.&lt;/p&gt;

&lt;p&gt;The useful thing about wit.ai is chatting text I sent by discord also showed up on the wit.ai training session, so even if a bot failed to understand what I meant, I can always go to wit and correct it.&lt;/p&gt;

&lt;p&gt;The downside is documentation is not so clear, so... anyway... it's alright. I will take it...&lt;/p&gt;




&lt;h2&gt;
  
  
  2. I learned Discord.js
&lt;/h2&gt;

&lt;p&gt;In the end, I chose to create a discord bot over a Slack bot even tho I am not really a discord user. &lt;/p&gt;

&lt;p&gt;Some servers I joined to get help usually has too many channels on the sidebar, and too many discussion is going on. It just intimates too much. Like you entered in a club and see full of people dancing a Michael Jackson thriller perfectly, but I don't know that damn dance. "Quick, get out from that club!"&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%2F4s6kddyhlpg9kb6ij831.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4s6kddyhlpg9kb6ij831.gif" width="300" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, during this project, I could learn about discord.js and at the same time a little bit more about discord itself...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install discord.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exciting thing about discord js is it gives so many options to interact with other users. Can create buttons, slash commands, interact with emoji etc.. way more than just sending a message.&lt;/p&gt;

&lt;p&gt;The most useful message type is &lt;strong&gt;Embed Message&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;( I set my coordinator as Tahani from the TV series 'The good place' because I love her character on the show so much. so jolly!)&lt;/p&gt;

&lt;p&gt;Embed message returns the beautifully formatted information. You can directly add the field like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_embed.addFields({name : "Count", value : Count })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or you can create the dictionary and loop for each keys&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object.keys(style).forEach( k=&amp;gt;{
    _newEmbed.addFields({name : k , value :style[k].toString() })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful reference for discord bot -&amp;gt; &lt;a href="https://discordjs.guide/" rel="noopener noreferrer"&gt;Discord JS Guide&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. I learned Notion API
&lt;/h2&gt;

&lt;p&gt;I love notionHQ. Actually, I am obsessed with it. I've been using it since it was not the mainstream. Recently I heard it acquired automate.io. Alight, NotionHQ, Let's go! Bigger and bigger! But there were a few moments.. when I was considering leaving Notion... it's because Notion doesn't have a recurring task option!&lt;/p&gt;

&lt;p&gt;Like I wrote in the intro, repeating and repeating and repeating tasks is already so boring, and &lt;strong&gt;I have to create the checkbox of it every time?&lt;/strong&gt; Oh shit, I don't want that.&lt;/p&gt;

&lt;p&gt;I left the Notion and came back again over and over, like a troubled teenager arguing with a parent. A year ago, Notion released notion API; "This is the final, guys. I am going to build an automation system for you and going to be with you happily ever after 4everr."&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install @notionhq/client&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Notion API is great! but unfortunately, there are a few limits for (v1.0.3) right now.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Editing a block is not available yet. Basically, if you want to tick the checkbox block, you should delete the current checkbox item and append a duplicated block again. It's not impossible but kinda annoying. Duplicating a single block is not such a bad case, but how about if you want to duplicate a whole page? Sadly you still need to delete a whole page and duplicate it. If the page has a children element, the problem gets more annoying.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get children to block only return the top-level items on the hierarchy.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;children&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;NOTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&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="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so for example, if there's a notion block like this&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%2Fgich1zcv6zv64jz63xek.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgich1zcv6zv64jz63xek.JPG" width="190" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code output will be only [block_A, block_B]. So even if you duplicate the parent with its children, [block_Aa, block_Ab, block_Ba,block_Bb] won't be duplicated.&lt;/p&gt;

&lt;p&gt;If you want all the children under &lt;strong&gt;parent&lt;/strong&gt;, there's no other way than keep asking children if they has grand-grand children…    &lt;/p&gt;

&lt;p&gt;​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;block_A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasChildren&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="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;NOTION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&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="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;block_A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;
&lt;li&gt;Some block type is not supported. Just think non-native notion blocks such as embed tweet or embed Figma are not supported, which is totally fine, but. butttt. &lt;strong&gt;code blocks&lt;/strong&gt; are also not supported, so I felt kinda sad. :(&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All i want is block to save the code so I can run it by ‘eval()’ which can be just any text block. Just not pretty as I wished, that’s all. Haha&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var scripts = blocks.filter( block =&amp;gt; Object.keys(block)[0] == "callout"  )
await eval(scripts[0])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fgpi89knhb3cqhazg4bej.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpi89knhb3cqhazg4bej.JPG" alt="Every lunch time, recipe inspiration function runs." width="419" height="381"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. I learned about Cron
&lt;/h2&gt;

&lt;p&gt;One of the must-have features for the coordinator bot was sending scheduled messages, so I can do the task on time. &lt;/p&gt;

&lt;p&gt;To do so, I got to know &lt;a href="https://www.npmjs.com/package/cron" rel="noopener noreferrer"&gt;Cron&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F7yuc6fgbrxh9b2r9q61d.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7yuc6fgbrxh9b2r9q61d.gif" width="619" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A CRON expression is a string comprising five or six fields separated by white space[&lt;a href="https://en.wikipedia.org/wiki/Cron#cite_note-17" rel="noopener noreferrer"&gt;16]&lt;/a&gt; that represents a set of times, normally as a schedule to execute some routine. &lt;br&gt;
 from &lt;a href="https://en.wikipedia.org/wiki/Cron" rel="noopener noreferrer"&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;***??... Another regular expression all over again.. horror..  But actually, the breakdown of cron time is pretty simple, &lt;code&gt;min + hour + date + month + week.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now I should generate cron time by ‘Date()’. This time, instead of writing in js, I wrote as notion built-in formula so even if I update the schedule by notion, the clon time value also can be updated at the same time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;min : 
empty(prop("Unit")) ? if(minute(prop("Date")) + minute(prop("Date")) == 0, format(minute(prop("Edited"))), format(minute(prop("Date")))) : if(prop("Unit") == "minute", "*/" + format(if(empty(prop("Recurring")), 1, prop("Recurring"))), if(prop("Unit") != "hour" or prop("Unit") != "minute", if(empty(prop("Date")), if(empty(prop("Date")), format(minute(prop("Edited"))), format(minute(prop("Date")))), format(minute(prop("Date")))), "*"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such a ugly code, for now but so far it seems working as wish. (full code is on my &lt;a href="https://github.com/happping/discord-coordinator-bot#formula-for-clon-time" rel="noopener noreferrer"&gt;repo&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;While I can create a new scheduled event by Notion, I wished to create the new reminder by chat as well. Luckily Wit.ai has built-in entities called "duration" and "datetime", such a lifesaver! &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%2F2lhmx6t0r85czxg3mjm7.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lhmx6t0r85czxg3mjm7.JPG" alt="Text to clean format" width="673" height="633"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. I learned Puppeteer
&lt;/h2&gt;

&lt;p&gt;I started to feel more greedy and needy. Hey, bot, you can give weather and different time zone time, my today's tasks and project due date now.. how about.. recommendation for today's meal?? M.M.M&lt;/p&gt;

&lt;p&gt;First, I found the biggest recipe API called &lt;a href="https://spoonacular.com/food-api" rel="noopener noreferrer"&gt;Spoonacular API&lt;/a&gt;, but .. the food pictures look kinda.. not really inspiring..&lt;/p&gt;

&lt;p&gt;So instead of food API, I decided to fetch the information directly from my favourite cooking site. I heard the Puppeteer is good for web scraping, I always wanted to try, but this is my first time trying Puppeteer!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.npmjs.com/package/puppeteer" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; is such an amazing package, but there were a few confusing parts though.&lt;/p&gt;

&lt;p&gt;When I tried to get element by document.querySelector() .this returned &lt;code&gt;undefined&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt; object. I was panicked, and I thought Puppeteer failed to find the element, but it's there.. when I specifically ask textContent or src, href?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&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;return&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.selector&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;sel&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&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;As I understand is what Puppeteer gives me is not an HTML element but something else..  ( is there anybody explain a bit easy for me? i am helpless lol)&lt;/p&gt;

&lt;p&gt;Anyway, now I get a recommendation for a recipe. yay&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%2F0dmynt42pq3dnm8l5miy.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dmynt42pq3dnm8l5miy.JPG" width="506" height="840"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. I learned Heroku
&lt;/h2&gt;

&lt;p&gt;Because my bot will be deactivated when I don't run bot.js, I needed Heroku to serve it on the cloud.&lt;/p&gt;

&lt;p&gt;While I am hosting my sites on Netlify, I am still not familiar with the hosting service. I misunderstood Heroku and Netlify as the same kind of service, but this time, I learned they are not. Like this whole post is such lack of professional knowledge, this is my short understanding about Netlify and Heroku so far&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify is more for the front-end, and Heroku is more for the back-end&lt;/li&gt;
&lt;li&gt;Netlify is always active, but Heroku fall asleep...zzz&lt;/li&gt;
&lt;li&gt;Netlify is site hosting online, but Heroku is run the script on Linux&lt;/li&gt;
&lt;li&gt;Netlify start charging by build time, Heroku charging with something called &lt;strong&gt;Dyno hours&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dyno is a container that runs the command in Linux. The busier the app will be, the more dyno is required to be purchased. Because I am a free user, I have a single dyno. &lt;/p&gt;

&lt;p&gt;Free user has 1000 free dyno hours. It means that even if my app runs consistently without resting for the whole month, it wouldn't get over 730 hours. Free is always good. It makes me feel cosy.😊&lt;/p&gt;




&lt;h2&gt;
  
  
  Concluding
&lt;/h2&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%2Fzn90ov16cuu8kr1ufz7v.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzn90ov16cuu8kr1ufz7v.JPG" alt=" " width="590" height="1159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/happping/discord-coordinator-bot" rel="noopener noreferrer"&gt;My Git Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While I want to improve more and add more features, my Tahani bot works quite wonderfully right now. Great to have a companion! (sad to say that. lol)&lt;/p&gt;

&lt;p&gt;I tried a lot of new things this time, I learned so much; however, I also feel like I didn't spend enough time to learn about each of them more precisely. Maybe that's what I'm going to do with next dev.to&lt;/p&gt;

&lt;p&gt;Besides having a customised virtual assistant for my daily routine, the knowledge I earned was so valuable. There are so much potential to improve &lt;a href="https://www.instagram.com/reel/CHSAiMij_S7/?utm_source=ig_web_copy_link" rel="noopener noreferrer"&gt;my interactive character project&lt;/a&gt;&lt;br&gt;
Thank you for reading this messy post. I am not a trained developer (I a visual artist), but I am very excited to share thoughts and get to know more about creating something cool. &lt;/p&gt;

&lt;p&gt;Please comment on anything if you want to correct my post and educate me. It would be awesome to learn from all of you!&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%2Fwlyh85dxauf8xvmm38hp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlyh85dxauf8xvmm38hp.gif" width="470" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>codenewbie</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
