Friday, 16 December 2022

Web Tutorial: The Christmas Flipbook (Part 1/4)

Here's a good light-hearted web tutorial for this Christmas season!

It's a fun project I dreamed up years ago when I was working for a publications company. Basically, it's a HTML-based flipbook that can display content. Funny how working for different kinds of companies helps generate ideas, eh?

Let's kick this off with some HTML. I want to use jQuery for the project, so we will include the link. The div tags have been set to display in a red outline for visibility. The background will be a dark blue.
<!DOCTYPE html>
<html>
    <head>
        <title>Christmas Flipbook</title>

        <style>
            body
            {
                background-color: rgb(0, 0, 40);
                font-size: 12px;
                font-family: arial;
            }
      
            div { outline: 1px solid red; }
        </style>

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

        <script>

        </script>
    </head>

    <body>
        <div id="container">

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


The container div has a certain width and height. It will span two pages, so if one page is 300 by 400 pixels, then the entire div will be 600 by 400 pixels. It will put in the middle of the screen via the margin property.
body
{
    background-color: rgb(0, 0, 40);
    font-size: 12px;
    font-family: arial;
}

div { outline: 1px solid red; }

#container
{
    width: 600px;
    height: 400px;
    margin: 10% auto 0 auto;
}


Here's the container.


Now we want to properly define the left and right portions which will serve as containers for the pages. Add two divs inside. Each of them will be styled using the CSS class section. Their ids are left and right respectively.
<div id="container">
    <div id="left" class="section">

    </div>

    <div id="right" class="section">

    </div>

</div>


This is the styling for section. They will fit nicely within the container because widths have been set to 50% and they are floated left. We have set the outline property to a green color for visibiity. And at the same time, disable the div's red outline. We don't need it any more for now; plus it's going to interfere with the green outline.
body
{
    background-color: rgb(0, 0, 40);
    font-size: 12px;
    font-family: arial;
}

div { outline: 0px solid red; }

#container
{
    width: 600px;
    height: 400px;
    margin: 10% auto 0 auto;
}

.section
{
    width: 50%;
    height: 100%;
    outline: 1px solid green;
    float: left;
}


And here they are.


In each div, we will have three divs styled using the CSS class pageholder. They will overlap each other completely. Notice that I gave them very distinct ids. That's because we will be referencing these ids later. left_middle will lay on top of left_back and left_front will lay on top of left_middle. Same for those on the right.
<div id="container">
    <div id="left" class="section">
        <div id="left_back" class="pageholder">

        </div>

        <div id="left_middle" class="pageholder">

        </div>

        <div id="left_front" class="pageholder">

        </div>

    </div>

    <div id="right" class="section">
        <div id="right_back" class="pageholder">

        </div>

        <div id="right_middle" class="pageholder">

        </div>

        <div id="right_front" class="pageholder">

        </div>

    </div>
</div>


This is the pageholder CSS class. It takes up full width and height of its parent. I have given it a solid blue outline. The divs float left. Also, remove the green outline from section, because we don't want it to interfere with the blue outline.
.section
{
    width: 50%;
    height: 100%;
    outline: 0px solid green;
    float: left;
}

.pageholder
{
    width: 100%;
    height: 100%;
    outline: 1px solid blue;
    float: left;
}


You can see them now! But they overflow outside of the containers.


We will need to ensure that for left_front, left_middle, right_front and right_middle, the margin-left property is set to negative 100%. This, coupled with the left-floated property of the pageholder CSS class, will cause them to overlap nicely!
.section
{
    width: 50%;
    height: 100%;
    outline: 1px solid green;
    float: left;
}

.pageholder
{
    width: 100%;
    height: 100%;
    outline: 1px solid blue;
    float: left;
}

#left_front, #right_front, #left_middle, #right_middle
{
    margin-left: -100%;
}


You can see them now! Fitting nicely into the spaces defined by section.

It's time to style pages and covers. Let's insert one div inside the left_back div. Style it using the CSS class cover.
<div id="left_back" class="pageholder">
    <div class="cover">

    </div>

</div>


Here is the base CSS styling for cover. We give it a height and width 100 pixels smaller than the size of its container, then give it a padding of 50 pixels to compensate.
#left_front, #right_front, #left_middle, #right_middle
{
    margin-left: -100%;
}

