Showing posts with label Valentine. Show all posts
Showing posts with label Valentine. Show all posts

Monday, 9 February 2026

Web Tutorial: The Valentine's Day Ribbon

It's that time of the year, lovebirds! In a few days, Valentine's Day approaches! Granted, this year's Valentine's Day offering looks rather sparing in effort, but I assure you it was a pain in the ass to make.

I present... the animated Valentine's Day Ribbon SVG. I actually created it with the help of the SVG Maker we made last month, but we don't have to use that if we don't want to. This little SVG can be scaled up or down, and placed anywhere you want!

Here's the file to test the SVG on. We'll link to vday2026.svg, and view it at different scales. The page has a black background, but feel free to test with different background colors too.

index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Vday 2026 test</title>
  </head>

  <body style="background-color:black">
    <img src="vday2026.svg" />
    <img src="vday2026.svg" width="50" />
    <img src="vday2026.svg" width="500" />
  </body>
</html>


And here we begin with the standard SVG markup. We'll create it as a 300 by 300 pixel image.

vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">

</svg>


We will start off by defining some colors. This is the background that we will call valGradient. It's a linear gradient that goes from red to pink.

vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
    <defs>
       <linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
           <stop offset="0%" stop-color="rgb(200, 0, 0)">

           </stop>

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

           </stop>

       </linearGradient>
    </defs>
</svg>


valGradient2 is the exact opposite, going from pink to red.

vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
    <defs>
       <linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
           <stop offset="0%" stop-color="rgb(200, 0, 0)">

           </stop>

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

           </stop>
       </linearGradient>

       <linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
           <stop offset="0%" stop-color="rgb(250, 200, 200)">

           </stop>

           <stop offset="100%" stop-color="rgb(200, 0, 0)">

           </stop>
       </linearGradient>

    </defs>
</svg>


Now that we have our colors, let's start drawing the ribbon. We set the outline to a thick orange line so that we can see what we're doing.

vday2026.svg
<svg version="1.1" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg">
<defs>
   <linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
       <stop offset="0%" stop-color="rgb(200, 0, 0)">

       </stop>

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

       </stop>
   </linearGradient>

   <linearGradient id="valGradient2" x1="0%" y1="0%" x2="0%" y2="100%">
       <stop offset="0%" stop-color="rgb(250, 200, 200)">

       </stop>

       <stop offset="100%" stop-color="rgb(200, 0, 0)">

       </stop>
   </linearGradient>
</defs>

  <path
  fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />

</svg>


The path's d attribute starts with an M directive to move to point (150, 50). The Z directive is to return to point of origin, which is (150, 50).

vday2026.svg
<path d="M150 50 Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


This basically means to move to point (125, 30) but curve towards (140, 25).

vday2026.svg
<path d="M150 50
Q140 25 125 30
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


Let's apply a second curve so that the path should form an "n" shape, like a hill.

vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


That's what we should have so far. This is the top left part of the "heart". You should see that we have three of these shapes. The leftmost one is the default size, the middle one is scaled down and the rightmost one is enlarged.


Here's the rest of the points that make up the entire left side of the "heart".

vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60

Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


Coming along nicely. Bear in mind this is not meant to be perfect, but look kind of organic, if that makes sense.


Without belaboring the point, this is the entire first path.

vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50

Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


I think it's coming along quite nicely, wouldn't you say?


Here is the next path. It's a lot shorter.

vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />

<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


...yep.


This last one is the shortest of all.

vday2026.svg
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />

<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="2" />


And that's all the paths. But this is visually a little weird. The shading has no continuity.


We rectify this by using the reverse gradient, valGradient2. for the last two paths.

vday2026.svg
<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />

<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="2" />


Looks good now! Or, at least, less weird.


Now let's just remove the outlines.

vday2026.svg
<path d="M150 50
Q140 25 125 30
Q100 43 100 50
Q90 60 100 80
Q130 100 135 115
L140 110
Q130 90 110 80
Q95 60 110 50
Q130 20 150 60
Q190 20 200 60
Q200 90 160 100
Q110 130 140 175
L145 170
Q120 140 155 110
Q190 100 205 70
Q205 5 150 50
Z"
fill="url(#valGradient)" stroke="rgb(250,100,0)" stroke-width="0" />

