Shop — Grocery / Essentials Landing
A bright, fresh landing page for FreshCart, a fictional grocery delivery service, built entirely from CSS gradients, inline SVG and emoji instead of images. A hero pairs a 30-minute delivery promise with a working ZIP-code checker, backed by a category grid, a today's-deals row with live add-to-basket, a three-step how-it-works strip and an app sign-up call-to-action. Vanilla JS drives the cart counter, postcode lookup, toasts and smooth scroll.
MCP
Code
:root {
--bg: #ffffff;
--ink: #16201a;
--muted: #5f6f66;
--green: #1f9d55;
--green-d: #157a41;
--green-l: #eafbe7;
--orange: #ff8a1f;
--orange-d: #e0700a;
--sale: #e0245e;
--ok: #1f9d55;
--line: rgba(16, 28, 22, .1);
--soft: #f4f8f4;
--shadow: 0 10px 30px -12px rgba(20, 60, 35, .25);
--radius: 16px;
--maxw: 1140px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
margin: 0;
font-family: "Plus Jakarta Sans", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img, svg { display: block; max-width: 100%; }
h1, h2, h3, h4 { line-height: 1.15; margin: 0; letter-spacing: -.02em; }
a { color: inherit; }
.wrap { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }
.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;
}
.skip-link {
position: absolute; left: 12px; top: -48px; z-index: 100;
background: var(--green); color: #fff; padding: 10px 16px;
border-radius: 10px; font-weight: 600; text-decoration: none;
transition: top .15s ease;
}
.skip-link:focus { top: 12px; }
:focus-visible {
outline: 3px solid var(--orange);
outline-offset: 2px;
border-radius: 6px;
}
/* ---------- Buttons ---------- */
.btn {
font: inherit; font-weight: 700; cursor: pointer;
border: 0; border-radius: 12px; padding: 13px 20px;
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
transition: transform .12s ease, background .15s ease, box-shadow .15s ease;
white-space: nowrap;
}
.btn:active { transform: translateY(1px) scale(.99); }
.btn-primary { background: var(--green); color: #fff; box-shadow: 0 8px 18px -8px rgba(31, 157, 85, .7); }
.btn-primary:hover { background: var(--green-d); }
.btn-light { background: #fff; color: var(--green-d); }
.btn-light:hover { background: #f0fff5; }
/* ---------- Promo bar ---------- */
.promo-bar {
background: linear-gradient(90deg, var(--green) 0%, var(--green-d) 60%, var(--orange-d) 130%);
color: #fff; text-align: center; font-size: .86rem;
}
.promo-bar p { margin: 0; padding: 9px 16px; }
.promo-bar strong { font-weight: 800; }
/* ---------- Header ---------- */
.site-header {
position: sticky; top: 0; z-index: 40;
background: rgba(255, 255, 255, .9);
backdrop-filter: saturate(160%) blur(10px);
border-bottom: 1px solid var(--line);
}
.header-inner { display: flex; align-items: center; gap: 18px; height: 66px; }
.brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; color: var(--green-d); }
.brand-mark {
display: grid; place-items: center; width: 38px; height: 38px;
background: var(--green-l); border-radius: 11px; color: var(--green);
}
.brand-name { font-weight: 800; font-size: 1.22rem; letter-spacing: -.03em; color: var(--ink); }
.brand-name span { color: var(--orange-d); }
.main-nav { margin-left: auto; display: flex; gap: 6px; }
.main-nav a {
text-decoration: none; color: var(--ink); font-weight: 600; font-size: .94rem;
padding: 8px 12px; border-radius: 10px; transition: background .15s ease, color .15s ease;
}
.main-nav a:hover { background: var(--green-l); color: var(--green-d); }
.header-actions { display: flex; align-items: center; gap: 12px; margin-left: 6px; }
.deliver-chip {
display: inline-flex; align-items: center; gap: 7px;
background: var(--green-l); color: var(--green-d);
font-size: .82rem; font-weight: 700; padding: 7px 12px; border-radius: 999px;
max-width: 200px;
}
.deliver-chip .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 3px rgba(31,157,85,.2); }
.deliver-chip span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cart-btn {
position: relative; background: var(--ink); color: #fff;
border: 0; width: 44px; height: 44px; border-radius: 12px;
display: grid; place-items: center; cursor: pointer;
transition: transform .12s ease, background .15s ease;
}
.cart-btn:hover { background: #000; }
.cart-btn:active { transform: scale(.94); }
.cart-count {
position: absolute; top: -7px; right: -7px; min-width: 21px; height: 21px;
background: var(--orange); color: #fff; border-radius: 999px;
font-size: .72rem; font-weight: 800; display: grid; place-items: center;
padding: 0 5px; border: 2px solid #fff;
}
.cart-btn.bump { animation: bump .35s ease; }
@keyframes bump { 0%,100% { transform: scale(1); } 40% { transform: scale(1.18); } }
/* ---------- Hero ---------- */
.hero {
background:
radial-gradient(1100px 460px at 88% -10%, rgba(255, 138, 31, .16), transparent 60%),
radial-gradient(900px 480px at 6% 0%, rgba(31, 157, 85, .14), transparent 55%),
linear-gradient(180deg, #f6fbf6, #ffffff 70%);
padding: 56px 0 60px;
}
.hero-grid {
display: grid; grid-template-columns: 1.05fr .95fr; gap: 48px; align-items: center;
}
.hero-copy { max-width: 560px; }
.eyebrow {
display: inline-flex; align-items: center; gap: 8px;
background: #fff; color: var(--green-d); border: 1px solid var(--line);
font-weight: 700; font-size: .82rem; padding: 6px 13px; border-radius: 999px;
box-shadow: var(--shadow);
}
.eyebrow.light { background: rgba(255,255,255,.16); color: #fff; border-color: rgba(255,255,255,.3); box-shadow: none; }
.pulse { width: 9px; height: 9px; border-radius: 50%; background: var(--green); position: relative; }
.eyebrow.light .pulse { background: #fff; }
.pulse::after {
content: ""; position: absolute; inset: -4px; border-radius: 50%;
border: 2px solid var(--green); animation: ping 1.6s ease-out infinite;
}
.eyebrow.light .pulse::after { border-color: #fff; }
@keyframes ping { 0% { transform: scale(.6); opacity: 1; } 100% { transform: scale(1.9); opacity: 0; } }
.hero h1 { font-size: clamp(2.1rem, 5vw, 3.3rem); font-weight: 800; margin: 16px 0 12px; }
.hero h1 .hl {
color: var(--green);
background: linear-gradient(120deg, var(--green) 0%, var(--orange-d) 130%);
-webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.lede { color: var(--muted); font-size: 1.06rem; margin: 0 0 22px; }
.zip-form { display: flex; gap: 10px; max-width: 460px; }
.zip-field {
flex: 1; display: flex; align-items: center; gap: 9px;
background: #fff; border: 1.5px solid var(--line); border-radius: 12px;
padding: 0 12px; color: var(--muted); transition: border-color .15s ease, box-shadow .15s ease;
}
.zip-field:focus-within { border-color: var(--green); box-shadow: 0 0 0 3px rgba(31,157,85,.16); }
.zip-field input {
flex: 1; border: 0; outline: none; background: transparent;
font: inherit; color: var(--ink); padding: 13px 0; min-width: 0;
}
.zip-field input::placeholder { color: #98a69e; }
.zip-msg { min-height: 22px; margin: 12px 0 0; font-weight: 700; font-size: .92rem; }
.zip-msg.ok { color: var(--green-d); }
.zip-msg.err { color: var(--sale); }
.zip-msg .eta { color: var(--orange-d); }
.trust-row {
list-style: none; display: flex; flex-wrap: wrap; gap: 22px;
margin: 26px 0 0; padding: 18px 0 0; border-top: 1px solid var(--line);
color: var(--muted); font-size: .9rem;
}
.trust-row strong { display: block; color: var(--ink); font-size: 1.18rem; font-weight: 800; }
/* Hero art */
.hero-art { position: relative; display: grid; place-items: center; min-height: 360px; }
.bag {
position: relative; width: 260px; height: 290px;
filter: drop-shadow(0 24px 36px rgba(20, 60, 35, .22));
}
.bag-body {
position: absolute; inset: 36px 0 0; border-radius: 16px 16px 26px 26px;
background: linear-gradient(160deg, #fff7ec, #ffe9cf);
border: 2px solid rgba(224,112,10,.25);
display: grid; grid-template-columns: repeat(3, 1fr); align-content: end;
gap: 6px; padding: 14px;
}
.bag-top {
position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
width: 150px; height: 36px; border-radius: 12px 12px 0 0;
background: linear-gradient(180deg, #ffe9cf, #ffd9a8);
border: 2px solid rgba(224,112,10,.25); border-bottom: 0;
}
.veg {
font-size: 2.1rem; text-align: center; line-height: 1;
filter: drop-shadow(0 4px 6px rgba(0,0,0,.12));
animation: floaty 4s ease-in-out infinite;
}
.veg.carrot { animation-delay: .3s; } .veg.bread { animation-delay: .6s; }
.veg.milk { animation-delay: .9s; } .veg.apple { animation-delay: 1.2s; } .veg.tomato { animation-delay: 1.5s; }
@keyframes floaty { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
.eta-badge {
position: absolute; bottom: 8px; right: 0;
background: var(--green); color: #fff; border-radius: 16px;
padding: 14px 18px; text-align: center; box-shadow: var(--shadow);
border: 3px solid #fff;
}
.eta-num { display: block; font-size: 1.8rem; font-weight: 800; line-height: 1; }
.eta-num small { font-size: .7rem; font-weight: 700; margin-left: 2px; }
.eta-label { font-size: .72rem; opacity: .92; font-weight: 600; }
/* ---------- Sections ---------- */
.section { padding: 56px 0; }
.sec-head { text-align: center; max-width: 620px; margin: 0 auto 34px; }
.sec-head h2 { font-size: clamp(1.6rem, 3.4vw, 2.2rem); font-weight: 800; }
.sec-head p { color: var(--muted); margin: 10px 0 0; }
/* Categories */
.cat-grid {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px;
}
.cat-tile {
width: 100%; cursor: pointer; font: inherit; text-align: left;
background: var(--tile, var(--soft)); border: 1px solid var(--line);
border-radius: var(--radius); padding: 18px;
display: flex; flex-direction: column; gap: 4px;
transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.cat-tile:hover { transform: translateY(-4px); box-shadow: var(--shadow); border-color: transparent; }
.cat-ico {
font-size: 1.9rem; width: 52px; height: 52px; border-radius: 14px;
background: rgba(255,255,255,.7); display: grid; place-items: center;
margin-bottom: 8px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.04);
}
.cat-name { font-weight: 700; font-size: 1.02rem; }
.cat-meta { color: var(--muted); font-size: .82rem; }
/* Deals */
.deals-sec { background: var(--soft); }
.deal-grid {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px;
}
.deal-card {
background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
overflow: hidden; display: flex; flex-direction: column;
transition: transform .14s ease, box-shadow .14s ease;
}
.deal-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.deal-photo {
position: relative; aspect-ratio: 4 / 3; display: grid; place-items: center;
font-size: 3.4rem; background: var(--tint, #eef7ef);
}
.deal-photo::after {
content: ""; position: absolute; inset: 0;
background: radial-gradient(120px 90px at 30% 28%, rgba(255,255,255,.55), transparent 70%);
}
.deal-badge {
position: absolute; top: 10px; left: 10px; z-index: 1;
background: var(--sale); color: #fff; font-size: .72rem; font-weight: 800;
padding: 4px 9px; border-radius: 999px;
}
.deal-body { padding: 14px; display: flex; flex-direction: column; gap: 7px; flex: 1; }
.deal-name { font-weight: 700; font-size: 1rem; line-height: 1.25; }
.deal-unit { color: var(--muted); font-size: .82rem; }
.deal-rating { font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: 5px; }
.deal-rating .stars { color: var(--orange); letter-spacing: 1px; }
.deal-price-row { display: flex; align-items: baseline; gap: 8px; margin-top: auto; }
.deal-price { font-weight: 800; font-size: 1.2rem; color: var(--ink); }
.deal-was { color: var(--muted); text-decoration: line-through; font-size: .86rem; }
.deal-stock { font-size: .76rem; font-weight: 700; color: var(--green-d); display: inline-flex; align-items: center; gap: 5px; }
.deal-stock.low { color: var(--orange-d); }
.deal-add {
margin-top: 10px; width: 100%; border: 0; cursor: pointer; font: inherit; font-weight: 700;
background: var(--green-l); color: var(--green-d); border-radius: 11px; padding: 11px;
display: inline-flex; align-items: center; justify-content: center; gap: 7px;
transition: background .14s ease, color .14s ease, transform .1s ease;
}
.deal-add:hover { background: var(--green); color: #fff; }
.deal-add:active { transform: scale(.97); }
.deal-add.added { background: var(--ink); color: #fff; }
/* How it works */
.how-grid {
list-style: none; margin: 0; padding: 0;
display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px;
}
.how-step {
position: relative; background: #fff; border: 1px solid var(--line);
border-radius: var(--radius); padding: 26px 22px 24px; text-align: center;
}
.how-num {
position: absolute; top: -16px; left: 50%; transform: translateX(-50%);
width: 34px; height: 34px; border-radius: 50%; background: var(--orange); color: #fff;
font-weight: 800; display: grid; place-items: center; border: 3px solid #fff; box-shadow: var(--shadow);
}
.how-ico {
font-size: 2.4rem; display: grid; place-items: center;
width: 72px; height: 72px; margin: 8px auto 14px; border-radius: 20px; background: var(--green-l);
}
.how-step h3 { font-size: 1.1rem; font-weight: 700; margin-bottom: 6px; }
.how-step p { color: var(--muted); font-size: .92rem; margin: 0; }
.how-grid::after { content: none; }
/* App CTA */
.app-sec { padding: 0; }
.app-grid {
background: linear-gradient(135deg, var(--green-d) 0%, var(--green) 55%, #2bb56a 110%);
border-radius: 28px; color: #fff; overflow: hidden;
display: grid; grid-template-columns: 1.1fr .9fr; gap: 30px; align-items: center;
padding: 44px; margin: 56px 0;
box-shadow: 0 30px 60px -30px rgba(21, 122, 65, .8);
}
.app-copy { max-width: 480px; }
.app-grid h2 { font-size: clamp(1.7rem, 3.6vw, 2.4rem); font-weight: 800; margin: 14px 0 10px; }
.app-copy p { color: rgba(255,255,255,.9); margin: 0 0 20px; }
.app-form { display: flex; gap: 10px; max-width: 420px; }
.app-form input {
flex: 1; border: 0; border-radius: 12px; padding: 13px 14px;
font: inherit; min-width: 0; color: var(--ink);
}
.app-form input:focus-visible { outline: 3px solid var(--orange); outline-offset: 2px; }
.app-msg { min-height: 22px; margin: 12px 0 0; font-weight: 700; font-size: .92rem; color: #fff7ec; }
.store-badges { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; align-items: center; }
.store-badge {
background: rgba(0,0,0,.28); border: 1px solid rgba(255,255,255,.25);
border-radius: 10px; padding: 9px 14px; font-weight: 700; font-size: .88rem;
}
.rating-badge { font-size: .86rem; font-weight: 700; color: #fff7ec; }
.app-art { display: grid; place-items: center; }
.phone {
position: relative; width: 220px; height: 340px;
background: #0e1712; border-radius: 34px; padding: 12px;
border: 3px solid rgba(255,255,255,.18);
box-shadow: 0 30px 50px -20px rgba(0,0,0,.5);
}
.phone-notch {
position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
width: 80px; height: 18px; background: #0e1712; border-radius: 0 0 12px 12px; z-index: 2;
}
.phone-screen {
height: 100%; background: #f6fbf6; border-radius: 24px; padding: 28px 14px 14px;
display: flex; flex-direction: column; gap: 9px; overflow: hidden;
}
.ps-bar { display: flex; gap: 6px; margin-bottom: 4px; }
.ps-bar span { height: 8px; border-radius: 6px; background: var(--green-l); }
.ps-bar span:first-child { width: 56px; background: var(--green); }
.ps-bar span:last-child { flex: 1; }
.ps-card {
background: #fff; border: 1px solid var(--line); border-radius: 12px;
padding: 9px; display: flex; align-items: center; gap: 9px;
}
.ps-emoji { font-size: 1.5rem; }
.ps-lines { flex: 1; display: flex; flex-direction: column; gap: 5px; }
.ps-lines i { height: 7px; border-radius: 5px; background: #e4eee7; }
.ps-price { font-weight: 800; font-size: .82rem; color: var(--green-d); }
.ps-cta {
margin-top: auto; background: var(--orange); color: #fff; text-align: center;
font-weight: 800; font-size: .82rem; padding: 11px; border-radius: 12px;
}
/* ---------- Footer ---------- */
.site-footer { border-top: 1px solid var(--line); background: var(--soft); margin-top: 10px; }
.footer-inner {
display: grid; grid-template-columns: 1.4fr 2fr; gap: 30px;
padding: 44px 20px 26px;
}
.footer-brand .brand-name { font-size: 1.3rem; }
.footer-brand p { color: var(--muted); font-size: .9rem; margin: 10px 0 0; max-width: 280px; }
.footer-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.footer-cols h4 { font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin-bottom: 12px; }
.footer-cols a { display: block; text-decoration: none; color: var(--ink); font-size: .92rem; padding: 4px 0; }
.footer-cols a:hover { color: var(--green-d); }
.footer-bottom {
display: flex; flex-wrap: wrap; gap: 10px; justify-content: space-between;
padding: 16px 20px 28px; border-top: 1px solid var(--line);
color: var(--muted); font-size: .84rem;
}
.footer-bottom p { margin: 0; }
/* ---------- Toast ---------- */
.toast-wrap {
position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
z-index: 90; display: flex; flex-direction: column; gap: 10px; align-items: center;
pointer-events: none; width: max-content; max-width: calc(100vw - 40px);
}
.toast {
background: var(--ink); color: #fff; padding: 13px 18px; border-radius: 12px;
font-weight: 600; font-size: .92rem; box-shadow: var(--shadow);
display: flex; align-items: center; gap: 9px;
animation: toastIn .3s cubic-bezier(.2,.9,.3,1.3);
}
.toast.leave { animation: toastOut .25s ease forwards; }
.toast .t-ico { font-size: 1.1rem; }
@keyframes toastIn { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(10px) scale(.97); } }
/* ---------- Responsive ---------- */
@media (max-width: 980px) {
.hero-grid { grid-template-columns: 1fr; gap: 36px; }
.hero-art { order: -1; min-height: 280px; }
.cat-grid { grid-template-columns: repeat(3, 1fr); }
.deal-grid { grid-template-columns: repeat(2, 1fr); }
.app-grid { grid-template-columns: 1fr; padding: 36px; }
.app-art { order: -1; }
.footer-inner { grid-template-columns: 1fr; }
}
@media (max-width: 720px) {
.main-nav { display: none; }
.how-grid { grid-template-columns: 1fr; gap: 28px; }
.cat-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
.promo-bar p { font-size: .78rem; }
.deliver-chip { display: none !important; }
.section { padding: 44px 0; }
.zip-form { flex-direction: column; }
.zip-form .btn { width: 100%; }
.app-form { flex-direction: column; }
.app-form .btn { width: 100%; }
.footer-cols { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 380px) {
.deal-grid { grid-template-columns: 1fr; }
.trust-row { gap: 16px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastWrap = document.getElementById("toastWrap");
function toast(msg, icon) {
var el = document.createElement("div");
el.className = "toast";
el.innerHTML = '<span class="t-ico" aria-hidden="true">' + (icon || "✅") + "</span><span></span>";
el.querySelector("span:last-child").textContent = msg;
toastWrap.appendChild(el);
setTimeout(function () {
el.classList.add("leave");
el.addEventListener("animationend", function () { el.remove(); }, { once: true });
}, 2600);
}
/* ---------- Money formatter ---------- */
function money(n) {
return "$" + n.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
/* ---------- Cart state ---------- */
var cart = 0;
var cartCount = document.getElementById("cartCount");
var cartBtn = document.getElementById("cartBtn");
function addToCart(qty, label) {
cart += qty;
cartCount.textContent = String(cart);
cartBtn.setAttribute("aria-label", "Cart, " + cart + " item" + (cart === 1 ? "" : "s"));
cartBtn.classList.remove("bump");
void cartBtn.offsetWidth; // reflow to restart animation
cartBtn.classList.add("bump");
if (label) toast(label + " added to basket", "🛒");
}
cartBtn.addEventListener("click", function () {
if (cart === 0) toast("Your basket is empty — add some deals!", "🧺");
else toast(cart + " item" + (cart === 1 ? "" : "s") + " in your basket · " + money(cart * 3.2) + " est.", "🧾");
});
/* ---------- ZIP / postcode delivery check ---------- */
// Fictional serviceable ZIPs mapped to a neighbourhood + ETA.
var ZONES = {
"10001": { area: "Chelsea, NY", eta: 25 },
"10003": { area: "East Village, NY", eta: 28 },
"11201": { area: "Brooklyn Heights, NY", eta: 30 },
"94103": { area: "SoMa, San Francisco", eta: 22 },
"90012": { area: "Downtown LA", eta: 35 },
"60601": { area: "The Loop, Chicago", eta: 30 },
"02108": { area: "Beacon Hill, Boston", eta: 27 },
"98101": { area: "Downtown Seattle", eta: 32 }
};
var zipForm = document.getElementById("zipForm");
var zipInput = document.getElementById("zip");
var zipMsg = document.getElementById("zipMsg");
var headerChip = document.getElementById("headerDeliverChip");
var headerText = document.getElementById("headerDeliverText");
zipInput.addEventListener("input", function () {
this.value = this.value.replace(/[^0-9]/g, "").slice(0, 5);
zipMsg.className = "zip-msg";
zipMsg.textContent = "";
});
zipForm.addEventListener("submit", function (e) {
e.preventDefault();
var val = zipInput.value.trim();
if (val.length !== 5) {
zipMsg.className = "zip-msg err";
zipMsg.textContent = "⚠ Please enter a 5-digit ZIP code.";
zipInput.focus();
return;
}
var zone = ZONES[val];
if (zone) {
zipMsg.className = "zip-msg ok";
zipMsg.innerHTML = "🎉 Great news — we deliver to <strong>" + zone.area +
"</strong>! Next slot in <span class=\"eta\">~" + zone.eta + " min</span>.";
headerChip.hidden = false;
headerText.textContent = "Deliver to " + zone.area;
toast("We deliver to " + zone.area + "!", "🚲");
} else {
// Deterministic "coming soon" fallback so unknown ZIPs still get a friendly answer.
var days = (parseInt(val.slice(-2), 10) % 6) + 2;
zipMsg.className = "zip-msg err";
zipMsg.innerHTML = "😔 Not in your area just yet — but we're expanding fast. " +
"We expect to reach <strong>" + val + "</strong> in about <strong>" + days + " weeks</strong>.";
headerChip.hidden = true;
toast("We'll email you when we reach " + val, "📬");
}
});
/* ---------- Category tiles ---------- */
Array.prototype.forEach.call(document.querySelectorAll(".cat-tile"), function (tile) {
tile.addEventListener("click", function () {
var name = tile.getAttribute("data-cat").replace(/&/g, "&");
toast("Browsing " + name + " — opening aisle…", "🔎");
});
});
/* ---------- Deals: data + render ---------- */
var DEALS = [
{ name: "Organic Hass Avocados", unit: "Pack of 4", emoji: "🥑", tint: "#eafbe7", price: 4.49, was: 6.99, rating: 4.8, reviews: 1240, stock: "In stock", low: false, badge: "-36%" },
{ name: "Free-Range Eggs", unit: "Dozen, large", emoji: "🥚", tint: "#fff6e0", price: 3.29, was: 4.50, rating: 4.9, reviews: 980, stock: "In stock", low: false, badge: "-27%" },
{ name: "Sourdough Loaf", unit: "Freshly baked, 800g", emoji: "🍞", tint: "#fbeede", price: 3.10, was: 4.20, rating: 4.7, reviews: 612, stock: "Low stock", low: true, badge: "-26%" },
{ name: "Cherry Tomatoes", unit: "Vine-ripened, 500g", emoji: "🍅", tint: "#fde9ec", price: 2.20, was: 3.00, rating: 4.6, reviews: 430, stock: "In stock", low: false, badge: "-27%" },
{ name: "Greek Yogurt", unit: "Natural, 1kg tub", emoji: "🥛", tint: "#eef1ff", price: 2.80, was: 3.80, rating: 4.8, reviews: 720, stock: "In stock", low: false, badge: "-26%" },
{ name: "Honeycrisp Apples", unit: "Bag of 6", emoji: "🍎", tint: "#fdeede", price: 3.95, was: 5.10, rating: 4.7, reviews: 540, stock: "In stock", low: false, badge: "-23%" },
{ name: "Cold-Pressed Orange Juice", unit: "1L bottle", emoji: "🧃", tint: "#e6f7fb", price: 4.10, was: 5.50, rating: 4.9, reviews: 305, stock: "Low stock", low: true, badge: "-25%" },
{ name: "Aged Cheddar", unit: "Block, 250g", emoji: "🧀", tint: "#fff3d6", price: 4.75, was: 6.20, rating: 4.8, reviews: 388, stock: "In stock", low: false, badge: "-23%" }
];
var grid = document.getElementById("dealGrid");
DEALS.forEach(function (d, i) {
var stars = "★★★★★".slice(0, Math.round(d.rating)) + "☆☆☆☆☆".slice(0, 5 - Math.round(d.rating));
var li = document.createElement("li");
li.className = "deal-card";
li.innerHTML =
'<div class="deal-photo" style="--tint:' + d.tint + '">' +
'<span class="deal-badge">' + d.badge + '</span>' +
'<span aria-hidden="true">' + d.emoji + '</span>' +
"</div>" +
'<div class="deal-body">' +
'<h3 class="deal-name">' + d.name + "</h3>" +
'<p class="deal-unit">' + d.unit + "</p>" +
'<p class="deal-rating"><span class="stars" aria-hidden="true">' + stars + "</span>" +
'<span class="sr-only">' + d.rating + " out of 5</span> " + d.rating +
" (" + d.reviews.toLocaleString("en-US") + ")</p>" +
'<div class="deal-price-row">' +
'<span class="deal-price">' + money(d.price) + "</span>" +
'<span class="deal-was">' + money(d.was) + "</span>" +
"</div>" +
'<span class="deal-stock' + (d.low ? " low" : "") + '">' +
(d.low ? "⚡ " : "✓ ") + d.stock + "</span>" +
'<button class="deal-add" type="button">+ Add to basket</button>' +
"</div>";
var btn = li.querySelector(".deal-add");
btn.addEventListener("click", function () {
addToCart(1, d.name);
btn.classList.add("added");
btn.innerHTML = "✓ Added";
setTimeout(function () {
btn.classList.remove("added");
btn.innerHTML = "+ Add to basket";
}, 1400);
});
grid.appendChild(li);
});
/* ---------- App signup ---------- */
var appForm = document.getElementById("appForm");
var phone = document.getElementById("phone");
var appMsg = document.getElementById("appMsg");
appForm.addEventListener("submit", function (e) {
e.preventDefault();
var digits = phone.value.replace(/[^0-9]/g, "");
if (digits.length < 7) {
appMsg.textContent = "⚠ Please enter a valid mobile number.";
phone.focus();
return;
}
appMsg.textContent = "📲 Done! We've texted a download link to your phone.";
toast("Download link sent — check your messages", "📲");
appForm.reset();
});
/* ---------- Smooth-scroll for in-page nav (respects reduced motion via CSS) ---------- */
Array.prototype.forEach.call(document.querySelectorAll('a[href^="#"]'), function (a) {
a.addEventListener("click", function (e) {
var id = a.getAttribute("href");
if (id.length < 2) return;
var target = document.querySelector(id);
if (target) {
e.preventDefault();
target.scrollIntoView({ block: "start" });
target.setAttribute("tabindex", "-1");
target.focus({ preventScroll: true });
}
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FreshCart — Groceries delivered in 30 minutes</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<div class="promo-bar" role="region" aria-label="Promotion">
<p>🥕 Free delivery on your first 3 orders over <strong>$35</strong> — use code <strong>FRESH30</strong></p>
</div>
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="#" aria-label="FreshCart home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 32 32" width="28" height="28" fill="none">
<path d="M4 7h3l3.2 13.4a2 2 0 0 0 2 1.6h9.1a2 2 0 0 0 2-1.5L28 11H9" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="13" cy="27" r="2" fill="currentColor"/>
<circle cx="24" cy="27" r="2" fill="currentColor"/>
</svg>
</span>
<span class="brand-name">Fresh<span>Cart</span></span>
</a>
<nav class="main-nav" aria-label="Primary">
<a href="#categories">Shop</a>
<a href="#deals">Deals</a>
<a href="#how">How it works</a>
<a href="#app">Get the app</a>
</nav>
<div class="header-actions">
<span class="deliver-chip" id="headerDeliverChip" hidden>
<span class="dot" aria-hidden="true"></span>
<span id="headerDeliverText">Deliver to —</span>
</span>
<button class="cart-btn" id="cartBtn" type="button" aria-label="Cart, 0 items">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
<path d="M3 5h2l2.4 10.1a1.5 1.5 0 0 0 1.46 1.15h7.1a1.5 1.5 0 0 0 1.46-1.1L20 8H6.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="10" cy="20" r="1.4" fill="currentColor"/>
<circle cx="17" cy="20" r="1.4" fill="currentColor"/>
</svg>
<span class="cart-count" id="cartCount" aria-hidden="true">0</span>
</button>
</div>
</div>
</header>
<main id="main">
<!-- HERO -->
<section class="hero" aria-labelledby="hero-title">
<div class="wrap hero-grid">
<div class="hero-copy">
<span class="eyebrow">
<span class="pulse" aria-hidden="true"></span>
Delivering now in your area
</span>
<h1 id="hero-title">Fresh groceries at your door in <span class="hl">30 minutes</span></h1>
<p class="lede">Farm-fresh produce, dairy, bakery and pantry staples — hand-picked and delivered fast. No queues, no parking, no carrying bags up the stairs.</p>
<form class="zip-form" id="zipForm" novalidate>
<label class="sr-only" for="zip">Enter your ZIP or postcode</label>
<div class="zip-field">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" aria-hidden="true">
<path d="M12 21s7-5.6 7-11a7 7 0 1 0-14 0c0 5.4 7 11 7 11Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
<circle cx="12" cy="10" r="2.6" stroke="currentColor" stroke-width="1.8"/>
</svg>
<input id="zip" name="zip" type="text" inputmode="numeric" autocomplete="postal-code"
placeholder="Enter ZIP code (try 10001)" aria-describedby="zipMsg" maxlength="5" />
</div>
<button class="btn btn-primary" type="submit">Check delivery</button>
</form>
<p class="zip-msg" id="zipMsg" role="status" aria-live="polite"></p>
<ul class="trust-row" aria-label="Why shop with us">
<li><strong>30 min</strong> avg delivery</li>
<li><strong>4.9★</strong> 28k reviews</li>
<li><strong>0%</strong> markup on produce</li>
</ul>
</div>
<div class="hero-art" aria-hidden="true">
<div class="bag">
<div class="bag-top"></div>
<div class="bag-body">
<span class="veg leaf">🥬</span>
<span class="veg carrot">🥕</span>
<span class="veg bread">🥖</span>
<span class="veg milk">🥛</span>
<span class="veg apple">🍎</span>
<span class="veg tomato">🍅</span>
</div>
</div>
<div class="eta-badge">
<span class="eta-num">30<small>min</small></span>
<span class="eta-label">to your door</span>
</div>
</div>
</div>
</section>
<!-- CATEGORIES -->
<section class="section" id="categories" aria-labelledby="cat-title">
<div class="wrap">
<div class="sec-head">
<h2 id="cat-title">Shop by category</h2>
<p>Over 6,000 everyday items, all in one basket.</p>
</div>
<ul class="cat-grid" role="list">
<li><button class="cat-tile" data-cat="Produce" style="--tile:#eafbe7;--ico:#1f9d55"><span class="cat-ico" aria-hidden="true">🥦</span><span class="cat-name">Produce</span><span class="cat-meta">412 items</span></button></li>
<li><button class="cat-tile" data-cat="Dairy & Eggs" style="--tile:#fff6e0;--ico:#d98a00"><span class="cat-ico" aria-hidden="true">🧀</span><span class="cat-name">Dairy & Eggs</span><span class="cat-meta">186 items</span></button></li>
<li><button class="cat-tile" data-cat="Bakery" style="--tile:#fbeede;--ico:#b46a1a"><span class="cat-ico" aria-hidden="true">🥐</span><span class="cat-name">Bakery</span><span class="cat-meta">94 items</span></button></li>
<li><button class="cat-tile" data-cat="Meat & Fish" style="--tile:#fde9ec;--ico:#e0245e"><span class="cat-ico" aria-hidden="true">🐟</span><span class="cat-name">Meat & Fish</span><span class="cat-meta">138 items</span></button></li>
<li><button class="cat-tile" data-cat="Pantry" style="--tile:#eef1ff;--ico:#3457ff"><span class="cat-ico" aria-hidden="true">🥫</span><span class="cat-name">Pantry</span><span class="cat-meta">523 items</span></button></li>
<li><button class="cat-tile" data-cat="Beverages" style="--tile:#e6f7fb;--ico:#0aa4c2"><span class="cat-ico" aria-hidden="true">🧃</span><span class="cat-name">Beverages</span><span class="cat-meta">271 items</span></button></li>
<li><button class="cat-tile" data-cat="Frozen" style="--tile:#eef9ff;--ico:#2a86e0"><span class="cat-ico" aria-hidden="true">🍦</span><span class="cat-name">Frozen</span><span class="cat-meta">160 items</span></button></li>
<li><button class="cat-tile" data-cat="Household" style="--tile:#f1f0f6;--ico:#6b5bd2"><span class="cat-ico" aria-hidden="true">🧼</span><span class="cat-name">Household</span><span class="cat-meta">204 items</span></button></li>
</ul>
</div>
</section>
<!-- DEALS -->
<section class="section deals-sec" id="deals" aria-labelledby="deals-title">
<div class="wrap">
<div class="sec-head">
<h2 id="deals-title">Deals today</h2>
<p>Fresh prices, updated every morning. Tap to add to your basket.</p>
</div>
<ul class="deal-grid" id="dealGrid" role="list"><!-- injected by script.js --></ul>
</div>
</section>
<!-- HOW DELIVERY WORKS -->
<section class="section how-sec" id="how" aria-labelledby="how-title">
<div class="wrap">
<div class="sec-head">
<h2 id="how-title">How delivery works</h2>
<p>From basket to doorstep in three simple steps.</p>
</div>
<ol class="how-grid" role="list">
<li class="how-step">
<span class="how-num" aria-hidden="true">1</span>
<span class="how-ico" aria-hidden="true">🛒</span>
<h3>Fill your basket</h3>
<p>Browse thousands of items and add what you need. Smart suggestions help you skip nothing.</p>
</li>
<li class="how-step">
<span class="how-num" aria-hidden="true">2</span>
<span class="how-ico" aria-hidden="true">🧑🌾</span>
<h3>We hand-pick it</h3>
<p>Local shoppers select the freshest produce and check every expiry date for you.</p>
</li>
<li class="how-step">
<span class="how-num" aria-hidden="true">3</span>
<span class="how-ico" aria-hidden="true">🚲</span>
<h3>30-min delivery</h3>
<p>A rider brings it to your door, contact-free if you like. Track it live the whole way.</p>
</li>
</ol>
</div>
</section>
<!-- APP CTA -->
<section class="section app-sec" id="app" aria-labelledby="app-title">
<div class="wrap app-grid">
<div class="app-copy">
<span class="eyebrow light"><span class="pulse" aria-hidden="true"></span> New: 1-tap reorder</span>
<h2 id="app-title">Get the FreshCart app</h2>
<p>Reorder your weekly staples in seconds, track your rider on a live map, and unlock app-only deals. Sign up and we'll text you a download link.</p>
<form class="app-form" id="appForm" novalidate>
<label class="sr-only" for="phone">Mobile number</label>
<input id="phone" name="phone" type="tel" inputmode="tel" placeholder="Your mobile number" aria-describedby="appMsg" />
<button class="btn btn-light" type="submit">Text me the app</button>
</form>
<p class="app-msg" id="appMsg" role="status" aria-live="polite"></p>
<div class="store-badges" aria-hidden="true">
<span class="store-badge">⌘ App Store</span>
<span class="store-badge">▶ Google Play</span>
<span class="rating-badge">4.9 ★ · 28k ratings</span>
</div>
</div>
<div class="app-art" aria-hidden="true">
<div class="phone">
<div class="phone-notch"></div>
<div class="phone-screen">
<div class="ps-bar"><span></span><span></span></div>
<div class="ps-card">
<span class="ps-emoji">🍎</span>
<div class="ps-lines"><i style="width:70%"></i><i style="width:40%"></i></div>
<span class="ps-price">$2.40</span>
</div>
<div class="ps-card">
<span class="ps-emoji">🥛</span>
<div class="ps-lines"><i style="width:60%"></i><i style="width:45%"></i></div>
<span class="ps-price">$1.80</span>
</div>
<div class="ps-card">
<span class="ps-emoji">🥖</span>
<div class="ps-lines"><i style="width:55%"></i><i style="width:35%"></i></div>
<span class="ps-price">$3.10</span>
</div>
<div class="ps-cta">Checkout · 30 min 🚲</div>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="wrap footer-inner">
<div class="footer-brand">
<span class="brand-name">Fresh<span>Cart</span></span>
<p>Fresh groceries, delivered fast. A fictional storefront demo.</p>
</div>
<nav class="footer-cols" aria-label="Footer">
<div>
<h4>Shop</h4>
<a href="#categories">Produce</a>
<a href="#categories">Bakery</a>
<a href="#deals">Today's deals</a>
</div>
<div>
<h4>Company</h4>
<a href="#how">How it works</a>
<a href="#app">Careers</a>
<a href="#app">Press</a>
</div>
<div>
<h4>Help</h4>
<a href="#app">Contact</a>
<a href="#app">Delivery areas</a>
<a href="#app">Returns</a>
</div>
</nav>
</div>
<div class="wrap footer-bottom">
<p>© 2026 FreshCart Co. · Illustrative demo, no real checkout.</p>
<p class="footer-trust">🔒 Secure checkout · 🍃 Carbon-neutral delivery</p>
</div>
</footer>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Grocery / Essentials Landing
A clean, friendly storefront landing for FreshCart, a fictional grocery and essentials delivery service. The look is white-and-ink with a fresh-green and orange accent system, set in Plus Jakarta Sans. Every visual — the produce-filled shopping bag, the phone mockup, the category tiles and deal cards — is drawn with CSS gradients, inline SVG and emoji, so there are no external images. A sticky header carries the brand, in-page nav, a live cart counter and a “deliver to” chip that fills in once you check your area.
The hero leads with a 30-minute delivery promise and a working ZIP / postcode checker:
type a serviceable code (try 10001) and it confirms the neighbourhood and the next delivery slot,
updating the header chip and firing a toast; unknown codes get a friendly, deterministic
“coming soon” estimate instead. Below, a shop-by-category grid covers Produce, Dairy & Eggs,
Bakery, Meat & Fish, Pantry, Beverages, Frozen and Household, and a deals today row renders
discounted products from data with star ratings, review counts, stock chips and a one-tap
add-to-basket button that bumps the cart and confirms the action.
A three-step how delivery works strip and a green app sign-up card round out the page, the
latter validating a phone number before texting a (pretend) download link. Everything is vanilla JS:
a toast() helper, money formatting, smooth in-page scrolling with focus management, and animations
that respect prefers-reduced-motion. Controls are keyboard-usable with visible focus rings,
contrast meets WCAG AA, and the layout reflows cleanly from wide screens down to ~360px.
Illustrative storefront UI only — fictional products, prices, and reviews. No real checkout.