Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Thursday, 19 February 2026

Web Tutorial: Year of the Horse SVG Animation (Part 3/3)

In this final part, we will use what we did for the grassy ground, to make more background. This one will be hills.

For that, we create a g tag.
</circle>

<g>  
</g>  


<g>    
  <path d="M0 350
    Q100 340 200 350
    Q300 345 350 350
    Q500 340 600 350
    Q700 340 800 350
    Q900 345 950 350
    Q1100 340 1200 350
    L1200 450
    L0 450
    Z"

    stroke-dasharray="2,3"
    stroke="url(#grassGradient)"
    stroke-width="5"
    fill="url(#grassGradient)"
  />

  <animateTransform
   attributeName="transform"
   type="translate"
   from="0,0"
   to="-600,0"
   dur="2.5s"
   repeatCount="indefinite"
  />
</g>


In there, we have several path tags. Each of these will be filled using hillGradient.
<g>  
  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />

</g>


In the defs tag, we add a new linearGradient tag, hillGradient.
<linearGradient id="skyGradient" x1="0%" y1="0%" x2="0%" y2="50%">
  <stop offset="0%" stop-color="rgb(0, 0, 50)">
    <animate
     attributeName="stop-color"
     values="rgb(0, 0, 50);rgb(50, 150, 255);rgb(150, 20, 0);rgb(0, 0, 50)"
     dur="10s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgb(50, 50, 250)">
    <animate
     attributeName="stop-color"
     values="rgb(50, 50, 250);rgb(250, 250, 255);rgb(250, 150, 0);rgb(50, 50, 250)"
     dur="10s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>
</linearGradient>

<linearGradient id="hillGradient" x1="0%" y1="0%" x2="0%" y2="80%">

</linearGradient>


<linearGradient id="grassGradient" x1="0%" y1="0%" x2="0%" y2="50%">
  <stop offset="0%" stop-color="rgb(80, 100, 80)">
    <animate
     attributeName="stop-color"
     values="rgb(80, 100, 80);rgb(100, 150, 100);rgb(120, 200, 120);rgb(100, 150, 100);rgb(80, 100, 80)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgb(0, 20, 0)">
    <animate
     attributeName="stop-color"
     values="rgb(0, 20, 0);rgb(20, 50, 20);rgb(50, 100, 50);rgb(20, 50, 20);rgb(0, 20, 0)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>
</linearGradient>


It's mostly shades of brown. I could go into it, but honestly I'd just be repeating myself from the previous part of this tutorial, so I won't.
<linearGradient id="hillGradient" x1="0%" y1="0%" x2="0%" y2="80%">
  <stop offset="0%" stop-color="rgb(100, 50, 50)">
    <animate
     attributeName="stop-color"
     values="rgb(100, 50, 50);rgb(200, 100, 100);rgb(250, 200, 200);rgb(200, 100, 100);rgb(100, 50, 50)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgb(50, 0, 0)">
    <animate
     attributeName="stop-color"
     values="rgb(50, 0, 0);rgb(150, 100, 100);rgb(200, 100, 100);rgb(150, 100, 100);rgb(50, 0, 0)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

</linearGradient>


Behold! But the real magic isn't here yet.


Before that, let's add another layer to the background - a second series of path tags, these filled using hill2Gradient. For this, make a copy of hillGradient and then modify it to a significantly darker series of browns.
<linearGradient id="hillGradient" x1="0%" y1="0%" x2="0%" y2="80%">
  <stop offset="0%" stop-color="rgb(100, 50, 50)">
    <animate
     attributeName="stop-color"
     values="rgb(100, 50, 50);rgb(200, 100, 100);rgb(250, 200, 200);rgb(200, 100, 100);rgb(100, 50, 50)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgb(50, 0, 0)">
    <animate
     attributeName="stop-color"
     values="rgb(50, 0, 0);rgb(150, 100, 100);rgb(200, 100, 100);rgb(150, 100, 100);rgb(50, 0, 0)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>
</linearGradient>

<linearGradient id="hill2Gradient" x1="0%" y1="0%" x2="0%" y2="80%">
  <stop offset="0%" stop-color="rgba(150, 150, 150, 0.5)">
    <animate
     attributeName="stop-color"
     values="rgba(150, 150, 150, 0.5);rgba(180, 180, 180, 0.5);rgba(200, 200, 200, 0.5);rgba(180, 180, 180, 0.5);rgba(150, 150, 150, 0.5)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgba(50, 50, 50, 0.5)">
    <animate
     attributeName="stop-color"
     values="rgba(50, 50, 50, 0.5);rgba(80, 80, 80, 0.5);rgba(100, 100, 100, 0.5);rgba(80, 80, 80, 0.5);rgba(50, 50, 50, 0.5)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>
