<main class="feed">
<header>
<h1>Activity</h1>
<p>Scroll down. Each batch of ten loads as the end of the list comes into view.</p>
</header>
<div class="list" id="list"></div>
<div class="sentinel" id="sentinel" aria-live="polite">
<div class="spinner" id="spinner" role="status" aria-label="Loading more" hidden></div>
<p class="end" id="end" hidden>You are all caught up.</p>
</div>
</main>
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
background: #f1f5f9;
color: #0f172a;
}
.feed {
max-width: 520px;
margin: 0 auto;
padding: 32px 20px 60px;
}
header h1 {
font-size: 1.5rem;
margin: 0 0 6px;
}
header p {
margin: 0 0 24px;
color: #64748b;
line-height: 1.5;
}
.list {
display: flex;
flex-direction: column;
gap: 12px;
}
.card {
display: flex;
gap: 14px;
align-items: center;
background: #ffffff;
padding: 16px;
border-radius: 14px;
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 8px 20px -14px rgba(15, 23, 42, 0.25);
animation: rise 0.35s ease both;
}
@keyframes rise {
from { opacity: 0; transform: translateY(10px); }
}
.avatar {
flex: 0 0 auto;
width: 40px;
height: 40px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 0.8125rem;
font-weight: 700;
}
.card strong {
display: block;
font-size: 0.9375rem;
}
.card div span {
font-size: 0.8125rem;
color: #64748b;
}
.sentinel {
display: flex;
justify-content: center;
padding: 24px 0;
min-height: 72px;
}
.spinner {
width: 28px;
height: 28px;
border: 3px solid #e2e8f0;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.end {
margin: 0;
font-size: 0.875rem;
color: #94a3b8;
}
const list = document.getElementById("list");
const sentinel = document.getElementById("sentinel");
const spinner = document.getElementById("spinner");
const end = document.getElementById("end");
const names = ["Ava Chen", "Noah Patel", "Mia Okafor", "Liam Rossi", "Zoe Martin", "Ethan Kim", "Isla Novak", "Leo Haddad", "Ruby Sato", "Omar Diaz"];
const actions = ["pushed 3 commits", "opened a pull request", "left a review", "closed an issue", "deployed to production", "updated the docs", "merged a branch", "added a teammate", "changed a setting", "restored a backup"];
const colors = ["#2563eb", "#7c3aed", "#059669", "#ea580c", "#db2777", "#0891b2"];
const pageSize = 10;
const lastPage = 5;
let page = 0;
let loading = false;
function loadPage() {
loading = true;
spinner.hidden = false;
setTimeout(function() {
page++;
for (let i = 0; i < pageSize; i++) {
const n = (page - 1) * pageSize + i;
const card = document.createElement("article");
card.className = "card";
card.style.animationDelay = (i * 40) + "ms";
const initials = names[n % names.length].split(" ").map(function(part) {
return part[0];
}).join("");
card.innerHTML = '<span class="avatar" style="background:' + colors[n % colors.length] + '">' + initials + '</span><div><strong>' + names[n % names.length] + '</strong><span>' + actions[(n * 3) % actions.length] + ' in project-' + (n + 1) + '</span></div>';
list.appendChild(card);
}
spinner.hidden = true;
loading = false;
if (page >= lastPage) {
observer.disconnect();
end.hidden = false;
} else {
observer.unobserve(sentinel);
observer.observe(sentinel);
}
}, 700);
}
const observer = new IntersectionObserver(function(entries) {
if (entries[0].isIntersecting && !loading) {
loadPage();
}
}, { rootMargin: "0px 0px 200px 0px" });
observer.observe(sentinel);