Creating a Pre-Launch Shopify Landing Page


It’s not uncommon to want a landing page setup on a store’s domain before the store is fully flushed out and ready to launch. Maybe you’re waiting on theme development or the product isn’t ready to ship. Whatever the reason may be, today I’ll show you a “hacky” way to show a single landing page with an email capture on Shopify. Special thanks to Parth Shah for his codepen we are utilizing.

Find the code on Github here.

Prefer Video?

Enjoy, otherwise read on!

Pro Tip

You can add the landing page to password.liquid file to show the landing page while your theme is still password protected. Just add the HTML/CSS there and include an additional input for the password to see the theme preview.

Edit index.json

Index.json is the template that our homepage route utilizes for content. We need to strip it all out and replace it only with the section we will create. Note we cannot save this file until we create the new section.

// index.json
{
  "sections": {
    "landing-page": {
      "type": "Landing-Page",
      "settings": {}
    }
  },
  "order": [
    "landing-page"
  ]
}

Create our Landing Page Section

Now we can create a file in the section directory and name it “Landing-Page.liquid”, ensuring it matches exactly what we put in the index.json file above. For simplicity I am going to add all of our styles, HTML and JS in a single file. First let’s add our styles.

<style>
  html {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}

*,
*:before,
*:after {
    -webkit-box-sizing: inherit;
    -moz-box-sizing: inherit;
    box-sizing: inherit;
}

@import url(https://fonts.googleapis.com/css?family=Quicksand:400,300,700);

html,
body {
    width: 100%;
    margin: 0 auto;
    background-color: #1c1c1c;
    font-family: "Quicksand", sans-serif;

    form.form {
      color: #fff;
      font-family: "Quicksand", sans-serif;
      margin: 10px;
      
    }

    form.form input.input-email {
      background-color: #1c1c1c;
      border: 1px solid #e6dbae;
      border-radius: 10px;
      height: 27px;
      color: #fff;
      padding-left: 10px;
    }

    form.form input:focus{
      outline: none;
    }

    form.form input.btn-submit {
      background-color: #1c1c1c;
      border: 1px solid #e6dbae;
      border-radius: 10px;
      color: #e6dbae;
      margin-left: 5px;
      padding: 5px;
      box-sizing: content-box;
    }

    #background {
        position: fixed;
        top: 50%;
        left: 50%;
        min-width: 100%;
        min-height: 100%;
        width: auto;
        height: auto;
        z-index: -100;
        -webkit-transform: translateX(-50%) translateY(-50%);
        transform: translateX(-50%) translateY(-50%);
        background: url(polina.jpg) no-repeat;
        background-size: cover;
        overflow: hidden;
        -webkit-filter: blur(5px);
        -moz-filter: blur(5px);
        -o-filter: blur(5px);
        -ms-filter: blur(5px);
        filter: blur(5px);
    }

    .main {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 50%;
        left: 50%;
        margin: 0 auto;
        transform: translate(-50%, -50%);
        background: rgba(0, 0, 0, 0.5);

        #content {
            width: 100%;
            max-width: 600px;
            margin: 0 auto;
            min-height: 24px;
            height: 100%;
            position: relative;
            text-align: center;
            top: 33%;

            .title {
                color: white;
                font-family: "Quicksand", sans-serif;
                font-size: 4rem;
                text-transform: uppercase;
                padding-bottom: 0px;
                margin-bottom: 0px;

                span {
                    font-size: 4rem;
                    cursor: pointer;
                }

                &:hover {
                    -webkit-animation: MISSION-HOVER 1.5s infinite;
                    -moz-animation: MISSION-HOVER 1.5s infinite;
                    -o-animation: MISSION-HOVER 1.5s infinite;
                    animation: MISSION-HOVER 1.5s infinite;
                    animation-direction: alternate;
                    animation-timing-function: ease;
                }

                @-webkit-keyframes MISSION-HOVER {

                    0%,
                    100% {
                        font-size: 4rem;
                        color: white;
                    }

                    50% {
                        color: #bffcff;
                        letter-spacing: 5px;
                        text-shadow: 0px 0px 30px rgba(191, 252, 255, 1);
                    }
                }
            }

            p {
                color: #e6dbae;
                font-family: "Quicksand", sans-serif;
                font-size: 1.5rem;
                margin: 0 auto;
                padding: 0;
                letter-spacing: 0.5rem;
                text-transform: uppercase;

                &:last-child {
                    font-size: 0.75rem;
                    font-weight: 700;
                    margin: 3em auto;
                    padding: 0;
                    letter-spacing: 0.1rem;

                    >a {
                        text-decoration: none;
                        color: inherit;
                        -moz-transition: all 0.2s ease-in;
                        -o-transition: all 0.2s ease-in;
                        -webkit-transition: all 0.2s ease-in;
                        transition: all 0.2s ease-in;

                        &:hover {
                            color: #fff;
                        }
                    }
                }
            }

            section {
                color: #fff;
                margin: 0 auto;
                line-height: 24px;
                font-size: 1rem;
                font-weight: 700;

                ul {
                    list-style-type: none;
                    margin-bottom: 0;
                    margin-left: 0;

                    li {
                        display: inline-block;
                        margin-right: 2rem;
                        width: 6rem;
                    }
                }

                .timenumbers {
                    display: block;
                    font-size: 1.3rem;
                    font-weight: 400;
                    line-height: 1.5rem;
                    margin: 0 auto;
                    text-align: center;
                }

                p.timedescription {
                    font-size: 0.5rem;
                    font-variant: small-caps;
                    line-height: 1.5rem;
                    margin: 0 auto;
                    text-align: center;
                    position: relative;
                    top: 0px;
                }
            }
        }
    }
}
</style>

