Thursday, 26 February 2026

Film Review: Mercy

Picture this - three years from now, in the year 2029, the state of crime in the USA has devolved to the point where Artificial Intelligence now handles the jobs of judge, jury and executioner. That's the dystopian environment in which this movie takes place, and it obviously caught my interest because, A.I. (Also, Rebecca Ferguson. Mmm mmm.)


That, in a nutshell, is Mercy. A telling of a tale set in a dystopian American future.

Much of the movie is in screenlife format - in fact, the director is Timur Bekmambetov, who also did Unfriended and Searching. And - ugh - War of the Worlds 2025.

Warning - major spoilers ahead!

This actually makes the movie sound more exciting than it really is. I assure you, other than the odd twist here and there, there are almost no surprises in this one. Consequently, whatever I reveal here could hardly count as a spoiler, much less a major one.

The Premise

Chris Raven awakes to find himself in an AI-controlled courtroom where he is being tried for the crime of murder. His guilt has been set at a level of 98%, and he has to use all material at his disposal to bring that figure down to 92%, within 90 minutes.

The Characters

A barely likeable Chris Pratt as detective Chris Raven. Pratt is actually a decent actor when he puts his mind to it despite that annoyingly distracting chiselled jawline. I know this because I recently saw him in The Terminal List, and he just about blew me away with the intensity of his performance. He takes things up a notch for this movie.

Rebecca Ferguson is largely underutilized as Judge Maddox, the A.I personality presiding over Raven's case. It's a failed endeavour. I say this because from what Ive seen of Ferguson in the Mission Impossible and Dune movies, this woman is incapable of a bad performance. But wasting her hugely expressive face and undeniable acting chops for the robotic role of an AI... now this should be a crime, dammit.

Kali Reis as Jaq Diallo. Last saw her in True Detective: Night Country. That neck tattoo was hella distracting, but I'd recognize those badass vibes anywhere. She was a tad one-dimensional here, but still awesome.

Chris Sullivan as Rob Nelson, who's actually secretly the brother of David Webb. He's out for revenge against Chris Raven and the Mercy Court. Did the nice guy act a little too well at the start, which immediately led me to think he was hiding something.

Jeff Pierre as Patrick Burke. Sleazy dirtbag who used cooking and conversation as a way to seduce a married woman. Felt zero sympathy for the ass-kicking he eventually got. Which means the actor did a fantastic job here.

Rafi Gavron is the ultra-suspicious Holt Charles. Looks like a little rat, and really leaned into it. I respect the craft that went into his limited appearance.

Kenneth Choi as dead partner Ray Vale. I dunno if this is Prototype Bias, but this little Asian dude looked the furthest thing from a cop imaginable.

Annabelle Wallis as Nicole Raven, Chris Raven's murdered wife. Not a very likeable character even if we're supposed to sympathize with her for having a alcoholic husband. Something about the character just came across as whiny.

Kylie Rogers plays Britt Raven, the daughter of Chris Raven. The kid did well. She met the emotional beats and did the whole supporting cast member thing to a tee. Not a step out of place.

Ross Gosla as David Webb. Honestly, when I heard the name, I thought this might be a Jason Bourne tie-in. No such luck. Gosla doesn't say much in the role - it's meant for backstory purposes.

Mark Daneri cameos as as Chris Raven's father-in-law. What a sanctimonous prick. I was amused by that performance. It was way too short, though.

The Mood

The entire show physically takes place within the sterile confines of an A.I-powered courtroom. However, one could be forgiven for forgetting that fact because a lot of the story is told in flashbacks and video sequences. It starts off as a whodunit, with the protagonist racing against time to prove his own innocence.

There's a fair bit of action around the last third of the movie with a bomb threat and a vehicular chase scene, but by then most of the mystery is gone and it's basically straight-up action thriller territory.

What I liked

The begininning expository sequence was neatly tied into the introduction of the system that Raven wakes up to. It was a nice stylistic choice - the startup sequence that also serves as exposition.

The futuristic vehicle Jaq is riding for most of the car chase sequences, is beyond cool. It's like this sleek hover bike.

I did enjoy the little VR sequences where things happening in real-time are reflecting in an AI-regenerated reality happening around Chris Raven. So an explosion happening miles away looks like it's happening right around him. Very cool.

What I didn't