.cover
{
    width: 200px;
    height: 300px;    
    padding: 50px;            
}


But the left side and the right side will look different due to lighting and stuff. Here, for the sake of simplicity, we are going to assume that the outer and inner side of the book cover will look the same. Anyway, we specify that cover will appear this way if it is inside a div styled using left. I want cover, in this case, to have a gradient background that goes from scarlet to brown.
.cover
{
    width: 200px;
    height: 300px;    
    padding: 50px;            
}

#left .cover
{
    background: -moz-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
}


Looks like it works!


And just to add to the illusion of depth, I am going to give it a 3 pixel deep red lining on the top, bottom and left edges. Here, I am using the box-shadow property which is covered in more detail here at this link. (https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow)
#left .cover
{
    background: -moz-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, 3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}


Doesn't look like much now, but it will wow you later when it's all done!


Now add a similar div in right_back.
<div id="left" class="section">
    <div id="left_back" class="pageholder">
        <div class="cover">

        </div>
    </div>

    <div id="left_middle" class="pageholder">

    </div>

    <div id="left_front" class="pageholder">

    </div>
</div>

<div id="right" class="section">
    <div id="right_back" class="pageholder">
        <div class="cover">

        </div>

    </div>

    <div id="right_middle" class="pageholder">

    </div>

    <div id="right_front" class="pageholder">

    </div>
</div>


We will style it the same way, except that the colors are reversed! And of course, the outlining.
#left .cover
{
    background: -moz-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, 3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}

#right .cover
{
    background: -moz-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, -3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}


Looks promising, doesn't it?


OK, we've done the covers. Now let's work on pages. Add these divs into left_middle and right_middle. They will be styled using the CSS class page.
<div id="left" class="section">
    <div id="left_back" class="pageholder">
        <div class="cover">

        </div>
    </div>

    <div id="left_middle" class="pageholder">
        <div class="page">

        </div>

    </div>

    <div id="left_front" class="pageholder">

    </div>
</div>

<div id="right" class="section">
    <div id="right_back" class="pageholder">
        <div class="cover">

        </div>
    </div>

    <div id="right_middle" class="pageholder">
        <div class="page">

        </div>

    </div>

    <div id="right_front" class="pageholder">

    </div>
</div>


This is the base styling. The height and width of this, is slightly lower than that of cover, and there's a two pixel margin at the top. This will serve to have the "page" with the "cover" showing up in the background at the edges.
.cover
{
    width: 200px;
    height: 300px;    
    padding: 50px;            
}

.page
{
    width: 196px;
    height: 296px;    
    padding: 50px;
    margin-top: 2px;            
}


#left .cover
{
    background: -moz-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, 3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}

#right .cover
{
    background: -moz-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, -3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}


And these are the specific stylings. The graduation is from dark brown, to yellow, to beige.
.cover
{
    width: 200px;
    height: 300px;    
    padding: 50px;            
}

.page
{
    width: 196px;
    height: 296px;    
    padding: 50px;
    margin-top: 2px;            
}

#left .page
{

    background: -moz-linear-gradient(left,  rgb(200, 200, 150) 0%, rgb(200, 200, 150) 25%, rgb(150, 150, 50) 70%, rgb(50, 50, 10) 100%);
    background: -webkit-linear-gradient(left,  rgb(200, 200, 150) 0%, rgb(200, 200, 150) 25%, rgb(150, 150, 50) 70%, rgb(50, 50, 10) 100%);
    background: linear-gradient(to right,  rgb(200, 200, 150) 0%, rgb(200, 200, 150) 25%, rgb(150, 150, 50) 70%, rgb(50, 50, 10) 100%);
}

#right .page
{
    background: -moz-linear-gradient(left,  rgb(50, 50, 10) 0%, rgb(150, 150, 50) 30%, rgb(200, 200, 150) 75%, rgb(200, 200, 150) 100%);
    background: -webkit-linear-gradient(left,  rgb(50, 50, 10) 0%, rgb(150, 150, 50) 30%, rgb(200, 200, 150) 75%, rgb(200, 200, 150) 100%);
    background: linear-gradient(to right,  rgb(50, 50, 10) 0%, rgb(150, 150, 50) 30%, rgb(200, 200, 150) 75%, rgb(200, 200, 150) 100%);
}


