Pure CSS frame — drop any image or iframe into .page.
Your website goes here
Replace the gradient with a real screenshot to preview your project.
<div class="browser"> <div class="chrome"> <!-- traffic-light dots --> <div class="dots"> <span></span><span></span><span></span> </div> <div class="url-bar"> <i class="ph-fill ph-lock-key"></i>mysite.com </div> </div> <!-- drop your screenshot, iframe, or content here --> <div class="page"> <img src="screenshot.jpg" alt=""> </div> </div>
.browser { max-width: 900px; background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 25px 60px rgba(0,0,0,.25); } /* Chrome toolbar */ .chrome { background: #e9eaec; padding: 12px 16px; display: flex; align-items: center; gap: 10px; } /* Traffic-light dots */ .dots span { width: 12px; height: 12px; border-radius: 50%; } .dots span:nth-child(1) { background: #ff5f57; } .dots span:nth-child(2) { background: #ffbd2e; } .dots span:nth-child(3) { background: #28c840; } .url-bar { flex: 1; background: #fff; padding: 6px 14px; border-radius: 999px; font-family: monospace; font-size: 13px; } .page { aspect-ratio: 16 / 10; }
<div class="trio"> <div class="device desktop"> <div class="screen"></div> </div> <div class="device tablet"> <div class="screen"></div> </div> <div class="device phone"> <div class="screen"></div> </div> </div>
.trio { display: flex; align-items: flex-end; justify-content: center; gap: 30px; flex-wrap: wrap; } /* Desktop — 16:10 */ .desktop { width: 420px; background: #222; padding: 8px 8px 14px; border-radius: 10px 10px 3px 3px; } .desktop .screen { aspect-ratio: 16 / 10; } .desktop::after { content: ""; display: block; width: 120px; height: 10px; margin: 10px auto 0; background: #ccc; border-radius: 0 0 8px 8px; } /* Tablet — 3:4 */ .tablet { width: 200px; border-radius: 18px; } .tablet .screen { aspect-ratio: 3 / 4; } /* Phone — 9:19 */ .phone { width: 110px; border-radius: 16px; } .phone .screen { aspect-ratio: 9 / 19; }
<div class="video-wrap"> <!-- autoplay + muted + loop is the portfolio trifecta --> <video autoplay muted loop playsinline poster="poster.jpg"> <source src="demo.mp4" type="video/mp4"> </video> <div class="meta"> <div class="cat">Case Study</div> <h3>Watch the interaction</h3> </div> </div>
.video-wrap { position: relative; max-width: 900px; border-radius: 14px; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,.22); } video { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; } /* Caption overlay at the bottom */ .meta { position: absolute; bottom: 0; left: 0; right: 0; padding: 24px 28px; color: #fff; background: linear-gradient(180deg, transparent, rgba(0,0,0,.75)); }
Drag the handle to compare.
<div class="ba-slider" id="ba"> <img class="layer before" src="before.jpg"> <img class="layer after" src="after.jpg"> <div class="handle"></div> </div>
/* Both images stack, .after is clipped to show half */ .ba-slider { position: relative; aspect-ratio: 16 / 10; overflow: hidden; user-select: none; } .layer { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } /* clip-path draws the reveal line */ .layer.after { clip-path: inset(0 0 0 50%); } .handle { position: absolute; top: 0; bottom: 0; left: 50%; width: 3px; background: #fff; cursor: ew-resize; }
// Drag the handle — updates clip-path on the "after" layer const ba = document.getElementById('ba'); const after = ba.querySelector('.after'); const handle = ba.querySelector('.handle'); let dragging = false; const setPos = (x) => { const r = ba.getBoundingClientRect(); const pct = Math.max(0, Math.min(100, ((x - r.left) / r.width) * 100)); handle.style.left = pct + '%'; after.style.clipPath = `inset(0 0 0 ${pct}%)`; }; handle.addEventListener('mousedown', () => dragging = true); window.addEventListener('mouseup', () => dragging = false); window.addEventListener('mousemove', e => dragging && setPos(e.clientX));
Featured collection
A curated lineup of warm, quiet objects designed for small spaces.
<div class="split"> <div class="meta"> <h3>Luma — Site redesign</h3> <p>Short project summary...</p> <div class="tags"> <span>Web Design</span> <span>2026</span> </div> </div> <!-- Fixed-height box, long image scrolls inside --> <div class="scroll-frame"> <img src="fullpage.jpg" alt=""> </div> </div>
.split { display: grid; grid-template-columns: 360px 1fr; gap: 36px; align-items: start; } /* The magic: fixed height + overflow-y creates a live scroll */ .scroll-frame { height: 540px; overflow-y: auto; border-radius: 12px; box-shadow: 0 15px 40px rgba(0,0,0,.2); background: #fff; } .scroll-frame img { width: 100%; display: block; } @media (max-width: 800px) { .split { grid-template-columns: 1fr; } }
Luma — A warm, modern identity for a lighting brand
A complete identity and digital system — logo, typography, packaging, photography direction, and a new e-commerce site.
<section class="project-hero"> <div class="breadcrumb"> Work <span>/ Luma</span> </div> <h1>Project title</h1> <p>One-paragraph summary.</p> <div class="meta-bar"> <div class="meta-item"> <small>Client</small><span>Name</span> </div> <!-- year / services / role --> </div> </section> <section class="project-image"> <img src="hero.jpg" alt=""> </section>
.project-title { font-size: 44px; font-weight: 800; letter-spacing: -0.03em; } /* Horizontal meta strip: client / year / services / role */ .meta-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 20px; padding: 22px 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; } .meta-item small { text-transform: uppercase; letter-spacing: 0.12em; font-size: 11px; color: #888; } /* 16:9 hero image keeps layout stable on load */ .project-image img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 14px; }