Pages Hard
Blog Magazine
Modern blog layout with dark/light theme transitions, animated typography, and reading-view article cards.
Open in Lab
MCP
gsap lenis splittext view-transitions-api
Targets: JS HTML
Code
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
BLOG MAGAZINE โ styles.css
Dual-theme editorial layout with View Transitions API support
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
/* โโ Theme Tokens โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
[data-theme="dark"] {
--page-bg: #111118;
--page-surface: #1a1a24;
--page-border: #2a2a3a;
--page-text: #e8e8f0;
--page-muted: #8888a0;
--page-accent: #7c6cff;
--page-accent-warm: #ff7eb3;
--page-code-bg: #1e1e2e;
}
[data-theme="light"] {
--page-bg: #f8f7f4;
--page-surface: #ffffff;
--page-border: #e0ddd6;
--page-text: #1a1a24;
--page-muted: #6a6a78;
--page-accent: #5a4cd4;
--page-accent-warm: #d4507a;
--page-code-bg: #f0eff0;
}
/* โโ View Transition Animations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
::view-transition-new(root) {
animation: vt-wipe 0.5s ease-in-out;
clip-path: circle(0% at var(--wipe-x) var(--wipe-y));
}
@keyframes vt-wipe {
to {
clip-path: circle(var(--wipe-radius) at var(--wipe-x) var(--wipe-y));
}
}
::view-transition-old(root) {
animation: none;
}
/* โโ Reset & Base โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
background: var(--page-bg);
color: var(--page-text);
font-family: Georgia, "Times New Roman", "Noto Serif", serif;
line-height: 1.7;
min-height: 100vh;
transition: background 0.3s ease, color 0.3s ease;
}
a {
color: var(--page-accent);
text-decoration: none;
}
button {
font-family: inherit;
cursor: pointer;
}
/* โโ UI Font (metadata, labels, buttons) โโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.category-tag,
.reading-time,
.card-footer,
.card-author,
.card-date,
.btn-read,
.newsletter-btn,
.newsletter-input,
.theme-toggle,
.card-close-btn,
.site-footer {
font-family: "SF Pro Display", "Inter", system-ui, sans-serif;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SECTION 1: MASTHEAD
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.masthead {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 50vh;
padding: 6rem 2rem 4rem;
text-align: center;
}
.masthead-inner {
max-width: 720px;
}
.masthead-title {
font-size: clamp(2.4rem, 6vw, 4.5rem);
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--page-text);
margin-bottom: 1rem;
}
.masthead-subtitle {
font-size: clamp(0.95rem, 2vw, 1.2rem);
color: var(--page-muted);
font-style: italic;
letter-spacing: 0.01em;
}
/* Theme toggle button */
.theme-toggle {
position: fixed;
top: 1rem;
right: 5.5rem;
z-index: 10000;
width: 42px;
height: 42px;
border-radius: 50%;
border: 1px solid var(--page-border);
background: var(--page-surface);
color: var(--page-text);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, border-color 0.2s, transform 0.2s;
}
.theme-toggle:hover {
border-color: var(--page-accent);
transform: scale(1.08);
}
/* Show sun in dark mode, moon in light mode */
[data-theme="dark"] .theme-icon--sun {
display: block;
}
[data-theme="dark"] .theme-icon--moon {
display: none;
}
[data-theme="light"] .theme-icon--sun {
display: none;
}
[data-theme="light"] .theme-icon--moon {
display: block;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SECTION 2: FEATURED ARTICLE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.featured {
padding: 0 2rem 4rem;
max-width: 1100px;
margin: 0 auto;
}
.featured-card {
background: var(--page-surface);
border: 1px solid var(--page-border);
border-radius: 16px;
overflow: hidden;
transition: border-color 0.3s, background 0.3s;
}
.featured-card:hover {
border-color: var(--page-accent);
}
.featured-image {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.featured-gradient {
width: 100%;
height: 100%;
background: linear-gradient(135deg, var(--page-accent), var(--page-accent-warm));
opacity: 0.85;
transition: opacity 0.4s;
}
.featured-card:hover .featured-gradient {
opacity: 1;
}
.featured-body {
padding: 2rem 2.5rem 2.5rem;
}
.featured-meta {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.category-tag {
display: inline-block;
padding: 0.25rem 0.75rem;
background: color-mix(in srgb, var(--page-accent) 15%, transparent);
color: var(--page-accent);
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.reading-time {
font-size: 0.8rem;
color: var(--page-muted);
}
.featured-title {
font-size: clamp(1.6rem, 3.5vw, 2.4rem);
font-weight: 700;
line-height: 1.2;
margin-bottom: 1rem;
color: var(--page-text);
}
.featured-excerpt {
font-size: 1.05rem;
color: var(--page-muted);
line-height: 1.7;
margin-bottom: 1.5rem;
max-width: 640px;
}
.btn-read {
display: inline-block;
padding: 0.75rem 1.8rem;
background: var(--page-accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.02em;
transition: background 0.2s, transform 0.2s;
}
.btn-read:hover {
background: color-mix(in srgb, var(--page-accent) 85%, #000);
transform: translateY(-1px);
color: #fff;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SECTION 3: ARTICLE GRID
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.articles-section {
padding: 2rem 2rem 6rem;
max-width: 1100px;
margin: 0 auto;
}
.articles-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
transition: all 0.4s ease;
}
/* Individual article card */
.article-card {
background: var(--page-surface);
border: 1px solid var(--page-border);
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s, background 0.3s;
position: relative;
}
.article-card:hover {
border-color: var(--page-accent);
transform: translateY(-3px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}
/* Card gradient headers */
.card-gradient {
height: 140px;
transition: height 0.4s ease;
}
.card-gradient--indigo {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.card-gradient--emerald {
background: linear-gradient(135deg, #11998e, #38ef7d);
}
.card-gradient--amber {
background: linear-gradient(135deg, #f093fb, #f5576c);
}
.card-body {
padding: 1.25rem 1.5rem 1.5rem;
}
.card-body .category-tag {
margin-bottom: 0.75rem;
}
.card-title {
font-size: 1.15rem;
font-weight: 700;
line-height: 1.3;
margin-bottom: 0.6rem;
color: var(--page-text);
}
.card-excerpt {
font-size: 0.9rem;
color: var(--page-muted);
line-height: 1.6;
margin-bottom: 1rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: var(--page-muted);
padding-top: 0.75rem;
border-top: 1px solid var(--page-border);
}
.card-author {
font-weight: 600;
color: var(--page-text);
}
/* Full article content (hidden by default) */
.card-full-content {
display: none;
padding: 0 1.5rem 1.5rem;
}
.card-full-content p {
font-size: 1rem;
color: var(--page-text);
line-height: 1.8;
margin-bottom: 1.25rem;
}
.card-full-content p:last-child {
margin-bottom: 0;
}
/* Close button (hidden by default) */
.card-close-btn {
display: none;
position: absolute;
top: 1rem;
right: 1rem;
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 1.3rem;
line-height: 1;
z-index: 10;
transition: background 0.2s, transform 0.2s;
align-items: center;
justify-content: center;
}
.card-close-btn:hover {
background: rgba(0, 0, 0, 0.75);
transform: scale(1.1);
}
/* โโ Reading View โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.articles-grid.has-reading-view {
display: flex;
flex-direction: column;
align-items: center;
}
.articles-grid.has-reading-view .article-card {
display: none;
}
.articles-grid.has-reading-view .article-card.reading-view {
display: block;
width: 100%;
max-width: 680px;
cursor: default;
transform: none;
box-shadow: 0 8px 60px rgba(0, 0, 0, 0.12);
}
.article-card.reading-view:hover {
transform: none;
}
.article-card.reading-view .card-gradient {
height: 100px;
}
.article-card.reading-view .card-title {
font-size: 1.6rem;
margin-bottom: 1rem;
}
.article-card.reading-view .card-excerpt {
font-size: 1rem;
line-height: 1.8;
margin-bottom: 1.5rem;
}
.article-card.reading-view .card-full-content {
display: block;
}
.article-card.reading-view .card-close-btn {
display: flex;
}
.article-card.reading-view .card-footer {
margin: 0 1.5rem;
padding-bottom: 0.5rem;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SECTION 4: NEWSLETTER CTA
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.newsletter {
position: relative;
overflow: hidden;
padding: 6rem 2rem;
text-align: center;
background: var(--page-surface);
border-top: 1px solid var(--page-border);
}
.newsletter-orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.2;
pointer-events: none;
}
.newsletter-orb--1 {
width: 400px;
height: 400px;
background: var(--page-accent);
top: -100px;
left: -80px;
}
.newsletter-orb--2 {
width: 350px;
height: 350px;
background: var(--page-accent-warm);
bottom: -120px;
right: -60px;
}
.newsletter-inner {
position: relative;
z-index: 1;
max-width: 520px;
margin: 0 auto;
}
.newsletter-heading {
font-size: clamp(1.6rem, 3vw, 2.2rem);
font-weight: 700;
margin-bottom: 0.75rem;
color: var(--page-text);
}
.newsletter-subtitle {
font-size: 0.95rem;
color: var(--page-muted);
margin-bottom: 2rem;
line-height: 1.6;
}
.newsletter-form {
display: flex;
gap: 0.75rem;
max-width: 420px;
margin: 0 auto;
}
.newsletter-input {
flex: 1;
padding: 0.75rem 1rem;
background: var(--page-bg);
border: 1px solid var(--page-border);
border-radius: 8px;
color: var(--page-text);
font-size: 0.9rem;
outline: none;
transition: border-color 0.2s;
}
.newsletter-input::placeholder {
color: var(--page-muted);
}
.newsletter-input:focus {
border-color: var(--page-accent);
}
.newsletter-btn {
padding: 0.75rem 1.5rem;
background: var(--page-accent);
color: #fff;
border: none;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 600;
white-space: nowrap;
transition: background 0.2s, transform 0.2s;
}
.newsletter-btn:hover:not(:disabled) {
background: color-mix(in srgb, var(--page-accent) 85%, #000);
transform: translateY(-1px);
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
FOOTER
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.site-footer {
text-align: center;
padding: 2rem;
font-size: 0.8rem;
color: var(--page-muted);
border-top: 1px solid var(--page-border);
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
DEMO SHELL OVERRIDES FOR LIGHT THEME
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
[data-theme="light"] .demo-shell-back {
background: rgba(255, 255, 255, 0.85) !important;
border-color: rgba(0, 0, 0, 0.12) !important;
color: var(--page-accent) !important;
}
[data-theme="light"] .demo-shell-back:hover {
background: rgba(255, 255, 255, 1) !important;
border-color: rgba(0, 0, 0, 0.25) !important;
}
[data-theme="light"] .demo-shell-info {
background: rgba(248, 247, 244, 0.95) !important;
border-top-color: rgba(0, 0, 0, 0.08) !important;
color: var(--page-muted) !important;
}
[data-theme="light"] .demo-shell-info .demo-title {
color: var(--page-text) !important;
}
[data-theme="light"] .demo-shell-info .demo-tag {
background: color-mix(in srgb, var(--page-accent) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--page-accent) 25%, transparent) !important;
color: var(--page-accent) !important;
}
[data-theme="light"] .motion-toggle {
background: rgba(255, 255, 255, 0.85) !important;
border-color: rgba(0, 0, 0, 0.12) !important;
color: var(--page-accent) !important;
}
[data-theme="light"] .motion-toggle:hover {
background: rgba(255, 255, 255, 1) !important;
border-color: rgba(0, 0, 0, 0.25) !important;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
REDUCED MOTION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.reduced-motion *,
.reduced-motion *::before,
.reduced-motion *::after {
transition-duration: 0s !important;
animation-duration: 0s !important;
}
.reduced-motion .article-card,
.reduced-motion .featured-card {
transform: none !important;
opacity: 1 !important;
}
.reduced-motion .masthead-title,
.reduced-motion .masthead-subtitle,
.reduced-motion .featured-title,
.reduced-motion .newsletter-heading {
opacity: 1 !important;
transform: none !important;
}
.reduced-motion ::view-transition-new(root),
.reduced-motion ::view-transition-old(root) {
animation: none !important;
}
/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
RESPONSIVE
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@media (max-width: 900px) {
.articles-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.articles-grid {
grid-template-columns: 1fr;
}
.masthead {
min-height: 40vh;
padding: 5rem 1.25rem 3rem;
}
.featured {
padding: 0 1rem 3rem;
}
.featured-body {
padding: 1.5rem;
}
.featured-image {
aspect-ratio: 4 / 3;
}
.articles-section {
padding: 2rem 1rem 4rem;
}
.card-body {
padding: 1rem 1.25rem 1.25rem;
}
.card-full-content {
padding: 0 1.25rem 1.25rem;
}
.newsletter {
padding: 4rem 1.25rem;
}
.newsletter-form {
flex-direction: column;
}
.newsletter-btn {
width: 100%;
}
.theme-toggle {
right: 4.5rem;
width: 36px;
height: 36px;
}
.article-card.reading-view .card-title {
font-size: 1.3rem;
}
}if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import { SplitText } from "gsap/SplitText";
import { ScrambleTextPlugin } from "gsap/ScrambleTextPlugin";
import Lenis from "lenis";
gsap.registerPlugin(ScrollTrigger, SplitText, ScrambleTextPlugin);
initDemoShell({
title: "Blog Magazine",
category: "pages",
tech: ["gsap", "lenis", "splittext", "view-transitions-api"],
});
const lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
let reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add("reduced-motion");
window.addEventListener("motion-preference", (e) => {
reduced = e.detail.reduced;
document.documentElement.classList.toggle("reduced-motion", reduced);
ScrollTrigger.refresh();
});
const dur = (d) => (reduced ? 0 : d);
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// SECTION 1: MASTHEAD โ SplitText + ScrambleText entrance
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const mastheadTitle = document.getElementById("masthead-title");
const mastheadSubtitle = document.getElementById("masthead-subtitle");
// SplitText char-by-char reveal for title
const titleSplit = new SplitText(mastheadTitle, {
type: "chars",
charsClass: "char",
});
gsap.set(titleSplit.chars, {
opacity: 0,
y: reduced ? 0 : 40,
rotateX: reduced ? 0 : -60,
transformPerspective: 600,
});
const mastheadTl = gsap.timeline({ delay: 0.4 });
mastheadTl.to(titleSplit.chars, {
opacity: 1,
y: 0,
rotateX: 0,
duration: dur(0.7),
ease: "expo.out",
stagger: { each: 0.03 },
});
// ScrambleText for subtitle
if (reduced) {
// Skip scramble, just show immediately
mastheadSubtitle.style.opacity = "1";
} else {
const subtitleText = mastheadSubtitle.textContent;
gsap.set(mastheadSubtitle, { opacity: 1 });
mastheadTl.fromTo(
mastheadSubtitle,
{ opacity: 0 },
{
opacity: 1,
duration: 0.01,
},
0.6
);
mastheadTl.to(
mastheadSubtitle,
{
duration: dur(1.2),
scrambleText: {
text: subtitleText,
chars: "lowerCase",
speed: 0.5,
revealDelay: 0.3,
},
},
0.6
);
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// THEME TOGGLE โ View Transitions API with circular clip-path wipe
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const supportsVT = typeof document.startViewTransition === "function";
const themeToggle = document.getElementById("theme-toggle");
function updateToggleIcon(theme) {
// Icon visibility handled by CSS data-theme selectors โ no JS needed
themeToggle.setAttribute(
"aria-label",
theme === "dark" ? "Switch to light theme" : "Switch to dark theme"
);
}
themeToggle.addEventListener("click", (e) => {
const isDark = document.documentElement.dataset.theme === "dark";
const newTheme = isDark ? "light" : "dark";
if (!supportsVT || reduced) {
document.documentElement.dataset.theme = newTheme;
updateToggleIcon(newTheme);
return;
}
const { clientX: x, clientY: y } = e;
const radius = Math.hypot(
Math.max(x, window.innerWidth - x),
Math.max(y, window.innerHeight - y)
);
document.documentElement.style.setProperty("--wipe-x", `${x}px`);
document.documentElement.style.setProperty("--wipe-y", `${y}px`);
document.documentElement.style.setProperty("--wipe-radius", `${radius}px`);
document.startViewTransition(() => {
document.documentElement.dataset.theme = newTheme;
updateToggleIcon(newTheme);
});
});
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// SECTION 2: FEATURED ARTICLE โ SplitText line reveal on scroll
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const featuredTitle = document.getElementById("featured-title");
if (featuredTitle) {
const featuredSplit = new SplitText(featuredTitle, {
type: "lines",
linesClass: "line",
});
gsap.set(featuredSplit.lines, {
opacity: 0,
y: reduced ? 0 : 40,
});
gsap.to(featuredSplit.lines, {
opacity: 1,
y: 0,
duration: dur(0.7),
ease: "expo.out",
stagger: { each: 0.12 },
scrollTrigger: {
trigger: ".featured",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
}
// Featured card entrance
const featuredCard = document.querySelector(".featured-card");
if (featuredCard) {
gsap.set(featuredCard, { opacity: 0, y: reduced ? 0 : 30 });
gsap.to(featuredCard, {
opacity: 1,
y: 0,
duration: dur(0.8),
ease: "expo.out",
scrollTrigger: {
trigger: ".featured",
start: "top 80%",
toggleActions: "play none none reverse",
},
});
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// SECTION 3: ARTICLE GRID โ stagger entrance + reading view
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const articlesGrid = document.getElementById("articles-grid");
const articleCards = document.querySelectorAll(".article-card");
// Card entrance animation
articleCards.forEach((card, i) => {
gsap.set(card, { opacity: 0, y: reduced ? 0 : 40 });
gsap.to(card, {
opacity: 1,
y: 0,
duration: dur(0.6),
ease: "expo.out",
delay: i * 0.1,
scrollTrigger: {
trigger: ".articles-section",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
});
// โโ Reading View Logic โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let activeReadingCard = null;
function openReadingView(card) {
if (activeReadingCard === card) return;
// Close any existing reading view first
if (activeReadingCard) {
closeReadingView(false);
}
const doOpen = () => {
card.classList.add("reading-view");
articlesGrid.classList.add("has-reading-view");
activeReadingCard = card;
// Scroll to the card
card.scrollIntoView({ behavior: reduced ? "auto" : "smooth", block: "start" });
// Refresh ScrollTrigger after layout change
setTimeout(() => ScrollTrigger.refresh(), 100);
};
if (supportsVT && !reduced) {
// Set a view-transition-name on the card dynamically
card.style.viewTransitionName = "article-expand";
document
.startViewTransition(() => {
doOpen();
})
.finished.then(() => {
card.style.viewTransitionName = "";
});
} else {
doOpen();
}
}
function closeReadingView(useTransition = true) {
if (!activeReadingCard) return;
const card = activeReadingCard;
const doClose = () => {
card.classList.remove("reading-view");
articlesGrid.classList.remove("has-reading-view");
activeReadingCard = null;
// Refresh ScrollTrigger after layout change
setTimeout(() => ScrollTrigger.refresh(), 100);
};
if (supportsVT && !reduced && useTransition) {
card.style.viewTransitionName = "article-expand";
document
.startViewTransition(() => {
doClose();
})
.finished.then(() => {
card.style.viewTransitionName = "";
});
} else {
doClose();
}
}
// Click handlers for cards
articleCards.forEach((card) => {
const closeBtn = card.querySelector(".card-close-btn");
card.addEventListener("click", (e) => {
// Do not open if we're clicking the close button
if (e.target.closest(".card-close-btn")) return;
// Only open if not already in reading view
if (!card.classList.contains("reading-view")) {
openReadingView(card);
}
});
closeBtn.addEventListener("click", (e) => {
e.stopPropagation();
closeReadingView();
});
});
// Close reading view on Escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && activeReadingCard) {
closeReadingView();
}
});
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// SECTION 4: NEWSLETTER โ SplitText word reveal
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const newsletterHeading = document.getElementById("newsletter-heading");
if (newsletterHeading) {
const nlSplit = new SplitText(newsletterHeading, {
type: "words",
wordsClass: "word",
});
gsap.set(nlSplit.words, {
opacity: 0,
y: reduced ? 0 : 25,
});
gsap.to(nlSplit.words, {
opacity: 1,
y: 0,
duration: dur(0.6),
ease: "expo.out",
stagger: { each: 0.08 },
scrollTrigger: {
trigger: ".newsletter",
start: "top 75%",
toggleActions: "play none none reverse",
},
});
}
// Newsletter subtitle entrance
const nlSubtitle = document.querySelector(".newsletter-subtitle");
if (nlSubtitle) {
gsap.set(nlSubtitle, { opacity: 0, y: reduced ? 0 : 15 });
gsap.to(nlSubtitle, {
opacity: 1,
y: 0,
duration: dur(0.6),
ease: "expo.out",
scrollTrigger: {
trigger: ".newsletter",
start: "top 70%",
toggleActions: "play none none reverse",
},
});
}
// Newsletter form entrance
const nlForm = document.querySelector(".newsletter-form");
if (nlForm) {
gsap.set(nlForm, { opacity: 0, y: reduced ? 0 : 15 });
gsap.to(nlForm, {
opacity: 1,
y: 0,
duration: dur(0.6),
ease: "expo.out",
scrollTrigger: {
trigger: ".newsletter",
start: "top 65%",
toggleActions: "play none none reverse",
},
});
}
// Footer entrance
const siteFooter = document.querySelector(".site-footer");
if (siteFooter) {
gsap.set(siteFooter, { opacity: 0 });
gsap.to(siteFooter, {
opacity: 1,
duration: dur(0.5),
ease: "expo.out",
scrollTrigger: {
trigger: siteFooter,
start: "top 95%",
toggleActions: "play none none reverse",
},
});
}<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog Magazine โ stealthisdesign</title>
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"https://esm.sh/gsap@3.13.0","gsap/ScrollTrigger":"https://esm.sh/gsap@3.13.0/ScrollTrigger","gsap/SplitText":"https://esm.sh/gsap@3.13.0/SplitText","gsap/Flip":"https://esm.sh/gsap@3.13.0/Flip","gsap/ScrambleTextPlugin":"https://esm.sh/gsap@3.13.0/ScrambleTextPlugin","gsap/TextPlugin":"https://esm.sh/gsap@3.13.0/TextPlugin","gsap/all":"https://esm.sh/gsap@3.13.0/all","gsap/":"https://esm.sh/gsap@3.13.0/","lenis":"https://esm.sh/lenis@1.1.13/dist/lenis.mjs","three":"https://esm.sh/three@0.171.0","three/addons/":"https://esm.sh/three@0.171.0/examples/jsm/"}}</script>
<style>html.lenis,
html.lenis body {
height: auto;
}
.lenis:not(.lenis-autoToggle).lenis-stopped {
overflow: clip;
}
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] {
overscroll-behavior: contain;
}
.lenis.lenis-smooth iframe {
pointer-events: none;
}
.lenis.lenis-autoToggle {
transition-property: overflow;
transition-duration: 1ms;
transition-behavior: allow-discrete;
}</style>
</head>
<body>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- SECTION 1: MASTHEAD / HEADER -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<header class="masthead" id="masthead">
<div class="masthead-inner">
<h1 class="masthead-title" id="masthead-title">The Digital Atelier</h1>
<p class="masthead-subtitle" id="masthead-subtitle">Thoughts on design, code, and craft</p>
</div>
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle theme">
<svg class="theme-icon theme-icon--sun" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<svg class="theme-icon theme-icon--moon" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>
</header>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- SECTION 2: FEATURED ARTICLE -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="featured" id="featured">
<article class="featured-card">
<div class="featured-image" aria-hidden="true">
<div class="featured-gradient"></div>
</div>
<div class="featured-body">
<div class="featured-meta">
<span class="category-tag">Design</span>
<span class="reading-time">5 min read</span>
</div>
<h2 class="featured-title" id="featured-title">The Art of Digital Minimalism</h2>
<p class="featured-excerpt">In an era of information overload, the most powerful design choice is often what you leave out. Minimalism in digital design is not about austerity โ it is about intentionality, clarity, and respect for the viewer's attention.</p>
<a href="#" class="btn-read" onclick="return false;">Read article</a>
</div>
</article>
</section>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- SECTION 3: ARTICLE GRID -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="articles-section" id="articles">
<div class="articles-grid" id="articles-grid">
<!-- Card 1 -->
<article class="article-card" data-card="0" id="card-0">
<div class="card-gradient card-gradient--indigo" aria-hidden="true"></div>
<div class="card-body">
<span class="category-tag">Code</span>
<h3 class="card-title">Composability Over Complexity</h3>
<p class="card-excerpt">The best codebases are not the ones with the cleverest abstractions. They are the ones where every piece does one thing well and combines naturally with the rest.</p>
<div class="card-footer">
<span class="card-author">Maya Chen</span>
<span class="card-date">Feb 8, 2026</span>
</div>
</div>
<div class="card-full-content">
<p>There is a moment in every growing codebase when complexity begins to feel inevitable. Features pile onto features, edge cases multiply, and the once-clean architecture starts to buckle under the weight of pragmatic decisions. This is the inflection point where most teams reach for heavyweight frameworks, elaborate design patterns, or multi-layered abstractions to restore order.</p>
<p>But there is another path. Composability โ the practice of building software from small, self-contained units that combine predictably โ offers a more sustainable answer. The Unix philosophy captured this decades ago: write programs that do one thing well and work together through simple interfaces. The same principle translates directly to modern frontend components, backend services, and even infrastructure.</p>
<p>Consider a function that formats a date, another that fetches user data, and a third that renders a profile card. Each is trivially testable in isolation. Composed together, they produce a complete feature. When requirements change โ and they always do โ you replace or modify a single piece without destabilizing the whole. This is not elegance for its own sake; it is a practical strategy for teams that need to move quickly without accumulating technical debt.</p>
</div>
<button class="card-close-btn" aria-label="Close reading view">×</button>
</article>
<!-- Card 2 -->
<article class="article-card" data-card="1" id="card-1">
<div class="card-gradient card-gradient--emerald" aria-hidden="true"></div>
<div class="card-body">
<span class="category-tag">Craft</span>
<h3 class="card-title">The Quiet Power of Typography</h3>
<p class="card-excerpt">Typography is the invisible architecture of every interface. When it works, nobody notices. When it fails, everything feels wrong.</p>
<div class="card-footer">
<span class="card-author">Liam Hartwell</span>
<span class="card-date">Jan 24, 2026</span>
</div>
</div>
<div class="card-full-content">
<p>We spend our days immersed in type without thinking about it. The size of a heading, the spacing between lines, the weight of a label โ these micro-decisions accumulate into something larger: the feeling of using a product. A cramped paragraph signals carelessness. Generous whitespace between sections creates breathing room that lets ideas land. The typeface you choose for body copy communicates more than the words themselves โ it sets a tone before a single sentence is read.</p>
<p>The best typographic systems are invisible precisely because they have been obsessively considered. They establish hierarchy without shouting, guide the eye without arrows, and create rhythm without decoration. A well-set page is like a well-conducted orchestra: the individual instruments disappear into the coherence of the whole.</p>
<p>For digital products, this means treating typography as a first-class design decision rather than an afterthought. Define a type scale early. Set line heights that give text room to breathe โ typically 1.5 to 1.7 for body text. Limit yourself to two typefaces at most. And test your typography at every viewport: what feels spacious on a desktop can become suffocating on a phone. The goal is not beauty for its own sake, but communication made effortless.</p>
</div>
<button class="card-close-btn" aria-label="Close reading view">×</button>
</article>
<!-- Card 3 -->
<article class="article-card" data-card="2" id="card-2">
<div class="card-gradient card-gradient--amber" aria-hidden="true"></div>
<div class="card-body">
<span class="category-tag">Design</span>
<h3 class="card-title">Designing for Slow Attention</h3>
<p class="card-excerpt">Not every interaction needs to be instant. Sometimes the most respectful design is one that gives people space to think before they act.</p>
<div class="card-footer">
<span class="card-author">Sofia Reyes</span>
<span class="card-date">Jan 11, 2026</span>
</div>
</div>
<div class="card-full-content">
<p>Speed has become the default metric of digital quality. Faster load times, quicker interactions, shorter paths to conversion โ the industry optimizes relentlessly for velocity. But speed is not always a virtue. In contexts that involve reflection, commitment, or emotional weight, designing for speed can actually undermine the experience.</p>
<p>Consider the difference between deleting a file and deleting an account. Both are destructive actions, but one is trivially reversible and the other carries genuine consequence. A confirmation dialog that appears and vanishes in milliseconds does not give someone space to truly consider what they are about to do. A brief pause, a clear explanation of what will happen, a moment of intentional friction โ these are not obstacles. They are acts of care.</p>
<p>Designing for slow attention means recognizing that some moments deserve more weight than others. It means using animation not to dazzle but to orient. It means writing interface copy that acknowledges the gravity of a decision rather than burying it in corporate euphemism. The goal is not to slow everything down, but to be thoughtful about where speed helps and where it harms. In a world that demands constant acceleration, deliberately slowing down at the right moments is a radical design choice.</p>
</div>
<button class="card-close-btn" aria-label="Close reading view">×</button>
</article>
</div>
</section>
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<!-- SECTION 4: NEWSLETTER CTA -->
<!-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -->
<section class="newsletter" id="newsletter">
<div class="newsletter-orb newsletter-orb--1" aria-hidden="true"></div>
<div class="newsletter-orb newsletter-orb--2" aria-hidden="true"></div>
<div class="newsletter-inner">
<h2 class="newsletter-heading" id="newsletter-heading">Stay in the loop</h2>
<p class="newsletter-subtitle">Essays on design, engineering, and the craft of building for the web. No spam, unsubscribe anytime.</p>
<div class="newsletter-form">
<input type="email" class="newsletter-input" placeholder="you@example.com" aria-label="Email address" disabled>
<button class="newsletter-btn" disabled>Subscribe</button>
</div>
</div>
</section>
<footer class="site-footer">
<p>© 2026 The Digital Atelier. All rights reserved.</p>
</footer>
<script type="module" src="script.js"></script>
</body>
</html>Blog Magazine
Modern blog layout with dark/light theme transitions, animated typography, and reading-view article cards.
Source
- Repository:
libs-genclaude - Original demo id:
27-blog-magazine
Notes
Modern blog layout with dark/light theme transitions, animated typography, and reading-view article cards.