Open demo

CSS code

HTML
<main class="sp">
  <div class="sp-content">
    <a class="sp-logo" href="#"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><circle cx="15" cy="15" r="15" fill="#2f5d3a"/><path d="m5 21 6.5-9 4 5 3-4L25 21z" fill="#f5efe4"/></svg>Ridgeline Outfitters</a>
    <section class="sp-body" aria-labelledby="sp-title">
      <p class="sp-eyebrow">New store · Bend, Oregon</p>
      <h1 class="sp-title" id="sp-title">Our first store opens on 17 April.</h1>
      <p class="sp-lead">Tents, packs and ski gear you can try before you buy, a repair bench in the back and coffee on the porch. Find us at 214 NW Wall Street, next to the old mill.</p>
      <dl class="sp-facts">
        <div><dt>Opening day</dt><dd>Saturday 17 April, 10:00</dd></div>
        <div><dt>Free on the day</dt><dd>Bike fitting and ski waxing clinics</dd></div>
      </dl>
      <form class="sp-form" data-signup="sp-done" action="#" method="post">
        <label for="sp-email">Get an invite to opening night, Friday 16 April</label>
        <div class="sp-field">
          <input id="sp-email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required aria-describedby="sp-err">
          <button type="submit">Request invite</button>
        </div>
        <p class="sp-err" id="sp-err" data-error aria-live="polite"></p>
      </form>
      <div class="sp-done" id="sp-done" tabindex="-1" hidden><p><strong>Invite requested.</strong> We have 120 places for opening night and will confirm yours by email in early April.</p></div>
    </section>
    <p class="sp-foot"><span>© 2026 Ridgeline Outfitters</span><a href="#">Follow the build on Instagram</a></p>
  </div>
  <figure class="sp-media">
    <svg viewBox="0 0 600 800" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Illustration of pine-covered ridges at sunrise">
      <defs>
        <linearGradient id="sp-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#1e2a4a"/><stop offset=".38" stop-color="#6b5b8c"/><stop offset=".6" stop-color="#e3907c"/><stop offset=".72" stop-color="#f6c28b"/></linearGradient>
        <linearGradient id="sp-mist" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6c28b" stop-opacity="0"/><stop offset="1" stop-color="#f3d3b5" stop-opacity=".55"/></linearGradient>
        <path id="sp-pine" d="M0-64 13-30H7L19-8H10L22 0H-22l12-8H-19L-7-30H-13z"/>
      </defs>
      <rect width="600" height="800" fill="url(#sp-sky)"/>
      <g fill="#fff" opacity=".7"><circle cx="70" cy="90" r="1.6"/><circle cx="180" cy="60" r="1.2"/><circle cx="300" cy="120" r="1.4"/><circle cx="420" cy="70" r="1.8"/><circle cx="530" cy="140" r="1.2"/><circle cx="250" cy="200" r="1"/><circle cx="480" cy="220" r="1"/></g>
      <circle cx="390" cy="520" r="74" fill="#fde3b0"/>
      <path d="M0 500 80 440l70 32 90-78 80 62 80-44 70 30 70-64 60 42V800H0z" fill="#8a6f96"/>
      <rect y="440" width="600" height="120" fill="url(#sp-mist)"/>
      <path d="M0 570 90 498l80 52 90-72 90 72 80-52 90 60 80-50V800H0z" fill="#56506f"/>
      <path d="M0 650q150-70 300-30t300-24V800H0z" fill="#2f3f3c"/>
      <g fill="#1b2724"><use href="#sp-pine" x="40" y="640"/><use href="#sp-pine" x="78" y="628"/><use href="#sp-pine" x="120" y="622"/><use href="#sp-pine" x="455" y="600"/><use href="#sp-pine" x="500" y="596"/><use href="#sp-pine" x="548" y="598"/><use href="#sp-pine" x="590" y="600"/></g>
      <path d="M0 730q200-50 360-20t240-14V800H0z" fill="#141d1b"/>
      <g fill="#0e1513"><use href="#sp-pine" x="30" y="740" transform="translate(30 740) scale(1.6) translate(-30 -740)"/><use href="#sp-pine" x="560" y="720" transform="translate(560 720) scale(1.8) translate(-560 -720)"/></g>
    </svg>
    <figcaption>Bend, Oregon · 44.06° N</figcaption>
  </figure>
