Monday, 19 January 2026

Making the contact tracing effort mandatory, redux

Back in 2021, I wrote about the mobile app TraceTogether and its use during the COVID-19 pandemic. My rhetoric back then, in hindsight, was fiery; but I stand by what I said.

I had thought that this issue was behind us. However, last couple weeks, four entire years after I last mentioned it, the issue arose again in Parliament. A bill was passed - the Public Sector (Governance) Act (Amendment) Bill, which allows for sharing of citizens' data with bodies other than public agencies.

TraceTogether

During the debate, Opposition MP Kenneth Tiong brought up the fact that in 2021, Singaporeans discovered that data collected by TraceTogether could legally be shared with the police for the purposes of criminal investigation, and this had resulted in damage in terms of public trust. 

Minister of State for Digital Development and Information Jasmin Lau refuted this, and predictably, this led to a flurry of activity on my Facebook feed. Again, predictably, supports of the current ruling party, the PAP, stoutly re-iterated their trust in the Singapore Government, while supporters of the Opposition did the exact opposite.

Mostly incoherent sycophantic babbling on both sides, if I'm being honest. These days it feels like the most useless political opinions in the world can be found on Social Media. These political fanboys are nuts, and not even in a cool way.

My assertion back then

I was of the opinion that lives were at stake, and certain things such as privacy, had to take a back seat. I honestly do not consider there to be anything controversial about this statement, and would go so far to say that anyone who would knowingly endanger the lives of the public for the sake of their own privacy, is a selfish muppet.

Bring tracked.

Not that I suspect the Singapore Government to be the type to seize any opportunity to obsessively track its citizens like some kind of overbearing control freak Asian parent. However, the potential for abuse existed, no matter how small. The probability was not a non-zero value. But it's no exaggeration when I say, if the Singapore Government's efforts to control the spread of COVID-19 led to them being able to track my whereabouts for the rest of my life, it was a tradeoff I would have taken. Happily.

However...

Certain quarters started spouting some ignorant claptrap in support of the ruling party. They claimed that anyone who objected to having their data tracked back then, had something to hide. Anyone whose conscience was clear surely would not object to having their data tracked for the greater good.

Oh FFS, children.

It's a principle of privacy. Yes, I'm sure there were those who had plenty to hide, but generally, people were objecting out of principle and not because they were planning to commit acts of moral indecency. Anyone who can't see that, either isn't familiar with the concept of principles, or has none to begin with.

Form-filling.

I had to deal with this principle several years ago when I bought an insurance policy. An insurance agent came to my place and proceeded to have me fill out a form as a matter of routine. Among the questions in the form were gems such as "have you contracted HIV or any STDs in the past year?" and the agent actually asked me this question out loud. In front of my mother.

Wow. This bloody idiot, I swear to God.

The answer was "no", but even if the answer had been "yes", did she really think it was something I would have been comfortable sharing in front of my own goddamn mother? It's the principle of the thing. Whatever happened to professional discretion? Did she somehow think that no one should be allowed to keep personal secrets from their own mother?

So yes... personal privacy as a principle is very real.

Comparisons

It's no comparison, of course. While the principle is the same, the stakes are vastly different. In the case of COVID-19, it was a global pandemic where public health and safety were at stake. In the case of that shockingly indiscreet insurance agent, it was merely a case of bureaucracy. Yes, I'm aware that it could be a lot bigger than that, but it still isn't on the level of public health and safety.

While I value my personal privacy, I don't value it over the lives of others. I certainly do not value anyone's personal privacy over the lives of others, and am mystified at the possibility that anyone could feel otherwise.

It's 2026. This really should be the final time I speak on this.

Not-so-privately,
T___T

Thursday, 15 January 2026

More ways to declare colors in CSS!

Color codes used to be simple. If I ignored HSV and CMYK, RGB values were perfectly adequate. For more than a decade, I used rgb() and rgba() in CSS to get colors I wanted. This changed recently when I came across some new ways of using the rgb() function, quite by accident.

Fun with colors!

It began when I was doing this, using rgb() instead of rgba(). Obviously I wasn't using "HELLO WORLD" but this is the least complicated example I can think of.
<h1 style="color:rgb(200, 100, 50, 0.5)">HELLO WORLD</h1>


And it actually worked. That pale shade of orange!

HELLO WORLD



So I did a little bit of research into the rgb() function and it turned up a lot of stuff. Apparently this syntax is legacy, which means it's been around forever! 