<path d="M150 120
L145 130
Q170 150 135 185
Q120 200 120 250
L125 230
L135 250
Q120 200 145 185
Q170 150 150 120
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />

<path d="M155 180
L150 185
Q170 200 160 250
L165 240
L170 250
Q170 190 155 180
Z"
fill="url(#valGradient2)" stroke="rgb(250,100,0)" stroke-width="0" />


So far so good...


Now what we need to do is animate the gradients. For valGradient, the first color, as defined by the attribute stop-color, is red. We use the animate tag to animate the stop-color attribute, making it go from red to pink, and red again. repeatCount is set to indefinite to make it go on forever. The dur attribute is set to 4 seconds, though you can certainly vary this if you want.

vday2026.svg
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
    <stop offset="0%" stop-color="rgb(200, 0, 0)">
        <animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
    </stop>

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

    </stop>
</linearGradient>


For the second color, we do the reverse. It starts off pink, so we make it go red, then pink again.

vday2026.svg
<linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
    <stop offset="0%" stop-color="rgb(200, 0, 0)">
       <animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
    </stop>

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


For valGradient2, we repeat, but in reverse!

vday2026.svg
<defs>
  <linearGradient id="valGradient" x1="0%" y1="0%" x2="0%" y2="100%">
       <stop offset="0%" stop-color="rgb(200, 0, 0)">
          <animate attributeName="stop-color" values="rgb(200, 0, 0);rgb(250, 200, 200);rgb(200, 0, 0)" dur="4s" repeatCount="indefinite" />
       </stop>

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

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

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


The romantic conclusion

This is a very small web tutorial, mostly because I didn't see the point of charting out every single point in the path. Still, I hope this illustrates why I enjoy SVGs so much. Even with just a small subset of all the features available, there's plenty that can be done.

Yours pathologically,
T___T

Wednesday, 12 February 2025

Web Tutorial: Valentine's Day SVG Message

Welcome to 2025's annual Valentine's Day themed web tutorial! I'm a little late this year, but the path of true love runs deep, not smooth!

I want to go light, and to that end, we'll just do a bit of SVG animation. No style tags, no JavaScript. Sound good? No? Too bad sunshine, we're totally doing it.

Here's the beginning HTML. There's an svg tag in the body tag. We'll be doing this with only inline styling, so specify the width and height, then use inline styling to specify the margin and display properties so that it's set in the middle of the screen.
<!DOCTYPE html>
<html>
  <head>
    <title>Valentine's Day</title>
  </head>
  <body>
    <svg width="800px" height="400px" style="display:block; margin:5% auto 0 auto">

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


Add a defs tag in here, because we are going to be specifying some backgrounds.
<svg width="800px" height="400px" style="display:block; margin:5% auto 0 auto">
  <defs>
  
  </defs>  

</svg>


The first background we will specify has an id of textBackground and it's a linear gradient. The plan is to make it fade from deep pink to light pink, then deep pink again.
<svg width="800px" height="400px" style="display:block; margin:5% auto 0 auto">
  <defs>
    <linearGradient id="textBackground" x1="0" x2="0" y1="0" y2="1">
      <stop offset="0%" stop-color="rgb(255, 50, 50)" />
      <stop offset="40%" stop-color="rgb(255, 200, 200)" />
      <stop offset="60%" stop-color="rgb(255, 200, 200)" />
      <stop offset="100%" stop-color="rgb(255, 50, 50)" />
    </linearGradient>

  </defs>  
</svg>


And we are going to have text in the SVG, with this background. We want the letters I and U, huge and bold, right in the middle of the SVG.
<svg width="800px" height="400px" style="display:block; margin:5% auto 0 auto">
  <defs>
    <linearGradient id="textBackground" x1="0" x2="0" y1="0" y2="1">
      <stop offset="0%" stop-color="rgb(255, 50, 50)" />
      <stop offset="40%" stop-color="rgb(255, 200, 200)" />
      <stop offset="60%" stop-color="rgb(255, 200, 200)" />
      <stop offset="100%" stop-color="rgb(255, 50, 50)" />
    </linearGradient>
  </defs>  

  <text x="400px" y="300px" text-anchor="middle"
    font-size="300px" font-family="Verdana" font-weight="bold"
    fill="url(#textBackground)">I U
  </text>

