Science — Popular-Science Magazine Landing
A lively, trustworthy popular-science magazine homepage with a bold cover feature, a colorful topic chip row, a filterable grid of story cards with category color tags and read times, a horizontal Explainers scroller, a gradient newsletter band, and a ranked trending sidebar. Vanilla JS powers topic filtering, instant search, count-up stats, an animated starfield hero, and email validation with toast feedback. Fully responsive down to 360px and keyboard-accessible.
MCP
Code
:root {
--bg: #ffffff;
--bg-alt: #eef3ff;
--ink: #0f1b2d;
--ink-2: #33445c;
--muted: #697892;
--accent: #1f4ed8;
--accent-d: #1740b0;
--pop: #ff5a36;
--line: rgba(15, 27, 45, 0.12);
--line-2: rgba(15, 27, 45, 0.2);
--ok: #2f9e6f;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--shadow: 0 1px 2px rgba(15, 27, 45, 0.06), 0 8px 24px rgba(15, 27, 45, 0.07);
--shadow-lg: 0 12px 40px rgba(31, 78, 216, 0.16);
--serif: "Source Serif 4", Georgia, serif;
--sans: "Inter", system-ui, sans-serif;
--mono: "JetBrains Mono", ui-monospace, monospace;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.wrap { max-width: 1160px; margin: 0 auto; padding: 0 20px; }
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
position: absolute; left: -999px; top: 8px; z-index: 100;
background: var(--accent); color: #fff; padding: 10px 16px; border-radius: var(--r-sm);
}
.skip:focus { left: 12px; }
a { color: inherit; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
/* Buttons */
.btn {
font-family: var(--sans); font-weight: 600; font-size: 15px;
border: 0; border-radius: 999px; padding: 11px 22px; cursor: pointer;
transition: transform .12s ease, box-shadow .18s ease, background .18s ease;
}
.btn:active { transform: translateY(1px) scale(.99); }
.btn-pop { background: var(--pop); color: #fff; box-shadow: 0 6px 18px rgba(255, 90, 54, 0.32); }
.btn-pop:hover { background: #ff4520; box-shadow: 0 10px 26px rgba(255, 90, 54, 0.42); }
.btn-light { background: #fff; color: var(--accent-d); }
.btn-light:hover { background: var(--bg-alt); }
.icon-btn {
display: grid; place-items: center; width: 40px; height: 40px;
border: 1px solid var(--line); background: #fff; border-radius: 50%;
color: var(--ink-2); cursor: pointer; transition: background .15s, color .15s;
}
.icon-btn:hover { background: var(--bg-alt); color: var(--accent); }
/* Topbar */
.topbar {
position: sticky; top: 0; z-index: 40;
background: rgba(255, 255, 255, 0.92); backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-inner { display: flex; align-items: center; gap: 24px; height: 66px; }
.brand { display: flex; align-items: baseline; gap: 8px; text-decoration: none; }
.brand-dot {
width: 14px; height: 14px; border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #fff, var(--pop) 60%, var(--accent));
align-self: center; box-shadow: 0 0 0 3px rgba(31, 78, 216, 0.12);
}
.brand-name { font-family: var(--serif); font-weight: 700; font-size: 24px; letter-spacing: -.02em; }
.brand-tag { font-size: 12px; color: var(--muted); font-weight: 500; }
.topnav { display: flex; gap: 22px; margin-left: auto; }
.topnav a { text-decoration: none; font-weight: 500; font-size: 15px; color: var(--ink-2); position: relative; }
.topnav a::after {
content: ""; position: absolute; left: 0; right: 0; bottom: -22px; height: 3px;
background: var(--pop); transform: scaleX(0); transition: transform .18s; border-radius: 3px;
}
.topnav a:hover { color: var(--ink); }
.topnav a:hover::after { transform: scaleX(1); }
.topbar-actions { display: flex; align-items: center; gap: 10px; }
.searchbar { border-top: 1px solid var(--line); padding: 14px 0; background: #fff; }
.searchbar input {
width: 100%; font-family: var(--sans); font-size: 17px; padding: 12px 16px;
border: 1px solid var(--line-2); border-radius: var(--r-md); background: var(--bg-alt);
}
.searchbar input:focus { outline: none; border-color: var(--accent); background: #fff; }
/* Chips */
.chips { display: flex; gap: 10px; flex-wrap: wrap; margin: 26px 0 8px; }
.chip {
--c: var(--accent);
font-family: var(--sans); font-weight: 600; font-size: 14px;
padding: 8px 16px; border-radius: 999px; cursor: pointer;
border: 1.5px solid var(--line-2); background: #fff; color: var(--ink-2);
transition: all .15s ease;
}
.chip:hover { border-color: var(--c); color: var(--c); transform: translateY(-1px); }
.chip.is-active { background: var(--c); border-color: var(--c); color: #fff; }
.chip[data-topic="all"] { --c: var(--accent); }
/* Hero */
.hero {
display: grid; grid-template-columns: 1fr 320px; gap: 28px;
margin: 18px 0 56px;
}
.hero-feature {
display: grid; grid-template-columns: 1fr 1fr; overflow: hidden;
border-radius: var(--r-lg); background: var(--bg-alt);
border: 1px solid var(--line); box-shadow: var(--shadow);
}
.hero-media { min-height: 380px; position: relative; }
.hero-media svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero-arms { animation: spin 60s linear infinite; transform-origin: 50% 45%; }
@keyframes spin { to { transform: rotate(360deg); } }
.hero-body { padding: 36px 34px; display: flex; flex-direction: column; gap: 14px; }
.kicker {
--c: var(--accent);
font-family: var(--sans); font-weight: 700; font-size: 12.5px; letter-spacing: .08em;
text-transform: uppercase; color: var(--c);
}
.hero-body h1 {
font-family: var(--serif); font-weight: 700; font-size: clamp(26px, 3.4vw, 40px);
line-height: 1.12; letter-spacing: -.02em; margin: 0;
}
.dek { font-family: var(--serif); font-size: 18px; color: var(--ink-2); margin: 0; }
.byline { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 13.5px; color: var(--muted); margin-top: auto; }
.byline strong { color: var(--ink); }
.avatar {
width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
background: var(--accent); color: #fff; font-weight: 700; font-size: 11px;
}
.dot { color: var(--line-2); }
.readtime { font-family: var(--mono); font-size: 12.5px; }
.clock { color: var(--pop); }
.hero-body .btn { align-self: flex-start; margin-top: 4px; }
/* Trending rail */
.trending {
border: 1px solid var(--line); border-radius: var(--r-lg); padding: 22px 20px;
background: #fff; box-shadow: var(--shadow); align-self: start;
}
.rail-title {
font-family: var(--sans); font-size: 13px; font-weight: 700; text-transform: uppercase;
letter-spacing: .07em; color: var(--accent); margin: 0 0 14px;
}
.trend-list { list-style: none; margin: 0; padding: 0; counter-reset: t; }
.trend-list li {
display: grid; grid-template-columns: 30px 1fr; gap: 4px 12px;
padding: 13px 0; border-top: 1px solid var(--line); align-items: start;
}
.trend-list li:first-child { border-top: 0; padding-top: 0; }
.rank { grid-row: span 2; font-family: var(--mono); font-weight: 500; font-size: 18px; color: var(--pop); }
.trend-list a { font-family: var(--serif); font-weight: 600; font-size: 15px; text-decoration: none; line-height: 1.3; }
.trend-list a:hover { color: var(--accent); }
.t-meta { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
.rail-stat { margin-top: 18px; padding-top: 16px; border-top: 2px solid var(--bg-alt); }
.rail-stat-num { display: block; font-family: var(--mono); font-size: 28px; font-weight: 500; color: var(--accent-d); }
.rail-stat-lbl { font-size: 12.5px; color: var(--muted); }
/* Section heads */
.section-head { display: flex; align-items: baseline; gap: 14px; margin: 0 0 20px; }
.section-head h2 { font-family: var(--serif); font-size: 28px; letter-spacing: -.02em; margin: 0; }
.count, .section-sub { font-size: 14px; color: var(--muted); }
.section-sub { font-style: italic; font-family: var(--serif); }
/* Story grid */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; margin-bottom: 56px; }
.card {
display: flex; flex-direction: column; overflow: hidden; cursor: pointer;
background: #fff; border: 1px solid var(--line); border-radius: var(--r-md);
box-shadow: var(--shadow); transition: transform .18s ease, box-shadow .18s ease;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.card-media { height: 158px; position: relative; }
.card-media svg { width: 100%; height: 100%; display: block; }
.card-body { padding: 16px 18px 20px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.tag {
--c: var(--accent);
align-self: flex-start; font-family: var(--sans); font-weight: 700; font-size: 11px;
letter-spacing: .05em; text-transform: uppercase; color: var(--c);
background: color-mix(in srgb, var(--c) 12%, #fff); padding: 4px 10px; border-radius: 999px;
}
.card h3 { font-family: var(--serif); font-weight: 700; font-size: 19px; line-height: 1.22; margin: 0; letter-spacing: -.01em; }
.card p { font-size: 14px; color: var(--ink-2); margin: 0; }
.card-foot { margin-top: auto; display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); padding-top: 6px; }
.card-foot .readtime { font-family: var(--mono); }
/* Explainers scroller */
.scroller {
display: flex; gap: 18px; overflow-x: auto; padding: 4px 4px 18px; margin-bottom: 56px;
scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
}
.scroller::-webkit-scrollbar { height: 8px; }
.scroller::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 8px; }
.exp-card {
--c: var(--accent);
scroll-snap-align: start; flex: 0 0 260px;
background: #fff; border: 1px solid var(--line); border-top: 4px solid var(--c);
border-radius: var(--r-md); padding: 20px 20px 18px; box-shadow: var(--shadow);
display: flex; flex-direction: column; gap: 8px; cursor: pointer;
transition: transform .16s ease, box-shadow .16s ease;
}
.exp-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }
.exp-num { font-family: var(--mono); font-size: 13px; color: var(--c); font-weight: 500; }
.exp-card h3 { font-family: var(--serif); font-size: 18px; line-height: 1.25; margin: 0; }
.exp-card p { font-size: 13.5px; color: var(--ink-2); margin: 0; flex: 1; }
.exp-tag { font-family: var(--sans); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--c); }
/* Subscribe band */
.subscribe {
border-radius: var(--r-lg); padding: 44px 40px; margin-bottom: 56px; color: #fff;
background: linear-gradient(120deg, var(--accent-d), var(--accent) 60%, #3a6bf0);
position: relative; overflow: hidden;
}
.subscribe::before {
content: ""; position: absolute; right: -60px; top: -60px; width: 260px; height: 260px;
background: radial-gradient(circle, rgba(255, 90, 54, 0.5), transparent 70%); border-radius: 50%;
}
.sub-inner { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: 28px; align-items: center; }
.sub-copy h2 { font-family: var(--serif); font-size: 30px; margin: 0 0 8px; letter-spacing: -.02em; }
.sub-copy p { margin: 0; color: rgba(255, 255, 255, 0.88); }
.sub-copy strong { color: #fff; font-family: var(--mono); font-weight: 500; }
.sub-form { display: flex; flex-wrap: wrap; gap: 10px; }
.sub-form input {
flex: 1; min-width: 200px; font-family: var(--sans); font-size: 16px;
padding: 13px 16px; border: 0; border-radius: var(--r-md); background: #fff; color: var(--ink);
}
.sub-form input:focus { outline: 3px solid var(--pop); outline-offset: 2px; }
.sub-msg { flex-basis: 100%; margin: 0; font-size: 14px; font-weight: 500; min-height: 1.2em; }
.sub-msg.ok { color: #d6ffe9; }
.sub-msg.err { color: #ffd9cf; }
/* Footer */
.footer { border-top: 1px solid var(--line); padding: 26px 0; background: var(--bg-alt); }
.footer-inner { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; font-size: 13.5px; color: var(--muted); }
.footer-links a { margin-left: 18px; text-decoration: none; }
.footer-links a:hover { color: var(--accent); }
/* Toast */
.toast {
position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 24px);
background: var(--ink); color: #fff; padding: 13px 22px; border-radius: 999px;
font-size: 14px; font-weight: 500; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.28);
opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 90;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }
.card.hide { display: none; }
/* Responsive */
@media (max-width: 960px) {
.hero { grid-template-columns: 1fr; }
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
.hero-feature { grid-template-columns: 1fr; }
.hero-media { min-height: 220px; }
.sub-inner { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.topnav { display: none; }
.brand-tag { display: none; }
.grid { grid-template-columns: 1fr; }
.section-head h2 { font-size: 23px; }
.subscribe { padding: 30px 22px; }
.exp-card { flex-basis: 220px; }
}
@media (prefers-reduced-motion: reduce) {
.hero-arms { animation: none; }
html { scroll-behavior: auto; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("show");
}, 2400);
}
/* ---------- topic palette ---------- */
var COLORS = {
Space: "#6b4bff",
Biology: "#0f9d6b",
Climate: "#1f9be0",
Tech: "#ff7a18",
Mind: "#e0457b"
};
/* ---------- story data (fictional) ---------- */
var STORIES = [
{ topic: "Climate", title: "The ocean swallowed a decade of our heat. We just found it.", dek: "Argo float #5907021 traced 0.91 W/m² sinking past 2,000 m.", author: "L. Hartono", read: 8 },
{ topic: "Biology", title: "This fungus farms ants, and the ants seem fine with it", dek: "A 47-million-year mutualism rewrites who domesticated whom.", author: "P. Okafor", read: 5 },
{ topic: "Tech", title: "A battery made of rust could buffer an entire city", dek: "Iron-air cells hit 41 $/kWh in the Verdant Grid pilot.", author: "S. Marchetti", read: 7 },
{ topic: "Mind", title: "Your brain quietly edits time every time you blink", dek: "fMRI shows a 27 ms backfill in 1,204 fictional volunteers.", author: "R. Delacroix", read: 6 },
{ topic: "Space", title: "A perfect spiral existed 700 million years too early", dek: "Halcyon Deep Field clocks it at z = 10.2, redshift-confirmed.", author: "M. Okonkwo-Reyes", read: 9 },
{ topic: "Biology", title: "The molecule that lets tardigrades shrug off the vacuum", dek: "CAHS-D glasses their cells at a measured 0.3% hydration.", author: "T. Nguyen-Bauer", read: 4 },
{ topic: "Climate", title: "Mangroves bank more carbon than we dared to count", dek: "Coastal cores log 1.8 GtC unaccounted across the tropics.", author: "A. Mwangi", read: 6 },
{ topic: "Tech", title: "A 12-qubit chip ran the algorithm nobody could fake", dek: "Sycorax-12 sampled a 2.3 × 10¹⁶ state space in 4.2 s.", author: "J. Park-Holloway", read: 7 },
{ topic: "Space", title: "Enceladus is venting the building blocks of life", dek: "Cassini-legacy reanalysis finds phosphates at 0.5 mmol/L.", author: "F. Castellanos", read: 6 }
];
/* ---------- decorative card artwork ---------- */
function art(color, seed) {
var c2 = color;
var ns = "http://www.w3.org/2000/svg";
var s = '<svg viewBox="0 0 400 158" preserveAspectRatio="none" xmlns="' + ns + '">';
s += '<defs><linearGradient id="lg' + seed + '" x1="0" y1="0" x2="1" y2="1">';
s += '<stop offset="0%" stop-color="' + c2 + '"/><stop offset="100%" stop-color="#0a0e2a"/></linearGradient></defs>';
s += '<rect width="400" height="158" fill="url(#lg' + seed + ')"/>';
// pseudo-random orbs/lines from seed
var r = seed * 9301 + 49297;
for (var i = 0; i < 4; i++) {
r = (r * 9301 + 49297) % 233280;
var x = (r / 233280) * 400;
r = (r * 9301 + 49297) % 233280;
var y = (r / 233280) * 158;
r = (r * 9301 + 49297) % 233280;
var rad = 10 + (r / 233280) * 36;
s += '<circle cx="' + x.toFixed(0) + '" cy="' + y.toFixed(0) + '" r="' + rad.toFixed(0) + '" fill="rgba(255,255,255,' + (0.06 + i * 0.04).toFixed(2) + ')"/>';
}
s += '</svg>';
return s;
}
/* ---------- render grid ---------- */
var grid = document.getElementById("story-grid");
var gridCount = document.getElementById("grid-count");
function render(topic) {
grid.innerHTML = "";
var shown = 0;
STORIES.forEach(function (st, i) {
if (topic !== "all" && st.topic !== topic) return;
shown++;
var color = COLORS[st.topic];
var card = document.createElement("article");
card.className = "card";
card.setAttribute("tabindex", "0");
card.setAttribute("role", "link");
card.setAttribute("aria-label", st.title + ", " + st.topic + ", " + st.read + " minute read");
card.innerHTML =
'<div class="card-media">' + art(color, i + 1) + "</div>" +
'<div class="card-body">' +
'<span class="tag" style="--c:' + color + '">' + st.topic + "</span>" +
"<h3>" + st.title + "</h3>" +
"<p>" + st.dek + "</p>" +
'<div class="card-foot"><span>By ' + st.author + "</span><span aria-hidden=\"true\">•</span>" +
'<span class="readtime">▷ ' + st.read + " min</span></div>" +
"</div>";
function open() { toast("Opening: " + st.title); }
card.addEventListener("click", open);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); }
});
grid.appendChild(card);
});
if (gridCount) gridCount.textContent = shown + (shown === 1 ? " story" : " stories");
}
render("all");
/* ---------- topic chips ---------- */
var chips = document.querySelectorAll(".chip");
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) { c.classList.remove("is-active"); });
chip.classList.add("is-active");
render(chip.dataset.topic);
});
});
/* ---------- search toggle + filter ---------- */
var searchBtn = document.getElementById("search-btn");
var searchbar = document.getElementById("searchbar");
var searchInput = document.getElementById("search-input");
searchBtn.addEventListener("click", function () {
var open = searchbar.hidden;
searchbar.hidden = !open;
searchBtn.setAttribute("aria-expanded", String(open));
if (open) searchInput.focus();
});
searchInput.addEventListener("input", function () {
var q = searchInput.value.trim().toLowerCase();
var cards = grid.querySelectorAll(".card");
var n = 0;
cards.forEach(function (card) {
var txt = card.textContent.toLowerCase();
var match = !q || txt.indexOf(q) !== -1;
card.classList.toggle("hide", !match);
if (match) n++;
});
if (gridCount) gridCount.textContent = q ? n + ' match' + (n === 1 ? "" : "es") + ' for "' + q + '"' : n + " stories";
});
/* ---------- read links + cover / trending ---------- */
document.querySelectorAll("[data-read]").forEach(function (el) {
el.addEventListener("click", function (e) {
e.preventDefault();
toast("Opening: " + el.dataset.read);
});
});
/* ---------- explainer cards ---------- */
document.querySelectorAll(".exp-card").forEach(function (card) {
card.setAttribute("tabindex", "0");
var title = card.querySelector("h3").textContent;
function go() { toast("Explainer: " + title); }
card.addEventListener("click", go);
card.addEventListener("keydown", function (e) {
if (e.key === "Enter") go();
});
});
/* ---------- newsletter form ---------- */
var form = document.getElementById("sub-form");
var email = document.getElementById("sub-email");
var msg = document.getElementById("sub-msg");
form.addEventListener("submit", function (e) {
e.preventDefault();
var v = email.value.trim();
var valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
if (!valid) {
msg.textContent = "Please enter a valid email address.";
msg.className = "sub-msg err";
email.focus();
return;
}
msg.textContent = "You're in! Confirmation sent to " + v + ".";
msg.className = "sub-msg ok";
email.value = "";
toast("Subscribed — welcome aboard ✨");
});
document.getElementById("join-btn").addEventListener("click", function () {
document.getElementById("subscribe").scrollIntoView({ behavior: "smooth" });
setTimeout(function () { email.focus(); }, 500);
});
/* ---------- count-up animations ---------- */
function animateCount(el) {
var target = parseInt(el.dataset.count, 10) || 0;
var suffix = el.dataset.suffix || "";
var dur = 1100, start = null;
function step(ts) {
if (!start) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.floor(eased * target).toLocaleString("en-US") + suffix;
if (p < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
var counters = document.querySelectorAll("[data-count]");
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) { animateCount(en.target); io.unobserve(en.target); }
});
}, { threshold: 0.4 });
counters.forEach(function (c) { io.observe(c); });
} else {
counters.forEach(animateCount);
}
/* ---------- hero starfield ---------- */
var starGroup = document.querySelector(".hero-stars");
if (starGroup) {
var stars = "";
for (var i = 0; i < 50; i++) {
var x = Math.random() * 600, y = Math.random() * 420, r = Math.random() * 1.4 + 0.3;
stars += '<circle cx="' + x.toFixed(0) + '" cy="' + y.toFixed(0) + '" r="' + r.toFixed(1) + '" opacity="' + (0.4 + Math.random() * 0.6).toFixed(2) + '"/>';
}
starGroup.innerHTML = stars;
}
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Orbit — Popular Science, Curiously Told</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&family=Source+Serif+4:ital,opt_sz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="topbar">
<div class="wrap topbar-inner">
<a class="brand" href="#" aria-label="Orbit home">
<span class="brand-dot" aria-hidden="true"></span>
<span class="brand-name">Orbit</span>
<span class="brand-tag">science, curiously told</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#feature">Features</a>
<a href="#grid">Stories</a>
<a href="#explainers">Explainers</a>
<a href="#subscribe">Subscribe</a>
</nav>
<div class="topbar-actions">
<button class="icon-btn" id="search-btn" aria-label="Search articles">
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><circle cx="11" cy="11" r="7" fill="none" stroke="currentColor" stroke-width="2"/><line x1="16.5" y1="16.5" x2="21" y2="21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<button class="btn btn-pop" id="join-btn">Join free</button>
</div>
</div>
<div class="searchbar" id="searchbar" hidden>
<div class="wrap">
<input type="search" id="search-input" placeholder="Search 4,200+ stories — try "black holes" or "CRISPR"" aria-label="Search stories" />
</div>
</div>
</header>
<main id="main" class="wrap">
<!-- Topic chips -->
<nav class="chips" aria-label="Topics">
<button class="chip is-active" data-topic="all">All</button>
<button class="chip" data-topic="Space" style="--c:#6b4bff">Space</button>
<button class="chip" data-topic="Biology" style="--c:#0f9d6b">Biology</button>
<button class="chip" data-topic="Climate" style="--c:#1f9be0">Climate</button>
<button class="chip" data-topic="Tech" style="--c:#ff7a18">Tech</button>
<button class="chip" data-topic="Mind" style="--c:#e0457b">Mind</button>
</nav>
<!-- Hero feature -->
<section class="hero" id="feature" aria-labelledby="hero-title">
<article class="hero-feature">
<div class="hero-media" role="img" aria-label="Illustration of a spiral galaxy with a bright core">
<svg viewBox="0 0 600 420" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<defs>
<radialGradient id="g1" cx="50%" cy="46%" r="60%">
<stop offset="0%" stop-color="#fff6c9"/>
<stop offset="22%" stop-color="#ff9a3c"/>
<stop offset="55%" stop-color="#7b2ff7"/>
<stop offset="100%" stop-color="#0a0e2a"/>
</radialGradient>
</defs>
<rect width="600" height="420" fill="url(#g1)"/>
<g class="hero-arms" stroke="rgba(255,255,255,.55)" fill="none" stroke-width="2">
<path d="M300 190 C 360 150 470 180 520 250"/>
<path d="M300 190 C 240 230 130 200 80 130"/>
<path d="M300 190 C 340 250 320 350 250 390"/>
<path d="M300 190 C 260 130 280 40 360 20"/>
</g>
<g fill="#fff" class="hero-stars"></g>
</svg>
</div>
<div class="hero-body">
<span class="kicker" style="--c:#6b4bff">Space · Cover Story</span>
<h1 id="hero-title">The galaxy that should not exist — and what it tells us about the dawn of everything</h1>
<p class="dek">Astronomers point the fictional Halcyon Deep Field Survey at a smudge of light 13.1 billion years old, and find a fully formed spiral where theory predicted only chaos.</p>
<div class="byline">
<span class="avatar" aria-hidden="true">MR</span>
<span>By <strong>Dr. Mira Okonkwo-Reyes</strong> · Senior space correspondent</span>
<span class="dot" aria-hidden="true">•</span>
<span class="readtime"><span class="clock" aria-hidden="true">◷</span> 9 min read</span>
</div>
<button class="btn btn-pop" data-read="The galaxy that should not exist">Read the story</button>
</div>
</article>
<!-- Trending sidebar -->
<aside class="trending" aria-labelledby="trend-title">
<h2 id="trend-title" class="rail-title">Trending now</h2>
<ol class="trend-list">
<li><span class="rank">01</span><a href="#" data-read="Why your brain edits time">Why your brain quietly edits time when you blink</a><span class="t-meta">Mind · 6 min</span></li>
<li><span class="rank">02</span><a href="#" data-read="The fungus that farms ants">The fungus that farms ants — not the other way around</a><span class="t-meta">Biology · 5 min</span></li>
<li><span class="rank">03</span><a href="#" data-read="A battery made of rust">A battery made of rust could store a city's solar power</a><span class="t-meta">Tech · 7 min</span></li>
<li><span class="rank">04</span><a href="#" data-read="The ocean's missing heat">We found the ocean's missing heat. It's worse than that</a><span class="t-meta">Climate · 8 min</span></li>
<li><span class="rank">05</span><a href="#" data-read="Tardigrades survived the vacuum">Tardigrades survived the vacuum — here's the molecule that saved them</a><span class="t-meta">Biology · 4 min</span></li>
</ol>
<div class="rail-stat">
<span class="rail-stat-num" data-count="4287">0</span>
<span class="rail-stat-lbl">stories published this year</span>
</div>
</aside>
</section>
<!-- Featured grid -->
<section id="grid" aria-labelledby="grid-title">
<div class="section-head">
<h2 id="grid-title">Latest features</h2>
<span class="count" id="grid-count"></span>
</div>
<div class="grid" id="story-grid"></div>
</section>
<!-- Explainers scroller -->
<section id="explainers" aria-labelledby="exp-title">
<div class="section-head">
<h2 id="exp-title">Explainers</h2>
<span class="section-sub">2-minute reads that actually click</span>
</div>
<div class="scroller" id="explainer-scroller" tabindex="0" aria-label="Explainers, scroll horizontally">
<article class="exp-card" style="--c:#1f9be0"><span class="exp-num">01</span><h3>What is a tipping point, really?</h3><p>The math behind systems that flip all at once.</p><span class="exp-tag">Climate</span></article>
<article class="exp-card" style="--c:#6b4bff"><span class="exp-num">02</span><h3>Why is the night sky dark?</h3><p>Olbers' paradox and the age of the universe.</p><span class="exp-tag">Space</span></article>
<article class="exp-card" style="--c:#0f9d6b"><span class="exp-num">03</span><h3>How does mRNA actually work?</h3><p>From recipe to protein in five steps.</p><span class="exp-tag">Biology</span></article>
<article class="exp-card" style="--c:#ff7a18"><span class="exp-num">04</span><h3>What is a qubit?</h3><p>Superposition without the hand-waving.</p><span class="exp-tag">Tech</span></article>
<article class="exp-card" style="--c:#e0457b"><span class="exp-num">05</span><h3>Why do we dream?</h3><p>Four theories, ranked by the evidence.</p><span class="exp-tag">Mind</span></article>
<article class="exp-card" style="--c:#1f9be0"><span class="exp-num">06</span><h3>How big is a light-year?</h3><p>A road trip you could never finish.</p><span class="exp-tag">Space</span></article>
</div>
</section>
<!-- Newsletter band -->
<section class="subscribe" id="subscribe" aria-labelledby="sub-title">
<div class="sub-inner">
<div class="sub-copy">
<h2 id="sub-title">The week's universe, in your inbox</h2>
<p>One email every Friday. Big ideas, no jargon, unsubscribe anytime. Join <strong data-count="128400" data-suffix="+">0</strong> curious readers.</p>
</div>
<form class="sub-form" id="sub-form" novalidate>
<label class="sr-only" for="sub-email">Email address</label>
<input type="email" id="sub-email" placeholder="[email protected]" required />
<button class="btn btn-light" type="submit">Subscribe</button>
<p class="sub-msg" id="sub-msg" role="status" aria-live="polite"></p>
</form>
</div>
</section>
</main>
<footer class="footer">
<div class="wrap footer-inner">
<span>© 2026 Orbit Media (fictional). Illustrative UI only.</span>
<span class="footer-links"><a href="#">About</a><a href="#">Ethics</a><a href="#">Contact</a></span>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Popular-Science Magazine Landing
A homepage for Orbit, a fictional popular-science magazine that aims to feel energetic but credible. A sticky brand bar opens into an inline search field; below it, a hero feature pairs a slowly rotating SVG galaxy and generated starfield with a big serif headline, category kicker, byline, and read time. A colorful chip row (Space, Biology, Climate, Tech, Mind) sits above the fold, and a ranked “Trending now” rail counts up its yearly-story stat as it scrolls into view.
The featured grid renders from a small dataset of plausible-but-fictional stories — each with author, dek (units, redshifts, DOIs-in-spirit), category-tinted tag, and read time on a procedurally generated gradient cover. Clicking a chip filters the grid live; typing in search narrows it instantly with a match count. Below, an Explainers scroller offers snap-scrolling two-minute cards, and a gradient Subscribe band validates email inline and confirms with a toast.
Everything is vanilla JS with no external libraries: equations and figures are faked with inline SVG and CSS, cards and counters animate with requestAnimationFrame and IntersectionObserver, and all interactive elements are keyboard-operable with visible focus and ARIA labels.
Illustrative UI only — fictional authors, data, and figures; not real scientific results.