06Wood grain sliding panels
Two wood grain leaves part left and right to reveal a bright slot inside, stay open a beat, then close again, the grain lines travelling with each leaf.
Best forCategory reveals and split introduction sections
두 짝 분할나무 결 gradient대칭 이동

<main class="stage">
<section class="wooddoors" aria-label="Wood grain sliding panels">
<div class="wooddoors__frame">
<div class="wooddoors__slot">
<p class="wooddoors__slot-title">The second story</p>
<p class="wooddoors__slot-desc">Bright content behind the wood</p>
</div>
<div class="wooddoors__leaf wooddoors__leaf--l" aria-hidden="true">
<span class="wooddoors__knot"></span>
<span class="wooddoors__handle"></span>
</div>
<div class="wooddoors__leaf wooddoors__leaf--r" aria-hidden="true">
<span class="wooddoors__knot wooddoors__knot--low"></span>
<span class="wooddoors__handle"></span>
</div>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
// 스큐어모픽 나무 — 세로 결 repeating-linear-gradient + 매듭 radial-gradient. 사진 없이 결이 흐른다
$wood-a: #b57e46; // 결 밝은 톤
$wood-b: #a96f38; // 결 중간
$wood-c: #c08a4e; // 결 가장 밝게
$wood-d: #96622e; // 결 어두운 톤
$wood-lo: #906a47; // 패널 아래 그늘
$brass: #ddd3ba; // 놋쇠 손잡이
$duration: $dur-loop; // 열리고 잠시 머물고 닫히는 한 주기
$easing: $ease-spring; // 미닫이는 끝에서 살짝 튕긴다
$color: $wood-a;
@include stage($stage-yellow);
.wooddoors {
display: grid;
place-items: center;
}
// 틀 — 안쪽 내용을 감싸고 두 짝이 틀 밖으로 나가면 잘린다 (overflow)
.wooddoors__frame {
position: relative;
width: clamp(212px, 66vw, 300px);
aspect-ratio: 16 / 10;
border-radius: $r-card;
overflow: hidden;
background: #2d1c0d;
box-shadow: 0 10px 16px rgba(23, 20, 26, .35), inset 0 0 0 4px #241608;
}
// 틀 안 밝은 슬롯 — 나무가 열려야 보이는 내용
.wooddoors__slot {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: $sp-1;
padding: $sp-4;
border-radius: $r-card;
background: $subject-cream;
}
.wooddoors__slot-title {
margin: 0;
color: #574a38;
font-weight: 800; font-size: clamp(13px, 3.4vw, 17px); line-height: 1.2;
font-family: inherit;
}
.wooddoors__slot-desc {
margin: 0;
color: #7a6c56;
font-weight: 500; font-size: clamp(10px, 2.6vw, 12px); line-height: 1.3;
font-family: inherit;
}
// 나무 짝 — 세로 결 줄무늬가 짝과 함께 움직인다 (gradient 가 배경이라 결이 "흐르는" 게 아니라 짝이 통째로 이동)
.wooddoors__leaf {
position: absolute;
top: 0; bottom: 0;
width: 50.5%; // 가운데 살짝 겹친다 — 닫았을 때 틈이 안 보인다
background:
radial-gradient(ellipse 14px 22px at 62% 30%, rgba(74, 44, 12, .55) 0 38%, rgba($wood-c, .5) 42% 52%, transparent 60%),
repeating-linear-gradient(90deg,
$wood-a 0 12px, $wood-b 12px 15px, $wood-c 15px 27px, $wood-d 27px 30px, $wood-a 30px 38px, $wood-lo 38px 41px);
box-shadow: inset 0 2px 0 rgba(255, 230, 190, .3), inset 0 -4px 0 rgba(45, 25, 6, .55);
animation: doorsOpenL $duration $easing infinite;
}
.wooddoors__leaf--l { left: 0; }
.wooddoors__leaf--r {
right: 0;
animation-name: doorsOpenR;
}
// 매듭(나이테)과 놋쇠 손잡이
.wooddoors__knot {
position: absolute;
top: 22%; left: 16%;
width: 28px;
height: 44px;
border-radius: 50%;
background: repeating-radial-gradient(ellipse at 50% 50%, rgba(74, 44, 12, .5) 0 2px, rgba($wood-c, .4) 2px 5px);
}
.wooddoors__knot--low { top: 48%; left: auto; right: 20%; }
.wooddoors__handle {
position: absolute;
top: calc(50% - 8px);
width: 8px;
height: 16px;
border-radius: $r-pill;
background: linear-gradient(90deg, #c9a24e, $brass 55%, #b09760);
box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}
.wooddoors__leaf--l .wooddoors__handle { right: 12px; }
.wooddoors__leaf--r .wooddoors__handle { left: 12px; }
// 두 짝이 90% 넘게 열렸다 닫힌다 — 결 줄무늬가 짝을 타고 이동한다. 텔레포트 없다
@keyframes doorsOpenL {
0%, 10% { transform: translateX(0); }
42%, 60% { transform: translateX(-92%); }
88%, 100% { transform: translateX(0); }
}
@keyframes doorsOpenR {
0%, 10% { transform: translateX(0); }
42%, 60% { transform: translateX(92%); }
88%, 100% { transform: translateX(0); }
}
@media (max-width: 340px), (max-height: 220px) {
.wooddoors__slot { padding: $sp-3; }
.wooddoors__knot { width: 20px; height: 32px; }
}
@media (prefers-reduced-motion: reduce) {
.wooddoors__leaf { animation: none !important; transform: translateX(-92%); }
.wooddoors__leaf--r { transform: translateX(92%); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.wooddoors {
display: grid;
place-items: center;
}
.wooddoors__frame {
position: relative;
width: clamp(212px, 66vw, 300px);
aspect-ratio: 16/10;
border-radius: 16px;
overflow: hidden;
background: #2d1c0d;
box-shadow: 0 10px 16px rgba(23, 20, 26, 0.35), inset 0 0 0 4px #241608;
}
.wooddoors__slot {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
padding: 16px;
border-radius: 16px;
background: #fff7e6;
}
.wooddoors__slot-title {
margin: 0;
color: #574a38;
font-weight: 800;
font-size: clamp(13px, 3.4vw, 17px);
line-height: 1.2;
font-family: inherit;
}
.wooddoors__slot-desc {
margin: 0;
color: #7a6c56;
font-weight: 500;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1.3;
font-family: inherit;
}
.wooddoors__leaf {
position: absolute;
top: 0;
bottom: 0;
width: 50.5%;
background: radial-gradient(ellipse 14px 22px at 62% 30%, rgba(74, 44, 12, 0.55) 0 38%, rgba(192, 138, 78, 0.5) 42% 52%, transparent 60%), repeating-linear-gradient(90deg, #b57e46 0 12px, #a96f38 12px 15px, #c08a4e 15px 27px, #96622e 27px 30px, #b57e46 30px 38px, #906a47 38px 41px);
box-shadow: inset 0 2px 0 rgba(255, 230, 190, 0.3), inset 0 -4px 0 rgba(45, 25, 6, 0.55);
animation: doorsOpenL 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.wooddoors__leaf--l {
left: 0;
}
.wooddoors__leaf--r {
right: 0;
animation-name: doorsOpenR;
}
.wooddoors__knot {
position: absolute;
top: 22%;
left: 16%;
width: 28px;
height: 44px;
border-radius: 50%;
background: repeating-radial-gradient(ellipse at 50% 50%, rgba(74, 44, 12, 0.5) 0 2px, rgba(192, 138, 78, 0.4) 2px 5px);
}
.wooddoors__knot--low {
top: 48%;
left: auto;
right: 20%;
}
.wooddoors__handle {
position: absolute;
top: calc(50% - 8px);
width: 8px;
height: 16px;
border-radius: 999px;
background: linear-gradient(90deg, #c9a24e, #ddd3ba 55%, #b09760);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.wooddoors__leaf--l .wooddoors__handle {
right: 12px;
}
.wooddoors__leaf--r .wooddoors__handle {
left: 12px;
}
@keyframes doorsOpenL {
0%, 10% {
transform: translateX(0);
}
42%, 60% {
transform: translateX(-92%);
}
88%, 100% {
transform: translateX(0);
}
}
@keyframes doorsOpenR {
0%, 10% {
transform: translateX(0);
}
42%, 60% {
transform: translateX(92%);
}
88%, 100% {
transform: translateX(0);
}
}
@media (max-width: 340px), (max-height: 220px) {
.wooddoors__slot {
padding: 12px;
}
.wooddoors__knot {
width: 20px;
height: 32px;
}
}
@media (prefers-reduced-motion: reduce) {
.wooddoors__leaf {
animation: none !important;
transform: translateX(-92%);
}
.wooddoors__leaf--r {
transform: translateX(92%);
}
}import type { CSSProperties } from "react";
import styles from "./WoodPanelDoors.module.scss";
export type WoodPanelDoorsProps = {
title?: string;
desc?: string;
/** 스크린리더용 섹션 이름 (기본 "나무 결 미닫이 패널") */
ariaLabel?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function WoodPanelDoors({
title = "두 번째 이야기",
desc = "나무를 열면 밝은 내용",
ariaLabel = "나무 결 미닫이 패널",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#b57e46",
}: WoodPanelDoorsProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.wooddoors} style={vars} aria-label={ariaLabel}>
<div className={styles.frame}>
<div className={styles.slot}>
<p className={styles.slotTitle}>{title}</p>
<p className={styles.slotDesc}>{desc}</p>
</div>
<div className={`${styles.leaf} ${styles.leafL}`} aria-hidden="true">
<span className={styles.knot} />
<span className={styles.handle} />
</div>
<div className={`${styles.leaf} ${styles.leafR}`} aria-hidden="true">
<span className={`${styles.knot} ${styles.knotLow}`} />
<span className={styles.handle} />
</div>
</div>
</section>
);
}06 Wood grain sliding panels — Measured area 38.896% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · Skeuomorphism Design Kit: 9 Tactile Landing Parts
The post this demo lives in
Skeuomorphism Design Kit: 9 Tactile Landing PartsSkeuomorphism design brings the feel of real materials — leather, metal, wood, glass — onto the screen. This kit rebuild See all demos in this post