</svg>


We see two nice big block letters!


Our goal is to have "I ♥ U" as the final image, so let's work backwards and create a pink heart. In this case, it's a path. I copied it from a previous repository to save time, and adjusted the position.
<svg width="800px" height="400px" style="display:block; margin:5% auto 0 auto">
  <defs>
    <linearGradient id="textBackground" x1="0" x2="0" y1="0" y2="1">
      <stop offset="0%" stop-color="rgb(255, 50, 50)" />
      <stop offset="40%" stop-color="rgb(255, 200, 200)" />
      <stop offset="60%" stop-color="rgb(255, 200, 200)" />
      <stop offset="100%" stop-color="rgb(255, 50, 50)" />
    </linearGradient>
  </defs>  

  <text x="400px" y="300px" text-anchor="middle"
    font-size="300px" font-family="Verdana" font-weight="bold"
    fill="url(#textBackground)">I U
  </text>

  <path d="M 350 150
    q -50 -60, -100 0
    q -50 80, 100 150
    q 150 -70, 100 -150
    q -50 -60, -100 0
    Z"
  >

  </path>

</svg>


We set the fill to a gradient background which we'll create shortly, The outline will be a translucent pink.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
>

</path>


Because we haven't created the background yet, you should only see the outline of the heart.


In the definitions, create the pinkheartBackground gradient. It will be radial, going from almost white, then pink, then deep pink.
<defs>
  <linearGradient id="textBackground" x1="0" x2="0" y1="0" y2="1">
    <stop offset="0%" stop-color="rgb(255, 50, 50)" />
    <stop offset="40%" stop-color="rgb(255, 200, 200)" />
    <stop offset="60%" stop-color="rgb(255, 200, 200)" />
    <stop offset="100%" stop-color="rgb(255, 50, 50)" />
  </linearGradient>

  <radialGradient id="pinkheartBackground" cx="10%" cy="10%" r="90%" fx="10%" fy="10%">
    <stop offset="0%" style="stop-color:rgb(255, 250, 250); stop-opacity:1" />
    <stop offset="20%" style="stop-color:rgb(255, 220, 220); stop-opacity:1" />
    <stop offset="90%" style="stop-color:rgb(220, 200, 200); stop-opacity:1" />
    <stop offset="100%" style="stop-color:rgb(200, 150, 150); stop-opacity:1" />
  </radialGradient>

</defs>  


There's a pink heart!


We're now going to cover the letters with red hearts. To that end, let's first define the gradient. It's basically a copy of pinkheartBackground but with redder hues.
<defs>
  <linearGradient id="textBackground" x1="0" x2="0" y1="0" y2="1">
    <stop offset="0%" stop-color="rgb(255, 50, 50)" />
    <stop offset="40%" stop-color="rgb(255, 200, 200)" />
    <stop offset="60%" stop-color="rgb(255, 200, 200)" />
    <stop offset="100%" stop-color="rgb(255, 50, 50)" />
  </linearGradient>

  <radialGradient id="pinkheartBackground" cx="10%" cy="10%" r="90%" fx="10%" fy="10%">
    <stop offset="0%" style="stop-color:rgb(255, 250, 250); stop-opacity:1" />
    <stop offset="20%" style="stop-color:rgb(255, 220, 220); stop-opacity:1" />
    <stop offset="90%" style="stop-color:rgb(220, 200, 200); stop-opacity:1" />
    <stop offset="100%" style="stop-color:rgb(200, 150, 150); stop-opacity:1" />
  </radialGradient>

  <radialGradient id="redheartBackground" cx="10%" cy="10%" r="90%" fx="10%" fy="10%">
    <stop offset="0%" style="stop-color:rgb(255, 150, 150); stop-opacity:1" />
    <stop offset="20%" style="stop-color:rgb(255, 100, 100); stop-opacity:1" />
    <stop offset="90%" style="stop-color:rgb(220, 0, 0); stop-opacity:1" />
    <stop offset="100%" style="stop-color:rgb(200, 0, 0); stop-opacity:1" />
  </radialGradient>

