Friday, 7 April 2023

TikTok Congressional Hearing: The Good, The Bad and The Ugly (Part 1/3)

Six. Hours. Over the third weekend of March, that was the amount of time I spent, over three days on YouTube listening to TikTok CEO Chew Shou Zi, also a fellow Singaporean (which was the only reason I subjected myself to this spectacle at all), fending off a verbal pounding and answering some of the dumbest questions known to mankind.

Welcome to the absurdity that is known as American Congress.

Many of these esteemed gentlemen and ladies who made up the panel, are, for want of a better term, hysterical children. There were several points during the hearing where I alternated between cringing, groaning, or losing my will to live. For hours, they battered Chew Shou Zi with accusations, sarcasm and outright hostility. Some of them just seemed to like the sound of their own voice a wee bit too much, asking questions but not giving TikTok's CEO any space to answer.

But hey, it wasn't entirely bad. Roughly half of these Representatives actually didn't come off like flaming idiots, though it's possibly relative to the ones that did. Let's go over the positives.

The Good

The first entry in this section, of course, goes to Chew Shou Zi. In the span of several hours, he worked the hardest. He was clear, eloquent, and for the most part, articulate. He endured the thankless task facing off against an obviously combative Congress. There were times he stumbled, and I think he resorted way too much to his go-to phrase "I'll get back to you", but this was forgivable because just watching the entire thing was exhausting enough. Imagine having to actually be in his seat.

Chew Shou Zi

Though he was unfailingly polite, sometimes he did push back.

"With a lot of respect, American social companies don’t have a good track record with data privacy and user security. I mean, look at Facebook and Cambridge Analytica, just one example."


And now, some of the members of the panel that I think asked reasonably intelligent questions, or at least didn't conduct themselves like brats. Yes, surprising as it may sound, not all of them made me wonder how they have a job at all.

Frank Pallone

Frank Pallone of New Jersey, went right after the opening statement by Cathy McMorris Rodgers. He pointed out addictive algorithms of big tech, nothing I feel can or should be argued against. He also said that he was focusing his attention not only on TikTok, but on Social Media as a whole with regard to data privacy concerns. That's fair, and I certainly respected that.

Even when he was asking questions, even when some of those questions veered into the combative, he managed to do so in a manner I consider both professional and dignified.

Anna Eshoo

Anna Eshoo of California wasn't especially good. But while she started out with genuine questions regarding the CCP, she seemed unwilling to accept Chew's answers, at one point describing his answer as "presposterous". I'm tempted to say, no lady, you're presposterous, but in all fairness, she didn't do enough to not be in this section. Besides, she actually had the sense to keep quiet and let the CEO answer her questions about TikTok's algorithms in detail, which tells me she was actually interested in the answer rather than simply talking for the sake of talking.

John Sarbanes

John Sarbanes of Maryland was a breath of fresh air, coming right after the dumpster fire that was Bill Johnson (in the "Ugly" section). He conducted himself with dignity and brought up the issues of addictive algorithms used in TikTok, and he said it in a way I didn't strongly disagree with. He appeared open to a compromise for the company to dial down these algorithms, though perhaps skeptical of Chew's willingness to do so. Even when he did interrupt, it wasn't done as a power move or intimidation tactic, and was only because he was running out of time and needed to ask more questions.

Jay Obernolte

Jay Obernolte of California came in as a software engineer and raised several questions that I found of interest, such as a size of the code base, code reviews and software configuration management systems. In particular, he also took the trouble to point out possible flaws in TikTok's proposed Project Texas, and did so without being a prick about it. The conversation between him and Chew was natural, flowed, and a sheer pleasure to listen to.

He even came back for an encore after the break, and posed another question.
"How could looking at the algorithm confirm that it's free from foreign influence? Because the algorithm is just a neural net architecture with inputs and outputs and weights and how to train that."


I swear, this guy knows what he's talking about, and he explained it way better than I ever could have. Hats off! I don't think it's a coincidence that the other Representatives yielded their time to him so he could come back and ask Chew more questions. His questions were thought-provoking and he was actually interested in the answers and could respond thoughtfully to them!

John Curtis

John Curtis of Utah tried to be affable, to his credit. He recognized that Chew was caught up in giving detailed answers, so he continually exhorted, in his very friendly manner, Chew Shou Zi to go back "up to thirty thousand feet" and give a general picture instead. There was something I don't quite trust about this guy. He gave me the vibe of being the "good cop" in the "Good Cop, Bad Cop" routine. My personal feelings aside, I should at least give him props for trying. He's not in here just for his bedside manner. Also, his point about the difference between "a publisher and a distributor" was quite engaging.

Kim Schrier

Kim Schrier of Washington is a pediatrician and she conveyed her concerns very well. Her point about addictive behavior like smoking as really on the nose and felt like a personal attack (heh heh).

"We're seeing eating disorders in elementary age kids now. And I need you to expedite that process as much as possible because parents out there are worried, and I'm worried as a pediatrician, parents can't take themselves off of these platforms. Kids, there's no way they're gonna take themselves off. And we need you to do your part. It may affect your bottom line, but it could save this generation."


She came across as genuinely concerned and sincere as opposed to just posturing. Maybe that makes her a really good politician, if ya know what I mean.

Dan Crenshaw

Dan Crenshaw of Texas was the last to speak.  I didn't want to like this discount Nick Fury, but he did finish the hearing in bold fashion. He even exhibited a healthy level of self-awareness!

