Clinic — Locations & Hours
A locations and hours page with three site cards, each pairing a stylized CSS map, full address, on-site service tags and a phone number with a Monday-to-Sunday hours table. Vanilla JS highlights today's row, reads the device clock and computes a live Open now, Closes soon or Closed badge from each day's real opening times, plus Get directions and Call buttons that surface a toast.
MCP
Code
: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;
}
a {
color: inherit;
}
:focus-visible {
outline: 3px solid rgba(18, 156, 147, 0.45);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ── Layout ─────────────────────────────────────────────────────────────── */
.locs {
max-width: 1140px;
margin: 0 auto;
padding: 44px 22px 64px;
}
.locs-head {
max-width: 680px;
margin-bottom: 30px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.72rem;
font-weight: 700;
color: var(--teal-d);
}
.locs-head h1 {
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.025em;
margin: 6px 0 12px;
}
.lede {
color: var(--ink-2);
font-size: 1.02rem;
max-width: 620px;
}
.lede em {
font-style: normal;
font-weight: 600;
color: var(--teal-d);
}
.now-line {
margin-top: 14px;
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.82rem;
font-weight: 600;
color: var(--muted);
background: var(--white);
border: 1px solid var(--line);
border-radius: 999px;
padding: 6px 14px;
box-shadow: var(--shadow-1);
}
.now-line::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--teal);
box-shadow: 0 0 0 3px rgba(18, 156, 147, 0.18);
}
/* ── Grid ───────────────────────────────────────────────────────────────── */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
gap: 22px;
}
/* ── Card ───────────────────────────────────────────────────────────────── */
.loc-card {
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
box-shadow: var(--shadow-1);
display: flex;
flex-direction: column;
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.loc-card:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-2);
border-color: var(--line-2);
}
/* ── Stylized CSS map ───────────────────────────────────────────────────── */
.map {
position: relative;
height: 138px;
overflow: hidden;
background:
radial-gradient(120% 120% at 80% 10%, #dcefe9 0%, #cdeae3 40%, #c2e4dd 100%);
border-bottom: 1px solid var(--line);
}
.map.map-alt {
background: radial-gradient(120% 120% at 20% 20%, #e3f0ff 0%, #d6e7f7 45%, #c9def1 100%);
}
.map.map-alt2 {
background: radial-gradient(120% 120% at 70% 30%, #e8f3ec 0%, #d7ecdf 45%, #c8e4d4 100%);
}
.map-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(16, 50, 47, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(16, 50, 47, 0.05) 1px, transparent 1px);
background-size: 26px 26px;
}
.map-road {
position: absolute;
background: var(--white);
opacity: 0.92;
border-radius: 2px;
}
.road-a {
height: 12px;
left: -6%;
right: -6%;
top: 58%;
transform: rotate(-7deg);
}
.road-b {
width: 12px;
top: -10%;
bottom: -10%;
left: 32%;
}
.road-c {
height: 9px;
left: 40%;
right: -8%;
top: 26%;
transform: rotate(4deg);
opacity: 0.8;
}
.map-park {
position: absolute;
width: 64px;
height: 50px;
right: 12%;
bottom: 12%;
background: #bfe3c9;
border-radius: 12px;
box-shadow: inset 0 0 0 3px rgba(47, 158, 111, 0.18);
}
.map-park-2 {
right: auto;
left: 10%;
top: 14%;
bottom: auto;
}
.map-water {
position: absolute;
left: -8%;
bottom: -20%;
width: 60%;
height: 80px;
background: linear-gradient(180deg, #bcdcf2, #a9d0ee);
border-radius: 50% 50% 0 0 / 100% 100% 0 0;
transform: rotate(-6deg);
opacity: 0.85;
}
.pin {
position: absolute;
left: 50%;
top: 50%;
width: 26px;
height: 26px;
margin: -34px 0 0 -13px;
background: var(--coral);
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
box-shadow: 0 6px 14px rgba(212, 80, 62, 0.35);
}
.pin-dot {
position: absolute;
inset: 7px;
background: var(--white);
border-radius: 50%;
}
.pin::after {
content: "";
position: absolute;
left: 50%;
bottom: -16px;
transform: translateX(-50%) rotate(45deg);
width: 18px;
height: 7px;
background: rgba(16, 50, 47, 0.18);
border-radius: 50%;
filter: blur(2px);
}
.map-label {
position: absolute;
left: 12px;
bottom: 12px;
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--ink-2);
background: rgba(255, 255, 255, 0.82);
padding: 4px 9px;
border-radius: 999px;
backdrop-filter: blur(2px);
}
/* ── Card body ──────────────────────────────────────────────────────────── */
.loc-body {
padding: 18px 18px 20px;
display: flex;
flex-direction: column;
flex: 1;
}
.loc-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.loc-top h2 {
font-size: 1.12rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.addr {
margin-top: 4px;
font-size: 0.86rem;
color: var(--muted);
}
/* ── Open/Closed badge ──────────────────────────────────────────────────── */
.badge {
flex-shrink: 0;
font-size: 0.72rem;
font-weight: 700;
padding: 5px 11px;
border-radius: 999px;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 6px;
}
.badge::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
}
.badge.is-open {
background: rgba(47, 158, 111, 0.14);
color: var(--ok);
}
.badge.is-soon {
background: rgba(217, 138, 43, 0.16);
color: var(--warn);
}
.badge.is-closed {
background: rgba(212, 80, 62, 0.12);
color: var(--danger);
}
/* ── Hours table ────────────────────────────────────────────────────────── */
.hours {
width: 100%;
border-collapse: collapse;
margin: 16px 0 6px;
font-size: 0.86rem;
}
.hours tr {
transition: background 0.15s;
}
.hours th {
text-align: left;
font-weight: 600;
color: var(--ink-2);
padding: 6px 8px;
width: 64px;
}
.hours td {
text-align: right;
color: var(--ink-2);
padding: 6px 8px;
font-variant-numeric: tabular-nums;
}
.hours td.closed,
.hours td:empty {
color: var(--muted);
}
.hours tr.is-today {
background: var(--teal-50);
}
.hours tr.is-today th,
.hours tr.is-today td {
color: var(--teal-700);
font-weight: 700;
}
.hours tr.is-today th {
border-radius: var(--r-sm) 0 0 var(--r-sm);
position: relative;
}
.hours tr.is-today th::before {
content: "";
position: absolute;
left: -2px;
top: 5px;
bottom: 5px;
width: 3px;
border-radius: 2px;
background: var(--teal);
}
.hours tr.is-today td {
border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
/* ── Services ───────────────────────────────────────────────────────────── */
.services {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 14px 0 18px;
}
.services li {
font-size: 0.76rem;
font-weight: 600;
color: var(--teal-d);
background: var(--teal-50);
border: 1px solid rgba(18, 156, 147, 0.16);
padding: 4px 10px;
border-radius: 999px;
}
/* ── Footer (phone + actions) ───────────────────────────────────────────── */
.loc-foot {
margin-top: auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
padding-top: 14px;
border-top: 1px solid var(--line);
}
.phone {
font-size: 0.92rem;
font-weight: 700;
color: var(--ink);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 7px;
}
.phone::before {
content: "";
width: 16px;
height: 16px;
flex-shrink: 0;
background: var(--teal-d);
-webkit-mask: no-repeat center / contain
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M6.6 10.8a15.5 15.5 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.25 11.4 11.4 0 0 0 3.6.57 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .57 3.6 1 1 0 0 1-.25 1z'/%3E%3C/svg%3E");
mask: no-repeat center / contain
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M6.6 10.8a15.5 15.5 0 0 0 6.6 6.6l2.2-2.2a1 1 0 0 1 1-.25 11.4 11.4 0 0 0 3.6.57 1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A17 17 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1 11.4 11.4 0 0 0 .57 3.6 1 1 0 0 1-.25 1z'/%3E%3C/svg%3E");
}
.phone:hover {
color: var(--teal-d);
text-decoration: underline;
}
.loc-actions {
display: flex;
gap: 8px;
}
/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn {
border: none;
border-radius: 10px;
padding: 9px 15px;
font: inherit;
font-weight: 600;
font-size: 0.84rem;
cursor: pointer;
transition: transform 0.12s, background 0.15s, border-color 0.15s, color 0.15s;
}
.btn:active {
transform: translateY(1px);
}
.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);
}
.btn.solid {
background: var(--teal-d);
color: #fff;
}
.btn.solid:hover {
background: var(--teal-700);
}
/* ── Toast ──────────────────────────────────────────────────────────────── */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
background: var(--ink);
color: #fff;
padding: 13px 20px;
border-radius: 12px;
font-size: 0.9rem;
font-weight: 500;
box-shadow: var(--shadow-2);
z-index: 50;
max-width: 90vw;
}
.toast[hidden] {
display: none;
}
/* ── Responsive ─────────────────────────────────────────────────────────── */
@media (max-width: 520px) {
.locs {
padding: 30px 16px 48px;
}
.locs-head h1 {
font-size: 1.6rem;
}
.grid {
grid-template-columns: 1fr;
}
.loc-foot {
flex-direction: column;
align-items: stretch;
}
.loc-actions {
justify-content: stretch;
}
.loc-actions .btn {
flex: 1;
}
}// ── Toast ────────────────────────────────────────────────────────────────────
const toast = document.getElementById("toast");
function showToast(msg) {
toast.textContent = msg;
toast.hidden = false;
clearTimeout(showToast._t);
showToast._t = setTimeout(() => (toast.hidden = true), 2600);
}
// ── Time helpers ─────────────────────────────────────────────────────────────
const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
// "HH:MM" → minutes since midnight, or null when empty/closed.
function toMinutes(value) {
if (!value) return null;
const [h, m] = value.split(":").map(Number);
if (Number.isNaN(h) || Number.isNaN(m)) return null;
return h * 60 + m;
}
function fmtClock(date) {
return date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
}
function fmtTime(mins) {
const h = Math.floor(mins / 60);
const m = mins % 60;
const d = new Date();
d.setHours(h, m, 0, 0);
return fmtClock(d);
}
// ── Live open / closed status ────────────────────────────────────────────────
// Returns { state: "open" | "soon-close" | "soon-open" | "closed", label }.
function computeStatus(card, now) {
const day = now.getDay(); // 0 = Sun … 6 = Sat
const minutesNow = now.getHours() * 60 + now.getMinutes();
const todayCell = card.querySelector(`tr[data-day="${day}"] td`);
const open = toMinutes(todayCell?.dataset.open);
const close = toMinutes(todayCell?.dataset.close);
// Open today and currently within hours.
if (open !== null && close !== null && minutesNow >= open && minutesNow < close) {
if (close - minutesNow <= 60) {
return { state: "soon-close", label: `Closes ${fmtTime(close)}` };
}
return { state: "open", label: `Open · until ${fmtTime(close)}` };
}
// Closed now — does it open later today?
if (open !== null && minutesNow < open) {
if (open - minutesNow <= 60) {
return { state: "soon-open", label: `Opens ${fmtTime(open)}` };
}
return { state: "closed", label: `Opens ${fmtTime(open)}` };
}
// Otherwise, find the next day with hours (within the coming week).
for (let step = 1; step <= 7; step++) {
const d = (day + step) % 7;
const cell = card.querySelector(`tr[data-day="${d}"] td`);
const o = toMinutes(cell?.dataset.open);
if (o !== null) {
const when = step === 1 ? "tomorrow" : DAY_NAMES[d];
return { state: "closed", label: `Opens ${when}` };
}
}
return { state: "closed", label: "Closed" };
}
function paintCard(card, now) {
const day = now.getDay();
// Highlight today's row.
card.querySelectorAll("tr[data-day]").forEach((row) => {
row.classList.toggle("is-today", Number(row.dataset.day) === day);
});
// Live badge.
const { state, label } = computeStatus(card, now);
const badge = card.querySelector("[data-status]");
badge.hidden = false;
badge.className = "badge";
if (state === "open") badge.classList.add("is-open");
else if (state === "soon-close" || state === "soon-open") badge.classList.add("is-soon");
else badge.classList.add("is-closed");
badge.textContent = label;
}
function refresh() {
const now = new Date();
document.getElementById("now-line").textContent =
`${DAY_NAMES[now.getDay()]}, ${fmtClock(now)} — your local time`;
document.querySelectorAll(".loc-card").forEach((card) => paintCard(card, now));
}
refresh();
// Re-evaluate every 30s so the badge flips at opening/closing time without a reload.
setInterval(refresh, 30000);
// ── Actions ──────────────────────────────────────────────────────────────────
document.querySelector(".grid").addEventListener("click", (e) => {
const btn = e.target.closest("[data-action]");
if (!btn) return;
const name = btn.dataset.name;
if (btn.dataset.action === "directions") {
showToast(`Opening directions to Northpoint — ${name}…`);
} else if (btn.dataset.action === "call") {
showToast(`Calling Northpoint — ${name}…`);
}
});<!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>Locations & Hours · Northpoint Clinic</title>
</head>
<body>
<main class="locs">
<header class="locs-head">
<p class="eyebrow">Northpoint Clinic</p>
<h1>Locations & hours</h1>
<p class="lede">
Three neighbourhood sites, all with on-site labs and same-day care.
Hours below reflect your local time — today is highlighted and the
<em>Open now</em> badge updates live.
</p>
<p class="now-line" id="now-line" aria-live="polite"></p>
</header>
<section class="grid">
<!-- ── Location 1 ── -->
<article class="loc-card" data-loc="downtown">
<div class="map" aria-hidden="true">
<span class="map-grid"></span>
<span class="map-road road-a"></span>
<span class="map-road road-b"></span>
<span class="map-road road-c"></span>
<span class="map-park"></span>
<span class="pin">
<span class="pin-dot"></span>
</span>
<span class="map-label">Harbour District</span>
</div>
<div class="loc-body">
<div class="loc-top">
<div>
<h2>Downtown Harbour</h2>
<p class="addr">
118 Marin Quay, Suite 300<br />Bayside, CA 94107
</p>
</div>
<span class="badge" data-status hidden>—</span>
</div>
<table class="hours" aria-label="Opening hours, Downtown Harbour">
<tbody>
<tr data-day="1">
<th scope="row">Mon</th>
<td data-open="08:00" data-close="19:00">8:00 AM – 7:00 PM</td>
</tr>
<tr data-day="2">
<th scope="row">Tue</th>
<td data-open="08:00" data-close="19:00">8:00 AM – 7:00 PM</td>
</tr>
<tr data-day="3">
<th scope="row">Wed</th>
<td data-open="08:00" data-close="19:00">8:00 AM – 7:00 PM</td>
</tr>
<tr data-day="4">
<th scope="row">Thu</th>
<td data-open="08:00" data-close="20:00">8:00 AM – 8:00 PM</td>
</tr>
<tr data-day="5">
<th scope="row">Fri</th>
<td data-open="08:00" data-close="18:00">8:00 AM – 6:00 PM</td>
</tr>
<tr data-day="6">
<th scope="row">Sat</th>
<td data-open="09:00" data-close="14:00">9:00 AM – 2:00 PM</td>
</tr>
<tr data-day="0">
<th scope="row">Sun</th>
<td data-open="" data-close="">Closed</td>
</tr>
</tbody>
</table>
<ul class="services" aria-label="On-site services">
<li>Primary care</li>
<li>Phlebotomy lab</li>
<li>Imaging & X-ray</li>
<li>Pharmacy</li>
</ul>
<div class="loc-foot">
<a class="phone" href="tel:+14155550118">(415) 555-0118</a>
<div class="loc-actions">
<button class="btn ghost" data-action="call" data-name="Downtown Harbour">
Call
</button>
<button class="btn solid" data-action="directions" data-name="Downtown Harbour">
Get directions
</button>
</div>
</div>
</div>
</article>
<!-- ── Location 2 ── -->
<article class="loc-card" data-loc="westgate">
<div class="map map-alt" aria-hidden="true">
<span class="map-grid"></span>
<span class="map-road road-a"></span>
<span class="map-road road-b"></span>
<span class="map-road road-c"></span>
<span class="map-park map-park-2"></span>
<span class="pin">
<span class="pin-dot"></span>
</span>
<span class="map-label">Westgate Green</span>
</div>
<div class="loc-body">
<div class="loc-top">
<div>
<h2>Westgate Family</h2>
<p class="addr">
4 Linden Walk, Ground Floor<br />Westgate, CA 94402
</p>
</div>
<span class="badge" data-status hidden>—</span>
</div>
<table class="hours" aria-label="Opening hours, Westgate Family">
<tbody>
<tr data-day="1">
<th scope="row">Mon</th>
<td data-open="07:30" data-close="18:00">7:30 AM – 6:00 PM</td>
</tr>
<tr data-day="2">
<th scope="row">Tue</th>
<td data-open="07:30" data-close="18:00">7:30 AM – 6:00 PM</td>
</tr>
<tr data-day="3">
<th scope="row">Wed</th>
<td data-open="07:30" data-close="18:00">7:30 AM – 6:00 PM</td>
</tr>
<tr data-day="4">
<th scope="row">Thu</th>
<td data-open="07:30" data-close="18:00">7:30 AM – 6:00 PM</td>
</tr>
<tr data-day="5">
<th scope="row">Fri</th>
<td data-open="07:30" data-close="16:30">7:30 AM – 4:30 PM</td>
</tr>
<tr data-day="6">
<th scope="row">Sat</th>
<td data-open="" data-close="">Closed</td>
</tr>
<tr data-day="0">
<th scope="row">Sun</th>
<td data-open="" data-close="">Closed</td>
</tr>
</tbody>
</table>
<ul class="services" aria-label="On-site services">
<li>Family medicine</li>
<li>Paediatrics</li>
<li>Immunisations</li>
<li>Women's health</li>
</ul>
<div class="loc-foot">
<a class="phone" href="tel:+16505550042">(650) 555-0042</a>
<div class="loc-actions">
<button class="btn ghost" data-action="call" data-name="Westgate Family">
Call
</button>
<button class="btn solid" data-action="directions" data-name="Westgate Family">
Get directions
</button>
</div>
</div>
</div>
</article>
<!-- ── Location 3 ── -->
<article class="loc-card" data-loc="riverside">
<div class="map map-alt2" aria-hidden="true">
<span class="map-grid"></span>
<span class="map-road road-a"></span>
<span class="map-road road-b"></span>
<span class="map-road road-c"></span>
<span class="map-water"></span>
<span class="pin">
<span class="pin-dot"></span>
</span>
<span class="map-label">Riverside Mile</span>
</div>
<div class="loc-body">
<div class="loc-top">
<div>
<h2>Riverside Urgent Care</h2>
<p class="addr">
92 Mill Race Road, Unit B<br />Riverside, CA 92501
</p>
</div>
<span class="badge" data-status hidden>—</span>
</div>
<table class="hours" aria-label="Opening hours, Riverside Urgent Care">
<tbody>
<tr data-day="1">
<th scope="row">Mon</th>
<td data-open="08:00" data-close="22:00">8:00 AM – 10:00 PM</td>
</tr>
<tr data-day="2">
<th scope="row">Tue</th>
<td data-open="08:00" data-close="22:00">8:00 AM – 10:00 PM</td>
</tr>
<tr data-day="3">
<th scope="row">Wed</th>
<td data-open="08:00" data-close="22:00">8:00 AM – 10:00 PM</td>
</tr>
<tr data-day="4">
<th scope="row">Thu</th>
<td data-open="08:00" data-close="22:00">8:00 AM – 10:00 PM</td>
</tr>
<tr data-day="5">
<th scope="row">Fri</th>
<td data-open="08:00" data-close="22:00">8:00 AM – 10:00 PM</td>
</tr>
<tr data-day="6">
<th scope="row">Sat</th>
<td data-open="09:00" data-close="20:00">9:00 AM – 8:00 PM</td>
</tr>
<tr data-day="0">
<th scope="row">Sun</th>
<td data-open="09:00" data-close="20:00">9:00 AM – 8:00 PM</td>
</tr>
</tbody>
</table>
<ul class="services" aria-label="On-site services">
<li>Urgent care</li>
<li>Minor injuries</li>
<li>On-site lab</li>
<li>Telehealth booth</li>
</ul>
<div class="loc-foot">
<a class="phone" href="tel:+19515550092">(951) 555-0092</a>
<div class="loc-actions">
<button class="btn ghost" data-action="call" data-name="Riverside Urgent Care">
Call
</button>
<button class="btn solid" data-action="directions" data-name="Riverside Urgent Care">
Get directions
</button>
</div>
</div>
</div>
</article>
</section>
</main>
<div class="toast" id="toast" hidden role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Locations & Hours
A calm, scannable directory for Northpoint Clinic’s three neighbourhood sites. Each card leads with a stylized, dependency-free CSS map — roads, a park or river, and a coral location pin — then sets out the full address, a Monday-to-Sunday hours table, a row of on-site service tags and a tappable phone number. The layout flows as a responsive grid that collapses to a single column on small screens.
The interactivity is genuinely live. Vanilla JS reads the device clock, highlights today’s row in each table and computes an Open now, Closes soon, Opens soon or Closed badge by comparing the current time against that day’s real opening and closing hours. When a site is shut, the badge looks ahead to the next open day and tells you when it reopens. A header line echoes the local day and time, and the status re-evaluates every thirty seconds so badges flip at opening and closing time without a reload.
Get directions and Call buttons confirm the chosen action through a small toast, keeping the copy empathetic and the page keyboard-friendly with clear focus states and AA-contrast colour throughout.
Illustrative UI only — not intended for real medical use.