Monday 27 February 2017

Shopify stands steady

Earlier this month, Shopify, one of the world's most visible online shopping platforms, found itself in a bit of a quandary. This was due to them hosting Breitbart News Network's online store the same way they had hosted so many other online stores. Customers are pulling out and Shopify is facing pressure from activist groups to cease doing business with Breitbart by way of the #DeleteShopify campaign. Even internal staff are voicing their concerns at the company being associated with Breitbart.

Some background

Breitbart News Network is an Alt-right site that plays host to very unpopular opinions, such as how climate change is fake, how President Trump will be a great President, how women in tech suck at interviews and should stop blaming sexism for their lack of success...

And their online store features inflammatory merchandise like this. Admittedly, that's pretty offensive to certain people.



In the wake of the 2016 US Presidential Elections, the USA has been hugely divided. Hardly a day goes by without some unsavory news about Trump being published, along with the inevitable moral outrage. This has led to Shopify being pressured to discontinue their association with Breitbart. Kind of alarming when you consider that Shopify, as a business, isn't even based in the USA - it's based in Canada.

What do I think?

I think it's sad. No, scratch that. I think it's a goddamn tragedy. I've always thought that tech existed to serve people. The bits and bytes don't care what color you are, what gender, what sexual orientation, and most pertinently, what your political affiliations are. As long as what you do is within the bounds of the law in the country you are operating in and do not contradict the company's mission statement, tech serves you merrily with neither fear nor favor. Self-righteous zealots, in their frantic fervor, in their intolerance towards all things Trump and all things even tangentially connected to Trump, are threatening to subvert that.

I don't have a particularly high opinion of Donald Trump. For that matter, I'm not especially fond of Shopify either. As for Breitbart, they don't have a leg to stand on where boycotts are concerned, not if you look at the one they orchestrated last year against Kellog's.

But, my personal biases aside, this is abhorrent. I can handle Shopify's customers leaving if they don't want to be associated with a platform that also serves Breitbart. I don't mind Shopify's employees leaving if they no longer feel comfortable working for a company that serves Breitbart. They are all well and truly within their rights. What irks me is the attempt to browbeat the company into giving in to the noisy demands of this rabid mob.

Bullying is bullying. And the fact that it's meant to hurt an even bigger bully, does not make it right.

Shopify's response so far

I'm heartened to report that Shopify is sticking to its guns. Amazon and Facebook have faced similar pressure, but aren't backing down - for the moment, anyway. Then again, Amazon and Facebook are the big boys. Shopify, in comparison, is the new kid on the block.

CEO Tobias Lütke has been unequivocal in his commitment to continue serving Breitbart News Network despite claiming to dislike it, and I applaud that. Because if you give in to that baying mob even once, where does it end? Tech platforms should be free to serve without being forced to take sides. Tech is about logic. We don't deal in feelings.

Way to go, activists. What a Breit idea.
T___T

Monday 20 February 2017

Working While Unwell

Late last year, I succumbed to the common cold. It was a 38.6 degree fever, running nose, headache, the works. As you read this, you're probably thinking bitch, please... what's so special about that?

Well, glad you asked.

It was special because prior to that, the last time I got sick enough to take Medical Leave was in 2008. Yep, eight long years.

You know what happens when you haven't been sick for that long? You forget what it's like to be sick. You can no longer tell when you're sick. There were times I thought I was coming down with something, only to instantly feel better after a bowl of kway teow soup. Turns out I was just hungry.

Instant cure!

So yes - I didn't know I was sick. It started off as a bit of lethargy in the morning, and a slight headache. By noon, my nose had started running. Still, no big deal, right? By 4pm, I could no longer concentrate because my head was feeling warm and the little throbbing in my head had become a full-on Tico Torres drum solo. (Yes, I'm a Bon Jovi fan. Blow me.)

I went to my doctor and she gave me two days off. Two days later, I returned to the office and fund, to my horror, that the code I had written that day was unusable. It worked, but would fail extreme cases. It was inefficient, amateurish and a total mess. I had to rewrite everything.

OK, I wasn't writing code that saves lives or sinks ships. It wasn't that kind of code. All I really lost was an entire day's work. But what if I'd been doing something really important, that lives depend on?

