Media Bento Grid
A six-tile capability bento: one wide image across two columns, one tall image across two rows, then two small and two wide tiles. Every tile is a full-bleed image with a bottom fade; on hover the image slowly zooms and the arrow after the title slides forward.
Taken from the page template Ranzhen AI Video Studio Home. Use it on its own, or combine it with the other components from the same template to rebuild the full page.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Media Bento Grid to your project in your colors.
Where to use it
Sections that present information in groups: features, case studies and work, team members, article lists.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
Code
Source for Media Bento Grid. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-rzb"><div class="x-rzb__in" style="width:1000px;height:640px"><a class="x-rzb__c b1" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/m-product.webp)"></i><b>4K quality visuals <span>›</span></b><small>Realistic light, crisp detail, and text that renders accurately.</small></a><a class="x-rzb__c b2" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/h2.webp)"></i><b>Reusable assets <span>›</span></b><small>The same product, scene and style stay consistent in every video.</small></a><a class="x-rzb__c b3" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/c2.webp)"></i><b>Cinematic camera moves <span>›</span></b><small>Real motion, rhythm and smooth camera moves.</small></a><a class="x-rzb__c b4" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/h6.webp)"></i><b>Lifelike characters <span>›</span></b><small>Natural expressions, voice and demeanor.</small></a><a class="x-rzb__c b5" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/h8.webp)"></i><b>Music, dialogue and sound effects <span>›</span></b><small>Shape every scene with sound.</small></a><a class="x-rzb__c b6" href="#"><i style="background-image:url(https://8pt.dev/assets/tpl/ranzhen/s4.webp)"></i><b>Endless styles <span>›</span></b><small>From photoreal to animation, there’s a look for you.</small></a></div></div>
.x-rzb{position:relative;flex:none}
.x-rzb__in{position:absolute;left:0;top:0;transform-origin:0 0;display:grid;grid-template-columns:repeat(6,1fr);grid-template-rows:repeat(3,1fr);gap:14px;font-family:var(--d-font,system-ui,sans-serif)}
.x-rzb__c{position:relative;display:flex;flex-direction:column;justify-content:flex-end;padding:20px;border-radius:18px;overflow:hidden;isolation:isolate;background:#111;color:#fff;text-decoration:none}
.x-rzb__c>i{position:absolute;inset:0;z-index:-2;background:center/cover no-repeat;transition:transform .8s cubic-bezier(.16,1,.3,1)}
.x-rzb__c::after{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,transparent 35%,rgba(0,0,0,.85))}
.x-rzb__c b{font-size:18px;font-weight:700}.x-rzb__c b span{display:inline-block;color:#FF8A3D;transition:transform .25s cubic-bezier(.16,1,.3,1)}
.x-rzb__c small{margin-top:4px;font-size:13px;color:#cfc9cc}
.x-rzb__c:hover>i,.x-rzb__c:focus-visible>i{transform:scale(1.06)}.x-rzb__c:hover b span,.x-rzb__c:focus-visible b span{transform:translateX(4px)}
.x-rzb__c:focus-visible{outline:2px solid #FF8A3D;outline-offset:2px}
.x-rzb__c.b1{grid-column:span 4}.x-rzb__c.b2{grid-column:span 2;grid-row:span 2}.x-rzb__c.b3,.x-rzb__c.b4{grid-column:span 2}.x-rzb__c.b5,.x-rzb__c.b6{grid-column:span 3}
@media (prefers-reduced-motion:reduce){.x-rzb__c>i,.x-rzb__c b span{transition:none}}const init = (root) => {
const fit = (box, W, H) => {
const inner = box.firstElementChild;
const run = () => {
const cs = getComputedStyle(root);
const aw = root.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight);
const ah = root.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom);
if (aw <= 0 || ah <= 0) return;
const s = Math.max(0.2, Math.min(aw / W, ah / H, 1));
box.style.width = W * s + 'px'; box.style.height = H * s + 'px'; inner.style.transform = `scale(${s})`;
};
const ro = new ResizeObserver(run); ro.observe(root); run();
…