Scale / Zoom Transition
A card expands to reveal more detail. The zoom-in metaphor intuitively signals drilling deeper into content without a full page load.
Scale / Zoom Transition the 3rd of 11 designs in the 11 CSS Page Transitions collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<!-- Header -->
<header class="header">
<div class="logo">Bloom</div>
<div class="header-right">
<span>Shop</span>
<span>Lookbook</span>
<span>🛍 3</span>
</div>
</header>
<!-- Product Grid -->
<div class="grid" id="grid"></div>
<!-- Detail Overlay -->
<div class="detail-overlay" id="overlay">
<div class="detail-backdrop" id="backdrop"></div>
<div class="detail-panel">
<div class="detail-handle"></div>
<button class="close-btn" id="closeBtn">✕</button>
<div class="detail-layout">
<div class="detail-visual" id="detailVisual"></div>
<div>
<div class="detail-cat" id="detailCat"></div>
<h2 class="detail-title" id="detailTitle"></h2>
<div class="tag-row" id="detailTags"></div>
<p class="detail-desc" id="detailDesc"></p>
<div class="detail-price-row">
<div class="detail-price" id="detailPrice"></div>
<div class="detail-old-price" id="detailOld"></div>
</div>
<div class="detail-actions">
<button class="btn-add">Add to Cart</button>
<button class="btn-wish">♡</button>
</div>
</div>
</div>
</div>
</div> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f2ede4;
--card-bg: #fff;
--ink: #1c1814;
--muted: #8c8278;
--detail-dur: 480ms;
--detail-ease: cubic-bezier(0.32, 0.72, 0, 1);
}
html, body {
min-height: 100%; font-family: 'Cabinet Grotesk', sans-serif;
background: var(--bg); color: var(--ink);
}
/* Header */
.header {
padding: 32px 48px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky; top: 0; z-index: 5;
background: var(--bg);
}
.logo {
font-family: 'Fraunces', serif;
font-size: 22px;
font-weight: 300;
font-style: italic;
color: var(--ink);
}
.header-right {
display: flex;
gap: 24px;
align-items: center;
font-size: 13px;
font-weight: 500;
color: var(--muted);
}
/* Grid */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2px;
padding: 0 2px 2px;
}
/* Card */
.card {
background: var(--card-bg);
cursor: pointer;
overflow: hidden;
position: relative;
transition: transform 0.2s;
}
.card:hover { z-index: 2; }
.card-thumb {
aspect-ratio: 4/3;
display: flex;
align-items: center;
justify-content: center;
font-size: 72px;
position: relative;
overflow: hidden;
transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
}
.card:hover .card-thumb { transform: scale(1.04); }
.card-meta {
padding: 20px 24px 28px;
}
.card-cat {
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 8px;
}
.card-title {
font-family: 'Fraunces', serif;
font-size: 22px;
font-weight: 300;
line-height: 1.2;
margin-bottom: 8px;
}
.card-price {
font-size: 14px;
font-weight: 700;
color: var(--ink);
}
/* Detail overlay */
.detail-overlay {
position: fixed;
inset: 0;
z-index: 100;
pointer-events: none;
display: flex;
align-items: flex-end;
}
.detail-backdrop {
position: absolute;
inset: 0;
background: rgba(28, 24, 20, 0);
transition: background var(--detail-dur) var(--detail-ease);
}
.detail-panel {
position: relative;
width: 100%;
max-width: 100%;
background: white;
border-radius: 20px 20px 0 0;
padding: 40px 48px 56px;
transform: translateY(100%) scale(0.92);
transform-origin: center bottom;
transition:
transform var(--detail-dur) var(--detail-ease),
opacity var(--detail-dur) var(--detail-ease);
opacity: 0;
max-height: 85vh;
overflow-y: auto;
}
.detail-overlay.open {
pointer-events: auto;
}
.detail-overlay.open .detail-backdrop {
background: rgba(28, 24, 20, 0.6);
}
.detail-overlay.open .detail-panel {
transform: translateY(0) scale(1);
opacity: 1;
}
.detail-handle {
width: 40px; height: 4px;
background: #ddd;
border-radius: 2px;
margin: 0 auto 32px;
}
.detail-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: start;
}
.detail-visual {
aspect-ratio: 1;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 100px;
transition: transform 0.6s var(--detail-ease);
}
.detail-overlay.open .detail-visual {
transform: scale(1);
}
.detail-cat {
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 12px;
}
.detail-title {
font-family: 'Fraunces', serif;
font-size: 36px;
font-weight: 300;
line-height: 1.1;
margin-bottom: 16px;
}
.detail-desc {
font-size: 14px;
line-height: 1.8;
color: var(--muted);
margin-bottom: 28px;
}
.detail-price-row {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
}
.detail-price {
font-size: 28px;
font-weight: 900;
}
.detail-old-price {
font-size: 16px;
color: var(--muted);
text-decoration: line-through;
}
.detail-actions {
display: flex;
gap: 12px;
}
.btn-add {
flex: 1;
height: 52px;
background: var(--ink);
color: white;
border: none;
border-radius: 8px;
font-family: 'Cabinet Grotesk', sans-serif;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.03em;
cursor: pointer;
transition: opacity 0.2s;
}
.btn-add:hover { opacity: 0.85; }
.btn-wish {
width: 52px; height: 52px;
background: var(--bg);
border: none;
border-radius: 8px;
font-size: 22px;
cursor: pointer;
}
.close-btn {
position: absolute;
top: 32px; right: 40px;
width: 36px; height: 36px;
border-radius: 50%;
background: var(--bg);
border: none;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--muted);
transition: background 0.2s;
}
.close-btn:hover { background: #e8e2d8; }
/* Tags */
.tag-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.tag {
padding: 5px 14px;
background: var(--bg);
border-radius: 20px;
font-size: 12px;
font-weight: 500;
color: var(--muted);
}
@media (max-width: 640px) {
.header { padding: 20px 20px; }
.grid { grid-template-columns: 1fr 1fr; }
.detail-layout { grid-template-columns: 1fr; }
.detail-panel { padding: 28px 24px 40px; }
.detail-visual { max-height: 200px; }
} const products = [
{ emoji: '🌸', bg: '#fce8f0', cat: 'Florals', title: 'Cherry Blossom Silk', price: '$128', old: '$180', tags: ['New', 'Limited', 'Silk'], desc: 'Hand-dyed in small batches using traditional Japanese techniques. Each piece is unique, with natural variations that make it truly one-of-a-kind.' },
{ emoji: '🌿', bg: '#e8f0e8', cat: 'Botanicals', title: 'Eucalyptus Linen', price: '$94', old: '', tags: ['Organic', 'Linen'], desc: 'Sustainably grown and minimally processed. Our eucalyptus linen is GOTS certified and biodegradable from thread to packaging.' },
{ emoji: '🍋', bg: '#f8f0d8', cat: 'Citrus', title: 'Lemon Grove Tee', price: '$56', old: '$72', tags: ['Sale', 'Cotton'], desc: 'A relaxed, slightly oversized silhouette in 100% organic cotton. Pre-washed for softness. Looks better the more you wear it.' },
{ emoji: '🌊', bg: '#dceef8', cat: 'Ocean', title: 'Coastal Linen Shirt', price: '$112', old: '', tags: ['Linen', 'Unisex'], desc: 'Inspired by Mediterranean mornings. The loose fit and breathable linen make it ideal for warm days from breakfast to sunset.' },
{ emoji: '🌙', bg: '#e8e0f8', cat: 'Celestial', title: 'Midnight Modal Dress', price: '$168', old: '$220', tags: ['Sale', 'Modal', 'New'], desc: 'Ultra-soft TENCEL modal in a deep midnight hue. The bias-cut drapes beautifully and the fabric moves with you — not against you.' },
{ emoji: '🍂', bg: '#f0e8d8', cat: 'Autumn', title: 'Terracotta Knit', price: '$136', old: '', tags: ['Knit', 'Wool'], desc: 'Hand-knitted in a relaxed, chunky stitch using undyed alpaca-merino blend. Warm without bulk, and gets softer with every wash.' },
];
const grid = document.getElementById('grid');
const overlay = document.getElementById('overlay');
const backdrop = document.getElementById('backdrop');
const closeBtn = document.getElementById('closeBtn');
// Render cards
products.forEach((p, i) => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-thumb" style="background:${p.bg}">${p.emoji}</div>
<div class="card-meta">
<div class="card-cat">${p.cat}</div>
<div class="card-title">${p.title}</div>
<div class="card-price">${p.price}</div>
</div>
`;
card.addEventListener('click', () => openDetail(p));
grid.appendChild(card);
});
function openDetail(p) {
document.getElementById('detailVisual').style.background = p.bg;
document.getElementById('detailVisual').textContent = p.emoji;
document.getElementById('detailCat').textContent = p.cat;
document.getElementById('detailTitle').textContent = p.title;
document.getElementById('detailPrice').textContent = p.price;
document.getElementById('detailOld').textContent = p.old;
document.getElementById('detailDesc').textContent = p.desc;
const tagsEl = document.getElementById('detailTags');
tagsEl.innerHTML = p.tags.map(t => `<span class="tag">${t}</span>`).join('');
overlay.classList.add('open');
document.body.style.overflow = 'hidden';
}
function closeDetail() {
overlay.classList.remove('open');
document.body.style.overflow = '';
}
closeBtn.addEventListener('click', closeDetail);
backdrop.addEventListener('click', closeDetail);