The movie is set in 2029, only a few years from now. I'm supposed to believe that in the space of a few years, the American Justice System would have devolved to one that presumes guilt, has no legal representation and a joke of a due process? As crazy as the world is today, that's a tough sell. And the movie does not come close to selling that ridiculous premise to me.

Nelson's plan made no sense. Instead of going for Jaq who's arguably the bigger villain, he chose the long-ass route of befriending Chris and Nicole, and then murdering Nicole. This was a headscratcher.

Jaq's motivations for destroying evidence also made no sense. David Webb being found innocent due to evidence being correctly processed, would also serve to show that the system was working as intended.

The little twists kind of predictable. Come on, who didn't see the reveals coming?

Chris Raven stops snivelling and starts getting into detective mode when his life is on the line, viewing everything through a compartmentalized, professional lens. This was nice to see, but unfortunately, it also highlighted why the rules governing the Mercy Court are impractical to begin with. Chris Raven, a trained police detective, manages to obtain the evidence neccessary and plead his case sufficiently to prove his innocence to the A.I. But even he barely makes it within the time limit, which basically means the average layperson has no chance in hell.

Conclusion

It was an interesting idea, marred by an implausible plot, unfathomable logic and unrelatable motivations. There were some nice spots in between, but not enough to make this qualify as "watchable". The screenlife format is no longer a novelty, and it's no longer an adequate selling point on its own.

My Rating

5 / 10

Guilty as charged,
T___T

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

Monday, 16 February 2026

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

So far we've animated opacity. We will soon animate linear backgrounds. The running horse deserves a good backdrop and we are going to provide one.

Create a rectangle that fills up the entire SVG, just behind the horse, text and frame. Note that the fill attribute points to skyGradient, which we have not yet created.
<svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
    <rect fill="url(#skyGradient)" x="0" y="0" width="600" height="400" />

    <text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>


Now we're going to create the linearGradient tag skyGradient, inside a defs tag. This gradient begins changing colors at the 50% mark, vertically.
<svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
  <defs>
    <linearGradient id="skyGradient" x1="0%" y1="0%" x2="0%" y2="50%">

    </linearGradient>
  </defs>


  <rect fill="url(#skyGradient)" x="0" y="0" width="600" height="400" />


It goes from a deep blue to a purplish blue.
<defs>
  <linearGradient id="skyGradient" x1="0%" y1="0%" x2="0%" y2="50%">
    <stop offset="0%" stop-color="rgb(0, 0, 50)">

    </stop>

    <stop offset="100%" stop-color="rgb(50, 50, 250)">

    </stop>

  </linearGradient>
</defs>

Here, you can see the gradient.



Now add some animation here. It animates the stop-color attribute, changing from deep blue to purplish blue, to brown, then back to deep blue, in the space of 10 seconds, and runs indefinitely. We set the begin attribute to 0 seconds, so it starts right away.
<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(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)">

  </stop>
</linearGradient>


And in the lower regions of the gradient, we also animate the stop-color attribute, going from blue to near-white, then orange, and back to blue.
<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>


Here you can see the transitions!


It feels like the horse is galloping through dawn...


... to day...


... to dusk!


We're going to add a sun. Or moon. Whatever, a heavenly body up in the sky. This is meant to help propagate the illusion that the horse is running forward. This takes the form of a circle tag. I've made it a pale yellow, with a translucent pale yellow border.
<rect fill="url(#skyGradient)" x="0" y="0" width="600" height="400" />

<circle r="10" cx="650" cy="100" fill="rgb(250,250,200)" stroke="rgba(250,250,200,0.8)" stroke-width="5">

</circle>


<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>


You won't see anything even if you refresh. That's because the center of the circle, defined by the attributes cx and cy, are outside of the SVG, which is only 600 pixels in width. We're going to animate the position of this circle. The attribute to animate is cx, because we'll be moving the circle horizontally. Set repeatCount to 1; we won't need anything higher than this. For now, the duration should be 5 seconds. Let's name this moon0.
<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="5s"
   begin="0s"
   repeatCount="1"
  />

</circle>


We now create a new animation, moon1. It will run parallel to moon0, and animate the opacity property from 1 to 0. This means that the "moon" will enter the screen from the right, and fade in.
<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="5s"
   begin="0s"
   repeatCount="1"
  />

  <animate
   id="moon1"
   attributeName="opacity"
   from="1" to="0"
   dur="5s"
   begin="0s"
   repeatCount="1"
  />