</defs>  


Now let's create one heart. It's a copy of the pink heart, but using redheartBackground as the fill, and a red outline.
<text x="400px" y="300px" text-anchor="middle"
  font-size="300px" font-family="Verdana" font-weight="bold"
  fill="url(#textBackground)">I U
</text>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
>

</path>


<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
>

</path>


But you won't see much, because that red heart will be directly behind the pink heart. Let's apply a bit of translation and rotation to it. We won't scale this one, but let's include it in there, anyway. transform-origin should be set to the middle of the heart.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>

</path>


Now you can see a bit of it peeking out!


Now we are going to place differently translated, rotated and scaled red hearts in the SVG. You don't have to do it exactly how I did, I just did these at random.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(200, 0) rotate(90) scale(0.8, 0.8)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(200, 50) rotate(-20) scale(1, 1)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 50) rotate(-90) scale(0.8, 0.8)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-150, -100) rotate(-20) scale(1, 1)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-150, 100) rotate(50) scale(0.8, 0.8)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-50, -50) rotate(-50) scale(1, 1)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(50, -150) rotate(-20) scale(0.5, 0.5)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, -100) rotate(-80) scale(0.5, 0.5)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-250, 150) rotate(100) scale(0.5, 0.5)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-50, 50) rotate(-60) scale(0.5, 0.5)"
>

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(150, 50) rotate(80) scale(0.5, 0.5)"
>

</path>

<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1, 1)"
>

</path>


Yep, all these hearts.


Now on to animation!

We've set the stage. We want the pink heart to look like it fell right onto the heap of red hearts. To do that, we first scale the pink heart upwards, i.e, increase its size.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>


See what we did to the pink heart?


Now, we are going to simulate a fall "towards" the red hearts. TO that end, we will reduce the size of the pink heart in animation. To that end we will add the animateTransform tag. What we are going to alter is the transform attribute, thus transform will be the value of attributeName. We want to alter the scale, so that's the value of type. from and to attributes are straightforward. The original values are 1.5, and we want to scale it down to 0.5.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
  />

</path>


Then we further add details. begin will be set to 0 seconds so that the animation fires off as soon as the SVG loads. dur is how long the animation lasts, and I specified 1.5 seconds. We set repeatCount to 1, which means it only happens once.
<animateTransform
  attributeName="transform"
  type="scale"
  from="1.5 1.5"
  to="0.5 0.5"
  begin="0s"
  fill="freeze"
  dur="1.5s"
  repeatCount="1"

/>


Refresh. Your pink heart should appear large, then grow smaller as it "falls".


Here, I've added some more scaling animations to reflect the pink heart shaking as it "hits" the red hearts and stops. You notice that the last one has an id, finalPinkHeartScale. This is a bad naming convention because this is far from "final", but in any case, we need this one to have an id because we are going to be taking reference from it.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />


  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

</path>


Time to animate the red hearts. We want each red heart to look like the pink heart is "absorbing" it. So we should have it move back towards the center of the pink heart and shrink till nothing while doing that. Let's use the first red heart as an example. Add an animation. Again attributeName is transform, type is translate, and we take reference from the path tag's transform attribute's translate directive. From that value to 0, which is, not-so-coincidentally, where the pink heart is.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="100 -50" to="0 0"
  />   
 
</path>


We need to make it start half a second after finalPinkHeartScale ends. I set this animation's duration at 1 second. We don't want this to repeat, so set repeatCount to 1.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="100 -50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1"
  />    
</path>


Here's a second animation. This one makes the red heart shrink to 0. Again, the from attribute should take reference from the path tag's transform attribute's scale directive. We need to add the additive attribute and set it to sum. This is so that it doesn't mess up the first animation.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="100 -50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

</path>


We add a third animation to "reset" rotation to 0. As before, the additive attribute is sum.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="100 -50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-30" to="0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

</path>


