megacolorboy

Abdush Shakoor's Weblog

Writings, experiments & ideas.

A new makeover for 2022

Function over fashion. Minimal. Scraped out the redundant and added some new features.

I have been reading a lot of articles and blogs than writing these days, thanks to my hectic schedule, I barely get the time to work on my blog.

Initially, I thought of porting my blog to Django or Hugo but I decided to tune up my blog engine by adding new features like Tags/Categories and Archives, optimizing the time-taken to convert Markdown to HTML files and making it more flexible for the future.

Also, I revamped the entire blog's layout by writing a new theme and making it a bit more responsive than it previously was. However, I retained the colors and the fonts used. Thank you Rasmus for the amazing Inter font ❤.

Still got some work to do

Yes, new layout and all but there's still few left in pending and hopefully, I'd complete it by the end of July 2022. But here's what's left:

  • Thanks to Simon Willison's Blog, I took the idea of Blogmarks and it's basically a collection of articles and quotes that I find interesting and would share it here.
  • Planning to move all project/widget based posts to a section called "Projects", which would also have a different layout.
  • Book reviews and a new resume template
  • Dark theme with a better color palette (I wasn't happy with the color scheme that I wrote previously.)

Wait, where's megacolorboy?

For the time being, I thought of removing my username "megacolorboy" from the Masthead for the sake of professionalism and hey, I ain't a kid no more, so time to grow up, you know?

I'd like to thank my beautiful wife for letting me work on my passion even though it might annoy her at times, I love her for that ❤!

Hope you like the new makeover!

I switched to Fedora 35

Switched from Void Linux to Fedora 35

Yes, I distro-hopped again but I guess it's a meaningful decision.

I was using Void Linux with an i3wm tiling manager setup to see if it would improve my programming productivity.

I've got to say that i3wm is an amazing tiling manager and if you are into tiling windows, this should be something worth of your time.

On the other hand, I thought of trying out Void Linux because I was tired of Ubuntu and it's bloat, whereas Void Linux is quite minimal and lightweight.

Then, why leave it?

It has a good package manager that is similar to Arch's AUR repository but the lack of documentation and updates made me feel that there's barely contributions for it.

I had issues using docker and virtualbox in it, often giving me compatibility issues at times, which left me frustrated.

Hello, Fedora!

When I decided to distro-hop, I had to give myself some time and research as to which distro would really suit me as a programmer and that would just work out of the box.

I wanted something that:

  • is developer-oriented.
  • stable but bleeding-edge.
  • just works out-of-the-box.
  • has a minimal and clean looking interface.

That's when Fedora came into the picture and according to what I have researched especially on forums like Reddit, it's directed towards programmers and sysadmins.

Another sellout point for me was RedHat because I use CentOS at my workplace to configure and deploy web applications.

RedHat developers are really dedicated and ensure that they push the stable yet bleeding-edge releases.

The installation process was smooth and the new Gnome 41 is a bliss.

I didn't want to use i3wm this time because I felt that I always tiled multiple terminal windows, so I thought I would replace that with tmux instead. And besides, I don't really want to spend a lot of time tinkering each and every part of my UI, I'd rather spend that time writing code that solves problems instead.

Let's see how long Fedora would last!

Until next time, then!

Is SELinux complicated?

If you turn off SELinux for a living, try this.

This might look a rant post but hear me out, okay?

Ever deployed an application on CentOS and tried to figure out why it isn't working when it's caused due to some permission issue by SELinux enabled?

Yeah, I know how annoying that is. So, whenever I go on StackOverflow, many people suggest to completely turn it off.

Well, it might be tempting to type sudo setenforce 0 (which sets SELinux to Permissive mode), I wouldn't really recommend you to edit the SELinux config and disable it completely.

I recently read a blog post on CentOS's official blog and I'd like to quote them for asking this:

But why do articles feel the need to outright deactivate SELinux rather than help readers work through any problems they might have? Is SELinux that hard?

Good question. Is it really that hard or is it because we don't really understand it? I think it's the latter.

But why so?

I guess, as users, we really need some sort of user interface to receive feedback because not everyone is really good at the terminal and most importantly, not good at memorizing options and flag combinations.

Here's a sample error that I would face sometimes while deploying a Laravel web application on CentOS 7:

[Mon May 16 11:39:32.996441 2016] [:error] [pid 2434] [client XXX.XXX.XXX.XXX:8080] PHP Fatal error:  Uncaught UnexpectedValueException: The stream or file "/var/www/html/MYSITE/storage/logs/laravel.log" could not be opened: failed to open stream: Permission denied in /var/www/html/MYSITE/bootstrap/cache/compiled.php:13701
Stack trace:
#0 /var/www/html/MYSITE/bootstrap/cache/compiled.php(13635): Monolog\\Handler\\StreamHandler->write(Array)
#1 /var/www/html/MYSITE/bootstrap/cache/compiled.php(13396): Monolog\\Handler\\AbstractProcessingHandler->handle(Array)
#2 /var/www/html/MYSITE/bootstrap/cache/compiled.php(13494): Monolog\\Logger->addRecord(400, Object(Symfony\\Component\\Debug\\Exception\\FatalErrorException), Array)
#3 /var/www/html/MYSITE/bootstrap/cache/compiled.php(13189): Monolog\\Logger->error(Object(Symfony\\Component\\Debug\\Exception\\FatalErrorException), Array)
#4 /var/www/html/MYSITE/bootstrap/cache/compiled.php(13160): Illuminate\\Log\\Writer->writeLog('error', Object(Symfony\\Component\\Debug\\Exception\\FatalErrorException), Array)
# in /var/www/html/MYSITE/bootstrap/cache/compiled.php on line 13701

By looking at this error, you do understand it's permission-related but it doesn't really indicate that it's an SELinux problem because there could be thousands of reasons why there could be such an error. And of course, when I look it up on Google, I do find many people facing the same issue due to SELinux being enabled.

Instead of disabling it completely, I would try doing this:

chcon -R -t httpd_sys_rw_content_t storage

And off goes the error and the application didn't give any permission-related issues again.

Is it worth disabling SELinux? No.

But does SELinux have an issue of being user friendly? I guess, yes.

I'm just like you, sometimes, I do switch off SELinux at times when it's critical to deploy the application so that I can leave home early but that's not really a good practice.

Conclusion

I do wish that the open source developers of SELinux could do something to make much more friendlier to use? Or maybe, some way that let's the user know what to do instead of letting the user chmod-ing and chown-ing files and directories endlessly. Or worse, completely disabling SELinux!

Hope you found this article interesting!

Stay tuned for more.

On learning new technologies

Sharing my tips on how I learn new technologies at work and home nowadays.

If you're a programmer, you already know that it's quite a daunting task to keep up with the latest stream of technologies:

  • Frameworks
  • Libraries
  • Programming Languages
  • Programming Paradigms

And there's never an end to it, it's just an ever-growing thing.

So, whenever I'm walking or commuting, sometimes, these questions pop up in my head:

  1. How can I learn something new without wasting my free-time?
  2. How can I learn all of the existing technologies out there? Is it possible?

Well, it's technically impossible for me to know about everything — in and out. Computer Science is such a broad field and has a lot of branches and sub-branches.

However, you can learn a piece of technology just enough to know that it exists and know when to learn more about it when the time arises.

Don't try to learn everything out there

In my years of developing web applications, I have seen two types of programmers:

  1. One who has deep knowledge of a particular technology
  2. An all-rounder but also not-so-deep knowledge about multiple technologies

Where do I fall ? Personally, I feel that I belong to the latter kind because I believe that spending a lot of time trying to specialize or being an expert in one technology is just too time-consuming and given the fact that everyday there's a new framework or programming language released out there, it's better to put yourself in the middle ground.

Why you ask? Here's why:

  1. Technology dies fast. For example, there was a time when jQuery used to be really popular amongst the Front End Developers but today, most of them move towards libraries like ReactJS, VueJS and NextJS to build their projects and sooner or later, another framework will come out to replace ReactJS and become the next thing.
  2. I don't think most of the technologies out there are relevant to the problem that you are trying to solve. Like why do I need to look into an API about geospatial analytics unless I'm trying to build an application that needs such a requirement, you get it right?

Basically, trying to learn every single piece of technology out there, it's just basically wasting time.

Ways and source of learning new stuff

Whenever I wanted to learn a new programming language, I would often go to Project Euler to flex my skills and that would help me learn more about it's features. Recently, I started LeetCode and Advent of Code - 2021. It's quite interesting solving these challenges as it helps you to learn more about the technology and feels good after solving a problem.

The next thing I would recommend is following a tech news feed like Hacker News but it can get quite addicting and distracting at times. Another good source of information are weekly newsletters. If you don't really know which one to follow, here's a repository filled with Awesome Newsletters that spans various areas like DevOps, Front end, Back end and Data Science. Try signing up on one of them and see how it goes.

Time is costly, use it wisely

The brain can only do so much in about 9 to 10 hours a day and in order for you to be efficient and productive in learning new stuff, try the following:

  1. Spend an hour learning, reading or practicing a new tool.
  2. Interesting stuff but not needed yet? Try bookmarking or make a note of it, so that you can revisit it again.
  3. If it fits your needs and project requirements, then go ahead and learn it!

If you found this article useful, please share it with your friends and colleagues.

Stay tuned for more!

Dark/Light mode with CSS and JavaScript

A simple guide on how to implement a dark/light theme switcher with CSS and JavaScript

It's quite common these days that many websites let their users to decide their preferred color scheme(s). Giving this sort of customizability offers good user experience.

In this article, I'll provide a simple step-by-step guide on how to implement a dark/light theme switcher with HTML, CSS and JavaScript.

Prerequisites

This article assumes that the reader has a basic know-how on HTML, CSS, JavaScript and basic knowledge on using the command-line.

Using CSS variables

I always wanted to implement one for this website too and I thought of making use of CSS variables as I found it to be quite straight forward and I don't have to worry too much about browser support.

Try adding the below CSS to your stylesheet:

    :root {
        --background-color: white;
        --font-color: black;
        --accent-color: red;
        --alt-background-color: black;
        --alt-font-color: white;
        --alt-accent-color: yellow;
    }

    html {
        background-color: var(--background-color);
        color: var(--font-color);
    }

    a {
        color: var(--accent-color);
    }

    html[data-theme="dark"] {
        background-color: var(--alt-background-color);
        color: var(--alt-font-color);
    }

    html[data-theme="dark"] a {
        color: var(--alt-accent-color);
    }
   

The :root selector contains a set of default values and in this case, these are just different colors, kind of like how we initialize variables in other programming languages.

For example, whenever the data-theme attribute is set to dark, the default values will be overidden by the html[data-theme="dark"] CSS rule for the theme to take effect.

Really, it's that simple!

Add some markup

That depends on what you really want to have in your website but for this tutorial, you can just place a simple button somewhere in your navigation bar or anywhere you like:

<button class="themeSwitcher">Dark/Light</button>

Toggle between light and dark themes

Yes, we are getting there and you just have to write a simple logic that checks if whether the current theme is dark or light based on the class used on the <body> element.

$('.themeSwitcher').on('click', function(){
    switch($('body').attr('data-theme')){
        case "dark":
            $('body').attr('data-theme', 'dark');
            break;

        case "light":
        default:
            $('body').attr('data-theme', '');
            break;
    }
});

Save user's preference in their browser

If your button works as expected, good! Now, once you refresh the page, the background would return to it's default mode but that's not what we wanted, right?

But why does it return instead of staying dark? Because your "preference" is not stored in your browser.

Modify your code to store your preferences in your browser:

<script>
$('.themeSwitcher').on('click', function(){
    switch($('body').attr('data-theme')){
        case "dark":
            $('body').attr('data-theme', 'dark');
            localStorage.setItem("theme", "dark");
            break;

        case "light":
        default:
            $('body').attr('data-theme', '');
            localStorage.setItem("theme", "");
            break;
    }
});
</script>

This should work fine but you'll want to avoid the "flickering" issue while changing themes or refreshing the page, in order to do that, make sure that you check the user preference before the page is completely loaded:

<script>
    if(localStorage.theme){
        document.documentElement.setAttribute('data-theme', localStorage.getItem("theme"));
    }
</script>

Conclusion

Well, if you've noticed, I wrote a simple theme switcher for my blog too. Try it out and you can inspect the code to see how it works.

Hope you enjoyed this article!

Stay tuned for more!

One year of writing TIL articles

A little self-reflection on how writing TIL articles has improved my productivity and learning.

Last year, around this time, I was working from home due to the COVID-19 restrictions placed by the UAE Government.

During this time, I noticed that I ran out of ideas for my blog and didn't even get time to maintain it for a long time.

Then, I came across Josh Branchaud's TIL collection, which inspired me to start my own TIL section in my blog. At first, I had the idea of merging it with my main blog but then, I thought of keeping it separate and well, fast-forward to a year now, it's been one of the most productive platforms for writing short articles based on whatever I have learnt or solved.

As for those of you who might not know, TIL a.k.a stands for Today I Learned and what I really liked about it is that there isn't any barrier that stops you from writing a short article and that was the one thing that motivated me to write something on my blog.

Has it helped it you?

Yes, it did help me in two ways.

Professionally, it has motivated me to learn more about my field and share my knowledge about whatever I learned while solving a particular problem or a tip that I found useful. The idea was likened to a personal StackOverflow repository that I would often revisit to refresh my memory.

Personally, it helped me reflect my values as a programmer and made me realize that there's a lot of things to learn out there and it's pretty much endless as there's always something to learn every single day. And yes, it helped me improve my writing abilities too.

Although, this article resides in a what I would call it as a "Technical blog", I guess, this applies to everyone who wants to refresh their writing productivity and help them get started back on the tracks again.

Hope you liked reading this article.

Stay tuned for more!

Understanding SPF, DKIM and DMARC protocols

An insight into the three main email security protocols that protects your email from malicious attackers.

Today, email spam is one of the most common cyberattacks conducted by people with malicious intent intending to steal your passwords and personal credentials, leading users to phishing sites to steal bank account details, identity theft and so on.

Because of this, ISPs and email providers such as Gmail and Office 365 are taking anti-spam measures by enforcing stricter protocols in the type of emails that they receive, so it's good to implement those protocols and ensure that your email gets delivered and not delayed or worse, rejected by the mail servers.

So, what are those protocols?

SPF, DKIM and DMARC are the three main secure protocols used to authenticate your mail server and this will prove the ISPs and other mail service providers that the mail being sent is legitimate and authorized.

What is SPF?

It's an acronym for Sender Policy Framework. SPF is nothing but a DNS TXT record that specifies the server(s) and IP addresses that are allowed to send email from a specific domain.

Assuming you are the sender, just think of sending a postcard to your friend in which you add your address as well, so that your friend knows who the recipient is and he/she would most likely open it because they trust it.

But in technical terms, the actual recipient is not the user but rather the mail server that receives the mail.

Create an SPF record

An SPF record is a very simple string and it can be easily created. However, there are a few parts to it:

  1. Version of SPF used.
  2. IP addresses that are authorized to send emails.
  3. Third party domain(s) that are authorized to send emails.
  4. An ending tag named "all" which tells the receiving server on what policy to apply if the sending server is not a part of the SPF record.

So, let's what does it look like and what each part of it does:

v=spf1 ip4:111.111.111.111 include:example-domain.com -all

v=spf1 states the SPF version being implemented. Currently, there's no any other version at this point. So, it should always stay as this version until another version is released.

ip4:111.111.111.111 is the IP address of the mail server/domain that's authorized to send emails for that domain. You can use multiple IP addresses and can be listed individually like this ip4:111.111.111.111 ip4:222.222.222.222 or through a CIDR like ip4:111.111.111.0/20. If both IPv4 and IPv6 addresses are being used by mail server, make sure that both of those addresses are being listed.

include:example-domain.com is a secondary domain that's authorized to send emails on behalf of the primary mail domain(s) listed. Just like the previous rule above, you can add multiple secondary domains but bear in mind that only a maximum of 10 domains are allowed for any sending domain.

-all is a tag that instructs the receiving server on how to handle messages from a domain that isn't a part of the SPF record. There are some options and they are all dictated by a single character that precedes the all keyword. The options are:

  • -all means hard fail. The receiving server should reject the email if the sender domain is not authorized.
  • ~all means soft fail. The receiving server can flag it as a possible spam if the sender domain is not authorized.
  • +all means authorized. The receiving server allows the email even if the sender domain is not authorized. Now, this is not recommended and please do not use this option.

What is DKIM?

It's an acronym for DomainKeys Identified Mail a.k.a Email signing. Just like SPF, DKIM is also a simple DNS TXT record that tells the receiving server that the mail is certified which allows to build a trust between both the sending and receiving servers.

To handle this trust, DKIM makes uses of an RSA cryptographic algorithm to create a pair of public and private encryption keys. The private key will remain on the server (i.e. the mail server) whereas the public key is placed in your DNS records.

How to create a DKIM record?

Depending on your mail provider, it can be easily generated by tools provided by them and once generated, it can be copy-pasted to the DNS records.

This is how a typical DKIM record would look like:

v=DKIM1; k=rsa;
p=iHeFQ+7rCiSQs3DPjR2eUSZSv4i/Kp+sipRfVH7BGf+SxcwOkX7X8R1RVObMQsFcbIxnrq7Ba2QCf0YZlL9iqJf32V+baDI8IykuDztuoNUF2Kk0pawZkbSPNHYRtLxV2CTOtc+x4eIeSeYptaiu7g7GupekLZ2DE1ODHhuP4I=

Regardless of how it's being generated, this is what each part of the header means:

v=DKIM1 is the DKIM protocol version used.

p= is the Base64 encoded public key generated.

k= is the mechanism used to decode the DKIM signature. The encryption key is usually based on rsa-sha1 or rsa-sha256 signing algorithm.

You can use DMARC analyzer's DKIM checker to check if your DKIM record is valid.

What is DMARC?

It's an acronym for Domain-based Message Authentication, Reporting and Conformance. This protocol is built around SPF and DKIM and it ensures the following:

  1. Verifies that the sender's email is protected by SPF and DKIM protocols.
  2. Instructs the receiving mail server on what to do if the authentication fails.
  3. Provides a way for the receiving server to send a report to the sender about the DMARC evaluation i.e. whether it passed or failed.

If you have come this far in the article, you might have understood why both SPF and DKIM were explained and why they are quite necessary.

How to create a DMARC record?

Once you have both SPF and DKIM records in place, then it's easy to create your DMARC record. There are many sites and I would recommend you to try MXToolbox's DMARC Record Generator.

This is how a DMARC record would look like:

v=DMARC1; p=none; fo=1; rua=mailto:address@example.com;

And here's what each part of the header translates to:

v is the version tag, similar to the SPF record. It should always be DMARC1 in the record.

p is the policy tag. none means to not do anything to the email, quarantine means to flag it as spam and reject means to reject the email.

fo is the tag that lets the receiving servers know that failed messages must be returned to the sender or not. There are four values for this tag:

  • 0: Generate a report if both DKIM and SPF produce a "Pass" result.
  • 1: Generate a report if both DKIM and SPF produce a result other than "Pass".
  • d: Generate a report if the email had failed the DKIM evaluation.
  • s: Generate a report if the email had failed the SPF evaluation.

It's actually recommended to use fo=1 which can help you look for any email delivery issues.

rua tells the receiving server on where to send the aggregate reports. This could provide insights into the health of the email server and can help identify any malicious activities.

There are many optional tags that can be used but these are the tags are most commonly used by default.

Conclusion

With the increase in spam emails, it's good to take such preventive measures to ensure that you are following best practices and doing your part to prevent malicious emails and other security related issues.

If you want to read more about these protocols, you can read the following links recommended below:

Hope you liked reading this article! 😄

Make your own generative pixel art in less than 100 lines of code

By modifying my old random pixel generator, I was able to generate a Space Invaders-esque pixel art.

Try it out!

Refresh the page to generate unique Space Invader-esque patterns as the results are unpredictable!

Generative art is a topic that still fascinates me because of the fact that you can produce something unique by just writing few (sometimes, more) lines of code. Especially, if it's self-generating art that makes use of fixed rules and a slight dash of randomness to produce unique results.

When it comes to writing code, it's quite straightforward. You come up with the rules and constraints and voila, you have something that works. Having said that, setting up rules for generative art can get quite tricky.

You might have read about my previous post about The Game of Life, it contains only four rules and each of them took a part in evolving the system through each generation. With generative systems like that, you can never predict the results as complex patterns will emerge due to it's randomness.

In my view, a combination of predictability and randomness is needed in order to create a good looking generative art.

Why you should explore it?

There could be many reasons, maybe you're bored, curious or passionate to learn something new. Who knows? Open up your editor and try it for yourself.

Exploring the craft of making generative art has allowed me to:

  • Gain different experiences — It allows you to sharpen your algorithms & data structures and maybe even, learn a new technique.
  • Create something visually appealing — A picture is equal to a thousand words.
  • Instant results that makes you feel good — I mean, it's hard to explain but y'know what I mean, right?

Where to start?

Just like any other project, you just need:

  • An inspiration or an idea that you can work on.
  • The right kind of technology to use.

With today's article, I'll be showing you how I built a Space Invader-esque pixel art generator in JavaScript that makes use of the Canvas API.

Building a generator

I'd like to give a shout out to the person who built a twitterbot that generates random sprites using Python's Pillow image library.

I was inspired and I thought of writing it in JavaScript and as you can see above, it did turn out pretty well.

Here's the code for your reference and please read the comments to know how it functions:

// To store any used colors
var colorStack = [];

// Selected color palette
var colors = [
    '#30e3ca',
    '#ff5a5f',
    '#40514e',
    '#e4f9f5',
    '#40514e',
    '#e4f9f5',
    '#e4f9f5',
];

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');

var width = canvas.width;
var height = canvas.height;

function createSquare(squareDimensions, color, element, spriteDim) {
    const {squareX, squareY, squareWidth, squareHeight} = squareDimensions;

    // If it's a middle element, apply a color
    if (element == parseInt(spriteDim/2)) {
        ctx.fillStyle = color;
        ctx.fillRect(parseInt(squareX), parseInt(squareY), parseInt(squareWidth/squareX)+3, parseInt(squareHeight/squareY)+3);
    }
    // If it's the last element, then use the color that you saved previously
    else if (colorStack.length == element + 1) {
        ctx.fillStyle = colorStack.pop();
        ctx.fillRect(parseInt(squareX), parseInt(squareY), parseInt(squareWidth/squareX)+3, parseInt(squareHeight/squareY)+3);  
    }
    // Else, apply a color and save this for the last element.
    else {
        colorStack.push(color);
        ctx.fillStyle = color;
        ctx.fillRect(parseInt(squareX), parseInt(squareY), parseInt(squareWidth/squareX)+3, parseInt(squareHeight/squareY)+3);      
    }
}

function createInvader(invaderDimensions, spriteDim) { 

    var {posX, posY, invaderWidth, invaderHeight} = invaderDimensions;
    var squareSize = (invaderWidth - posX) / spriteDim;

    var cellPosition = 1;
    var element = 0;

    for(var y=0; y<spriteDim; y++){
        // Starts from the left side of the grid.
        // Think of it as something like this:
        // [-3,-2,-1,0,1,2,3]
        cellPosition *= -1;

        // First element
        element = 0;

        for(var x=0; x<spriteDim; x++) {
            squareX = x * squareSize + posX;
            squareY = y * squareSize + posY;
            squareWidth = squareX + squareSize;
            squareHeight = squareY + squareSize;

            // Pick a random color from the color palette
            var color = colors[Math.floor(Math.random() * colors.length)];

            var squareDimensions = {
                'squareX': squareX+2,
                'squareY':squareY+2,
                'squareWidth':squareWidth,
                'squareHeight':squareHeight,
            };

            // Create a square with a color and desired dimensions.
            createSquare(squareDimensions, color, element, spriteDim);

             // If it's the middle element or the starting element, 
             // then shift it's position to the leftmost.
            if(element == parseInt(spriteDim/2) || element == 0) {
                cellPosition *= -1;
            }

            element += cellPosition;
        }
    }
}

function main() {
    var spriteDim = 7;
    var numberOfInvaders = 15;
    var invadersSize = parseInt(width / numberOfInvaders);
    var padding = parseInt(invadersSize / spriteDim);

    for(var x=0; x<numberOfInvaders; x++) {
        for(var y=0; y<numberOfInvaders; y++) {
            var posX = (x * invadersSize) + padding + 2;
            var posY = (y * invadersSize) + padding + 2;
            var invaderWidth = posX + invadersSize - (padding * 3);
            var invaderHeight = posY + invadersSize - (padding * 3);

            var invaderDimensions = {
                'posX': posX,
                'posY': posY,
                'invaderWidth': invaderWidth,
                'invaderHeight': invaderHeight
            };

            createInvader(invaderDimensions, spriteDim);
        }
    }   
}

main();

Well, I won't say that is a perfect solution but hey, it works and yes, it doesn't take a lot of code to achieve something like this.

Explanation

I'll try my best to explain how this whole thing works.

First, you need to initialize a <canvas> DOM element of the desired width and height. Then in the main() function, you determine the size of each invader by specifying the number of invaders and dividing it with the width of the canvas. These values will then be used to determine the coordinates for each invader.

Second, the function createInvader() follows nearly the same process as the main function except that the coordinates for each pixel is determined by calculating the width of the invader and subtracting it's x position divided by the dimensions of each invader.

Third, as you can see in the function createSquare(), it contains 3 simple rules in which all of them draws a square with a color but with an emphasis that each invader has a symmetrical pattern.

The code looks deceptively simple but achieving this complexity did take a lot of trial and error and a little bit of simple calculus 😂.

Conclusion

Generative art might be something that you may not need to explore but it's quite fun and you may never know what you might be able to produce by combining both code and visuals.

Hope you liked reading this article.

Stay tuned for more! 🤘