Vet — Medical Records
A pet medical records page with a chronological timeline of visit entries showing date, type, vet, diagnosis and treatment, type and date filters, expandable rows revealing notes and attachments, visually distinct vaccine and lab entries, and a print and download action.
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: 18px;
--radius-sm: 12px;
--shadow: 0 10px 30px rgba(15, 127, 115, 0.08);
--font: 'Inter', system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
}
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
padding: 28px 16px;
display: flex;
justify-content: center;
}
.sheet {
width: 100%;
max-width: 760px;
background: var(--white);
border-radius: var(--radius);
box-shadow: var(--shadow);
border: 1px solid var(--line);
overflow: hidden;
}
/* ---- header ---- */
.head {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: space-between;
padding: 22px 24px;
background: linear-gradient(135deg, var(--teal-50), var(--white));
border-bottom: 1px solid var(--line);
}
.patient {
display: flex;
align-items: center;
gap: 14px;
}
.avatar {
width: 54px;
height: 54px;
border-radius: 50%;
background: var(--teal);
color: var(--white);
display: grid;
place-items: center;
font-size: 26px;
flex-shrink: 0;
box-shadow: 0 6px 16px rgba(23, 156, 142, 0.3);
}
.patient-meta h1 {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
}
.patient-name {
margin: 2px 0 0;
font-weight: 600;
color: var(--teal-d);
font-size: 0.92rem;
}
.patient-sub {
margin: 1px 0 0;
color: var(--muted);
font-size: 0.78rem;
}
.head-actions {
display: flex;
gap: 10px;
}
/* ---- buttons ---- */
.btn {
font-family: inherit;
font-size: 0.86rem;
font-weight: 600;
border-radius: 999px;
border: 1px solid transparent;
padding: 9px 16px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
transition: transform 0.12s ease, background 0.18s ease, box-shadow 0.18s ease;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: 3px solid rgba(23, 156, 142, 0.4);
outline-offset: 2px;
}
.btn.primary {
background: var(--teal);
color: var(--white);
box-shadow: 0 6px 14px rgba(23, 156, 142, 0.28);
}
.btn.primary:hover {
background: var(--teal-d);
}
.btn.ghost {
background: var(--white);
color: var(--teal-d);
border-color: var(--line);
}
.btn.ghost:hover {
background: var(--teal-50);
}
.btn.link {
background: none;
color: var(--muted);
padding: 6px 8px;
font-weight: 600;
}
.btn.link:hover {
color: var(--coral);
}
/* ---- filters ---- */
.filters {
padding: 18px 24px;
display: flex;
flex-wrap: wrap;
gap: 14px 20px;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--line);
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
font-family: inherit;
font-size: 0.82rem;
font-weight: 600;
border: 1px solid var(--line);
background: var(--white);
color: var(--muted);
padding: 7px 14px;
border-radius: 999px;
cursor: pointer;
transition: all 0.16s ease;
}
.chip:hover {
border-color: var(--teal);
color: var(--teal-d);
}
.chip.is-active {
background: var(--teal);
border-color: var(--teal);
color: var(--white);
}
.chip:focus-visible {
outline: 3px solid rgba(23, 156, 142, 0.4);
outline-offset: 2px;
}
.date-filter {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
color: var(--muted);
font-weight: 600;
}
.date-filter input {
font-family: inherit;
font-size: 0.8rem;
color: var(--ink);
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 6px 9px;
background: var(--white);
}
.date-filter input:focus-visible {
outline: none;
border-color: var(--teal);
box-shadow: 0 0 0 3px rgba(23, 156, 142, 0.18);
}
.count {
margin: 0;
padding: 14px 24px 4px;
font-size: 0.8rem;
color: var(--muted);
font-weight: 600;
}
/* ---- timeline ---- */
.timeline {
list-style: none;
margin: 0;
padding: 8px 24px 26px;
position: relative;
}
.entry {
position: relative;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
margin-top: 12px;
background: var(--white);
transition: box-shadow 0.18s ease, border-color 0.18s ease;
overflow: hidden;
}
.entry:hover {
box-shadow: 0 6px 18px rgba(15, 127, 115, 0.1);
}
.entry.is-open {
border-color: var(--teal);
}
/* type accents */
.entry[data-type="vaccine"] {
border-left: 4px solid var(--coral);
background: linear-gradient(90deg, var(--coral-50), var(--white) 60%);
}
.entry[data-type="lab"] {
border-left: 4px solid var(--teal);
background: linear-gradient(90deg, var(--teal-50), var(--white) 60%);
}
.entry-head {
width: 100%;
display: flex;
align-items: center;
gap: 14px;
background: none;
border: none;
font-family: inherit;
text-align: left;
padding: 14px 16px;
cursor: pointer;
color: inherit;
}
.entry-head:focus-visible {
outline: 3px solid rgba(23, 156, 142, 0.4);
outline-offset: -3px;
border-radius: var(--radius-sm);
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--teal);
flex-shrink: 0;
}
.entry[data-type="vaccine"] .dot { background: var(--coral); }
.entry[data-type="lab"] .dot { background: var(--teal-d); }
.entry-date {
display: flex;
flex-direction: column;
line-height: 1.1;
width: 56px;
flex-shrink: 0;
text-align: center;
}
.entry-date strong {
font-size: 0.9rem;
color: var(--ink);
}
.entry-date span {
font-size: 0.72rem;
color: var(--muted);
}
.entry-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
.entry-title {
font-weight: 600;
font-size: 0.95rem;
}
.entry-meta {
font-size: 0.78rem;
color: var(--muted);
}
.badges {
display: flex;
flex-wrap: wrap;
gap: 6px;
justify-content: flex-end;
}
.badge {
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.02em;
text-transform: uppercase;
padding: 4px 9px;
border-radius: 999px;
white-space: nowrap;
}
.type-visit { background: #eef2f1; color: var(--muted); }
.type-vaccine { background: var(--coral); color: var(--white); }
.type-lab { background: var(--teal); color: var(--white); }
.type-surgery { background: #f2e8ff; color: #7a4fb0; }
.badge.ok { background: var(--teal-50); color: var(--teal-d); }
.badge.due { background: var(--coral-50); color: #c75a2c; }
.chev {
color: var(--muted);
font-size: 0.85rem;
transition: transform 0.2s ease;
flex-shrink: 0;
}
.is-open .chev {
transform: rotate(180deg);
}
/* ---- expanded body ---- */
.entry-body {
padding: 4px 18px 18px 86px;
border-top: 1px dashed var(--line);
animation: slide 0.2s ease;
}
@keyframes slide {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.kv {
margin: 14px 0 10px;
display: grid;
gap: 8px;
}
.kv div {
display: flex;
gap: 8px;
font-size: 0.85rem;
}
.kv dt {
font-weight: 700;
color: var(--teal-d);
width: 88px;
flex-shrink: 0;
}
.kv dd {
margin: 0;
color: var(--ink);
}
.notes {
font-size: 0.85rem;
color: var(--ink);
background: var(--bg);
border-radius: var(--radius-sm);
padding: 10px 13px;
margin: 0 0 12px;
}
.attachments {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.attachments a {
font-size: 0.78rem;
font-weight: 600;
color: var(--teal-d);
text-decoration: none;
background: var(--teal-50);
border: 1px solid var(--line);
padding: 5px 11px;
border-radius: 999px;
transition: background 0.16s ease;
}
.attachments a:hover {
background: var(--white);
border-color: var(--teal);
}
.empty {
text-align: center;
color: var(--muted);
font-weight: 600;
padding: 30px 24px 40px;
margin: 0;
}
/* ---- responsive ---- */
@media (max-width: 520px) {
body { padding: 14px 8px; }
.head { padding: 18px 16px; }
.filters { padding: 14px 16px; }
.date-filter { width: 100%; flex-wrap: wrap; }
.count { padding: 12px 16px 0; }
.timeline { padding: 6px 14px 20px; }
.entry-head { flex-wrap: wrap; gap: 8px 12px; }
.badges { width: 100%; justify-content: flex-start; order: 4; }
.entry-body { padding-left: 18px; }
.kv dt { width: 78px; }
}
/* ---- print ---- */
@media print {
body { background: var(--white); padding: 0; }
.sheet { box-shadow: none; border: none; max-width: 100%; }
.head-actions, .filters, .chev, .count { display: none !important; }
.entry-body { display: block !important; }
.entry-body[hidden] { display: block !important; }
.entry { break-inside: avoid; box-shadow: none; }
}(function () {
'use strict';
var timeline = document.getElementById('timeline');
var entries = Array.prototype.slice.call(timeline.querySelectorAll('.entry'));
var chips = Array.prototype.slice.call(document.querySelectorAll('.chip'));
var fromDate = document.getElementById('fromDate');
var toDate = document.getElementById('toDate');
var clearDates = document.getElementById('clearDates');
var countEl = document.getElementById('count');
var emptyEl = document.getElementById('empty');
var printBtn = document.getElementById('printBtn');
var downloadBtn = document.getElementById('downloadBtn');
var activeType = 'all';
/* ---- expand / collapse ---- */
entries.forEach(function (entry) {
var head = entry.querySelector('.entry-head');
var body = entry.querySelector('.entry-body');
head.addEventListener('click', function () {
var open = entry.classList.toggle('is-open');
head.setAttribute('aria-expanded', String(open));
body.hidden = !open;
});
});
/* ---- filtering ---- */
function inRange(dateStr) {
if (fromDate.value && dateStr < fromDate.value) return false;
if (toDate.value && dateStr > toDate.value) return false;
return true;
}
function applyFilters() {
var shown = 0;
entries.forEach(function (entry) {
var type = entry.getAttribute('data-type');
var date = entry.getAttribute('data-date');
var typeOk = activeType === 'all' || activeType === type;
var dateOk = inRange(date);
var visible = typeOk && dateOk;
entry.hidden = !visible;
if (visible) shown++;
});
countEl.textContent = 'Showing ' + shown + ' of ' + entries.length + ' records';
emptyEl.hidden = shown !== 0;
timeline.hidden = shown === 0;
return shown;
}
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');
activeType = chip.getAttribute('data-type');
applyFilters();
});
});
fromDate.addEventListener('change', applyFilters);
toDate.addEventListener('change', applyFilters);
clearDates.addEventListener('click', function () {
fromDate.value = '';
toDate.value = '';
applyFilters();
});
/* ---- print ---- */
printBtn.addEventListener('click', function () {
window.print();
});
/* ---- download visible records as text ---- */
downloadBtn.addEventListener('click', function () {
var lines = ['MEDICAL RECORDS — Biscuit (Golden Retriever)', '='.repeat(48), ''];
entries.forEach(function (entry) {
if (entry.hidden) return;
var dateBlock = entry.querySelector('.entry-date');
var date = dateBlock.textContent.replace(/\s+/g, ' ').trim();
var title = entry.querySelector('.entry-title').textContent.trim();
var meta = entry.querySelector('.entry-meta').textContent.trim();
lines.push(date + ' — ' + title);
lines.push(' ' + meta);
var rows = entry.querySelectorAll('.kv div');
rows.forEach(function (row) {
var dt = row.querySelector('dt').textContent.trim();
var dd = row.querySelector('dd').textContent.trim();
lines.push(' ' + dt + ': ' + dd);
});
var notes = entry.querySelector('.notes');
if (notes) lines.push(' Notes: ' + notes.textContent.trim());
lines.push('');
});
var blob = new Blob([lines.join('\n')], { type: 'text/plain' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'biscuit-medical-records.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
applyFilters();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Biscuit — Medical Records</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&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="sheet" aria-labelledby="rec-title">
<header class="head">
<div class="patient">
<div class="avatar" aria-hidden="true">🐾</div>
<div class="patient-meta">
<h1 id="rec-title">Medical Records</h1>
<p class="patient-name">Biscuit · Golden Retriever · Male, 4 yrs</p>
<p class="patient-sub">Microchip 985112004563210 · Owner: Daniela Reyes</p>
</div>
</div>
<div class="head-actions">
<button type="button" class="btn ghost" id="printBtn">
<span aria-hidden="true">🖨️</span> Print
</button>
<button type="button" class="btn primary" id="downloadBtn">
<span aria-hidden="true">⬇️</span> Download
</button>
</div>
</header>
<section class="filters" aria-label="Filter records">
<div class="chip-row" role="group" aria-label="Record type">
<button type="button" class="chip is-active" data-type="all" aria-pressed="true">All</button>
<button type="button" class="chip" data-type="visit" aria-pressed="false">Visit</button>
<button type="button" class="chip" data-type="vaccine" aria-pressed="false">Vaccine</button>
<button type="button" class="chip" data-type="lab" aria-pressed="false">Lab</button>
<button type="button" class="chip" data-type="surgery" aria-pressed="false">Surgery</button>
</div>
<div class="date-filter">
<label for="fromDate">From</label>
<input type="date" id="fromDate" value="2024-01-01" />
<label for="toDate">To</label>
<input type="date" id="toDate" value="2026-12-31" />
<button type="button" class="btn link" id="clearDates">Clear</button>
</div>
</section>
<p class="count" id="count" aria-live="polite">Showing 6 of 6 records</p>
<ol class="timeline" id="timeline">
<li class="entry" data-type="lab" data-date="2026-05-18">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>May 18</strong><span>2026</span></span>
<span class="entry-main">
<span class="entry-title">Blood panel & thyroid screen</span>
<span class="entry-meta">Lab · Dr. Amara Okoye</span>
</span>
<span class="badges">
<span class="badge type-lab">Lab</span>
<span class="badge ok">Results in</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Within normal limits; mild eosinophilia</dd></div>
<div><dt>Treatment</dt><dd>No intervention — recheck in 6 months</dd></div>
</dl>
<p class="notes">CBC and chemistry unremarkable. T4 within reference range (2.1 µg/dL). Owner advised to monitor for seasonal itching.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">📄 cbc-results-2026-05-18.pdf</a></li>
<li><a href="#" onclick="return false">📄 chemistry-panel.pdf</a></li>
</ul>
</div>
</li>
<li class="entry" data-type="vaccine" data-date="2026-03-02">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>Mar 2</strong><span>2026</span></span>
<span class="entry-main">
<span class="entry-title">Rabies booster (3-year)</span>
<span class="entry-meta">Vaccine · Dr. Liam Schaefer</span>
</span>
<span class="badges">
<span class="badge type-vaccine">Vaccine</span>
<span class="badge due">Next due Mar 2029</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Routine immunization</dd></div>
<div><dt>Treatment</dt><dd>Rabies vaccine, 1 mL SQ, right hind</dd></div>
</dl>
<p class="notes">Lot #RB-44128, exp. 2027-09. No adverse reaction observed during 15-min wait. Certificate issued to owner.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">📄 rabies-certificate.pdf</a></li>
</ul>
</div>
</li>
<li class="entry" data-type="visit" data-date="2026-01-14">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>Jan 14</strong><span>2026</span></span>
<span class="entry-main">
<span class="entry-title">Recurrent ear scratching</span>
<span class="entry-meta">Visit · Dr. Amara Okoye</span>
</span>
<span class="badges">
<span class="badge type-visit">Visit</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Otitis externa, left ear (yeast)</dd></div>
<div><dt>Treatment</dt><dd>Posatex drops, 8 drops BID × 7 days; e-collar</dd></div>
</dl>
<p class="notes">Cytology showed Malassezia. Cleaned both ears in-clinic. Recheck booked for Jan 28 — owner to avoid water near ears.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">🖼️ ear-cytology.jpg</a></li>
</ul>
</div>
</li>
<li class="entry" data-type="surgery" data-date="2025-08-21">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>Aug 21</strong><span>2025</span></span>
<span class="entry-main">
<span class="entry-title">Dental cleaning & extraction</span>
<span class="entry-meta">Surgery · Dr. Priya Nandakumar</span>
</span>
<span class="badges">
<span class="badge type-surgery">Surgery</span>
<span class="badge ok">Recovered</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Grade 2 periodontal disease; fractured PM4</dd></div>
<div><dt>Treatment</dt><dd>Scale & polish under GA; extraction of upper left PM4</dd></div>
</dl>
<p class="notes">Anesthesia uneventful (iso, 38 min). Sent home with Onsior 3 days. Sutures dissolvable. Soft food 7 days.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">📄 dental-chart.pdf</a></li>
<li><a href="#" onclick="return false">📄 anesthesia-log.pdf</a></li>
</ul>
</div>
</li>
<li class="entry" data-type="vaccine" data-date="2025-03-05">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>Mar 5</strong><span>2025</span></span>
<span class="entry-main">
<span class="entry-title">DHPP & Leptospirosis</span>
<span class="entry-meta">Vaccine · Dr. Liam Schaefer</span>
</span>
<span class="badges">
<span class="badge type-vaccine">Vaccine</span>
<span class="badge due">Next due Mar 2026</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Routine immunization</dd></div>
<div><dt>Treatment</dt><dd>DHPP + Lepto4 combo, 1 mL SQ, left hind</dd></div>
</dl>
<p class="notes">Annual core boosters. No reaction. Weight recorded at 32.4 kg — stable since last visit.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">📄 vaccine-record.pdf</a></li>
</ul>
</div>
</li>
<li class="entry" data-type="visit" data-date="2024-11-09">
<button type="button" class="entry-head" aria-expanded="false">
<span class="dot" aria-hidden="true"></span>
<span class="entry-date"><strong>Nov 9</strong><span>2024</span></span>
<span class="entry-main">
<span class="entry-title">Annual wellness exam</span>
<span class="entry-meta">Visit · Dr. Amara Okoye</span>
</span>
<span class="badges">
<span class="badge type-visit">Visit</span>
<span class="badge ok">Healthy</span>
</span>
<span class="chev" aria-hidden="true">▾</span>
</button>
<div class="entry-body" hidden>
<dl class="kv">
<div><dt>Diagnosis</dt><dd>Healthy adult; BCS 5/9</dd></div>
<div><dt>Treatment</dt><dd>Heartworm test negative; flea/tick preventive dispensed</dd></div>
</dl>
<p class="notes">Full physical normal. Heart and lungs clear. Discussed weight maintenance and dental home care.</p>
<ul class="attachments">
<li><a href="#" onclick="return false">📄 wellness-summary.pdf</a></li>
</ul>
</div>
</li>
</ol>
<p class="empty" id="empty" hidden>No records match your filters.</p>
</main>
<script src="script.js"></script>
</body>
</html>Medical Records
A warm, friendly medical history page for a single patient — here a golden retriever named Biscuit. The header carries the patient summary, a record-type filter (All, Visit, Vaccine, Lab, Surgery) and a date range, plus print and download actions. Below it, a chronological timeline lists every entry with its date, type, attending vet, diagnosis and treatment.
Each row expands to reveal clinician notes and any attached files. Vaccine entries get a coral accent with a next-due reminder, and Lab entries are tinted teal with a results badge, so they stand out at a glance. Filtering by type or date narrows the timeline live and updates the visible count.
Everything is vanilla JS — no libraries. The download action serializes the currently visible records to a small text file, and print opens the browser dialog with a tidy print stylesheet.
Illustrative UI only — not for real veterinary use.