</circle>


For the next two animations, moon2 runs once moon1 ends, and moves the "moon" all the way to the left side of the screen, out of sight.
<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="5s"
   begin="0s"
   repeatCount="1"
  />

  <animate
   id="moon1"
   attributeName="opacity"
   from="1" to="0"
   dur="5s"
   begin="0s"
   repeatCount="1"
  />

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

</circle>


The final animation, moon3, also runs once moon1 ends, and animates the opacity attribute back to 1. In essence, the "moon" moves left, out of screen, and fades out.
<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="5s"
   begin="0s"
   repeatCount="1"
  />

  <animate
   id="moon1"
   attributeName="opacity"
   from="1" to="0"
   dur="5s"
   begin="0s"
   repeatCount="1"
  />

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

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

</circle>


We then make sure moon0 also runs when moon3 ends, making this entire sequence run indefinitely.
<animate
id="moon0"
attributeName="cx"
from="620" to="400"
dur="5s"
begin="0s;moon3.end"
repeatCount="1"
/>


You can see that circle fade in fron the right...


...and fade out on the right as the sky changes!


Now we have a galloping horse, and it looks like it's galloping through night and day! The horse looks like it's galloping through the air. We'll need to plant some ground for it.

Before that, let's define the background colors for the ground. We want it paler green at the top and deeper green at the bottom. And then we want to animate the colors in time to the "sky", so we set both animations at 10 seconds. Both animations cycle through different shades of green at four stages, but the constant is that the upper half of the grassGradient is always significantly lighter than the lower half.
<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="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="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="10s"
       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="10s"
       begin="0s"
       repeatCount="indefinite"
      />
    </stop>
  </linearGradient>

</defs>


And now let's use a path tag. It goes all the way from the far left bottom of the SVG, stretching to the right end of the SVG. There are curves along the way, resulting in slight bumps. We'll want to use the stroke-dasharray attribute to simulate a fuzzy edge, like grass.
<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="5s"
   begin="0s;moon3.end"
   repeatCount="1"
  />

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

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

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

<path d="M0 350
  Q100 340 200 350
  Q300 345 350 350
  Q500 340 600 350
  Z"

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


<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>


Now, we want the path to stretch all the way to twice the width of the SVG. So copy and paste whatever we've done so far to create more points...
<path d="M0 350
  Q100 340 200 350
  Q300 345 350 350
  Q500 340 600 350
  Q100 340 200 350
  Q300 345 350 350
  Q500 340 600 350

  Z"

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


... note that for the numbers pertaining to the horizontal positioning, we'll want to add 600 (which is the width of the SVG) to them, while keeping the vertical positioning the same. Then use the L directive to finish out the path.
<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)"
/>


You see the grassy ground!


Now, this looks weird because in order to complete the illusion of movement and progress, the ground must move with the horse. Envelope the path in a g tag. This will be necessary if we ever want to add any separate objects with the ground, such as a rock, or a shrub. But let's keep it simple for now.
<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)"
  />
</g>


Here, the g tag simplifies the animation, because when we add that animation, it's on the g tag instead of the path. Instead of worrying about what attribute to animate, we use an animateTransform tag and translate it left 600 pixels, in the space of 2.5 seconds. And because 600 is the width of the SVG and we've already made the path twice that in width, when you set repeatCount to indefinite, it seems to go on forever!
<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>

Next

More background magic.

Friday, 13 February 2026

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

It's that time of the year again! Chinese New Year has arrived! 2026 is the Year of the Horse, and I want to work on a nice SVG animation. It's so nice that I'm going to have to break up this web tutorial into more manageable components.

First, we want a galloping horse. No two ways about it. It's the Year of the Horse, after all. From the internet, I obtained this file and scaled it down, then cut out the individual horses to use as frames for the animation. No, I didn't pay for it. I'm also not profiting from this web tutorial, so...

The original image.



horse00.png



horse01.png



horse02.png



horse03.png


This part of the web tutorial shows you how to import external images into an SVG. Let's get the HTML in there. It's a 600 by 400 pixel animation. I set the background to green for visibility. Note that I've included a meta tag for UTF-8. There will be Chinese characters in here.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
     <meta charset="utf-8">
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">

    </svg>
  </body>
</html>


