Secure Your Website: Implementing reCAPTCHA on Contact Forms

reCAPTCHA for Contact Forms to Enhance Website Security

I first explored reCAPTCHA for this website’s contact form. After getting too many spam messages, I decided to add some protection. It is Google’s implementation of CAPTCHA (reimagined) and has a long history.

The Turing Test is a concept introduced by the British mathematician and computer scientist Alan Turing in 1950 as a way to evaluate a machine’s ability to exhibit intelligent behavior indistinguishable from that of a human. The test is named after Alan Turing, who proposed it in his paper titled “Computing Machinery and Intelligence.” The history of CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) traces back to the late 1990s when researchers began exploring ways to distinguish between humans and automated scripts or bots online.

Google’s reCAPTCHA service requires you to register the website you’re going to use it on:

register a site recaptcha

 

It provides “Up to 1,000,000 assessments/month at no cost”. Once registered you’ll get a site key (for the client side) and a secret key (for the server side).

PHP & JavaScript Implementation

You can use the keys to implement the Google reCAPTCHA service on a vanilla website. In your HTML contact form, you need to add the site key as an empty `<div> element’s `sitekey` data attribute:

<form id="contactForm">
    <div class="">
        <div><input type="text" placeholder="Name" name="name" value="" class=""></div>
        <div><input type="email" placeholder="Email" value="" name="email" class=""></div>            
        <div><textarea placeholder="Message" rows="3" name="message" id="contactMessage" class=""></textarea></div>
        <div class="g-recaptcha" data-sitekey="XX-SITE-KEY-HERE-XX"></div>
        <div><button type="button" id="contactMe" class="btn">Send</button></div>
    </div>
</form>

The `.g-recaptcha`  is transformed to a hidden input and visible user interface by Google’s JavaScript API. The UI is a checkbox that humans can click, but bots will miss. CAPTCHA mechanisms typically present users with tasks that are easy for humans to solve but difficult for automated scripts or bots.

contact form with reCAPTCHA

CAPTCHA challenges can take various forms, such as:

  1. Text-based CAPTCHA: Users are asked to transcribe distorted or obscured text displayed in an image.
  2. Image-based CAPTCHA: Users are prompted to identify objects, animals, or characters within a series of images.
  3. Checkbox CAPTCHA: This is what our example here is using. Users are asked to check a box to confirm that they are not a robot. This can be combined with additional checks, such as analyzing mouse movements or browsing patterns, to verify user authenticity.
  4. Interactive CAPTCHA: Users are presented with interactive puzzles or challenges, such as rotating objects or dragging and dropping items into specific locations.

I defer the loading of that script using lazy loading:

function captchaLazyLoad(){
	contactCaptchaTarget = document.getElementById('contactSection')
	if (!contactCaptchaTarget) {
        return;
    }
	let contactCaptchaObserver = new IntersectionObserver(function(entries, observer) {
		if (entries[0].isIntersecting) {
            var script = document.createElement('script');
		    script.src = "https://www.google.com/recaptcha/api.js";
		    document.body.appendChild(script);
            contactCaptchaObserver.unobserve(contactCaptchaTarget);
        }
	})
	contactCaptchaObserver.observe(contactCaptchaTarget);
}
captchaLazyLoad();

The assessment value is serialized with the form data and passed along via a RESTful AJAX POST request to the back-end service:

var notifications = new UINotifications();
$("#contactMe").click(function(){
	var contactMessage = $("#contactMessage").val();
	if(contactMessage.length < 1){
		notifications.showStatusMessage("Don't leave the message area empty.");
		return;
	}
	var data = $("#contactForm").serialize();
	$.ajax({
		type:"POST",
		data:data,
		url:"/contact.php",
		success:function(response){
			console.log(response)
			if(response === "bot"){
				notifications.showStatusMessage("Please confirm your humanity.");
				return;
			}
			notifications.showStatusMessage("Thank you for your message.");
			$("form input, form textarea").val("");					
		}
		
	});
});

The receiving PHP file takes the reCAPTCHA assessment value, combined with our secret key, and passes them along to Google’s “site verify” service:

$secret = "XX-SERCET-KEY-XX";
$captcha = $_POST["g-recaptcha-response"];
$verify=file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret={$secret}&response={$captcha}");
$captcha_success=json_decode($verify);
if ($captcha_success->success==false) {
	echo "bot";
	die();
}

If it is a bot, we `die()`.

reCAPTCHA v3 (2018) and WordPress Contact Form 7

In 2018, Google introduced reCAPTCHA v3, a version of CAPTCHA that works invisibly in the background to assess the risk of user interactions on websites. reCAPTCHA v3 assigns a risk score to each user action, allowing website owners to take appropriate action based on the perceived risk level.

A recent WordPress client messaged me “is there anyway NOT to receive anywhere from 5-10 of these a day. ” He was referencing spam messages coming from his website’s contact form. His contact page uses Contact Form 7, a popular WordPress plugin that allows website owners to easily create and manage contact forms.

I was able to easily integrate reCAPTCHA v3 by installing a 3rd party plugin. After installation, I was able to enter the site and secret keys into the WordPress dashboard. Version 3 does not require users to check any boxes or solve any challenges – it is seamless and invisible.

Engaging Website Elements: Add Typing and Backspace Effects with JavaScript

javascript typewritter

On the homepage of my website, I wanted to add an animated effect that appears to backspace some text, and type out new text. Here is a video of the final product:

There are basic JavaScript tutorial snippets that can type things out:

var currentIndex = 0;
var text = 'This is a typing effect!'; /* The text */
var typingSpeed = 50; /* The speed/duration of the effect in milliseconds */

function typeWriter() {
  if (currentIndex < text.length) {
    document.getElementById("output").innerHTML += text.charAt(currentIndex);
    currentIndex++;
    setTimeout(typeWriter, typingSpeed);
  }
}

There are also more robust libraries that could do much more, like TypeIt JS. I shy away from using libraries for small implementations, so I want to write my own vanilla solution. First, I wrapped the dynamic portion in a <span> tag with its own ID:

<h2>Need help with your<br />&nbsp;<span id="dynamicText">website?</span></h2>

I added a line break AND a non-breaking whitespace character to ensure that the changing text used a consistent amount of space. Otherwise, on smaller screen sizes, you could see elements jump as the content changes.

Here is the JavaScript that controls the animation:

  const dynamicText = document.getElementById('dynamicText');
  let originalText = dynamicText.innerText;
  const newTexts = ["app?", "database?", "UI/UX?", "AI?", "API?", "blockchain?", "website?"];
  let textIndex = 0;
  let index = originalText.length;

  function deleteText() {
    dynamicText.innerText = originalText.slice(0, index--);

    if (index >= 0) {
      setTimeout(deleteText, 100); // Adjust the timeout to control the speed of deletion
    } else if (textIndex < newTexts.length) {
      originalText = newTexts[textIndex];
      setTimeout(typeNewText, 500); // Adjust the delay before typing new text
    }
  }

  function typeNewText() {
    if (textIndex < newTexts.length) {
      const newText = newTexts[textIndex];
      index = 0;

      function type() {
        dynamicText.innerText = newText.slice(0, index++);

        if (index <= newText.length) {
          setTimeout(type, 100); // Adjust the timeout to control the speed of typing
        } else {
          textIndex++;
          if (textIndex < newTexts.length) {
            setTimeout(deleteText, 500); // Adjust the delay before deleting the text
          }
        }
      }

      type();
    }
  }

The code snippet begins by declaring a constant variable dynamicText, which stores a reference to an HTML element with the id ‘dynamicText’. This element is where the typing effect will be displayed. Following this, a variable originalText is initialized with the initial text content of the ‘dynamicText’ element. This serves as the starting point for the typing effect.

Next, an array newTexts is defined, containing a list of texts that will be typed out in sequence after the original text is deleted. These texts represent the subsequent messages to be displayed in the typing animation.

Two numerical variables, textIndex and index, are declared to keep track of the current text being typed from the newTexts array and the index within the text being typed, respectively.

The deleteText() function is responsible for deleting the original text character by character until it’s fully removed. It utilizes the setTimeout function to control the speed of deletion. Once the original text is completely deleted, the function triggers the typeNewText() function to start typing the next text from the newTexts array.

Similarly, the typeNewText() function is defined to type out the new text character by character. It also utilizes setTimeout to control the speed of typing. Once the entire new text is typed, the function updates the textIndex to move to the next text in the array and triggers the deleteText() function again to delete the typed text and repeat the process with the next text.

Blinking Cursor

To add a dynamic touch, I incorporated a blinking cursor to the typing effect. The “cursor” itself if a vertical pipe bar character: “|”. I wrapped in a <span> tag and gave it a CSS class cursor.

<h2>Need help with your<br />&nbsp;<span id="dynamicText">website?</span><span class="cursor">|</span></h2>

Making it blink was as simple as adding some CSS rules:

.cursor {
  margin-left: 5px;
  animation: blink 1s steps(1) infinite;
  font-size: .9em;
}

@keyframes blink {
  50% { opacity: 0; }
}

Finally, I remove the cursor when the final word is being typed out:

if(textIndex+1 === newTexts.length){
	var cursorElement = document.querySelector('.cursor');
	if (cursorElement) {
	cursorElement.remove();
}

I add that code to the conditional block responsible for checking if all of the words have been iterated through. The final source looks like this:

const dynamicText = document.getElementById('dynamicText');
let originalText = dynamicText.innerText;
const newTexts = ["app?", "database?", "UI/UX?", "AI?", "API?", "blockchain?", "website?"];
let textIndex = 0;
let index = originalText.length;

function deleteText() {
  dynamicText.innerText = originalText.slice(0, index--);

  if (index >= 0) {
    setTimeout(deleteText, 100); // Adjust the timeout to control the speed of deletion
  } else if (textIndex < newTexts.length) {
    originalText = newTexts[textIndex];
    setTimeout(typeNewText, 500); // Adjust the delay before typing new text
  }
}

function typeNewText() {
  if (textIndex < newTexts.length) {
    const newText = newTexts[textIndex];
    index = 0;

    function type() {
      dynamicText.innerText = newText.slice(0, index++);

      if (index <= newText.length) {
        setTimeout(type, 100); // Adjust the timeout to control the speed of typing

		if(textIndex+1 === newTexts.length){
			var cursorElement = document.querySelector('.cursor');
			if (cursorElement) {
			cursorElement.remove();
		}
        }

      } else {
        textIndex++;
        if (textIndex < newTexts.length) {
          setTimeout(deleteText, 500); // Adjust the delay before deleting the text
        }
      }
    }

    type();
  }
}

window.onload = function() {
  setTimeout(deleteText, 1500);
};

Tracking Google Ad Conversions in WordPress

google ads

If you’re a business owner or marketer, you’re likely familiar with Google Ads as a strong way to drive traffic to your website. Setting it up effectively can require some technical know-how, especially when integrating with WordPress. Allow me to walk you through properly integrating Google Ads with Google Analytics and Google Tag Manager, the right way, to maximize your website’s performance and data tracking capabilities.

Why Use Google Ads for Your Website?

Google Ads is an excellent tool for driving targeted traffic to your website. By bidding on keywords relevant to your business, you can show ads to users actively searching for what you offer. But, to really understand the value of that traffic, it’s crucial to track it correctly. That’s where tools like Google Analytics and Google Tag Manager come in.

Option 1: Directly Connect Google Analytics and Google Ads with Custom JavaScript

For many businesses and marketers, a straightforward setup of Google Analytics and Google Ads is enough to track important website interactions without needing a more complex setup. If you’re looking to avoid additional tools or platforms, you can connect Google Analytics and Google Ads directly and use custom JavaScript to track key actions on your website. Here’s how to do it:

Site Kit for WordPress

Step 1: Install Google Analytics (GA4)

To begin, you’ll need to install Google Analytics on your WordPress site. There are a couple of ways to do this:

  • Using a Plugin: WordPress plugins like Site Kit by Google simplify the process. After installation, the plugin will automatically insert the Google Analytics tracking code into your website, and you’ll be able to see key data like page views and user behavior within your Google Analytics dashboard.
  • Manual Code Insertion: If you prefer not to use a plugin, you can manually add the GA4 tracking code to your WordPress site. To do this, copy the tracking script from your Google Analytics account and paste it into your site’s header or footer file (usually found in your theme settings or via your website’s code editor). This will start collecting data immediately.

Step 2: Link Google Analytics to Google Ads

Once Google Analytics is set up, the next step is to link it with your Google Ads account. This will allow you to see how your ad campaigns are performing in terms of real user behavior and conversions on your website.

  • In Google Analytics, navigate to the Admin section and find the Google Ads Linking option under the Property settings.
  • Follow the prompts to link your Google Ads account. Once linked, data such as user behavior, conversions, and audience insights will flow between Google Ads and Analytics, allowing you to optimize your campaigns based on how visitors interact with your site.

Step 3: Set Up Custom JavaScript for Conversion Tracking

For more advanced tracking, you can use custom JavaScript to monitor specific actions on your site, like button clicks, form submissions, or external link redirects. Here’s how you can do this:

custom javascript

  • Google Ads Conversion Tracking: Google Ads provides a conversion tracking code that you can place directly into your website’s HTML. For example, if you want to track when users complete a form or click a specific button, you can insert this conversion tracking code into the relevant part of your site’s HTML.
    • Navigate to your Google Ads account, go to Tools & Settings > Conversions, and create a new conversion action.
    • Google Ads will generate a conversion tracking code that includes a JavaScript snippet. You can add this code directly into your website’s HTML (either through your WordPress editor or directly into the page code) to track actions.
  • Tracking Button Clicks: If you want to track a button click (for example, a “Contact Us” button), you can insert the conversion tracking code into the button’s HTML attributes. This will trigger a conversion event each time a user clicks the button, allowing you to monitor how often it leads to valuable actions on your site.
  • Tracking Form Submissions: Similarly, if you want to track when users submit a contact form, you can embed the conversion tracking code into the form’s submission page or thank-you page. This is especially useful for tracking lead generation or contact inquiries.

Here is a sample of the code I used:

<script>
document.addEventListener('DOMContentLoaded', function() {
    var buttons = document.querySelectorAll('.cta-consultation'); // Select all elements with the class 'cta-consultation'

    // Check if there are any buttons to avoid errors
    if (buttons.length > 0) {
        buttons.forEach(function(button) {
            button.addEventListener('click', function() {
                // Fire the Google Ads event
                gtag('event', 'conversion', {
                    'send_to': 'AW-XXX/XXX',
                    'event_callback': function() {
                        console.log('Conversion tracked!'); // Optional: for testing purposes
                    }
                });
            });
        });
    }
});
</script>

Step 4: Monitor and Optimize Conversions

Once the custom JavaScript is in place, Google Analytics will start tracking the data, and you’ll be able to see how users are interacting with your site in the Conversions section of Google Ads. Over time, you can monitor the performance of your campaigns and make adjustments to improve your return on investment (ROI).

Key Advantages of This Approach

Simple and Direct: For small or less complex websites, directly connecting Google Analytics and Google Ads using custom JavaScript is a simple and effective solution.

Option 2: Integrating Google Analytics and Google Ads Using Google Tag Manager

For businesses that want more flexibility and control over their tracking setup, using Google Tag Manager (GTM) offers a more advanced approach compared to directly connecting Google Analytics (GA4) and Google Ads. By connecting GA4 through GTM and setting up Google Ads to pull conversion data from GA4, you can streamline the process and enable more precise tracking, including custom click tracking. Here’s how to do it:

Step 1: Set Up Google Analytics (GA4) Through Google Tag Manager

Instead of directly embedding Google Analytics code into your WordPress site, you can set up GA4 using Google Tag Manager. This allows for more flexibility in managing and customizing tracking tags without manually editing the site code. Be careful to not have it set up twice (once directly on its own, and also in GTM), or else you may see double measurements

Install Google Tag Manager on Your WordPress Site

First, install GTM by either using a plugin like Site Kit by Google or manually adding the GTM container code to your site’s header. This will serve as the foundation for all tracking scripts on your site.

  • Create a GA4 Tag in GTM:
    • In GTM, create a new tag by selecting Google Analytics: GA4 Configuration.
    • Add your GA4 Measurement ID (found in your Google Analytics account) to the tag configuration.
    • Set the trigger to All Pages, so it tracks page views across your entire site.
    • Save and publish the changes in GTM, and your website will now be tracking page views and user behavior through GA4 without needing any code embedded directly in your site.

Step 2: Set Up Custom Click Tracking Through GTM Triggers

One of the major advantages of using Google Tag Manager is the ability to create custom triggers to track specific user interactions, such as button clicks or link clicks, without having to modify the site’s code.

Create a Click Trigger in GTM

To track specific interactions like button clicks, you’ll need to set up a trigger in GTM. In GTM, create a new Trigger and select the Click – All Elements option. Define the conditions for your trigger, such as when a user clicks a specific button or link. For example, if you want to track a button leading to a third-party calendar page, you can set the trigger to fire when a user clicks on the button with that specific URL. My conversion was based on clicking any button or link that led to a Google Calendar scheduling page. Since the editor we were using (Kubio) generated complex HTML around the button, I was having trouble targeting exact clicks. Ultimately, I used a selector that targeted click URLs that contained part of the destination:

gtm trigger

On another setup, I had to target “Just Links” for the event to fire. Make sure you test in preview mode to make sure it works!

Just links trigger

  • Create a Google Analytics Event Tag:
    • Create a new Google Analytics: GA4 Event tag in GTM.
    • Set the event parameters, such as what action or label you want to track (e.g., “Button Click” or “Contact Form Submission”).
    • Assign the click trigger you just created to this tag, so it fires when the user interaction happens.

gtm tags

Once your trigger is firing, the event will be recorded in GA4. This allows you to see detailed analytics on how users interact with specific elements on your site. You may have to wait until the next day to see it showing up and to continue the rest of this process.

Step 3: Link Google Ads to GA4 for Conversion Tracking

Once GA4 is collecting conversion data, you’ll want to make sure that Google Ads is also using this data for your ad campaigns. Instead of directly adding Google Ads tracking code to your website, you can import conversions from GA4 into Google Ads. Make sure you mark the event as a ‘Key Event’ in GA4:

ga4 key event

  • Link Google Ads to GA4: In your Google Analytics admin settings, find the Google Ads Linking option under the Property settings and link your Google Ads account.
  • Import Conversions into Google Ads: In your Google Ads account, navigate to Tools & Settings > Conversions. Click to import conversions from Google Analytics. You will now be able to track and optimize your Google Ads campaigns based on the conversion events recorded in GA4.

Step 5: Set Up Google Ads to Track Click Conversions

With the custom click tracking events flowing into GA4, you can now import these events into Google Ads as conversions.

  • In GA4, mark the click event you created as a Conversion (similar to how you would for other key actions).

key events

  • Then, in Google Ads, import the conversion from GA4, so your ad campaigns can track the performance of these custom click actions.

Conversions

This setup allows you to track highly specific user actions (like button clicks or form submissions) and optimize your Google Ads campaigns based on those actions.

GTM allows you to track custom events like button clicks, form submissions, and other interactions without modifying your site’s code. With Google Tag Manager, you can manage all of your tracking codes (for Google Analytics, Google Ads, and other tools) in one place. By pulling conversion data from GA4 into Google Ads, you can optimize your campaigns based on real user actions and get more detailed insights into how users interact with your site.

Transform Your Site: A Case Study on Redesigning with WordPress Gutenberg on 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 simplified cloud hosting solution. The WordPress installation uses Bitnami as the package library.

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, "OnCueCostumesOnline.com"
The legacy site, “OnCueCostumesOnline.com”

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){
	 .hide-on-mobile{
		 display: none
	 }
	.navigation-columns{
		flex-wrap: wrap !important;
	}

	.navigation-column{
		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) {
	.mobile-margin-top{ 
		margin-top: 32px !important;
	}
}

h6 a{
	text-decoration:none !important;
}

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

input[type="search"]{
       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('https://www.oncuecostumes.com/wp-content/themes/movie-cinema-blocks/assets/fonts/peacesans.ttf') 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 => {
    link.parentNode.removeChild(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 user@example.com. When I tried updating this to a verified address, WordPress would also try to send an email to user@example.com, 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.

Adding SSL Certificate for HTTPS

The Bitnami installation comes with a tool `bncert-tool` that handles everything. It even sets up redirects to ensure your site always uses the secure HTTPS protocol. Refer to this AWS documentation: “Enable HTTPS on your WordPress instance in Lightsail“. You can run it from the command line interface. If it is not already installed you can download it: `wget -O bncert-linux-x64.run https://downloads.bitnami.com/files/bncert/latest/bncert-linux-x64.run`

