Pages Medium
Blog Listing Page
A blog index page with featured post hero, category filter tabs, search bar, post grid cards, and pagination. No external libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif;
background: #f8fafc;
color: #1e293b;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
a {
text-decoration: none;
color: inherit;
}
button {
font-family: inherit;
cursor: pointer;
}
/* ── Nav ── */
.nav {
position: sticky;
top: 0;
z-index: 100;
background: #fff;
border-bottom: 1px solid #e2e8f0;
}
.nav-inner {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-logo {
font-size: 1.25rem;
font-weight: 700;
color: #0f172a;
}
.nav-actions {
display: flex;
align-items: center;
gap: 12px;
}
.nav-search-btn {
background: none;
border: none;
color: #64748b;
padding: 8px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s, color 0.15s;
}
.nav-search-btn:hover {
background: #f1f5f9;
color: #0f172a;
}
.btn-subscribe {
display: inline-flex;
align-items: center;
padding: 8px 20px;
background: #6366f1;
color: #fff;
font-size: 0.875rem;
font-weight: 600;
border-radius: 8px;
transition: background 0.15s;
}
.btn-subscribe:hover {
background: #4f46e5;
}
/* ── Main ── */
.main {
max-width: 1200px;
margin: 0 auto;
padding: 32px 24px 64px;
}
/* ── Featured Post ── */
.featured {
margin-bottom: 40px;
}
.featured-card {
position: relative;
display: block;
height: 480px;
border-radius: 16px;
overflow: hidden;
color: #fff;
}
.featured-image {
position: absolute;
inset: 0;
}
.featured-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.85) 0%,
rgba(0, 0, 0, 0.3) 50%,
rgba(0, 0, 0, 0.05) 100%
);
}
.featured-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 40px;
}
.featured-title {
font-size: 2rem;
font-weight: 800;
line-height: 1.2;
margin: 12px 0 16px;
max-width: 640px;
}
.featured-excerpt {
font-size: 1.05rem;
line-height: 1.6;
opacity: 0.9;
max-width: 560px;
margin-bottom: 20px;
}
.featured-meta {
display: flex;
align-items: center;
gap: 16px;
font-size: 0.875rem;
opacity: 0.85;
}
.featured-meta .read-more {
margin-left: auto;
font-weight: 600;
opacity: 1;
transition: opacity 0.15s;
}
.featured-card:hover .read-more {
opacity: 0.7;
}
/* ── Author ── */
.author {
display: flex;
align-items: center;
gap: 8px;
}
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
flex-shrink: 0;
}
.author-name {
font-weight: 500;
}
/* ── Category Tabs ── */
.filters {
margin-bottom: 24px;
}
.category-tabs {
display: flex;
gap: 8px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding-bottom: 4px;
}
.category-tabs::-webkit-scrollbar {
display: none;
}
.tab {
flex-shrink: 0;
padding: 8px 20px;
font-size: 0.875rem;
font-weight: 500;
border-radius: 9999px;
border: 1.5px solid #cbd5e1;
background: transparent;
color: #475569;
transition: all 0.15s;
}
.tab:hover {
border-color: #6366f1;
color: #6366f1;
}
.tab.active {
background: #6366f1;
border-color: #6366f1;
color: #fff;
}
/* ── Search ── */
.search-section {
display: flex;
justify-content: center;
margin-bottom: 32px;
}
.search-wrap {
position: relative;
width: 100%;
max-width: 480px;
}
.search-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
color: #94a3b8;
pointer-events: none;
}
.search-input {
width: 100%;
padding: 12px 16px 12px 44px;
font-size: 0.9375rem;
font-family: inherit;
border: 1.5px solid #e2e8f0;
border-radius: 12px;
background: #fff;
color: #1e293b;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
.search-input::placeholder {
color: #94a3b8;
}
.search-input:focus {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
}
/* ── Post Grid ── */
.post-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
margin-bottom: 48px;
}
/* ── Card ── */
.card {
background: #fff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
transition: box-shadow 0.2s, transform 0.2s;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card.hidden {
display: none;
}
.card-link {
display: flex;
flex-direction: column;
height: 100%;
}
.card-image {
height: 200px;
flex-shrink: 0;
}
.card-body {
padding: 20px;
display: flex;
flex-direction: column;
flex: 1;
}
.card-title {
font-size: 1.05rem;
font-weight: 700;
line-height: 1.35;
margin: 10px 0 8px;
color: #0f172a;
}
.card-excerpt {
font-size: 0.875rem;
color: #64748b;
line-height: 1.55;
flex: 1;
}
.card-footer {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f1f5f9;
font-size: 0.8125rem;
color: #94a3b8;
}
.card-footer .avatar {
width: 24px;
height: 24px;
}
.card-footer .author-name {
color: #475569;
font-size: 0.8125rem;
}
.read-time {
margin-left: auto;
}
/* ── Badges ── */
.badge {
display: inline-block;
padding: 4px 12px;
font-size: 0.75rem;
font-weight: 600;
border-radius: 9999px;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.badge-engineering {
background: #eef2ff;
color: #4f46e5;
}
.badge-design {
background: #fdf2f8;
color: #db2777;
}
.badge-product {
background: #fffbeb;
color: #d97706;
}
.badge-company {
background: #ecfdf5;
color: #059669;
}
/* ── Empty State ── */
.empty-state {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 64px 24px;
text-align: center;
}
.empty-state.visible {
display: flex;
}
.empty-title {
font-size: 1.125rem;
font-weight: 600;
color: #475569;
margin-top: 16px;
}
.empty-desc {
font-size: 0.875rem;
color: #94a3b8;
margin-top: 4px;
}
/* ── Pagination ── */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.page-btn {
min-width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
font-weight: 500;
border: 1.5px solid #e2e8f0;
border-radius: 10px;
background: #fff;
color: #475569;
transition: all 0.15s;
}
.page-btn:hover:not(:disabled) {
border-color: #6366f1;
color: #6366f1;
}
.page-btn.active {
background: #6366f1;
border-color: #6366f1;
color: #fff;
}
.page-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.page-btn.prev,
.page-btn.next {
font-size: 1rem;
padding: 0 14px;
}
.page-ellipsis {
min-width: 40px;
text-align: center;
color: #94a3b8;
font-size: 0.875rem;
user-select: none;
}
/* ── Responsive ── */
@media (max-width: 900px) {
.post-grid {
grid-template-columns: repeat(2, 1fr);
}
.featured-card {
height: 400px;
}
.featured-title {
font-size: 1.5rem;
}
.featured-content {
padding: 28px;
}
}
@media (max-width: 600px) {
.post-grid {
grid-template-columns: 1fr;
}
.featured-card {
height: 360px;
}
.featured-title {
font-size: 1.25rem;
}
.featured-excerpt {
font-size: 0.9375rem;
}
.featured-content {
padding: 20px;
}
.nav-inner {
padding: 0 16px;
}
.main {
padding: 20px 16px 48px;
}
}(function () {
"use strict";
var cards = Array.from(document.querySelectorAll("#postGrid .card"));
var tabs = document.querySelectorAll("#categoryTabs .tab");
var searchInput = document.getElementById("searchInput");
var emptyState = document.getElementById("emptyState");
var pagination = document.getElementById("pagination");
var prevBtn = document.getElementById("prevBtn");
var nextBtn = document.getElementById("nextBtn");
var navSearchBtn = document.getElementById("navSearchBtn");
var pageBtns = document.querySelectorAll(".page-btn[data-page]");
var POSTS_PER_PAGE = 9;
var activeCategory = "all";
var currentPage = 1;
var debounceTimer = null;
/* ── Helpers ── */
function getFilteredCards() {
var query = (searchInput.value || "").trim().toLowerCase();
return cards.filter(function (card) {
var category = card.getAttribute("data-category");
var title = card.querySelector(".card-title").textContent.toLowerCase();
var excerpt = card.querySelector(".card-excerpt").textContent.toLowerCase();
var matchesCategory = activeCategory === "all" || category === activeCategory;
var matchesSearch = !query || title.indexOf(query) !== -1 || excerpt.indexOf(query) !== -1;
return matchesCategory && matchesSearch;
});
}
function render() {
var filtered = getFilteredCards();
var totalPages = Math.max(1, Math.ceil(filtered.length / POSTS_PER_PAGE));
if (currentPage > totalPages) {
currentPage = totalPages;
}
var start = (currentPage - 1) * POSTS_PER_PAGE;
var end = start + POSTS_PER_PAGE;
/* Show / hide cards */
cards.forEach(function (card) {
card.classList.add("hidden");
});
filtered.slice(start, end).forEach(function (card) {
card.classList.remove("hidden");
});
/* Empty state */
if (filtered.length === 0) {
emptyState.classList.add("visible");
pagination.style.display = "none";
} else {
emptyState.classList.remove("visible");
pagination.style.display = "flex";
}
/* Pagination buttons */
updatePagination(totalPages);
}
function updatePagination(totalPages) {
prevBtn.disabled = currentPage <= 1;
nextBtn.disabled = currentPage >= totalPages;
pageBtns.forEach(function (btn) {
var page = parseInt(btn.getAttribute("data-page"), 10);
btn.classList.toggle("active", page === currentPage);
});
}
/* ── Category Tabs ── */
tabs.forEach(function (tab) {
tab.addEventListener("click", function () {
tabs.forEach(function (t) {
t.classList.remove("active");
});
tab.classList.add("active");
activeCategory = tab.getAttribute("data-category");
currentPage = 1;
render();
});
});
/* ── Search ── */
searchInput.addEventListener("input", function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
currentPage = 1;
render();
}, 200);
});
/* ── Nav search button ── */
navSearchBtn.addEventListener("click", function () {
searchInput.focus();
searchInput.scrollIntoView({ behavior: "smooth", block: "center" });
});
/* ── Pagination ── */
pageBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
currentPage = parseInt(btn.getAttribute("data-page"), 10);
render();
window.scrollTo({
top: document.querySelector(".post-grid").offsetTop - 80,
behavior: "smooth",
});
});
});
prevBtn.addEventListener("click", function () {
if (currentPage > 1) {
currentPage--;
render();
window.scrollTo({
top: document.querySelector(".post-grid").offsetTop - 80,
behavior: "smooth",
});
}
});
nextBtn.addEventListener("click", function () {
var filtered = getFilteredCards();
var totalPages = Math.ceil(filtered.length / POSTS_PER_PAGE);
if (currentPage < totalPages) {
currentPage++;
render();
window.scrollTo({
top: document.querySelector(".post-grid").offsetTop - 80,
behavior: "smooth",
});
}
});
/* ── Init ── */
render();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Blog — StealThis</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Nav -->
<nav class="nav">
<div class="nav-inner">
<a href="#" class="nav-logo">Blog</a>
<div class="nav-actions">
<button class="nav-search-btn" aria-label="Search" id="navSearchBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</button>
<a href="#" class="btn-subscribe">Subscribe</a>
</div>
</div>
</nav>
<main class="main">
<!-- Featured Post -->
<section class="featured">
<a href="#" class="featured-card">
<div class="featured-image" style="background:linear-gradient(135deg,#6366f1 0%,#8b5cf6 50%,#a78bfa 100%)"></div>
<div class="featured-overlay"></div>
<div class="featured-content">
<span class="badge badge-engineering">Engineering</span>
<h1 class="featured-title">Building Scalable Design Systems for Modern Product Teams</h1>
<p class="featured-excerpt">How we architected a component library that serves 40 product teams across three platforms. Lessons learned from two years of iteration and cross-functional collaboration.</p>
<div class="featured-meta">
<div class="author">
<div class="avatar" style="background:#c084fc"></div>
<span class="author-name">Sarah Chen</span>
</div>
<time>Mar 18, 2026</time>
<span class="read-more">Read more →</span>
</div>
</div>
</a>
</section>
<!-- Category Tabs -->
<section class="filters">
<div class="category-tabs" id="categoryTabs">
<button class="tab active" data-category="all">All</button>
<button class="tab" data-category="engineering">Engineering</button>
<button class="tab" data-category="design">Design</button>
<button class="tab" data-category="product">Product</button>
<button class="tab" data-category="company">Company</button>
</div>
</section>
<!-- Search -->
<section class="search-section">
<div class="search-wrap">
<svg class="search-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" class="search-input" id="searchInput" placeholder="Search articles..." />
</div>
</section>
<!-- Post Grid -->
<section class="post-grid" id="postGrid">
<!-- Card 1 -->
<article class="card" data-category="engineering">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#3b82f6,#1d4ed8)"></div>
<div class="card-body">
<span class="badge badge-engineering">Engineering</span>
<h2 class="card-title">Migrating to Edge Functions Without Breaking Production</h2>
<p class="card-excerpt">Our step-by-step approach to moving critical API routes to the edge while maintaining zero downtime.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#60a5fa"></div>
<span class="author-name">James Park</span>
</div>
<time>Mar 15, 2026</time>
<span class="read-time">7 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 2 -->
<article class="card" data-category="design">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#f472b6,#db2777)"></div>
<div class="card-body">
<span class="badge badge-design">Design</span>
<h2 class="card-title">The Case for Motion Design in Enterprise Software</h2>
<p class="card-excerpt">Why subtle animations can dramatically improve usability and reduce cognitive load in complex workflows.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#f9a8d4"></div>
<span class="author-name">Mia Torres</span>
</div>
<time>Mar 12, 2026</time>
<span class="read-time">5 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 3 -->
<article class="card" data-category="product">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#fbbf24,#f59e0b)"></div>
<div class="card-body">
<span class="badge badge-product">Product</span>
<h2 class="card-title">How We Prioritize Features Using Customer Impact Scoring</h2>
<p class="card-excerpt">A data-driven framework that replaced gut-feeling roadmap decisions with measurable outcomes.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#fcd34d"></div>
<span class="author-name">David Kim</span>
</div>
<time>Mar 10, 2026</time>
<span class="read-time">6 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 4 -->
<article class="card" data-category="company">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#34d399,#059669)"></div>
<div class="card-body">
<span class="badge badge-company">Company</span>
<h2 class="card-title">Our Remote Work Playbook After Three Years Fully Distributed</h2>
<p class="card-excerpt">The rituals, tools, and cultural norms that keep a 200-person team aligned across 14 time zones.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#6ee7b7"></div>
<span class="author-name">Lisa Nguyen</span>
</div>
<time>Mar 8, 2026</time>
<span class="read-time">8 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 5 -->
<article class="card" data-category="engineering">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#818cf8,#4f46e5)"></div>
<div class="card-body">
<span class="badge badge-engineering">Engineering</span>
<h2 class="card-title">Reducing Bundle Size by 60% with Tree Shaking and Code Splitting</h2>
<p class="card-excerpt">Practical techniques we used to cut initial load time from 4.2 seconds to 1.6 seconds.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#a5b4fc"></div>
<span class="author-name">Alex Rivera</span>
</div>
<time>Mar 5, 2026</time>
<span class="read-time">9 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 6 -->
<article class="card" data-category="design">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#fb923c,#ea580c)"></div>
<div class="card-body">
<span class="badge badge-design">Design</span>
<h2 class="card-title">Accessibility Audits That Actually Change How You Build</h2>
<p class="card-excerpt">Moving beyond checklist compliance to embed inclusive thinking into every design decision.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#fdba74"></div>
<span class="author-name">Priya Sharma</span>
</div>
<time>Mar 3, 2026</time>
<span class="read-time">6 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 7 -->
<article class="card" data-category="product">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#e879f9,#c026d3)"></div>
<div class="card-body">
<span class="badge badge-product">Product</span>
<h2 class="card-title">When to Say No: A Product Manager's Guide to Scope Creep</h2>
<p class="card-excerpt">Frameworks for making tough trade-off decisions without demoralizing your engineering team.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#f0abfc"></div>
<span class="author-name">Marcus Cole</span>
</div>
<time>Feb 28, 2026</time>
<span class="read-time">4 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 8 -->
<article class="card" data-category="engineering">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#22d3ee,#0891b2)"></div>
<div class="card-body">
<span class="badge badge-engineering">Engineering</span>
<h2 class="card-title">Real-Time Collaboration with CRDTs: Lessons from the Trenches</h2>
<p class="card-excerpt">What we learned implementing conflict-free replicated data types for our collaborative editor.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#67e8f9"></div>
<span class="author-name">Nina Petrova</span>
</div>
<time>Feb 25, 2026</time>
<span class="read-time">10 min read</span>
</div>
</div>
</a>
</article>
<!-- Card 9 -->
<article class="card" data-category="company">
<a href="#" class="card-link">
<div class="card-image" style="background:linear-gradient(135deg,#a3e635,#65a30d)"></div>
<div class="card-body">
<span class="badge badge-company">Company</span>
<h2 class="card-title">Why We Open-Sourced Our Internal Component Library</h2>
<p class="card-excerpt">The business case for giving away code and how it accelerated our hiring pipeline.</p>
<div class="card-footer">
<div class="author">
<div class="avatar" style="background:#bef264"></div>
<span class="author-name">Tom Brennan</span>
</div>
<time>Feb 22, 2026</time>
<span class="read-time">5 min read</span>
</div>
</div>
</a>
</article>
</section>
<!-- Empty State -->
<div class="empty-state" id="emptyState">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
<p class="empty-title">No posts found</p>
<p class="empty-desc">Try adjusting your search or filter to find what you're looking for.</p>
</div>
<!-- Pagination -->
<nav class="pagination" id="pagination">
<button class="page-btn prev" id="prevBtn" disabled>←</button>
<button class="page-btn active" data-page="1">1</button>
<button class="page-btn" data-page="2">2</button>
<button class="page-btn" data-page="3">3</button>
<span class="page-ellipsis">…</span>
<button class="page-btn" data-page="12">12</button>
<button class="page-btn next" id="nextBtn">→</button>
</nav>
</main>
<script src="script.js"></script>
</body>
</html>Blog Listing Page
A complete blog index page with a featured post hero section, category filter tabs, search functionality, card grid layout, and pagination controls.
Features
- Featured post hero — large card with image, category badge, title, excerpt, author, date
- Category tabs — All, Engineering, Design, Product, Company filter buttons
- Search bar — real-time search filtering by title and excerpt
- Post grid — 3-column card grid with thumbnail, category badge, title, excerpt, read time, author
- Pagination — numbered page buttons with prev/next
- Empty state — “No posts found” message when search yields no results
- Responsive — 3 cols → 2 cols → 1 col
When to use it
- Blog index / archive page
- Content hub landing page
- News listing page