</main>
CSS
/* Split coming soon page: content and form on the left, a full-height image on the right.
   The image is an inline SVG here; swap it for <img src="store.jpg" alt="…"> and keep the same .sp-media rules. */
.sp{--sp-bg:#f5efe4;--sp-ink:#1f2a1f;--sp-muted:#4e5a4c;--sp-accent:#2f5d3a;--sp-accent-hover:#244a2e;--sp-line:#8c8a7c;--sp-err:#a42a1c;
  line-height:1.5;min-height:100vh;min-height:100dvh;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  color:var(--sp-ink);background:var(--sp-bg);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
.sp [hidden]{display:none!important}
.sp a{color:inherit}
.sp a:focus-visible,.sp button:focus-visible,.sp input:focus-visible{outline:2px solid var(--sp-accent);outline-offset:3px}
.sp-content{display:flex;flex-direction:column;gap:40px;padding:34px clamp(22px,5vw,64px)}
.sp-logo{align-self:flex-start;display:inline-flex;align-items:center;gap:10px;font-weight:800;letter-spacing:-.01em;text-decoration:none}
.sp-body{flex:1;display:flex;flex-direction:column;justify-content:center;max-width:500px}
.sp-eyebrow{display:flex;align-items:center;gap:10px;margin:0 0 18px;font-size:.8rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--sp-accent)}
.sp-eyebrow::before{content:"";width:28px;height:2px;background:currentColor}
.sp-title{margin:0 0 18px;font-family:Rockwell,"Roboto Slab","Iowan Old Style",Georgia,serif;font-size:clamp(2.2rem,4.4vw,3.4rem);line-height:1.05;letter-spacing:-.02em}
.sp-lead{margin:0 0 26px;font-size:1.04rem;line-height:1.65;color:var(--sp-muted)}
.sp-facts{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:0 0 30px;padding:18px 0;border-top:1px solid #d8cfbe;border-bottom:1px solid #d8cfbe}
.sp-facts dt{font-size:.74rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--sp-muted)}
.sp-facts dd{margin:4px 0 0;font-weight:600;line-height:1.4}
.sp-form label{display:block;margin:0 0 8px;font-size:.92rem;font-weight:700}
.sp-field{display:flex;gap:8px}
.sp-field input{flex:1;min-width:0;height:50px;padding:0 16px;font:inherit;color:var(--sp-ink);background:#fffdf8;border:1px solid var(--sp-line);border-radius:8px}
.sp-field input::placeholder{color:#6f6c5f}
.sp-field input[aria-invalid="true"]{border-color:var(--sp-err);box-shadow:0 0 0 1px var(--sp-err)}
.sp-field button{flex:none;height:50px;padding:0 22px;font:inherit;font-weight:700;color:#fff;background:var(--sp-accent);border:0;border-radius:8px;cursor:pointer;transition:background-color .2s}
.sp-field button:hover{background:var(--sp-accent-hover)}
.sp-err{min-height:1.4em;margin:8px 0 0;font-size:.88rem;color:var(--sp-err)}
.sp-done{padding:16px 18px;background:#e4ead9;color:#1f3a24;border-left:4px solid var(--sp-accent);border-radius:4px 8px 8px 4px;line-height:1.5}
.sp-done:focus{outline:none}
.sp-done p{margin:0}
.sp-foot{display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px 20px;margin:0;font-size:.85rem;color:var(--sp-muted)}
.sp-media{position:relative;margin:0;overflow:hidden;background:#2f3f3c}
.sp-media svg,.sp-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.sp-media figcaption{position:absolute;left:24px;bottom:24px;padding:8px 14px;font-size:.82rem;font-weight:600;color:#fff;background:rgba(15,22,20,.78);border-radius:999px;backdrop-filter:blur(6px)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
@media (max-width:767.98px){.sp{grid-template-columns:1fr}.sp-media{order:-1;min-height:240px}.sp-body{max-width:none}}
@media (max-width:480px){.sp-facts{grid-template-columns:1fr}}
@media (max-width:575.98px){.sp-field{flex-direction:column}.sp-field input{flex:none}.sp-field button{width:100%}}
@media (prefers-reduced-motion:reduce){.sp *{transition:none}}
JavaScript
// Sign-up form, front end only: it checks the address and shows the confirmation.
// Nothing is sent. Point the form's action at your mailing-list provider, or post it with fetch() below.
document.querySelectorAll('form[data-signup]').forEach(function (form) {
  var input = form.querySelector('input[type="email"]');
  var error = form.querySelector('[data-error]');
  var done = document.getElementById(form.getAttribute('data-signup'));
  form.noValidate = true; // our message replaces the browser bubble; without JS, native validation still works
  function clear() { error.textContent = ''; input.removeAttribute('aria-invalid'); }
  input.addEventListener('input', clear);
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var value = input.value.trim();
    if (!value || !input.checkValidity()) {
      error.textContent = value ? 'Check the address: it needs an @ and a domain, like [email protected].' : 'Enter your email address.';
      input.setAttribute('aria-invalid', 'true');
      input.focus();
      return;
    }
    clear();
    form.hidden = true;
    done.hidden = false;
    done.focus();
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<main class="sp row g-0">
  <div class="sp-content col-md-6 d-flex flex-column gap-5">
    <a class="sp-logo d-inline-flex align-items-center gap-2 fw-bolder align-self-start" href="#"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><circle cx="15" cy="15" r="15" fill="#2f5d3a"/><path d="m5 21 6.5-9 4 5 3-4L25 21z" fill="#f5efe4"/></svg>Ridgeline Outfitters</a>
    <section class="sp-body flex-grow-1 d-flex flex-column justify-content-center" aria-labelledby="sp-title">
      <p class="sp-eyebrow d-flex align-items-center gap-2 fw-bold text-uppercase mb-3">New store · Bend, Oregon</p>
      <h1 class="sp-title mb-3" id="sp-title">Our first store opens on 17 April.</h1>
      <p class="sp-lead text-body-secondary mb-4">Tents, packs and ski gear you can try before you buy, a repair bench in the back and coffee on the porch. Find us at 214 NW Wall Street, next to the old mill.</p>
      <dl class="sp-facts row row-cols-1 row-cols-sm-2 gy-3 mx-0 mb-4">
        <div class="col ps-0"><dt class="fw-bold text-uppercase text-body-secondary">Opening day</dt><dd class="fw-semibold mt-1 mb-0">Saturday 17 April, 10:00</dd></div>
        <div class="col ps-0"><dt class="fw-bold text-uppercase text-body-secondary">Free on the day</dt><dd class="fw-semibold mt-1 mb-0">Bike fitting and ski waxing clinics</dd></div>
      </dl>
      <form data-signup="sp-done" action="#" method="post">
        <label class="form-label fw-bold" for="sp-email">Get an invite to opening night, Friday 16 April</label>
        <div class="d-flex flex-column flex-sm-row gap-2">
          <input class="form-control" id="sp-email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required aria-describedby="sp-err">
          <button class="btn btn-sp flex-shrink-0" type="submit">Request invite</button>
        </div>
        <p class="sp-err small mt-2 mb-0" id="sp-err" data-error aria-live="polite"></p>
      </form>
      <div class="sp-done px-3 py-3" id="sp-done" tabindex="-1" hidden><p class="m-0"><strong>Invite requested.</strong> We have 120 places for opening night and will confirm yours by email in early April.</p></div>
    </section>
    <p class="d-flex flex-wrap justify-content-between gap-2 small text-body-secondary m-0"><span>© 2026 Ridgeline Outfitters</span><a href="#">Follow the build on Instagram</a></p>
  </div>
  <figure class="sp-media col-md-6 order-first order-md-last position-relative overflow-hidden m-0">
    <svg viewBox="0 0 600 800" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Illustration of pine-covered ridges at sunrise">
      <defs>
        <linearGradient id="sp-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#1e2a4a"/><stop offset=".38" stop-color="#6b5b8c"/><stop offset=".6" stop-color="#e3907c"/><stop offset=".72" stop-color="#f6c28b"/></linearGradient>
        <linearGradient id="sp-mist" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6c28b" stop-opacity="0"/><stop offset="1" stop-color="#f3d3b5" stop-opacity=".55"/></linearGradient>
        <path id="sp-pine" d="M0-64 13-30H7L19-8H10L22 0H-22l12-8H-19L-7-30H-13z"/>
      </defs>
      <rect width="600" height="800" fill="url(#sp-sky)"/>
      <g fill="#fff" opacity=".7"><circle cx="70" cy="90" r="1.6"/><circle cx="180" cy="60" r="1.2"/><circle cx="300" cy="120" r="1.4"/><circle cx="420" cy="70" r="1.8"/><circle cx="530" cy="140" r="1.2"/><circle cx="250" cy="200" r="1"/><circle cx="480" cy="220" r="1"/></g>
      <circle cx="390" cy="520" r="74" fill="#fde3b0"/>
      <path d="M0 500 80 440l70 32 90-78 80 62 80-44 70 30 70-64 60 42V800H0z" fill="#8a6f96"/>
      <rect y="440" width="600" height="120" fill="url(#sp-mist)"/>
      <path d="M0 570 90 498l80 52 90-72 90 72 80-52 90 60 80-50V800H0z" fill="#56506f"/>
      <path d="M0 650q150-70 300-30t300-24V800H0z" fill="#2f3f3c"/>
      <g fill="#1b2724"><use href="#sp-pine" x="40" y="640"/><use href="#sp-pine" x="78" y="628"/><use href="#sp-pine" x="120" y="622"/><use href="#sp-pine" x="455" y="600"/><use href="#sp-pine" x="500" y="596"/><use href="#sp-pine" x="548" y="598"/><use href="#sp-pine" x="590" y="600"/></g>
      <path d="M0 730q200-50 360-20t240-14V800H0z" fill="#141d1b"/>
      <g fill="#0e1513"><use href="#sp-pine" x="30" y="740" transform="translate(30 740) scale(1.6) translate(-30 -740)"/><use href="#sp-pine" x="560" y="720" transform="translate(560 720) scale(1.8) translate(-560 -720)"/></g>
    </svg>
    <figcaption class="position-absolute fw-semibold text-white rounded-pill">Bend, Oregon · 44.06° N</figcaption>
  </figure>
</main>
CSS
/* Bootstrap .row.g-0 split into two .col-md-6 halves; .form-label, .form-control and .btn for the invite form.
   The image is an inline SVG here; swap it for <img src="store.jpg" alt="…"> and keep the same .sp-media rules. */
.sp{--sp-accent:#2f5d3a;--sp-accent-hover:#244a2e;--sp-line:#8c8a7c;--sp-err:#a42a1c;
  --bs-body-bg:#f5efe4;--bs-body-color:#1f2a1f;--bs-secondary-color:#4e5a4c;
  --bs-body-font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  min-height:100dvh;color:var(--bs-body-color);background:var(--bs-body-bg);font-family:var(--bs-body-font-family)}
.sp a{color:inherit}
.sp a:focus-visible{outline:2px solid var(--sp-accent);outline-offset:3px;border-radius:2px}
.sp-content{padding:34px clamp(22px,5vw,64px)}
.sp-logo{letter-spacing:-.01em;text-decoration:none}
.sp-body{max-width:500px}
.sp-eyebrow{font-size:.8rem;letter-spacing:.14em;color:var(--sp-accent)}
.sp-eyebrow::before{content:"";width:28px;height:2px;background:currentColor}
.sp-title{font-family:Rockwell,"Roboto Slab","Iowan Old Style",Georgia,serif;font-size:clamp(2.2rem,4.4vw,3.4rem);font-weight:700;line-height:1.05;letter-spacing:-.02em}
.sp-lead{font-size:1.04rem;line-height:1.65}
.sp-facts{padding:18px 0;border-top:1px solid #d8cfbe;border-bottom:1px solid #d8cfbe}
.sp-facts dt{font-size:.74rem;letter-spacing:.1em}
.sp-facts dd{line-height:1.4}
.sp .form-label{font-size:.92rem}
.sp .form-control{height:50px;padding:0 16px;background:#fffdf8;border-color:var(--sp-line);border-radius:8px}
.sp .form-control::placeholder{color:#6f6c5f}
.sp .form-control:focus{border-color:var(--sp-line);box-shadow:none;outline:2px solid var(--sp-accent);outline-offset:3px}
.sp .form-control[aria-invalid="true"]{border-color:var(--sp-err);box-shadow:0 0 0 1px var(--sp-err)}
.sp .btn-sp{--bs-btn-bg:var(--sp-accent);--bs-btn-border-color:var(--sp-accent);--bs-btn-color:#fff;--bs-btn-font-weight:700;--bs-btn-border-radius:8px;--bs-btn-padding-x:22px;
  --bs-btn-hover-bg:var(--sp-accent-hover);--bs-btn-hover-border-color:var(--sp-accent-hover);--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:var(--sp-accent-hover);--bs-btn-active-border-color:var(--sp-accent-hover);--bs-btn-active-color:#fff;
  --bs-btn-focus-box-shadow:0 0 0 3px var(--bs-body-bg),0 0 0 5px var(--sp-accent);height:50px}
.sp-err{min-height:1.4em;color:var(--sp-err)}
.sp-done{background:#e4ead9;color:#1f3a24;border-left:4px solid var(--sp-accent);border-radius:4px 8px 8px 4px}
.sp-done:focus{outline:none}
.sp-media{background:#2f3f3c}
.sp-media svg,.sp-media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.sp-media figcaption{left:24px;bottom:24px;padding:8px 14px;font-size:.82rem;background:rgba(15,22,20,.78);backdrop-filter:blur(6px)}
@media (max-width:767.98px){.sp-media{min-height:240px}.sp-body{max-width:none}}
@media (prefers-reduced-motion:reduce){.sp .btn,.sp .form-control{transition:none}}
JavaScript
// Sign-up form, front end only: it checks the address and shows the confirmation.
// Nothing is sent. Point the form's action at your mailing-list provider, or post it with fetch() below.
document.querySelectorAll('form[data-signup]').forEach(function (form) {
  var input = form.querySelector('input[type="email"]');
  var error = form.querySelector('[data-error]');
  var done = document.getElementById(form.getAttribute('data-signup'));
  form.noValidate = true; // our message replaces the browser bubble; without JS, native validation still works
  function clear() { error.textContent = ''; input.removeAttribute('aria-invalid'); }
  input.addEventListener('input', clear);
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var value = input.value.trim();
    if (!value || !input.checkValidity()) {
      error.textContent = value ? 'Check the address: it needs an @ and a domain, like [email protected].' : 'Enter your email address.';
      input.setAttribute('aria-invalid', 'true');
      input.focus();
      return;
    }
    clear();
    form.hidden = true;
    done.hidden = false;
    done.focus();
  });
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<main class="grid min-h-dvh grid-cols-1 bg-sp-bg font-[-apple-system,BlinkMacSystemFont,'Segoe_UI',Roboto,Helvetica,Arial,sans-serif] text-sp-ink md:grid-cols-2">
  <div class="flex flex-col gap-10 px-[clamp(22px,5vw,64px)] py-[34px]">
    <a class="inline-flex items-center gap-2.5 self-start font-extrabold tracking-[-.01em] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-sp-accent" href="#"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><circle cx="15" cy="15" r="15" fill="#2f5d3a"/><path d="m5 21 6.5-9 4 5 3-4L25 21z" fill="#f5efe4"/></svg>Ridgeline Outfitters</a>
    <section class="flex flex-1 flex-col justify-center md:max-w-[500px]" aria-labelledby="sp-title">
      <p class="mb-[18px] flex items-center gap-2.5 text-[.8rem] font-bold tracking-[.14em] text-sp-accent uppercase before:h-0.5 before:w-7 before:bg-current">New store · Bend, Oregon</p>
      <h1 class="mb-[18px] font-sp-slab text-[clamp(2.2rem,4.4vw,3.4rem)] leading-[1.05] font-bold tracking-[-.02em]" id="sp-title">Our first store opens on 17 April.</h1>
      <p class="mb-[26px] text-[1.04rem] leading-[1.65] text-sp-muted">Tents, packs and ski gear you can try before you buy, a repair bench in the back and coffee on the porch. Find us at 214 NW Wall Street, next to the old mill.</p>
      <dl class="mb-[30px] grid grid-cols-1 gap-3.5 border-y border-[#d8cfbe] py-[18px] min-[481px]:grid-cols-2">
        <div><dt class="text-[.74rem] font-bold tracking-[.1em] text-sp-muted uppercase">Opening day</dt><dd class="mt-1 leading-[1.4] font-semibold">Saturday 17 April, 10:00</dd></div>
        <div><dt class="text-[.74rem] font-bold tracking-[.1em] text-sp-muted uppercase">Free on the day</dt><dd class="mt-1 leading-[1.4] font-semibold">Bike fitting and ski waxing clinics</dd></div>
      </dl>
      <form data-signup="sp-done" action="#" method="post">
        <label class="mb-2 block text-[.92rem] font-bold" for="sp-email">Get an invite to opening night, Friday 16 April</label>
        <div class="flex flex-col gap-2 min-[576px]:flex-row">
          <input class="h-[50px] min-w-0 flex-none min-[576px]:flex-1 rounded-lg border border-sp-line bg-[#fffdf8] px-4 text-sp-ink placeholder:text-[#6f6c5f] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-sp-accent aria-invalid:border-sp-err aria-invalid:shadow-[0_0_0_1px_var(--color-sp-err)]" id="sp-email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required aria-describedby="sp-err">
          <button class="h-[50px] flex-none cursor-pointer rounded-lg bg-sp-accent px-[22px] font-bold text-white transition-colors duration-200 hover:bg-sp-accent-hover focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-sp-accent motion-reduce:transition-none" type="submit">Request invite</button>
        </div>
        <p class="mt-2 min-h-[1.4em] text-[.88rem] text-sp-err" id="sp-err" data-error aria-live="polite"></p>
      </form>
      <div class="rounded-l-[4px] rounded-r-lg border-l-4 border-sp-accent bg-[#e4ead9] px-[18px] py-4 leading-normal text-[#1f3a24] focus:outline-none" id="sp-done" tabindex="-1" hidden><p><strong>Invite requested.</strong> We have 120 places for opening night and will confirm yours by email in early April.</p></div>
    </section>
    <p class="flex flex-wrap justify-between gap-x-5 gap-y-2 text-[.85rem] text-sp-muted"><span>© 2026 Ridgeline Outfitters</span><a class="underline focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-sp-accent" href="#">Follow the build on Instagram</a></p>
  </div>
  <figure class="relative -order-1 min-h-[240px] overflow-hidden bg-[#2f3f3c] md:order-none md:min-h-0 [&>svg]:absolute [&>svg]:inset-0 [&>svg]:size-full">
    <svg viewBox="0 0 600 800" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Illustration of pine-covered ridges at sunrise">
      <defs>
        <linearGradient id="sp-sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#1e2a4a"/><stop offset=".38" stop-color="#6b5b8c"/><stop offset=".6" stop-color="#e3907c"/><stop offset=".72" stop-color="#f6c28b"/></linearGradient>
        <linearGradient id="sp-mist" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6c28b" stop-opacity="0"/><stop offset="1" stop-color="#f3d3b5" stop-opacity=".55"/></linearGradient>
        <path id="sp-pine" d="M0-64 13-30H7L19-8H10L22 0H-22l12-8H-19L-7-30H-13z"/>
      </defs>
      <rect width="600" height="800" fill="url(#sp-sky)"/>
      <g fill="#fff" opacity=".7"><circle cx="70" cy="90" r="1.6"/><circle cx="180" cy="60" r="1.2"/><circle cx="300" cy="120" r="1.4"/><circle cx="420" cy="70" r="1.8"/><circle cx="530" cy="140" r="1.2"/><circle cx="250" cy="200" r="1"/><circle cx="480" cy="220" r="1"/></g>
      <circle cx="390" cy="520" r="74" fill="#fde3b0"/>
      <path d="M0 500 80 440l70 32 90-78 80 62 80-44 70 30 70-64 60 42V800H0z" fill="#8a6f96"/>
      <rect y="440" width="600" height="120" fill="url(#sp-mist)"/>
      <path d="M0 570 90 498l80 52 90-72 90 72 80-52 90 60 80-50V800H0z" fill="#56506f"/>
      <path d="M0 650q150-70 300-30t300-24V800H0z" fill="#2f3f3c"/>
      <g fill="#1b2724"><use href="#sp-pine" x="40" y="640"/><use href="#sp-pine" x="78" y="628"/><use href="#sp-pine" x="120" y="622"/><use href="#sp-pine" x="455" y="600"/><use href="#sp-pine" x="500" y="596"/><use href="#sp-pine" x="548" y="598"/><use href="#sp-pine" x="590" y="600"/></g>
      <path d="M0 730q200-50 360-20t240-14V800H0z" fill="#141d1b"/>
      <g fill="#0e1513"><use href="#sp-pine" x="30" y="740" transform="translate(30 740) scale(1.6) translate(-30 -740)"/><use href="#sp-pine" x="560" y="720" transform="translate(560 720) scale(1.8) translate(-560 -720)"/></g>
    </svg>
    <figcaption class="absolute bottom-6 left-6 rounded-full bg-[rgba(15,22,20,.78)] px-3.5 py-2 text-[.82rem] font-semibold text-white backdrop-blur-[6px]">Bend, Oregon · 44.06° N</figcaption>
  </figure>
</main>
CSS
/* The image is an inline SVG here; swap it for <img src="store.jpg" alt="…" class="absolute inset-0 size-full object-cover">. */
@theme {
  --color-sp-bg: #f5efe4;
  --color-sp-ink: #1f2a1f;
  --color-sp-muted: #4e5a4c;
  --color-sp-accent: #2f5d3a;
  --color-sp-accent-hover: #244a2e;
  --color-sp-line: #8c8a7c;
  --color-sp-err: #a42a1c;
  --font-sp-slab: Rockwell, "Roboto Slab", "Iowan Old Style", Georgia, serif;
}
JavaScript
// Sign-up form, front end only: it checks the address and shows the confirmation.
// Nothing is sent. Point the form's action at your mailing-list provider, or post it with fetch() below.
document.querySelectorAll('form[data-signup]').forEach(function (form) {
  var input = form.querySelector('input[type="email"]');
  var error = form.querySelector('[data-error]');
  var done = document.getElementById(form.getAttribute('data-signup'));
  form.noValidate = true; // our message replaces the browser bubble; without JS, native validation still works
  function clear() { error.textContent = ''; input.removeAttribute('aria-invalid'); }
  input.addEventListener('input', clear);
  form.addEventListener('submit', function (e) {
    e.preventDefault();
    var value = input.value.trim();
    if (!value || !input.checkValidity()) {
      error.textContent = value ? 'Check the address: it needs an @ and a domain, like [email protected].' : 'Enter your email address.';
      input.setAttribute('aria-invalid', 'true');
      input.focus();
      return;
    }
    clear();
    form.hidden = true;
    done.hidden = false;
    done.focus();
  });
});

About this set

A two-column coming soon page with the message on one side and a full-height image on the other. The demo is an outdoor shop opening its first store: a logo, an eyebrow with a rule, a slab-serif headline with the opening date, a paragraph with the address, a two-item fact strip for the opening day and what is free on the day, and a form to request an invite to opening night with a visible label. The right half is an inline SVG landscape of ridges and pines at sunrise with a location caption in a dark pill, and a comment shows how to swap in a photo with an img element. Use it for a physical opening, a restaurant, a studio or anything where a picture sells the place better than words. Both halves are equal columns that fill the viewport. Below 768 pixels the image moves to the top as a 240 pixel band and the content follows. The invite form is front end only: it validates the address and shows a confirmation that tells the visitor what happens next.

What’s included

  • Equal split with a full-height image column
  • Inline SVG landscape, with a comment on swapping in a photo
  • Opening facts in a description list
  • Visible form label and an invite confirmation
  • Image moves to a top band on small screens

Accessibility

The illustration has role="img" and an aria-label, and the caption is real text in a figcaption with 4.5:1 contrast on its dark pill. The form label is visible, the error is an aria-live region linked with aria-describedby, the field gets aria-invalid when wrong, and focus moves to the confirmation on success. Links, the field and the button show a 2px green focus outline, and the button's transition is removed under prefers-reduced-motion.

Customise it

Colours are --sp-bg, --sp-ink and --sp-accent on .sp (plain CSS and Bootstrap) or the --color-sp-* theme colours in Tailwind, and the headline font is the Rockwell stack. To use a photo, put an img inside .sp-media; it is absolutely positioned with object-fit: cover.