Now do this for all red hearts! I varied the begin and dur attributes for all of them.
<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(100, -50) rotate(-30) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="100 -50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-30" to="0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  
</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(200, 0) rotate(90) scale(0.8, 0.8)"
>
  <animateTransform
    attributeName="transform" type="translate" from="200 0" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.8 0.8" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="90" to="0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />
  
</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(200, 50) rotate(-20) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="200 50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-20" to="0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 50) rotate(-90) scale(0.8, 0.8)"
>
  <animateTransform
    attributeName="transform" type="translate" from="0 50" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.8 0.8" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-90" to="0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-150, -100) rotate(-20) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="-150 -100" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-20" to="0"
    begin="finalPinkHeartScale.end+1s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-150, 100) rotate(50) scale(0.8, 0.8)"
>
  <animateTransform
    attributeName="transform" type="translate" from="-150 100" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.8 0.8" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="50" to="0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-50, -50) rotate(-50) scale(1, 1)"
>
  <animateTransform
    attributeName="transform" type="translate" from="-50 -50" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="1 1" to="0 0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-50" to="0"
    begin="finalPinkHeartScale.end+0.5s" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(50, -150) rotate(-20) scale(0.5, 0.5)"
>
  <animateTransform
    attributeName="transform" type="translate" from="50 -150" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.5 0.5" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-20" to="0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, -100) rotate(-80) scale(0.5, 0.5)"
>
  <animateTransform
    attributeName="transform" type="translate" from="0 -100" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.5 0.5" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-80" to="0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-250, 150) rotate(100) scale(0.5, 0.5)"
>
  <animateTransform
    attributeName="transform" type="translate" from="-250 150" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.5 0.5" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="100" to="0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="1s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(-50, 50) rotate(-60) scale(0.5, 0.5)"
>
  <animateTransform
    attributeName="transform" type="translate" from="-50 50" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.5 0.5" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="-60" to="0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>

<path d="M 350 150 q -50 -60, -100 0 q -50 80, 100 150 q 150 -70, 100 -150 q -50 -60, -100 0 Z"
  fill="url(#redheartBackground)" stroke="rgba(255, 0, 0, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(150, 50) rotate(80) scale(0.5, 0.5)"
>
  <animateTransform
    attributeName="transform" type="translate" from="150 50" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1"
  />  

  <animateTransform
    attributeName="transform" type="scale" from="0.5 0.5" to="0 0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

  <animateTransform
    attributeName="transform" type="rotate" from="80" to="0"
    begin="finalPinkHeartScale.end" fill="freeze" dur="0.5s" repeatCount="1" additive="sum"
  />  

</path>


See all these hearts starting to move...


...and some of them are still staying in place...


...but eventually they all get "absorbed".


This does not look all that realistic. The pink heart should actually get bigger the more red hearts it "absorbs". So add another animation to increase the size of the pink heart, half a second after finalPinkHeartScale ends, to coincide with some of the "absorbings".
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.4 0.4"
    to="0.7 0.7"
    begin="finalPinkHeartScale.end+0.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

</path>


A little more...
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.4 0.4"
    to="0.7 0.7"
    begin="finalPinkHeartScale.end+0.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.9 0.9"
    begin="finalPinkHeartScale.end+1s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

</path>


...and some more.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.4 0.4"
    to="0.7 0.7"
    begin="finalPinkHeartScale.end+0.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.9 0.9"
    begin="finalPinkHeartScale.end+1s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

 <animateTransform 
    attributeName="transform"
    type="scale"
    from="0.7 0.7"
    to="1 1"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
 />
</path>


Finally, I have an animation applied on the stroke-width attribute that will carry on indefinitely once the pink heart has grown to its full size.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.4 0.4"
    to="0.7 0.7"
    begin="finalPinkHeartScale.end+0.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.9 0.9"
    begin="finalPinkHeartScale.end+1s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.7 0.7"
    to="1 1"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animate
    attributeName="stroke-width"
    values="10px; 50px; 10px"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.5s"
    repeatCount="indefinite"
  />

</path>


Tell me this ain't awesome.



Final touches...