But now that I was going down this rabbit hole, it appeared that there were other ways to use the rgb() function.

Without commas

rgb() can also be used without commas and with a forward slash if you want to specify opacity. Which sounds really counterintuitive, I know. Apparently some people find this easier. Go figure.
<h1 style="color:rgb(200 100 50 / 0.8)">HELLO WORLD</h1>


See? I upped the opacity.

HELLO WORLD



Using percentages

This is where it gets interesting. Instead of a value from 0 to 255, we can just quit the mental gymnastics and use percentage values. So if I do this...
<h1 style="color:rgb(80%, 50%, 0%, 90%)">HELLO WORLD</h1>


...or this...
<h1 style="color:rgb(80% 50% 0% / 90%)">HELLO WORLD</h1>


...I get this! Even more opacity!

HELLO WORLD



How is any of this useful?

It's really up to the individual web developer, isn't it? I personally prefer using commas, but being able to use percentage values is really cool.

The amazing thing is that this was all in place way before I discovered it, quite by accident. Truly, you learn something every day.

Well, color me flabbergasted!
T___T

Sunday, 11 January 2026

Web Tutorial: SVG Maker

One of the many annoying things about creating SVGs is the constant switching back-and-forth between code and on-screen rendering. One good way around this is multiple monitors, but that still requires moving the mouse cursor from one screen to the other. Why is it a big deal? Developers switch screens all the time when coding. That's true, but when you're dealing with SVGs, especially when writing paths, this gets repeated a lot and can be a huge pain in the ass.

The other way is to use a site like JsFiddle. It comes with several features which can be nice... but I'm a big fan of keeping things simple.

With that in mind, today we'll be using jQuery to create a quick-and-dirty application that I'm going to call the SVG Maker. The user will fill in things like width, height, and the inner XML, and the preview will show accordingly.

We begin with some standard HTML, and linkage to jQuery. In styling, we set the font.
<!DOCTYPE html>
<html>
  <head>
    <title>SVG Maker</title>

    <style>
      body
      {
        font-family: arial;
        font-size: 14px;
      }
    </style>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <script>

    </script>
  </head>

  <body>

  </body>
</html>


In the body, we add two divs, each styled using the CSS class panel, which we will create later.
<!DOCTYPE html>
<html>
  <head>
    <title>SVG Maker</title>

    <style>
      body
      {
        font-family: arial;
        font-size: 14px;
      }
    </style>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <script>

    </script>
  </head>

  <body>
    <div class="panel">
  
    </div>

    <div class="panel">

    </div>

  </body>
</html>


This will be followed by the addition of labels and inputs. We have txtWidth and txtHeight, which accept only numerical values above 10, with a default of 100. HTML5 is just wonderful, eh? We also have a textbox txtCode. Finally, we have a button, btnPreview.
<html>
  <head>
    <title>SVG Maker</title>

    <style>
      body
      {
         font-family: arial;
         font-size: 14px;
      }
    </style>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <script>

    </script>
  </head>

  <body>
    <div class="panel">
      <label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
      <br />
      <label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
      <br />
      <label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
      <br />
      <button id="btnPreview">Preview</button>
      <br />

    </div>

    <div class="panel">

    </div>
  </body>
</html>


Perhaps most importantly, we have an SVG, svgResult. It has a black outline for visibility.
<html>
  <head>
    <title>SVG Maker</title>

    <style>
      body
      {
         font-family: arial;
         font-size: 14px;
      }
    </style>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

    <script>

    </script>
  </head>

  <body>
    <div class="panel">
      <label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
      <br />
      <label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
      <br />
      <label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
      <br />
      <button id="btnPreview">Preview</button>
      <br />
    </div>

    <div class="panel">
      <svg id="svgResult" style="outline: 1px solid rgb(0, 0, 0)" xmlns="http://www.w3.org/2000/svg" />
    </div>
  </body>
</html>


Here it is. Nothing to shout about, eh? That will be improved on, soon.