</linearGradient>


<linearGradient id="grassGradient" x1="0%" y1="0%" x2="0%" y2="50%">
  <stop offset="0%" stop-color="rgb(80, 100, 80)">
    <animate
     attributeName="stop-color"
     values="rgb(80, 100, 80);rgb(100, 150, 100);rgb(120, 200, 120);rgb(100, 150, 100);rgb(80, 100, 80)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>

  <stop offset="100%" stop-color="rgb(0, 20, 0)">
    <animate
     attributeName="stop-color"
     values="rgb(0, 20, 0);rgb(20, 50, 20);rgb(50, 100, 50);rgb(20, 50, 20);rgb(0, 20, 0)"
     dur="100s"
     begin="0s"
     repeatCount="indefinite"
    />
  </stop>
</linearGradient>


Here's the second series of paths. Make sure it comes before the first series of paths, in the code.
</circle>

<g>    
  <path d="M100 350
    L140 200
    L180 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M200 350
    L240 300
    L260 330
    L290 220
    L330 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M350 350
    L380 260
    L410 290
    L440 260
    L500 300
    L550 200
    L600 350
    Z"

    fill="url(#hill2Gradient)"
  />
</g>


<g>    
  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />
</g>


So you see a darker series of hills behind that first series! It doesn't look that impressive. Not yet, at least.


Now, we want to extend both series of hills, for reasons that will be clear later. We want to copy the exact same layout, but to the right of the existing layout. So what we do first, is make a copy in their respective g tags.
<g>    
  <path d="M100 350
    L140 200
    L180 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M200 350
    L240 300
    L260 330
    L290 220
    L330 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M350 350
    L380 260
    L410 290
    L440 260
    L500 300
    L550 200
    L600 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M100 350
    L140 200
    L180 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M200 350
    L240 300
    L260 330
    L290 220
    L330 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M350 350
    L380 260
    L410 290
    L440 260
    L500 300
    L550 200
    L600 350
    Z"

    fill="url(#hill2Gradient)"
  />

</g>

<g>    
  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />

</g>


Then modify the copies so that the horizontal specs each have 600 added to them, because 600 is the width of the SVG, remember? What this means is that now there are more hills, identical to what's visible, but outside of the SVG.
<g>    
  <path d="M100 350
    L140 200
    L180 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M200 350
    L240 300
    L260 330
    L290 220
    L330 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M350 350
    L380 260
    L410 290
    L440 260
    L500 300
    L550 200
    L600 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M700 350
    L740 200
    L780 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M800 350
    L840 300
    L860 330
    L890 220
    L930 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M950 350
    L980 260
    L1010 290
    L1040 260
    L1100 300
    L1150 200
    L1200 350
    Z"

    fill="url(#hill2Gradient)"
  />
</g>

<g>    
  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M600 350
    L640 300
    L660 330
    L690 250
    L720 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M800 350
    L840 280
    L880 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M880 350
    L900 300
    L920 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M950 350
    L1030 310
    L1060 330
    L1090 300
    L1120 330
    L1150 310
    L1200 350
    Z"

    fill="url(#hillGradient)"
  />
</g>


What we want to do now, is add animateTransform tags as we did for the grassy background. We do it for both g tags containing the "hills". The animations will move the "hills" a full 600 pixels left, and repeat forever. The difference is that the light brown hills will animate faster at a duration 3 seconds, while the hills further in the background at a dark brown, will animate slower at a duration of 5 seconds! The contrast is going to be awesome!
<g>    
  <path d="M100 350
    L140 200
    L180 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M200 350
    L240 300
    L260 330
    L290 220
    L330 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M350 350
    L380 260
    L410 290
    L440 260
    L500 300
    L550 200
    L600 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M700 350
    L740 200
    L780 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M800 350
    L840 300
    L860 330
    L890 220
    L930 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <path d="M950 350
    L980 260
    L1010 290
    L1040 260
    L1100 300
    L1150 200
    L1200 350
    Z"

    fill="url(#hill2Gradient)"
  />

  <animateTransform
   attributeName="transform"
   type="translate"
   from="0,0"
   to="-600,0"
   dur="5s"
   repeatCount="indefinite"
  />

</g>

<g>    
  <path d="M0 350
    L40 300
    L60 330
    L90 250
    L120 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M200 350
    L240 280
    L280 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M280 350
    L300 300
    L320 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M350 350
    L430 310
    L460 330
    L490 300
    L520 330
    L550 310
    L600 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M600 350
    L640 300
    L660 330
    L690 250
    L720 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M800 350
    L840 280
    L880 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M880 350
    L900 300
    L920 350
    Z"

    fill="url(#hillGradient)"
  />

  <path d="M950 350
    L1030 310
    L1060 330
    L1090 300
    L1120 330
    L1150 310
    L1200 350
    Z"

    fill="url(#hillGradient)"
  />

  <animateTransform
   attributeName="transform"
   type="translate"
   from="0,0"
   to="-600,0"
   dur="3s"
   repeatCount="indefinite"
  />

</g>


At this point, you might want to slow down all the animations in the linear gradients, from 10 seconds to 100.
<defs>
  <linearGradient id="skyGradient" x1="0%" y1="0%" x2="0%" y2="50%">
    <stop offset="0%" stop-color="rgb(0, 0, 50)">
      <animate
       attributeName="stop-color"
       values="rgb(0, 0, 50);rgb(50, 150, 255);rgb(150, 20, 0);rgb(0, 0, 50)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>

    <stop offset="100%" stop-color="rgb(50, 50, 250)">
      <animate
       attributeName="stop-color"
       values="rgb(50, 50, 250);rgb(250, 250, 255);rgb(250, 150, 0);rgb(50, 50, 250)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>
  </linearGradient>

  <linearGradient id="hillGradient" x1="0%" y1="0%" x2="0%" y2="80%">
    <stop offset="0%" stop-color="rgb(100, 50, 50)">
      <animate
       attributeName="stop-color"
       values="rgb(100, 50, 50);rgb(200, 100, 100);rgb(250, 200, 200);rgb(200, 100, 100);rgb(100, 50, 50)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>

    <stop offset="100%" stop-color="rgb(50, 0, 0)">
      <animate
       attributeName="stop-color"
       values="rgb(50, 0, 0);rgb(150, 100, 100);rgb(200, 100, 100);rgb(150, 100, 100);rgb(50, 0, 0)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>
  </linearGradient>

  <linearGradient id="hill2Gradient" x1="0%" y1="0%" x2="0%" y2="80%">
    <stop offset="0%" stop-color="rgba(150, 150, 150, 0.5)">
      <animate
       attributeName="stop-color"
       values="rgba(150, 150, 150, 0.5);rgba(180, 180, 180, 0.5);rgba(200, 200, 200, 0.5);rgba(180, 180, 180, 0.5);rgba(150, 150, 150, 0.5)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>

    <stop offset="100%" stop-color="rgba(50, 50, 50, 0.5)">
      <animate
       attributeName="stop-color"
       values="rgba(50, 50, 50, 0.5);rgba(80, 80, 80, 0.5);rgba(100, 100, 100, 0.5);rgba(80, 80, 80, 0.5);rgba(50, 50, 50, 0.5)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>
  </linearGradient>

  <linearGradient id="grassGradient" x1="0%" y1="0%" x2="0%" y2="50%">
    <stop offset="0%" stop-color="rgb(80, 100, 80)">
      <animate
       attributeName="stop-color"
       values="rgb(80, 100, 80);rgb(100, 150, 100);rgb(120, 200, 120);rgb(100, 150, 100);rgb(80, 100, 80)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>

    <stop offset="100%" stop-color="rgb(0, 20, 0)">
      <animate
       attributeName="stop-color"
       values="rgb(0, 20, 0);rgb(20, 50, 20);rgb(50, 100, 50);rgb(20, 50, 20);rgb(0, 20, 0)"
       dur="100s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>
  </linearGradient>
</defs>


And make the circle animations also ten times slower. We made them fast while creating the SVG so we didn't have to wait around forever for stuff to happen, but now that we're about to finish, a slow-moving sky background makes more sense.
<circle r="10" cx="650" cy="100" fill="rgb(250,250,200)" stroke="rgba(250,250,200,0.8)" stroke-width="5">
  <animate
   id="moon0"
   attributeName="cx"
   from="620" to="400"
   dur="50s"
   begin="0s;moon3.end"
   repeatCount="1"
  />

  <animate
   id="moon1"
   attributeName="opacity"
   from="1" to="0"
   dur="50s"
   begin="0s;moon3.end"
   repeatCount="1"
  />

  <animate
   id="moon2"
   attributeName="cx"
   from="300" to="-10"
   dur="50s"
   begin="moon1.end"
   repeatCount="1"
  />

  <animate
   id="moon3"
   attributeName="opacity"
   from="0" to="1"
   dur="50s"
   begin="moon1.end"
   repeatCount="1"
  />
