<?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: Piko</title>
    <description>The latest articles on Forem by Piko (@piko).</description>
    <link>https://forem.com/piko</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%2F1204485%2F38dfaf94-986d-4438-aed7-369c3b0836a7.png</url>
      <title>Forem: Piko</title>
      <link>https://forem.com/piko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/piko"/>
    <language>en</language>
    <item>
      <title>I Built an AI That Reads My Blinks and Speaks Morse Code</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Wed, 24 Sep 2025 14:27:14 +0000</pubDate>
      <link>https://forem.com/piko/i-built-an-ai-that-reads-my-blinks-and-speaks-morse-code-heres-the-code-18df</link>
      <guid>https://forem.com/piko/i-built-an-ai-that-reads-my-blinks-and-speaks-morse-code-heres-the-code-18df</guid>
      <description>&lt;p&gt;&lt;strong&gt;What if you could speak with your eyes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a thought that feels like it's straight out of science fiction. But when I first saw an AI mapping 478 points across my face in real-time, directly in my browser, that science fiction suddenly felt tangible. The white mesh, clinging to every contour, knew the exact position of my lips, my cheeks, the tip of my nose. It knew when I blinked.&lt;/p&gt;

&lt;p&gt;I decided I was going to use it to achieve a lifelong dream: to actually speak with my eyes. The plan was to translate my blinks into Morse code.&lt;br&gt;
It turned out to be way harder and far more interesting than I ever imagined!&lt;/p&gt;

&lt;p&gt;This project was also deeply inspired by the incredible story of Jeremiah Denton, a captured US pilot who blinked the word "TORTURE" in Morse code during a propaganda video. I wanted to see if I could build a digital, open-source version of that incredible human ingenuity.&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%2Funcjcvpwm9tk83h2vabe.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%2Funcjcvpwm9tk83h2vabe.gif" alt="Jeremiah Denton Blinking In Morse Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a breakdown of the most interesting technical challenges and how I solved them.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Tech Stack is surprisingly simple:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;JavaScript (ES6 Modules):&lt;/strong&gt; To handle all the logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;MediaPipe Face Landmarker:&lt;/strong&gt; For the real-time facial landmark and blendshape detection. This is the core of the project.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;HTML5 &amp;amp; CSS3:&lt;/strong&gt; For the UI. No frameworks needed!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Challenge #1: How to Tell a Dot from a Dash?
&lt;/h3&gt;

&lt;p&gt;The first problem was detecting blinks. MediaPipe provides real-time scores for 52 different facial expressions, called "blendshapes," that track everything from a smile to a raised eyebrow. Luckily for me, two of those blendshapes were exactly what I needed: eyeBlinkLeft and eyeBlinkRight.&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%2F0ykya3bvsnwhef0jo3b0.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%2F0ykya3bvsnwhef0jo3b0.gif" alt="facelandmarker codepen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The way they work is simple: a score near 0 means the eye is open, and a score closer to 1 means it's closed.&lt;/p&gt;

&lt;p&gt;My first thought was, "Easy, I'll just use setTimeout to see how long the eye is closed." This was a terrible idea. It was messy and out of sync with the video's requestAnimationFrame loop.The way they work is simple: a score near 0 means the eye is open, and a score closer to 1 means it's closed.&lt;br&gt;
My first thought was, "Easy, I'll just use setTimeout to see how long the eye is closed." This was a terrible idea. It was messy and out of sync with the video's requestAnimationFrame loop.&lt;/p&gt;

&lt;p&gt;The solution was much cleaner: &lt;strong&gt;count the frames.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since the AI analyzes every frame, I could just increment a counter for every consecutive frame the eyes were closed.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;The Logic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If the eye-blink score is above a threshold (say, &lt;code&gt;0.5&lt;/code&gt;), increment &lt;code&gt;blinkFrameCounter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  If the score drops below the threshold, check the counter's value.&lt;/li&gt;
&lt;li&gt;  If &lt;code&gt;blinkFrameCounter&lt;/code&gt; is between &lt;code&gt;2-14&lt;/code&gt; frames, it's a &lt;strong&gt;short blink (dot)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  If it's &lt;code&gt;15&lt;/code&gt; or more frames, it's a &lt;strong&gt;long blink (dash)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the code snippet for that logic:&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="c1"&gt;// From js/detection.js&lt;/span&gt;

