/* ═══════════════════════════════════════════════════════════════════
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":"/vendor/gsap/index.js","gsap/ScrollTrigger":"/vendor/gsap/ScrollTrigger.js","gsap/SplitText":"/vendor/gsap/SplitText.js","gsap/Flip":"/vendor/gsap/Flip.js","gsap/ScrambleTextPlugin":"/vendor/gsap/ScrambleTextPlugin.js","gsap/TextPlugin":"/vendor/gsap/TextPlugin.js","gsap/all":"/vendor/gsap/all.js","gsap/":"/vendor/gsap/","lenis":"/vendor/lenis/dist/lenis.mjs","three":"/vendor/three/build/three.module.js","three/addons/":"/vendor/three/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>