At first, I tried installing the certs manually, using Let’s Encrypt – which was a bad idea. I encountered a problem resulting in a mismatch between the SSL certificate (server.crt) and the private key (server.key). This prevented Apache from restarting (panic). I was able to resolve the problem by generating a new self-signed certificate (relief). Make sure you take back-ups and snapshots before messing with anything via command line.

Snapshots

Lightsail allows for manual and automatic snapshots. My WordPress installation was about 40 gigabytes. Storage costs five cents per gigabyte – which is about two dollars per month to store a single snapshot.

 

Case Study: Fixing a WooCommerce Website 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;

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

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

Why Every Business Needs a Website in 2024

small business websites

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

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 @aol.com or @gmail.com 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.

Logo

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.

 

Boost Your Marketing: Integrating Newsletter Signups 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 antpace.com”. 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>
            	<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>
            </div>
	</form>
</div>

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

<script>
$(function(){
	const notifications = new UINotifications();
	$("#signupButton").click(function(){
		const email = $("#hubspot-email").val();
		if(email.length < 1){
			notifications.showStatusMessage("An email address is required.");
			return;
		}
		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();
			formData.properties[fieldName] = fieldValue;
		});
		$.ajax({
			type: "POST",
			data: JSON.stringify(formData),
			url: "/hubspot-service.php",
			success:function(response){
				console.log(response);
				notifications.showStatusMessage("Thank you for signing up.");
                                $("#hubspotForm")[0].reset();
			
			}
			
		});
	});

});
</script>

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:

<?php

$url = 'https://api.hubapi.com/crm/v3/objects/contacts';
$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);
}

curl_close($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 = [];
	magicSquares.push(magicSquare1);

	// 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)
		magicSquares.push(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]));
		magicSquares.push(flipped);
	}
	
	return magicSquares;
}

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

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:

<script>

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

	// 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)
		magicSquares.push(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]));
		magicSquares.push(flipped);
	}
	
	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]]);
console.log(finalCost);
</script>

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

Additional References

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>
</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.

<style>
	.gridItemBorder{
		border-bottom: 2px solid #333;
		grid-column: span 2;
	}
</style>


<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>
</div>

The end result is just what we wanted:

css grid layout with row separators