</circle>


If you run the animation now, it's a galloping horse where the ground moves fastest. The first series of hills moves slightly slower, and the back series of hills are even slower. The sky background with the sun/moon moves at about a tenth of the speed. This provides the illusion of a multi-layered background.

Beautiful, ain't it? Enjoy the animation magic, and your Lunar New Year!





Quit horsing around!
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 palette 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 palette, 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 without 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

Sunday, 31 August 2025

Web Tutorial: The new TeochewThunder SVG logo

Now that the logo for TeochewThunder has been changed, for this month's web tutorial, we will be examining the process behind the new SVG logo. This is basically a XML file that your web browser will display as an image. Today, not only am I going to show you how to produce it, I will show you the little hacks I employed along the way.

And the beauty of this is, you just need a text editor. The file will be saved with a .svg extension, and you can open it in your web browser.

We begin with the svg tag. The version and xmlns attributes are important here, for this to be rendered correctly by the browser.

logo2.svg
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">

</svg>  


We set width and height of the SVG.

logo2.svg
<svg version="1.1" width="210px" height="130px" xmlns="http://www.w3.org/2000/svg">

</svg>  


Now, we could just get right to drawing the SVG, but let's exercise some developer prudence. Let's create a grid of lines to aid us visually. These are a series of horizontal lines that run through the SVG, each spaced 10 pixels apart.

logo2.svg
<svg version="1.1" width="210px" height="130px" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" x2="210" y1="10" y2="10" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="20" y2="20" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="30" y2="30" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="40" y2="40" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="50" y2="50" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="60" y2="60" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="70" y2="70" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="80" y2="80" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="90" y2="90" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="100" y2="100" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="110" y2="110" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="120" y2="120" stroke="grey" stroke-width="1"/>

</svg>  


This is being viewed at 300% scale.


Now for vertical lines.

logo2.svg
<svg version="1.1" width="210px" height="130px" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" x2="210" y1="10" y2="10" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="20" y2="20" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="30" y2="30" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="40" y2="40" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="50" y2="50" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="60" y2="60" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="70" y2="70" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="80" y2="80" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="90" y2="90" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="100" y2="100" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="110" y2="110" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="120" y2="120" stroke="grey" stroke-width="1"/>

  <line x1="10" x2="10" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="20" x2="20" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="30" x2="30" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="40" x2="40" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="50" x2="50" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="60" x2="60" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="70" x2="70" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="80" x2="80" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="90" x2="90" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="100" x2="100" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="110" x2="110" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="120" x2="120" y1="0" y2="150" stroke="grey" stroke-width="1"/>
  <line x1="130" x2="130" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="140" x2="140" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="150" x2="150" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="160" x2="160" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="170" x2="170" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="180" x2="180" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="190" x2="190" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="200" x2="200" y1="0" y2="130" stroke="grey" stroke-width="1"/>

</svg>


Now we have a nice grid! Effectively what we have here are 10 pixel squares.


Start a path tag with an empty d attribute and set the fill attribute to the infamous TeochewThunder orange!
logo2.svg
<svg version="1.1" width="210px" height="130px" xmlns="http://www.w3.org/2000/svg">
  <line x1="0" x2="210" y1="10" y2="10" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="20" y2="20" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="30" y2="30" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="40" y2="40" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="50" y2="50" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="60" y2="60" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="70" y2="70" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="80" y2="80" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="90" y2="90" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="100" y2="100" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="110" y2="110" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="120" y2="120" stroke="grey" stroke-width="1"/>

  <line x1="10" x2="10" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="20" x2="20" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="30" x2="30" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="40" x2="40" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="50" x2="50" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="60" x2="60" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="70" x2="70" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="80" x2="80" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="90" x2="90" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="100" x2="100" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="110" x2="110" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="120" x2="120" y1="0" y2="150" stroke="grey" stroke-width="1"/>
  <line x1="130" x2="130" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="140" x2="140" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="150" x2="150" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="160" x2="160" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="170" x2="170" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="180" x2="180" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="190" x2="190" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="200" x2="200" y1="0" y2="130" stroke="grey" stroke-width="1"/>

  <path d="" fill="rgb(255, 150, 0)"
  />

</svg>