The bulk of these styles are from our wonderful codepen example, but there are a few custom styles for our email input we added. Now let’s add the HTML below:

<video autoplay loop muted poster="screenshot.jpg" id="background">
  <!-- External resources removed by CodePen support due to server authentication access restrictions -->
<!--         <source type="video/mp4" src="https://staging.coverr.co/s3/mp4/Boxing-gym.mp4">
        <source type="video/webm" src="https://staging.coverr.co/s3/webm/Boxing-gym.webm"> -->
</video>
<div class="main">
  <div id='content'>
    <div class='title'>
      <span>NICK'S SHOP</span>
    </div>
    <p>coming soon</p>
    <section>
      <ul id="countdown">
        <li><span class="days timenumbers">01</span>
          <p class="timeRefDays timedescription">days</p>
        </li>
        <li><span class="hours timenumbers">00</span>
          <p class="timeRefHours timedescription">hours</p>
        </li>
        <li><span class="minutes timenumbers">00</span>
          <p class="timeRefMinutes timedescription">minutes</p>
        </li>
        <li><span class="seconds timenumbers yellow-text">00</span>
          <p class="timeRefSeconds timedescription">seconds</p>
        </li>
      </ul>
    </section>
    {%- assign formId = 'ContactForm' -%}
    {% form 'contact', id: formId, class: 'form' %}
      <div>
        {% if form.posted_successfully? %}
        
        <p style="font-size: 1rem; letter-spacing: 0; text-transform: none;" class="note note--success">We have received your submission and will reach out to you soon!</p>
        {% else %}
        <p style="font-size: 1rem; letter-spacing: 0; text-transform: none;">Become the first to know. Enter your email to stay up to date.</p>
        <input
          style="visibility: hidden; height: 1px;"
          type="product"
          id="{{ formId }}-product"
          name="contact[Invoice Submitted]"
          value="Pre-Launch Email Registration"
        >
        {% endif %}
      </div>
      <div class="grid__item medium-up--one-half" style="display: flex; justify-content: center;">
        <input
            type="email"
            id="{{ formId }}-email"
            name="contact[email]"
            autocorrect="off"
            autocapitalize="off"
            value="{{ form[email] }}"
            aria-required="true"
            class="input-email"
            placeholder="Email"
            {%- if form.errors contains 'email' -%}
              class="input--error"
              aria-invalid="true"
              aria-describedby="{{ formId }}-email-error"
            {%- endif -%}
          >
        <input type="submit" class="btn-submit" value="Submit">
      </div>
      {{ form.errors | default_errors }}
    {% endform %}
  </div>
</div>

Here you can see we added the Shopify contact form so we can receive emails when users submit them. When successful we show a message thanking them and if there are any errors we make sure to display those too. Now let’s add our JavaScript below:


<script>
  (function($) {
    $.fn.countdown = function(options, callback) {
        //custom 'this' selector
        thisEl = $(this);

        // array of custom settings
        var settings = {
            'date': null,
            'format': null
        };

        // append the settings array to options
        if (options) {
            $.extend(settings, options);
        }

        //create the countdown processing function
        function countdown_proc() {
            var eventDate = Date.parse(settings.date) / 1000;
            var currentDate = Math.floor($.now() / 1000);

            if (eventDate <= currentDate) {
                callback.call(this);
                clearInterval(interval);
            }

            var seconds = eventDate - currentDate;

            var days = Math.floor(seconds / (60 * 60 * 24));
            //calculate the number of days

            seconds -= days * 60 * 60 * 24;
            //update the seconds variable with number of days removed

            var hours = Math.floor(seconds / (60 * 60));
            seconds -= hours * 60 * 60;
            //update the seconds variable with number of hours removed

            var minutes = Math.floor(seconds / 60);
            seconds -= minutes * 60;
            //update the seconds variable with number of minutes removed

            //conditional statements
            if (days == 1) { thisEl.find(".timeRefDays").text("day"); } else { thisEl.find(".timeRefDays").text("days"); }
            if (hours == 1) { thisEl.find(".timeRefHours").text("hour"); } else { thisEl.find(".timeRefHours").text("hours"); }
            if (minutes == 1) { thisEl.find(".timeRefMinutes").text("minute"); } else { thisEl.find(".timeRefMinutes").text("minutes"); }
            if (seconds == 1) { thisEl.find(".timeRefSeconds").text("second"); } else { thisEl.find(".timeRefSeconds").text("seconds"); }

            //logic for the two_digits ON setting
            if (settings.format == "on") {
                days = (String(days).length >= 2) ? days : "0" + days;
                hours = (String(hours).length >= 2) ? hours : "0" + hours;
                minutes = (String(minutes).length >= 2) ? minutes : "0" + minutes;
                seconds = (String(seconds).length >= 2) ? seconds : "0" + seconds;
            }

            //update the countdown's html values.
            thisEl.find(".days").text(days);
            thisEl.find(".hours").text(hours);
            thisEl.find(".minutes").text(minutes);
            thisEl.find(".seconds").text(seconds);
        }

        //run the function
        countdown_proc();

        //loop the function
        interval = setInterval(countdown_proc, 1000);
    };

})(jQuery);



//Provide the plugin settings
$("#countdown").countdown({
        //The countdown end date
        date: "30 May 2024 12:00:00",

        // on (03:07:52) | off (3:7:52) - two_digits set to ON maintains layout consistency
        format: "on"
    },

    function() {
        // This will run when the countdown ends
        //alert("We're Out Now");
    });


function setHeights() {
    var windowHeight = $(window).height();
    $('.slide').height(windowHeight);
}

setHeights();

$(window).resize(function() {
    setHeights();
});

function addSticky() {
    $('.slide').each(function() {
        var scrollerAnchor = $(this).offset().top;
        if (window.scrollY >= scrollerAnchor) {
            $(this).addClass('fix-it');
        } else {
            $(this).removeClass('fix-it');
        }
    });
}

$(window).scroll(function() {
    addSticky();
});
</script>

You may have noticed this chunk of JavaScript uses jQuery. Normally I try to avoid using JQuery as it is a large dependency to load in but I thought for this simple landing page example it would be ok. So let’s add our jQuery CDN and make some changes within theme.liquid

Displaying our Landing Page

Now that we have the section file created and referenced in our template, we just need to strip down our theme.liquid file and add our jQuery CDN. You can replace the entire theme.liquid file with the following:

<!doctype html>
<html class="no-js" lang="{{ request.locale.iso_code }}">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="theme-color" content="">
    <link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <script>
      const path = "{{ request.path }}"
      if (path != "/") {
        window.location.href = "/"
      }
    </script>
  </head>
  <body>
    <div style="display: none;">
      {{ content_for_header }}
    </div>
    {{ content_for_layout }}
  </body>
</html>

There are 2 things required in this file. {{ content_for_header }} which we are wrapping in a div and hiding. {{ content_for_layout }} which will reference the template to display the correct pages based on the URL. We are adding some custom JS in the script to check if the request for the page goes to anything other than the root homepage and if so redirect the user back to the homepage. We don’t want them accessing any other pages on our site that may look horrendous.

Conclusion

Now we should see a beautiful custom landing page that captures emails and shows a countdown to our store launch! Stay tuned for more Shopify development content.