Which was why, certain comments made in the wake of the death of an SIA Stewardess disturbed me.

What was said:
1. "Crew report sick then cancel flight" - how would you feel if you were the passenger & had a connecting flight to catch

Lady, I would be fucking grateful I was not on a plane being manned by a crew that wasn't fully fit. Who in his or her right mind wants to be several miles up in the air, your lives in the hands of people who aren't feeling up to speed?

"Casual" MCs

This also brought to my attention certain things that were revealed about SIA's policies. Apparently, taking MC for ailments like fever and stomachache were considered casual use of MC entitlement and during staff appraisals, such things would be taken into consideration.

Now, "casual" is subjective. When I told a friend (who falls sick frequently) that I had a 38.6 degree fever, he scoffed and told me it was mild - he'd had a 40 degree fever before. Let me just take this opportunity to congratulate him on making this a competition that nobody wants to win - and winning it. But that aside, this hammered home the point hard. Someone who falls sick frequently is certainly not going to think much of a run-of-the-mill fever. Someone who hasn't been sick in the last eight years, like me for instance, is going to feel like he is dying.

Yes, I get it. Companies want to guard against malingering staff who exploit Medical Leave policies to claim paid leave. But trying to turn this into a one-size-fits-all policy isn't the solution.

Besides, air crew is no laughing matter. Lives depend on you making the correct decisions. And it is devilishly difficult to make correct, or even good, decisions when you have a fever and headache. I found that out the hard way! So for air crew, how is fever considered "casual"?

Subjective basis

I'm probably guilty of being one of those who scoff at the idea of taking MC over minor ailments, such as a sore throat.

Walk it off, pussy. It's a sore throat, not a broken neck.

But see, I have the luxury of saying that. I am a web developer. I code. I think up solutions. My ability to talk doesn't factor much into my work at all. What if I were, say, an insurance salesman? A teacher? A help-desk operator? All of these require talking.

If I had a bout of arthritis, I could probably still hobble my way to work. I might even have a really productive workday, because my lack of mobility doesn't affect my ability to code. But what if I were a footballer? A policeman? A firefighter?

Last year, I attended a live pro wrestling show from WrestlingCityAsia. There was a tag team match going on, and one of the wrestlers seemed off-form. The photographer told me that the wrestler was having fever and diarrhoea, and praised the wrestler's commitment to his work.

Come on, that's bullshit.

Not that simple.

These guys aren't playing Rock, Paper, Scissors in the ring. They're bodyslamming, suplexing and dropkicking. A wrestler's work depends a lot on physicality. He has to lift his opponent correctly, drop his opponent correctly and fall correctly. An amazing mount of coordination is involved. And if said performer is ill, the possibility of someone getting injured, being crippled or even dying, just went way up.

Remember what I said here? I paid a measly twenty bucks for my ticket. No one needs to die for it.

Don't try to be a hero

So, no - working while sick is not heroic or admirable. If it affects your work and lives depend on you doing your work well, it is probably one of the most selfish, irresponsible things you can do. And that's not even mentioning working while down with a contagious condition.

The world doesn't revolve around you, and the planet will not stop revolving on its axis if you miss a day (or two) of work. And if your company can't, or won't, respect the fact that human beings do fall ill, perhaps it's time to say au revoir.

Best of health!
T___T

Thursday 16 February 2017

Paper clip Power

During my 6 years in desktop support, I was often asked the following question.

"Why do you IT support guys always have a paper clip with you?"

Now, even among fellow practitioners, the reasons may vary. Interestingly enough, my ex-boss liked to use it to dig his ears.

This is how it's done.

I kid you not. He would unfurl the paper clip and stick the end in his ear to scrape away at his inner ear lining. Now, this practice is not only unsanitary, it's potentially dangerous and I wouldn't recommend it at all.

So why did I carry a paper clip around?

As mentioned earlier, the reasons may vary among IT professionals. Thus, I can only speak for myself. However, I suspect that most carry the paper clip for the same reason.

You see, back in the day, CD and DVD ROM drives on desktops and laptops often got stuck for a multitude of reasons. And one simple hardware override is to use the amazing paper clip.

See the tiny hole in the picture below, just underneath the CD ROM drive?