This is entirely superfluous, but still fun to do. Add two circles to the SVG. Both are a translucent deep pink, though the second one is more translucent. You won't see anything since both have r attributes set to 0.
<path d="M 350 150
  q -50 -60, -100 0
  q -50 80, 100 150
  q 150 -70, 100 -150
  q -50 -60, -100 0
  Z"

  fill="url(#pinkheartBackground)" stroke="rgba(255, 200, 200, 0.2)" stroke-width="10px"
  transform-origin="50% 50%" transform="translate(0, 0) scale(1.5, 1.5)"
>
  <animateTransform
    attributeName="transform"
    type="scale"
    from="1.5 1.5"
    to="0.5 0.5"
    begin="0s"
    fill="freeze"
    dur="1.5s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.6 0.6"
    begin="1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    id="finalPinkHeartScale"
    attributeName="transform"
    type="scale"
    from="0.6 0.6"
    to="0.5 0.5"
    begin="1.7s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.4 0.4"
    to="0.7 0.7"
    begin="finalPinkHeartScale.end+0.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.5 0.5"
    to="0.9 0.9"
    begin="finalPinkHeartScale.end+1s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animateTransform
    attributeName="transform"
    type="scale"
    from="0.7 0.7"
    to="1 1"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.2s"
    repeatCount="1"
  />

  <animate
    attributeName="stroke-width"
    values="10px; 50px; 10px"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.5s"
    repeatCount="indefinite"
  />
</path>

<circle r="0" fill="rgba(200, 150, 150, 0.8)">

</circle>

<circle r="0" fill="rgba(200, 150, 150, 0.5)">

</circle>


We want each circle to traverse the perimeter of the pink heart. So we'll input the path for the pink heart into an animateMotion animation. We want this to begin only after finalPinkHeartScale. The duration is set to 5 seconds and we set this to animate forever.
<circle r="0" fill="rgba(200, 150, 150, 0.8)">
  <animateMotion
    path="M 350 150
    q -50 -60, -100 0
    q -50 80, 100 150
    q 150 -70, 100 -150
    q -50 -60, -100 0
    Z"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="5s"
    repeatCount="indefinite"
  />

</circle>

<circle r="0" fill="rgba(200, 150, 150, 0.5)">

</circle>


And here's another animation, this one animating the r attribute from 5 to 10 pixels. This animation is faster and will also repeat forever.
<circle r="0" fill="rgba(200, 150, 150, 0.8)">
  <animateMotion
    path="M 350 150
    q -50 -60, -100 0
    q -50 80, 100 150
    q 150 -70, 100 -150
    q -50 -60, -100 0
    Z"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="5s"
    repeatCount="indefinite"
  />
  
  <animate
    attributeName="r"
    values="5px; 10px; 5px"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.5s"
    repeatCount="indefinite"
  />

</circle>

<circle r="0" fill="rgba(200, 150, 150, 0.5)">

</circle>


We'll do the same for the second circle, but with slightly different animaton values for contrast.
<circle r="0" fill="rgba(200, 150, 150, 0.8)">
  <animateMotion
    path="M 350 150
    q -50 -60, -100 0
    q -50 80, 100 150
    q 150 -70, 100 -150
    q -50 -60, -100 0
    Z"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="5s"
    repeatCount="indefinite"
  />
  
  <animate
    attributeName="r"
    values="5px; 10px; 5px"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.5s"
    repeatCount="indefinite"
  />
</circle>

<circle r="0" fill="rgba(200, 150, 150, 0.5)">
  <animateMotion
    path="M 350 150
    q -50 -60, -100 0
    q -50 80, 100 150
    q 150 -70, 100 -150
    q -50 -60, -100 0
    Z"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="3s"
    repeatCount="indefinite"
  />
  
  <animate
    attributeName="r"
    values="3px; 10px; 3px"
    begin="finalPinkHeartScale.end+1.5s"
    fill="freeze"
    dur="0.5s"
    repeatCount="indefinite"
  />

</circle>


And there you have the two translucent deep pink circles traversing the perimeter of the pink heart.


Well, it's been a pleasure!

SVG animations can be a lot of fun, especially when we're just doing silly inconsequential stuff like that. Have a nice, romantic week!

Don't U just ♥ SVG?
T____T

Friday, 11 October 2024

TeochewThunder: Year Ten (Part 1/2)

Ten big ones. Ten!