"I wanna say this to all the teenagers out there and the TikTok influencers who think we're just old and out of touch and don't know what we're talking about, trying to take away your favorite app. You may not care that your data's being accessed now, but it will be one day when you do care about it."


And while his words may have been strong, he didn't steam-roller the conversation like some of his colleagues did, or play to the gallery. This is borderline, really, but I think he belongs in this section.

Here are the rest. Some were reasonably polite. They accepted answers calmly without excessive judgement and acknowledged that Chew was answering to the best of his ability. Some raised interesting questions or perspectives. Some miraculously did both.

Most importantly, they let him speak. This is a low bar to clear, but by default, they belong in this section.

Michael Burgess of Texas
Diana DeGette of Colorado
Doris Matsui of California
Kat Castor of Florida
Yvette Clarke of New York
Debbie Dingell of Michigan
Nanette Barragán of California
Lisa Blunt Rochester of Delaware
Darren Soto of Florida
Lori Trahan of Massachusetts
Ann Kuster of New Hampshire
Lizzie Fletcher of Texas
Scott Peters of California
Mariannette Miller-Meeks of Iowa
Kelly Armstrong of North Dakota

This does not mean that I agree with everything they said. It just means that by my estimation, and I could be grossly off-base here, they conducted themselves properly.

I will also say that contrary to how much of the internet, in particular TikTok users (surprise, surprise) have portrayed this committee as a bunch of buffoons, many of them didn't really say anything I consider objectionable. They gave Chew Shou Zi an honest shot even if their attitude was sometimes a little prickly. Some of them were even friendly (some, like John Curtis, overly so).

Next

The Bad

Sunday, 2 April 2023

Big Tech Layoffs In Progress!

Microsoft. Google. Amazon. Apple. FacebookMeta. Twitter. What do all these companies have in common? They're big tech, and since last year, they have been engaging in massive layoffs. Ostensibly, this came about because they all massively overhired in preparation for a tech boom that did not come to pass. And now that reality has hit, these companies are jettisoning employees like it's going out of fashion.

Microsoft has announced huge cuts. So has Amazon. And if you recall me discussing the situation at Twitter, that was just the tip of the proverbial iceberg.

Getting dumped by Facebook.

Google and Meta have not been slacking in that department either. Sundar Pichai and Mark Zuckerberg, respectively, went on record to say, that they "take full responsibility for this decision"  and take "full responsibility for the decisions that led us here". I'm not sure what "full responsibility" here entails, but OK. A lot of that vaunted "full responsibility" going around. Google's severance packages seem pretty great, so that's something.

What does all this mean for the tech sector in Singapore?

Well, for starters, big tech offices in Singapore are also cutting back on hiring. Fresh graduates dreaming of getting a start in big tech will probably not be realizing those dreams anytime soon. Other than that, this changes very little.

Tech is still booming. Companies both big and small, across all industries, all need tech and those that aren't recently-started outfits are going into a process of digitalization. It's no longer a luxury; the fact that tech is pervasive in many areas of life, has turned it into a necessity. The pay may not be quite the same, and the value-add on the CV isn't all that sexy, but opportunities are there.

Tech is everywhere.

People need to remember that tech is not just big tech; it's everywhere. Finance. Logistics. Healthcare. Entertainment. The possibilities are endless. Tech professionals who let go of this perception that career success only means working in big tech, will get on just fine. As for the rest? They'll sort themselves out, or show themselves out.

Personally...

When I was a young web developer, like many others, I dared to harbor dreams of one day making it into big tech. The closest I ever got was being an agency contractor to one such firm. Looking at the way things are now, perhaps that's for the best. This is not to gloat at the misfortune of those who have been laid off, but to marvel at how well I've been thriving in spite of not "making it". The grass is always greener on the other side of the fence, but in all honesty, I can't complain.

Would my life have been far more interesting if I had succeeded in being part of the movers and shakers of the tech industry? Perhaps. I'll never know.

Dreaming about
big tech.


I do know that, notwithstanding the generous severance packages, I'm probably in a better position than those who are in limbo right now. Sure, they have big tech on their resumes and that's always a plus. But how would that benefit them? Like, using it as a bargaining chip into another big tech company which is probably also laying staff off? They could apply at smaller outfits, but even if they were willing to take that hit in both remuneration and status, what company would risk taking on employees who are used to "bigger" things?

So many questions.

The takeaway

It's not the end of the world. Not even close. It's not even the end of the tech industry.

Demand for tech is not going down, but as far as big tech is concerned, the demand was grossly overestimated in the first place. And now that the growth has been found to be not as great as initially projected, the layoffs are taking place to cut costs. Nothing to be alarmed about. Unclench those buttcheeks!

Don't be evilunemployed,
T___T

Thursday, 30 March 2023

Web Tutorial: Easter Egg Line Game (Part 4/4)