Here's some CSS. panel gives the dashboard and preview area some definition with a nice orange outline. Bear in mind that this isn't meant to look pretty. It's meant to be useful visually, which isn't necessarily (and quite often isn't) the same thing.
<style>
  body
  {
     font-family: arial;
     font-size: 14px;
  }

  .panel
  {
     outline: 2px solid rgb(200, 100, 0);
     width: 90%;
     padding: 1em;
     margin: 1em auto 0 auto;
  }
</style>


We'll then style labels, inputs and buttons. Here, I set the display properties to inline-block and set width and float properties, so as to align them properly. It's not that important from an aesthetic point of view because it still doesn't look pretty, but at least it doesn't look like a mess, which is important from a usability point of view.
<style>
  body
  {
      font-family: arial;
      font-size: 14px;
  }

  .panel
  {
      outline: 2px solid rgb(200, 100, 0);
      width: 90%;
      padding: 1em;
      margin: 1em auto 0 auto;
  }

  label
  {
      display: inline-block;
      width: 10em;
      float: left;
  }

  input[type="number"]
  {
      width: 3em;
  }

  button
  {
      display: inline-block;
      width: 10em;
      float: right;
  }

</style>


And that's how it all looks like now.


We'll want some JavaScript here. Specifically, create the renderSVG() function.
<script>
  function renderSVG()
  {

  }

</script>


We want this function to run when the page loads.
<script>
  function renderSVG()
  {

  }

  $(document).ready(function() {
    renderSVG();
  });

</script>


And we also want to bind the execution to the click event of btnPreview, when the page loads.
<script>
  function renderSVG()
  {

  }

  $(document).ready(function() {
    renderSVG();

    $("#btnPreview").click(function() {
      renderSVG();
    });

  });
</script>


Now it's time to work on renderSVG(). Declare width, height and content. Set them to the values of these HTML elements. In the case of width and height, we want to coerce these values into numerical ones, using the parseInt() function.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var content = $("#txtCode").val();

}


Here, we ensure that svgResult's width and height attributes match the values of width and height.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var content = $("#txtCode").val();

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);

}


Then we make sure that svgResult's HTML content is content. But we can't do it directly because it's an SVG. We have to first replace its HTML content with its HTML content, if that makes sense, then set it using content.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var content = $("#txtCode").val();

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(content);
}


Take a gander. When you reload the page now, the SVG is 100 by 100 pixels because those are the default values.


Change the number, click the Preview button, and watch the SVG change in size.


Add in some XML content, maybe a nice orange circle. Click Preview, and presto!


Adding a grid

It would be helpful to have some visual aids while creating the SVG. To that end, we'll add a customizable grid.

In the UI, we have txtGrid, a numerical input with a minimum value of 10. This means that the smallest grid we can have here is 10 by 10 pixels.
<label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
<br />
<label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
<br />
<label for="txtGrid">Grid:</label><input type="number" id="txtGrid" min="0" value="10" />
<br />

<label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
<br />


And here's the new input.


In the JavaScript function renderSVG(), we handle this inclusion by declaring grid, and setting it to the value of the HTML element txtGrid. We also declare gridLines as an empty string. This will eventually be XML content. To that end, we will want to prepend gridlines to content when rendering the SVG.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}


We only want to create the grid if gridLines is greater than 0.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  if (grid > 0)
  {

  }


  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}

We'll then use a For loop to iterate from grid to width, using the value of grid as an incrementor...
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  if (grid > 0)
  {
    for (var i = grid; i < width; i += grid)
    {

    }

  }

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}

...and with each iteration, use i to add the XML for vertical line tags to gridLines.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  if (grid > 0)
  {
    for (var i = grid; i < width; i += grid)
    {
      gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
    }
  }

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}


Look at this! Grey vertical lines.

Then we repeat this, using height this time, for horizontal line tags.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  if (grid > 0)
  {
    for (var i = grid; i < width; i += grid)
    {
      gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
    }

    for (var i = grid; i <= height; i += grid)
    {
      gridLines += '<line x1="0" y1="' + i + '" x2="' + width + '" y2="' + i + '" stroke="rgb(200,200,200)" stroke-width="1" />';
    }

  }

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}


Now we have a grid!


Adding objects

Time for a little value-add! We have the bare minimum - an interface to change the size and content of the SVG, and a grid to help draw. But what would add even more value?

Well, the ability to add pre-determined objects such as circles and lines to the SVG.

