Clinic — Prescriptions & Refills
An active-prescriptions panel showing drug, dose, schedule, prescriber, remaining refills and next-due date, with one-tap per-item refill requests, a filter bar (active / needs refill / all), and a refill-all shortcut — all with live state and toast feedback.
MCP
Код
:root {
--teal: #129c93;
--teal-d: #0c7a73;
--teal-700: #0a655f;
--teal-50: #e7f5f3;
--coral: #ff7a66;
--coral-soft: #ffe6df;
--ink: #16322f;
--ink-2: #3a534f;
--muted: #6b827e;
--bg: #f1f7f6;
--white: #ffffff;
--line: rgba(16, 50, 47, 0.1);
--line-2: rgba(16, 50, 47, 0.18);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--font: "Inter", system-ui, -apple-system, sans-serif;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--shadow-1: 0 1px 2px rgba(16, 50, 47, 0.05), 0 4px 14px rgba(16, 50, 47, 0.06);
--shadow-2: 0 16px 40px rgba(12, 122, 115, 0.16);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
-webkit-font-smoothing: antialiased;
line-height: 1.5;
}
button {
font-family: inherit;
}
/* ── Layout ── */
.rx {
max-width: 680px;
margin: 0 auto;
padding: 32px 20px 56px;
display: flex;
flex-direction: column;
gap: 20px;
}
/* ── Header ── */
.rx-head {
display: flex;
flex-direction: column;
gap: 18px;
}
.rx-head-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.eyebrow {
font-size: 0.74rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--teal-d);
}
.rx-head h1 {
font-size: 1.6rem;
font-weight: 800;
letter-spacing: -0.02em;
margin-top: 4px;
}
.sub {
font-size: 0.9rem;
color: var(--muted);
margin-top: 6px;
max-width: 42ch;
}
/* ── Refill all button ── */
.btn {
border: none;
border-radius: 10px;
padding: 9px 16px;
font-weight: 600;
font-size: 0.84rem;
cursor: pointer;
transition: transform 0.12s, background 0.15s, border-color 0.15s,
box-shadow 0.15s, color 0.15s;
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible {
outline: 2px solid var(--teal);
outline-offset: 2px;
}
.refill-all {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--teal-d);
color: #fff;
box-shadow: var(--shadow-1);
}
.refill-all:hover:not(:disabled) {
background: var(--teal-700);
}
.refill-all:disabled {
background: #cfe1de;
color: var(--muted);
cursor: not-allowed;
box-shadow: none;
}
.refill-all .pill {
display: inline-grid;
place-items: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.22);
font-size: 0.74rem;
font-weight: 700;
}
.refill-all:disabled .pill {
background: rgba(16, 50, 47, 0.08);
}
/* ── Filters ── */
.filters {
display: inline-flex;
align-self: flex-start;
flex-wrap: wrap;
gap: 4px;
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 4px;
box-shadow: var(--shadow-1);
}
.filter {
border: none;
background: transparent;
border-radius: 999px;
padding: 8px 16px;
font-weight: 600;
font-size: 0.88rem;
color: var(--muted);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: background 0.15s, color 0.15s;
}
.filter:hover {
color: var(--ink-2);
}
.filter.is-active {
background: var(--teal-d);
color: #fff;
}
.filter:focus-visible {
outline: 2px solid var(--teal);
outline-offset: 2px;
}
.count {
display: inline-grid;
place-items: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
border-radius: 999px;
background: var(--teal-50);
color: var(--teal-d);
font-size: 0.74rem;
font-weight: 700;
}
.filter.is-active .count {
background: rgba(255, 255, 255, 0.22);
color: #fff;
}
/* ── List ── */
.list {
display: flex;
flex-direction: column;
gap: 14px;
}
/* ── Card ── */
.card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
box-shadow: var(--shadow-1);
transition: opacity 0.2s, border-color 0.15s, box-shadow 0.15s,
transform 0.15s;
}
.card[hidden] {
display: none;
}
.card:hover {
border-color: var(--line-2);
box-shadow: var(--shadow-2);
}
.card.needs-refill {
border-left: 4px solid var(--warn);
}
.card.is-done {
box-shadow: none;
background: #fafdfc;
}
.card.just-requested {
animation: flash 0.6s ease;
}
@keyframes flash {
0% {
background: var(--teal-50);
}
100% {
background: var(--white);
}
}
.card-main {
display: grid;
grid-template-columns: auto 1fr;
gap: 14px;
}
.med-icon {
width: 44px;
height: 44px;
flex-shrink: 0;
display: grid;
place-items: center;
border-radius: var(--r-sm);
background: linear-gradient(150deg, var(--teal-50), #d9efec);
color: var(--teal-700);
font-size: 0.95rem;
font-weight: 800;
letter-spacing: -0.02em;
}
.card.needs-refill .med-icon {
background: linear-gradient(150deg, #fdeede, #fbe2c5);
color: #b06f1f;
}
.card.is-done .med-icon {
background: #eef3f2;
color: var(--muted);
}
.med {
min-width: 0;
}
.med-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.med h2 {
font-size: 1.05rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.dose {
font-weight: 600;
font-size: 0.9rem;
color: var(--muted);
}
.schedule {
display: flex;
align-items: center;
gap: 7px;
font-size: 0.86rem;
color: var(--ink-2);
font-weight: 500;
margin-top: 8px;
}
.schedule svg {
flex-shrink: 0;
color: var(--teal);
}
.doctor {
font-size: 0.82rem;
color: var(--muted);
margin-top: 5px;
}
/* ── Badges ── */
.badge {
font-size: 0.72rem;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
white-space: nowrap;
flex-shrink: 0;
}
.badge.ok {
background: rgba(47, 158, 111, 0.14);
color: var(--ok);
}
.badge.warn {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.badge.requested {
background: var(--teal-50);
color: var(--teal-d);
}
.badge.done {
background: rgba(16, 50, 47, 0.07);
color: var(--muted);
}
/* ── Card footer ── */
.card-foot {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 14px;
margin-top: 16px;
padding-top: 14px;
border-top: 1px dashed var(--line);
}
.stats {
display: flex;
gap: 26px;
}
.stat dt {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
}
.stat dd {
font-size: 1rem;
font-weight: 700;
color: var(--ink);
margin-top: 2px;
}
.refills.low {
color: var(--warn);
}
.warn-text {
color: var(--warn) !important;
}
/* ── Request button ── */
.btn.request {
flex-shrink: 0;
background: var(--white);
border: 1px solid var(--teal);
color: var(--teal-d);
}
.btn.request:hover:not(:disabled) {
background: var(--teal-50);
border-color: var(--teal-d);
}
.btn.request.is-requested {
background: var(--teal-50);
border-color: transparent;
color: var(--teal-d);
cursor: default;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn.request:disabled {
background: var(--white);
border: 1px solid var(--line-2);
color: var(--muted);
cursor: not-allowed;
}
/* ── Empty state ── */
.empty {
text-align: center;
color: var(--muted);
font-size: 0.9rem;
font-weight: 500;
padding: 36px 16px;
background: var(--white);
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
}
.empty[hidden] {
display: none;
}
/* ── Footer ── */
.rx-foot {
font-size: 0.8rem;
color: var(--muted);
line-height: 1.6;
padding: 14px 16px;
background: rgba(18, 156, 147, 0.05);
border: 1px solid var(--line);
border-radius: var(--r-md);
}
/* ── Toast ── */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
background: var(--ink);
color: #fff;
padding: 13px 20px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 500;
box-shadow: var(--shadow-2);
z-index: 50;
max-width: 90vw;
}
.toast[hidden] {
display: none;
}
/* ── Responsive ── */
@media (max-width: 520px) {
.rx-head-top {
flex-direction: column;
}
.refill-all {
align-self: stretch;
justify-content: center;
}
.card-foot {
flex-direction: column;
align-items: stretch;
gap: 14px;
}
.btn.request {
width: 100%;
text-align: center;
}
.stats {
gap: 0;
justify-content: space-between;
}
}// ── Toast ──────────────────────────────────────────────────────────────────
const toast = document.getElementById("toast");
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 2800);
}
const list = document.getElementById("list");
const cards = () => Array.from(list.querySelectorAll(".card"));
const checkIcon =
'<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">' +
'<path d="M20 6 9 17l-5-5" fill="none" stroke="currentColor" ' +
'stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
// ── Per-state metadata ───────────────────────────────────────────────────────
const BADGE = {
active: { cls: "badge ok", text: "Active" },
"needs-refill": { cls: "badge warn", text: "Needs refill" },
requested: { cls: "badge requested", text: "Refill requested" },
completed: { cls: "badge done", text: "Course complete" },
};
// ── Live counts + filter visibility ──────────────────────────────────────────
function matchesFilter(state, filter) {
if (filter === "all") return true;
if (filter === "active") return state === "active" || state === "requested";
if (filter === "needs-refill") return state === "needs-refill";
return true;
}
function currentFilter() {
return document.querySelector(".filter.is-active").dataset.filter;
}
function isEligible(card) {
return (
card.dataset.state === "needs-refill" &&
Number(card.querySelector("[data-refills]").textContent) > 0
);
}
function refreshCounts() {
const all = cards();
const set = (id, n) => (document.getElementById(id).textContent = n);
set("count-all", all.length);
set(
"count-active",
all.filter((c) => matchesFilter(c.dataset.state, "active")).length
);
set(
"count-needs-refill",
all.filter((c) => c.dataset.state === "needs-refill").length
);
const eligible = all.filter(isEligible).length;
set("eligible-count", eligible);
const refillAll = document.getElementById("refill-all");
refillAll.disabled = eligible === 0;
}
function applyFilter() {
const filter = currentFilter();
let visible = 0;
cards().forEach((card) => {
const show = matchesFilter(card.dataset.state, filter);
card.hidden = !show;
if (show) visible++;
});
document.getElementById("empty").hidden = visible !== 0;
}
// ── Filter tabs ──────────────────────────────────────────────────────────────
document.querySelectorAll(".filter").forEach((btn) => {
btn.addEventListener("click", () => {
document.querySelectorAll(".filter").forEach((f) => {
const active = f === btn;
f.classList.toggle("is-active", active);
f.setAttribute("aria-selected", String(active));
});
applyFilter();
});
});
// ── Request a single refill (pending → requested) ────────────────────────────
function requestRefill(card, { silent } = {}) {
if (!isEligible(card)) return false;
const refillsEl = card.querySelector("[data-refills]");
const left = Number(refillsEl.textContent) - 1;
refillsEl.textContent = left;
refillsEl.classList.toggle("low", left <= 2);
card.dataset.state = "requested";
card.classList.remove("needs-refill");
const badge = card.querySelector("[data-badge]");
badge.className = BADGE.requested.cls;
badge.textContent = BADGE.requested.text;
// Reset the warn-styled due date now that it's handled.
const due = card.querySelector(".warn-text");
if (due) due.classList.remove("warn-text");
const btn = card.querySelector("[data-action='request']");
btn.classList.add("is-requested");
btn.disabled = true;
btn.innerHTML = checkIcon + "Requested";
const name = card.querySelector("h2").firstChild.textContent.trim();
if (!silent) {
showToast(`Refill requested for ${name} — your care team will review it.`);
}
return name;
}
// ── Refill all eligible at once ──────────────────────────────────────────────
function refillAll() {
const eligible = cards().filter(isEligible);
if (eligible.length === 0) return;
eligible.forEach((card) => requestRefill(card, { silent: true }));
refreshCounts();
applyFilter();
showToast(
`${eligible.length} refill request${eligible.length > 1 ? "s" : ""} sent ` +
`to your care team.`
);
}
// ── Event delegation ─────────────────────────────────────────────────────────
list.addEventListener("click", (e) => {
const btn = e.target.closest("[data-action='request']");
if (!btn || btn.disabled) return;
const card = btn.closest(".card");
const requested = requestRefill(card);
if (requested) {
refreshCounts();
applyFilter();
card.classList.remove("just-requested");
void card.offsetWidth; // restart animation
card.classList.add("just-requested");
}
});
document.getElementById("refill-all").addEventListener("click", refillAll);
// ── Init ─────────────────────────────────────────────────────────────────────
refreshCounts();
applyFilter();<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
/>
<link rel="stylesheet" href="style.css" />
<title>Prescriptions & Refills · Northpoint Clinic</title>
</head>
<body>
<main class="rx">
<header class="rx-head">
<div class="rx-head-top">
<div>
<p class="eyebrow">Northpoint Clinic</p>
<h1>Prescriptions & refills</h1>
<p class="sub">
Manage your active medications and request refills before you run
out.
</p>
</div>
<button class="btn refill-all" id="refill-all" data-action="refill-all">
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path
d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Refill all <span class="pill" id="eligible-count">2</span>
</button>
</div>
<div class="filters" role="tablist" aria-label="Filter prescriptions">
<button
class="filter is-active"
role="tab"
aria-selected="true"
data-filter="all"
>
All <span class="count" id="count-all">5</span>
</button>
<button
class="filter"
role="tab"
aria-selected="false"
data-filter="active"
>
Active <span class="count" id="count-active">4</span>
</button>
<button
class="filter"
role="tab"
aria-selected="false"
data-filter="needs-refill"
>
Needs refill <span class="count" id="count-needs-refill">2</span>
</button>
</div>
</header>
<section class="list" id="list" aria-live="polite">
<!-- Lisinopril -->
<article class="card" data-state="active">
<div class="card-main">
<div class="med-icon" aria-hidden="true">Rx</div>
<div class="med">
<div class="med-top">
<h2>Lisinopril <span class="dose">10 mg</span></h2>
<span class="badge ok" data-badge>Active</span>
</div>
<p class="schedule">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
1 tablet once daily, in the morning
</p>
<p class="doctor">Prescribed by Dr. Lena Okafor · Cardiology</p>
</div>
</div>
<div class="card-foot">
<dl class="stats">
<div class="stat">
<dt>Refills left</dt>
<dd class="refills" data-refills>3</dd>
</div>
<div class="stat">
<dt>Next due</dt>
<dd>Jul 02</dd>
</div>
</dl>
<button class="btn request" data-action="request">
Request refill
</button>
</div>
</article>
<!-- Metformin -->
<article class="card needs-refill" data-state="needs-refill">
<div class="card-main">
<div class="med-icon" aria-hidden="true">Rx</div>
<div class="med">
<div class="med-top">
<h2>Metformin <span class="dose">500 mg</span></h2>
<span class="badge warn" data-badge>Needs refill</span>
</div>
<p class="schedule">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
1 tablet twice daily, with meals
</p>
<p class="doctor">Prescribed by Dr. Ravi Patel · Primary care</p>
</div>
</div>
<div class="card-foot">
<dl class="stats">
<div class="stat">
<dt>Refills left</dt>
<dd class="refills low" data-refills>1</dd>
</div>
<div class="stat">
<dt>Next due</dt>
<dd class="warn-text">Jun 11</dd>
</div>
</dl>
<button class="btn request" data-action="request">
Request refill
</button>
</div>
</article>
<!-- Atorvastatin -->
<article class="card" data-state="active">
<div class="card-main">
<div class="med-icon" aria-hidden="true">Rx</div>
<div class="med">
<div class="med-top">
<h2>Atorvastatin <span class="dose">20 mg</span></h2>
<span class="badge ok" data-badge>Active</span>
</div>
<p class="schedule">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
1 tablet once daily, at bedtime
</p>
<p class="doctor">Prescribed by Dr. Lena Okafor · Cardiology</p>
</div>
</div>
<div class="card-foot">
<dl class="stats">
<div class="stat">
<dt>Refills left</dt>
<dd class="refills" data-refills>5</dd>
</div>
<div class="stat">
<dt>Next due</dt>
<dd>Aug 14</dd>
</div>
</dl>
<button class="btn request" data-action="request">
Request refill
</button>
</div>
</article>
<!-- Levothyroxine -->
<article class="card needs-refill" data-state="needs-refill">
<div class="card-main">
<div class="med-icon" aria-hidden="true">Rx</div>
<div class="med">
<div class="med-top">
<h2>Levothyroxine <span class="dose">75 mcg</span></h2>
<span class="badge warn" data-badge>Needs refill</span>
</div>
<p class="schedule">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
1 tablet once daily, 30 min before breakfast
</p>
<p class="doctor">Prescribed by Dr. Maya Bloom · Endocrinology</p>
</div>
</div>
<div class="card-foot">
<dl class="stats">
<div class="stat">
<dt>Refills left</dt>
<dd class="refills low" data-refills>2</dd>
</div>
<div class="stat">
<dt>Next due</dt>
<dd class="warn-text">Jun 09</dd>
</div>
</dl>
<button class="btn request" data-action="request">
Request refill
</button>
</div>
</article>
<!-- Amoxicillin -->
<article class="card is-done" data-state="completed">
<div class="card-main">
<div class="med-icon" aria-hidden="true">Rx</div>
<div class="med">
<div class="med-top">
<h2>Amoxicillin <span class="dose">500 mg</span></h2>
<span class="badge done" data-badge>Course complete</span>
</div>
<p class="schedule">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
d="M12 7v5l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
1 capsule three times daily for 7 days
</p>
<p class="doctor">Prescribed by Dr. Ravi Patel · Primary care</p>
</div>
</div>
<div class="card-foot">
<dl class="stats">
<div class="stat">
<dt>Refills left</dt>
<dd class="refills" data-refills>0</dd>
</div>
<div class="stat">
<dt>Finished</dt>
<dd>May 28</dd>
</div>
</dl>
<button class="btn request" data-action="request" disabled>
No refills
</button>
</div>
</article>
</section>
<p class="empty" id="empty" hidden>
No prescriptions match this filter.
</p>
<footer class="rx-foot">
Refill requests are reviewed by your care team, usually within one
business day. For urgent needs, call the clinic at (555) 240-1180.
</footer>
</main>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Prescriptions & Refills
A patient medication panel for Northpoint Clinic. Each prescription card shows the drug name, dose, dosing schedule, prescribing doctor, number of remaining refills, and the next-due date. The filter bar at the top switches between All, Active, and Needs refill views, with live counts. Each card has a Request refill button that cycles through a pending → requested state and decrements the refill count. The Refill all action triggers all eligible items at once. Toast notifications confirm every action.
Illustrative UI only — not intended for real medical use.