01락온 히어로
헤드라인 카드를 감싼 네 귀퉁이 브래킷이 바깥에서 안으로 좁혀지며 잠기고, 뒤로는 스캔라인이 위에서 아래로 한 번씩 훑고 지나갑니다.
쓰임SaaS 랜딩 첫 화면, 제품 출시 커버
브래킷 락온스캔라인 스윕모노스페이스 리드아웃

<main class="stage">
<section class="hero is-demo">
<span class="hero__corner hero__corner--tl" aria-hidden="true"></span>
<span class="hero__corner hero__corner--tr" aria-hidden="true"></span>
<span class="hero__corner hero__corner--bl" aria-hidden="true"></span>
<span class="hero__corner hero__corner--br" aria-hidden="true"></span>
<div class="hero__scan" aria-hidden="true"></div>
<p class="hero__eyebrow"><span class="hero__dot" aria-hidden="true"></span>SENTRY / ONLINE</p>
<h1 class="hero__title">놓치는 순간 없이<br>지켜보는 대시보드</h1>
<p class="hero__desc">실시간으로 감지하고 기록합니다</p>
<button class="hero__cta" type="button">무료로 시작</button>
<p class="hero__readout">SIG <span class="hero__cursor" aria-hidden="true"></span></p>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 브래킷 락온 + 스캔 한 주기
$easing: $ease-spring; // 브래킷이 조여드는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-ink);
.hero {
position: relative;
box-sizing: border-box;
width: clamp(240px, 82vw, 380px);
padding: $sp-5 $sp-4;
border-radius: $r-sm;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
overflow: hidden;
// 320px 폰 체크: eyebrow·2줄 제목·설명·CTA·터미널 readout 5단이 129·130 형제보다 한 단 더 많아
// 200px 높이를 넘는다 (131-01 실측: scrollHeight 238px vs innerHeight 200px). 단마다 여백을
// 한 단계씩 좁혀 맞춘다.
}
.hero__scan {
position: absolute;
inset: 0;
pointer-events: none;
background:
repeating-linear-gradient(to bottom, rgba($color, 0) 0, rgba($color, 0) 5px, rgba($color, .1) 6px, rgba($color, 0) 7px),
linear-gradient(to bottom, rgba($color, 0) 0%, rgba($color, .32) 48%, rgba($color, 0) 100%);
background-size: 100% 100%, 100% 240%;
background-position: 0 0, 0 -140%;
animation: scanSweep $duration linear infinite;
}
@keyframes scanSweep {
0% { background-position: 0 0, 0 -140%; }
100% { background-position: 0 0, 0 140%; }
}
.hero__corner {
position: absolute;
width: 18px;
height: 18px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.hero__corner--tl { top: 0; left: 0; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.hero__corner--tr { top: 0; right: 0; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.hero__corner--bl { bottom: 0; left: 0; border-bottom-width: 2px; border-left-width: 2px; transform-origin: bottom left; animation-delay: -.3s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.hero__corner--br { bottom: 0; right: 0; border-bottom-width: 2px; border-right-width: 2px; transform-origin: bottom right; animation-delay: -.45s; } // 규칙8: 괄호 꼭짓점 모서리 고정
@keyframes cornerPulse {
0%, 100% { transform: scale(.55); opacity: .35; }
50% { transform: scale(1); opacity: 1; }
}
.hero__eyebrow {
position: relative;
margin: 0 0 $sp-2;
display: flex;
align-items: center;
gap: $sp-2;
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1;
letter-spacing: .12em;
text-transform: uppercase;
}
.hero__dot {
width: 6px;
height: 6px;
flex: none;
border-radius: 50%;
background: $color;
animation: blink $dur-loop steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .2; }
}
.hero__title {
position: relative;
margin: 0 0 $sp-1;
color: $subject-cream;
font-weight: 700;
font-size: clamp(18px, 5vw, 25px);
line-height: 1.15;
font-family: inherit;
}
.hero__desc {
position: relative;
margin: 0 0 $sp-3;
color: rgba($subject-cream, .85); // 스캔라인이 지나는 밝은 프레임(#544623)에서 .6 은 4.29:1 — .85 로 6.77:1
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
font-family: inherit;
}
.hero__cta {
position: relative;
border: 1px solid $color;
border-radius: $r-control;
background: rgba($color, .12);
color: $color;
padding: $sp-1 $sp-5;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: .02em;
}
.hero__readout {
position: relative;
margin: $sp-2 0 0;
color: rgba($color, .7);
font-family: "Courier New", monospace;
font-weight: 400;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .04em;
}
.hero__cursor {
display: inline-block;
width: 6px;
height: 10px;
background: $color;
vertical-align: -1px;
animation: blink $dur-loop steps(1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.hero__scan, .hero__corner, .hero__dot, .hero__cursor {
animation: none !important;
}
.hero__corner { transform: scale(1); opacity: 1; }
.hero__dot, .hero__cursor { opacity: 1; }
}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 {
position: relative;
box-sizing: border-box;
width: clamp(240px, 82vw, 380px);
padding: 20px 16px;
border-radius: 8px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4), 0 0 24px rgba(255, 210, 63, 0.16);
overflow: hidden;
}
.hero__scan {
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(to bottom, rgba(255, 210, 63, 0) 0, rgba(255, 210, 63, 0) 5px, rgba(255, 210, 63, 0.1) 6px, rgba(255, 210, 63, 0) 7px), linear-gradient(to bottom, rgba(255, 210, 63, 0) 0%, rgba(255, 210, 63, 0.32) 48%, rgba(255, 210, 63, 0) 100%);
background-size: 100% 100%, 100% 240%;
background-position: 0 0, 0 -140%;
animation: scanSweep 2s linear infinite;
}
@keyframes scanSweep {
0% {
background-position: 0 0, 0 -140%;
}
100% {
background-position: 0 0, 0 140%;
}
}
.hero__corner {
position: absolute;
width: 18px;
height: 18px;
pointer-events: none;
border-color: #ffd23f;
border-style: solid;
border-width: 0;
animation: cornerPulse 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.hero__corner--tl {
top: 0;
left: 0;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.hero__corner--tr {
top: 0;
right: 0;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
.hero__corner--bl {
bottom: 0;
left: 0;
border-bottom-width: 2px;
border-left-width: 2px;
transform-origin: bottom left;
animation-delay: -0.3s;
}
.hero__corner--br {
bottom: 0;
right: 0;
border-bottom-width: 2px;
border-right-width: 2px;
transform-origin: bottom right;
animation-delay: -0.45s;
}
@keyframes cornerPulse {
0%, 100% {
transform: scale(0.55);
opacity: 0.35;
}
50% {
transform: scale(1);
opacity: 1;
}
}
.hero__eyebrow {
position: relative;
margin: 0 0 8px;
display: flex;
align-items: center;
gap: 8px;
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.hero__dot {
width: 6px;
height: 6px;
flex: none;
border-radius: 50%;
background: #ffd23f;
animation: blink 2s steps(1) infinite;
}
@keyframes blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.2;
}
}
.hero__title {
position: relative;
margin: 0 0 4px;
color: #fff7e6;
font-weight: 700;
font-size: clamp(18px, 5vw, 25px);
line-height: 1.15;
font-family: inherit;
}
.hero__desc {
position: relative;
margin: 0 0 12px;
color: rgba(255, 247, 230, 0.85);
font-weight: 500;
font-size: clamp(12px, 2.8vw, 14px);
line-height: 1.4;
font-family: inherit;
}
.hero__cta {
position: relative;
border: 1px solid #ffd23f;
border-radius: 12px;
background: rgba(255, 210, 63, 0.12);
color: #ffd23f;
padding: 4px 20px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.02em;
}
.hero__readout {
position: relative;
margin: 8px 0 0;
color: rgba(255, 210, 63, 0.7);
font-family: "Courier New", monospace;
font-weight: 400;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.04em;
}
.hero__cursor {
display: inline-block;
width: 6px;
height: 10px;
background: #ffd23f;
vertical-align: -1px;
animation: blink 2s steps(1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.hero__scan, .hero__corner, .hero__dot, .hero__cursor {
animation: none !important;
}
.hero__corner {
transform: scale(1);
opacity: 1;
}
.hero__dot, .hero__cursor {
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./HeroHud.module.scss";
export type HeroHudProps = {
brand?: string;
title?: string;
desc?: string;
cta?: string;
readout?: string;
/** 한 주기(기본 2000ms) */
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function HeroHud({
brand = "SENTRY / ONLINE",
title = "놓치는 순간 없이 지켜보는 대시보드",
desc = "실시간으로 감지하고 기록합니다",
cta = "무료로 시작",
readout = "SIG",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: HeroHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.hero} style={vars}>
<span className={`${styles.corner} ${styles.cornerTl}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerTr}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerBl}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerBr}`} aria-hidden="true" />
<div className={styles.scan} aria-hidden="true" />
<p className={styles.eyebrow}>
<span className={styles.dot} aria-hidden="true" />
{brand}
</p>
<h1 className={styles.title}>{title}</h1>
<p className={styles.desc}>{desc}</p>
<button type="button" className={styles.cta}>{cta}</button>
<p className={styles.readoutLine}>
{readout} <span className={styles.cursor} aria-hidden="true" />
</p>
</section>
);
}01 락온 히어로 — 측정 누적면적 48.094% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · HUD 디자인, 화면이 나를 겨누는 랜딩 부품 9개
이 데모가 속한 글
HUD 디자인, 화면이 나를 겨누는 랜딩 부품 9개HUD 디자인은 게임이나 SF 영화 속 조준경·레이더처럼, 화면 위에 좌표·상태 수치를 얹어 마치 나를 지켜보고 있는 듯한 인상을 주는 인터페이스 스타일입니다. 이 편은 그 감시 카메라 감성을 실제로 돌아가는 랜딩 이 글의 데모 전부 보기


