안녕하세요, 디자인 엔지니어 올리브예요. 멋진 사이트 분석 4편이에요.
Nina Park의 첫 화면 시간차 등장과 서비스 목록 호버는 CSS만으로 따라 만들 수 있어요. 첫 화면은 요소마다 transition-delay를 0.1~0.2초씩 다르게 줘서 차례로 올라오게 하고, 목록은 grid-template-rows: 0fr → 1fr로 세부 항목을 펼쳐요. 아래 데모는 이 블로그 색으로 바꿔서 직접 만든 거예요. 다시 보기를 누르거나 마우스를 올려 보세요.
3편에서는 Nina Park의 흑백 화면, 소문자 제목, 글꼴 세 가지를 봤어요. 그중 제 눈에 가장 오래 남은 건 두 가지 움직임이었어요. 원본이 어떻게 움직이는지 프레임 단위로 재 보고, 같은 속도로 만들어 봤어요.
| 구분 | 원본에서 본 것 | 만드는 법 |
|---|---|---|
| 첫 화면 등장 | 배경 → 제목 → 태그 → 사진 → 소개 문장 순서로 하나씩 올라와요 | transition-delay 시간차 |
| 서비스 목록 호버 | 올린 줄만 펼쳐지며 바탕·글자색이 바뀌고 세부 항목이 나와요 | grid-template-rows 0fr → 1fr |
1. 첫 화면은 어떤 순서로 나타나나요?
원본은 어떻게 움직이나요?
사이트에 처음 들어가거나 새로고침하면, 메뉴만 먼저 보이고 나머지는 정해진 순서대로 하나씩 나타나요. 배경이 시작한 때를 0초로 놓고 재 보니 이랬어요.
| 순서 | 요소 | 시작 | 길이 | 움직임 |
|---|---|---|---|---|
| 1 | 하프톤 배경 | 0초 | 약 0.9초 | 1.2배 → 1배로 줄어들며 나타나요 |
| 2 | 제목 첫 줄 "nina park" | 0.7초 | 약 0.8초 | 10px 아래에서 올라오며 나타나요 (0.98배 → 1배) |
| 3 | 제목 둘째 줄 "visual designer" | 0.9초 | 약 0.8초 | 첫 줄과 같아요 |
| 4 | 태그 5개 | 0.9초부터 0.1초 간격 | 약 0.3초 | 하나씩 10px 올라와요 |
| 5 | 사진·예약 문구·버튼 묶음 | 1.1초 | 약 1초 | 80px 아래에서 크게 올라와요 |
| 6 | 소개 문장 세 줄 | 1.6초부터 0.1초 간격 | 약 0.8초 | 한 줄씩 10px 올라와요 |
모든 움직임이 처음에 빠르고 끝에서 천천히 멈추는 감속 곡선이에요. 재미있는 건 제목이 글자 단위로 쪼개져 있는데도, 한 줄 안의 글자들은 모두 같은 순간에 움직였다는 점이에요. 그래서 따라 만들 때는 글자를 쪼갤 필요 없이 줄 단위로 움직여도 똑같이 보여요.