&lt;span class="c1"&gt;// Check if the average blink score is above our threshold&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;rightEyeBlink&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;leftEyeBlink&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BLINK_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blinkFrameCounter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Still blinking, don't do anything yet&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// If we've stopped blinking, check how long we blinked for&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;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blinkFrameCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BLINK_CONSECUTIVE_FRAMES&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;isLong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blinkFrameCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LONG_BLINK_FRAMES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blinkFrameCounter&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="c1"&gt;// Reset for next time&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isLong&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&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;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Success!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// If we didn't blink long enough, just reset&lt;/span&gt;
&lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blinkFrameCounter&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge #2: Detecting Nods Without the Jitters
&lt;/h3&gt;

&lt;p&gt;For separating letters and words, I wanted to use head nods. My first attempt was to track the &lt;code&gt;y&lt;/code&gt; position of the nose tip (landmark #4) and compare it frame-by-frame. This failed spectacularly. The system was so sensitive it would trigger from me just breathing!&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%2Ffo30tp1z0amchvuidqfn.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%2Ffo30tp1z0amchvuidqfn.gif" alt="nose tip tracking"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key was to realize a nod is a journey, not a snapshot. The solution: a &lt;strong&gt;"sliding window"&lt;/strong&gt; algorithm.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;The Logic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Keep a running history of the nose's &lt;code&gt;y&lt;/code&gt; position in an array (&lt;code&gt;noseYHistory&lt;/code&gt;) for the last 15 frames.&lt;/li&gt;
&lt;li&gt;  In every frame, find the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; Y-position within that history.&lt;/li&gt;
&lt;li&gt;  If &lt;code&gt;(max - min)&lt;/code&gt; is greater than our movement threshold, we have a clear, intentional nod.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This smoothed out all the noise and only registered real nods.&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="c1"&gt;// From js/detection.js&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;processNod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;landmarks&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;noseY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOSE_TIP_INDEX&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;DOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;videoHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&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;noseY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Keep the history at a fixed length&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;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOD_HISTORY_LENGTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Don't detect another nod immediately&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;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodCooldownCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodCooldownCounter&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOD_HISTORY_LENGTH&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;minY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&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;maxY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&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;maxY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minY&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;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOD_MOVEMENT_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodCooldownCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOD_COOLDOWN_FRAMES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Set cooldown&lt;/span&gt;
            &lt;span class="nx"&gt;appState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noseYHistory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// Clear history after detection&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// We have a nod!&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="kc"&gt;false&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;
  
  
  Challenge #3: The Cheek-to-Nose Ratio for Head Turns
&lt;/h3&gt;

&lt;p&gt;Detecting head turns for backspace and spaces had the same "jitter" problem. The solution came from noticing how my face's proportions change when I turn.&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%2Fjdo12e6qn11b03extsxw.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%2Fjdo12e6qn11b03extsxw.gif" alt="cheek to nose ratio change during turns"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When looking straight, the distance from my nose to my left cheek is about the same as to my right cheek. When I turn left, my right cheek gets "wider" from the nose's perspective, and my left cheek gets "narrower."&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;The Logic:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Get the &lt;code&gt;x&lt;/code&gt; coordinates of the left cheek (#454), right cheek (#234), and nose (#4).&lt;/li&gt;
&lt;li&gt;  Calculate the total width: &lt;code&gt;leftCheek.x - rightCheek.x&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Calculate the ratio: &lt;code&gt;(nose.x - rightCheek.x) / totalCheekWidth&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  When looking straight, this ratio is &lt;code&gt;~0.5&lt;/code&gt;. When I turn left, it goes up (&lt;code&gt;&amp;gt; 0.65&lt;/code&gt;). When I turn right, it goes down (&lt;code&gt;&amp;lt; 0.35&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This worked so well I was very happy!&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="c1"&gt;// From js/detection.js&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;processTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;landmarks&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;nose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOSE_TIP_INDEX&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;leftCheek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LEFT_CHEEK_INDEX&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;rightCheek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;landmarks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RIGHT_CHEEK_INDEX&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;totalCheekWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;leftCheek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rightCheek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;noseToRightCheekDist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;rightCheek&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;totalCheekWidth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Avoid division by zero&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;turnRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;noseToRightCheekDist&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;totalCheekWidth&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;turnRatio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TURN_RIGHT_RATIO_THRESHOLD&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;left&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;else&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;turnRatio&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TURN_LEFT_RATIO_THRESHOLD&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;right&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;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Head is centered&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;This was one of the most fun projects I've ever built. It's a great example of how accessible and powerful browser-based AI has become. What once required specialized hardware can now be built with a bit of creative problem-solving and open-source tools.&lt;/p&gt;

&lt;p&gt;If you have any questions, drop them in the comments below. I'd love to hear your thoughts! What would you build with this tech?&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Video:
&lt;/h3&gt;

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




&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://fantastic-hamster-94cff7.netlify.app/" rel="noopener noreferrer"&gt;Click Here!&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Full Video:&lt;/strong&gt; &lt;a href="https://youtu.be/LB8nHcPoW-g" rel="noopener noreferrer"&gt;Watch on YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/PikoCanFly/speak-with-your-eyes-ai" rel="noopener noreferrer"&gt;Click Here!&lt;/a&gt;
Happy coding! ✨&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>computervision</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to Deploy a Django App to Production in 2025</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Mon, 01 Sep 2025 18:13:59 +0000</pubDate>
      <link>https://forem.com/piko/how-to-deploy-a-django-app-to-production-in-2025-5df6</link>
      <guid>https://forem.com/piko/how-to-deploy-a-django-app-to-production-in-2025-5df6</guid>
      <description>&lt;h2&gt;
  
  
  From Localhost to Live: The Essential Django Deployment Guide
&lt;/h2&gt;

&lt;p&gt;You've done it. You’ve built a Django app, and it’s running beautifully on your local machine with its virtual environment and all dependencies installed. Now comes the big question: how do you get this thing ready for the world to see?&lt;/p&gt;

&lt;p&gt;Deploying a Django app isn't just about copying files to a server. It's about transforming your development setup into a secure, efficient, and reliable production environment. This guide will walk you through the essential steps to bridge that gap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Golden Rule: Never Hardcode Secrets
&lt;/h3&gt;

&lt;p&gt;Before we touch anything, let's establish the most important principle: &lt;strong&gt;separate your configuration from your code&lt;/strong&gt;. Your production environment will have different settings than your local one (different databases, secret keys, debug settings). Hardcoding these values is a recipe for security risks and deployment headaches.&lt;/p&gt;

&lt;p&gt;The solution is to use &lt;strong&gt;environment variables&lt;/strong&gt;. We'll store our configuration in the hosting environment and have Django read from there.&lt;/p&gt;

&lt;p&gt;For local development, we can simulate this with a &lt;code&gt;.env&lt;/code&gt; file and the &lt;code&gt;python-dotenv&lt;/code&gt; library.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the library:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in your project's root (the same level as &lt;code&gt;manage.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
DEBUG=True
SECRET_KEY=your-insecure-local-secret-key
DATABASE_URL=sqlite:///db.sqlite3
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Add &lt;code&gt;.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file immediately! This file should never be committed to version control.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Production-Proof Your &lt;code&gt;settings.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Now, let's modify your &lt;code&gt;settings.py&lt;/code&gt; to read from these environment variables.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.1 Load Environment Variables
&lt;/h4&gt;

&lt;p&gt;At the top of your &lt;code&gt;settings.py&lt;/code&gt;, add the following to load the &lt;code&gt;.env&lt;/code&gt; file (it won't do anything in production, which is what we want).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1.2 Secure &lt;code&gt;SECRET_KEY&lt;/code&gt; and &lt;code&gt;DEBUG&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Replace your hardcoded &lt;code&gt;SECRET_KEY&lt;/code&gt; and &lt;code&gt;DEBUG&lt;/code&gt; values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="c1"&gt;# Pull the SECRET_KEY from the environment
&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Set DEBUG to False unless explicitly set to "True" in the environment
&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEBUG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1.3 Configure &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;In production, you must tell Django which domain(s) are permitted to host your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="c1"&gt;# We'll get the actual domain from our hosting provider later
&lt;/span&gt;&lt;span class="n"&gt;ALLOWED_HOSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALLOWED_HOSTS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost,127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Important for security when running on HTTPS
&lt;/span&gt;&lt;span class="n"&gt;CSRF_TRUSTED_ORIGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CSRF_TRUSTED_ORIGINS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Bring in the Production Stack
&lt;/h3&gt;

&lt;p&gt;Your development server and database aren't cut out for production traffic. Let's bring in the right tools for the job.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 Use a Production WSGI Server: Gunicorn
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;manage.py runserver&lt;/code&gt; command is only for development. For production, you need a robust WSGI server. &lt;strong&gt;Gunicorn&lt;/strong&gt; (Green Unicorn) is a popular and reliable choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.2 Serve Static Files with WhiteNoise
&lt;/h4&gt;

&lt;p&gt;When &lt;code&gt;DEBUG&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt;, Django stops serving static files (your CSS, JavaScript, and images). &lt;strong&gt;WhiteNoise&lt;/strong&gt; is the easiest way to get your app to serve its own static files securely and efficiently.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install WhiteNoise:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;whitenoise
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Add WhiteNoise to your middleware, right after the &lt;code&gt;SecurityMiddleware&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Set the &lt;code&gt;STATIC_ROOT&lt;/code&gt; and configure the staticfiles storage backend.
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;
&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;django.middleware.security.SecurityMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whitenoise.middleware.WhiteNoiseMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Add this line
&lt;/span&gt;    &lt;span class="c1"&gt;# ... other middleware
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# This is where collectstatic will gather all static files
&lt;/span&gt;&lt;span class="n"&gt;STATIC_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;staticfiles&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This improves performance by caching and compressing files
&lt;/span&gt;&lt;span class="n"&gt;STORAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;staticfiles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whitenoise.storage.CompressedManifestStaticFilesStorage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  2.3 Switch to a Production Database: PostgreSQL
&lt;/h4&gt;

&lt;p&gt;SQLite is a single-file database that can't handle concurrent requests well, making it unsuitable for most production sites. PostgreSQL is a powerful, open-source database that's a standard choice for Django apps.&lt;/p&gt;

&lt;p&gt;To make connecting easy, we'll use &lt;code&gt;dj-database-url&lt;/code&gt; and &lt;code&gt;psycopg2-binary&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Packages:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;psycopg2-binary dj-database-url
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
This will read your database connection string from a single &lt;code&gt;DATABASE_URL&lt;/code&gt; environment variable. It cleverly defaults to your existing SQLite setup if the variable isn't found.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dj_database_url&lt;/span&gt;

&lt;span class="c1"&gt;# ... at the bottom of the file ...
&lt;/span&gt;
&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dj_database_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;# Default to SQLite if DATABASE_URL is not set
&lt;/span&gt;        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sqlite:///&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db.sqlite3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Automate Your Deployment Tasks
&lt;/h3&gt;

&lt;p&gt;You need to perform the same set of tasks every time you deploy: install packages, collect static files, and run migrations. Let's automate this with a simple build script.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;code&gt;build.sh&lt;/code&gt; in your project's root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Exit immediately if a command exits with a non-zero status.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Install Python dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Run Django's collectstatic to gather static files&lt;/span&gt;
python manage.py collectstatic &lt;span class="nt"&gt;--no-input&lt;/span&gt;

&lt;span class="c"&gt;# Apply any database migrations&lt;/span&gt;
python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, make the script executable: &lt;code&gt;chmod +x build.sh&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Preparations
&lt;/h3&gt;

&lt;p&gt;You're almost there!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/strong&gt; Your virtual environment now has Gunicorn, WhiteNoise, and other production packages. Update your requirements file to reflect this.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip freeze &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Push to Git:&lt;/strong&gt; Commit all your changes and push them to your GitHub (or GitLab) repository. Your code is now fully prepared for deployment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Time to Deploy!
&lt;/h3&gt;

&lt;p&gt;Now you can head to your hosting platform of choice (like Seenode, Heroku, Render, etc.). The process will generally be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Connect Your Repo:&lt;/strong&gt; Link the platform to your GitHub repository.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Set Build &amp;amp; Start Commands:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Build Command:&lt;/strong&gt; &lt;code&gt;./build.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Start Command:&lt;/strong&gt; &lt;code&gt;gunicorn movie_spot.wsgi --bind 0.0.0.0:80&lt;/code&gt; (Replace &lt;code&gt;movie_spot&lt;/code&gt; with your project's name).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Set Environment Variables:&lt;/strong&gt; This is where you'll add your production &lt;code&gt;SECRET_KEY&lt;/code&gt;, set &lt;code&gt;DEBUG&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;, and provide the &lt;code&gt;DATABASE_URL&lt;/code&gt; from the PostgreSQL database you create on the platform. You'll also set your live domain in &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; and &lt;code&gt;CSRF_TRUSTED_ORIGINS&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it! With these steps, you've taken your Django project from a local development build to a secure, robust, and production-ready application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are you a visual learner?
&lt;/h2&gt;

&lt;p&gt;I've created a &lt;a href="https://youtu.be/99tjYN1wihg" rel="noopener noreferrer"&gt;complete video walkthrough&lt;/a&gt; of this deployment guide. Watch and code along as we take our project from localhost to a live, production-ready application on the cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/99tjYN1wihg" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimg.youtube.com%2Fvi%2F99tjYN1wihg%2Fmaxresdefault.jpg" alt="Django Deployment Tutorial: From Local Setup to Live Server on Seenode | Beginner Friendly Guide&amp;lt;br&amp;gt;
" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy deploying&lt;/p&gt;

</description>
      <category>django</category>
      <category>deployment</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>javaScript newbies, I have a challenge for you!</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Sat, 18 Nov 2023 15:21:31 +0000</pubDate>
      <link>https://forem.com/piko/javascript-newbies-i-have-a-challenge-for-you-4eif</link>
      <guid>https://forem.com/piko/javascript-newbies-i-have-a-challenge-for-you-4eif</guid>
      <description>&lt;p&gt;As a beginner or someone learning programming, it can be overwhelming to start working on solo projects. However, like anything complex, it helps to take baby steps.&lt;/p&gt;

&lt;p&gt;For example, after you learn something new you could try to figure out a way to apply what you have learned. You do not have to create anything massive or huge. Creating something small but has a clear purpose can be a greateway to get your feet wet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Okay, but how do I find ideas on what to create?
&lt;/h2&gt;

&lt;p&gt;Every invention starts with an idea, and every idea starts with a question. So every time you learn something new try to ask yourself something like:&lt;br&gt;
 “How can I use this to create something with purpose?”&lt;br&gt;
“Do I know of a problem that this can help solve?”&lt;/p&gt;

&lt;p&gt;It also helps to try and stop after learning something new, and see how you could use that with the things you have already learned. For example, I wrote an RGB color generator function a while back, I was still in my early coding days. It started with me asking myself “ if the RGB model consists of different values of the colors Red, Blue, and Green. Can I just make a function that relies on the &lt;code&gt;Math.random&lt;/code&gt; method to generate different colors, by generating a random integer value of each color component?”&lt;/p&gt;

&lt;p&gt;It was at a time, when I felt I knew all the concepts that would allow me to create a function like that, but I still lacked the confidence. However, I pushed myself and I did it, and it was only possible because I broke down the problem into simple TODOs. &lt;/p&gt;
&lt;h2&gt;
  
  
  Here is your Challenge!
&lt;/h2&gt;

&lt;p&gt;If you are someone who is new to coding and is looking for a challenge to help you slowly breakout of tutorial hell, and start creating your own project. I challenge you to try and create an RGB color generator function. I made a code pen - You'll find it below complete with all the extra code required, and all you have to do it complete the function. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The concepts you should be familiar with to complete the challenge:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defining a function&lt;/li&gt;
&lt;li&gt;Declaring variables: We declare a variable to hold the values of - &lt;a href="https://www.rapidtables.com/web/color/RGB_Color.html"&gt;The RGB color format &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Math.random method &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;To try the challenge just complete the colorGen function in the JS section:&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Spend at least half an hour trying, and after that if you feel completely at loss. You could watch this short tutorial to help you out:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/t4ePblC6jPk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Best of luck!&lt;/p&gt;

</description>
      <category>learn</category>
      <category>javascript</category>
      <category>codenewbie</category>
      <category>challenge</category>
    </item>
    <item>
      <title>Create an SVG Fractal Tree by Writing a Recursive Function - Easy JavaScript Tutorial</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Wed, 15 Nov 2023 20:11:29 +0000</pubDate>
      <link>https://forem.com/piko/i-am-very-excited-i-wrote-a-simple-function-that-generates-fractal-trees-3986</link>
      <guid>https://forem.com/piko/i-am-very-excited-i-wrote-a-simple-function-that-generates-fractal-trees-3986</guid>
      <description>&lt;p&gt;After a long hike searching for mushrooms and observing the trees around me with their beautiful patterns, I wondered if I could create a function to generate a tree in JavaScript. The next morning, I started researching and learned a bit about fractal mathematics and how it involves repeating the same pattern at different scales. This realization meant that I could probably create a recursive function to generate my tree.&lt;/p&gt;

&lt;p&gt;I set out to create the function, and it turned out to be much simpler than I initially thought. Of course, this simplicity came after rewriting messy code multiple times. So now, I finally have a function that creates a cool fractal tree in less than 20 lines of code - it could be simplified to 10.&lt;/p&gt;

&lt;p&gt;To render the tree, I used the SVG element, which I really enjoy working with. The function draws the first line (the base of the tree) and then recursively draws two lines at a time. The angles and lengths are calculated dynamically.&lt;/p&gt;

&lt;p&gt;I learned a lot during this process, especially about how elegant and enjoyable using recursive functions can be. If you want to see how I did it or learn about using recursive functions to generate a pattern, I made a YouTube tutorial:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/gwv7yqwxN1g"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you want to jump straight to the code:&lt;/p&gt;

&lt;p&gt;Code Pen: &lt;a href="https://codepen.io/Piko-the-solid/pen/RwvjNed"&gt;https://codepen.io/Piko-the-solid/pen/RwvjNed&lt;/a&gt;&lt;br&gt;
Github: &lt;a href="https://github.com/PikoCanFly/fractal-tree"&gt;https://github.com/PikoCanFly/fractal-tree&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>svg</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>What made you decide to learn coding? Which language do you prefer and which do you use the most? Why?</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Sun, 12 Nov 2023 22:16:43 +0000</pubDate>
      <link>https://forem.com/piko/what-made-you-decide-to-learn-coding-which-language-do-you-prefer-and-which-do-you-use-the-most-why-lhc</link>
      <guid>https://forem.com/piko/what-made-you-decide-to-learn-coding-which-language-do-you-prefer-and-which-do-you-use-the-most-why-lhc</guid>
      <description></description>
    </item>
    <item>
      <title>I made an app that generates random blob creatures! Use cases include: endless avatar creation</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Wed, 08 Nov 2023 15:05:56 +0000</pubDate>
      <link>https://forem.com/piko/i-made-an-app-that-generates-random-blob-creatures-use-cases-include-endless-avatar-creation-5dio</link>
      <guid>https://forem.com/piko/i-made-an-app-that-generates-random-blob-creatures-use-cases-include-endless-avatar-creation-5dio</guid>
      <description>&lt;p&gt;So the other day I discovered the Haikei blob generator and I just started obsessively hitting that generate button; you know the one that looks like a die 🎲 and after 15 minutes of mindless clicking, I decided I needed to make my very own blob generator. And the research began!&lt;/p&gt;

&lt;p&gt;I revisited articles about Bezier curves, and learnt that about using them in SVG. After hours of experimenting I came up with a function that generates blobs! Later, I just added some eyes! And now I have a blob creature generator.&lt;/p&gt;

&lt;p&gt;If you want to see how I did it, check out this video I made:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/eWgc1_30sHU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;If you want to checkout the code:&lt;br&gt;
&lt;a href="https://github.com/PikoCanFly/blobster.git"&gt;https://github.com/PikoCanFly/blobster.git&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy &lt;del&gt;blobbing&lt;/del&gt; coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>math</category>
      <category>svg</category>
    </item>
    <item>
      <title>I wrote a function that creates a drawing animation for any logo or simple SVG Image</title>
      <dc:creator>Piko</dc:creator>
      <pubDate>Tue, 07 Nov 2023 20:12:40 +0000</pubDate>
      <link>https://forem.com/piko/i-wrote-a-function-that-creates-a-drawing-animation-for-any-logo-or-simple-svg-image-5g1e</link>
      <guid>https://forem.com/piko/i-wrote-a-function-that-creates-a-drawing-animation-for-any-logo-or-simple-svg-image-5g1e</guid>
      <description>&lt;p&gt;A week + couple of days ago I wrote a JavaScript function that I am so proud of; it can quickly create a drawing style animation from any SVG logo/simple SVG image. It's proven to be a fun way to quickly create loading animations. I have shared it with a few people and someone suggested posting about here. - side note: This my first post on dev.to &lt;/p&gt;

&lt;p&gt;To create the animations I played around with the strokeDasharray and strokeDashoffset CSS attributes.&lt;/p&gt;

&lt;p&gt;If you want to see how I made it here is a short YT video:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/jRhDakGGlGE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>css</category>
    </item>
  </channel>
</rss>