We've built most of the moving parts, now let's get to scoring, and ending the game. To do this, we need more properties in data. Add the pointsPerEgg property. Set it at 10. We can change this later if you feel like it.
data:
{
    started: false,
    paused: false,
    max: 100,
    moves: 50,
    remainingMoves: 50,
    pointsPerEgg: 10,
    score: 0,
    grid: [],
    selection: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


Remember the confirm() method? The first For loop was for the selection array. In it, declare inc as a formula. If i is greater than 1, square the values of i less 1, then multiply by pointsPerEgg. If i is 1 or less, inc is 0. And then increment score by inc. What this does is, as long as there are more than 2 elements in selection, the player earns points. The more eggs above 2, the more points! If the player only has 2 eggs selected when clicking "CONFIRM", there are zero points.
for (var i = 0; i < this.selection.length; i++)
{
    var inc = (i > 1 ? (i - 1) * (i - 1) * this.pointsPerEgg : 0);
    this.score += inc;


    arr[this.selection[i][0]][this.selection[i][1]].style = "";
    arr[this.selection[i][0]][this.selection[i][1]].color = "";
}


Let's give this a go. If I select these 2 blue eggs and click "CONFIRM", I get 0 points.




But if I select these 4 blue eggs and click "CONFIRM"...




...I get 140 points!




Go on, count it. If pointsPerEgg is 10...

Egg 0: 0 points
Egg 1: 0 points
Egg 2: 1 x 1 x 10 = 10 points
Egg 3: 2 x 2 x 10 = 40 points
Egg 4: 3 x 3 x 10 = 90 points

The total is (10 + 40 + 90 = 140) points!

Now for more scoring! In the For loop, add a variable, color. Set it to the color property of the element in arr that is pointed to by the appropriate values in the current values of selection. Basically, that will be the color of the eggs in the selected line.
for (var i = 0; i < this.selection.length; i++)
{
    var color = arr[this.selection[i][0]][this.selection[i][1]].color;
    var inc = (i > 1 ? (i - 1) * (i - 1) * this.pointsPerEgg : 0);
    this.score += inc;

    arr[this.selection[i][0]][this.selection[i][1]].style = "";
    arr[this.selection[i][0]][this.selection[i][1]].color = "";
}


Increment the appropriate color in the rainbow object by 10. If the value has exceeded the value of max, set it to max.
for (var i = 0; i < this.selection.length; i++)
{
    var color = arr[this.selection[i][0]][this.selection[i][1]].color;
    var inc = (i > 1 ? (i - 1) * (i - 1) * this.pointsPerEgg : 0);
    this.score += inc;
    this.rainbow[color] += 10;
    if (this.rainbow[color] >= this.max) this.rainbow[color] = this.max;


    arr[this.selection[i][0]][this.selection[i][1]].style = "";
    arr[this.selection[i][0]][this.selection[i][1]].color = "";
}


Let's see what this does! If I select 4 green eggs, for example...




...the green stripe in the rainbow is filled up by 40%! Try selecting more green eggs and see if you can get above 100%. You shouldn't be able to (because of that If block we added to prevent that specifically), but you'll continue to score points anyway.




Now, what happens if we fill up the entire rainbow? We're about to handle that. In the confirm() method, we set paused to false at the end. Now instead, we will set it to the value returned by running the isEndGame() method, which will return either true or false. Thus, if it is an endgame scenario, the game will remain paused. Otherwise, pause is set to false and the game continues.
setTimeout(
    ()=>
    {
        this.fillMissingEggs();
        this.paused = this.isEndGame();
    },
    500
);


Create the method. In it, declare variable endGameScenario and set it to false by default. At the end of the method, return endGameScenario.
            setTimeout(
                ()=>
                {
                    this.fillMissingEggs();
                    this.paused = this.isEndGame();
                },
                500
            );
        },
        1000
    );
},
isEndGame: function()
{
    var endGameScenario = false;

    return endGameScenario;
},

isPossibleToMove: function()
{
    var possible = false;
    var arr = this.grid;

    for (var i = 0; i < arr.length; i++)
    {
        if (possible) break;

        for (var j = 0; j < arr[i].length; j++)
        {
            if (i > 0)
            {
                if (arr[i][j].color == arr[i - 1][j].color)
                {
                    possible = true;
                    break;
                }
            }

            if (i < arr.length - 1)
            {
                if (arr[i][j].color == arr[i + 1][j].color)
                {
                    possible = true;
                    break;
                }                                            
            }

            if (j > 0)
            {
                if (arr[i][j].color == arr[i][j - 1].color)
                {
                    possible = true;
                    break;
                }        
            }

            if (j < arr[i].length - 1)
            {
                if (arr[i][j].color == arr[i][j + 1].color)
                {
                    possible = true;
                    break;
                }    
            }
        }
    }

    return possible;
}


Right! Now, if remainingMoves is 0, that means there are no more moves and thus the game is over. In this case, we set endGameScenario to true.
isEndGame: function()
{
    var endGameScenario = false;

    if (this.remainingMoves == 0) endGameScenario = true;

    return endGameScenario;
},


If every property in the rainbow array is the value of max, then the game is also over.
isEndGame: function()
{
    var endGameScenario = false;

    if (this.remainingMoves == 0) endGameScenario = true;
    if (this.rainbow.red == this.max && this.rainbow.orange == this.max && this.rainbow.yellow == this.max && this.rainbow.green == this.max && this.rainbow.blue == this.max && this.rainbow.indigo == this.max && this.rainbow.violet == this.max) endGameScenario = true;

    return endGameScenario;
},


Here, we check if it's an endGameScenario. If not, we need to run the handleimpossibleScenario() method.
isEndGame: function()
{
    var endGameScenario = false;

    if (this.remainingMoves == 0) endGameScenario = true;
    if (this.rainbow.red == this.max && this.rainbow.orange == this.max && this.rainbow.yellow == this.max && this.rainbow.green == this.max && this.rainbow.blue == this.max && this.rainbow.indigo == this.max && this.rainbow.violet == this.max) endGameScenario = true;

    if (endGameScenario)
    {

    }
    else
    {
        this.handleimpossibleScenario();
    }


    return endGameScenario;
},