For that, let's add a series of buttons to the interface. Also add a checkbox with a label, cbAppend.
<div class="panel">
  <label for="txtWidth">Width:</label><input type="number" id="txtWidth" min="10" value="100" />
  <br />
  <label for="txtHeight">Height:</label><input type="number" id="txtHeight" min="10" value="100" />
  <br />
  <label for="txtGrid">Grid:</label><input type="number" id="txtGrid" min="0" value="10" />
  <br />
  <label for="txtCode">Code:</label><textarea cols="100" rows="10" id="txtCode"></textarea>
  <br />
  <label for="cbAppend">Append?</label><input type="checkbox" id="cbAppend" />
  <br />
  <button>Add Line</button>
  <button>Add Rect</button>
  <button>Add Ellipse</button>
  <button>Add Circle</button>
  <button>Add Path</button>
  <button>Add Text</button>
  <br />
  <hr />

  <button id="btnPreview">Preview</button>
  <br />
</div>


See the buttons and checkbox? Good.


Add an event to the first button. It will be a function call to addNode(), with the argument "line".
<button onClick="addNode('line')">Add Line</button>
<button>Add Rect</button>
<button>Add Ellipse</button>
<button>Add Circle</button>
<button>Add Path</button>
<button>Add Text</button>


In the JavaScript, create two functions, addNode() and getDefaultNode(). Both of them have the parameter nodeType.
function renderSVG()
{
  var width = parseInt($("#txtWidth").val());
  var height = parseInt($("#txtHeight").val());
  var grid = parseInt($("#txtGrid").val());
  var content = $("#txtCode").val();
  var gridLines = "";

  if (grid > 0)
  {
    for (var i = grid; i < width; i += grid)
    {
      gridLines += '<line x1="' + i + '" y1="0" x2="' + i + '" y2="' + height + '" stroke="rgb(200,200,200)" stroke-width="1" />';
    }

    for (var i = grid; i <= height; i += grid)
    {
      gridLines += '<line x1="0" y1="' + i + '" x2="' + width + '" y2="' + i + '" stroke="rgb(200,200,200)" stroke-width="1" />';
    }
  }

  $("#svgResult").attr("width", width);
  $("#svgResult").attr("height", height);
  $("#svgResult").html($("#svgResult").html()).html(gridLines + content);
}

function getDefaultNode(nodeType)
{

}

function addNode(nodeType)
{

}


$(document).ready(function() {
  renderSVG();

  $("#btnPreview").click(function() {
    renderSVG();
  });
});


Just for clarity, let's start on getDefaultNode(). We use a Switch statement on nodeType, because there are many possible values. For now, there is only "line". Notice we don't use a break statement here because all these cases return a value.
function getDefaultNode(nodeType)
{
  switch (nodeType)
  {
    case "line":
    default: return "";
  }

}


In the case of "line", an orange line tag is returned.
function getDefaultNode(nodeType)
{
  switch (nodeType)
  {
    case "line": return '<line x1="0" y1="0" x2="300" y2="200" stroke="rgb(250,100,0)" stroke-width="2" />';
    default: return "";
  }
}


Now for addNode(). We declare node, and use getDefaultNode(), passing in nodeType as the argument, to get the XML value which we then assign to this variable. Then we declare content, which is the text content of txtCode, and append, which is a Boolean depending on whether cbAppend is checked.
function addNode(nodeType)
{
  var node = getDefaultNode(nodeType);
  var content = $("#txtCode").val();
  var append = $("#cbAppend").prop("checked");

}


If append is true, that means we want node to be appended to content. We'll set txtCode's text content as that. If not, then we prepend node to content.
function addNode(nodeType)
{
  var node = getDefaultNode(nodeType);
  var content = $("#txtCode").val();
  var append = $("#cbAppend").prop("checked");

  if (append)
  {
    $("#txtCode").val(content + node);
  }
  else
  {
    $("#txtCode").val(node + content);
  }

}


After that, the updated code should be in txtCode. We'll run renderSVG().
function addNode(nodeType)
{
  var node = getDefaultNode(nodeType);
  var content = $("#txtCode").val();
  var append = $("#cbAppend").prop("checked");

  if (append)
  {
    $("#txtCode").val(content + node);
  }
  else
  {
    $("#txtCode").val(node + content);
  }

  renderSVG();
}


See what happens when I click the Add Line button? The code appears in the text box and an orange line appears in the SVG!


