Vet — Online Pharmacy / Store
A friendly pet-pharmacy storefront with a searchable, category-filterable product grid showing food, meds and supplies with prices, ratings and prescription badges, plus add-to-cart controls feeding a live cart count, slide-in mini-cart drawer and running total with quantity steppers.
MCP
Code
:root {
--teal: #179c8e;
--teal-d: #0f7f73;
--teal-50: #e6f5f2;
--coral: #ff8a5c;
--ink: #173a36;
--muted: #6b827e;
--bg: #f2f8f6;
--white: #ffffff;
--line: #d9ebe7;
--rx: #b4581f;
--rx-bg: #fff1e6;
--shadow: 0 10px 30px rgba(15, 127, 115, 0.12);
--radius: 20px;
--font: 'Inter', system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
padding: 24px 16px 64px;
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
}
.shell {
max-width: 1040px;
margin: 0 auto;
}
/* ---- Top bar ---- */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 16px 20px;
box-shadow: var(--shadow);
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.brand-mark {
display: grid;
place-items: center;
width: 48px;
height: 48px;
font-size: 24px;
background: var(--teal-50);
border-radius: 16px;
}
.topbar h1 {
font-size: 20px;
font-weight: 800;
letter-spacing: -0.02em;
}
.brand-sub {
font-size: 13px;
color: var(--muted);
font-weight: 500;
}
.cart-btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: inherit;
font-size: 15px;
font-weight: 600;
color: var(--white);
background: var(--teal);
border: none;
border-radius: 999px;
padding: 11px 18px;
cursor: pointer;
transition: background 0.18s, transform 0.12s;
}
.cart-btn:hover { background: var(--teal-d); }
.cart-btn:active { transform: scale(0.97); }
.cart-icon { font-size: 17px; }
.cart-count {
display: inline-grid;
place-items: center;
min-width: 22px;
height: 22px;
padding: 0 6px;
font-size: 12px;
font-weight: 800;
color: var(--teal-d);
background: var(--white);
border-radius: 999px;
transition: transform 0.18s;
}
.cart-count.bump { transform: scale(1.35); }
/* ---- Controls ---- */
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px;
margin: 22px 0 18px;
}
.search-wrap {
position: relative;
flex: 1 1 280px;
}
.search-icon {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
font-size: 15px;
opacity: 0.6;
}
#search {
width: 100%;
font-family: inherit;
font-size: 15px;
color: var(--ink);
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 13px 18px 13px 44px;
}
#search::placeholder { color: var(--muted); }
#search:focus-visible,
.chip:focus-visible,
.cart-btn:focus-visible,
.close-btn:focus-visible,
.add-btn:focus-visible,
.qty button:focus-visible,
.remove:focus-visible,
.checkout-btn:focus-visible {
outline: 3px solid rgba(23, 156, 142, 0.45);
outline-offset: 2px;
}
.filters {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.chip {
font-family: inherit;
font-size: 14px;
font-weight: 600;
color: var(--muted);
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 10px 16px;
cursor: pointer;
transition: all 0.16s;
}
.chip:hover { border-color: var(--teal); color: var(--teal-d); }
.chip.is-active {
color: var(--white);
background: var(--teal);
border-color: var(--teal);
}
/* ---- Grid ---- */
.section-title {
font-size: 16px;
font-weight: 700;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.result-count {
font-size: 13px;
font-weight: 700;
color: var(--teal-d);
background: var(--teal-50);
border-radius: 999px;
padding: 2px 10px;
}
.grid {
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.card {
display: flex;
flex-direction: column;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 18px;
transition: transform 0.16s, box-shadow 0.16s;
}
.card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow);
}
.thumb {
display: grid;
place-items: center;
height: 96px;
font-size: 44px;
background: var(--teal-50);
border-radius: 14px;
margin-bottom: 14px;
}
.card-tags {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.cat-tag {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--teal-d);
background: var(--teal-50);
border-radius: 999px;
padding: 3px 9px;
}
.rx-badge {
font-size: 11px;
font-weight: 800;
color: var(--rx);
background: var(--rx-bg);
border-radius: 999px;
padding: 3px 9px;
}
.card h3 {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
}
.card .desc {
font-size: 13px;
color: var(--muted);
margin: 4px 0 10px;
flex: 1;
}
.rating {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
color: var(--muted);
margin-bottom: 4px;
}
.stars { color: var(--coral); letter-spacing: 1px; }
.rx-warn {
font-size: 12px;
color: var(--rx);
background: var(--rx-bg);
border-radius: 10px;
padding: 7px 10px;
margin: 8px 0;
}
.card-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: auto;
padding-top: 10px;
}
.price {
font-size: 18px;
font-weight: 800;
color: var(--ink);
}
.add-btn {
font-family: inherit;
font-size: 14px;
font-weight: 700;
color: var(--white);
background: var(--coral);
border: none;
border-radius: 12px;
padding: 9px 14px;
cursor: pointer;
transition: filter 0.16s, transform 0.12s;
}
.add-btn:hover { filter: brightness(0.94); }
.add-btn:active { transform: scale(0.95); }
.add-btn.added { background: var(--teal); }
.empty-grid {
text-align: center;
color: var(--muted);
padding: 48px 16px;
font-size: 15px;
}
/* ---- Drawer ---- */
.scrim {
position: fixed;
inset: 0;
background: rgba(23, 58, 54, 0.4);
opacity: 0;
transition: opacity 0.25s;
z-index: 40;
}
.scrim.show { opacity: 1; }
.drawer {
position: fixed;
top: 0;
right: 0;
width: 380px;
max-width: 92vw;
height: 100%;
background: var(--bg);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 50;
box-shadow: -16px 0 40px rgba(15, 127, 115, 0.16);
}
.drawer.open { transform: translateX(0); }
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 22px;
background: var(--white);
border-bottom: 1px solid var(--line);
}
.drawer-head h2 { font-size: 18px; font-weight: 800; }
.close-btn {
font-family: inherit;
font-size: 16px;
color: var(--muted);
background: var(--teal-50);
border: none;
width: 34px;
height: 34px;
border-radius: 10px;
cursor: pointer;
transition: color 0.16s;
}
.close-btn:hover { color: var(--ink); }
.drawer-body {
flex: 1;
overflow-y: auto;
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 12px;
}
.line {
display: flex;
gap: 12px;
background: var(--white);
border: 1px solid var(--line);
border-radius: 16px;
padding: 12px;
}
.line-thumb {
display: grid;
place-items: center;
width: 48px;
height: 48px;
font-size: 24px;
background: var(--teal-50);
border-radius: 12px;
flex-shrink: 0;
}
.line-main { flex: 1; min-width: 0; }
.line-main h4 {
font-size: 14px;
font-weight: 700;
}
.line-rx {
font-size: 11px;
font-weight: 700;
color: var(--rx);
}
.line-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
}
.qty {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--teal-50);
border-radius: 999px;
padding: 3px;
}
.qty button {
font-family: inherit;
font-size: 15px;
font-weight: 700;
color: var(--teal-d);
background: var(--white);
border: none;
width: 26px;
height: 26px;
border-radius: 999px;
cursor: pointer;
line-height: 1;
}
.qty button:hover { background: var(--teal); color: var(--white); }
.qty span { min-width: 20px; text-align: center; font-weight: 700; font-size: 13px; }
.line-price { font-weight: 800; font-size: 14px; }
.remove {
font-family: inherit;
font-size: 12px;
color: var(--muted);
background: none;
border: none;
cursor: pointer;
padding: 2px 0;
margin-top: 4px;
}
.remove:hover { color: var(--coral); }
.cart-empty {
text-align: center;
color: var(--muted);
padding: 40px 24px;
font-size: 15px;
font-weight: 600;
}
.cart-empty span { font-size: 13px; font-weight: 400; }
.drawer-foot {
background: var(--white);
border-top: 1px solid var(--line);
padding: 18px 22px 22px;
}
.rx-note {
font-size: 12px;
font-weight: 600;
color: var(--rx);
background: var(--rx-bg);
border-radius: 10px;
padding: 9px 12px;
margin-bottom: 12px;
}
.total-row {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 14px;
}
.total-row span { color: var(--muted); font-size: 14px; }
.total-row strong { font-size: 22px; font-weight: 800; }
.checkout-btn {
width: 100%;
font-family: inherit;
font-size: 16px;
font-weight: 700;
color: var(--white);
background: var(--teal);
border: none;
border-radius: 14px;
padding: 14px;
cursor: pointer;
transition: background 0.16s;
}
.checkout-btn:hover:not(:disabled) { background: var(--teal-d); }
.checkout-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* ---- Responsive ---- */
@media (max-width: 520px) {
body { padding: 16px 12px 48px; }
.topbar { flex-direction: column; align-items: stretch; gap: 14px; }
.cart-btn { justify-content: center; }
.controls { flex-direction: column; align-items: stretch; }
.filters { justify-content: flex-start; overflow-x: auto; }
.grid { grid-template-columns: 1fr; }
.drawer { width: 100%; max-width: 100%; }
}(function () {
'use strict';
var PRODUCTS = [
{ id: 'p1', name: 'Hill’s Science Diet Adult Chicken', cat: 'food', emoji: '🐕', price: 42.99, rating: 4.8, reviews: 312, rx: false, desc: 'Dry dog food, 12 lb bag' },
{ id: 'p2', name: 'Royal Canin Kitten Formula', cat: 'food', emoji: '🐱', price: 28.5, rating: 4.7, reviews: 198, rx: false, desc: 'Wet pouches, pack of 12' },
{ id: 'p3', name: 'Apoquel 16mg Allergy Relief', cat: 'meds', emoji: '💊', price: 64.0, rating: 4.6, reviews: 87, rx: true, desc: 'Anti-itch tablets, 30 ct' },
{ id: 'p4', name: 'Bravecto Flea & Tick Chew', cat: 'meds', emoji: '🐛', price: 54.95, rating: 4.9, reviews: 421, rx: true, desc: '12-week protection, large dog' },
{ id: 'p5', name: 'Frontline Plus (no Rx)', cat: 'meds', emoji: '🦴', price: 39.99, rating: 4.5, reviews: 256, rx: false, desc: 'Topical flea drops, 3 doses' },
{ id: 'p6', name: 'Orthopedic Memory Foam Bed', cat: 'supplies', emoji: '🛏️', price: 79.0, rating: 4.8, reviews: 143, rx: false, desc: 'Washable cover, medium' },
{ id: 'p7', name: 'Stainless Steel Slow Feeder', cat: 'supplies', emoji: '🍽️', price: 18.75, rating: 4.4, reviews: 92, rx: false, desc: 'Anti-gulp bowl, 2-cup' },
{ id: 'p8', name: 'Gabapentin 100mg Capsules', cat: 'meds', emoji: '💊', price: 31.5, rating: 4.3, reviews: 64, rx: true, desc: 'Pain & anxiety, 60 ct' },
{ id: 'p9', name: 'Greenies Dental Treats', cat: 'food', emoji: '🦷', price: 22.99, rating: 4.7, reviews: 510, rx: false, desc: 'Daily chews, 27 ct' },
{ id: 'p10', name: 'Reflective No-Pull Harness', cat: 'supplies', emoji: '🦮', price: 26.4, rating: 4.6, reviews: 178, rx: false, desc: 'Adjustable, sizes S–XL' },
{ id: 'p11', name: 'Cerenia Anti-Nausea Tablets', cat: 'meds', emoji: '💊', price: 47.25, rating: 4.5, reviews: 41, rx: true, desc: 'Motion sickness, 4 ct' },
{ id: 'p12', name: 'Cat Scratching Post Tower', cat: 'supplies', emoji: '🏗️', price: 58.0, rating: 4.8, reviews: 207, rx: false, desc: 'Sisal-wrapped, 32 in' }
];
var CAT_LABEL = { food: 'Food', meds: 'Meds', supplies: 'Supplies' };
var cart = {}; // id -> qty
var activeCat = 'all';
var query = '';
var grid = document.getElementById('grid');
var emptyGrid = document.getElementById('emptyGrid');
var resultCount = document.getElementById('resultCount');
var resultLabel = document.getElementById('resultLabel');
var search = document.getElementById('search');
var chips = Array.prototype.slice.call(document.querySelectorAll('.chip'));
var cartBtn = document.getElementById('cartBtn');
var cartCount = document.getElementById('cartCount');
var drawer = document.getElementById('cartDrawer');
var scrim = document.getElementById('scrim');
var closeCart = document.getElementById('closeCart');
var cartItems = document.getElementById('cartItems');
var cartEmpty = document.getElementById('cartEmpty');
var totalEl = document.getElementById('total');
var rxNote = document.getElementById('rxNote');
var checkout = document.getElementById('checkout');
function money(n) {
return '$' + n.toFixed(2);
}
function starString(rating) {
var full = Math.round(rating);
return '★★★★★'.slice(0, full) + '☆☆☆☆☆'.slice(0, 5 - full);
}
function matches(p) {
if (activeCat !== 'all' && p.cat !== activeCat) return false;
if (query) {
var hay = (p.name + ' ' + p.desc + ' ' + CAT_LABEL[p.cat]).toLowerCase();
if (hay.indexOf(query) === -1) return false;
}
return true;
}
function renderGrid() {
var list = PRODUCTS.filter(matches);
grid.innerHTML = '';
list.forEach(function (p) {
var li = document.createElement('li');
li.className = 'card';
var rxBadge = p.rx ? '<span class="rx-badge">℞ Rx</span>' : '';
var rxWarn = p.rx ? '<p class="rx-warn">℞ Prescription — vet approval needed before this ships.</p>' : '';
li.innerHTML =
'<div class="thumb" aria-hidden="true">' + p.emoji + '</div>' +
'<div class="card-tags"><span class="cat-tag">' + CAT_LABEL[p.cat] + '</span>' + rxBadge + '</div>' +
'<h3>' + p.name + '</h3>' +
'<p class="desc">' + p.desc + '</p>' +
'<div class="rating"><span class="stars" aria-hidden="true">' + starString(p.rating) + '</span>' +
'<span>' + p.rating.toFixed(1) + ' (' + p.reviews + ')</span></div>' +
rxWarn +
'<div class="card-foot"><span class="price">' + money(p.price) + '</span>' +
'<button class="add-btn" type="button" data-id="' + p.id + '" aria-label="Add ' + stripTags(p.name) + ' to cart">Add</button></div>';
grid.appendChild(li);
});
var count = list.length;
resultCount.textContent = count;
emptyGrid.hidden = count !== 0;
grid.hidden = count === 0;
var catTxt = activeCat === 'all' ? 'All products' : CAT_LABEL[activeCat];
resultLabel.childNodes[0].nodeValue = catTxt + ' ';
}
function stripTags(s) {
return s.replace(/<[^>]*>/g, '').replace(/&/g, '&');
}
function totalQty() {
var n = 0;
for (var k in cart) if (cart.hasOwnProperty(k)) n += cart[k];
return n;
}
function cartTotal() {
var t = 0;
for (var k in cart) {
if (!cart.hasOwnProperty(k)) continue;
var p = byId(k);
if (p) t += p.price * cart[k];
}
return t;
}
function byId(id) {
for (var i = 0; i < PRODUCTS.length; i++) if (PRODUCTS[i].id === id) return PRODUCTS[i];
return null;
}
function hasRx() {
for (var k in cart) {
if (!cart.hasOwnProperty(k)) continue;
var p = byId(k);
if (p && p.rx) return true;
}
return false;
}
function updateCartCount() {
var n = totalQty();
cartCount.textContent = n;
cartCount.classList.remove('bump');
void cartCount.offsetWidth; // restart animation
cartCount.classList.add('bump');
}
function renderCart() {
var ids = Object.keys(cart);
cartItems.innerHTML = '';
if (ids.length === 0) {
cartEmpty.hidden = false;
checkout.disabled = true;
} else {
cartEmpty.hidden = true;
checkout.disabled = false;
ids.forEach(function (id) {
var p = byId(id);
if (!p) return;
var qty = cart[id];
var line = document.createElement('div');
line.className = 'line';
var rxTag = p.rx ? '<div class="line-rx">℞ Vet approval needed</div>' : '';
line.innerHTML =
'<div class="line-thumb" aria-hidden="true">' + p.emoji + '</div>' +
'<div class="line-main"><h4>' + p.name + '</h4>' + rxTag +
'<div class="line-bottom"><div class="qty">' +
'<button type="button" data-dec="' + id + '" aria-label="Decrease quantity">−</button>' +
'<span aria-live="polite">' + qty + '</span>' +
'<button type="button" data-inc="' + id + '" aria-label="Increase quantity">+</button></div>' +
'<span class="line-price">' + money(p.price * qty) + '</span></div>' +
'<button class="remove" type="button" data-rm="' + id + '">Remove</button></div>';
cartItems.appendChild(line);
});
}
totalEl.textContent = money(cartTotal());
rxNote.hidden = !hasRx();
}
function addToCart(id, btn) {
cart[id] = (cart[id] || 0) + 1;
updateCartCount();
renderCart();
if (btn) {
btn.textContent = 'Added ✓';
btn.classList.add('added');
setTimeout(function () {
btn.textContent = 'Add';
btn.classList.remove('added');
}, 1000);
}
}
function changeQty(id, delta) {
if (!cart[id]) return;
cart[id] += delta;
if (cart[id] <= 0) delete cart[id];
updateCartCount();
renderCart();
}
function removeItem(id) {
delete cart[id];
updateCartCount();
renderCart();
}
function openDrawer() {
scrim.hidden = false;
requestAnimationFrame(function () { scrim.classList.add('show'); });
drawer.classList.add('open');
drawer.setAttribute('aria-hidden', 'false');
closeCart.focus();
}
function closeDrawer() {
scrim.classList.remove('show');
drawer.classList.remove('open');
drawer.setAttribute('aria-hidden', 'true');
setTimeout(function () { scrim.hidden = true; }, 250);
cartBtn.focus();
}
// ---- Events ----
grid.addEventListener('click', function (e) {
var btn = e.target.closest('.add-btn');
if (btn) addToCart(btn.getAttribute('data-id'), btn);
});
cartItems.addEventListener('click', function (e) {
var t = e.target;
if (t.hasAttribute('data-inc')) changeQty(t.getAttribute('data-inc'), 1);
else if (t.hasAttribute('data-dec')) changeQty(t.getAttribute('data-dec'), -1);
else if (t.hasAttribute('data-rm')) removeItem(t.getAttribute('data-rm'));
});
chips.forEach(function (chip) {
chip.addEventListener('click', function () {
chips.forEach(function (c) {
c.classList.remove('is-active');
c.setAttribute('aria-pressed', 'false');
});
chip.classList.add('is-active');
chip.setAttribute('aria-pressed', 'true');
activeCat = chip.getAttribute('data-cat');
renderGrid();
});
});
search.addEventListener('input', function () {
query = search.value.trim().toLowerCase();
renderGrid();
});
cartBtn.addEventListener('click', openDrawer);
closeCart.addEventListener('click', closeDrawer);
scrim.addEventListener('click', closeDrawer);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && drawer.classList.contains('open')) closeDrawer();
});
checkout.addEventListener('click', function () {
if (totalQty() === 0) return;
checkout.textContent = 'Demo only — no real checkout';
setTimeout(function () { checkout.textContent = 'Checkout'; }, 1600);
});
// ---- Init ----
renderGrid();
renderCart();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PawScript — Online Pet Pharmacy</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=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="shell">
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">🐾</span>
<div>
<h1>PawScript</h1>
<p class="brand-sub">Online Pet Pharmacy & Store</p>
</div>
</div>
<button class="cart-btn" id="cartBtn" aria-haspopup="dialog" aria-controls="cartDrawer">
<span class="cart-icon" aria-hidden="true">🛒</span>
<span>Cart</span>
<span class="cart-count" id="cartCount" aria-live="polite">0</span>
</button>
</header>
<section class="controls" aria-label="Filter products">
<div class="search-wrap">
<label class="visually-hidden" for="search">Search products</label>
<span class="search-icon" aria-hidden="true">🔍</span>
<input type="search" id="search" placeholder="Search food, meds, flea treatment…" autocomplete="off" />
</div>
<div class="filters" role="group" aria-label="Filter by category">
<button class="chip is-active" data-cat="all" aria-pressed="true">All</button>
<button class="chip" data-cat="food" aria-pressed="false">Food</button>
<button class="chip" data-cat="meds" aria-pressed="false">Meds</button>
<button class="chip" data-cat="supplies" aria-pressed="false">Supplies</button>
</div>
</section>
<main>
<h2 class="section-title" id="resultLabel">All products <span id="resultCount" class="result-count">0</span></h2>
<ul class="grid" id="grid" aria-labelledby="resultLabel"></ul>
<p class="empty-grid" id="emptyGrid" hidden>No products match your search. Try another term.</p>
</main>
</div>
<div class="scrim" id="scrim" hidden></div>
<aside class="drawer" id="cartDrawer" role="dialog" aria-modal="true" aria-label="Shopping cart" aria-hidden="true">
<div class="drawer-head">
<h2>Your cart</h2>
<button class="close-btn" id="closeCart" aria-label="Close cart">✕</button>
</div>
<div class="drawer-body" id="cartItems"></div>
<p class="cart-empty" id="cartEmpty">Your cart is empty.<br /><span>Add food, meds or supplies to get started.</span></p>
<div class="drawer-foot">
<div class="rx-note" id="rxNote" hidden>
<span aria-hidden="true">℞</span> Some items need vet approval before they ship.
</div>
<div class="total-row">
<span>Subtotal</span>
<strong id="total">$0.00</strong>
</div>
<button class="checkout-btn" id="checkout" disabled>Checkout</button>
</div>
</aside>
<script src="script.js"></script>
</body>
</html>Online Pet Pharmacy & Store
A warm, approachable storefront for a veterinary pharmacy. The product grid lists pet food, meds and supplies — each card shows a name, category tag, price, star rating and, where relevant, a Rx prescription badge with a “vet approval needed” note. A category filter and a live search box narrow the grid instantly.
Shoppers add items with a single tap, which bumps a live cart count in the header. Opening the cart reveals a slide-in mini-cart drawer with quantity steppers, per-line and running totals, and a tidy empty state. Removing the last item resets the drawer gracefully.
Everything is vanilla JS — no frameworks, no build step. Filtering, search, cart math, the drawer and quantity steppers all run client-side, with focus styles, labelled controls and a responsive single-column layout under 520px.
Illustrative UI only — not for real veterinary use.