If it's time to end the game, we will do this fancy animation. We want the final score to appear, but we want the number to run! At the same time, the number of moves remaining will go to zero. The more moves remaining there are, the more points the user scores. So first, let us add this property, pointsPerMove, to data. Set it at 100. You can change it later.
data:
{
    started: false,
    paused: false,
    max: 100,
    moves: 50,
    remainingMoves: 50,
    pointsPerEgg: 10,
    pointsPerMove: 100,
    score: 0,
    grid: [],
    selection: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


Back to the isEndGame() method, we declare scoreTimer and set it by running the setInterval() function with a 100 millisecond delay. It will run the code within every 100 milliseconds.
isEndGame: function()
{
    var endGameScenario = false;

    if (this.remainingMoves == 0) endGameScenario = true;
    if (this.rainbow.red == this.max && this.rainbow.orange == this.max && this.rainbow.yellow == this.max && this.rainbow.green == this.max && this.rainbow.blue == this.max && this.rainbow.indigo == this.max && this.rainbow.violet == this.max) endGameScenario = true;

    if (endGameScenario)
    {
        var scoreTimer = setInterval(
            ()=>
            {

            },
            100
        );

    }
    else
    {
        this.handleimpossibleScenario();
    }
    
    return endGameScenario;
},


In here, we check for the value of remainingMoves.
var scoreTimer = setInterval(
    ()=>
    {
        if (this.remainingMoves > 0)
        {

        }
        else
        {

        }

    },
    100
);


If it's more than 0, decrement it. And then increment score by pointsPerMove.
var scoreTimer = setInterval(
    ()=>
    {
        if (this.remainingMoves > 0)
        {
            this.remainingMoves--;
            this.score += this.pointsPerMove;

        }
        else
        {

        }
    },
    100
);


If it's arrived at 0, run clearInterval() on scoreTimer to stop the sequence. Then add the string "(GAME OVER)" to score. That's lazy AF, but it works, so blow me.
var scoreTimer = setInterval(
    ()=>
    {
        if (this.remainingMoves > 0)
        {
            this.remainingMoves--;
            this.score += this.pointsPerMove;
        }
        else
        {
            clearInterval(scoreTimer);
            this.score += " (GAME OVER)";

        }
    },
    100
);


Now we're going to test this. Play till you fill up the entire rainbow. Once you fill up the last stripe, you'll find that you can't click on the eggs anymore, or at least, there's no effect when you do. And then the numbers start moving till "Moves Remaining" is 0, with the scoreline increasing each time! You can also test what happens when you use up all your moves.




You may have noticed that the "REPLAY" button came on, as well, once the game ended. Do this, so that when the user clicks on it, it restarts the game.
<div class="bottom">
    <br />
    <button v-on:click="confirm()" v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-on:click="setStarted(false)" v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


Cleanup and stuff

Now let's give the game a nice header, using the styles we created.
<div class="title">
    <div class="letter red">E</div>
    <div class="letter orange">A</div>
    <div class="letter yellow">S</div>
    <div class="letter green">T</div>
    <div class="letter blue">E</div>
    <div class="letter indigo">R</div>
    <div class="square">
        <div class="egg style1 violet">
            <div>&nbsp;</div>
            <div>&nbsp;</div>
            <div>&nbsp;</div>
        </div>
    </div>
    <br style="clear:both" />
    <h2><i>line game</i></h2>

</div>


Here is the letter CSS class. We'll set height, width, and make it float left. Text is aligned center.
    .violet
    {
        background-color: rgb(200, 50, 200);
    }

    .letter
    {
        width: 50px;
        height: 55px;
        font-weight: bold;
        font-size: 3em;
        float: left;
        text-align: center;
        color: rgb(0, 0, 0);
    }

</style>


While we're doing this, let's remove the red outline.
div {outline: 0px solid red;}


Now this looks respectable! I really liked being able to add an egg at the end.




Phew, that's it!

This was quite a Herculean effort. But I think I did justice to this Easter's web tutorial. Go try it, and have fun playing it!

Have an AMAZING Easter,
T___T

Monday, 27 March 2023

Web Tutorial: Easter Egg Line Game (Part 3/4)

In this part, we will need more data for gameplay.

Add started and paused as Boolean values, set to false. started and paused are used to determine what buttons are available. paused is also meant to disable certain functions while animation is running.
data:
{
    started: false,
    paused: false,

    max: 100,
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


In the reset() method, set paused to false. That's because we want to un-pause whenever there's a reset.
reset: function()
{
    this.resetEggs();
    this.remainingMoves = this.moves;
    this.score = 0;
    this.rainbow = {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    };

    this.handleimpossibleScenario();

    this.paused = false;
},


Now in the HTML, bind the styles of the buttons to the setButtonVisibility() method, passing in the names of the buttons as arguments.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


And then create the method. Declare vis. By default, it is "none". We return a style string using the value of vis.
getMarginTop: function(color)
{
    var percentage = this.max - this.rainbow[color];
    return "margin-top:" + ((percentage / this.max) * 480) + "px";
},
getButtonVisibility: function(section)
{
    var vis = "none";

    return "display:" + vis;
},

getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},


So first of all, we handle the scenarios for "start". We use the "start" button only if started is false and we want to set it to true. But regardless, if the game is paused, the button is hidden.
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (this.paused) vis = "none";


    return "display:" + vis;
},


