Clinic — Clinic Landing
A marketing landing page for a fictional family practice with a translucent sticky nav, anchor links and a Book appointment CTA. The hero pairs a headline and dual call-to-action buttons with three count-up trust stats and a next-available appointment card, followed by a services grid, a why-choose-us feature row, a doctors strip, a patient testimonial and a footer carrying hours and contact. Smooth scroll, scroll-spy, reveal-on-scroll and a mobile menu round it out.
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);
--shadow-3: 0 24px 60px rgba(12, 122, 115, 0.2);
--maxw: 1080px;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 84px;
}
body {
font-family: var(--font);
background: var(--bg);
color: var(--ink);
-webkit-font-smoothing: antialiased;
line-height: 1.5;
}
a {
color: inherit;
text-decoration: none;
}
:focus-visible {
outline: 3px solid var(--teal);
outline-offset: 2px;
border-radius: 4px;
}
.skip-link {
position: fixed;
top: -60px;
left: 16px;
z-index: 200;
background: var(--ink);
color: #fff;
padding: 10px 16px;
border-radius: 10px;
font-weight: 600;
font-size: 0.88rem;
transition: top 0.2s;
}
.skip-link:focus {
top: 14px;
}
/* ── Buttons ─────────────────────────────────────────────── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: none;
border-radius: 999px;
padding: 13px 24px;
font: inherit;
font-weight: 600;
font-size: 0.94rem;
cursor: pointer;
white-space: nowrap;
transition: transform 0.12s ease, background 0.15s, border-color 0.15s, box-shadow 0.15s, color
0.15s;
}
.btn:active {
transform: translateY(1px);
}
.btn-sm {
padding: 9px 16px;
font-size: 0.86rem;
}
.btn-block {
width: 100%;
}
.btn-primary {
background: var(--teal-d);
color: #fff;
box-shadow: 0 6px 16px rgba(12, 122, 115, 0.28);
}
.btn-primary:hover {
background: var(--teal-700);
box-shadow: 0 10px 22px rgba(12, 122, 115, 0.34);
transform: translateY(-1px);
}
.btn-primary:active {
transform: translateY(0);
}
.btn-ghost {
background: var(--white);
border: 1px solid var(--line-2);
color: var(--ink-2);
}
.btn-ghost:hover {
border-color: var(--teal);
color: var(--teal-d);
background: var(--teal-50);
}
/* ── Brand ───────────────────────────────────────────────── */
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
}
.brand-mark {
display: grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 10px;
background: linear-gradient(145deg, var(--teal), var(--teal-700));
color: #fff;
box-shadow: 0 6px 14px rgba(12, 122, 115, 0.3);
}
.brand-name {
font-weight: 800;
font-size: 1.12rem;
letter-spacing: -0.02em;
color: var(--ink);
}
.brand-name.big {
font-size: 1.5rem;
}
.brand-dot {
color: var(--teal);
}
/* ── Nav ─────────────────────────────────────────────────── */
.nav {
position: sticky;
top: 0;
z-index: 100;
background: rgba(241, 247, 246, 0.72);
backdrop-filter: saturate(160%) blur(14px);
-webkit-backdrop-filter: saturate(160%) blur(14px);
border-bottom: 1px solid transparent;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
.nav.is-scrolled {
border-bottom-color: var(--line);
background: rgba(241, 247, 246, 0.88);
box-shadow: 0 6px 24px rgba(16, 50, 47, 0.05);
}
.nav-inner {
max-width: var(--maxw);
margin: 0 auto;
padding: 14px 20px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.nav-links {
display: flex;
align-items: center;
gap: 4px;
margin-left: auto;
}
.nav-links a[data-link] {
padding: 8px 14px;
border-radius: 999px;
font-weight: 500;
font-size: 0.92rem;
color: var(--ink-2);
transition: background 0.15s, color 0.15s;
}
.nav-links a[data-link]:hover {
background: var(--teal-50);
color: var(--teal-d);
}
.nav-links a[data-link].is-active {
background: var(--teal-50);
color: var(--teal-d);
font-weight: 600;
}
.nav-cta {
display: flex;
align-items: center;
gap: 10px;
}
.nav-toggle {
display: none;
flex-direction: column;
justify-content: center;
gap: 5px;
width: 42px;
height: 42px;
border: 1px solid var(--line-2);
border-radius: 12px;
background: var(--white);
cursor: pointer;
padding: 0 10px;
}
.nav-toggle span {
display: block;
height: 2px;
border-radius: 2px;
background: var(--ink);
transition: transform 0.22s, opacity 0.22s;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] span:nth-child(2) {
opacity: 0;
}
.nav-toggle[aria-expanded="true"] span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
/* ── Hero ────────────────────────────────────────────────── */
.hero {
max-width: var(--maxw);
margin: 0 auto;
padding: 64px 20px 40px;
}
.hero-grid {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 48px;
align-items: center;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--white);
border: 1px solid var(--line);
color: var(--teal-d);
font-weight: 600;
font-size: 0.82rem;
padding: 7px 14px;
border-radius: 999px;
box-shadow: var(--shadow-1);
}
.eyebrow .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ok);
box-shadow: 0 0 0 4px rgba(47, 158, 111, 0.18);
animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
box-shadow: 0 0 0 3px rgba(47, 158, 111, 0.18);
}
50% {
box-shadow: 0 0 0 6px rgba(47, 158, 111, 0.08);
}
}
.hero-copy h1 {
margin-top: 18px;
font-size: clamp(2.1rem, 5vw, 3.2rem);
font-weight: 800;
line-height: 1.08;
letter-spacing: -0.03em;
}
.hero-copy h1 .hl {
color: var(--teal-d);
position: relative;
white-space: nowrap;
}
.hero-copy h1 .hl::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0.06em;
height: 0.32em;
background: var(--coral-soft);
border-radius: 4px;
z-index: -1;
}
.lede {
margin-top: 18px;
max-width: 46ch;
font-size: 1.08rem;
color: var(--ink-2);
}
.hero-actions {
margin-top: 26px;
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.stats {
margin-top: 36px;
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.stat {
flex: 1;
min-width: 120px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px 18px;
box-shadow: var(--shadow-1);
}
.stat-num {
font-size: 1.7rem;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--teal-d);
line-height: 1;
}
.stat-label {
margin-top: 6px;
font-size: 0.82rem;
color: var(--muted);
}
/* ── Hero card ───────────────────────────────────────────── */
.hero-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 22px;
box-shadow: var(--shadow-2);
position: relative;
}
.hero-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(160deg, rgba(18, 156, 147, 0.4), transparent 60%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
.hc-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.hc-tag {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.hc-time {
margin-top: 8px;
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.02em;
color: var(--ink);
}
.hc-doc {
margin-top: 2px;
color: var(--ink-2);
font-size: 0.92rem;
font-weight: 500;
}
.hc-list {
list-style: none;
margin: 18px 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.hc-list li {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
color: var(--ink-2);
}
.tick {
display: grid;
place-items: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--teal-50);
color: var(--teal-d);
font-size: 0.7rem;
font-weight: 800;
flex-shrink: 0;
}
.hc-foot {
margin-top: 14px;
font-size: 0.78rem;
color: var(--muted);
text-align: center;
}
/* ── Badges ──────────────────────────────────────────────── */
.badge {
font-size: 0.72rem;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
white-space: nowrap;
}
.badge.ok {
background: rgba(47, 158, 111, 0.14);
color: var(--ok);
}
.badge.warn {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
/* ── Sections ────────────────────────────────────────────── */
.section {
max-width: var(--maxw);
margin: 0 auto;
padding: 64px 20px;
}
.section-tint {
max-width: none;
background: linear-gradient(180deg, #fff, #f6fbfa);
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
.section-tint > * {
max-width: var(--maxw);
margin-left: auto;
margin-right: auto;
}
.section-head {
text-align: center;
max-width: 620px;
margin: 0 auto 40px;
}
.kicker {
display: inline-block;
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--teal);
margin-bottom: 10px;
}
.section-head h2 {
font-size: clamp(1.6rem, 3.4vw, 2.2rem);
font-weight: 800;
letter-spacing: -0.025em;
}
.section-sub {
margin-top: 12px;
color: var(--ink-2);
font-size: 1.02rem;
}
.grid {
display: grid;
gap: 18px;
}
.services-grid {
grid-template-columns: repeat(3, 1fr);
}
.features-grid {
grid-template-columns: repeat(4, 1fr);
}
.doctors-grid {
grid-template-columns: repeat(3, 1fr);
}
/* ── Service cards ───────────────────────────────────────── */
.svc {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 24px;
box-shadow: var(--shadow-1);
transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
}
.svc:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-2);
border-color: rgba(18, 156, 147, 0.35);
}
.svc-icon {
display: grid;
place-items: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--teal-50);
font-size: 1.4rem;
}
.svc h3 {
margin-top: 16px;
font-size: 1.08rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.svc p {
margin-top: 8px;
font-size: 0.92rem;
color: var(--ink-2);
}
/* ── Features ────────────────────────────────────────────── */
.feature {
text-align: left;
padding: 8px 4px;
}
.feat-icon {
display: grid;
place-items: center;
width: 44px;
height: 44px;
border-radius: 12px;
background: var(--white);
border: 1px solid var(--line);
box-shadow: var(--shadow-1);
font-size: 1.3rem;
}
.feature h3 {
margin-top: 14px;
font-size: 1.02rem;
font-weight: 700;
}
.feature p {
margin-top: 6px;
font-size: 0.9rem;
color: var(--ink-2);
}
/* ── Doctors ─────────────────────────────────────────────── */
.doc {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 26px 24px;
text-align: center;
box-shadow: var(--shadow-1);
transition: transform 0.18s, box-shadow 0.18s;
}
.doc:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-2);
}
.doc-avatar {
display: grid;
place-items: center;
width: 72px;
height: 72px;
margin: 0 auto;
border-radius: 50%;
font-weight: 800;
font-size: 1.3rem;
color: #fff;
background: linear-gradient(145deg, var(--teal), var(--teal-700));
box-shadow: 0 8px 18px rgba(12, 122, 115, 0.28);
}
.doc-avatar.alt {
background: linear-gradient(145deg, var(--coral), #e85f48);
box-shadow: 0 8px 18px rgba(255, 122, 102, 0.35);
}
.doc-avatar.warm {
background: linear-gradient(145deg, #4f6f6a, var(--ink));
box-shadow: 0 8px 18px rgba(16, 50, 47, 0.3);
}
.doc h3 {
margin-top: 16px;
font-size: 1.1rem;
font-weight: 700;
}
.doc-spec {
margin-top: 3px;
font-size: 0.86rem;
font-weight: 600;
color: var(--teal-d);
}
.doc-bio {
margin: 12px 0 16px;
font-size: 0.9rem;
color: var(--ink-2);
}
/* ── Testimonial ─────────────────────────────────────────── */
.quote {
max-width: 760px;
margin: 0 auto;
background: linear-gradient(160deg, var(--teal-d), var(--teal-700));
color: #fff;
border-radius: var(--r-lg);
padding: 48px 44px 36px;
position: relative;
box-shadow: var(--shadow-3);
overflow: hidden;
}
.quote-mark {
position: absolute;
top: 8px;
left: 28px;
font-size: 7rem;
line-height: 1;
font-weight: 800;
color: rgba(255, 255, 255, 0.16);
}
.quote blockquote {
position: relative;
font-size: clamp(1.15rem, 2.4vw, 1.45rem);
font-weight: 500;
letter-spacing: -0.01em;
line-height: 1.5;
}
.quote figcaption {
margin-top: 26px;
display: flex;
align-items: center;
gap: 14px;
}
.q-avatar {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.18);
font-weight: 700;
font-size: 0.9rem;
flex-shrink: 0;
}
.q-meta {
display: flex;
flex-direction: column;
line-height: 1.3;
}
.q-meta strong {
font-weight: 700;
}
.q-meta span {
font-size: 0.84rem;
color: rgba(255, 255, 255, 0.78);
}
.q-stars {
margin-left: auto;
color: #ffd66e;
letter-spacing: 2px;
font-size: 1rem;
}
/* ── Book CTA ────────────────────────────────────────────── */
.book {
scroll-margin-top: 84px;
}
.book-card {
display: grid;
grid-template-columns: 1fr 360px;
gap: 40px;
align-items: center;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 40px;
box-shadow: var(--shadow-2);
}
.book-copy h2 {
font-size: clamp(1.6rem, 3.4vw, 2.1rem);
font-weight: 800;
letter-spacing: -0.025em;
}
.book-copy p {
margin-top: 12px;
color: var(--ink-2);
font-size: 1.02rem;
max-width: 42ch;
}
.book-form {
display: flex;
flex-direction: column;
gap: 14px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.field span {
font-size: 0.84rem;
font-weight: 600;
color: var(--ink-2);
}
.field input,
.field select {
font: inherit;
font-size: 0.94rem;
color: var(--ink);
background: var(--bg);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 12px 14px;
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.field input::placeholder {
color: var(--muted);
}
.field input:focus,
.field select:focus {
outline: none;
border-color: var(--teal);
background: var(--white);
box-shadow: 0 0 0 3px rgba(18, 156, 147, 0.15);
}
.field input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(212, 80, 62, 0.14);
}
.book-fine {
font-size: 0.78rem;
color: var(--muted);
text-align: center;
}
/* ── Footer ──────────────────────────────────────────────── */
.footer {
background: var(--ink);
color: rgba(255, 255, 255, 0.82);
margin-top: 40px;
scroll-margin-top: 84px;
}
.footer-grid {
max-width: var(--maxw);
margin: 0 auto;
padding: 52px 20px 36px;
display: grid;
grid-template-columns: 1.4fr 1fr 1fr;
gap: 40px;
}
.footer .brand-name {
color: #fff;
}
.footer .brand-dot {
color: var(--coral);
}
.foot-brand p {
margin-top: 14px;
font-size: 0.92rem;
max-width: 32ch;
color: rgba(255, 255, 255, 0.66);
}
.foot-col h4 {
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(255, 255, 255, 0.55);
margin-bottom: 14px;
}
.foot-col ul {
list-style: none;
display: flex;
flex-direction: column;
gap: 10px;
}
.foot-col li {
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 0.9rem;
}
.foot-col a {
transition: color 0.15s;
}
.foot-col a:hover {
color: #fff;
}
.closed {
color: var(--coral);
}
.footer-bar {
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.footer-bar > * {
display: block;
}
.footer-bar {
max-width: var(--maxw);
margin: 0 auto;
padding: 20px 20px 36px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
font-size: 0.84rem;
color: rgba(255, 255, 255, 0.55);
}
.footer-bar a:hover {
color: #fff;
}
/* ── Toast ───────────────────────────────────────────────── */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%) translateY(10px);
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: 150;
max-width: 90vw;
opacity: 0;
transition: opacity 0.2s, transform 0.2s;
}
.toast:not([hidden]) {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ── Reveal on scroll ────────────────────────────────────── */
.reveal {
opacity: 0;
transform: translateY(18px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.is-in {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
.reveal {
opacity: 1;
transform: none;
transition: none;
}
.eyebrow .dot {
animation: none;
}
}
/* ── Responsive ──────────────────────────────────────────── */
@media (max-width: 920px) {
.hero-grid {
grid-template-columns: 1fr;
gap: 36px;
}
.hero-card {
max-width: 420px;
}
.features-grid {
grid-template-columns: repeat(2, 1fr);
}
.book-card {
grid-template-columns: 1fr;
gap: 28px;
}
}
@media (max-width: 760px) {
.services-grid,
.doctors-grid {
grid-template-columns: repeat(2, 1fr);
}
.nav-links {
position: absolute;
top: calc(100% + 8px);
right: 20px;
left: 20px;
flex-direction: column;
align-items: stretch;
gap: 4px;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 10px;
box-shadow: var(--shadow-2);
transform-origin: top;
transform: scaleY(0.96);
opacity: 0;
visibility: hidden;
transition: opacity 0.18s, transform 0.18s, visibility 0.18s;
}
.nav-links.is-open {
opacity: 1;
visibility: visible;
transform: scaleY(1);
}
.nav-links a[data-link] {
padding: 12px 14px;
}
.nav-toggle {
display: flex;
}
.nav-cta .btn-sm {
display: none;
}
}
@media (max-width: 520px) {
.hero {
padding: 40px 18px 28px;
}
.section {
padding: 48px 18px;
}
.services-grid,
.doctors-grid,
.features-grid {
grid-template-columns: 1fr;
}
.stats {
gap: 10px;
}
.stat {
min-width: 0;
padding: 14px;
}
.stat-num {
font-size: 1.4rem;
}
.quote {
padding: 38px 24px 28px;
}
.quote figcaption {
flex-wrap: wrap;
}
.q-stars {
margin-left: 0;
width: 100%;
}
.book-card {
padding: 26px 20px;
}
.footer-grid {
grid-template-columns: 1fr;
gap: 30px;
}
.footer-bar {
flex-direction: column;
align-items: flex-start;
}
}// ── 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);
}
// ── Sticky nav shadow on scroll ──────────────────────────────────────────────
const nav = document.getElementById("nav");
const onScroll = () => nav.classList.toggle("is-scrolled", window.scrollY > 8);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
// ── Mobile nav toggle ────────────────────────────────────────────────────────
const navToggle = document.getElementById("navToggle");
const navLinks = document.getElementById("navLinks");
function setMenu(open) {
navLinks.classList.toggle("is-open", open);
navToggle.setAttribute("aria-expanded", String(open));
navToggle.setAttribute("aria-label", open ? "Close menu" : "Open menu");
}
navToggle.addEventListener("click", () =>
setMenu(navToggle.getAttribute("aria-expanded") !== "true")
);
// Smooth-scroll for in-page links + close mobile menu after a tap.
document.querySelectorAll('a[href^="#"]').forEach((link) => {
link.addEventListener("click", (e) => {
const id = link.getAttribute("href");
if (id.length < 2) return;
const target = document.querySelector(id);
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
history.replaceState(null, "", id);
setMenu(false);
});
});
// Close menu on Escape or when clicking outside.
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") setMenu(false);
});
document.addEventListener("click", (e) => {
if (!navLinks.classList.contains("is-open")) return;
if (e.target.closest("#navLinks") || e.target.closest("#navToggle")) return;
setMenu(false);
});
// ── Reveal on scroll ─────────────────────────────────────────────────────────
const reveals = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
const io = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry, i) => {
if (!entry.isIntersecting) return;
// Stagger siblings revealing together for a gentle cascade.
setTimeout(() => entry.target.classList.add("is-in"), (i % 4) * 70);
obs.unobserve(entry.target);
});
},
{ threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
);
reveals.forEach((el) => io.observe(el));
} else {
reveals.forEach((el) => el.classList.add("is-in"));
}
// ── Animated count-up for hero stats ─────────────────────────────────────────
function animateCount(el) {
const target = Number.parseFloat(el.dataset.count);
const decimals = Number.parseInt(el.dataset.decimals || "0", 10);
const suffix = el.dataset.suffix || "";
const duration = 1400;
const start = performance.now();
function frame(now) {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - (1 - p) ** 3; // easeOutCubic
const value = target * eased;
el.textContent =
(decimals ? value.toFixed(decimals) : Math.round(value).toLocaleString("en-US")) + suffix;
if (p < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
const statEls = document.querySelectorAll(".stat-num[data-count]");
if ("IntersectionObserver" in window) {
const statIo = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
animateCount(entry.target);
obs.unobserve(entry.target);
});
},
{ threshold: 0.6 }
);
statEls.forEach((el) => statIo.observe(el));
} else {
statEls.forEach(animateCount);
}
// ── Scroll-spy: highlight the active nav link ────────────────────────────────
const sections = ["services", "why", "doctors", "voices", "book"]
.map((id) => document.getElementById(id))
.filter(Boolean);
const linkFor = (id) => document.querySelector(`.nav-links a[href="#${id}"]`);
if ("IntersectionObserver" in window && sections.length) {
const spy = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const link = linkFor(entry.target.id);
if (!link) return;
if (entry.isIntersecting) {
document
.querySelectorAll(".nav-links a.is-active")
.forEach((a) => a.classList.remove("is-active"));
link.classList.add("is-active");
}
});
},
{ rootMargin: "-45% 0px -50% 0px" }
);
sections.forEach((s) => spy.observe(s));
}
// ── Call button feedback ─────────────────────────────────────────────────────
const callBtn = document.getElementById("callBtn");
if (callBtn) {
callBtn.addEventListener("click", () => showToast("Calling Northpoint Clinic — (555) 014-8821"));
}
// ── Booking form (illustrative — nothing is sent) ────────────────────────────
const bookForm = document.getElementById("bookForm");
if (bookForm) {
const nameInput = bookForm.elements.name;
nameInput.addEventListener("input", () => nameInput.classList.remove("invalid"));
bookForm.addEventListener("submit", (e) => {
e.preventDefault();
if (!nameInput.value.trim()) {
nameInput.classList.add("invalid");
nameInput.focus();
showToast("Please add your name so we know who to expect.");
return;
}
const reason = bookForm.elements.reason.value;
const first = nameInput.value.trim().split(/\s+/)[0];
showToast(
`Thanks, ${first} — your ${reason.toLowerCase()} request is in. We'll text to confirm.`
);
bookForm.reset();
});
}<!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>Welcome · Northpoint Clinic</title>
</head>
<body>
<a class="skip-link" href="#hero">Skip to content</a>
<!-- ── Sticky nav ── -->
<header class="nav" id="nav">
<div class="nav-inner">
<a class="brand" href="#hero" aria-label="Northpoint Clinic home">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none">
<path
d="M12 21s-7-4.35-7-10a4 4 0 0 1 7-2.65A4 4 0 0 1 19 11c0 5.65-7 10-7 10Z"
fill="currentColor"
/>
<path d="M12 8v6M9 11h6" stroke="#fff" stroke-width="1.6" stroke-linecap="round" />
</svg>
</span>
<span class="brand-name">Northpoint<span class="brand-dot">Clinic</span></span>
</a>
<nav class="nav-links" id="navLinks" aria-label="Primary">
<a href="#services" data-link>Services</a>
<a href="#why" data-link>Why us</a>
<a href="#doctors" data-link>Doctors</a>
<a href="#voices" data-link>Patients</a>
<a href="#contact" data-link>Contact</a>
</nav>
<div class="nav-cta">
<a class="btn btn-primary btn-sm" href="#book" data-link>Book appointment</a>
<button
class="nav-toggle"
id="navToggle"
aria-label="Open menu"
aria-expanded="false"
aria-controls="navLinks"
>
<span></span><span></span><span></span>
</button>
</div>
</div>
</header>
<main>
<!-- ── Hero ── -->
<section class="hero" id="hero">
<div class="hero-grid">
<div class="hero-copy reveal">
<span class="eyebrow">
<span class="dot" aria-hidden="true"></span> Accepting new patients
</span>
<h1>
Calm, attentive care for your <span class="hl">whole family</span>.
</h1>
<p class="lede">
Northpoint Clinic blends warm primary care with on-site specialists, same-week
appointments and video visits — so getting well never feels rushed.
</p>
<div class="hero-actions">
<a class="btn btn-primary" href="#book" data-link>Book appointment</a>
<a class="btn btn-ghost" href="tel:+15550148821" id="callBtn">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" aria-hidden="true">
<path
d="M5 4h3l1.5 4-2 1.5a12 12 0 0 0 5 5l1.5-2 4 1.5V18a2 2 0 0 1-2 2A15 15 0 0 1 4 6a2 2 0 0 1 2-2Z"
stroke="currentColor"
stroke-width="1.6"
stroke-linejoin="round"
/>
</svg>
Call us
</a>
</div>
<dl class="stats" aria-label="Clinic highlights">
<div class="stat">
<dt class="stat-num" data-count="14200" data-suffix="+">0</dt>
<dd class="stat-label">Patients cared for</dd>
</div>
<div class="stat">
<dt class="stat-num" data-count="18" data-suffix=" yrs">0</dt>
<dd class="stat-label">Serving the neighborhood</dd>
</div>
<div class="stat">
<dt class="stat-num" data-count="4.9" data-decimals="1" data-suffix="★">0</dt>
<dd class="stat-label">Average patient rating</dd>
</div>
</dl>
</div>
<aside class="hero-card reveal" aria-label="Next available appointment">
<div class="hc-top">
<span class="hc-tag">Next available</span>
<span class="badge ok">Today</span>
</div>
<p class="hc-time">3:40 PM</p>
<p class="hc-doc">Dr. Lena Okafor · Primary care</p>
<ul class="hc-list">
<li><span class="tick" aria-hidden="true">✓</span> Same-day sick visits</li>
<li><span class="tick" aria-hidden="true">✓</span> Video or in-person</li>
<li><span class="tick" aria-hidden="true">✓</span> Most insurance accepted</li>
</ul>
<a class="btn btn-primary btn-block" href="#book" data-link>Reserve this slot</a>
<p class="hc-foot">Northpoint Clinic · 218 Cedar Ave, Suite 3</p>
</aside>
</div>
</section>
<!-- ── Services ── -->
<section class="section" id="services">
<header class="section-head reveal">
<span class="kicker">What we do</span>
<h2>Services built around you</h2>
<p class="section-sub">
From routine check-ups to focused specialty care, all under one calm roof.
</p>
</header>
<div class="grid services-grid">
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">🩺</span>
<h3>Primary care</h3>
<p>Annual physicals, screenings and everyday concerns with a doctor who knows you.</p>
</article>
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">❤️</span>
<h3>Cardiology</h3>
<p>On-site ECG, blood-pressure management and heart-health planning.</p>
</article>
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">🧒</span>
<h3>Pediatrics</h3>
<p>Gentle, reassuring visits for newborns through teens, including vaccines.</p>
</article>
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">🦷</span>
<h3>Dental</h3>
<p>Cleanings, fillings and friendly preventive care for the whole family.</p>
</article>
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">🧪</span>
<h3>Lab & diagnostics</h3>
<p>Fast on-site bloodwork with results shared straight to your patient portal.</p>
</article>
<article class="svc reveal">
<span class="svc-icon" aria-hidden="true">💬</span>
<h3>Mental health</h3>
<p>Confidential counseling and telehealth sessions, scheduled around your week.</p>
</article>
</div>
</section>
<!-- ── Why choose us ── -->
<section class="section section-tint" id="why">
<header class="section-head reveal">
<span class="kicker">Why Northpoint</span>
<h2>Care that respects your time</h2>
</header>
<div class="grid features-grid">
<div class="feature reveal">
<span class="feat-icon" aria-hidden="true">⏱️</span>
<h3>Same-week visits</h3>
<p>Book online in under a minute and be seen within days, not weeks.</p>
</div>
<div class="feature reveal">
<span class="feat-icon" aria-hidden="true">📱</span>
<h3>Everything in one app</h3>
<p>Messages, records, prescriptions and visit summaries in your secure portal.</p>
</div>
<div class="feature reveal">
<span class="feat-icon" aria-hidden="true">🤝</span>
<h3>One team, all of you</h3>
<p>Your doctors share notes so you never have to repeat your story twice.</p>
</div>
<div class="feature reveal">
<span class="feat-icon" aria-hidden="true">🌙</span>
<h3>After-hours support</h3>
<p>A nurse line answers evenings and weekends, so you are never on your own.</p>
</div>
</div>
</section>
<!-- ── Doctors ── -->
<section class="section" id="doctors">
<header class="section-head reveal">
<span class="kicker">Meet the team</span>
<h2>Doctors who listen</h2>
<p class="section-sub">Familiar faces, accepting new patients this month.</p>
</header>
<div class="grid doctors-grid">
<article class="doc reveal">
<span class="doc-avatar" data-initials="LO" aria-hidden="true">LO</span>
<h3>Dr. Lena Okafor</h3>
<p class="doc-spec">Primary care & Cardiology</p>
<p class="doc-bio">Believes the best medicine starts with a good, unhurried conversation.</p>
<span class="badge ok">Accepting patients</span>
</article>
<article class="doc reveal">
<span class="doc-avatar alt" data-initials="RP" aria-hidden="true">RP</span>
<h3>Dr. Ravi Patel</h3>
<p class="doc-spec">Pediatrics</p>
<p class="doc-bio">Turns nervous check-ups into the easy part of a parent's week.</p>
<span class="badge ok">Accepting patients</span>
</article>
<article class="doc reveal">
<span class="doc-avatar warm" data-initials="MB" aria-hidden="true">MB</span>
<h3>Dr. Maya Bloom</h3>
<p class="doc-spec">Mental health</p>
<p class="doc-bio">Creates a steady, judgment-free space to talk things through.</p>
<span class="badge warn">Waitlist open</span>
</article>
</div>
</section>
<!-- ── Testimonial ── -->
<section class="section" id="voices">
<figure class="quote reveal">
<span class="quote-mark" aria-hidden="true">“</span>
<blockquote>
I have never felt rushed at Northpoint. They remembered my daughter's name, followed up
the next day, and made a stressful week feel manageable. It finally feels like care.
</blockquote>
<figcaption>
<span class="q-avatar" aria-hidden="true">JR</span>
<span class="q-meta">
<strong>Jordan Reyes</strong>
<span>Patient since 2021</span>
</span>
<span class="q-stars" aria-label="Rated 5 out of 5">★★★★★</span>
</figcaption>
</figure>
</section>
<!-- ── Book CTA ── -->
<section class="section book" id="book">
<div class="book-card reveal">
<div class="book-copy">
<h2>Ready when you are</h2>
<p>
Pick a time that fits your life. We will confirm by text and send everything you need
before your visit.
</p>
</div>
<form class="book-form" id="bookForm" novalidate>
<label class="field">
<span>Full name</span>
<input type="text" name="name" placeholder="Alex Morgan" autocomplete="name" required />
</label>
<label class="field">
<span>Reason for visit</span>
<select name="reason">
<option>Primary care</option>
<option>Cardiology</option>
<option>Pediatrics</option>
<option>Dental</option>
<option>Mental health</option>
</select>
</label>
<button class="btn btn-primary btn-block" type="submit">Request appointment</button>
<p class="book-fine">Illustrative form — no data is sent anywhere.</p>
</form>
</div>
</section>
</main>
<!-- ── Footer ── -->
<footer class="footer" id="contact">
<div class="footer-grid">
<div class="foot-brand">
<span class="brand-name big">Northpoint<span class="brand-dot">Clinic</span></span>
<p>Warm, attentive care for your whole family — in person or online.</p>
</div>
<div class="foot-col">
<h4>Hours</h4>
<ul>
<li><span>Mon – Fri</span><span>8:00 AM – 7:00 PM</span></li>
<li><span>Saturday</span><span>9:00 AM – 2:00 PM</span></li>
<li><span>Sunday</span><span class="closed">Closed</span></li>
</ul>
</div>
<div class="foot-col">
<h4>Contact</h4>
<ul>
<li><a href="tel:+15550148821">(555) 014-8821</a></li>
<li><a href="mailto:[email protected]">[email protected]</a></li>
<li>218 Cedar Ave, Suite 3</li>
</ul>
</div>
</div>
<div class="footer-bar">
<span>© 2026 Northpoint Clinic — a fictional practice.</span>
<a href="#hero" data-link>Back to top ↑</a>
</div>
</footer>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Clinic Landing
A complete marketing landing page for Northpoint Clinic, a fictional family practice. A translucent sticky navigation bar gains a subtle shadow as the page scrolls, with anchor links that smooth-scroll to each section and a persistent Book appointment call to action. The hero leads with a headline and an accepting new patients eyebrow, a primary booking button and a secondary Call us button, and three trust stats — patients cared for, years serving the neighborhood and average rating — that count up the first time they scroll into view. Alongside it, a next-available appointment card surfaces the soonest slot.
Below the hero, a services grid presents six cards with emoji icons and gentle hover lift, a Why Northpoint feature row highlights four reasons to choose the clinic, and a doctors strip introduces three clinicians with status badges. A warm patient testimonial sits on a teal gradient, and a closing booking section pairs calm copy with a small, validated request form. The footer carries opening hours, contact details and a back-to-top link.
Everything runs on vanilla JS: an IntersectionObserver drives the reveal-on-scroll cascade and the stat count-up, a scroll-spy highlights the active nav link, the mobile menu toggles open with full keyboard and outside-click handling, and form submissions and the call button surface friendly toast feedback. Motion is muted for visitors who prefer reduced motion.
Illustrative UI only — not intended for real medical use.