This blog has been around for ten years since October in 2014. It feels like a momentous occasion, only it's really not, because I wasn't working up to this being any kind of milestone. In all honesty, I just kind of did my thing, rinse and repeat, and before I knew it, it was the tail end of 2024.

Ten glorious years!

So what's been going on? As I speak, numerous wars rage across the planet, and some of them even involve actual physical violence. Artificial Intelligence is being hyped to the heavens the same way Blockchain and the Metaverse were. The USA is being all dramatic about their upcoming Presidential Election.

In other words, same shit, different day. Business as usual, folks!

This blog has kept moving along even with the numerous changes in my life since it first started in 2014. It hasn't been a huge success but it doesn't need to be. Maintaining it has kept me sharp, sharper than if I'd spent my time playing video games and scrolling on TikTok.

Blogging has been slow this year. From the October of last year, I concentrated on moving house, and finally completed the move on the last day of 2023. Since then, it's been a period of settling in. In addition to that, my Lenovo died and I was scrambling for a replacement.

Web Tutorials

This year, I broke away from my bread-and-butter of HTML, CSS and JavaScript, and started making web tutorials in Python. Mostly I just didn't want to neglect the skills I picked up in 2021 and allow them to atrophy too much. So if you regularly check in to see what other cool stuff I have that you can jump into right away without needing to install an environment for it... tough luck I guess.

Been doing a bit more
of Python lately.

Along the way, though, I found time to do something in ReactJS. And the result was last month's web tutorial.

My work has begun incorporating Generative AI for minor improvements. Nothing earth-shattering. Just trying to develop that muscle memory in the event that one day I'll really need it. And also so that on the occasions I write about AI, I'll actually have some idea what I'm talking about.

Since I'm on the subject of web tutorials, some readers would have noticed that my Easter web tutorial was posted after Easter this year, as opposed to before. This was a total accident as I lost track. But come to think of it, why can't I do this? There's no good reason, for example, for a Valentine web tutorial to always be before Valentine's Day. Sometimes the occurence of Valentine's Day and Chinese New Year in the same month, makes this a logistical challenge. Thus, from this year forth, I'm giving myself the liberty of posting such web tutorials after the day.

Tech News

As mentioned earlier, AI has been dominating my newsfeed regularly. The other tech news regular, unfortunately, is big tech layoffs. Just seems to be a thing these days. Thankfully I'm not in big tech. I'm a tech practitioner in the food and beverage industry. Not one of the high-earners to be honest, but I don't have a problem with staying employed. As of last week, I've been at the company four years now.

Getting laid off.

Elon Musk also appears on my feed from time to time for something stupid. It gets less interesting each time, thus I'm saving my limited supply of fucks for something more... fuck-worthy, I guess.

There's been plenty of news related to work or tech (sometimes both!) on the home front. Some were neither, to be fair, even though I managed to relate it to my professional experiences - such as with Singapore's Smoking Samsui Woman Controversy Through a Web Developer's Eyes.

All in all, I think I've gotten better about identifying what qualifies as tech news, or at least tying it in by viewing it through a tech lens.

Other Stuff

My ongoing quest to remove irrelevant tags in this blog is progressing well. In the space of a few months, I've combed through the tags from A to Z. Some of the tags I removed were Donald Trump and Hillary Clinton, because clickbaity as they can be, they're not tech personalities and that's what I want to keep this blog about. Arguably I should have started way before now, but ah well, hindsight and all.

I've started included more personal stuff on here. Much of my personal stuff is tied into my identity as a software dev, either directly or indirectly, so that's great. A lot of my life realizations were gleaned from writing software.

I'm chill now. Swear to
fuckin' God.

This has been ongoing for a couple years now, but I'm proud to report that my efforts to decrease the amount of profanity that I use on this blog, is paying off. At least, it has for this year. What's really needed is to sustain the effort for another year, and another, until it becomes second nature. The last thing I want is to come off as this perpetually raging old man.

I've improved on my ability to use AI to generate images for this blog. It's been a year, and you'll probably see that the images I've generated are far less plastic. I hope. Plus, it's free. Since I'm not making a damn cent off this blog, I think it's fair.

Next

Greatest hits of 2024.