We want to place horse00.png right here, around the bottom middle.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
    <meta charset="utf-8">
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55">

     </image>

    </svg>
  </body>
</html>


See this?


Now place the rest of the PNG files, all overlapping one another.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
    <meta charset="utf-8">
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse01.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse02.png" x="250" y="300" width="100" height="55">

     </image>

     <image href="horse03.png" x="250" y="300" width="100" height="55">

     </image>

    </svg>
  </body>
</html>


It's going to look a right mess, until...


...you set opacity to 0! At that point, the horses will all disappear.
<!DOCTYPE html>
<html>
  <head>
    <title>Year of the Horse</title>
    <meta charset="utf-8">
  </head>

  <body>
    <svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
     <image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">

     </image>

     <image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  
     </image>

     <image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  
     </image>

     <image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">

     </image>
    </svg>
  </body>
</html>


We'll then animate the first image with this. This animation has the id horse0, which we'll need for referencing later. We set attributeName to opacity because that's what we'll be animating. It goes from 0.8 to 1, as you can see from the from and to attributes, with a very short duration of 0.1 seconds. It begins as soon as the SVG loads, so begin is set to 0 seconds. And lastly, we set repeatCount to 1. In theory, it should only execute once.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />

</image>


For the next one, we have horse1. It is identical to horse0, except that it begins only when horse0 ends.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />
</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>


And so on, and so for.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />
</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />
</image>

<image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse2"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

</image>

<image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse3"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

</image>


Of course, you'll want the reverse animation to make the previous frame disappear. These ones won't require an id. Now when you refresh your page, you'll see the horse animate... but only once.
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>

<image href="horse01.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse1"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

</image>

<image href="horse02.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse2"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse1.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

</image>

<image href="horse03.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse3"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="horse2.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse3.end"
   repeatCount="1"
  />

</image>


In here, you'll need an additional trigger. horse0 should run once when the SVG loads... and also when horse3 ends. This will in turn trigger the rest of the animations in an infinite loop!
<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
  <animate
   id="horse0"
   attributeName="opacity"
   from="0.8" to="1"
   dur="0.1s"
   begin="0s;horse3.end"
   repeatCount="1"
  />

  <animate
   attributeName="opacity"
   from="0.5" to="0"
   dur="0.1s"
   begin="horse0.end"
   repeatCount="1"
  />

</image>


Because of the reverse animations we added, you can see a shadowy effect of the horse in the animation! Without it, the animation would look a lot jerkier.



Excellent! We have a galloping horse. What next?

We'll do some easy parts first. Let's have some text. It's a simple text tag where I have some Chinese New Year greeting in Chinese text, in orange fill and red outline. In English, it means "Teochew Thunder wishes all the spirit of dragons and horses!"
<svg width="600" height="400" style="background-color:rgb(100,200,0)" viewBox="0 0 600 400" xmlns="www.w3.org">
  <text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

  <image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">
    <animate
      id="horse0"
      attributeName="opacity"
      from="0.8" to="1"
      dur="0.1s"
      begin="0s;horse3.end"
      repeatCount="1"
    />

    <animate
      attributeName="opacity"
      from="0.5" to="0"
      dur="0.1s"
      begin="horse0.end"
      repeatCount="1"
    />
  </image>


Cool, right?


Then four red rectangles on each of the corners of the SVGs, leaving a 10 pixel border of space.
<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

<rect fill="none" x="10" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="10" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />


<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">


See this?


And then lines to connect the rectangles.
<text x="300" y="30" text-anchor="middle" font-size="30px" fill="rgb(250, 100, 0)" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2">潮州雷祝大家龙马精神!</text>

<line x1="20" y1="20" x2="100" y2="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="500" y1="20" x2="580" y2="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="20" y1="20" x2="20" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="580" y1="20" x2="580" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<line x1="20" y1="380" x2="580" y2="380" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />

<rect fill="none" x="10" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="10" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="10" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />
<rect fill="none" x="570" y="370" width="20" height="20" stroke="rgba(250, 0, 0, 0.8)" stroke-width="2" />

<image href="horse00.png" x="250" y="300" width="100" height="55" opacity="0">


It's actually pretty simple, but the effect is that of a horse galloping in a frame.


That's it for now!

This is actually a good stopping point if it's enough for you. You just need to change the ugly green background to something else. But we're going to seriously improve on this...

Next

Adding a changing sky background.