Stick it in, baby.

Stick the end of your paper clip in till you meet some resistance, then push. This triggers a button which manually ejects your CD ROM drive. Your computer doesn't even have to be turned on!

I may be biased here, but I find it's better to stick paper clips in a CD ROM drives than your ears.

Do stick around,
T___T

Friday 10 February 2017

Web Tutorial: Valentine Scratch Card

A mushy lovey-dovey upcoming Valentine's Day, dear readers!

Today we'll be making a Valentine's Day scratch card. A card whose contents will be hidden until you run your mouse cursor over it, revealing the contents of the card bit by bit.

The HTML is really simple, and the majority of the work will be borne by the CSS and JavaScript. Here goes...
<!DOCTYPE html>
<html>
    <head>
        <title>Scratch!</title>
    </head>
    <body>
        <div id="scratch_wrapper">

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


I wasn't having you on, was I? The HTML really is that simple. You have a basic HTML layout and a div with an id of scratch_wrapper.

OK, let's style scratch_wrapper. We'll make it a 400 by 400 pixel card, and set the margin property to 10% auto 0 auto so that it sits in the middle of your screen. Then we'll set the border to a nice red so you can see what's happening.
    <head>
        <title>Scratch!</title>
        <style>
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }
        </style>
    </head>


This is your card so far!


OK, time for a bit of JavaScript. Modify your HTML this way...
    <body onload="unscratch()">


... and add the unscratch() function. This causes the scratch card to revert to its full initial state, where the contents are hidden from view. First, you clear the scratch_wrapper div.
        <style>
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }
        </style>

        <script>
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";
            }
        </script>


Next, you specify a nested For loop. 80 times by 80 times. Why 80? We'll get to that in a minute.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {

                    }
                }
            }


In the inner loop of the nested For loop, add this code. It creates a div element and assigns it the CSS class of scratch and full.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                    }
                }
            }


Then the unique id of this element is created according to its row, i, and its column, j.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                    }
                }
            }


Next, you insert this div element into the scratch_wrapper div using the appendChild() method.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        wrapper.appendChild(scratch);
                    }
                }
            }


What's scratch and full?

Aha! I see you've been paying attention. Your objective here is to fill the scratch_wrapper div with tiny squares. scratch defines the dimensions of each square, while full defines the color. You'll see that each square is 5 by 5 pixels. This is not a coincidence. The scratch_card div is 400 pixels wide and 400 pixels tall. If you have 80 rows of 80 squares, each square 5 by 5 pixels, you have (5 x 80 = 400)! The float property is set to left so that when the 400 pixel limit is reached, the next row automatically begins on the left. As for the full CSS class, it defines a color red (feel free to change it!) at 100% opacity.
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }

            .scratch
            {
                width:5px;
                height:5px;
                float:left;
            }

            .full
            {
                background-color:rgba(255,0,0,1);
            }


Your card is now covered with (80 x 80 = 6400) red squares!


Our next objective is to make each square reveal part of the underlying card as your mouse cursor goes over it. To that end, modify your JavaScript. This line adds an event to each div created. On a mouse over, it will fire off the scratchit() function, passing in the id of the current square as an argument.
                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        scratch.onmouseover=function(){scratchit(this.id)};
                        wrapper.appendChild(scratch);
                    }
                }


And of course, we'll need to create the scratchit() function, which will take in scratchid as a string parameter.
        <script>
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        scratch.onmouseover=function(){scratchit(this.id)};
                        wrapper.appendChild(scratch);
                    }
                }
            }

            function scratchit(scratchid)
            {

            }


Before we go further...

Let's examine what the code is supposed to do.

