08Intro grid section card
One large card and four small ones take named grid-area slots, the big title appears first, and the small cards follow on a stagger.
Best forA service overview above the fold, a brand intro
grid-areagrid-template-areasanimation-delay

<main class="stage">
<svg class="ig__defs" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><symbol id="igsparkle" viewBox="0 0 256 256"><g fill="currentColor"><path d="m194.82 151.43l-55.09 20.3l-20.3 55.09a7.92 7.92 0 0 1-14.86 0l-20.3-55.09l-55.09-20.3a7.92 7.92 0 0 1 0-14.86l55.09-20.3l20.3-55.09a7.92 7.92 0 0 1 14.86 0l20.3 55.09l55.09 20.3a7.92 7.92 0 0 1 0 14.86" opacity=".2"/><path d="M197.58 129.06L146 110l-19-51.62a15.92 15.92 0 0 0-29.88 0L78 110l-51.62 19a15.92 15.92 0 0 0 0 29.88L78 178l19 51.62a15.92 15.92 0 0 0 29.88 0L146 178l51.62-19a15.92 15.92 0 0 0 0-29.88ZM137 164.22a8 8 0 0 0-4.74 4.74L112 223.85L91.78 169a8 8 0 0 0-4.78-4.78L32.15 144L87 123.78a8 8 0 0 0 4.78-4.78L112 64.15L132.22 119a8 8 0 0 0 4.74 4.74L191.85 144ZM144 40a8 8 0 0 1 8-8h16V16a8 8 0 0 1 16 0v16h16a8 8 0 0 1 0 16h-16v16a8 8 0 0 1-16 0V48h-16a8 8 0 0 1-8-8m104 48a8 8 0 0 1-8 8h-8v8a8 8 0 0 1-16 0v-8h-8a8 8 0 0 1 0-16h8v-8a8 8 0 0 1 16 0v8h8a8 8 0 0 1 8 8"/></g></symbol><symbol id="igclock" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m0 192a88 88 0 1 1 88-88a88.1 88.1 0 0 1-88 88m64-88a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8V72a8 8 0 0 1 16 0v48h48a8 8 0 0 1 8 8"/></g></symbol><symbol id="iglockkey" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 88H48a8 8 0 0 0-8 8v112a8 8 0 0 0 8 8h160a8 8 0 0 0 8-8V96a8 8 0 0 0-8-8m-80 72a20 20 0 1 1 20-20a20 20 0 0 1-20 20" opacity=".2"/><path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-80-96a28 28 0 0 0-8 54.83V184a8 8 0 0 0 16 0v-17.17a28 28 0 0 0-8-54.83m0 40a12 12 0 1 1 12-12a12 12 0 0 1-12 12"/></g></symbol><symbol id="igsharenetwork" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 200a32 32 0 1 1-32-32a32 32 0 0 1 32 32M176 88a32 32 0 1 0-32-32a32 32 0 0 0 32 32" opacity=".2"/><path d="M176 160a39.9 39.9 0 0 0-28.62 12.09l-46.1-29.63a39.8 39.8 0 0 0 0-28.92l46.1-29.63a40 40 0 1 0-8.66-13.45l-46.1 29.63a40 40 0 1 0 0 55.82l46.1 29.63A40 40 0 1 0 176 160m0-128a24 24 0 1 1-24 24a24 24 0 0 1 24-24M64 152a24 24 0 1 1 24-24a24 24 0 0 1-24 24m112 72a24 24 0 1 1 24-24a24 24 0 0 1-24 24"/></g></symbol></svg>
<section class="ig is-demo" id="ig" aria-label="Service overview">
<article class="ig__hero">
<span class="ig__tag">New release</span>
<div class="ig__line"><h2 class="ig__title">Your team's work on one screen</h2></div>
<p class="ig__lede">Scattered files and schedules in one place.</p>
<a class="ig__link" href="#tour">Take a tour</a>
</article>
<article class="ig__cell ig__cell--a">
<svg class="ig__ic" aria-hidden="true"><use href="#igsparkle"/></svg>
<h3 class="ig__name">Quick start</h3>
<p class="ig__note">Set up in 3 minutes</p>
</article>
<article class="ig__cell ig__cell--b">
<svg class="ig__ic" aria-hidden="true"><use href="#igclock"/></svg>
<h3 class="ig__name">Auto backup</h3>
<p class="ig__note">Saved hourly</p>
</article>
<article class="ig__cell ig__cell--c">
<svg class="ig__ic" aria-hidden="true"><use href="#iglockkey"/></svg>
<h3 class="ig__name">Access control</h3>
<p class="ig__note">Access by role</p>
</article>
<article class="ig__cell ig__cell--d">
<svg class="ig__ic" aria-hidden="true"><use href="#igsharenetwork"/></svg>
<h3 class="ig__name">Team sharing</h3>
<p class="ig__note">One link is enough</p>
</article>
</section>
</main>@use "sass:math";
@use "../../../../site/static/demo-tokens" as *;
// 인트로 그리드 섹션 카드 — 자리를 먼저 그리고 카드를 그 자리에 보낸다.
// grid-template-areas 가 도면이고 grid-area 가 "이 카드는 저 칸" 이라는 이름표다.
// 도면이 글자로 보이니 좁은 화면 도면을 한 줄씩 다시 그리는 것만으로 배치가 통째로 바뀐다 —
// 카드의 순서(HTML)나 클래스는 손대지 않는다.
//
// grid-template-areas — 넓은 화면 "hero a b" / "hero c d", 좁은 화면 "hero hero" / "a b" / "c d"
// grid-area — 카드마다 자기 칸 이름 하나. 번호(1 / 3 / 2 / 4)를 세지 않아도 된다
// animation-delay — 큰 카드의 제목이 먼저, 작은 카드 넷이 시차를 두고 따라 올라온다
//
// 지연이 걸린 루프에는 animation-fill-mode: backwards 가 반드시 함께 간다 —
// 없으면 지연 구간에 카드의 작성 상태(= 도착 상태)가 0프레임에 한 번 번쩍인다.
$duration: $dur-enter;
$easing: $ease-spring;
$color: $subject-blue;
// 라벨 띠 면 — 코발트보다 짙은 남색($subject-blue-deep). 크림 글자와 5.98:1
$tag-face: $subject-blue-deep;
// 2초 루프 안에서 "올라오는" 구간의 길이. $duration 을 절반으로 줄이면 구간도 절반이 된다
$enter: math.div($duration, $dur-loop) * 100%;
$rise: 14px;
// 흰 무대 위에서 카드 경계를 세우는 실선 (토큰 밖 실측값 — 잉크 16%)
$hair: rgba($subject-ink, .16);
@include stage($stage-paper);
.ig__defs { position: absolute; width: 0; height: 0; }
.ig {
box-sizing: border-box; width: min(100%, 432px);
display: grid;
grid-template-columns: 1.25fr 1fr 1fr;
grid-template-areas:
"hero a b"
"hero c d";
// 두 줄을 같은 높이로 못박는다 — 내용이 짧은 줄이 얇아지면 큰 카드 옆이 어긋나 보인다
grid-template-rows: repeat(2, 1fr);
gap: $sp-2;
}
// 칸 이름표 — 카드가 HTML 어디에 있든 자기 칸으로 간다
.ig__hero { grid-area: hero; }
.ig__cell--a { grid-area: a; }
.ig__cell--b { grid-area: b; }
.ig__cell--c { grid-area: c; }
.ig__cell--d { grid-area: d; }
.ig__hero {
box-sizing: border-box;
display: flex; flex-direction: column; gap: $sp-2;
padding: $sp-3;
border-radius: $r-card;
background: $subject-ink;
}
.ig__tag {
align-self: start;
padding: $sp-1 $sp-2;
border-radius: $r-pill;
background: $tag-face; color: $subject-cream;
font-weight: 700; font-size: clamp(9px, 2.4vw, 10px); line-height: 1.2;
}
// 제목 줄은 잘라 두고 그 안에서 올라온다 — 카드 밖으로 새지 않는다
.ig__line { overflow: hidden; }
.ig__title {
margin: 0;
color: $subject-cream;
font-weight: 800; font-size: clamp(14px, 4.2vw, 19px); line-height: 1.2;
}
.ig__lede {
margin: 0;
color: $subject-cream;
font-weight: 500; font-size: clamp(9px, 2.6vw, 11px); line-height: 1.4;
}
// 섹션 전체가 아니라 링크가 어디로 가는 길인지 말한다 (WCAG 2.4.4 link purpose)
.ig__link {
margin-top: auto; align-self: start;
color: $subject-cream;
font-weight: 700; font-size: clamp(9px, 2.6vw, 11px); line-height: 1.3;
text-decoration: underline; text-underline-offset: 2px;
}
.ig__link:focus-visible { outline: 2px solid $subject-cream; outline-offset: 2px; border-radius: $r-xs; }
.ig__cell {
box-sizing: border-box;
display: flex; flex-direction: column; gap: $sp-1;
padding: $sp-2;
border: 1px solid $hair; border-radius: $r-card;
background: $subject-cream;
}
.ig__ic { width: 18px; height: 18px; color: $color; }
.ig__name {
margin: 0;
color: $subject-ink;
font-weight: 700; font-size: clamp(10px, 3vw, 12px); line-height: 1.25;
}
.ig__note {
margin: 0;
color: $subject-ink;
font-weight: 500; font-size: clamp(9px, 2.4vw, 10px); line-height: 1.3;
}
// ── 격자 미리보기 루프 (2초). 큰 카드 제목이 먼저, 작은 카드가 a → b → c → d 순으로
.ig.is-demo .ig__title,
.ig.is-demo .ig__cell {
animation-duration: $dur-loop;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ig.is-demo .ig__title { animation-name: igTitle; }
.ig.is-demo .ig__cell { animation-name: igCell; }
.ig.is-demo .ig__cell--a { animation-delay: .14s; }
.ig.is-demo .ig__cell--b { animation-delay: .24s; }
.ig.is-demo .ig__cell--c { animation-delay: .34s; }
.ig.is-demo .ig__cell--d { animation-delay: .44s; }
// 복귀(다시 내려감)는 88% 안에 끝난다 — 24프레임의 마지막 샘플이 95.8% 지점이다
@keyframes igTitle {
0% { transform: translateY(120%); }
#{$enter} { transform: translateY(0); }
74% { transform: translateY(0); }
88%, 100% { transform: translateY(120%); }
}
@keyframes igCell {
0% { transform: translateY($rise) scale(.94); }
#{$enter} { transform: translateY(0) scale(1); }
74% { transform: translateY(0) scale(1); }
88%, 100% { transform: translateY($rise) scale(.94); }
}
// ── 좁은 화면: 도면만 다시 그린다. 카드도 클래스도 그대로다
// 320×200 은 세로가 174px(여백 12.8px x 2)뿐이라 도면을 두 단으로 압축한다 —
// 히어로는 가로로 길게, 작은 칸 넷은 2x2 로. 글자는 clamp 하한이 이미 폰 크기다
@media (max-width: 340px) {
.ig {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-areas:
"hero hero hero hero"
"a a b b"
"c c d d";
grid-template-rows: auto auto auto;
gap: $sp-1;
}
.ig__hero { gap: 0; padding: $sp-1; }
.ig__cell { gap: 0; padding: $sp-1; }
.ig__ic { width: 14px; height: 14px; }
}
@media (prefers-reduced-motion: reduce) {
.ig.is-demo .ig__title,
.ig.is-demo .ig__cell {
animation: none !important;
transform: none !important;
}
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ig__defs {
position: absolute;
width: 0;
height: 0;
}
.ig {
box-sizing: border-box;
width: min(100%, 432px);
display: grid;
grid-template-columns: 1.25fr 1fr 1fr;
grid-template-areas: "hero a b" "hero c d";
grid-template-rows: repeat(2, 1fr);
gap: 8px;
}
.ig__hero {
grid-area: hero;
}
.ig__cell--a {
grid-area: a;
}
.ig__cell--b {
grid-area: b;
}
.ig__cell--c {
grid-area: c;
}
.ig__cell--d {
grid-area: d;
}
.ig__hero {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
border-radius: 16px;
background: #17141a;
}
.ig__tag {
align-self: start;
padding: 4px 8px;
border-radius: 999px;
background: #0a4fe8;
color: #fff7e6;
font-weight: 700;
font-size: clamp(9px, 2.4vw, 10px);
line-height: 1.2;
}
.ig__line {
overflow: hidden;
}
.ig__title {
margin: 0;
color: #fff7e6;
font-weight: 800;
font-size: clamp(14px, 4.2vw, 19px);
line-height: 1.2;
}
.ig__lede {
margin: 0;
color: #fff7e6;
font-weight: 500;
font-size: clamp(9px, 2.6vw, 11px);
line-height: 1.4;
}
.ig__link {
margin-top: auto;
align-self: start;
color: #fff7e6;
font-weight: 700;
font-size: clamp(9px, 2.6vw, 11px);
line-height: 1.3;
text-decoration: underline;
text-underline-offset: 2px;
}
.ig__link:focus-visible {
outline: 2px solid #fff7e6;
outline-offset: 2px;
border-radius: 4px;
}
.ig__cell {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px;
border: 1px solid rgba(23, 20, 26, 0.16);
border-radius: 16px;
background: #fff7e6;
}
.ig__ic {
width: 18px;
height: 18px;
color: #2f6df6;
}
.ig__name {
margin: 0;
color: #17141a;
font-weight: 700;
font-size: clamp(10px, 3vw, 12px);
line-height: 1.25;
}
.ig__note {
margin: 0;
color: #17141a;
font-weight: 500;
font-size: clamp(9px, 2.4vw, 10px);
line-height: 1.3;
}
.ig.is-demo .ig__title,
.ig.is-demo .ig__cell {
animation-duration: 2s;
animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
animation-iteration-count: infinite;
animation-fill-mode: backwards;
}
.ig.is-demo .ig__title {
animation-name: igTitle;
}
.ig.is-demo .ig__cell {
animation-name: igCell;
}
.ig.is-demo .ig__cell--a {
animation-delay: 0.14s;
}
.ig.is-demo .ig__cell--b {
animation-delay: 0.24s;
}
.ig.is-demo .ig__cell--c {
animation-delay: 0.34s;
}
.ig.is-demo .ig__cell--d {
animation-delay: 0.44s;
}
@keyframes igTitle {
0% {
transform: translateY(120%);
}
30% {
transform: translateY(0);
}
74% {
transform: translateY(0);
}
88%, 100% {
transform: translateY(120%);
}
}
@keyframes igCell {
0% {
transform: translateY(14px) scale(0.94);
}
30% {
transform: translateY(0) scale(1);
}
74% {
transform: translateY(0) scale(1);
}
88%, 100% {
transform: translateY(14px) scale(0.94);
}
}
@media (max-width: 340px) {
.ig {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-areas: "hero hero hero hero" "a a b b" "c c d d";
grid-template-rows: auto auto auto;
gap: 4px;
}
.ig__hero {
gap: 0;
padding: 4px;
}
.ig__cell {
gap: 0;
padding: 4px;
}
.ig__ic {
width: 14px;
height: 14px;
}
}
@media (prefers-reduced-motion: reduce) {
.ig.is-demo .ig__title,
.ig.is-demo .ig__cell {
animation: none !important;
transform: none !important;
}
}// 사람이 만지면 미리보기 루프를 뗀다 — 뗀 뒤에도 다섯 칸은 grid-area 가 정한 자리에 그대로 있는다
var introGrid = document.getElementById('ig');
['pointerdown', 'focusin'].forEach(function (evt) {
introGrid.addEventListener(evt, function () { introGrid.classList.remove('is-demo'); });
});// IntroGridSection — vanilla 판(run/081/demo/08-content-card-intro-grid/index.html) 의 React 포트.
// grid-template-areas 가 도면이고 grid-area 가 카드마다 붙는 칸 이름표다 — 좁은 화면에서는
// 도면만 다시 그린다. 큰 카드 제목이 먼저 뜨고 작은 카드 넷이 animation-delay 시차로 따라 올라온다.
// @keyframes 본문은 vanilla 와 같은 글자다 (값을 다시 타이핑하지 않는다).
import { useState } from "react";
import type { CSSProperties } from "react";
import styles from "./IntroGridSection.module.scss";
export type IntroGridSectionProps = {
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & {
"--duration"?: string;
"--easing"?: string;
"--color"?: string;
};
type Cell = { area: string; icon: string; name: string; note: string };
const CELLS: Cell[] = [
{ area: "a", icon: "sparkle", name: "빠른 시작", note: "설정 3분" },
{ area: "b", icon: "clock", name: "자동 백업", note: "매시 저장" },
{ area: "c", icon: "lock", name: "권한 관리", note: "역할별 접근" },
{ area: "d", icon: "share", name: "팀 공유", note: "링크 한 줄" },
];
function DuotoneIcon({ id }: { id: string }) {
return (
<svg className={styles[id]} aria-hidden="true">
<use href={"#ig" + id} />
</svg>
);
}
export default function IntroGridSection({
duration = 600,
easing = "cubic-bezier(.2, .8, .2, 1)",
color = "#2f6df6",
}: IntroGridSectionProps) {
const [preview, setPreview] = useState(true);
const vars: Vars = {
"--duration": duration + "ms",
"--easing": easing,
"--color": color,
};
const stop = () => setPreview(false);
return (
<main className={styles.stage}>
<svg className={styles.defs} aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<symbol id="igsparkle" viewBox="0 0 256 256"><g fill="currentColor"><path d="m194.82 151.43l-55.09 20.3l-20.3 55.09a7.92 7.92 0 0 1-14.86 0l-20.3-55.09l-55.09-20.3a7.92 7.92 0 0 1 0-14.86l55.09-20.3l20.3-55.09a7.92 7.92 0 0 1 14.86 0l20.3 55.09l55.09 20.3a7.92 7.92 0 0 1 0 14.86" opacity=".2"/><path d="M197.58 129.06L146 110l-19-51.62a15.92 15.92 0 0 0-29.88 0L78 110l-51.62 19a15.92 15.92 0 0 0 0 29.88L78 178l19 51.62a15.92 15.92 0 0 0 29.88 0L146 178l51.62-19a15.92 15.92 0 0 0 0-29.88ZM137 164.22a8 8 0 0 0-4.74 4.74L112 223.85L91.78 169a8 8 0 0 0-4.78-4.78L32.15 144L87 123.78a8 8 0 0 0 4.78-4.78L112 64.15L132.22 119a8 8 0 0 0 4.74 4.74L191.85 144ZM144 40a8 8 0 0 1 8-8h16V16a8 8 0 0 1 16 0v16h16a8 8 0 0 1 0 16h-16v16a8 8 0 0 1-16 0V48h-16a8 8 0 0 1-8-8m104 48a8 8 0 0 1-8 8h-8v8a8 8 0 0 1-16 0v-8h-8a8 8 0 0 1 0-16h8v-8a8 8 0 0 1 16 0v8h8a8 8 0 0 1 8 8"/></g></symbol>
<symbol id="igclock" viewBox="0 0 256 256"><g fill="currentColor"><path d="M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96" opacity=".2"/><path d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m0 192a88 88 0 1 1 88-88a88.1 88.1 0 0 1-88 88m64-88a8 8 0 0 1-8 8h-56a8 8 0 0 1-8-8V72a8 8 0 0 1 16 0v48h48a8 8 0 0 1 8 8"/></g></symbol>
<symbol id="iglock" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 88H48a8 8 0 0 0-8 8v112a8 8 0 0 0 8 8h160a8 8 0 0 0 8-8V96a8 8 0 0 0-8-8m-80 72a20 20 0 1 1 20-20a20 20 0 0 1-20 20" opacity=".2"/><path d="M208 80h-32V56a48 48 0 0 0-96 0v24H48a16 16 0 0 0-16 16v112a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16M96 56a32 32 0 0 1 64 0v24H96Zm112 152H48V96h160zm-80-96a28 28 0 0 0-8 54.83V184a8 8 0 0 0 16 0v-17.17a28 28 0 0 0-8-54.83m0 40a12 12 0 1 1 12-12a12 12 0 0 1-12 12"/></g></symbol>
<symbol id="igshare" viewBox="0 0 256 256"><g fill="currentColor"><path d="M208 200a32 32 0 1 1-32-32a32 32 0 0 1 32 32M176 88a32 32 0 1 0-32-32a32 32 0 0 0 32 32" opacity=".2"/><path d="M176 160a39.9 39.9 0 0 0-28.62 12.09l-46.1-29.63a39.8 39.8 0 0 0 0-28.92l46.1-29.63a40 40 0 1 0-8.66-13.45l-46.1 29.63a40 40 0 1 0 0 55.82l46.1 29.63A40 40 0 1 0 176 160m0-128a24 24 0 1 1-24 24a24 24 0 0 1 24-24M64 152a24 24 0 1 1 24-24a24 24 0 0 1-24 24m112 72a24 24 0 1 1 24-24a24 24 0 0 1-24 24"/></g></symbol>
</svg>
<section
className={styles.ig + (preview ? " " + styles.isDemo : "")}
style={vars}
aria-label="서비스 소개"
onPointerDown={stop}
onFocus={stop}
>
<article className={styles.hero}>
<span className={styles.tag}>새 버전</span>
<div className={styles.line}>
<h2 className={styles.title}>팀의 작업을 한 화면에</h2>
</div>
<p className={styles.lede}>흩어진 파일과 일정을 한 곳에서 봅니다.</p>
<a className={styles.link} href="#tour">서비스 둘러보기</a>
</article>
{CELLS.map((cell) => (
<article className={styles.cell + " " + styles["cell" + cell.area.toUpperCase()]} key={cell.area}>
<DuotoneIcon id={cell.icon} />
<h3 className={styles.name}>{cell.name}</h3>
<p className={styles.note}>{cell.note}</p>
</article>
))}
</section>
</main>
);
}08 Intro grid section card — Measured area 18.81% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · CSS Card Design Examples — 9 Content Card Types
The post this demo lives in
CSS Card Design Examples — 9 Content Card TypesCSS card design is the craft of building containers that lay out content. The rule across all nine: put the click on the See all demos in this post
If you like this effect - similar demos
Landing sections02 Feature card grid — cards revealing in order from a visible-ratio valueHero Section Design Examples: 9 Patterns
Landing sections09 Team grid expand9 Profile Card Design Styles, Copy-Paste Ready
Landing sections06 Included features light up row by rowPricing Table Design: 9 Live Interactions