And then there's the other buttons. We should only be able to stop a game if started is true. And we should only be able to restart a game if remainingMoves is 0 (which means the game is over).
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";


    return "display:" + vis;
},


Now, because started is false, only the "BEGIN" button is visible!




Let us add an action to the button. It calls the setStarted() method, passing the value true in as an argument.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


The method starts off by setting started to the value of the parameter started, which is either true or false.
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},
setStarted: function(started)
{
    this.started = started;
},

fillMissingEggs: function()
{
    var arr = this.grid;

    for (var i = 0; i < arr.length; i++)
    {
        for (var j = 0; j < arr[i].length; j++)
        {
            if (arr[i][j].style == "" && arr[i][j].color == "")
            {
                arr[i][j] = this.getRandomPair();
            }
        }
    }

    this.grid = [];
    this.grid = arr;
},


If the value of the parameter started is true, we also call reset(). If not, we set the value of paused to false.
setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }

},


Now we set an action to the "STOP GAME" button. Again, we call setStarted() but pass in false as an argument.
<div class="bottom">
    <br />
    <button v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-on:click="setStarted(false)" v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
</div>


Refresh. Click the "BEGIN" button. You should see it disappear to be replaced by the "STOP GAME" button. If you click that, it should revert to the "BEGIN" button.




Let's spruce up this interface a little in the meantime. Make this change to the styling for middle. Remove the specification for height. Add two other properties. overflow should be set to hidden, and we set transition to 1 second because we're going to animate this.
.middle
{
    width: 100%;
    float: left;
    /*height: 500px;*/
    overflow: hidden;
    webKit-transition: all 1s;
    transition: all 1s;
}


In the div styled using middle, bind the style attribute to the getMiddleHeight() method, passing in "game" as an argument.
<div class="middle" v-bind:style=getMiddleHeight("game")>


Now add a div, also styled using middle, also with the style attribute bound. This time, however, the argument should be "instructions".
<div class="middle" v-bind:style=getMiddleHeight("instructions")>

</div>


<div class="middle" v-bind:style=getMiddleHeight("game")>
    <div class="grid">


Maybe add some text.
<div class="middle" v-bind:style=getMiddleHeight("instructions")>
    <h1>Instructions</h1>
    <p>Your objective is to form a full rainbow in as few moves as possible.</p>
    <p>Select a line (2 or more) of similarly colored eggs and click CONFIRM. This counts as one move, and we fill up the appropriate color on the meter.</p>
    <p>A line can be any combination of horizontal or vertical lines.</p>
    <p>The game ends when you run out of moves, or when a full rainbow is formed.</p>
    <p>Extra points are earned from lines greater than 2 eggs.</p>

</div>


And then create the getMiddleHeight() method. We start by declaring height, which is set to 0 by default. At the end, we return a style string using the value of height.
getMarginTop: function(color)
{
    var percentage = this.max - this.rainbow[color];
    return "margin-top:" + ((percentage / this.max) * 480) + "px";
},
getMiddleHeight: function(section)
{
    var height = 0;

    return "height:" + height + "px";
},

getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    //if (section == "confirm" && this.selection.length >= 2) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";

    return "display:" + vis;
},


And here, we handle the cases. If the value of section is "game" and started is true, we set height to 500. If the value of section is "instructions" and the game is not started, we set height to 500. This basically means that either one of the divs that have been styled using middle, will be visible depending on the value of started.
getMiddleHeight: function(section)
{
    var height = 0;

    if (section == "game" && this.started) height = 500;
    if (section == "instructions" && !this.started) height = 500;

    return "height:" + height + "px";
},


Try it! Now you should see a panel of instructions when you refresh the game. When you click on "BEGIN GAME", thus setting started to true, the grid appears to scroll up! It's actually because the height of that div was 0 when started was false, and since the overflow property is hidden, that means the grid is not visible. And only partially visible when the height of the div is more than 0. Same for the instructions panel!








Just to make it look nicer, encase the instructions in a div and style it using instructions.
<div class="middle" v-bind:style=getMiddleHeight("instructions")>
    <div class="instructions">
        <h1>Instructions</h1>
        <p>Your objective is to form a full rainbow in as few moves as possible.</p>
        <p>Select a line (2 or more) of similarly colored eggs and click CONFIRM. This counts as one move, and will fill up the appropriate color on the meter.</p>
        <p>A line can be any combination of horizontal or vertical lines.</p>
        <p>The game ends when you run out of moves, or when a full rainbow is formed.</p>
        <p>Extra points are earned from lines greater than 2 eggs.</p>
    </div>
</div>


Here's the styling for instructions. Just making it look pretty, so don't worry about it.
.middle
{
    width: 100%;
    float: left;
    overflow: hidden;
    webKit-transition: all 1s;
    transition: all 1s;
}

.instructions
{
    border-radius: 10px;
    background-color: rgb(100, 100, 100);
    width: 80%;
    height: 80%;
    padding: 10px;
    margin: 5% auto 0 auto;
    overflow: hidden;
}


.grid
{
    width: 500px;
    height: 100%;
    float: left;        
}


Yup!




Time for gameplay!

We're supposed to be able to click the eggs to select them, so add an event to each square. Clicking on them will run the selectSquare() method, passing in the values of c and s as arguments.
<div class="square" v-for="(s, square) in col" v-on:click="selectSquare(c, s)">
    <div v-bind:class="getEggClass(c, s)">
        <div>&nbsp;</div>
        <div>&nbsp;</div>
        <div>&nbsp;</div>
    </div>