You'll notice that right now, it's not immediately apparent whether the line tag is appended or prepended. Let's just add the rest of the code. These are all the other tags that can be added. You may want to add your own.
function getDefaultNode(nodeType)
{
  switch (nodeType)
  {
    case "line": return '<line x1="0" y1="0" x2="300" y2="200" stroke="rgb(250,100,0)" stroke-width="2" />';
    case "rect": return '<rect width="200" height="100" x="10" y="10" rx="20" ry="20" fill="rgb(250,100,0)" />';
    case "ellipse": return '<ellipse rx="100" ry="50" cx="120" cy="80" fill="rgb(250,100,0)" stroke="rgb(250,100,0)" stroke-width="3" />';          
    case "circle": return '<circle r="45" cx="50" cy="50" fill="rgb(250,100,0)" />';
    case "path": return '<path d="M150 5 L75 200 L225 200 Z" fill="none" stroke="rgb(250,100,0)" stroke-width="3" />';
    case "text": return '<text x="5" y="15" fill="rgb(250,100,0)">abc 123</text>';

    default: return "";
  }
}


In the HTML, add the function calls.
<button onClick="addNode('line')">Add Line</button>
<button onClick="addNode('rect')">Add Rect</button>
<button onClick="addNode('ellipse')">Add Ellipse</button>
<button onClick="addNode('circle')">Add Circle</button>
<button onClick="addNode('path')">Add Path</button>
<button onClick="addNode('text')">Add Text</button>


Now add a rect tag and then a line, by clicking the buttons. Note that the line is prepended to the rect because the checkbox is not checked.


Check the checkbox and add a path tag. There, now you see that the path is appended.


How useful is it?

Well. I think it's useful. And of course I would, I made it! I have consciously avoided adding too many features to it precisely because the objective was to keep the interface as simple as possible while still incorporating features that are frequently used. Obviously, this varies from individual to individual.

<text>Till next time!</text>
T___T

Monday, 5 January 2026

Five Image File Formats For The Web

There are a variety of image file formats used on the internet. Some of them have been around for a very long time; others not so much. They come in various levels of utility and relevance to the web landscape. Understanding image file formats is arguably more of a design discipline than it is a tech discipline; nevertheless, it's web development know-how and useful for web developers worth their salt, to at least understand the basics.

Since we're only talking about images used for the web, we won't be discussing TIFFs (huge files used by photographers) or Bitmaps.

1. JPEG (.jpg or .jpeg)


The acronym stands for Joint Photographic Experts Group. This is a file format that has been around for almost as long as the internet has been alive. It is still extremely relevant today, though its place is very much under threat from WebP.

PNG (left), JPEG (right); for
the JPEG, logo is blurred
around the edges.

JPEGs are useful because they use a lossy compression format. This means that part of the color information will be lost when compressed, but that's OK because the parts that are lost are generally not the parts perceptible by human eyes. Thus the compression would have to be significantly lower than the original before one can perceive any degradation in quality. And that, in turn, means that file sizes can get significantly smaller.

Thus, especially for small to medium photographic images, JPEGs were (and in many ways, still are) the numero uno choice in the pre-broadband era of internet. Ever tried to access an online photo gallery on a 54kbps modem? It would be an absolute nightmare if low quality JPEG thumbnails weren't used. The economical file sizes were economical for those times, though this may be less of an issue today, now that internet speeds are streaming quality.

Its one weakness is noticeable image degradation in images with flat colors and sharp distinct edges.

Use it for: Photographic images where quality is negotiable.
Don't use it for: Images with flat colors such as simple illustrations and logos.

2. GIF (.gif)

This can be pronounced with a hard or soft "g". I personally think it should be pronounced with a hard sound because the "g" stands for "Graphic", as in Graphical Interchange Format.

This is a lossless compression format, though you would be sadly mistaken if you thought this meant there would be little to no image degradation. The image degradation occurs due to the conversion from a full color palatte of a photographic image to the 256 colors in a GIF palette, and not due to the compression... though the distinction is probably lost on the average layperson. Simply put, there are a limited number of colors in a GIF palatte, and if one was to convert a photographic image to GIF, unpleasant visual effects such as "banding" or "dithering" would appear.

JPEG (left), GIF (right); for
the GIF, image is dithered.

However, for flat colors and crisp edges such as logos or illustrations, GIFs are perfectly adequate.

GIFs were insanely popular back in the day where quality didn't matter so much if you had animation and transparency. And GIF supports both.

Use it for: Low-quality logos or illustrations, especially if transparency and animation are required.
Don't use it for: Photographic images, ever.

3. PNG (.png)


