Health Care Landing Page 3 - JavaScript (Vanilla), HTML5, CSS3 Fundamental Project 4 (Framework-free SPA)
An educational, portfolio-ready healthcare landing page that behaves like a small single-page application (SPA) using only native browser APIs: semantic HTML5, a single large CSS3 design system, and vanilla JavaScript split into ES modules. There is no React, Vue, Angular, or Next.js—so you can study routing, state, and UI polish without framework magic. It is ideal for learners who want to see how production-like patterns (History API routing, accessible modals, carousels, and shared state) look in plain JS.
- Live Demo: https://healthcare-ui-3.vercel.app/
- Project Summary
- What This Project Is (and Is Not)
- Features & Functionality
- Technology Stack
- Dependencies & Tooling
- Project Structure
- Client-Side Routes (No Server Router)
- API, Backend & Environment Variables
- How to Run Locally
- Build & Deploy (Vercel)
- Linting
- Architecture Walkthrough
- Reusing Parts in Other Projects
- Keywords
- Conclusion
- License
Health Care Landing Page 3 (healthcare-ui-3 on npm) is a single-page healthcare marketing site you can run entirely in the browser: one index.html, shared styles.css / fonts.css, and a small set of ES module files under js/ that power navigation, animations, modals, tabs, a filterable doctors carousel, and optional hero imagery fallbacks.
Goals:
- Show how far you can go without a UI framework while keeping patterns familiar to React-era developers (central state, subscribers, route-driven UI).
- Stay deployable as static files (Vercel or any static host) with no backend required for the demo.
- Serve as a teaching reference and portfolio piece for frontend fundamentals, accessibility, and polish.
| Aspect | Details |
|---|---|
| It is | A static-first frontend demo: one index.html, rich styles.css, and modular JS that adds SPA-style navigation, animations, modals, tabs, and a doctors “reel.” |
| It is not | A full-stack app. There is no custom REST API in this repo, no database, and no server-side rendering. Forms and buttons are UI demos unless you wire them to a real backend. |
| Deploy target | Vercel (or any static host). vercel.json rewrites unknown paths to index.html so client routes work on refresh. |
- Responsive layout — Mobile drawer navigation, fluid typography, and section layouts that adapt from narrow phones to wide desktops.
- Sticky header & skip link — Keyboard-friendly “Skip to main content” and an accessible primary nav.
- Hero experience — Rotating background layers (Ken Burns–style motion), crossfade between slides, optional Unsplash fallbacks if local hero images fail, parallax accent layer.
- Booking / lead form (UI) — Glass-style card in the hero; educational markup only (no live submission endpoint in-repo).
- Services grid & modals — Cards open a shared
<dialog>-based modal with per-service content (serviceModal.js). - Stats, tabs, tables — Demo content for layout and accessibility patterns.
- Plans & information — Accessible tablist with optional auto-rotation; staggered line reveals; min-height sync so shorter tabs do not jump the page (
tabs.js). - Doctors directory — Infinite horizontal reel (duplicated ribbon for seamless loop), department dropdown filter (
dropdown.js,doctorsCarousel.js,appState.js), doctor profile modal (doctorModal.js). - Resources section — Staggered animations similar to tab panels (
resourcesLines.js). - Contact hero — Full-bleed image section with overlays and CTA.
- Footer — Multi-column links and copyright bar.
- SEO-oriented
<head>— Meta description, Open Graph, Twitter cards, JSON-LD, canonical URL (seeindex.html). - Ripple / CTA shine — Optional micro-interactions (
ripple.js+ CSS). - Safe images —
data-safe-srcpattern tries fallbacks when an image errors (safeImage.js).
| Layer | Choice | Why it matters for learners |
|---|---|---|
| Markup | HTML5 | Semantic regions (<header>, <main>, <section>, <dialog>), ARIA on tabs and modals. |
| Styling | CSS3 | Custom properties, grid/flex, animations, prefers-reduced-motion, container queries where used. |
| Behavior | JavaScript (ES modules) | import/export, no bundler required in dev if you use a local server (browser resolves modules). |
| Icons | Remix Icon (CDN) | Large set of medical/UI icons via classes like ri-heart-pulse-line. |
| Fonts | DM Sans & Fraunces | Loaded via fonts.css and local woff2 under public/fonts/ (see index.html preloads). |
| Hosting | Vercel | Static output directory + SPA-style rewrites in vercel.json. |
Runtime (in the browser): None from npm—only CDN styles for Remix Icon.
Dev dependencies (from package.json):
| Package | Role |
|---|---|
eslint |
Lints js/**/*.js and scripts/**/*.mjs for common mistakes and style consistency. |
@eslint/js |
ESLint’s recommended baseline rules (flat config). |
globals |
Predefined browser and node global names so ESLint does not flag window, document, fs, etc. |
Example — install dev tools:
npm installExample — run linter:
npm run linthealthcare-ui-3/
├── index.html # Single page: all sections + dialogs + SEO meta
├── styles.css # Global design system and section styles
├── fonts.css # @font-face for self-hosted DM Sans / Fraunces
├── package.json # npm scripts: build, lint
├── package-lock.json
├── eslint.config.js # ESLint flat config
├── vercel.json # Rewrites, cache headers, security headers
├── README.md
├── js/ # ES modules (entry: main.js)
│ ├── main.js # Wires all features after DOM ready
│ ├── router.js # History API: path → scroll to section id
│ ├── appState.js # Tiny publish/subscribe state (route, department, drawer)
│ ├── sidebar.js # Mobile drawer open/close + focus trap basics
│ ├── ripple.js # Button ripple effect
│ ├── scrollReveal.js # IntersectionObserver reveal + stagger
│ ├── parallax.js # Subtle hero parallax
│ ├── tabs.js # Accessible tabs + optional carousel + min-height measure
│ ├── resourcesLines.js # Staggered lines in resources block
│ ├── dropdown.js # Department filter + outside click / Escape
│ ├── doctorsCarousel.js # Infinite reel + prev/next + filter mode
│ ├── safeImage.js # Image onerror fallback chain
│ ├── serviceModal.js # Shared modal for service details
│ └── doctorModal.js # Shared modal for doctor profiles
├── scripts/
│ └── copy-static.mjs # Copies HTML/CSS/JS/public → dist/ for deploy
└── public/ # Static assets (fonts, hero images, favicon, robots.txt, etc.)
The dist/ folder is generated by npm run build; do not treat it as the source of truth.
Routing is implemented in js/router.js with the History API (pushState / popstate). Paths map to element ids on the same page:
| URL path | Scroll target (id in index.html) |
|---|---|
/ |
home (special case: scroll to top) |
/services |
service |
/about |
about |
/doctors |
pages |
/why |
blog |
/resources |
resources |
/contact |
contact |
How it works (learning angle):
- Nav links use
data-spa-linkandhref="/path". - Click is intercepted so the browser does not do a full document load.
history.pushStateupdates the URL;scrollToSectionbrings the right block into view.- On Vercel,
vercel.jsonsendsindex.htmlfor unknown paths so/servicesdoes not 404 on refresh.
There are no Express/FastAPI/Next.js API routes—only this client-side convention.
This project does not define any server-side API routes. All “actions” (open modal, filter doctors, change tab) run in the browser.
If you add a real backend later, you would typically:
- Point forms to your API with
fetch()or a<form action="https://api.example.com/...">. - Store secrets (API keys) only on the server or in serverless environment variables—not in client-side JS committed to Git.
You do not need a .env file to run, build, or deploy this project as-is. Nothing in the codebase reads process.env for app behavior (the site is static).
Optional (future) ideas:
- If you add a build step that injects a public analytics ID, some teams use
VITE_*or similar with a bundler; this repo currently has no Vite/Webpack env injection. - For Vercel, you can add environment variables in the project dashboard for serverless functions you might add later—they are not required for the current static output.
git clone <your-repo-url>
cd healthcare-ui-3
npm installOpening index.html as a file:// URL often breaks ES module imports. Use any static server from the project root:
Node (if you have npx):
npx serve .
# or
npx http-server . -p 8080Python 3:
python3 -m http.server 8000Then open http://localhost:8000 (or the port shown).
Right-click index.html → “Open with Live Server” (extension), ensuring the server root is the repo root so /js/... and /public/... resolve correctly.
The build copies static assets into dist/:
npm run buildscripts/copy-static.mjs copies index.html, styles.css, fonts.css, the js/ tree, and public/ into dist/. Vercel’s outputDirectory is dist (see vercel.json).
Educational note: This is not a compiled bundle—just a predictable folder layout for hosting.
npm run lintRuns ESLint on js/**/*.js and scripts/**/*.mjs. Fix reported issues before large refactors so module boundaries stay clear.
main.js imports feature modules and runs their init* functions after DOMContentLoaded. Order matters (e.g. router after DOM, observers after sections exist—see file header comment).
A minimal observer pattern:
import { getState, setState, subscribe } from "./appState.js";
subscribe((state) => {
console.log(state.route, state.department);
});
setState({ department: "cardiology" });This mirrors the idea of React context without JSX—good for teaching one source of truth.
Syncs URL ↔ scroll position ↔ nav aria-current. Invalid paths fall back to /.
Uses two ribbons of duplicated cards so the translate animation can loop; when a department filter is active or prefers-reduced-motion is on, the second ribbon is hidden and the reel pauses—read the file comments for why.
Implements WAI-ARIA tabs: role="tablist", aria-selected, aria-controls, and hidden on panels. Measures max panel height (FAQ <details> opened during measure) to reduce layout shift.
Use the native <dialog> element where possible for focus and showModal() semantics—compare with div-based modals in older tutorials.
| Module / pattern | Reuse idea |
|---|---|
router.js + SECTION_BY_PATH |
Copy the map and ids for any one-page marketing site with “fake” paths. |
appState.js |
Drop in as a 40-line global store for small demos. |
tabs.js |
Reuse for any tabbed pricing/FAQ; adjust selectors and carousel flag. |
doctorsCarousel.js |
Adapt track markup for logos, testimonials, or cards—keep ResizeObserver ideas. |
ripple.js / CTA shine CSS |
Paste classes onto buttons in another design system. |
styles.css tokens (:root) |
Copy --primary, --max-9xl, etc., to bootstrap a second brand theme. |
Always keep accessibility: preserve roles, labels, and keyboard behavior when you copy markup.
Healthcare website, landing page, vanilla JavaScript, ES modules, HTML5, CSS3, single-page application, History API, accessibility, responsive design, Intersection Observer, dialog element, portfolio project, frontend education, Remix Icon, Fraunces, DM Sans, Vercel, static site, open source, MIT License, patient experience UI, medical UI demo
Health Care Landing Page 3 is a framework-free way to study modern front-end mechanics: routing without React Router, state without Redux, and motion without a component library. Use it as a course companion, a portfolio piece, or a starting sketch for a real clinic site—then swap copy, connect real APIs, and tighten accessibility with your own content audit.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project - feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊










