Clinic — Pediatric Clinic Landing
A playful, child-friendly landing page for a fictional pediatric clinic, built with CSS shapes and emoji mascots instead of images. A bouncy hero leads into a tappable services grid that builds a live visit checklist, a reassuring band for parents, count-up stats with trust badges, a friendly pediatricians strip, and a warm booking call-to-action. Pastel gradients, gentle float animations, reveal-on-scroll and a mobile nav round it out.
MCP
代码
:root {
/* Pediatric pastel palette */
--sun: #ffd862;
--sun-soft: #fff2c8;
--sky: #7ec8f2;
--sky-soft: #dff0fb;
--bubble: #ff9ec4;
--bubble-soft: #ffe1ee;
--mint: #8fe3c4;
--coral: #ff9a7a;
--grape: #b9a6f1;
--ink: #2a2350;
--ink-2: #4a4374;
--muted: #7d77a3;
--bg: #fffdf4;
--white: #ffffff;
--line: rgba(42, 35, 80, 0.1);
--line-2: rgba(42, 35, 80, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 12px;
--r-md: 20px;
--r-lg: 32px;
--r-pill: 999px;
--shadow-sm: 0 4px 14px rgba(42, 35, 80, 0.08);
--shadow-md: 0 14px 34px rgba(42, 35, 80, 0.12);
--shadow-lg: 0 26px 60px rgba(42, 35, 80, 0.16);
--maxw: 1140px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-weight: 400;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
h1, h2, h3, h4 {
margin: 0;
line-height: 1.12;
letter-spacing: -0.02em;
font-weight: 800;
}
p {
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
img {
max-width: 100%;
}
:focus-visible {
outline: 3px solid var(--sky);
outline-offset: 3px;
border-radius: 6px;
}
/* ============ Decorative blobs ============ */
.sky-deco {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.blob {
position: absolute;
border-radius: 50%;
filter: blur(2px);
opacity: 0.5;
}
.blob--sun {
width: 420px;
height: 420px;
top: -160px;
right: -120px;
background: radial-gradient(circle at 30% 30%, var(--sun), var(--sun-soft));
animation: float 9s ease-in-out infinite;
}
.blob--sky {
width: 360px;
height: 360px;
bottom: 8%;
left: -140px;
background: radial-gradient(circle at 40% 30%, var(--sky), var(--sky-soft));
animation: float 11s ease-in-out infinite reverse;
}
.blob--bubble {
width: 280px;
height: 280px;
top: 44%;
right: -100px;
background: radial-gradient(circle at 40% 30%, var(--bubble), var(--bubble-soft));
animation: float 10s ease-in-out infinite;
}
.cloud {
position: absolute;
background: var(--white);
border-radius: var(--r-pill);
opacity: 0.6;
box-shadow:
-28px 8px 0 -4px var(--white),
30px 6px 0 -6px var(--white);
}
.cloud--1 { width: 70px; height: 26px; top: 16%; left: 12%; animation: drift 22s linear infinite; }
.cloud--2 { width: 54px; height: 20px; top: 30%; left: 60%; animation: drift 30s linear infinite; }
@keyframes float {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-26px) scale(1.04); }
}
@keyframes drift {
from { transform: translateX(-10vw); }
to { transform: translateX(110vw); }
}
/* ============ Layout helpers ============ */
main {
position: relative;
z-index: 1;
}
.section {
max-width: var(--maxw);
margin: 0 auto;
padding: 84px 24px;
}
.section--tight {
padding-top: 36px;
padding-bottom: 36px;
}
.section__head {
max-width: 640px;
margin: 0 auto 44px;
text-align: center;
}
.section__head h2 {
font-size: clamp(1.7rem, 3.6vw, 2.5rem);
margin: 14px 0 12px;
}
.section__head p {
color: var(--ink-2);
font-size: 1.05rem;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 14px;
border-radius: var(--r-pill);
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.pill--sun { background: var(--sun-soft); color: #8a6500; }
.pill--sky { background: var(--sky-soft); color: #1f6ea0; }
.pill--bubble { background: var(--bubble-soft); color: #b53774; }
/* ============ Buttons ============ */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 24px;
border-radius: var(--r-pill);
font-weight: 700;
font-size: 1rem;
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.16s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.btn--sm {
padding: 10px 18px;
font-size: 0.9rem;
}
.btn--primary {
background: var(--ink);
color: var(--white);
box-shadow: var(--shadow-sm);
}
.btn--primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
background: #342b66;
}
.btn--primary:active {
transform: translateY(0);
}
.btn--ghost {
background: var(--white);
color: var(--ink);
border-color: var(--line-2);
}
.btn--ghost:hover {
transform: translateY(-2px);
border-color: var(--ink);
box-shadow: var(--shadow-sm);
}
/* ============ Nav ============ */
.nav {
position: sticky;
top: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
max-width: var(--maxw);
margin: 0 auto;
padding: 16px 24px;
transition: background 0.25s ease, box-shadow 0.25s ease, padding 0.25s ease;
}
.nav.is-scrolled {
background: rgba(255, 253, 244, 0.86);
backdrop-filter: blur(10px);
box-shadow: var(--shadow-sm);
border-radius: 0 0 var(--r-md) var(--r-md);
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
font-weight: 800;
}
.brand__mark {
font-size: 1.7rem;
filter: drop-shadow(0 2px 4px rgba(42, 35, 80, 0.18));
animation: float 6s ease-in-out infinite;
}
.brand__name {
display: flex;
flex-direction: column;
font-size: 1.1rem;
line-height: 1;
color: var(--ink);
}
.brand__name small {
font-size: 0.62rem;
font-weight: 600;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
margin-top: 3px;
}
.nav__links {
display: flex;
align-items: center;
gap: 6px;
}
.nav__links > a {
padding: 9px 14px;
border-radius: var(--r-pill);
font-weight: 600;
font-size: 0.95rem;
color: var(--ink-2);
transition: background 0.18s ease, color 0.18s ease;
}
.nav__links > a:hover {
background: var(--white);
color: var(--ink);
}
.nav__links > a.is-active {
color: var(--ink);
background: var(--sun-soft);
}
.nav__cta {
background: var(--ink) !important;
color: var(--white) !important;
padding: 10px 18px !important;
box-shadow: var(--shadow-sm);
}
.nav__cta:hover {
transform: translateY(-1px);
}
.nav__toggle {
display: none;
flex-direction: column;
gap: 5px;
width: 46px;
height: 46px;
align-items: center;
justify-content: center;
background: var(--white);
border: 2px solid var(--line-2);
border-radius: var(--r-sm);
cursor: pointer;
}
.nav__toggle span {
width: 22px;
height: 2.5px;
background: var(--ink);
border-radius: 2px;
transition: transform 0.25s ease, opacity 0.2s ease;
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }
/* ============ Hero ============ */
.hero {
position: relative;
z-index: 1;
max-width: var(--maxw);
margin: 0 auto;
padding: 40px 24px 72px;
display: grid;
grid-template-columns: 1.05fr 0.95fr;
align-items: center;
gap: 32px;
}
.hero__text .pill {
margin-bottom: 18px;
}
.hero h1 {
font-size: clamp(2.4rem, 6vw, 4rem);
}
.hero__pop {
position: relative;
display: inline-block;
color: var(--bubble);
background: var(--bubble-soft);
padding: 0 0.12em;
border-radius: 14px;
transform: rotate(-2deg);
}
.hero__sparkle {
position: absolute;
top: -0.5em;
right: -0.5em;
font-size: 0.5em;
animation: float 4s ease-in-out infinite;
}
.hero__lead {
margin: 22px 0 28px;
font-size: 1.15rem;
color: var(--ink-2);
max-width: 30em;
}
.hero__actions {
display: flex;
flex-wrap: wrap;
gap: 14px;
}
.hero__chips {
display: flex;
flex-wrap: wrap;
gap: 10px;
list-style: none;
margin: 30px 0 0;
padding: 0;
}
.hero__chips li {
background: var(--white);
border: 1.5px solid var(--line);
padding: 9px 15px;
border-radius: var(--r-pill);
font-size: 0.88rem;
font-weight: 600;
color: var(--ink-2);
box-shadow: var(--shadow-sm);
}
/* Hero mascot art */
.hero__art {
position: relative;
display: grid;
place-items: center;
min-height: 360px;
}
.mascot {
position: relative;
width: 240px;
height: 240px;
border-radius: 46% 54% 52% 48% / 52% 46% 54% 48%;
background:
radial-gradient(circle at 35% 28%, var(--sun), var(--coral));
box-shadow: var(--shadow-lg), inset 0 -18px 32px rgba(42, 35, 80, 0.12);
animation: bob 5s ease-in-out infinite;
}
@keyframes bob {
0%, 100% { transform: translateY(0) rotate(-2deg); }
50% { transform: translateY(-18px) rotate(2deg); }
}
.mascot__face {
position: absolute;
inset: 0;
}
.mascot__eye {
position: absolute;
top: 42%;
width: 22px;
height: 30px;
background: var(--ink);
border-radius: 50%;
}
.mascot__eye--l { left: 32%; }
.mascot__eye--r { right: 32%; }
.mascot__eye::after {
content: "";
position: absolute;
top: 4px;
left: 5px;
width: 7px;
height: 7px;
background: var(--white);
border-radius: 50%;
}
.mascot__cheek {
position: absolute;
top: 56%;
width: 30px;
height: 18px;
background: var(--bubble);
border-radius: 50%;
opacity: 0.75;
filter: blur(1px);
}
.mascot__cheek--l { left: 20%; }
.mascot__cheek--r { right: 20%; }
.mascot__smile {
position: absolute;
top: 58%;
left: 50%;
transform: translateX(-50%);
width: 70px;
height: 36px;
border: 6px solid var(--ink);
border-top: none;
border-radius: 0 0 70px 70px;
}
.mascot__sticker {
position: absolute;
font-size: 1.8rem;
filter: drop-shadow(0 4px 6px rgba(42, 35, 80, 0.2));
}
.mascot__sticker--star { top: -14px; left: -10px; animation: float 5s ease-in-out infinite; }
.mascot__sticker--heart { bottom: 8px; right: -16px; animation: float 6s ease-in-out infinite reverse; }
.mascot__sticker--plus { top: 40%; right: -28px; font-size: 1.3rem; animation: float 7s ease-in-out infinite; }
.orbit {
position: absolute;
font-size: 2rem;
filter: drop-shadow(0 4px 8px rgba(42, 35, 80, 0.18));
}
.orbit--1 { top: 6%; left: 4%; animation: float 6s ease-in-out infinite; }
.orbit--2 { bottom: 4%; left: 16%; animation: float 7.5s ease-in-out infinite reverse; }
.orbit--3 { top: 10%; right: 2%; animation: float 8s ease-in-out infinite; }
/* ============ Services ============ */
.services {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.svc {
position: relative;
text-align: left;
font-family: inherit;
background: var(--white);
border: 2px solid var(--line);
border-radius: var(--r-lg);
padding: 26px 24px 24px;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.svc:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-md);
border-color: var(--line-2);
}
.svc__icon {
display: grid;
place-items: center;
width: 58px;
height: 58px;
border-radius: 20px;
font-size: 1.7rem;
background: color-mix(in srgb, var(--c) 32%, white);
margin-bottom: 16px;
transition: transform 0.2s ease;
}
.svc:hover .svc__icon {
transform: rotate(-8deg) scale(1.08);
}
.svc h3 {
font-size: 1.2rem;
margin-bottom: 8px;
}
.svc p {
color: var(--ink-2);
font-size: 0.95rem;
}
.svc__add {
display: inline-block;
margin-top: 16px;
font-weight: 700;
font-size: 0.85rem;
color: var(--muted);
transition: color 0.18s ease;
}
.svc:hover .svc__add { color: var(--ink); }
.svc.is-added {
border-color: var(--mint);
background: color-mix(in srgb, var(--mint) 14%, white);
}
.svc.is-added .svc__add {
color: var(--ok);
}
.svc.is-added .svc__add::before {
content: "✓ Added";
}
.svc.is-added .svc__add { font-size: 0; }
.svc.is-added .svc__add::before { font-size: 0.85rem; }
/* Checklist basket */
.basket {
display: flex;
align-items: center;
gap: 16px;
margin-top: 28px;
padding: 16px 22px;
background: var(--ink);
color: var(--white);
border-radius: var(--r-md);
box-shadow: var(--shadow-md);
animation: pop 0.25s ease;
}
@keyframes pop {
from { transform: scale(0.96); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.basket__text {
flex: 1;
font-size: 0.98rem;
}
.basket__text strong {
display: inline-grid;
place-items: center;
min-width: 28px;
height: 28px;
padding: 0 6px;
margin-right: 6px;
background: var(--sun);
color: var(--ink);
border-radius: var(--r-pill);
font-size: 0.95rem;
}
.basket__clear {
background: none;
border: none;
color: rgba(255, 255, 255, 0.7);
font-family: inherit;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
text-decoration: underline;
}
.basket__clear:hover { color: var(--white); }
/* ============ For parents band ============ */
.parents {
position: relative;
z-index: 1;
background: linear-gradient(135deg, var(--sky-soft), var(--bubble-soft));
margin: 20px 0;
}
.parents__inner {
max-width: var(--maxw);
margin: 0 auto;
padding: 72px 24px;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 40px;
align-items: center;
}
.parents__copy h2 {
font-size: clamp(1.6rem, 3.4vw, 2.3rem);
margin: 14px 0 14px;
}
.parents__copy > p {
color: var(--ink-2);
font-size: 1.05rem;
margin-bottom: 20px;
}
.parents__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 12px;
}
.parents__list li {
display: flex;
align-items: center;
gap: 12px;
font-weight: 600;
color: var(--ink);
}
.parents__list li span {
display: grid;
place-items: center;
width: 38px;
height: 38px;
background: var(--white);
border-radius: 12px;
font-size: 1.1rem;
box-shadow: var(--shadow-sm);
}
.quote {
margin: 0;
background: var(--white);
border-radius: var(--r-lg);
padding: 30px 28px;
box-shadow: var(--shadow-md);
border: 2px solid var(--line);
}
.quote blockquote {
margin: 0 0 18px;
font-size: 1.2rem;
font-weight: 600;
line-height: 1.45;
color: var(--ink);
}
.quote figcaption {
display: flex;
align-items: center;
gap: 12px;
color: var(--ink-2);
font-size: 0.92rem;
}
.quote__avatar {
display: grid;
place-items: center;
width: 46px;
height: 46px;
border-radius: 50%;
background: var(--sun-soft);
font-size: 1.4rem;
}
/* ============ Stats ============ */
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 18px;
}
.stat {
text-align: center;
background: var(--white);
border: 2px solid var(--line);
border-radius: var(--r-lg);
padding: 28px 16px;
box-shadow: var(--shadow-sm);
}
.stat__num {
display: block;
font-size: clamp(1.9rem, 4vw, 2.6rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(120deg, var(--sky), var(--bubble));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.stat__label {
display: block;
margin-top: 6px;
font-size: 0.9rem;
font-weight: 600;
color: var(--ink-2);
}
.badges {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
list-style: none;
margin: 34px 0 0;
padding: 0;
}
.badges li {
background: var(--white);
border: 1.5px solid var(--line);
padding: 10px 18px;
border-radius: var(--r-pill);
font-weight: 700;
font-size: 0.9rem;
color: var(--ink);
box-shadow: var(--shadow-sm);
}
/* ============ Team ============ */
.team {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.doc {
background: var(--white);
border: 2px solid var(--line);
border-radius: var(--r-lg);
padding: 26px 22px;
text-align: center;
transition: transform 0.18s ease, box-shadow 0.2s ease;
}
.doc:hover {
transform: translateY(-6px);
box-shadow: var(--shadow-md);
}
.doc__avatar {
display: grid;
place-items: center;
width: 84px;
height: 84px;
margin: 0 auto 14px;
border-radius: 50%;
font-size: 2.4rem;
background: color-mix(in srgb, var(--accent) 30%, white);
border: 3px solid color-mix(in srgb, var(--accent) 55%, white);
}
.doc h3 {
font-size: 1.1rem;
}
.doc__role {
margin-top: 4px;
font-weight: 700;
font-size: 0.85rem;
color: var(--muted);
}
.doc__bio {
margin: 10px 0 14px;
font-size: 0.9rem;
color: var(--ink-2);
}
.doc__tag {
display: inline-block;
padding: 5px 12px;
border-radius: var(--r-pill);
font-size: 0.78rem;
font-weight: 700;
background: color-mix(in srgb, var(--accent) 22%, white);
color: var(--ink);
}
/* ============ Book CTA ============ */
.cta {
position: relative;
z-index: 1;
max-width: var(--maxw);
margin: 0 auto;
padding: 24px 24px 84px;
}
.cta__card {
position: relative;
overflow: hidden;
text-align: center;
background: linear-gradient(135deg, var(--sun), var(--coral));
border-radius: var(--r-lg);
padding: 56px 28px;
box-shadow: var(--shadow-lg);
}
.cta__card h2 {
font-size: clamp(1.7rem, 4vw, 2.5rem);
color: var(--ink);
}
.cta__card p {
margin: 14px auto 26px;
max-width: 34em;
color: var(--ink-2);
font-size: 1.05rem;
font-weight: 500;
}
.cta__actions {
display: flex;
flex-wrap: wrap;
gap: 14px;
justify-content: center;
}
.cta__sticker {
position: absolute;
font-size: 3rem;
opacity: 0.85;
}
.cta__sticker--1 { top: 18px; left: 24px; animation: float 6s ease-in-out infinite; }
.cta__sticker--2 { bottom: 16px; right: 26px; animation: float 7s ease-in-out infinite reverse; }
/* ============ Footer ============ */
.footer {
position: relative;
z-index: 1;
background: var(--ink);
color: rgba(255, 255, 255, 0.82);
border-radius: var(--r-lg) var(--r-lg) 0 0;
}
.footer__top {
max-width: var(--maxw);
margin: 0 auto;
padding: 56px 24px 32px;
display: grid;
grid-template-columns: 1.5fr 1fr 1fr 1.2fr;
gap: 28px;
}
.footer__brand strong {
display: block;
margin: 8px 0 8px;
font-size: 1.1rem;
color: var(--white);
}
.footer__brand .brand__mark { animation: none; }
.footer__brand p {
font-size: 0.9rem;
max-width: 24em;
}
.footer__col h4 {
font-size: 0.8rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--sun);
margin-bottom: 14px;
}
.footer__col a,
.footer__col p {
display: block;
font-size: 0.92rem;
margin-bottom: 9px;
color: rgba(255, 255, 255, 0.78);
}
.footer__col a {
transition: color 0.16s ease;
}
.footer__col a:hover { color: var(--white); }
.footer__bottom {
border-top: 1px solid rgba(255, 255, 255, 0.12);
max-width: var(--maxw);
margin: 0 auto;
padding: 18px 24px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.6);
}
/* ============ Toast ============ */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translateX(-50%) translateY(20px);
background: var(--ink);
color: var(--white);
padding: 14px 22px;
border-radius: var(--r-pill);
font-weight: 600;
font-size: 0.95rem;
box-shadow: var(--shadow-lg);
z-index: 100;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast.is-show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ============ Reveal on scroll ============ */
.reveal {
opacity: 0;
transform: translateY(22px);
transition: opacity 0.55s ease, transform 0.55s ease;
}
.reveal.is-in {
opacity: 1;
transform: none;
}
/* ============ Responsive ============ */
@media (max-width: 900px) {
.services { grid-template-columns: repeat(2, 1fr); }
.stats { grid-template-columns: repeat(2, 1fr); }
.team { grid-template-columns: repeat(2, 1fr); }
.parents__inner { grid-template-columns: 1fr; gap: 28px; }
.footer__top { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 760px) {
.hero {
grid-template-columns: 1fr;
text-align: center;
padding-top: 24px;
}
.hero__actions, .hero__chips { justify-content: center; }
.hero__lead { margin-left: auto; margin-right: auto; }
.hero__art { order: -1; min-height: 300px; }
.nav__toggle { display: flex; }
.nav__links {
position: absolute;
top: calc(100% + 8px);
right: 16px;
left: 16px;
flex-direction: column;
align-items: stretch;
gap: 6px;
background: var(--white);
border: 2px solid var(--line);
border-radius: var(--r-md);
padding: 12px;
box-shadow: var(--shadow-md);
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}
.nav__links.is-open {
opacity: 1;
visibility: visible;
transform: none;
}
.nav__links > a { text-align: center; }
}
@media (max-width: 520px) {
.section { padding: 56px 18px; }
.services { grid-template-columns: 1fr; }
.stats { grid-template-columns: repeat(2, 1fr); }
.team { grid-template-columns: 1fr; }
.footer__top { grid-template-columns: 1fr; }
.footer__bottom { justify-content: center; text-align: center; }
.basket { flex-wrap: wrap; }
.basket__text { flex-basis: 100%; }
.cta__sticker { font-size: 2rem; }
.hero__chips { gap: 8px; }
.quote blockquote { font-size: 1.05rem; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
.reveal { opacity: 1; transform: none; }
}
/* Visibility guard: honor the [hidden] attribute over base display */
.basket[hidden] {
display: none;
}// Sunny Sprouts Pediatrics — landing interactions (vanilla JS, illustrative only)
(function () {
"use strict";
// ── Toast helper ───────────────────────────────────────────────────────────
const toastEl = document.getElementById("toast");
let toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-show"), 2800);
}
// Any element with data-toast fires a toast on click.
document.querySelectorAll("[data-toast]").forEach((el) => {
el.addEventListener("click", (e) => {
if (el.getAttribute("href") === "#" || el.dataset.preventNav === "true") {
e.preventDefault();
}
toast(el.dataset.toast);
});
});
// ── 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 || 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 (gentle staggered cascade) ────────────────────────────
const reveals = document.querySelectorAll(".reveal");
if ("IntersectionObserver" in window) {
const io = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry, i) => {
if (!entry.isIntersecting) return;
setTimeout(() => entry.target.classList.add("is-in"), (i % 4) * 80);
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 stats ────────────────────────────────────────────
function animateCount(el) {
const target = parseFloat(el.dataset.count);
const suffix = el.dataset.suffix || "";
const duration = 1500;
const start = performance.now();
function frame(now) {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - p, 3); // easeOutCubic
const value = Math.round(target * eased);
el.textContent = 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 spyIds = ["services", "parents", "team", "stats", "book"];
const linkFor = (id) =>
document.querySelector('.nav__links a[href="#' + id + '"]');
const spySections = spyIds
.map((id) => document.getElementById(id))
.filter(Boolean);
if ("IntersectionObserver" in window && spySections.length) {
const spy = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const link = linkFor(entry.target.id);
if (!link || !entry.isIntersecting) return;
document
.querySelectorAll(".nav__links a.is-active")
.forEach((a) => a.classList.remove("is-active"));
link.classList.add("is-active");
});
},
{ rootMargin: "-45% 0px -50% 0px" }
);
spySections.forEach((s) => spy.observe(s));
}
// ── Services checklist ("add to your visit") ───────────────────────────────
const grid = document.getElementById("serviceGrid");
const basket = document.getElementById("basket");
const basketCount = document.getElementById("basketCount");
const basketList = document.getElementById("basketList");
const basketClear = document.getElementById("basketClear");
const basketBook = document.getElementById("basketBook");
const chosen = new Set();
// Turn HTML entities (e.g. &) from data attributes back into text.
function decode(str) {
const t = document.createElement("textarea");
t.innerHTML = str;
return t.value;
}
function renderBasket() {
const items = [...chosen];
const n = items.length;
basketCount.textContent = String(n);
if (n === 0) {
basket.hidden = true;
return;
}
basket.hidden = false;
const names = items.map(decode);
// basketCount already shows the number; keep the trailing copy in sync.
basketList.textContent =
n === 1
? "service ready · " + names[0]
: "services ready · " + names.slice(0, 2).join(", ") +
(n > 2 ? " +" + (n - 2) + " more" : "");
}
if (grid) {
grid.addEventListener("click", (e) => {
const card = e.target.closest(".svc");
if (!card) return;
const name = card.dataset.svc;
const added = card.classList.toggle("is-added");
card.setAttribute("aria-pressed", String(added));
if (added) {
chosen.add(name);
toast("Added " + decode(name) + " to your checklist 💛");
} else {
chosen.delete(name);
toast("Removed " + decode(name));
}
renderBasket();
});
// Make each service card behave like a toggle for assistive tech.
grid.querySelectorAll(".svc").forEach((c) =>
c.setAttribute("aria-pressed", "false")
);
}
if (basketClear) {
basketClear.addEventListener("click", () => {
chosen.clear();
grid
.querySelectorAll(".svc.is-added")
.forEach((c) => {
c.classList.remove("is-added");
c.setAttribute("aria-pressed", "false");
});
renderBasket();
toast("Checklist cleared");
});
}
if (basketBook) {
basketBook.addEventListener("click", () => {
const n = chosen.size;
toast(
"Saved " + n + (n === 1 ? " service" : " services") +
" — booking is illustrative only 💛"
);
});
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Welcome · Sunny Sprouts Pediatrics</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;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Decorative floating blobs -->
<div class="sky-deco" aria-hidden="true">
<span class="blob blob--sun"></span>
<span class="blob blob--sky"></span>
<span class="blob blob--bubble"></span>
<span class="cloud cloud--1"></span>
<span class="cloud cloud--2"></span>
</div>
<!-- ============ NAV ============ -->
<header class="nav" id="nav">
<a class="brand" href="#top" aria-label="Sunny Sprouts Pediatrics home">
<span class="brand__mark" aria-hidden="true">🌻</span>
<span class="brand__name">Sunny Sprouts<small>Pediatrics</small></span>
</a>
<nav class="nav__links" id="navLinks" aria-label="Primary">
<a href="#services">Services</a>
<a href="#parents">For Parents</a>
<a href="#team">Our Team</a>
<a href="#stats">Why Us</a>
<a class="nav__cta" href="#book" data-toast="Booking widget is illustrative only 💛">Book a visit</a>
</nav>
<button class="nav__toggle" id="navToggle" aria-expanded="false" aria-controls="navLinks" aria-label="Open menu">
<span></span><span></span><span></span>
</button>
</header>
<main id="top">
<!-- ============ HERO ============ -->
<section class="hero" aria-labelledby="heroTitle">
<div class="hero__text reveal">
<span class="pill pill--bubble">👶 Newborn to teen care</span>
<h1 id="heroTitle">
Happy, healthy
<span class="hero__pop">kids<span class="hero__sparkle" aria-hidden="true">✨</span></span>
start here.
</h1>
<p class="hero__lead">
Gentle, unhurried pediatric care from a team that kids actually like to visit.
Same-week sick appointments, calm well-child checks, and answers when you need them most.
</p>
<div class="hero__actions">
<a class="btn btn--primary" href="#book" data-toast="Booking widget is illustrative only 💛">
Book a visit <span aria-hidden="true">→</span>
</a>
<a class="btn btn--ghost" href="#services">See our services</a>
</div>
<ul class="hero__chips" aria-label="Highlights">
<li>🩺 Open evenings & Saturdays</li>
<li>📞 24/7 nurse line</li>
<li>💉 Walk-in flu shots</li>
</ul>
</div>
<div class="hero__art reveal" aria-hidden="true">
<div class="mascot">
<div class="mascot__face">
<span class="mascot__cheek mascot__cheek--l"></span>
<span class="mascot__cheek mascot__cheek--r"></span>
<span class="mascot__eye mascot__eye--l"></span>
<span class="mascot__eye mascot__eye--r"></span>
<span class="mascot__smile"></span>
</div>
<span class="mascot__sticker mascot__sticker--star">⭐</span>
<span class="mascot__sticker mascot__sticker--heart">💗</span>
<span class="mascot__sticker mascot__sticker--plus">➕</span>
</div>
<span class="orbit orbit--1">🧸</span>
<span class="orbit orbit--2">🎈</span>
<span class="orbit orbit--3">🦷</span>
</div>
</section>
<!-- ============ SERVICES ============ -->
<section class="section" id="services" aria-labelledby="servicesTitle">
<div class="section__head reveal">
<span class="pill pill--sky">What we do</span>
<h2 id="servicesTitle">Care for every little milestone</h2>
<p>Tap a card to add it to your visit checklist — we’ll have it ready when you arrive.</p>
</div>
<div class="services" id="serviceGrid">
<button class="svc" data-svc="Well-child visits">
<span class="svc__icon" style="--c:var(--sun)">🌱</span>
<h3>Well-child visits</h3>
<p>Growth, milestones and gentle check-ups from your first newborn visit onward.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
<button class="svc" data-svc="Immunizations">
<span class="svc__icon" style="--c:var(--sky)">💉</span>
<h3>Immunizations</h3>
<p>On-schedule vaccines with comfort tricks that make the pokes a breeze.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
<button class="svc" data-svc="Sick visits">
<span class="svc__icon" style="--c:var(--bubble)">🤒</span>
<h3>Sick visits</h3>
<p>Same-week appointments for fevers, ear aches, rashes and the surprise sniffles.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
<button class="svc" data-svc="Development & behavior">
<span class="svc__icon" style="--c:var(--mint)">🧩</span>
<h3>Development & behavior</h3>
<p>Screenings and supportive guidance for learning, sleep and big feelings.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
<button class="svc" data-svc="Lactation support">
<span class="svc__icon" style="--c:var(--coral)">🍼</span>
<h3>Lactation support</h3>
<p>One-on-one feeding help from certified consultants — bottle or breast, no judgment.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
<button class="svc" data-svc="Teen wellness">
<span class="svc__icon" style="--c:var(--grape)">🪥</span>
<h3>Teen wellness</h3>
<p>Sports physicals, mental-health check-ins and a private space to ask anything.</p>
<span class="svc__add" aria-hidden="true">+ Add</span>
</button>
</div>
<div class="basket" id="basket" hidden>
<div class="basket__text">
<strong id="basketCount">0</strong>
<span id="basketList">services added to your checklist</span>
</div>
<button class="btn btn--primary btn--sm" id="basketBook" data-toast="We saved your checklist 💛 (demo only)">
Continue to booking
</button>
<button class="basket__clear" id="basketClear" aria-label="Clear checklist">Clear</button>
</div>
</section>
<!-- ============ FOR PARENTS BAND ============ -->
<section class="parents" id="parents" aria-labelledby="parentsTitle">
<div class="parents__inner reveal">
<div class="parents__copy">
<span class="pill pill--sun">For parents</span>
<h2 id="parentsTitle">You know your kid best. We’re here for the rest.</h2>
<p>
Parenting comes with a thousand questions. Our team picks up the phone, explains things
without the jargon, and never makes you feel rushed. Big worry or small one — it’s
always worth a call.
</p>
<ul class="parents__list">
<li><span aria-hidden="true">☎️</span> A real nurse answers our line, day or night.</li>
<li><span aria-hidden="true">📱</span> Message your care team and get same-day replies.</li>
<li><span aria-hidden="true">🗓️</span> Reminders so no shot or check-up slips through.</li>
<li><span aria-hidden="true">🤝</span> Translators available in 12 languages.</li>
</ul>
</div>
<figure class="quote">
<blockquote>
“They calmed me down at 2am over a fever, and had us seen by 9am. I’ve never
felt more looked after.”
</blockquote>
<figcaption>
<span class="quote__avatar" aria-hidden="true">🦋</span>
<span>
<strong>Priya R.</strong><br />
<small>Parent of two, with us since 2021</small>
</span>
</figcaption>
</figure>
</div>
</section>
<!-- ============ STATS / BADGES ============ -->
<section class="section section--tight" id="stats" aria-labelledby="statsTitle">
<div class="section__head reveal">
<span class="pill pill--bubble">Why families choose us</span>
<h2 id="statsTitle">Big care, measured in smiles</h2>
</div>
<div class="stats" id="statGrid">
<div class="stat reveal">
<span class="stat__num" data-count="18000" data-suffix="+">0</span>
<span class="stat__label">little patients seen</span>
</div>
<div class="stat reveal">
<span class="stat__num" data-count="98" data-suffix="%">0</span>
<span class="stat__label">parents would refer a friend</span>
</div>
<div class="stat reveal">
<span class="stat__num" data-count="12" data-suffix=" min">0</span>
<span class="stat__label">average wait time</span>
</div>
<div class="stat reveal">
<span class="stat__num" data-count="24" data-suffix="/7">0</span>
<span class="stat__label">nurse line, always on</span>
</div>
</div>
<ul class="badges reveal" aria-label="Accreditations">
<li>🏅 Board-certified pediatricians</li>
<li>🌟 Gold-Star Family Practice</li>
<li>🛡️ Vaccines for Children provider</li>
<li>💚 Breastfeeding-friendly certified</li>
</ul>
</section>
<!-- ============ TEAM STRIP ============ -->
<section class="section" id="team" aria-labelledby="teamTitle">
<div class="section__head reveal">
<span class="pill pill--sky">Meet the team</span>
<h2 id="teamTitle">Friendly faces your kids will remember</h2>
<p>Pediatricians and nurses who get down to eye level — sticker rewards included.</p>
</div>
<div class="team">
<article class="doc reveal" style="--accent:var(--sky)">
<div class="doc__avatar" aria-hidden="true">👩🏽⚕️</div>
<h3>Dr. Lena Okafor</h3>
<p class="doc__role">Lead Pediatrician</p>
<p class="doc__bio">Newborn care & gentle first visits. Fluent in dad-joke.</p>
<span class="doc__tag">Newborns</span>
</article>
<article class="doc reveal" style="--accent:var(--bubble)">
<div class="doc__avatar" aria-hidden="true">👨🏼⚕️</div>
<h3>Dr. Marco Avila</h3>
<p class="doc__role">Pediatrician</p>
<p class="doc__bio">Asthma & allergies. Keeps a bubble machine in his office.</p>
<span class="doc__tag">Allergy</span>
</article>
<article class="doc reveal" style="--accent:var(--sun)">
<div class="doc__avatar" aria-hidden="true">👩🏼⚕️</div>
<h3>Dr. Hana Kim</h3>
<p class="doc__role">Developmental Specialist</p>
<p class="doc__bio">Milestones, sleep & big feelings, one step at a time.</p>
<span class="doc__tag">Development</span>
</article>
<article class="doc reveal" style="--accent:var(--mint)">
<div class="doc__avatar" aria-hidden="true">🧑🏾⚕️</div>
<h3>Nurse Theo Banks</h3>
<p class="doc__role">Lactation Consultant, RN</p>
<p class="doc__bio">Feeding support & the calmest voice on the nurse line.</p>
<span class="doc__tag">Lactation</span>
</article>
</div>
</section>
<!-- ============ BOOK CTA ============ -->
<section class="cta" id="book" aria-labelledby="ctaTitle">
<div class="cta__card reveal">
<span class="cta__sticker cta__sticker--1" aria-hidden="true">🎈</span>
<span class="cta__sticker cta__sticker--2" aria-hidden="true">🌈</span>
<h2 id="ctaTitle">Ready when your little one is</h2>
<p>New patients welcome. Most insurance accepted. Bring the questions — and the snacks.</p>
<div class="cta__actions">
<a class="btn btn--primary" href="#" data-toast="Booking widget is illustrative only 💛">Book a visit</a>
<a class="btn btn--ghost" href="#" data-toast="Our nurse line is illustrative only ☎️">Call the nurse line</a>
</div>
</div>
</section>
</main>
<!-- ============ FOOTER ============ -->
<footer class="footer">
<div class="footer__top">
<div class="footer__brand">
<span class="brand__mark" aria-hidden="true">🌻</span>
<strong>Sunny Sprouts Pediatrics</strong>
<p>Growing healthy, happy kids in the Northpoint neighborhood since 2009.</p>
</div>
<nav class="footer__col" aria-label="Care">
<h4>Care</h4>
<a href="#services">Well-child visits</a>
<a href="#services">Immunizations</a>
<a href="#services">Sick visits</a>
<a href="#services">Lactation support</a>
</nav>
<nav class="footer__col" aria-label="Clinic">
<h4>Clinic</h4>
<a href="#team">Our team</a>
<a href="#parents">For parents</a>
<a href="#stats">Why us</a>
<a href="#book">Book a visit</a>
</nav>
<div class="footer__col">
<h4>Visit us</h4>
<p>14 Marigold Lane<br />Northpoint, OR 97000</p>
<p>Mon–Fri 8–7 · Sat 9–1</p>
</div>
</div>
<div class="footer__bottom">
<span>© 2026 Sunny Sprouts Pediatrics — a fictional clinic.</span>
<span class="footer__heart">Made with 💛 for little humans</span>
</div>
</footer>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Pediatric Clinic Landing
A warm, illustrated landing page for Sunny Sprouts Pediatrics, themed for families with young children. The whole look is built from CSS — soft pastel blobs drift behind the content, a rounded emoji-faced mascot bobs in the hero, and gentle float animations keep the page feeling alive without overwhelming it. The pastel-sun, sky-blue and bubblegum-pink palette and the rounded, friendly type set a calm, child-friendly tone throughout.
The services grid is interactive: each card — well-child visits, immunizations, sick visits, development & behavior, lactation support and teen wellness — acts as a toggle that adds the service to a live “visit checklist” pinned at the bottom of the section, with a running count and a summary of what you have selected. A for-parents band offers calm reassurance and a patient quote, while the stats row counts up to friendly numbers as it scrolls into view, backed by a row of trust badges. A team strip introduces fictional pediatricians, and a bright call-to-action closes the page.
Everything is vanilla JS: reveal-on-scroll cascades, a scroll-spy that highlights the active nav
link, an accessible mobile menu, count-up stats, and a small toast() helper that confirms every
action. Buttons are keyboard-usable, contrast meets WCAG AA, motion respects
prefers-reduced-motion, and the layout reflows cleanly down to ~360px.
Illustrative UI only — not intended for real medical use.