따라 만들면?
"다시 보기"를 눌러 보세요. 순서와 시작 시간, 길이는 위 표에서 잰 값 그대로예요.
<style>
.stage { position: relative; display: flex; flex-direction: column; height: 100vh; padding: 14px 18px 18px; overflow: hidden; background: #F4F6F4; }
/* 배경: 원본의 하프톤 사진 대신 연초록 원과 줄무늬 */
.bg {
position: absolute; inset: 0;
background:
radial-gradient(circle at 78% 32%, #E5F2E7 0 20%, transparent 20.5%),
radial-gradient(circle at 18% 88%, #E5F2E7 0 14%, transparent 14.5%),
repeating-linear-gradient(-45deg, transparent 0 7px, rgba(0, 163, 15, .05) 7px 8px);
}
.stage > :not(.bg) { position: relative; }
.nav { display: flex; justify-content: space-between; align-items: center; font-size: 11px; letter-spacing: .06em; color: #6B7F6E; }
.replay { padding: 4px 10px; border: 0; border-radius: 3px; background: #0A1F0E; color: #fff; font: inherit; cursor: pointer; }
.title { margin: 26px 0 0; font-size: 56px; font-weight: 400; line-height: 1.02; letter-spacing: -0.01em; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; }
.tags { display: flex; flex-wrap: wrap; gap: 4px; max-width: 190px; }
.tag { padding: 2px 8px; border: 1px solid #0A1F0E; border-radius: 999px; font-size: 10px; letter-spacing: .04em; }
.bottom { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; margin-top: auto; }
.intro { margin: 0; font-size: 17px; line-height: 1.4; }
.intro span { display: block; }
.card { width: 168px; flex: none; font-size: 10px; letter-spacing: .04em; }
.photo { height: 96px; margin: 4px 0; background: linear-gradient(135deg, #00A30F, #007A0B); }
.cta { display: inline-block; padding: 5px 8px; border-radius: 3px; background: #0A1F0E; color: #fff; }
/* 시간차 등장: 요소마다 --d(시작), --t(길이), --y(올라오는 거리)만 다르다 */
.bg { opacity: 0; transform: scale(1.2); }
.rise { display: inline-block; opacity: 0; transform: translateY(var(--y, 10px)) scale(var(--s, .98)); }
.stage.in .bg {
opacity: 1; transform: none;
transition: opacity .9s cubic-bezier(.45, 0, .25, 1), transform .9s cubic-bezier(.45, 0, .25, 1);
}
.stage.in .rise {
opacity: 1; transform: none;
transition: opacity var(--t, .8s) cubic-bezier(.25, 1, .5, 1) var(--d), transform var(--t, .8s) cubic-bezier(.25, 1, .5, 1) var(--d);
}
.tag { --t: .3s; --s: 1; }
.card { --t: 1s; --y: 80px; }
@media (max-width: 420px) {
.title { margin-top: 20px; font-size: 34px; }
.tags { max-width: none; }
.bottom { flex-direction: column; align-items: flex-start; gap: 12px; }
.intro { font-size: 14px; }
.card { width: 150px; }
.photo { height: 64px; }
}
</style>
<div class="stage">
<div class="bg"></div>
<div class="nav"><span>OLIVEVES</span><button type="button" class="replay">다시 보기</button></div>
<h1 class="title">
<span class="rise" style="--d: .7s; display: block;">olive</span>
<span class="row">
<span class="rise" style="--d: .9s;">design engineer</span>
<span class="tags">
<span class="tag rise" style="--d: .9s;">WEB DESIGN</span>
<span class="tag rise" style="--d: 1s;">FRAMER</span>
<span class="tag rise" style="--d: 1.1s;">SEO</span>
<span class="tag rise" style="--d: 1.2s;">AI TOOLS</span>
<span class="tag rise" style="--d: 1.3s;">FIGMA</span>
</span>
</span>
</h1>
<div class="bottom">
<p class="intro">
<span class="rise" style="--d: 1.6s;">웹디자인과 AI 도구를</span>
<span class="rise" style="--d: 1.7s;">직접 써 보고,</span>
<span class="rise" style="--d: 1.8s;">겪은 일을 기록해요.</span>
</p>
<div class="card rise" style="--d: 1.1s;">
NEW POSTS EVERY WEEK
<div class="photo"></div>
<span class="cta">READ THE BLOG ↗</span>
</div>
</div>
</div>
<script>
const stage = document.querySelector('.stage');
function play() {
stage.classList.remove('in');
stage.offsetWidth; // 처음 상태(숨김)를 한 번 그리게 한다
stage.classList.add('in');
}
// 화면에 들어오면 한 번 재생
new IntersectionObserver(([e], io) => {
if (e.isIntersecting) { play(); io.disconnect(); }
}, { threshold: 0.4 }).observe(stage);
document.querySelector('.replay').addEventListener('click', play);
</script>
- 순서는
transition-delay하나로: 요소마다--d만 다르게 주고, 같은 클래스(.in)를 붙이는 순간 각자 정해진 시간에 출발해요. - 거리와 길이로 무게 주기: 태그는 10px·0.3초로 가볍게, 사진 묶음은 80px·1초로 묵직하게 올라와요. 같은 등장이라도 요소 크기에 맞춰 달라요.
- 배경은 반대로 줄어들기: 다른 요소가 아래에서 올라오는 동안 배경만 1.2배에서 1배로 줄어들어서, 화면 전체가 '자리 잡는' 느낌이 나요.
2. 서비스 목록은 어떻게 펼쳐지나요?
원본은 어떻게 움직이나요?
마우스를 올리면 그 줄의 높이가 76px에서 260px로 0.2초 만에 커져요. 처음에 빨리 커지고 끝에서 천천히 멈추는 감속(ease-out) 곡선이에요. 같은 0.2초 동안 이런 일이 함께 일어나요.
- 바탕: 흰색 → 아주 옅은 회색
#F1F1F1 - 번호: 거의 안 보이는
#F1F1F1→ 회색#939393 - 서비스 이름: 회색
#777→ 검정#111 - 줄 안쪽 여백이 왼쪽 0 → 20px로 늘어서 글자가 오른쪽으로 살짝 밀려요
- 아래에 8px 회색 네모 점이 붙은 세부 항목 4개(18px)가 나타나요
마우스를 떼면 같은 0.2초 동안 다시 접혀요. 다른 줄로 옮기면 앞 줄이 접히는 동안 새 줄이 펼쳐져서, 목록이 물결처럼 움직여요.

따라 만들면?
목록에 마우스를 올려 보세요. 모바일에서는 줄을 눌러 보세요. 속도와 곡선은 원본에서 잰 값(0.2초, 감속)에 맞췄어요.
<style>
.stage { display: grid; justify-items: center; align-content: start; height: 100vh; padding: 28px 20px; background: #F4F6F4; }
.list { width: min(520px, 100%); margin: 0; padding: 0; list-style: none; }
/* 줄 하나 = 제목 줄 + 접혀 있는 세부 항목 (원본: 0.2초 감속) */
.item {
--ease: cubic-bezier(.5, 1, .89, 1);
display: grid; grid-template-rows: auto 0fr;
padding: 4px 20px 4px 0;
border-bottom: 1px solid #E6E9E6; background: #fff;
cursor: default; outline: none;
transition: grid-template-rows .2s var(--ease), padding .2s var(--ease), background-color .2s var(--ease);
}
.item:hover, .item:focus { grid-template-rows: auto 1fr; padding: 14px 20px 26px 16px; background: #E5F2E7; }
.title { display: flex; gap: 12px; font-size: 34px; line-height: 1.25; letter-spacing: -0.01em; }
.num { width: 26px; color: #E6E9E6; transition: color .2s var(--ease); }
.name { color: #6B7F6E; transition: color .2s var(--ease); }
.item:hover .num, .item:focus .num { color: #00A30F; }
.item:hover .name, .item:focus .name { color: #0A1F0E; }
/* 0fr일 때 높이 0이 되도록 min-height: 0 + overflow: hidden */
.sub { min-height: 0; overflow: hidden; }
.sub ul { margin: 0; padding: 8px 0 0 38px; list-style: none; display: grid; gap: 6px; }
.sub li { display: flex; align-items: center; gap: 10px; font-size: 15px; color: #6B7F6E; }
.sub li::before { content: ""; width: 7px; height: 7px; background: #00A30F; }
@media (max-width: 420px) {
.title { font-size: 22px; gap: 8px; }
.num { width: 18px; }
.sub ul { padding-left: 26px; }
.sub li { font-size: 13px; }
}
</style>
<div class="stage">
<ul class="list">
<li class="item" tabindex="0"><div class="title"><span class="num">1</span><span class="name">brand strategy</span></div>
<div class="sub"><ul><li>positioning</li><li>naming</li><li>brand story</li></ul></div></li>
<li class="item" tabindex="0"><div class="title"><span class="num">2</span><span class="name">visual identity</span></div>
<div class="sub"><ul><li>logo & wordmark</li><li>color & type system</li><li>guidelines</li></ul></div></li>
<li class="item" tabindex="0"><div class="title"><span class="num">3</span><span class="name">web design</span></div>
<div class="sub"><ul><li>landing page</li><li>portfolio site</li><li>design system</li></ul></div></li>
<li class="item" tabindex="0"><div class="title"><span class="num">4</span><span class="name">framer development</span></div>
<div class="sub"><ul><li>cms setup</li><li>interactions</li><li>launch</li></ul></div></li>
</ul>
</div>
- 높이 대신
grid-template-rows:height: auto는 전환이 안 되지만, 행 크기0fr → 1fr은 부드럽게 바뀌어요. 세부 항목 칸에는min-height: 0과overflow: hidden을 꼭 같이 줘요. - 여백도 같이 전환: 왼쪽 여백 0 → 16px이 글자를 오른쪽으로 밀어서, 펼쳐지는 줄이 '앞으로 나오는' 느낌을 줘요.
cubic-bezier(.5, 1, .89, 1)0.2초: 원본처럼 빨리 열리고 끝에서 살짝 늦춰져요. 키보드(:focus)와 모바일 터치에서도 펼쳐지게tabindex="0"을 달았어요.
올리브의 한마디
첫 화면 등장은 처음 볼 때 "오, 예쁘다" 하고 지나가기 쉬운데, 재 보니 꽤 꼼꼼하게 짜여 있었어요. 제목은 0.2초 간격, 태그는 0.1초 간격, 사진은 조금 늦게 무겁게. 무엇을 먼저 읽게 할지를 시간으로 정해 둔 것이라, 등장 순서가 곧 읽는 순서가 돼요.
서비스 목록은 보기에는 단순한데, 높이·여백·바탕·글자색 다섯 가지가 똑같이 0.2초 안에 함께 움직이고 있었어요. 하나라도 박자가 어긋나면 덜컹거렸을 거예요. 짧고 빠른 전환이 오히려 '잘 만든 느낌'을 준다는 걸 다시 배웠어요. 이 블로그 첫 화면에도 시간차 등장을 하나쯤 가져와 볼까 해요.
오늘도 읽어 주셔서 고마워요. 다음 글에서 만나요! 🌿