Portable Network Graphics files handle crisp edges and flat colors just as well as GIFs, with the added bonus of transparency and animation (though this requires the APNG extension) just like GIFs. Visually, they're an upgrade over GIFs since they can also handle photographic images withput the dithering or banding that often occurs with GIFs.

Where transparency is concerned, it's worth noting that GIFs offer full or no transparency, while PNGs offer full to no transparency - another major upgrade.

Partial transparency is
possible with PNGs.

Compared to JPEGs, they suffer significantly when comparing compression file sizes, since, like GIFs, they use a lossless compression method.

Still, with transparency (partial or full) and animation options, and the ability to handle both flat or photographic images, PNGs offers increased flexibility.

Use it for: Logos or illustrations, especially if transparency and animation are required.
Don't use it for: Unless transparency and animation are required, JPEGs should be preferred for photographic images... though in that case WebPs are still the superior choice.

4. SVG (.svg)

Take note of the name. Scalable Vector Graphics are just that - scalable. This means that unlike all the other image file formats, they can be scaled up or down with no loss in quality. This is because they are basically XML files, with instructions on how to render them, encoded as text. The browser then renders the images based on these instructions. Since they handle gradients as well, and aren't limited to 256 colors, these graphics aren't limited to flat colors like GIFs.

With SVGs, images can be
scaled up or down with no
loss in quality.

SVGs can suffer in comparison to PNGs at higher levels of complexity, since, as mentioned, they are basically XML files. Thus, a very complex SVG might be a huge file, while the equivalent PNG would still be a flat file and relatively small.

Use it for: Simple logos or illustrations, especially if transparency and animation are required.
Don't use it for: Anything else. Photographic images absolutely can be imported into SVGs, but the results are sub-optimal at best.

5. WebP (.webp)

WebP is Google's offering, and it is the currently apex predator of the image file format jungle. Not only does it handle photographic images at comparable compression sizes like a JPEG, it also handles transparency and animation like a PNG.

The WebP logo.

At just over a decade and change since its inception, It's still the new kid on the block, but already, it's settled in well. It doesn't hurt, of course, that Google the web browser generally uses it to generate image thumbnails in the search engine.

WebP hasn't yet become as ubiquitous and widely-supported as the other file formats, but it's probably a matter of time.

Use it for: For most things image-related, webP handles it all like a champ.
Don't use it for: For scalable logos and illustrations, SVG's still the superior choice.

Conclusion

Things have changed since the beginning of the internet and the online space is a whole new world now. Even though slow internet speeds and severely limited bandwidths are largely a thing of the past, it's still good practice to use the correct image file format for the correct occasion.

Get the picture yet?
T___T

Thursday, 1 January 2026

Finding Your Purpose Professionally

For years now, I've been reading that young people (which, to me, means Gen Z) are unemployable because they want to find purpose in work. By that, they mean that the company they eventually join, should have values that align with their own. They want to impact the world in meaningful ways.

This, of course, may not be compatible with the values that employers traditionally value - hard work and discipline.

Now, I've often stated my opinion that the value of hard work is often overrated. And people who fixate on that at the exclusion of all else, are doomed to fail. That doesn't mean I think that fixating on purpose in work is necessarily an improvement.

Heal the world; make it a
better place...

On the face of it, these are noble goals. Who doesn't want to make the world a better place?

On the other hand, what's "better"? There are some who would say the world would be better if everyone was unable to say hurtful things, and some might say the world would be better if certain kinds of people simply did not exist. Who gets to decide? I think most of us would have enough of a sense of self-preservation not to trust some wet-behind-the-years kid straight out from University, high on purist ideals, to make that decision. Not that I would necessarily depend on some Bible-thumper Churchgoer for those decisions either, if we're being honest.

In all fairness, however, career choices are highly personal. Still, let's examine the issue of wanting to find purpose in work. Some like to claim that it's all hubris and entitlement.

Is it really entitlement, though?

One might say this comes across as a little selfish. After all, every day one remains without a job is another day that financial slack is taken up by someone else. So holding out for a job with purpose does suggest a disproportionate amount of privilege.

But entitlement?

To be honest, this does come across as awfully entitled if they want the success from the start, without paying their dues or starting small like the rest of us. The people who have changed the world as we know it, were extraordinary people with extraordinary talent, some of whom endured extraordinary hardship. I'm by no means saying that people should not aim to be extraordinary - ambition is a grand thing to have, after all - but the fact is, not everyone can be extraordinary. That's literally what the word means. If everyone can be special, then no one is special.

