Vet — Pet Profiles
An owner-side pet profiles page with a searchable grid of pet cards showing avatar, species and breed, age, next appointment and a vaccination status badge, plus an add-pet card and a slide-in quick-view panel revealing basic details for each animal.
MCP
Code
:root {
--teal: #179c8e;
--teal-d: #0f7f73;
--teal-50: #e6f5f2;
--coral: #ff8a5c;
--coral-50: #fff0e9;
--ink: #173a36;
--muted: #6b827e;
--bg: #f2f8f6;
--white: #ffffff;
--line: #dcebe7;
--radius: 20px;
--radius-sm: 12px;
--shadow: 0 8px 24px rgba(15, 127, 115, 0.1);
--shadow-lg: 0 18px 48px rgba(15, 58, 54, 0.18);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.shell {
max-width: 940px;
margin: 0 auto;
padding: 32px 20px 64px;
}
/* Header */
.page-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 18px;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.brand-mark {
display: grid;
place-items: center;
width: 52px;
height: 52px;
font-size: 26px;
background: var(--teal-50);
border-radius: 16px;
}
.brand-eyebrow {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--teal-d);
}
h1 {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.02em;
}
.search {
position: relative;
flex: 1 1 280px;
max-width: 360px;
}
.search-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
opacity: 0.7;
pointer-events: none;
}
#search {
width: 100%;
font: inherit;
color: var(--ink);
padding: 12px 16px 12px 38px;
border: 1.5px solid var(--line);
border-radius: 999px;
background: var(--white);
outline: none;
transition: border-color 0.18s, box-shadow 0.18s;
}
#search::placeholder {
color: var(--muted);
}
#search:focus {
border-color: var(--teal);
box-shadow: 0 0 0 4px var(--teal-50);
}
.count {
font-size: 13px;
color: var(--muted);
margin-bottom: 18px;
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.card {
text-align: left;
font: inherit;
color: inherit;
background: var(--white);
border: 1.5px solid var(--line);
border-radius: var(--radius);
padding: 18px;
cursor: pointer;
transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
}
.card:hover,
.card:focus-visible {
transform: translateY(-3px);
box-shadow: var(--shadow);
border-color: var(--teal);
outline: none;
}
.pet-top {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.pet-avatar {
display: grid;
place-items: center;
width: 54px;
height: 54px;
font-size: 30px;
background: var(--teal-50);
border-radius: 18px;
flex-shrink: 0;
}
.pet-name {
font-size: 17px;
font-weight: 700;
letter-spacing: -0.01em;
}
.pet-breed {
font-size: 13px;
color: var(--muted);
}
.pet-meta {
display: flex;
flex-direction: column;
gap: 7px;
font-size: 13px;
color: var(--muted);
margin-bottom: 14px;
}
.pet-meta span {
display: flex;
gap: 7px;
}
.pet-meta b {
color: var(--ink);
font-weight: 600;
}
/* Status badge */
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 600;
padding: 5px 11px;
border-radius: 999px;
}
.badge::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.badge.ok {
color: var(--teal-d);
background: var(--teal-50);
}
.badge.due {
color: #c9572c;
background: var(--coral-50);
}
/* Add card */
.add-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 6px;
min-height: 200px;
border: 2px dashed var(--line);
background: transparent;
color: var(--muted);
}
.add-card:hover,
.add-card:focus-visible {
border-color: var(--coral);
color: var(--coral);
box-shadow: none;
}
.add-plus {
display: grid;
place-items: center;
width: 48px;
height: 48px;
font-size: 28px;
font-weight: 400;
line-height: 1;
color: var(--coral);
background: var(--coral-50);
border-radius: 16px;
}
.add-label {
font-size: 16px;
font-weight: 700;
color: var(--ink);
}
.add-sub {
font-size: 12px;
}
.empty {
text-align: center;
color: var(--muted);
padding: 36px 0;
font-size: 14px;
}
/* Quick-view */
.overlay {
position: fixed;
inset: 0;
background: rgba(15, 58, 54, 0.4);
backdrop-filter: blur(2px);
z-index: 20;
animation: fade 0.2s ease;
}
.quickview {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: min(380px, 92vw);
background: var(--white);
z-index: 30;
padding: 28px 26px;
box-shadow: var(--shadow-lg);
overflow-y: auto;
animation: slide 0.26s cubic-bezier(0.22, 1, 0.36, 1);
border-radius: var(--radius) 0 0 var(--radius);
}
.qv-close {
position: absolute;
top: 18px;
right: 18px;
width: 34px;
height: 34px;
font-size: 22px;
line-height: 1;
color: var(--muted);
background: var(--bg);
border: none;
border-radius: 50%;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.qv-close:hover {
background: var(--coral-50);
color: var(--coral);
}
.qv-head {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 16px;
padding-right: 32px;
}
.qv-avatar {
display: grid;
place-items: center;
width: 64px;
height: 64px;
font-size: 36px;
background: var(--teal-50);
border-radius: 20px;
}
.qv-head h2 {
font-size: 22px;
font-weight: 700;
letter-spacing: -0.01em;
}
.qv-breed {
font-size: 13px;
color: var(--muted);
}
.qv-details {
margin: 18px 0 22px;
border-top: 1px solid var(--line);
}
.qv-details > div {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 11px 0;
border-bottom: 1px solid var(--line);
}
.qv-details dt {
font-size: 13px;
color: var(--muted);
}
.qv-details dd {
font-size: 13px;
font-weight: 600;
text-align: right;
}
.qv-action {
width: 100%;
font: inherit;
font-weight: 600;
color: var(--white);
background: var(--teal);
border: none;
padding: 13px;
border-radius: var(--radius-sm);
cursor: pointer;
transition: background 0.16s, transform 0.12s;
}
.qv-action:hover {
background: var(--teal-d);
}
.qv-action:active {
transform: scale(0.98);
}
@keyframes fade {
from {
opacity: 0;
}
}
@keyframes slide {
from {
transform: translateX(100%);
}
}
/* Responsive */
@media (max-width: 520px) {
.shell {
padding: 22px 14px 48px;
}
.page-head {
flex-direction: column;
align-items: stretch;
}
.search {
max-width: none;
}
.grid {
grid-template-columns: 1fr;
}
h1 {
font-size: 22px;
}
.quickview {
width: 100vw;
border-radius: 0;
}
}(function () {
"use strict";
var pets = [
{
name: "Biscuit",
emoji: "🐶",
species: "Dog",
breed: "Golden Retriever",
age: "4 yrs",
weight: "29.4 kg",
chip: "956 000 102 884 731",
appt: "Jul 3, 2026 · 10:30",
clinic: "Pawthway — Riverside",
vax: "All current · next booster Mar 2027",
status: "ok",
},
{
name: "Mochi",
emoji: "🐱",
species: "Cat",
breed: "British Shorthair",
age: "2 yrs",
weight: "4.8 kg",
chip: "956 000 102 771 502",
appt: "Jun 28, 2026 · 16:00",
clinic: "Pawthway — Riverside",
vax: "Rabies due in 9 days",
status: "due",
},
{
name: "Pepper",
emoji: "🐰",
species: "Rabbit",
breed: "Holland Lop",
age: "1 yr",
weight: "1.6 kg",
chip: "—",
appt: "Sep 12, 2026 · 09:15",
clinic: "Pawthway — Oak Street",
vax: "Myxomatosis current",
status: "ok",
},
{
name: "Captain",
emoji: "🐦",
species: "Bird",
breed: "African Grey Parrot",
age: "11 yrs",
weight: "0.42 kg",
chip: "—",
appt: "No upcoming visit",
clinic: "Pawthway — Oak Street",
vax: "Annual check overdue",
status: "due",
},
{
name: "Luna",
emoji: "🐕",
species: "Dog",
breed: "Border Collie",
age: "6 yrs",
weight: "18.2 kg",
chip: "956 000 102 339 410",
appt: "Aug 1, 2026 · 14:45",
clinic: "Pawthway — Riverside",
vax: "All current · next booster Aug 2026",
status: "ok",
},
{
name: "Tofu",
emoji: "🐢",
species: "Tortoise",
breed: "Hermann's Tortoise",
age: "8 yrs",
weight: "1.1 kg",
chip: "—",
appt: "Jul 19, 2026 · 11:00",
clinic: "Pawthway — Oak Street",
vax: "Wellness exam due soon",
status: "due",
},
];
var grid = document.getElementById("grid");
var addCard = document.getElementById("add-pet");
var search = document.getElementById("search");
var count = document.getElementById("count");
var empty = document.getElementById("empty");
var emptyTerm = document.getElementById("empty-term");
var overlay = document.getElementById("overlay");
var quickview = document.getElementById("quickview");
var qvClose = document.getElementById("qv-close");
var lastFocused = null;
function badge(status) {
return status === "ok"
? '<span class="badge ok">Up to date</span>'
: '<span class="badge due">Due soon</span>';
}
function render(term) {
// Remove previously rendered pet cards (keep the add card).
var existing = grid.querySelectorAll(".pet-card");
existing.forEach(function (n) {
n.remove();
});
var q = (term || "").trim().toLowerCase();
var shown = 0;
pets.forEach(function (pet, i) {
var hay = (pet.name + " " + pet.species + " " + pet.breed).toLowerCase();
if (q && hay.indexOf(q) === -1) return;
shown++;
var card = document.createElement("button");
card.type = "button";
card.className = "card pet-card";
card.setAttribute("data-index", String(i));
card.setAttribute("aria-label", "View " + pet.name + " profile");
card.innerHTML =
'<div class="pet-top">' +
'<span class="pet-avatar" aria-hidden="true">' +
pet.emoji +
"</span>" +
"<div>" +
'<div class="pet-name">' +
pet.name +
"</div>" +
'<div class="pet-breed">' +
pet.species +
" · " +
pet.breed +
"</div>" +
"</div>" +
"</div>" +
'<div class="pet-meta">' +
"<span>Age <b>" +
pet.age +
"</b></span>" +
"<span>Next <b>" +
pet.appt +
"</b></span>" +
"</div>" +
badge(pet.status);
card.addEventListener("click", function () {
openQuickView(pet, card);
});
grid.appendChild(card);
});
var total = pets.length;
count.textContent = q
? shown + " of " + total + " pets match"
: total + " pets in your family";
if (q && shown === 0) {
empty.hidden = false;
emptyTerm.textContent = term;
addCard.style.display = "none";
} else {
empty.hidden = true;
addCard.style.display = "";
}
}
function openQuickView(pet, trigger) {
lastFocused = trigger || document.activeElement;
document.getElementById("qv-avatar").textContent = pet.emoji;
document.getElementById("qv-name").textContent = pet.name;
document.getElementById("qv-breed").textContent = pet.species + " · " + pet.breed;
document.getElementById("qv-age").textContent = pet.age;
document.getElementById("qv-weight").textContent = pet.weight;
document.getElementById("qv-chip").textContent = pet.chip;
document.getElementById("qv-appt").textContent = pet.appt;
document.getElementById("qv-clinic").textContent = pet.clinic;
document.getElementById("qv-vax").textContent = pet.vax;
var b = document.getElementById("qv-badge");
b.className = "badge " + pet.status;
b.textContent = pet.status === "ok" ? "Up to date" : "Due soon";
overlay.hidden = false;
quickview.hidden = false;
qvClose.focus();
document.addEventListener("keydown", onKey);
}
function closeQuickView() {
overlay.hidden = true;
quickview.hidden = true;
document.removeEventListener("keydown", onKey);
if (lastFocused && typeof lastFocused.focus === "function") {
lastFocused.focus();
}
}
function onKey(e) {
if (e.key === "Escape") closeQuickView();
}
overlay.addEventListener("click", closeQuickView);
qvClose.addEventListener("click", closeQuickView);
addCard.addEventListener("click", function () {
addCard.classList.add("nudge");
count.textContent = "Demo only — connect a form to add a new pet profile.";
setTimeout(function () {
render(search.value);
}, 1800);
});
var t;
search.addEventListener("input", function () {
clearTimeout(t);
t = setTimeout(function () {
render(search.value);
}, 90);
});
render("");
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pet Profiles — Pawthway Vet</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="shell">
<header class="page-head">
<div class="brand">
<span class="brand-mark" aria-hidden="true">🐾</span>
<div>
<p class="brand-eyebrow">Pawthway Veterinary</p>
<h1>My pets</h1>
</div>
</div>
<div class="search" role="search">
<span class="search-icon" aria-hidden="true">🔍</span>
<input
id="search"
type="search"
placeholder="Search by name, species or breed…"
aria-label="Search pets"
autocomplete="off"
/>
</div>
</header>
<p class="count" id="count" aria-live="polite"></p>
<section class="grid" id="grid" aria-label="Pet profiles">
<!-- Add pet card -->
<button class="card add-card" id="add-pet" type="button">
<span class="add-plus" aria-hidden="true">+</span>
<span class="add-label">Add a pet</span>
<span class="add-sub">Create a new profile</span>
</button>
<!-- pet cards injected by script.js -->
</section>
<p class="empty" id="empty" hidden>No pets match “<span id="empty-term"></span>”.</p>
</main>
<!-- Quick-view panel -->
<div class="overlay" id="overlay" hidden></div>
<aside
class="quickview"
id="quickview"
role="dialog"
aria-modal="true"
aria-labelledby="qv-name"
hidden
>
<button class="qv-close" id="qv-close" type="button" aria-label="Close quick view">×</button>
<div class="qv-head">
<span class="qv-avatar" id="qv-avatar" aria-hidden="true">🐾</span>
<div>
<h2 id="qv-name">Pet name</h2>
<p class="qv-breed" id="qv-breed">Species · Breed</p>
</div>
</div>
<span class="badge" id="qv-badge">Up to date</span>
<dl class="qv-details">
<div><dt>Age</dt><dd id="qv-age">—</dd></div>
<div><dt>Weight</dt><dd id="qv-weight">—</dd></div>
<div><dt>Microchip</dt><dd id="qv-chip">—</dd></div>
<div><dt>Next appointment</dt><dd id="qv-appt">—</dd></div>
<div><dt>Primary clinic</dt><dd id="qv-clinic">—</dd></div>
<div><dt>Vaccinations</dt><dd id="qv-vax">—</dd></div>
</dl>
<button class="qv-action" type="button">View full record</button>
</aside>
<script src="script.js"></script>
</body>
</html>Pet Profiles
A warm, owner-facing page for keeping track of the family’s animals. Each pet gets a soft rounded card with an emoji avatar, name, species and breed, age, the date of the next appointment and a vaccination status badge — Up to date in teal or Due soon in coral. A leading Add pet card invites new entries.
A live search box filters cards by name, species or breed as you type, and an empty-state message appears when nothing matches. Clicking any card opens a slide-in quick-view panel with the pet’s basic details: microchip number, weight, clinic, and an at-a-glance vaccination line.
Everything is vanilla HTML, CSS and JavaScript — no frameworks, no build step. Drop the three snippets together and the grid, search and quick-view all work out of the box.
Illustrative UI only — not for real veterinary use.