Now that we have a grid to work off, it should be easier. We start at (0, 0). Use "M" to move the cursor to (100, 0) - which is 10 squares to the right and 1 square down. Then use "l" to move 90 pixels back (9 squares) and no squares vertically. Then use "l" again to move 40 pixels (4 squares down. Add a "Z" to signify a return to the origin, and you'll get this triangle!

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40 Z
"
  fill="rgb(255, 150, 0)"
/>



The first "T" is taking shape, and we are moving right to the underlined part.

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40
  l 30 0
  l 0 70
  l 120 0
Z"
  fill="rgb(255, 150, 0)"
/>



Now we're taking care of the jagged part of the "lightning bolt". For this, we move the line to the right and up, then horizontally left, then right and up again.

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40
  l 30 0
  l 0 70
  l 120 0
  l 15 -50
  l -10 0
  l 5 -20
Z"
  fill="rgb(255, 150, 0)"
/>



A few horizontal and vertical strokes later, we're almost done with the horizontal crosspiece of the second "T", and on course to complete the first "T".

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40
  l 30 0
  l 0 70
  l 120 0
  l 15 -50
  l -10 0
  l 5 -20
  l 30 0
  l 0 -40
  l -90 0
  l 0 40
Z"
  fill="rgb(255, 150, 0)"
/>



This is where you do a little bit of trial and error, mirroring the slanting of the "lightning bolt", but in reverse.

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40
  l 30 0
  l 0 70
  l 120 0
  l 15 -50
  l -10 0
  l 5 -20
  l 30 0
  l 0 -40
  l -90 0
  l 0 40
  l 30 0
  l -5 40
  l 15 0
  l 0 20
Z"
  fill="rgb(255, 150, 0)"
/>



That's actually the hardest part - the rest is all horizontal and vertical strokes.

logo2.svg
<path d="M 100 10
  l -90 0
  l 0 40
  l 30 0
  l 0 70
  l 120 0
  l 15 -50
  l -10 0
  l 5 -20
  l 30 0
  l 0 -40
  l -90 0
  l 0 40
  l 30 0
  l -5 40
  l 15 0
  l 0 20
  l -80 0
  l 0 -60
  l 30 0
Z"
  fill="rgb(255, 150, 0)"
/>



What we do now is remove all the grid lines...
<svg version="1.1" width="210px" height="130px" xmlns="http://www.w3.org/2000/svg">
  <!---
  <line x1="0" x2="210" y1="10" y2="10" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="20" y2="20" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="30" y2="30" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="40" y2="40" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="50" y2="50" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="60" y2="60" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="70" y2="70" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="80" y2="80" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="90" y2="90" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="100" y2="100" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="110" y2="110" stroke="grey" stroke-width="1"/>
  <line x1="0" x2="210" y1="120" y2="120" stroke="grey" stroke-width="1"/>

  <line x1="10" x2="10" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="20" x2="20" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="30" x2="30" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="40" x2="40" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="50" x2="50" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="60" x2="60" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="70" x2="70" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="80" x2="80" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="90" x2="90" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="100" x2="100" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="110" x2="110" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="120" x2="120" y1="0" y2="150" stroke="grey" stroke-width="1"/>
  <line x1="130" x2="130" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="140" x2="140" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="150" x2="150" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="160" x2="160" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="170" x2="170" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="180" x2="180" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="190" x2="190" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  <line x1="200" x2="200" y1="0" y2="130" stroke="grey" stroke-width="1"/>
  -->

  <path d="M 100 10
    l -90 0
    l 0 40
    l 30 0
    l 0 70
    l 120 0
    l 15 -50
    l -10 0
    l 5 -20
    l 30 0
    l 0 -40
    l -90 0
    l 0 40
    l 30 0
    l -5 40
    l 15 0
    l 0 20
    l -80 0
    l 0 -60
    l 30 0Z"
    fill="rgb(255, 150, 0)"
  />
</svg>


And here's your (or rather, my) SVG in its final glory!


Now let's test this with HTML. Create this file wth a black background (or any color, really, other than orange).

tt_logo.html
<!DOCTYPE html>
<html>
  <head>
    <title>New T___T Logo</title>
  </head>

  <body style="background-color:black">

  </body>
</html>


And add in the SVG file that we created, but in different sizes, for testing.

tt_logo.html
<!DOCTYPE html>
<html>
  <head>
    <title>New T___T Logo</title>
  </head>

  <body style="background-color:black">
    <img src="logo2.svg" />
    <img src="logo2.svg" width="300" />
    <img src="logo2.svg" width="100" />

  </body>
</html>


Here, you can see the "S" part of SVG - "scalable", that is. No matter how big or small it is the logo remains crisp and sharp!


Final words

I like this new logo, and I thought the process of making it could be a good learning experience. The original will always have a place in my heart due to the lighthearted moments it was inspired from. But perhaps it's time for this blog to grow up, just a bit.

Ta___Ta for now!
T___T