</div>


Create the method.
setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }
},
selectSquare: function(c, s)
{

},

fillMissingEggs: function()
{
    var arr = this.grid;

    for (var i = 0; i < arr.length; i++)
    {
        for (var j = 0; j < arr[i].length; j++)
        {
            if (arr[i][j].style == "" && arr[i][j].color == "")
            {
                arr[i][j] = this.getRandomPair();
            }
        }
    }

    this.grid = [];
    this.grid = arr;
},


Because we will be working with the selection array, add this to data as an empty array.
data:
{
    started: false,
    paused: false,
    max: 100,
    moves: 50,
    remainingMoves: 50,
    score: 0,
    grid: [],
    selection: [],
    rainbow:
    {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    }
},


And make sure that in reset(), it's set to an empty array.
reset: function()
{
    this.resetEggs();
    this.remainingMoves = this.moves;
    this.score = 0;
    this.selection = [];
    this.rainbow = {
        red: 0,
        orange: 0,
        yellow: 0,
        green: 0,
        blue: 0,
        indigo: 0,
        violet: 0
    };

    this.handleimpossibleScenario();

    this.paused = false;
},


Now back to the selectSquare() method. Firstly, exit early if the value of paused is true. If the game is paused, we don't want anything to happen.
selectSquare: function(c, s)
{
    if (this.paused) return;
},


Declare the variable reset and set to false by default. At the end of the method, if reset has somehow been set to true, set selection to an empty array.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;

    if (reset) this.selection = [];

},


Now declare insertPos and set the value by calling the isLinkableAtPos() method, passing in the value of c and s. The method should return either 0 (if linkable at the beginning of selection) or 1 (linkable at the end of selection) or null (not linkable at all).
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (reset) this.selection = [];
},


Now, we have another method, isInSelection(). We call it and pass in the value of c and s. It's going to return either true (yes, that particular square is already selected) or false (no, it isn't). So if the result is false and the value of insertPos is null, we need to reset the selection array.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (!this.isInSelection(c, s) && insertPos == null)
    {
        reset = true;
    }


    if (reset) this.selection = [];
},


Now, if insertPos is 0, we call unshift() to insert the array containing c and s in the beginning of selection. If it's 1, use push() to insert at the end of selection.
selectSquare: function(c, s)
{
    if (this.paused) return;

    var reset = false;
    var insertPos = this.isLinkableAtPos(c, s);

    if (!this.isInSelection(c, s) && insertPos == null)
    {
        reset = true;
    }

    if (insertPos == 0 && insertPos != null)
    {
        this.selection.unshift([c, s]);
    }
    else
    {
        this.selection.push([c, s]);
    }


    if (reset) this.selection = [];
},


And of course, we need to define the methods isLinkableAtPos() and isInSelection().
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color;
},
isInSelection: function(c, s)
{

},
isLinkableAtPos: function(c, s)
{

},

setStarted: function(started)
{
    this.started = started;

    if (started)
    {
        this.reset();
    }
    else
    {
        this.paused = false;
    }
},


isInSelection() is simpler, so let's do that first. By default, it returns false. In this method, there are two parameters, c and s.
isInSelection: function(c, s)
{
    return false;
},
isLinkableAtPos: function(c, s)
{

},


Before that, we iterate through selection. If at any time, the array containing c and s shows up, we return true.
isInSelection: function(c, s)
{
    for (var i = 0; i < this.selection.length; i++)
    {
        if (this.selection[i][0] == c && this.selection[i][1] == s) return true;
    }


    return false;
},


That was simple enough! Now we will work on isLinkableAtPos(). In this method, again, there are two parameters, c and s. First, if there are no elements in the selection array, we return 1 to tell the parent method, selectSquare(), that we can push the array containing c and s into selection. By default, we return null.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;
    
    return null;

},


Let's break away for a bit to add this into the getEggClass() method. In here, we will add the outline CSS class if a call to isInSelection() returns true.
getEggClass: function(c, s)
{
    return "egg " + this.grid[c][s].style + " " + this.grid[c][s].color + " " + (this.isInSelection(c, s) ? "outline" : "");
},


Here's the outline CSS class. In fact, while we're at it, we will set this to the hover pseudoselector of egg. So if you mouse over an egg, it has a white dotted outline. If you click on it and add it to the selection array, the outline should be solid!
.egg
{
    width: 70%;
    height: 90%;
    border-radius: 50%/60% 60% 40% 40%;
    overflow: hidden;
    position: relative;
    margin: 2px auto 0 auto;
    filter: opacity(100);
}

.egg:hover
{
    outline: 2px dotted rgb(255, 255, 255);
}

.egg.outline
{
    outline: 2px solid rgb(255, 255, 255);
}


.egg:after
{
    display: block;
    position: absolute;
    content: "";
    width: 100%;
    height: 100%;
}


Now try positioning your mouse cursor over an egg (in this example, it's one of the red eggs in the middle. Does the white dotted outline appear? If you mouse out, it should disappear.




If you click on it, it should now be a solid white outline! If you click elsewhere, the outline should disappear because null was returned.




Now back to the isLinkableAtPos() method. We need to cater to other cases. First, we declare variables. firstNodeLocationCompatible is a flag that determines if the egg selected is right next to the first egg in the selection array, either horizontally or vertically. lastNodeLocationCompatible does the same, except for the last egg in the selection array. colorCompatible checks if the selected egg is the same color as all elements in the selection array. By default, the value is false.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;


    return null;
},