#left .cover
{
    background: -moz-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(150, 0, 0) 0%, rgb(50, 0, 0) 80%, rgb(30, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, 3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}

#right .cover
{
    background: -moz-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: -webkit-linear-gradient(left,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    background: linear-gradient(to right,  rgb(30, 0, 0) 0%, rgb(50, 0, 0) 20%, rgb(150, 0, 0) 100%);
    box-shadow: 0 3px 0 rgb(100, 0, 0) inset, -3px 0 0 rgb(100, 0, 0) inset, 0 -3px 0 rgb(100, 0, 0) inset;
}


Booyah!


While we're here...

Let's add a dashboard and buttons. Set the red outline for divs back on.
div { outline: 1px solid red; }


Add a div below container. Give it the id dashboard.
<div id="container">
    <div id="left" class="section">
        <div id="left_back" class="pageholder">
            <div class="cover">

            </div>
        </div>

        <div id="left_middle" class="pageholder">
            <div class="page">

            </div>
        </div>

        <div id="left_front" class="pageholder">

        </div>
    </div>

    <div id="right" class="section">
        <div id="right_back" class="pageholder">
            <div class="cover">

            </div>
        </div>

        <div id="right_middle" class="pageholder">
            <div class="page">

            </div>
        </div>

        <div id="right_front" class="pageholder">

        </div>
    </div>
</div>

<div id="dashboard">

</div>


The styling for dashboard is as follows. It is similar to container, but with a very low height.
#container
{
    width: 600px;
    height: 400px;
    margin: 10% auto 0 auto;
}

#dashboard
{
    width: 600px;
    height: 100px;
    margin: 5% auto 0 auto;
    text-align: center;
}


.section
{
    width: 50%;
    height: 100%;
    outline: 0px solid green;
    float: left;
}


This is where the buttons will go.

Now add buttons here.
<div id="dashboard">
    <button>&#9664;</button>
    <button>&#9654;</button>

</div>


The styling for the buttons are here. These are purely aesthetic. I've made them square, and added stylings for hover and disabled.
#dashboard
{
    width: 600px;
    height: 100px;
    margin: 5% auto 0 auto;
    text-align: center;
}

button
{
    width: 50px;
    height: 50px;
    border-radius: 10px;
    border: 3px solid rgb(100, 50, 0);
    background-color: rgb(255, 200, 0);
    color: rgb(100, 50, 0);
}

button:hover
{
    border: 3px solid rgb(255, 200, 0);
    background-color: rgb(100, 50, 0);
    color: rgb(255, 200, 0);
}

button:disabled
{
    border: 3px solid rgb(50, 0, 0);
    background-color: rgb(100, 50, 0);
    color: rgb(50, 0, 0);
}


.section
{
    width: 50%;
    height: 100%;
    outline: 0px solid green;
    float: left;
}


Turn the red outline off, probably for good this time.
div { outline: 0px solid red; }


Here be buttons!


Next

We will work with HTML content in the pages.

Sunday, 11 December 2022

Eyes Off The Prize!, redux

One year has passed since I wrote this blogpost about taking your eyes off the prize, and how this could be an actually more optimal way to move. This is an opportune time to revisit the subject, since I'm not sure I explained the concept all that well the last time round.

A recent shock

Back in June, I took a blood test. The results left me flabbergasted. While blood sugar and pressure were fine, cholesterol levels had skyrocketed since the last blood test six months ago. That meant in the past six months, something in my lifestyle had caused this to happen in my body.

Annual bloodwork.

For sure, it wasn't lack of exercise. I was working out five times a week. Couldn't have been the smoking; this blood test was a recent thing. Therefore, it was most definitely the food. It wasn't like I was pigging out every day, but I had been frying unhealthy stuff for dinner because it was cheap and convenient. Daily.

This had gone unnoticed because I had not put on weight, nor had my waistline expanded. This had led me to believe that everything was fine; in reality, it was the daily exercise that had kept fat at bay. However, cholesterol is not fat, visible or otherwise, and there is no way to exercise it off. Thus, the only way I found out was through a blood test, which, by the way, is absolutely recommended for everyone above their mid-thirties.

The doctor recommended medication, but I was like, fuck that noise. I was going to do it my way.

Lifestyle changes

A dietary change was in order. Instead of buttered toast or a sweet bun at the coffeeshop, breakfast was now a boiled egg and a lettuce sandwich. Instead of frying stuff for lunch, I had half that sandwich.

That lettuce sandwich!

And for dinner, I ate organic rolled oats boiled in milk, with bananas and dried fruit.

On weekends, I'd go out for meals with the wife and give myself a break. But come weekdays, it was back to the grind. Only, it turned out not to be that much of a grind, but more on that later.

Two months later, I went for another blood test and my cholesterol levels had gone down a full sixty percent. The doctor was impressed enough at the progress that instead of recommending medication, he instead told me to keep doing what I was doing.

The point behind taking your eyes off the prize

People were gushing over how much discipline it must have taken, but in truth, discipline was only required for about two weeks. Beyond that, I had hit my stride and it had become a matter of routine. I was on autopilot at that point. After the second week, I was eating my oats without thinking about the fact that I was eating oats.

You see, I did have an objective - improve on my cholesterol readings. More importantly, I wanted to achieve that goal in a way that would not put too heavy a strain on myself. Hence, the dietary change. The prep work, the washing up; all of that were tasks that were designed to take a minimum of fuss. The real feat most people imagined, was to be able to eat oats for days at a time, over a period of two months.

Oats daily.

That's where my singular advantage comes in. I'm quite possibly the most boring man in Singapore. I don't crave variety. I don't have this need to seek out new and exciting experiences. This makes it far easier for me than others, to be consistent. Once familiarity sets in, I'm good to go. Forget two months; it's been six now, and I'm still going. Thus, the probability of me going back to my old ways after meeting the original objective, is about nil.

Professionally...

This is apparent in the existence of this blog. Initially, this was set up so that I would hone my craft on a regular basis, and have prospective employers see this. And it worked. I learned new technologies, kept up to date with tech news and developments, kept myself sharp. I progressed from employer to employer, building up my portfolio as time passed.

As mentioned before, I surpassed my income goals a long time ago before I even realized it. Why do I still do this?

Why am I still
taking shots long
after scoring?

Because it's become a habit. A very productive habit. It has become an integral part of my lifestyle. Without it, I might be engaging in any number of less wholesome pursuits. Getting into the salary range was the original objective, but because my eyes weren't on the prize, I came further than I ever imagined. I arrived at this point at least partially because of this habit, and me staying on this trajectory is probably dependent on maintaining this habit. Stopping at this point just because I seem to have obtained the prize, could turn out to be dangerously complacent.

In Conclusion (for real this time)

The value of the prize is not the prize itself, but the habits you formed while getting there, and the lessons you learned.

No doubt, people need to have goals. However, what's arguably more important is a realistic and sustainable way to them, and beyond.

Don't let the destination get in the way of the journey,
T___T

Wednesday, 7 December 2022

Does Skin Art Affect Your Career Prospects?

Liverpool Football Club has had an excellent 2021/2022 season even though the current season has started out depressingly. I'm proud to have YNWA tattooed on my knuckles.

I remember various reactions back when I got my fingers inked. People were like, won't this affect your chances of getting a job? This was especially prevalent among the, shall we say, dinosaur elderly demographic, though in all fairness, younger people (who should know better, but apparently don't) have said pretty much the same thing.

Why they said what they said

Apparently, the conventional wisdom is that visible tattoos during a job interview is a very bad idea. Interviewers who see these tattoos are going to judge such a candidate negatively, and may even throw the application out altogether. Interviewers with a conservative bent are going to be prejudiced against visibly tattooed candidates right from the get-go. Some of the people who told me this - elderly relatives and such - probably are prejudiced against tattooed candidates themselves and might, I suspect, even act on those biases were they in a position to do so. They would know, right?

Facial tattoos are a bad idea.

To a limited extent, the assertion that visible tattoos are a bad idea, does make sense. I mean, if the tattoo is anywhere on your face, unless you're Mike Tyson or a Maori, you probably might want to re-examine your life choices. A facial tattoo screams I'm a rebel and I DGAF, which doesn't exactly instill confidence in either the candidate's emotional maturity or mental health. But if your tattoo is literally anywhere else... who gives a shit, really?

The other piece of conventional "wisdom" is - even if the risk of getting ostracized isn't that great, why take that risk at all? Why not optimize your chances of nailing that interview by covering up whatever body art you may have?

Well, kids, today must be your lucky day. His Teochewness is going to tell you exactly why.

Why none of it matters in the tech industry

People who think it matters, have no idea what the tech sector is like. They assume it is an industrial sector like any other. In some aspects, certainly. And in others, not so.

Tech hires on ability and affordability. To a certain degree (pun intended, heh heh), your paper qualifications don't even matter that much, even less so your physical appearance. This is not to say that Pretty Privilege does not exist or even just looking relatively boring and unthreatening does not have value in tech; but your ability to get things done is the first and foremost thing on any tech interviewer's mind. That, and whether or not the company can meet your salary demands. And in an industry where the demand far outweighs the supply, even mediocre techies like myself are almost guaranteed a job of some kind. The hiring managers cannot afford to turn people away based on something as petty as a tattoo.

Editor's Note: I'm well aware that Silicon Valley has begun engaging in layoffs and freezing of the headcount over in the USA. I'm speaking of Singapore's market here.

In tech, if you do get that job, it won't be due to a lack of visible tattoos. It will not even be a consideration. No one cares.

Visible tattoos.

What I'm trying to say is, tech workers are spoilt for choice. Not only are jobs in the tech sector available, in the absence of those, tech jobs outside of the tech sector are also available. The concern of your typical tech worker is whether or not they have the skills and experience that organizations require. Things like tattoos - on fingers, ankles, necks or even a full sleeve - simply do not make the list of things that are of any import.

It's usually people who don't enjoy the same staggering wealth of options that being a software developer offers, who are at the mercy of prospective employers. And therefore they are in the position of having to worry about issues that amount to trivialities for others. They are stuck in the mindset that employers are doing them a favor by hiring them... and quite often, that is true - for them. After all, if you're above a certain age, have few current and marketable skills and subscribe to antiquated nonsense (such as tattoos being a deciding factor), it is difficult to see the value proposition for prospective employers. People who have limited options should be grateful for whatever scraps come their way.

And if that sounds harsh, tough shit. Stop having limited options.

Why you should show those tattoos

Well, if people see those tattoos, and they're the kind of people who would turn qualified candidates away because they have some preconceived notions about skin art, are they really the kind of people you would want to work with?

What if the rest of the company is actually less particular and it's only the gatekeepers that have these prejudices? Well, are the kind of idiots that would hire gatekeepers like these, really the kind of people you would want to work with, or for?

These tattoos help
me filter out the
undesirables.

Now I am at the stage of my career where not only can I afford to turn away those opportunities, I can afford to burn those bridges... with extreme prejudice. Think of my tattoos as a screening device. Thus, if I really do get rejected at a tech interview simply because the interviewer did not like the tattoos on my knuckles, I'm not going to be losing any sleep over that perceived loss. It's not my loss. Indeed, I dodged a bullet.

If you think that sounds cocky, you're absolutely right. And if you ever intend to amount to anything, you should aspire to be in a position where you can afford to be that cocky. Anything less makes you just another shmuck.

Final note

I have spoken about this before, in passing. That was when I was trying to explain why it is not worth taking tech career advice from people who have never worked in tech. This is an expansion on an example I gave then, regarding my knuckle tattoos.

I stand by what I said then, and now.

Both employers and employees need to stop obsessing over skin art. Focus on worthier things. You're better than this.

Show your skin in the game!
T___T

Friday, 2 December 2022

Film Review: Black Mirror Episode Special: White Christmas

White Christmas is a Black Mirror Episode Special that was set around the conclusion of Series Two. And it's quite the watch.


This installment boasts several twists and turns, most notably the one big one near the end. And as is Black Mirror's wont, this revolves around technology being used, and of course, misused.

Warning

Spoilers! Many of the delicious plot twists are discussed and dissected here. If that isn't your thing, turn back now!

The Premise

A murder suspect is put in a virtual reality simulation to extract a confession. That's not exactly accurate, but it's the simplest way for me to explain it without writing an entire wall of text.

Basically, this story is told mainly in three parts, with each chapter a story of its own, tied back to the overarching narrative.

The Characters

John Hamm has a ball of a time playing Matthew Trent. From the sheer smugness and smarminess, along with the undeniable persuasive charm, he owns every scene he's in. I've only seen him in Baby Driver and Top Gun: Maverick, but man, this guy is good! It definitely doesn't hurt that he gets the choicest lines, even delivering cliches like "always move with purpose" with gleeful panache.

Rafe Spall plays Joe Potter, arguably the real victim of this whole mess. There's a hang-dog look about him that is perfect for this role. His eventual descent into despair and madness, while not entirely relatable, strikes a chord. Even though he's technically a murderer and at least partially responsible for one other death (of a child, no less), he actually winds up being the most sympathetic character in this story.

Rasmus Hardiker as Harry, the nerd who needs help in his pickup game. This ends up tragically, but in the meantime, he does a decent job portraying someone who isn't all that sure of himself and the horror of the guy's final moments.

Natalia Tena is Jennifer. She's a dark-haired aloof girl who's a bit of an ice-queen until she isn't... and reveals herself as mentally ill, with disastrous results for herself and Harry. I thought she looked familiar! She's the same actress playing Nymphadora Tonks from Harry Potter and the Order of the Phoenix, and it's mildly amusing she plays opposite another character named Harry in this one.

Oona Chaplin is delightful as Greta's Cookie AI. She delivers, in her limited on-screen time, the sheer horror of having your consciousness trapped perpetually in a system, with no escape, nothing to do but extremely pedestrian tasks. The fact that she's easy on the eyes has nothing to do with this!


Janet Montgomery
as Joe's ex Bethany Grey. She turns out to be the asshole of this series, probably by design. She is a prime example of people using the Block feature to escape from their moral responsibilities. This whole mess might have been avoided if she had owned her shit like an adult instead of using the Block feature to run away. But I think Montgomery was pretty good in that role. She effectively conveyed affection and self-centeredness, weakness and moodiness in the short screen time she had. I found her performance credible.

Dan Li as the guy Beth cheats on Joe for, Tim. Kind of drab as far as personality goes, nothing really interesting here.

Zahra Ahmadi as Gita, Tim's girlfriend, who, in Joe's words, is "more into him than he is into her". Pretty bubbly, and that's really all the role calls for.

Ken Dury as Beth's dad, Gordon Grey. The portrayal is very "get off my lawn" and cranky.

The Mood

It starts off comfy, in a log cabin. Then it begins to go into the dim and chill vibe of a house party before descending into the macabre horror that Black Mirror is known for.


Soon, it's all shades of white as Matthew goes into other expositions and flashbacks, and eventually we get into another kind of white as Matthew leaves the police station and out into the snowy weather.

There are different moods and lightings used, but ultimately it's grim humor and sadness.

What I liked

Overall, the dialogue was superb. It just served as good exposition, with a lot of foreshadowing baked in. Wouldn't be out of place in a Quentin Tarantino movie.

The Zed-eyes tech is nicely applied as "romantic services", as Matthew puts it. This is used for an expert to coach a user through the pickup game. With features like facial recognition and live camera syncing.






The group chat on Matthew's other screen. The usernames are hilarious and clever! Shout out to "I_AM_WALDO", though. Great callback to The Waldo Moment.


The sequence of Harry trying to confess that Matthew and the others are seeing what he sees and speaking into his ear to Jennifer, a schizophrenic, is ironic, darkly hilarious and horrifying all at the same time. Vintage Black Mirror!

The Block feature. Matthew explains that the Zed-eyes tech, built into their eyes, enables this. Anyone Blocked will visually and audibly blurred to the Blocker, and vice versa. It's creepy, yes... but at the same time I feel like apps like Clubhouse should work like this! Even still photographs will reflect this block, probably by way of image recognition. So cool! Only thing is, the block is lifted once the initiator dies, so that could serve as a very real motive for murder.




The entire sequence of Matthew's conversation with the Cookie, and explaining how this whole thing works. Jon Hamm is really in his element here as this supremely smarmy salesperson persona, and it serves to inform the viewer as well.




More callback goodness! Beth sings the song Abi sang back in Fifteen Million Merits.

The "paper" form that Joe signs at the police station actually acts like some kind of tablet, with the words appearing on "screen". That is a neat piece of tech - probably overkill, but still awesome.


This footage (cool TV, by the way) seems to be straight out from the talent show in Fifty Million Merits. Wow, another callback!


The plot twist of Beth's daughter's parentage. Phew, I totally did not see that coming.

This is not the first time I've seen someone being killed on-screen with a snow globe. I've seen it once in Unfaithful, but the sheer amount of blood had struck me as unrealistic. This one was better.

This is what it looks like when you're blocked by everyone...


...due to being a registered sex offender. There are horrifying implications to this - anyone could attack the identified sex offender just for being a sex offender, and assuming the victim survives the attack, he would not be able to identify the attacker due to the attacker appearing like a idistinct blur and sounding very muffled. Also, in this case Matthew's crime is relatively benign - that of voyeurism. But as far as this goes, there is no indication as to what degree the offence was, and people might assume the worst!


What I didn't

Dan Li's casting as the guy that Beth cheats on Joe for. What the fuck? Other than being Asian (and thus serving as a plot device), this character had absolutely nothing to contribute to the story. He's not even particularly hunky, and let's face it, if you have a distinct lack of characterization for a character, at least make it visually apparent why a woman would cheat on her boyfriend with him.

The scene where Joe's cookie is being sentence to a virtual several thousand years of listening to the same song in the cabin over and over, is decent. But the part about the music volume being cranked up every time he destroys the box, just doesn't make any kind of sense. Like, why would that even happen?

Conclusion

There was more good than bad about this episode special overall. Plenty of Black Mirror classic elements, humor and horror, all tech-related, complete with a really bleak ending. The way the story was constructed was genius! Really making my Christmas.

My Rating

9.5 / 10

Wishing you a very Black Christmas!
T___T

Saturday, 26 November 2022

Spot The Bug: No Face Given!

Tonight's episode of Spot The Bug is in, and it will feature HTML, jQuery and SVG.

HTML never ceases to surprise me. Every time I think I have its number, it flips the script on me. This was one such time, when I was trying to wrangle some dynamic SVG in jQuery.

All these bugs...

Basically, I had an SVG template for a face, and what I was trying to do was dynamically, on a click of a button, superimpose facial features onto that blank face. Sounds simple enough, right?

Here's the code for the HTML. Note that the JavaScript file is separate.
index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Facemaker</title>

        <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
        <script src="js/face.js"></script>

        <script>
            $(document).ready
            (
                () =>
                {
                    generateFace();
                }
            );
        </script>
    </head>
    
    <body>
        <table>
            <tr>
                <td style="width: 200px">
                    <label for="ddlEyes">Eyes
                        <select id="ddlEyes" onchange="generateFace()">
                            <option value="shades">Shades</option>
                            <option value="staring">Staring</option>
                            <option value="shut">Shut</option>
                        </select>
                    </label>
                    <br /><br />
                    <label for="ddlMouth">Mouth
                        <select id="ddlMouth" onchange="generateFace()">
                            <option value="grim">Grim</option>
                            <option value="lipstick">Lipstick</option>
                            <option value="smile">Smile</option>
                        </select>
                    </label>
                    <br /><br />
                    <label for="ddlNose">Nose
                        <select id="ddlNose" onchange="generateFace()">
                            <option value="big">Big</option>
                            <option value="narrow" selected>Narrow</option>
                            <option value="small">Small</option>
                        </select>
                    </label>
                </td>

                <td style="width: 200px">
                    <svg id="svgFace" style="width:200px; height:200px; outline: 1px solid black">

                    </svg>
                </td>
            </tr>
        </table>
    </body>
</html>


And here's the JavaScript code.
js/face.js
function generateFace()
{
    var eyesHTML = generateEyes($("#ddlEyes").val());
    var mouthHTML = generateMouth($("#ddlMouth").val());
    var noseHTML = generateNose($("#ddlNose").val());

    $("#svgFace").html("");

    var faceHTML = $("<ellipse />");
    faceHTML.attr("cx", 100);
    faceHTML.attr("cy", 100);
    faceHTML.attr("rx", 60);
    faceHTML.attr("ry", 80);
    faceHTML.attr("fill", "rgba(255, 200, 0, 1)");

    $("#svgFace").append(faceHTML);
    $("#svgFace").append(noseHTML);
    $("#svgFace").append(eyesHTML);
    $("#svgFace").append(mouthHTML);
}

function generateEyes(val)
{
    if (val == "shades")
    {
        var path = $("<path />");
        path.attr("d", "M55,65 C65,90 75,90 100,70 C125,90 135,90 145,65 Z");
        path.attr("fill", "black");

        return path;
    }

    if (val == "staring")
    {
        var text = $("<text />");
        text.html("O&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;O");
        text.attr("x", 100);
        text.attr("y", 80);
        text.attr("width", 100);
        text.attr("style", "font: 16px sans-serif");
        text.attr("stroke", "black");
        text.attr("stroke-width", 2);
        text.attr("text-anchor", "middle");

        return text;
    }

    if (val == "shut")
    {
        var text = $("<text />");
        text.html("U&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;U");
        text.attr("x", 100);
        text.attr("y", 80);
        text.attr("width", 100);
        text.attr("style", "font: 16px sans-serif");        
        text.attr("stroke", "black");
        text.attr("stroke-width", 2);
        text.attr("text-anchor", "middle");

        return text;
    }
}

function generateMouth(val)
{
    if (val == "grim")
    {
        var path = $("<path />");
        path.attr("d", "M80,150 C90,140 110,140 120,150 ");
        path.attr("stroke", "black");
        path.attr("stroke-width", 2);
        path.attr("fill", "none");

        return path;
    }

    if (val == "lipstick")
    {
        var ellipse = $("<ellipse />");
        ellipse.attr("cx", 100);
        ellipse.attr("cy", 140);
        ellipse.attr("rx", 20);
        ellipse.attr("ry", 5);
        ellipse.attr("stroke", "red");
        ellipse.attr("stroke-width", 3);
        ellipse.attr("fill", "none");

        return ellipse;
    }        

    if (val == "smile")
    {
        var path = $("<path />");
        path.attr("d", "M60,120 C90,140 110,140 140,120 ");
        path.attr("stroke", "black");
        path.attr("stroke-width", 2);
        path.attr("fill", "none");

        return path;
    }    
}

function generateNose(val)
{
    if (val == "big")
    {
        var polyline = $("<polyline />");
        polyline.attr("points", "60,60,90,60,80,120,120,120,110,60,140,60");
        polyline.attr("stroke", "black");
        polyline.attr("stroke-width", 2);
        polyline.attr("fill", "none");

        return polyline;
    }

    if (val == "small")
    {
        var path = $("<path />");
        path.attr("d", "M90,110 C95,115 105,115 110,110");
        path.attr("stroke", "black");
        path.attr("stroke-width", 2);
        path.attr("fill", "none");

        return path;
    }

    if (val == "narrow")
    {
        var path = $("<path />");
        path.attr("d", "M60,60 C70,50 90,40 95,60 L100,110 L105,60 C110,40 130,50 140,60");
        path.attr("stroke", "black");
        path.attr("stroke-width", 2);
        path.attr("fill", "none");

        return path;
    }
}


What went wrong

When selecting different options, the code should have cleared the template, and then generated a different face depending on the options selected. In fact, the code should have generated the face upon loading, depending on the default options set.

Alas, nothing happened no matter what I clicked.




Why it went wrong

Apparently, the browser does not interpret SVG content in exactly the same way it does HTML content. So using the append() method on an SVG would technically put the appropriate tags within the SVG, but it would not be rendered!

See what I mean? Inspecting the code shows that the tags were inside the SVG, but the browser was not reflecting it.




How I fixed it

Ultimately, all I needed was to append this line at the end of the function. What it does, is ensure that svgFace populates its own HTML with... well, its own HTML.
    $("#svgFace").append(faceHTML);
    $("#svgFace").append(noseHTML);
    $("#svgFace").append(eyesHTML);
    $("#svgFace").append(mouthHTML);
    
    $("#svgFace").html($("#svgFace").html());
}


And there, it worked!








Moral of the story

SVG was a relatively late addition to the HTML5 specification, and as such certain things have not quite caught up. Thankfully, the solution was very simple (if not entirely intuitive).

Way to (f)ace this one,
T___T