Clinic — Doctors / Team Page
A responsive doctors and care-team directory for a clinic site. Each card pairs a gradient initials avatar with the physician name, specialty, credentials, language chips and an availability badge marking whether they are accepting new patients or running a waitlist. A specialty dropdown and a live name-search input filter the grid in real time with a running result count, a graceful empty state, and book or join-waitlist actions that confirm with a toast.
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;
}
.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;
}
button:focus-visible,
input:focus-visible,
select:focus-visible {
outline: 2px solid var(--teal-d);
outline-offset: 2px;
}
/* ── Layout ── */
.page {
max-width: 980px;
margin: 0 auto;
padding: 40px 20px 64px;
display: flex;
flex-direction: column;
gap: 22px;
}
.page-head {
display: flex;
flex-direction: column;
gap: 8px;
}
.eyebrow {
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--teal-d);
}
.page-head h1 {
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.025em;
line-height: 1.1;
}
.lede {
color: var(--ink-2);
font-size: 1rem;
max-width: 56ch;
}
/* ── Toolbar ── */
.toolbar {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.field {
position: relative;
display: flex;
align-items: center;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--shadow-1);
transition: border-color 0.15s, box-shadow 0.15s;
}
.field:focus-within {
border-color: var(--teal);
}
.field.search {
flex: 1 1 280px;
min-width: 220px;
}
.field.search .ic {
width: 18px;
height: 18px;
margin-left: 14px;
flex-shrink: 0;
fill: none;
stroke: var(--muted);
stroke-width: 2;
stroke-linecap: round;
}
.field.search input {
flex: 1;
border: none;
background: transparent;
font: inherit;
font-size: 0.95rem;
color: var(--ink);
padding: 13px 14px 13px 10px;
}
.field.search input::placeholder {
color: var(--muted);
}
.field.select {
flex: 0 1 220px;
}
.field.select select {
appearance: none;
-webkit-appearance: none;
border: none;
background: transparent;
font: inherit;
font-size: 0.95rem;
font-weight: 500;
color: var(--ink);
padding: 13px 40px 13px 16px;
width: 100%;
cursor: pointer;
}
.field.select .chev {
position: absolute;
right: 14px;
width: 16px;
height: 16px;
pointer-events: none;
fill: none;
stroke: var(--muted);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.result-line {
font-size: 0.86rem;
color: var(--muted);
font-weight: 500;
}
.result-line span {
color: var(--ink-2);
font-weight: 700;
}
/* ── Grid ── */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
}
/* ── Doctor card ── */
.doc {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
display: flex;
flex-direction: column;
box-shadow: var(--shadow-1);
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.15s;
}
.doc:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-2);
border-color: rgba(18, 156, 147, 0.3);
}
.doc[hidden] {
display: none;
}
.doc-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
margin-bottom: 14px;
}
.avatar {
width: 56px;
height: 56px;
border-radius: 16px;
display: grid;
place-items: center;
font-size: 1.18rem;
font-weight: 800;
letter-spacing: 0.01em;
color: #fff;
background: linear-gradient(150deg, var(--g1), var(--g2));
box-shadow: 0 6px 16px rgba(12, 122, 115, 0.22);
flex-shrink: 0;
}
/* ── Status badge ── */
.status {
font-size: 0.72rem;
font-weight: 700;
padding: 5px 10px;
border-radius: 999px;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 6px;
}
.status::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.status.open {
background: rgba(47, 158, 111, 0.14);
color: var(--ok);
}
.status.waitlist {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.doc-name {
font-size: 1.08rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.doc-spec {
color: var(--teal-d);
font-size: 0.88rem;
font-weight: 600;
margin-top: 2px;
}
.doc-cred {
color: var(--muted);
font-size: 0.82rem;
margin-top: 6px;
}
/* ── Language chips ── */
.langs {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 14px;
}
.chip {
font-size: 0.74rem;
font-weight: 600;
color: var(--ink-2);
background: var(--teal-50);
border: 1px solid rgba(18, 156, 147, 0.18);
padding: 4px 10px;
border-radius: 999px;
}
/* ── Buttons ── */
.btn {
border: none;
border-radius: 11px;
padding: 11px 16px;
font: inherit;
font-weight: 700;
font-size: 0.88rem;
cursor: pointer;
transition: transform 0.12s, background 0.15s, border-color 0.15s, color 0.15s;
}
.btn:active {
transform: translateY(1px);
}
.btn.book {
margin-top: 18px;
background: var(--teal-d);
color: #fff;
}
.btn.book:hover {
background: var(--teal-700);
}
.doc[data-status="waitlist"] .btn.book {
background: var(--white);
color: var(--warn);
border: 1px solid rgba(217, 138, 43, 0.4);
}
.doc[data-status="waitlist"] .btn.book:hover {
background: rgba(217, 138, 43, 0.08);
border-color: var(--warn);
}
.btn.ghost {
background: var(--white);
border: 1px solid var(--line-2);
color: var(--ink-2);
}
.btn.ghost:hover {
background: var(--teal-50);
border-color: var(--teal);
color: var(--teal-d);
}
/* ── Empty state ── */
.empty {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 6px;
padding: 48px 20px;
background: var(--white);
border: 1px dashed var(--line-2);
border-radius: var(--r-lg);
}
.empty[hidden] {
display: none;
}
.empty svg {
width: 34px;
height: 34px;
fill: none;
stroke: var(--muted);
stroke-width: 1.8;
stroke-linecap: round;
margin-bottom: 6px;
}
.empty-title {
font-size: 1rem;
font-weight: 700;
color: var(--ink);
}
.empty-sub {
font-size: 0.88rem;
color: var(--muted);
}
.empty .btn {
margin-top: 12px;
}
/* ── 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;
}
@media (max-width: 520px) {
.page {
padding: 28px 16px 48px;
}
.page-head h1 {
font-size: 1.65rem;
}
.toolbar {
flex-direction: column;
}
.field.search,
.field.select {
flex: 1 1 auto;
width: 100%;
}
.grid {
grid-template-columns: 1fr;
}
}// ── Elements ─────────────────────────────────────────────────────────────────
const searchInput = document.getElementById("search");
const specialtySelect = document.getElementById("specialty");
const grid = document.getElementById("grid");
const cards = Array.from(grid.querySelectorAll(".doc"));
const resultLine = document.getElementById("result-line");
const emptyEl = document.getElementById("empty");
const clearBtn = document.getElementById("clear");
const toast = document.getElementById("toast");
const TOTAL = cards.length;
// ── Toast ────────────────────────────────────────────────────────────────────
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 2600);
}
// ── Filtering ────────────────────────────────────────────────────────────────
function applyFilters() {
const query = searchInput.value.trim().toLowerCase();
const specialty = specialtySelect.value;
let shown = 0;
cards.forEach((card) => {
const name = card.dataset.name.toLowerCase();
const matchesName = !query || name.includes(query);
const matchesSpecialty = specialty === "all" || card.dataset.specialty === specialty;
const visible = matchesName && matchesSpecialty;
card.hidden = !visible;
if (visible) shown++;
});
resultLine.innerHTML = 'Showing <span id="shown">' + shown + "</span> of " + TOTAL + " doctors";
grid.hidden = shown === 0;
emptyEl.hidden = shown !== 0;
}
searchInput.addEventListener("input", applyFilters);
specialtySelect.addEventListener("change", applyFilters);
// ── Clear filters ────────────────────────────────────────────────────────────
clearBtn.addEventListener("click", () => {
searchInput.value = "";
specialtySelect.value = "all";
applyFilters();
searchInput.focus();
});
// ── Book / waitlist actions ──────────────────────────────────────────────────
grid.addEventListener("click", (e) => {
const btn = e.target.closest("[data-action='book']");
if (!btn) return;
const card = btn.closest(".doc");
const name = "Dr. " + card.dataset.name;
if (card.dataset.status === "waitlist") {
btn.textContent = "On waitlist ✓";
btn.disabled = true;
showToast(name + " — you've been added to the waitlist. We'll reach out soon.");
} else {
showToast("Requesting a visit with " + name + " — choose a time next.");
}
});
// ── Init ─────────────────────────────────────────────────────────────────────
applyFilters();<!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>Our Doctors · Northpoint Clinic</title>
</head>
<body>
<main class="page">
<header class="page-head">
<p class="eyebrow">Northpoint Clinic</p>
<h1>Meet our doctors</h1>
<p class="lede">
A compassionate, board-certified team across primary and specialty
care. Filter by specialty or search by name to find the right fit.
</p>
</header>
<div class="toolbar" role="search">
<div class="field search">
<svg class="ic" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7" />
<line x1="16.5" y1="16.5" x2="21" y2="21" />
</svg>
<input
type="search"
id="search"
placeholder="Search by name…"
aria-label="Search doctors by name"
autocomplete="off"
/>
</div>
<div class="field select">
<label for="specialty" class="sr-only">Filter by specialty</label>
<select id="specialty" aria-label="Filter by specialty">
<option value="all">All specialties</option>
<option value="Primary care">Primary care</option>
<option value="Cardiology">Cardiology</option>
<option value="Dermatology">Dermatology</option>
<option value="Pediatrics">Pediatrics</option>
<option value="Psychiatry">Psychiatry</option>
<option value="Endocrinology">Endocrinology</option>
<option value="Orthopedics">Orthopedics</option>
</select>
<svg class="chev" viewBox="0 0 24 24" aria-hidden="true">
<polyline points="6 9 12 15 18 9" />
</svg>
</div>
</div>
<p class="result-line" id="result-line" aria-live="polite">
Showing <span id="shown">8</span> of 8 doctors
</p>
<section class="grid" id="grid" aria-label="Doctors">
<!-- card 1 -->
<article
class="doc"
data-name="Lena Okafor"
data-specialty="Cardiology"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #129c93; --g2: #0a655f"
>LO</span
>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Lena Okafor</h2>
<p class="doc-spec">Cardiology</p>
<p class="doc-cred">MD · Board certified, Cardiovascular Disease</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Igbo</li>
<li class="chip">French</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
<!-- card 2 -->
<article
class="doc"
data-name="Ravi Patel"
data-specialty="Primary care"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #ff7a66; --g2: #d4503e">RP</span>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Ravi Patel</h2>
<p class="doc-spec">Primary care</p>
<p class="doc-cred">MD · Board certified, Family Medicine</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Hindi</li>
<li class="chip">Gujarati</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
<!-- card 3 -->
<article
class="doc"
data-name="Maya Bloom"
data-specialty="Dermatology"
data-status="waitlist"
>
<div class="doc-head">
<span class="avatar" style="--g1: #7c6cff; --g2: #5a3fd6">MB</span>
<span class="status waitlist" title="Joining waitlist only"
>Waitlist</span
>
</div>
<h2 class="doc-name">Dr. Maya Bloom</h2>
<p class="doc-spec">Dermatology</p>
<p class="doc-cred">MD · Board certified, Dermatology</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Spanish</li>
</ul>
<button class="btn book" data-action="book">Join waitlist</button>
</article>
<!-- card 4 -->
<article
class="doc"
data-name="Daniel Cho"
data-specialty="Pediatrics"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #2f9e6f; --g2: #0c7a73">DC</span>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Daniel Cho</h2>
<p class="doc-spec">Pediatrics</p>
<p class="doc-cred">MD · Board certified, Pediatrics</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Korean</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
<!-- card 5 -->
<article
class="doc"
data-name="Amara Singh"
data-specialty="Psychiatry"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #e0913a; --g2: #b86d18">AS</span>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Amara Singh</h2>
<p class="doc-spec">Psychiatry</p>
<p class="doc-cred">MD · Board certified, Psychiatry</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Punjabi</li>
<li class="chip">Urdu</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
<!-- card 6 -->
<article
class="doc"
data-name="Tomás Herrera"
data-specialty="Endocrinology"
data-status="waitlist"
>
<div class="doc-head">
<span class="avatar" style="--g1: #129c93; --g2: #1f5fae">TH</span>
<span class="status waitlist" title="Joining waitlist only"
>Waitlist</span
>
</div>
<h2 class="doc-name">Dr. Tomás Herrera</h2>
<p class="doc-spec">Endocrinology</p>
<p class="doc-cred">MD · Board certified, Endocrinology</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Spanish</li>
<li class="chip">Portuguese</li>
</ul>
<button class="btn book" data-action="book">Join waitlist</button>
</article>
<!-- card 7 -->
<article
class="doc"
data-name="Hana Yamamoto"
data-specialty="Orthopedics"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #d4503e; --g2: #8f2f22">HY</span>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Hana Yamamoto</h2>
<p class="doc-spec">Orthopedics</p>
<p class="doc-cred">MD · Board certified, Orthopedic Surgery</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Japanese</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
<!-- card 8 -->
<article
class="doc"
data-name="Grace Adeyemi"
data-specialty="Primary care"
data-status="open"
>
<div class="doc-head">
<span class="avatar" style="--g1: #0c7a73; --g2: #16322f">GA</span>
<span class="status open" title="Accepting new patients"
>Accepting patients</span
>
</div>
<h2 class="doc-name">Dr. Grace Adeyemi</h2>
<p class="doc-spec">Primary care</p>
<p class="doc-cred">MD · Board certified, Internal Medicine</p>
<ul class="langs" aria-label="Languages spoken">
<li class="chip">English</li>
<li class="chip">Yoruba</li>
</ul>
<button class="btn book" data-action="book">Book</button>
</article>
</section>
<div class="empty" id="empty" hidden>
<svg viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7" />
<line x1="16.5" y1="16.5" x2="21" y2="21" />
</svg>
<p class="empty-title">No doctors match your search</p>
<p class="empty-sub">Try a different name or clear the filters.</p>
<button class="btn ghost" id="clear">Clear filters</button>
</div>
</main>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Doctors / Team Page
A calm, browsable directory for a clinic’s care team. Eight physicians sit in a responsive card grid, each led by a gradient initials avatar and an availability badge — Accepting patients in green or Waitlist in amber. Below the name come the specialty, credentials like MD and board certification, and a row of language chips so patients can find someone who speaks their language.
The toolbar pairs a live name search with a specialty dropdown. Typing or changing the filter narrows the grid instantly and updates the running count (“Showing 3 of 8 doctors”). When nothing matches, a friendly empty state offers a one-click way to clear the filters and start over.
Every card ends with an action: doctors accepting patients show a teal Book button, while waitlisted doctors show a softer Join waitlist button that confirms enrolment and disables itself once tapped. All feedback runs through a small toast, and the whole page is keyboard usable, screen-reader friendly and built with vanilla JS — no frameworks.
Illustrative UI only — not intended for real medical use.