Website Revamp with WordPress Gutenberg via AWS Lightsail

Website Revamp with WordPress Gutenberg via AWS Lightsail

AWS Lightsail

Dan owns a theatre costume shop called “On Cue Costumes” in Montclair, New Jersey. Like many of my clients, he had an existing website that needed to be modernized. It needed to be redesigned and responsive. This project reminded me a lot of an art website I did a few years ago. It had lots of pages, content, and images that needed to be transferred. And, like that project, I chose to use Amazon Web Services as our cloud provider and WordPress as the content management system. Last time, I installed WordPress and all of the server software manually using AWS EC2. This time I decided to use AWS Lightsail to setup a managed WordPress instance.

selecting wordpress for aws lightsail

This greatly reduced the time it took to get things up and running. It also provides cost predictably (EC2 is pay as you go) and automatic backups. (When running WordPress on EC2 I would run nightly SQL dumps as a redundancy mechanism). A month before starting work on this website I purchased a new domain in my personal AWS account.

When time came to begin working, I created a new AWS account for Dan’s business. Route 53 made it easy to transfer the domain name. Then, I created a new hosted zone for that domain and pointed the A record to the Lightsail instance’s IP address. Set up was easy enough to not need a walk-through. But, just to be sure, I watched a YouTube first. I’m glad I did because the top comment mentioned “Why did you not set a static IP address before creating your A records?”

The documentation mentions that “The default dynamic public IP address attached to your Amazon Lightsail instance changes every time you stop and restart the instance”. To preempt that from being a problem, I was able to create a static IP address and attach it to the instance. I updated the A record to use that new address.

Here is a before shot of the business’s website:

The legacy site, ""
The legacy site, “”

WordPress Gutenberg

Now that the infrastructure was set up, I was able to login to wp-admin. The Lightsail dashboard gives you the default credentials along with SSH access details. I used a premium theme called “Movie Cinema Blocks”. It has an aesthetic that fit the theatre look and made sense for this business.

premium wordpress theme
The original layout provided by the theme

The Gutenberg editor made it easy to craft the homepage with essential information and photos. The theme came with a layout pattern that I adjusted to highlight the content in a meaningful way. In a few places where I wanted to combine existing photos , I used the built in collage utility found in the Google Photos web app.

I created a child-theme after connecting via sFTP and edited the templates to remove the comment sections. I added custom CSS to keep things responsive:

