01Hero bento 2×2
Hero không còn là một khối lớn duy nhất mà tách thành nhiều thẻ cỡ khác nhau, tạo ngay bố cục bento cho màn hình đầu.
Dùng choMàn hình đầu trang landing SaaS, trang bìa portfolio
2x2radius 24호버 확대

<main class="stage">
<section class="hero">
<div class="hero__tile hero__tile--main is-demo">
<p class="hero__eyebrow">Nectar</p>
<h2 class="hero__title">Việc và đội nhóm trên một màn hình</h2>
<button class="hero__cta" type="button">Bắt đầu miễn phí</button>
</div>
<div class="hero__tile hero__tile--stat is-demo">
<span class="hero__dot" aria-hidden="true"></span>
<span class="hero__stat-label">Đồng bộ thời gian thực</span>
</div>
<div class="hero__tile hero__tile--visual is-demo" aria-hidden="true"></div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 카드가 뜨고 지는 한 주기
$easing: $ease-spring; // 등장 곡선
$color: $subject-blue; // 강조 색
@include stage($stage-ink);
.hero {
width: clamp(240px, 82vw, 380px);
height: clamp(160px, 46vw, 210px);
display: grid;
grid-template-columns: 1.4fr 1fr;
grid-template-rows: 1fr 1fr;
gap: clamp(8px, 2.4vw, 12px);
}
.hero__tile {
border-radius: $r-card;
background: $subject-cream;
box-shadow: $shadow-raised;
animation: heroIn $duration $easing infinite;
}
.hero__tile--main {
grid-row: 1 / 3;
display: flex;
flex-direction: column;
justify-content: center;
gap: clamp(6px, 1.6vw, 10px);
padding: clamp(12px, 3.6vw, 18px);
animation-delay: 0s;
}
.hero__tile--stat {
display: flex;
align-items: center;
gap: 8px;
padding: clamp(10px, 3vw, 14px);
animation-delay: -0.5s;
}
.hero__tile--visual {
background: $color;
opacity: .16;
animation-name: heroInFaint;
animation-delay: -1s;
}
.hero__eyebrow {
margin: 0;
color: $subject-blue-deep; // 밝은 면 위 파란 글자 — AA 4.5 (blog-design 글자 대비)
font-weight: 700; font-size: clamp(10px, 2.6vw, 12px); line-height: 1;
letter-spacing: .04em;
text-transform: uppercase;
}
.hero__title {
margin: 0;
color: $subject-ink;
font-weight: 700; font-size: clamp(15px, 4vw, 19px); line-height: 1.25;
}
.hero__cta {
align-self: flex-start;
border: 0;
border-radius: $r-pill;
background: $subject-ink;
color: $subject-cream;
padding: clamp(7px, 2vw, 9px) clamp(14px, 3.6vw, 18px);
font-weight: 600; font-size: clamp(11px, 2.8vw, 13px); line-height: 1;
}
.hero__dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: $color;
flex: none;
}
.hero__stat-label {
color: $subject-ink;
font-weight: 600; font-size: clamp(10.5px, 2.6vw, 12.5px); line-height: 1.3;
}
// 데모: 세 칸이 살짝 다른 박자로 떠올랐다 가라앉는다 (WAAPI 시계가 잡을 수 있게 keyframes)
@keyframes heroIn {
0% { opacity: 0; transform: translateY(10px) scale(.97); }
22% { opacity: 1; transform: translateY(0) scale(1); }
78% { opacity: 1; transform: translateY(0) scale(1); }
100% { opacity: 0; transform: translateY(10px) scale(.97); }
}
@keyframes heroInFaint {
0% { opacity: 0; transform: translateY(10px) scale(.97); }
22% { opacity: .16; transform: translateY(0) scale(1); }
78% { opacity: .16; transform: translateY(0) scale(1); }
100% { opacity: 0; transform: translateY(10px) scale(.97); }
}
@media (prefers-reduced-motion: reduce) {
.hero__tile {
animation: none !important;
opacity: 1;
transform: none;
}
.hero__tile--visual { opacity: .16; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.hero {
width: clamp(240px, 82vw, 380px);
height: clamp(160px, 46vw, 210px);
display: grid;
grid-template-columns: 1.4fr 1fr;
grid-template-rows: 1fr 1fr;
gap: clamp(8px, 2.4vw, 12px);
}
.hero__tile {
border-radius: 16px;
background: #fff7e6;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
animation: heroIn 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.hero__tile--main {
grid-row: 1/3;
display: flex;
flex-direction: column;
justify-content: center;
gap: clamp(6px, 1.6vw, 10px);
padding: clamp(12px, 3.6vw, 18px);
animation-delay: 0s;
}
.hero__tile--stat {
display: flex;
align-items: center;
gap: 8px;
padding: clamp(10px, 3vw, 14px);
animation-delay: -0.5s;
}
.hero__tile--visual {
background: #2f6df6;
opacity: 0.16;
animation-name: heroInFaint;
animation-delay: -1s;
}
.hero__eyebrow {
margin: 0;
color: #0a4fe8;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.hero__title {
margin: 0;
color: #17141a;
font-weight: 700;
font-size: clamp(15px, 4vw, 19px);
line-height: 1.25;
}
.hero__cta {
align-self: flex-start;
border: 0;
border-radius: 999px;
background: #17141a;
color: #fff7e6;
padding: clamp(7px, 2vw, 9px) clamp(14px, 3.6vw, 18px);
font-weight: 600;
font-size: clamp(11px, 2.8vw, 13px);
line-height: 1;
}
.hero__dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: #2f6df6;
flex: none;
}
.hero__stat-label {
color: #17141a;
font-weight: 600;
font-size: clamp(10.5px, 2.6vw, 12.5px);
line-height: 1.3;
}
@keyframes heroIn {
0% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
22% {
opacity: 1;
transform: translateY(0) scale(1);
}
78% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
}
@keyframes heroInFaint {
0% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
22% {
opacity: 0.16;
transform: translateY(0) scale(1);
}
78% {
opacity: 0.16;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(10px) scale(0.97);
}
}
@media (prefers-reduced-motion: reduce) {
.hero__tile {
animation: none !important;
opacity: 1;
transform: none;
}
.hero__tile--visual {
opacity: 0.16;
}
}import type { CSSProperties } from "react";
import styles from "./BentoHero.module.scss";
export type BentoHeroProps = {
eyebrow?: string;
title?: string;
cta?: string;
stat?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function BentoHero({
eyebrow = "Nectar",
title = "Your work and team, one screen",
cta = "Start for free",
stat = "Real-time sync",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
}: BentoHeroProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.hero} style={vars}>
<div className={`${styles.tile} ${styles.tileMain}`}>
<p className={styles.eyebrow}>{eyebrow}</p>
<h2 className={styles.title}>{title}</h2>
<button type="button" className={styles.cta}>{cta}</button>
</div>
<div className={`${styles.tile} ${styles.tileStat}`}>
<span className={styles.dot} aria-hidden="true" />
<span className={styles.statLabel}>{stat}</span>
</div>
<div className={`${styles.tile} ${styles.tileVisual}`} aria-hidden="true" />
</section>
);
}01 Hero bento 2×2 — Diện tích đo được 53.267% · khung hình chuyển động 16/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · Bento grid design — 9 phần landing đang thịnh
Bài viết chứa demo này
Bento grid design — 9 phần landing đang thịnhBento grid design là cách xếp các ô vuông to nhỏ khác nhau vào một grid như hộp cơm Nhật, đang thịnh trên Pinterest nhờ Xem tất cả demo trong bài này