Declare firstNode, and set it to the first element in the selection array. Also declare lastNode as the last element in the selection array. At this point, there should be at least one element in the array because the case where there are no elements, has already been handled with the guard clause at the beginning of the method.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];


    return null;
},


Now remember that firstNode and lastNode are both arrays containing two elements - column and row indexes. First we need to determine if firstNodeLocationCompatible is true. So if the first element of firstNode matches c and and the second element of firstNode is 1 removed from s (we use abs() to determine this), this means that the selected egg is next to the first egg vertically.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1)) firstNodeLocationCompatible = true;

    return null;
},


If the second element of firstNode matches s and and the first element of firstNode is 1 removed from c (again, we use abs() to determine this), this means that the selected egg is next to the first egg horizontally. Now if the selected egg is either horizontally or vertically next to that first egg, firstNodeLocationCompatible is true.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;

    return null;
},



The same logic goes for lastNode and lastNodeLocationCompatible.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;

    return null;
},


And then we get the color property from the element of grid pointed to by c and s, and compare it to the color property from the element of grid pointed to by the array in firstNode. If there's a match, colorCompatible is true.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;
    if (this.grid[firstNode[0]][firstNode[1]].color == this.grid[c][s].color) colorCompatible = true;

    return null;
},


If colorCompatible is true and firstNodeLocationCompatible is true, that means we can return 0 to say that the selected egg can be added to the beginning of the selection array. If colorCompatible is true and lastNodeLocationCompatible is true, we return 1 to say that the selected egg can be added to the end of the selection array.
isLinkableAtPos: function(c, s)
{
    if (this.selection.length == 0) return 1;

    var firstNodeLocationCompatible = false;
    var lastNodeLocationCompatible = false;
    var colorCompatible = false;

    var firstNode = this.selection[0];
    var lastNode = this.selection[this.selection.length - 1];

    if ((firstNode[0] == c && Math.abs(firstNode[1] - s) == 1) || (firstNode[1] == s && Math.abs(firstNode[0] - c) == 1)) firstNodeLocationCompatible = true;
    if ((lastNode[0] == c && Math.abs(lastNode[1] - s) == 1) || (lastNode[1] == s && Math.abs(lastNode[0] - c) == 1)) lastNodeLocationCompatible = true;
    if (this.grid[firstNode[0]][firstNode[1]].color == this.grid[c][s].color) colorCompatible = true;

    if (firstNodeLocationCompatible && colorCompatible) return 0;
    if (lastNodeLocationCompatible && colorCompatible) return 1;


    return null;
},


Now test this! Select an egg. In this example, I've selected a violet egg from the large clump of violet eggs in the middle. But if we then click on the green egg directly above it, it would get deselected, meaning the selection array would be empty.




If we were to select the violet egg two eggs to the right, it would also be deselected.




You can only select violet eggs right net to the initially selected violet egg, and the first and last eggs in the "line" changes with each egg added. For instance, now, if we were to click on the violet egg directly below the selected line of violet eggs, right next to the indigo egg, the selection array would get cleared as well. Because that egg, while the same color, is not horizontally or vertically next to the first and last eggs in the line.




Now that we're able to select eggs, we need to make sure that the "CONFIRM" button is shown when a minimum of two eggs are selected. In getButtonVisibility(), make sure that if section is "confirm" and selection has at least 2 elements, we set vis accordingly. Of course, if paused is true, everything is still hidden. The following line takes care of that.
getButtonVisibility: function(section)
{
    var vis = "none";

    if (section == "start" && !this.started) vis = "block";
    if (section == "confirm" && this.selection.length >= 2) vis = "block";
    if (this.paused) vis = "none";
    if (section == "stop" && this.started) vis = "block";
    if (section == "restart" && this.started && this.remainingMoves == 0) vis = "block";

    return "display:" + vis;
},


See what we did? When the two blue eggs are selected, the "CONFIRM" button appears! Try deselecting. The button should disappear again!




Let's set the "CONFIRM" button to run the method confirm().
<div class="bottom">
    <br />
    <button v-on:click="confirm()" v-bind:style=getButtonVisibility("confirm")>CONFIRM</button>
    <button v-on:click="setStarted(true)" v-bind:style=getButtonVisibility("start")>BEGIN</button>
    <button v-on:click="setStarted(false)" v-bind:style=getButtonVisibility("stop")>STOP GAME</button>
    <button v-bind:style=getButtonVisibility("restart")>REPLAY</button>
</div>


Create the confirm() method. We begin by decrementing remainingMoves.
getRandomPair: function()
{
    var randomIndex;
    randomIndex = Math.floor(Math.random() * 3) + 1;
    var style = "style" + randomIndex;

    randomIndex = Math.floor(Math.random() * 7);
    var color = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"][randomIndex];

    return {"style": style, "color": color};
},
confirm: function()
{
    this.remainingMoves--;
},

isPossibleToMove: function()
{
    var possible = false;
    var arr = this.grid;


We create arr as a temporary variable, setting it to the value of grid because we don't want the grid to re-render until we're good and ready. We want to make those changes in arr first. And then we set paused to true because there is going to be animation and we want to make sure that clicking stuff while it's going on, does not do anything.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

},