Everyone should have the right to aim high. Not everyone, however, should expect to be given an easy ride. Chances are, the first several companies won't be a great fit in terms of purpose and values. The pay and hours may (probably will) suck. And expecting not to have to deal with any of that shit because you're special or something, does smack of entitlement. I mean, you better be pretty damn special, sweetheart.

Be willing to start small.

All I'm saying is, as long as one's willing to start small and compromise without expecting instant success, it's not entitlement. It's just a goal like any other.

Now, I could spend this entire blogpost ranting endlessly about how kids these days don't understand the value of hard work and paying your dues, loyalty and all other Boomer values that employers tend to like perpetuating (wink wink, nudge nudge). Or I could unpack my own experiences when I had what the older generation called "unrealistic expectations".

And trust me, there's plenty to unpack.

Back in 2010...

I was in between jobs. Things were looking bleak. I was patiently holding out for a proper software dev job. My dad recommended me a tech support job at some medical company. I declined repeatedly. If anyone's at all familiar with my backstory, they would know how much I absolutely despise the role that I spent the first six years of my career in, and how hard I worked to never have to do another day of tech support in my life, ever again.

Dad didn't agree. To him, a job was just a job. To him, I was this spoilt brat who wasn't even giving it a fair chance.

I didn't understand why my dad, who sometimes couldn't tell a laptop from a lap dance, was presuming to give me any kind of career advice, let alone tech career advice. The truth was that he wasn't. He was just trying to get me to see reason; that any job was better than no job at all.

From a survival standpoint, he was absolutely right. However, from the standpoint of a career in software development, it would have been one hell of a stupid choice.

I didn't want just any job. I wanted a career. And the job I chose needed to at least be a positive step towards that goal, no matter how small. Dad didn't understand the concept of a career. He didn't have much of an education. His work experience consisted of selling used cars, selling insurance, basically selling. He might even have tried to run a garment factory once. As far as he was concerned, he was lucky to have any kind of job, let alone one that just required him to drive around and talk to people. To this day, I simultaneously resent and admire him for his ability to just suck it up.

I have an
education, dammit!

Because I was different. I had an education, and with it, the ability to be choosy. Honestly, if I just wanted to do any job, why waste all those years on tech qualifications?

Thus, I like to think that I had a practical reason to be choosy, as opposed to doing it for "purpose". (Like these young punks. Heh heh.)

It's now 2026!

I won't pretend I know how kids these days think. I know what they think, or at least, what the news reports say they think. But I don't know how they arrive at those conclusions, or how it even makes sense to them.

Purpose? Every job has a purpose. Perhaps not the high-minded noble purpose these young ones pontificate about constantly, but purpose nonetheless.

Hey, some people find purpose in not starving. And if you think that's not a purpose, shut your privileged piehole.

Some people find purpose in a job that enables them to take care of the people who matter. What the job actually entails isn't the issue. What effect it has on the world isn't the issue. The issue is that they have mouths to feed, and by God they are going to feed those mouths.

Still think all that isn't purpose? It might be more actual purpose than these youngsters have ever dealt with. Remember, before you try to save the world, take some time to help Mommy with the dishes.

Too good to do the dishes?

I don't mean to sound condescending (even though I probably do). After all, I've struggled with career choices myself. And the conclusions I came to as a result, some might uncharitably say was due to me not being successful in changing the world.

Newsflash: don't worry too much about changing the world. Leave it alone and it'll sort itself out most of the time, with or without you. Thinking that your input is required, is nothing short of vanity... though it can be argued that said hubris is exactly one of the ingredients required to change the world.

But I can say all that. I have the benefit of hindsight, after all. And perhaps young people need that exact same benefit.

Or it could be that they're right, and my ways are outdated, the same way my father's ways were outdated back in 2010.

My advice (not that anyone asked for it)

There's nothing wrong at all with starting small, or imperfect beginnings. So what if the company you land in doesn't align with your life goals, or at all? You're fresh out of University; you have zero leverage with which to change the world. And expecting to be able to do it right away is mere fantasy.

Clock your experience points. Pay your dues. Do all that, and you'll have a leg to stand on when you eventually pursue those lofty noble goals. Without practical experience, all you have are good intentions. And we all know what the road to hell is paved by.

This blogpost was written on purpose,
T___T