01クレヨン落書きヒーロー
見出しの下にクレヨンでさっと引いたような下線が1本のSVGストロークで自分で描かれ、カードの枠線は太さが不揃いな手描きの四角形です。完璧な直線ではないからこそ、人の手のぬくもりが出ます。
使いどころ子ども向け・教育・ハンドメイドブランドのLP最初の画面
stroke-dashoffset 밑줄손그림 border-radiusradius 24

<main class="stage">
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<filter id="rough1" x="-30%" y="-30%" width="160%" height="160%">
<feTurbulence type="fractalNoise" baseFrequency="0.045 0.06" numOctaves="2" seed="4" result="n"/>
<feDisplacementMap in="SourceGraphic" in2="n" scale="7" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</svg>
<section class="herocrayon">
<svg class="herocrayon__frame" viewBox="0 0 300 190" preserveAspectRatio="none" aria-hidden="true">
<rect x="6" y="6" width="288" height="178" rx="20" class="herocrayon__frame-rect"/>
</svg>
<div class="herocrayon__card">
<p class="herocrayon__eyebrow">CRAYON KIDS</p>
<h1 class="herocrayon__title">手で描いたみたいに</h1>
<svg class="herocrayon__underline" viewBox="0 0 170 16" aria-hidden="true">
<path class="herocrayon__underline-path" d="M4 9 C 28 3, 54 14, 84 7 S 136 3, 166 10" />
</svg>
<p class="herocrayon__desc">クレヨン感性のランディングキット</p>
<button class="herocrayon__cta" type="button">無料体験</button>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 테두리가 한 바퀴 다시 그려지는 주기
$easing: $ease-spring; // 카드가 들썩이는 곡선
$color: $stage-ink; // 글자 색 기본값(토큰) — 아래 크레용 잉크로 덮는다
// 크레용 팔레트 — 이 편의 역할 색. 값은 tailwind.config.js · tokens.css 와 같다
$crayon-paper: #fff8ef; // 종이 바탕
$crayon-ink: #33261c; // 크레용 잉크 — 글자는 항상 이 색(대비 12:1 이상)
$crayon-red: $stage-orange; // 레드 크레용 — 세련 패치: 토큰 정렬(#dbb6b7→#c89a8f, 휘도 0.29→0.27)
$crayon-yellow: $stage-yellow; // 옐로 크레용 — 세련 패치: 토큰 정렬(#d1c5a2→#d2c7a4, 휘도 0.65→0.68)
$crayon-blue: #9cb8ce; // 블루 크레용
$crayon-green: #6dbe6d; // 그린 크레용
$crayon-purple: #cbbfe0; // 퍼플 크레용
@include stage($crayon-paper);
.herocrayon {
position: relative;
width: clamp(230px, 76vw, 320px);
animation: cardBob $duration $easing infinite;
}
// 테두리는 글자와 분리된 SVG 레이어에만 흔들림 필터를 건다 — 글자에 걸면 읽기 어려워진다(접근성 섹션 참고)
.herocrayon__frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.herocrayon__frame-rect {
fill: none;
stroke: $crayon-ink;
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 16 11;
filter: url(#rough1);
animation: frameMarch $duration linear infinite;
}
.herocrayon__card {
position: relative;
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: $sp-2;
padding: $sp-6 $sp-5;
}
.herocrayon__eyebrow {
margin: 0;
color: $subject-orange-deep;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
letter-spacing: .08em;
}
.herocrayon__title {
margin: 0;
color: $crayon-ink;
font-weight: 700;
font-size: clamp(19px, 4.8vw, 25px);
line-height: 1.2;
}
.herocrayon__underline {
width: clamp(120px, 42vw, 170px);
height: 16px;
overflow: visible;
}
.herocrayon__underline-path {
fill: none;
stroke: $crayon-yellow;
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 210;
animation: underlineDraw $duration $ease-out infinite;
}
.herocrayon__desc {
margin: 0;
color: rgba($crayon-ink, .72);
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
}
.herocrayon__cta {
margin-top: $sp-1;
border: 3px solid $crayon-ink;
border-radius: $r-pill;
background: $crayon-yellow;
color: $crayon-ink;
padding: $sp-2 $sp-6;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1;
}
.herocrayon__cta:focus-visible {
outline: 3px solid $crayon-ink;
outline-offset: 2px;
}
// 데모: 테두리가 크레용으로 계속 덧그리듯 행진하고, 밑줄이 그려졌다 지워지길 반복하며, 카드는 살짝 들썩인다
@keyframes frameMarch {
to { stroke-dashoffset: -540; }
}
@keyframes underlineDraw {
0% { stroke-dashoffset: 210; }
45% { stroke-dashoffset: 0; }
80% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -210; }
}
@keyframes cardBob {
0%, 100% { transform: translateY(0) rotate(-0.4deg); }
50% { transform: translateY(-6px) rotate(0.5deg); }
}
// 반응형: 320×200 처럼 세로가 짧은 화면에선 카드 속을 줄인다 (BD9)
@media (max-width: 340px), (max-height: 220px) {
.herocrayon__card { padding: $sp-4 $sp-4; gap: $sp-1; }
.herocrayon__eyebrow { display: none; }
.herocrayon__underline { width: clamp(90px, 32vw, 120px); }
}
@media (prefers-reduced-motion: reduce) {
.herocrayon,
.herocrayon__frame-rect,
.herocrayon__underline-path {
animation: none !important;
transform: none;
}
.herocrayon__underline-path { stroke-dashoffset: 0; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #fff8ef;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.herocrayon {
position: relative;
width: clamp(230px, 76vw, 320px);
animation: cardBob 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.herocrayon__frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.herocrayon__frame-rect {
fill: none;
stroke: #33261c;
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 16 11;
filter: url(#rough1);
animation: frameMarch 2s linear infinite;
}
.herocrayon__card {
position: relative;
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
padding: 24px 20px;
}
.herocrayon__eyebrow {
margin: 0;
color: #c93000;
font-weight: 700;
font-size: clamp(10px, 2.6vw, 12px);
line-height: 1;
letter-spacing: 0.08em;
}
.herocrayon__title {
margin: 0;
color: #33261c;
font-weight: 700;
font-size: clamp(19px, 4.8vw, 25px);
line-height: 1.2;
}
.herocrayon__underline {
width: clamp(120px, 42vw, 170px);
height: 16px;
overflow: visible;
}
.herocrayon__underline-path {
fill: none;
stroke: #ffd23f;
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 210;
animation: underlineDraw 2s ease-out infinite;
}
.herocrayon__desc {
margin: 0;
color: rgba(51, 38, 28, 0.72);
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
}
.herocrayon__cta {
margin-top: 4px;
border: 3px solid #33261c;
border-radius: 999px;
background: #ffd23f;
color: #33261c;
padding: 8px 24px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1;
}
.herocrayon__cta:focus-visible {
outline: 3px solid #33261c;
outline-offset: 2px;
}
@keyframes frameMarch {
to {
stroke-dashoffset: -540;
}
}
@keyframes underlineDraw {
0% {
stroke-dashoffset: 210;
}
45% {
stroke-dashoffset: 0;
}
80% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: -210;
}
}
@keyframes cardBob {
0%, 100% {
transform: translateY(0) rotate(-0.4deg);
}
50% {
transform: translateY(-6px) rotate(0.5deg);
}
}
@media (max-width: 340px), (max-height: 220px) {
.herocrayon__card {
padding: 16px 16px;
gap: 4px;
}
.herocrayon__eyebrow {
display: none;
}
.herocrayon__underline {
width: clamp(90px, 32vw, 120px);
}
}
@media (prefers-reduced-motion: reduce) {
.herocrayon,
.herocrayon__frame-rect,
.herocrayon__underline-path {
animation: none !important;
transform: none;
}
.herocrayon__underline-path {
stroke-dashoffset: 0;
}
}import type { CSSProperties } from "react";
import styles from "./HeroCrayon.module.scss";
export type HeroCrayonProps = {
eyebrow?: string;
title?: string;
desc?: string;
cta?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function HeroCrayon({
eyebrow = "CRAYON KIDS",
title = "손으로 그린 것처럼",
desc = "크레용 감성 랜딩 키트",
cta = "무료 체험",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ff5a5f",
}: HeroCrayonProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.hero} style={vars}>
<svg width="0" height="0" style={{ position: "absolute" }} aria-hidden="true" focusable="false">
<filter id="hc-rough" x="-30%" y="-30%" width="160%" height="160%">
<feTurbulence type="fractalNoise" baseFrequency={0.045} numOctaves={2} seed={4} result="n" />
<feDisplacementMap in="SourceGraphic" in2="n" scale={7} xChannelSelector="R" yChannelSelector="G" />
</filter>
</svg>
<svg className={styles.frame} viewBox="0 0 300 190" preserveAspectRatio="none" aria-hidden="true">
<rect x={6} y={6} width={288} height={178} rx={20} className={styles.frameRect} />
</svg>
<div className={styles.card}>
<p className={styles.eyebrow}>{eyebrow}</p>
<h1 className={styles.title}>{title}</h1>
<svg className={styles.underline} viewBox="0 0 170 16" aria-hidden="true">
<path className={styles.underlinePath} d="M4 9 C 28 3, 54 14, 84 7 S 136 3, 166 10" />
</svg>
<p className={styles.desc}>{desc}</p>
<button type="button" className={styles.cta}>{cta}</button>
</div>
</section>
);
}01 クレヨン落書きヒーロー — 計測 累積面積 16.178% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · 手書き風 デザイン LPキット、クレヨン風セクション9選
このデモが属する記事
手書き風 デザイン LPキット、クレヨン風セクション9選手書き風 デザインは、カードやボタンの周りに太くよれた輪郭線を描き、その輪郭線だけにSVGフィルターをかけて、定規で引いたのではなく手で描いたように見せるスタイルです。手で描いたと思ったら実は CSS だった、というのがこのキットの正体です この記事のデモをすべて見る