Then we iterate through the selection array and make sure that all elements in arr that correspond with the element in selection, have the CSS class fadeout added to their styling. This means that any egg inside the grid that has been selected, is now styled using fadeout. And then we set grid to the value of arr.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }


    this.grid = [];
    this.grid = arr;
},


This is the fadeout CSS class. In here, we ensure that opacity is set to 0 and we have a transition duration of 1.
.egg
{
    width: 70%;
    height: 90%;
    border-radius: 50%/60% 60% 40% 40%;
    overflow: hidden;
    position: relative;
    margin: 2px auto 0 auto;
    filter: opacity(100);
}

.fadeout
{
    webkit-transition: all 1s;
    transition: all 1s;
    filter: opacity(0);
}


.egg:hover
{
    outline: 2px dotted rgb(255, 255, 255);
}


Now do a selection and click the "CONFIRM" button. For illustration, I have selected a bunch of indigo eggs.




And watch the indigo eggs fade out! The "CONFIRM" button disappears because pause is now true. Also note that the top right display now says "49 / 50" because remaningMoves has been decremented.




Back to the confirm() method! Now we're left with gaping black hole within the grid. We will need to fill them in... but it's not as simple as it sounds. First, we use the setTimeout() function to ensure that the next piece of code executes only after the fadeout animation is done. So if the fadeout CSS class is set to 1 second duration, make sure the setTimeout function delays for 1000 milliseconds.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {

        },
        1000
    );

},


So first, we iterate through the selection array to ensure that all those elements that we added the fadeout CSS class to, now have the style and color properties reset to empty strings.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

        },
        1000
    );
},


The next bit is going to be crazy. We want to go through every column, and look for any squares that are empty (style and color are empty strings), When we find them, we want to replace them with the values from the eggs above. So it looks like eggs are "falling" into place. So first, we examine each and every column of arr using a For loop.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {

            }

        },
        1000
    );
},


Now, we iterate through the current array inside arr, from the rear. Meaning, we work from the bottom. We don't do it for element 0 because there's no element above 0. So if there's any element at position 0 (at the top of the column) is empty, they will be filled in with random eggs. More on that later.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {

    }

}


Now we check to see if the style property is an empty string.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
                                                
        }

    }
}


If so, we traverse that particular array from that index onwards, all the way up to 0...
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
            for (var k = j; k >= 0; k--)
            {

            }  
                                             
        }
    }
}


...and during that traversal, if the current element is not empty, the previous element needs to take on its style and color properties. And we will set the current element to empty.
for (var i = 0; i < arr.length; i++)
{
    for (var j = arr[i].length - 1; j > 0; j--)
    {
        if (arr[i][j].style == "")
        {
            for (var k = j; k >= 0; k--)
            {
                if (arr[i][k].style != "")
                {
                    arr[i][j].style = arr[i][k].style;
                    arr[i][j].color = arr[i][k].color;
                    arr[i][k].style = "";
                    arr[i][k].color = "";
                    break;
                }

            }                                                
        }
    }
}


And then we will empty selection and update grid!
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {
                for (var j = arr[i].length - 1; j > 0; j--)
                {
                    if (arr[i][j].style == "")
                    {
                        for (var k = j; k >= 0; k--)
                        {
                            if (arr[i][k].style != "")
                            {
                                arr[i][j].style = arr[i][k].style;
                                arr[i][j].color = arr[i][k].color;
                                arr[i][k].style = "";
                                arr[i][k].color = "";
                                break;
                            }
                        }                                                
                    }
                }
            }

            this.selection = [];
            this.grid = arr;

        },
        1000
    );
},


Try this. Select a line of eggs. For example, I have selected green eggs. Then click "CONFIRM".




Now there's the fadeout, leaving a gaping hole...




And then you see the eggs at the top have fallen down to fill in the holes, leaving the gaps at the top!




Now use setTimeout() again with a delay of 500 milliseconds. In there, use fillMissingEggs() to fill in the empty elements with random eggs. And then set paused to false so that the "CONFIRM" button appears after the animation is done.
confirm: function()
{
    this.remainingMoves--;

    var arr = this.grid;
    this.paused = true;

    for (var i = 0; i < this.selection.length; i++)
    {
        arr[this.selection[i][0]][this.selection[i][1]].style += " fadeout";
    }

    this.grid = [];
    this.grid = arr;

    setTimeout(
        ()=>
        {
            for (var i = 0; i < this.selection.length; i++)
            {
                arr[this.selection[i][0]][this.selection[i][1]].style = "";
                arr[this.selection[i][0]][this.selection[i][1]].color = "";
            }

            for (var i = 0; i < arr.length; i++)
            {
                for (var j = arr[i].length - 1; j > 0; j--)
                {
                    if (arr[i][j].style == "")
                    {
                        for (var k = j; k >= 0; k--)
                        {
                            if (arr[i][k].style != "")
                            {
                                arr[i][j].style = arr[i][k].style;
                                arr[i][j].color = arr[i][k].color;
                                arr[i][k].style = "";
                                arr[i][k].color = "";
                                break;
                            }
                        }                                                
                    }
                }
            }

            this.selection = [];
            this.grid = arr;

            setTimeout(
                ()=>
                {
                    this.fillMissingEggs();
                    this.paused = false;
                },
                500
            );

        },
        1000
    );
},


No more screenshots. Just try the animations and see if it's working for you.

Next

Scoring, filling up the rainbow, and ending the game!