@media(max-width: 1630px){
	.navigation-column .wp-block-navigation{
		justify-content: center !important;
@media(max-width: 1550px){
	.navigation-columns .menu-column{
		flex-basis: 45% !important;

@media(max-width: 1000px){
		 display: none
		flex-wrap: wrap !important;

		flex-basis: 100% !important;
    	flex-grow: 1 !important;
	.navigation-column h1{
		text-align: center;
	.navigation-column .header-download-button{
		justify-content: center !important;
	.navigation-column .wp-block-navigation a{
		font-size: 14px;

@media (min-width: 1000px) {
    .hide-on-desktop {
        display: none !important;

.homepage-posts img{
	border-radius: 10px;
.homepage-posts a{
	text-decoration: none;

@media (max-width: 780px) {
		margin-top: 32px !important;

h6 a{
	text-decoration:none !important;

.entry-content a{
	text-decoration: none !important;

       background-color: white !important;

I kept the existing color palette and I used a free web font, called Peace sans, for the site logo:

@font-face {
  font-family: 'peacesans';
  src: url('') format('truetype');
  font-weight: normal;
  font-style: normal;

.logo-font {
  font-family: 'peacesans', Lexend Deca, sans-serif;

The biggest challenge was importing all of the content from the existing website. We added each costume show as a post and used categories for taxonomy. The “Latest Posts” block in Gutenberg allowed us to showcase the content organized by that classification.

The original website displayed a simple list of all records. It has separate pages only for shows that have images. Others are just listed as plain text. After manually adding all of the hyperlinked content, over one-hundred remained that were only titles.

list of shows

To remove the hyperlinked entries, so that I could copy/paste the rest, I used some plain JavaScript in the browser console:

// Get all anchor elements
const allLinks = document.getElementsByTagName('a');

// Convert the HTMLCollection to an array for easier manipulation
const linksArray = Array.from(allLinks);

// Remove each anchor element from the DOM
linksArray.forEach(link => {

It was easier to copy the remaining entries from the DOM inspector than it was from the UI. But that left me with <li> markup that needed to be deleted on every line. I pasted the result into Sublime Text, used the Selection -> Split into Lines control to clean up all at once, and found an online tool to quickly remove all empty lines.  I saved it as a plain text file. Then, I used a plugin called “WordPress Importer” to upload each title as an empty post.

Contact Form Email

The contact page needed to have a form that allows users to send a message to the business owner. To create the form, I used the “Contact Form 7” plugin. “WP Mail SMTP” let us integrate AWS SES to power the transactional messages. Roadblocks arose during this integration.

Authoritative Hosted Zone

The domain verification failed even though I added the appropriate DKIM CNAME records to the hosted zone I created in this new account. At this point, I still needed to verify a sending address. This business used a @yahoo account for their business email. I decided to use AWS WorkMail to create a simple info@ inbox. (In the past, Google Workspace has been my go-to). This gave me a pivotal clue to resolving the domain verification problem.

aws workmail warning

At the top of the WorkMail domain page, it warned that the domain’s hosted zone was not authoritative. It turns out, that after I transferred this domain from my personal account to the new one the nameserver records continued to point to the old hosted zone. I deleted the hosted zone in the origin account, and updated the NS records on the domain in the new account to point to the new hosted zone. Minutes later, the domain passed verification.

Sandbox limits

The initial SES sandbox environment has sending limits – only 200 per day and only to verified accounts. Since messages were only being sent to the business owner, we could have just verified his receiving email address. The main issue was that the default WordPress admin email address was literally When I tried updating this to a verified address, WordPress would also try to send an email to, causing SES to fail. I requested production access, and in the mean time tried to update that generic admin address in the database directly.

SSH Tunnel and Database Access

When looking at the server files in FileZilla, I noticed that phpMyAdmin was installed. I tried to access it from a web browser, but it warned that it was only accessible from localhost. I set up an SSH tunnel to access phpMyAdmin through my computer. From my Mac Terminal, I commanded:

ssh -L 8888:localhost:80 <your-username>@<your-server-ip> -i <your-ssh-key>

It told me that there was a bad permissions issue with the key file (even though this was the same file I had been using for sFTP). I fixed it from the command line:

chmod 600 onCueCostume-LightsailDefaultKey-us-east-1.cer

Once connected, I could access the server’s installation of phpMyAdmin from this browser URL: `http://localhost:8888/phpmyadmin`

Presented with a login screen, I didn’t know what credentials to use. From the Lightsail dashboard I connected to the server via web terminal. I looked up the MySql password with this command: `cat /home/bitnami/bitnami_credentials`

I assumed the username would be root – but no, it turned out to be user.

Production Access

After requesting production access, AWS responded, “It looks like we previously increased the sending limits for at least one other AWS account that you do not appear to be using. Before we make a decision about your current request, we would like to know why you cannot send from your existing unused account.”

What did this mean? I think it happened because I used my credit card on this new account, before switching it over to the business owner. This probably set off an automatic red flag on their end. Interestingly, the correspondence said “To protect our methods, we cannot provide any additional information about how we identified the related accounts.”

I responded and explained the situation honestly, “I am not aware of another account that I am not using. I do have my own AWS account for my web design business, but it is unrelated to this account – and it is not unused.” Within a few hours, production access was granted.

New Website

The original scope of this project was to build a new website showcasing content that already existed.  We were able to finalize the layout and design quickly. Here is a screenshot of the homepage:

homepage design of a wordpress website

To write this article, I referred to my ChatGPT chat log as a source of journal notes.

WooCommerce Website Fix for a New Client

I met Steven at his store on Bloomfield Avenue in Northern New Jersey. After I gave him my business card he told me his website needs help. The checkout wasn’t working, and users couldn’t even add products to their cart. This was how the previous web development vendor left things before their arrangement ended.

The website was powered by WordPress (managed by Bluehost), and used WooCommerce as its ecommerce solution. I helped him create a Stripe account, and connect it to his online store.  I finished configuring a premium WordPress theme called BeTheme, and gave him a multi-week marketing plan to help sales grow.

website screenshot

I used an image manipulation program (the GIMP) to create graphic assets used throughout the shop:

website graphic design

Many times I have to pick up where someone else left off. I could tell you another story about inheriting a Frankenstein tech stack from a previous vendor. They left off on non-talking terms after demanding back work payments to release the credentials to my team. My skill in figuring things out, regardless of the technology involved, shines in times like these.

My company tag line is “I can build your website” – it should really be “I can fix your website”. Business owners try to do it themselves, and often make it most of the way. When you need help, I am there to carry it over the finish line. I’ve been asked if services like Wix cuts into my business – it’s actually the opposite. Broken, incomplete, or unoptimized websites created on easy-to-use platforms have provided a solid market for my expertise.

Organic market

Local small businesses are what make neighborhoods unique and give families a chance to make a living themselves. It feels great to help people knowing we can both benefit. You can read more about the plan I use to help businesses with their existing website in another blog post.

Membership Discounts Without a Plugin

As part of the marketing plan, we decided to add membership accounts to the WordPress ecommerce website for Organic Sun Market. Enabling that capability was a few settings in the dashboard: WooCommerce > Settings > Accounts & Privacy

woocommerce accounts and privacy settings

I also added a “My Account” link to the site’s global navigation.

menu in wordpress

By default, WooCommerce provides a “My Account” page where users can log in, view their orders, update their information, and more. You can specify a custom page in the advanced settings: WooCommerce > Settings > Advanced

woocommerce advanced setting

The account page specified uses a WooCommerce short code to handle the content: [woocommerce_my_account]

account page shortcode

Change menu text if user is logged into WordPress

I wanted the “My Account” menu text to change if the user is not logged in. I was able to do this with the WordPress hook `wp_nav_menu` and a simple string replacement PHP function:

add_filter('wp_nav_menu', 'change_my_account_menu_item', 10, 2);

function change_my_account_menu_item($nav_menu, $args) {
// Check if the user is not logged in
    if (!is_user_logged_in()) {
        // Change "My Account" link to "Login/Register"
        $nav_menu = str_replace('My account', 'Login/Register', $nav_menu);
    return $nav_menu;

To incentivize users to create an account, we offer a 5% discount to any one logged in. The checkout page contains conditional messaging (depending on wether they are logged in or not) to communicate this incentive.

conditional css messaging on checkout

Hide or show UI elements if user is logged into WordPress

I am able to apply that  style condition with two custom CSS classes, specific to the presence of the WordPress body class ‘logged-in’:

.only-show-while-logged-in{display: none;}
body.logged-in .dont-show-while-logged-in{display:none;}
body.logged-in .only-show-while-logged-in{display:block;}

Apply WooCommerce discount to logged in users

I applied the discount by using custom PHP code in the child theme’s functions.php file with the `woocommerce_before_calculate_totals` hook:

add_action( 'woocommerce_before_calculate_totals', 'no_discount_if_not_logged_in', 10, 1);
function no_discount_if_not_logged_in( $cart ) {
	if (is_user_logged_in()) {              
		foreach ( $cart->get_cart() as $cart_item ) {        
			$discount_eliminate = $cart_item['data']->get_regular_price();
			$discount_percentage = 5; // Set your desired discount percentage
			$discount_amount = $discount_eliminate * ($discount_percentage / 100);
			$new_price = $discount_eliminate - $discount_amount;


Apply WooCommerce discount to logged in users on a specific category of products

Later, we changed the logic to be a 10% discount for logged-in members, but only on products that were part of a specific category called “bundles”.

add_action( 'woocommerce_before_calculate_totals', 'discount_for_specific_category', 10, 1);

function discount_for_specific_category( $cart ) {
    if ( is_user_logged_in() ) {
        // Define the category slug you want to apply the discount to
        $target_category = 'bundles';

        foreach ( $cart->get_cart() as $cart_item ) {
            $product_id = $cart_item['product_id'];

            // Check if the product belongs to the target category
            if ( has_term( $target_category, 'product_cat', $product_id ) ) {
                $discount_eliminate = $cart_item['data']->get_regular_price();
                $discount_percentage = 10; // Set your desired discount percentage
                $discount_amount = $discount_eliminate * ( $discount_percentage / 100 );
                $new_price = $discount_eliminate - $discount_amount;

                $cart_item['data']->set_price( $new_price );

Print Design

Many local small businesses take their marketing offline and into the real world. Print marketing is a business I have been a part of for almost two decades. I have designed, delivered, and distributed flyers, menus, business cards and more. As the holiday season approached, Steven asked me to create a poster for one of his healthy products.

graphic design request via text message

He sent me a draft he has been working on, along with some inspiration examples that expressed the direction he wanted things to go. This was the final product:

Dog treats poster

And here it is hanging in the store front:

Printed poster design

Don’t Be Invisible: A Website Puts Your Small Business on the Map

small business websites

Local small businesses without a brick-and-mortar presence fall into a unique category. They might be service-based businesses like freelance consultants (like myself), home repair services, personal trainers, or cleaning services that operate on a mobile basis or from a home office. These businesses rely heavily on word-of-mouth, local advertising, and community networking to attract and maintain their clientele.

Because this business model does not require a physical storefront, these owners may underestimate the value of a digital presence. They might perceive it as unnecessary, believing that their local reputation and personal customer relationships are sufficient for business growth and sustainability. Consequently, they may neglect their online visibility, not realizing the potential reach and efficiency gains from digital tools.

The digital gap for these businesses can be characterized by a lack of a professional website, minimal social media engagement, and reliance on outdated forms of communication like AOL or Gmail email addresses. While this might maintain a certain level of operation, it limits their ability to scale, reach new markets, and ultimately leaves honey on the table.

By not leveraging the digital space, these businesses miss out on the opportunity to build brand awareness beyond their immediate locality, engage with customers online, and streamline their operations through digital tools (customer management). As a result, they might struggle to compete with others who adopt a more integrated approach to physical and digital business practices.

Cleaning website design

Professional Image: Elevate Your Brand

Your website can be the first point of contact between your business and potential customers. Just as you wouldn’t attend a party in pajamas, you shouldn’t let your online presence be represented by a dated AOL email address or non-existent web page. A sleek, user-friendly website tells customers that you are a serious professional who invests in all aspects of your business. I can help you do it right with a custom domain and business email address.

Some businesses rely on on a third-party subdomain or a link-in-bio service. While these options may seem convenient and cost-effective in the short term, it looks unprofessional. It is harder for customers to remember and doesn’t carry the same weight of brand authority as a standalone domain. It’s like to setting up shop in someone else’s store.

Using a personal or email address for business communications can inadvertently signal a lack of professionalism and an outdated approach to business. I can assist you in transitioning to a custom domain email that reinforces your credibility. Too often, I walk around and see businesses make the mistake of thinking this detail doesn’t matter.

Testimonials & Social Validation

Word-of-mouth is powerful, but in the digital age, testimonials and portfolios on your website can reach further and speak louder. They serve as a perpetual source of validation for your work, allowing potential customers to see the breadth and quality of your services at any time. It’s the online equivalent of a recommendation from a trusted friend, accessible to everyone, everywhere.


Having a logo that fits your business image goes beyond digital space. I can show you tips and details for getting it right and making work across online  and print platforms.

A sample web design logo

Organic & Paid Search (Unclaimed Digital Territory)

If you’re running a business without a website and have been relying solely on traditional methods, it’s time to unlock new opportunity. Customers are searching for your service right now!

Organic search refers to the natural listings on search engine results pages (like Google or Bing) that appear because of their relevance to the search terms, as opposed to being advertisements. By not having a website, you’re missing out on the chance to appear in these listings—a place where a significant portion of your potential customers start their journey.

And, this is another reason to get setup with your own domain name if you’re currently using something like Bitly. Search engines like Google give more credibility to websites with a clear, branded domain name, which can significantly impact your search rankings and, by extension, your visibility to potential customers.

On the flip side, paid search advertising allows your website to jump to the top of search results by paying for prime placement. Paid search campaigns through platforms like Google Ads can be tailored to target the exact demographic you want to reach, with the ability to adjust for location, language, and more.

If you’re ready to explore the untapped potential of online search, let’s chat. I’m here to guide you through every step of the journey.

Who needs a website?

A website is your digital calling card and can be a sales generator if we do it right. Here are some of the top industries that I’ve helped grow online:

  • Restauraunt websites: Enjoy commision-free online ordering. With enticing designs and easy navigation, your patrons can effortlessly browse menus, book tables, and more.
  • E-commerce websites: Expand your business horizons by selling online.
  • Wedding websites: Share your love story, manage RSVPs, provide event details, and create lasting memories for you and your guests.
  • Martial arts school websites: Enroll students online, share class schedules, highlight events, and build a digital community.
  • Real Estate websites: Highlight property listings with interactive galleries, virtual tours, and advanced search filters.


Newsletter Signup with HubSpot API

hubspot api

I do a lot of in-person prospecting to win new business. I used to focus on giving out my business card, and then hoping the potential client would reach out to me. I learned that its better to capture their information, and then follow up. I used to collect so many business cards. Now I use a CRM, Customer Relationship Manager, called HubSpot. It does a lot of things, but primarily it helps to manage contacts. I want to let users sign up to my newsletter, and add themselves as a HubSpot contact, directly from my website.

hubspot forms

Create a Custom Newsletter Sign Up Form on Your Website Powered by HubSpot

Out-of-the-box, HubSpot can generate a number of different web forms that can be easily embedded into any website. These forms are used to capture leads and contacts.

HubSpot Private App

My website (this website) is mostly hand-coded. I want to build my own custom form, and submit the data to the HubSpot service. This is possible with the “Private app” feature. You can find this under ‘Settings -> Integrations -> Private Apps’. This strategy acts as a work-around to remove the HubSpot logo from forms without having to upgrade to premium.

HubSpot Private Apps

After entering a name and description for your “app”, you’ll need to select permission scopes. I called mine “Antpace-Website” and described it “signup forms on”. The only scope is gave it was called `crm.objects.contacts.write`

hubspot permission scopes

CRM API – Create Contact

To create a contact programmatically through the HubSpot API, we use our private app’s access token. In my website’s HTML, I create a simple form:

<div class="col-md-12">
    <h1 style="text-align: center;">Newsletter</h1>
    	<p>Joining the mailing list!</p>
        <form id="hubspotForm" class="styled-form">
            <input type="hidden" value="subscriber" name="lifecyclestage">
            	<div><input type="email" placeholder="Email *" value="" id="hubspot-email" name="email" class=""></div>    
                <div><input type="text" placeholder="First Name" name="firstname" value="" class=""></div>
                <div><input type="text" placeholder="Last Name" name="lastname" value="" class=""></div>        
                <div><input type="tel" placeholder="Phone" value="" name="phone" class=""></div>            
                <div><input type="text" placeholder="Company" value="" name="company" class=""></div>            
                <div><input type="text" placeholder="Website" value="" name="website" class=""></div>            
                <div><button type="button" id="signupButton" class="btn">Sign Up</button></div>

I add basic jQuery JavaScript to pass the form data along to my backend service when the button is clicked:

	const notifications = new UINotifications();
		const email = $("#hubspot-email").val();
		if(email.length < 1){
			notifications.showStatusMessage("An email address is required.");
		const formData = {
			properties: {}

		// Iterate over form fields and add them to formData
		$("#hubspotForm input").each(function() {
			const fieldName = $(this).attr("name");
			const fieldValue = $(this).val();[fieldName] = fieldValue;
			type: "POST",
			data: JSON.stringify(formData),
			url: "/hubspot-service.php",
				notifications.showStatusMessage("Thank you for signing up.");


The HubSpot API documentation says, “To create new contacts, make a POST request to /crm/v3/objects/contacts“. Some simple PHP cURL commands accomplishes this. Here is the content of hubspot-service.php:


$url = '';
$accessToken = 'xxx';

$headers = array(
    'Authorization: Bearer ' . $accessToken,
    'Content-Type: application/json',

$postData = file_get_contents("php://input");

$ch = curl_init($url);

curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'Error: ' . curl_error($ch);

echo $response;


You can see the end result by visiting my newsletter sign up page. Make sure you add yourself so you can stay up-to-date with technology tips for business.

Newsletter sign up page

I can use this same PHP service file for other HubSpot email sign up forms throughout my website.

Magic Squares in JavaScript

magic squares in javascript

magic square

In mathematics, a “magic square” is a matrix of numbers where each row, column, and diagonal add-up to the same number. That number is called the “magic constant“. The integers used are only positive, and do not repeat. The constant sum is determined by the size of the square and is described by a formula:

M = n * ((n^2 + 1) / 2)

Facts about the properties and classifications of these numeric formations have been discussed by scholars for millennia. Knowledge of this topic goes back thousands of years, and can be found referenced throughout the world.

History & Culture

Magic squares have a fascinating historical and cultural significance, often with mystical undertones. They are mentioned in the I Ching, Brhat Samhita, and other works concerned with the transcendental and occult. They can be seen used in art, divination, perfumery, recreational gaming, computer science, and more.

a computer programmer using the Brhat Samhita to generate a talismans
AI images generated using Midjourney

In the Brhat Samhita, the magic square is used as a symbolic representation of the planets. It makes use of magic squares in the creation of talismans for astrological purposes.

For the purposes of this blog post, we’ll view them through the lens of software engineering.

3×3 Magic Squares

There’s so much to cover on this topic. I’ll narrow it down to 3×3 lattices (magic squares can actually be any size), specifically in the context of the JavaScript programming language. A quad of numbers, like the one pictured above, can be described as a two-dimensional array:

const magicSquare = [[4, 9, 2], [3, 5, 7], [8, 1, 6]];

Squares of this size always have a magic constant of 15. And, the number 5 will be in the middle. There’s additional logic that explains which numerals can appear where and why. Those ideas are explored in the comments section of a HackerRank coding challenge titled “Forming a Magic Square”.

HackerRank Coding Challenge

This coding problem found on HackerRank asks programmers to figure out what it would take to convert a 3×3 matrix of integers into a magic square. The input array is almost valid, but requires a few replacements. For each change, we must track the difference between the numbers and return the total variance – referred to as the “cost”. The correct answer will be the lowest cost required to convert the input data into a magic square.

hackerrank coding challenge

The difficulty of this assignment is considered “medium”. The first step is realizing that there are a finite number of valid magic square configurations. As it turns out, there are exactly eight 3×3 permutations:

const magicSquares = [[[4, 9, 2], [3, 5, 7], [8, 1, 6]], 
                [[6, 1, 8], [7, 5, 3], [2, 9, 4]], 
                [[8, 1, 6], [3, 5, 7], [4, 9, 2]], 
                [[2, 9, 4], [7, 5, 3], [6, 1, 8]], 
                [[8, 3, 4], [1, 5, 9], [6, 7, 2]], 
                [[4, 3, 8], [9, 5, 1], [2, 7, 6]], 
                [[6, 7, 2], [1, 5, 9], [8, 3, 4]], 
                [[2, 7, 6], [9, 5, 1], [4, 3, 8]]];

Starting with any one of these, we can generate the other seven programmatically. The subsequent arrangements can be derived through rotation and reflection. Using JavaScript, I rotate an initial seed square three times to have the first four series. Then, I flip each one of those to get the final records.

function generateMagicSquares(magicSquare1){
	const magicSquares = [];

	// we need to rotate it 3 times to get all rotations
	for(let i = 0; i < 3; i++){
		var rotation = magicSquares[i].map((val, index) => magicSquares[i].map(row => row[index]).reverse());
		// console.log(rotation)

	// and then flip each one
	for(let i = 0; i < 4; i++){
		var flipped = magicSquares[i].map((_, colIndex) => magicSquares[i].map(row => row[colIndex]));
	return magicSquares;

const magicSquare1 = [[4, 9, 2], [3, 5, 7], [8, 1, 6]];
const magicSquares = generateMagicSquares(magicSquare1);

To solve this exercise, we’ll take the input array and compare it to each of the valid magic squares. We keep track of the cost on each iteration, and finally return the minimum.

function formingMagicSquare(s){
	const magicSquares = [[[4, 9, 2], [3, 5, 7], [8, 1, 6]], [[6, 1, 8], [7, 5, 3], [2, 9, 4]], [[8, 1, 6], [3, 5, 7], [4, 9, 2]], [[2, 9, 4], [7, 5, 3], [6, 1, 8]], [[8, 3, 4], [1, 5, 9], [6, 7, 2]], [[4, 3, 8], [9, 5, 1], [2, 7, 6]], [[6, 7, 2], [1, 5, 9], [8, 3, 4]], [[2, 7, 6], [9, 5, 1], [4, 3, 8]]];
	// let minCost = 100000;
	let minCost = Number.MAX_SAFE_INTEGER;
	let cost = 0;
	for(let i = 0; i < magicSquares.length; i++){
		cost = determineCost(s, magicSquares[i]);
		if(cost < minCost){
			minCost = cost;
	return minCost;

You’ll notice that the initial minimum cost is set to a very high number. As I loop through each of the valid magic squares, I check if the cost is lower than the current minimum and then replace the value.

With each comparison, subtraction is used to determine the cost to complete the transformation. That code loops through each digit of each row on both 2D arrays. The absolute value of the difference between each coordinate is tallied and returned.

function determineCost(inputArray, validMagicSquare){
	let cost = 0;
	for(let i = 0; i < 3; i++){ // each row
		for(let j = 0; j < 3; j++){ // each digit

			cost += Math.abs(inputArray[i][j] - validMagicSquare[i][j]);
	return cost;

The working code all stitched together looks like this:


function generateMagicSquares(magicSquare1){
	const magicSquares = [];

	// we need to rotate it 3 times to get all rotations
	for(let i = 0; i < 3; i++){
		var rotation = magicSquares[i].map((val, index) => magicSquares[i].map(row => row[index]).reverse());
		// console.log(rotation)

	// and then flip each one
	for(let i = 0; i < 4; i++){
		var flipped = magicSquares[i].map((_, colIndex) => magicSquares[i].map(row => row[colIndex]));
	return magicSquares;

function determineCost(inputArray, validMagicSquare){
	let cost = 0;
	for(let i = 0; i < 3; i++){ // each row
		for(let j = 0; j < 3; j++){ // each digit

			cost += Math.abs(inputArray[i][j] - validMagicSquare[i][j]);

	return cost;


function formingMagicSquare(s){
	// const magicSquares = [[[4, 9, 2], [3, 5, 7], [8, 1, 6]], [[6, 1, 8], [7, 5, 3], [2, 9, 4]], [[8, 1, 6], [3, 5, 7], [4, 9, 2]], [[2, 9, 4], [7, 5, 3], [6, 1, 8]], [[8, 3, 4], [1, 5, 9], [6, 7, 2]], [[4, 3, 8], [9, 5, 1], [2, 7, 6]], [[6, 7, 2], [1, 5, 9], [8, 3, 4]], [[2, 7, 6], [9, 5, 1], [4, 3, 8]]];
	const magicSquare1 = [[4, 9, 2], [3, 5, 7], [8, 1, 6]];
	const magicSquares = generateMagicSquares(magicSquare1);
	// let minCost = 100000;
	let minCost = Number.MAX_SAFE_INTEGER;
	let cost = 0;
	for(let i = 0; i < magicSquares.length; i++){
		cost = determineCost(s, magicSquares[i]);
		if(cost < minCost){
			minCost = cost;
	return minCost;

const finalCost = formingMagicSquare([[4, 9, 2], [3, 5, 7], [8, 1, 5]]);

This solution was not intuitive to me and took some research and experimentation. It was interesting to learn about the concept of magic squares (and other shapes) along the way.

HackerRank challenge completed

CSS Grid: Border Between Each Row

CSS Grid borders

User Interface Layout with CSS

CSS Grid is a layout system that allows you to design web interfaces with rows and columns. In earlier times, developers used less elegant techniques to arrange UI elements. In the 1990s, using HTML tables was the standard way. Many of my earliest clients used tables for their entire website and email newsletter code bases. During that time Dreamweaver was a popular product that let designers build websites and generated source code that was mostly tables. The semantic problem what this approach was that we were using tables for non-tabular data.

By the 2000s, the CSS float property became popular for multi-column layouts. We didn’t see Flexbox until the mid 2010s. You can read about examples of its use in the layout of my blog  and an image carousel I built.

CSS Grid

Grid is the latest addition to the toolbox of options. For a recent project I used CSS Grid to create a two column web application. Here is some example HTML:

<h1>Below is a grid of data</h1>
<div style="display: grid; grid-template-columns:repeat(2, 1fr); grid-gap: 24px">
	<div>Row 1, Column 1</div>
	<div>Row 1, Column 2</div>

	<div>Row 2, Column 1</div>
	<div>Row 2, Column 2</div>

	<div>Row 3, Column 1</div>
	<div>Row 3, Column 2</div>

The grid container has a property: `grid-template-columns:repeat(2, 1fr);`. The CSS property grid-template-columns is used to define the number and size of columns in a CSS Grid layout. This repeat() function is used to repeat a pattern a specified number of times. In this case, the pattern is defined by the second argument, 1fr. fr stands for “fractional unit.” This property is describing two columns, with each item taking up an equal amount of space. This is what it looks like:

grid css layout

I wanted a horizontal line to separate each row. Usually, just adding a border-bottom to each of the grid items will work. In this scenario, the grid design called for a grid-gap property that forced the border to break.

css grid-gap

We fixed this by adding a new grid item between each “row” (that is, every third element). Those elements would represent each border line. I set the grid-column property of those elements to span two columns.

		border-bottom: 2px solid #333;
		grid-column: span 2;

<h1>Below is a grid of data</h1>
<div style="display: grid; grid-template-columns:repeat(2, 1fr); grid-gap: 24px">
	<div class="gridItem">Row 1, Column 1</div>
	<div class="gridItem">Row 1, Column 2</div>

	<div class="gridItemBorder"></div>

	<div class="gridItem">Row 2, Column 1</div>
	<div class="gridItem">Row 2, Column 2</div>

	<div class="gridItemBorder"></div>

	<div class="gridItem">Row 3, Column 1</div>
	<div class="gridItem">Row 3, Column 2</div>

The end result is just what we wanted:

css grid layout with row separators

Web Development for Freelance Clients

freelance web dev process

When I win a new web development client I follow a structured progress. This ensures high quality results. Years of refinement and experience continue to define how I work with people and businesses to help them succeed.


This is the first step. Communication is key.

This initial discussion allows us to explore your project’s overarching goals. It’s a high-level chat to understand your aspirations and ensure our visions align. We’ll delve into:

  • Nature of the Project: Is this a revamp of an existing platform or the birth of something entirely new?
  • Scope of Work: What exactly are we building? It could range from a website, a dynamic web application, an eCommerce storefront, to other web-based projects.
  • Branding Elements: Have you established a brand identity already? Do you possess logo designs or specific themes you want incorporated?
  • Budget Considerations: A transparent conversation about the budget ensures we shape the project according to the resources available.
  • Project Goals: What do you hope to achieve with this project? Understanding your objectives ensures we’re steering in the right direction from the get-go.

After our conversation, we’ll examine any pre-existing materials that are pertinent. Perhaps you have an existing website that needs sprucing up or logos that you’d love incorporated. Alternatively, you might point to other inspiring sites or platforms, even if they’re not yours, to give a clearer picture of your vision. This step is all about gathering resources and setting a concrete foundation.

Based on our dialogue and the data collated, I’ll recommend the best technologies and tools. This could encompass anything from choosing the right content management system, database technologies, or frontend frameworks.


Step two. Every element discussed in our initial conversation is now put down in black and white. It’s our chance to reiterate the goals and ensure we’re on the same page. This documentation becomes a reference point, ensuring we stay aligned with the project’s vision.

We detail objectives, decide on the most fitting technology stack, outline the budget, and establish a clear timeline.


As we progress through this building phase, you’ll have access to a private server for reviews and approvals. Everything—from the user-friendly experience to backend configurations like domain setup, security measures, and CMS installation—is shaped by our prior discussions, ensuring both aesthetics and functionality are on point.


During the website’s initial build, I typically use placeholder content. With the design and layout set, it’s your cue to provide the actual content. While you can opt to share materials earlier, this phase is ideal. And rest assured, you’ll retain full control to modify content as needed later on.

Quality Review

Before launching, I rigorously test the product across various devices, browsers, and screen dimensions to ensure quality. This phase is dedicated to refining the detail, ensuring both appearance and functionality are flawless.

Final Touches

I ensure that your website is fully optimized for SEO, integrating structured data, sitemaps, and relevant keywords. By enhancing its security, mobile responsiveness, and speed, your SEO score will naturally improve. Further bolstering this, I can register your site with Google Search Console and activate AMP (Accelerated Mobile Pages) to amplify its prominence in Google searches. Additionally, I’m here to guide you in devising a content strategy tailored to elevate your online visibility.

For those seeking email hosting, I offer solutions tailored to your needs, with Google Workspace being a top recommendation for those starting afresh. When it comes to choosing a CMS, WordPress or Shopify are often top picks for businesses.

Beyond these services, I specialize in web design, crafting a visual identity that seamlessly embodies your brand. And rest assured, user experience is a top priority; I design ensuring visitors experience positive, smooth interactions across your digital platform.

This framework is described and outlined on another page of this website. It is what I use when I engage a client that has an existing website that might need maintenance or improvements.

Binary Search in JavaScript

binary search in javascript code

“Binary search” is a computer science term used in programming and software engineering. It is a way of determining if a value exists in an ordered data set. It’s considered a textbook algorithm. It is useful because it reduces the search space by half on each iteration.

Imagine we are asked to search for an integer in an array that is sorted in ascending order. The challenge is to return the target number’s index.

binary search

We could use a built in JavaScript function indexOf():

var search = function(nums, target) {
    var answer = nums.indexOf(target);
    return answer;
console.log(search([-1,0,3,4,5,9,12], 5)); // returns '4'

But that is an abstraction, which can be slow and expensive. Instead, we should implement our own binary search code.

Binary Search Implementation

Binary search starts in the middle of a collection, and checks if that is the requested term. The initial middle position is determined by taking the length of the array and dividing it by two. If the array has an even number of elements, then the ‘halfway position’ is considered close enough. I use a JavaScript Math function to round down, making sure I am not left with a decimal floating number:

cursor =  Math.floor( ((left + right) / 2) ); // about the middle

If the search term is not found, the position (the cursor) is moved. Since the collection is ordered from smallest to largest we know which direction to move the search cursor. If the target is less than the current value, we shift one to the left. If the target is greater than the current value, we shift two to the right.

This continues until the target query is found. That sequence is ran inside of a loop. The loop repeats while the left boundary is less than (or equal to) the right. The search cursor’s index is changed by adjusting the left or right bounds. Once adjusted, the cursor is recalculated on the subsequent iteration.

var search = function(nums, target) {
    var left = 0; // first element, initial left boundary
    var right = nums.length;
    right = right - 1; // last element, initial right boundary
    var cursor = 0;
    while(left <= right){
        cursor =  Math.floor( ((left + right) / 2) ); // about the middle to start
        console.log(cursor + ": " + nums[cursor]);
        if(nums[cursor] === target){
            return cursor; // query found!
        if(target < nums[cursor]){
            right = cursor - 1; // move cursor to the left by one
            console.log("cursor moved to the left  by one")
            left = cursor + 1; // move cursor to the right by two
            console.log("cursor moved to the right by two")
    return "false"; // not found
console.log(search([-1,0,1,2,3,4,5,6,7,8,9,10,11,12], 10));

The above code starts to search in the central location, nums[6] == 5. Look at the output below to see how the cursor moves until it finds the target value of 10 (at a zero-based index of 11):

binary search output

This algorithm has an O(log n) runtime complexity. That Big-O notation describes how it would scale as the input increases. Imagine an array with thousands of elements. O(log n) means that the time to execute this code will increase linearly only as the number of input items increase exponentially. That means our code is performant and efficient.

Why did the computer scientist use binary search to find his lost pencil? Because he wanted to reduce the search space by half every time he checked a desk drawer!

Square root algorithm using binary search

Another implementation example of using binary search in a JavaScript function is to calculate the square root of a number. Now, of course, Javascript has a built in Math.sqrt() function that is highly performant compared to any custom code we will write. But, it is a good exercise to think about how this works.

The square root of a number is a value that, when multiplied by itself, gives the original number. To solve for it when given an input, we are going to take a guess at what the answer (square root) is, starting with itself divided by two (giving us the midpoint between it and zero). We multiply that midpoint by itself to check our guess. It will only be correct on the first iteration if the input is the number four (four divided by two is two; two times two is four). Otherwise, we will move the cursor (halving the search range) to either the left or the right, depending on if the square of our guess is larger or smaller than the original input.

The concept of “moving the cursor” helps to visualize how the binary search algorithm zeroes in on the square root by iteratively narrowing down the search range based on the comparison of the square of the midpoint to the target number.

I accounted for two special cases in my code. For negative numbers we return false because negative numbers have imaginary square roots. For numbers smaller than one we set the range’s end to one because the square root of a fraction is larger than the fraction itself. Additionally, I set a precision threshold (0.0000000001), because some numbers have square roots that are irrational.

function binarySearchSquareRoot(n, precision = 1e-10) {
    if (n < 0) {
        throw new Error('Input must be a non-negative number');

    let start = 0;
    let end = n;

    // If the number is less than 1, set end to 1 to handle fractions
    if (n < 1) {
        end = 1;

    while (end - start > precision) {
        const mid = (start + end) / 2;
        const midSquared = mid * mid;

        if (midSquared === n) {
            return mid;  // Found exact square root
        } else if (midSquared < n) {
            start = mid;
        } else {
            end = mid;

    return (start + end) / 2;  // Return approximate square root

// Usage:
const number = 25;
const squareRoot = binarySearchSquareRoot(number);
console.log(`The square root of ${number} is approximately ${squareRoot}`);

Alternatively, the Babylonian algorithm can be  implemented to solve for square roots.

Convert Images to WebP to Improve Website Performance

upgrading image files

In a recent post I discussed auditing existing websites for potential performance enhancements. In the process I discovered issues with the Lighthouse estimates (a topic I covered briefly in a post about progressive web apps).

Lighthouse performance estimate

My performance only scored 50 out of 100. One “opportunity” (estimated to save 2.16s) was to “serve images in next-gen formats”. AVIF (based on the AV1 video codec) and WebP are considered next generation because of “superior compression and quality characteristics compared to their older JPEG and PNG counterparts“. I chose to upgrade my images WebP (instead of AVIF) mostly due to greater browser support.

Before I upload images to this site, I usually run them through TinyPNG. Sometimes, I’ll also do some manual pre-processing in the GIMP, like cropping or scaling images down. I know that there  must be a better way to do all of that through a proper CI pipeline – but for now that is my low tech solution. Now that I need to come up with a new process for next-gen image formats, it might be a good time to explore a more automated solution.

Convert multiple images to next-gen WebP file format

For converting single images, manually, one at a time I could use the GIMP or an online service. I used a command line tool on my mac called webp tools to bulk upgrade all of the files in an image directory.

brew install webp

For a single file I can use this command:

cwebp -q 80 software-education.png -o software-education.webp

To hit all of my image files in that folder I ran loops over the existing file formats. You should change this to include any other file formats used in your project.

for i in *.jpg; do cwebp -q 80 "$i" -o "${i%.jpg}.webp";done
for i in *.png; do cwebp -q 80 "$i" -o "${i%.png}.webp";done

I saved them to a new folder, so that I could easily delete all of the old files at once and then replace them. Alternatively, I can save them in the same directory and then run a command to delete all of the .png files: rm *.png

New folder amongst legacy image files

My website has been around for a while, so there are unused legacy media files. I manually searched my code base (ctrl + shift + f) for the file names listed in the various /image directories throughout my project, and deleted them if I found no references. (Maybe having so many disjointed image folders is part of a bigger problem with this project). If I did find it, I updated the file extension to webp. Later, I wrote a script to automatically find and delete unused image files from a project.

This change increased my Lighthouse performance score by ten points (I gained an additional two but upgrading MariaDB from version 10.2 to 10.4). I did this process is a few other directories of my project to complete the upgrade. I was going to convert my Apple touch icon files to a next-gen format too, but the documentation specify the use of the PNG format (good thing I checked).

WordPress and WebP

Having adopted WebP as the new standard file format for, I decided to upload .webp images to my posts – starting with this one. When I tried, WordPress complained: “This image cannot be processed by the web server. Convert it to JPEG or PNG before uploading.”

error from wordpress when trying to upload a next generation image file

This surprised me. I upgraded WordPress to the latest version (6.3.1 at that time), but it didn’t help. After further investigation, it looked like the problem had to do with the PHP image module gd.

I SSH’d into my EC2 instance to see what I could do. When I tried to install gd for my PHP version 7.4, I got a version mismatch error. It had to due with Amazon Linux 2 (the OS I run on AWS EC2) not supporting PHP 7.4 as it approaching or have passed their end-of-support dates. Every time I tried to installthe gd module, Amazon Linux Extras (the default Amazon Linux 2 package mechanism) would try to pull the version compatible with PHP 7.2. In an attempt to make it work, I manually disabled ‘amazon-linux-extras’, installed the Remi repository and made sure it was prioritized as my package manager. Still, “Packages skipped because of dependency problems”.

Amazon Linux 2 is at EOL.

The same thing happened when I tried using ImageMagick instead. This made me consider my Linux distribution. Not having gd installed what causing other problems when uploading media through WordPress (responsive image sizes are not being generated).

I had been wanting to upgrade the size of my EC2 instance anyway, so this might be the right time. I am considering Amazon Linux 2023 or a Bitnami image.  As you know, I’ll write a blog post about which I choose and the implementation details