The layout of the squares is as per follows... (assuming the first number is the row and the second number is the column. Here, I'll show you the top left corner of your scratch_wrapper div as an example.
scratch_0_0 scratch_0_1 scratch_0_2 scratch_0_3 scratch_0_4
scratch_1_0 scratch_1_1 scratch_1_2 scratch_1_3 scratch_1_4
scratch_2_0 scratch_2_1 scratch_2_2 scratch_2_3 scratch_2_4


Let's take x as the row of the current square (the square which the mouse cursor is over) and y as the column. For this example, x = 2 and y = 2. Therefore the id of the square in the example is scratch_2_2. When your mouse cursor is over the square, the surrounding squares are affected. Those squares touching the sides and corners of the current square are affected most severely, lowering their opacity to 20%. Squares one square away from the current square, are slightly affected, lowering their opacity to 50%.
scratch_0_0 scratch_0_1 scratch_0_2 scratch_0_3 scratch_0_4 scratch_0_5
scratch_1_0 scratch_1_1 scratch_1_2 scratch_1_3 scratch_1_4 scratch_1_5
scratch_2_0 scratch_2_1 scratch_2_2 scratch_2_3 scratch_2_4 scratch_2_5
scratch_3_0 scratch_3_1 scratch_3_2 scratch_3_3 scratch_3_4 scratch_3_5
scratch_4_0 scratch_4_1 scratch_4_2 scratch_4_3 scratch_4_4 scratch_4_5

Back to the code!

To do this, first, we take the id of the current square, using the argument scratchid and the split() method. From here, we define x and y.

            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);
            }


Then we run the changeopacity() function using the id and the argument "gone".
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");
            }


Next, we extrapolate the ids of the surrounding squares, and run the changeopacity() function using these ids and the argument "almostgone". They're more severely affected, remember?
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");
            }


Then we do the same for the squares one square away from the current square, but this time using the argument "half".
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-2)+"_"+y;
                changeopacity(id,"half");
              
                id="scratch_"+(x+2)+"_"+y;
                changeopacity(id,"half");

                id="scratch_"+x+"_"+(y-2);
                changeopacity(id,"half");
              
                id="scratch_"+x+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-1);
                changeopacity(id,"half");
            }


Next, we write the changeopacity() function.
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-2)+"_"+y;
                changeopacity(id,"half");
              
                id="scratch_"+(x+2)+"_"+y;
                changeopacity(id,"half");

                id="scratch_"+x+"_"+(y-2);
                changeopacity(id,"half");
              
                id="scratch_"+x+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-1);
                changeopacity(id,"half");
            }

            function changeopacity(scratchid,opacity)
            {

            }


What we're going to do first, is create three more CSS classes, half, almostgone and gone. They're almost identical to the full CSS class, except that half has 50% opacity while almostgone has 20%. gone is completely transparent.
            .full
            {
                background-color:rgba(255,0,0,1);
            }

            .half
            {
                background-color:rgba(255,0,0,0.5);
            }

            .almostgone
            {
                background-color:rgba(255,0,0,0.2);
            }

            .gone
            {
                background-color:rgba(255,0,0,0);
            }


So here we obtain the actual square via the id passed in, and perform the operation if the object returned is not a null. It will be null if the object does not exist, which can happen if your current square is near the edges of the scratch_wrapper div and you're trying to get its surrounding squares (which, by definition, don't exist).

Also, declare the variable currentopacity. It'll be useful soon,
            function changeopacity(scratchid,opacity)
            {
                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


The objective is to set the class of the current square to gone, which we've already done, and the surrounding squares to lower opacity, depending on their distance from the current square. But what if the squares affected already have lowered opacity due to an earlier mouse over?

So we find out what their current opacity is, using the getopacity() function. This merely takes the object and obtains its class. It removes the "scratch " (with the space) and returns the resulting string.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If you're trying to set the class to "half", first, obtain the current opacity and set the value of currentopacity to this.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If currentopacity is "full", proceed as planned.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


Now, if currentopacity is already "half", set the class to "almostgone" instead.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If currentopacity is "almostgone", set it to "gone"!
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


Now do the same for the case of "almostgone"!
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="almostgone")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch gone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }  
                }
            }


Run your code. Are you getting this effect?


OK, let's make a slight improvement. Alter the scratch CSS class.

Now try this again. The transition will make the effect look way better.
            .scratch
            {
                width:5px;
                height:5px;
                float:left;
                transition:all 1s;
            }


Finishing touches

Modify your scratch_wrapper div's CSS to use this image as background.


            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
                background-image: url(bg.jpg);
                background-size:cover;
                background-repeat:no-repeat;
                background-position:50% 50%;
            }


Niiiiiice.


Till next Valentine's, remember that beauty is only skin-deep!
T___T