HUD Interface Design: 9 UIs That Watch You Back
HUD interface design borrows the targeting reticles and radar overlays from sci-fi movies and games, laying corner brackets and coordinate readouts over the
This post turns that surveillance-console feel into nine working landing-page pieces — terminal ui design you can paste straight into a project, even if you've never written a line of CSS.
Scroll the full page below first. From the header nav down to the terminal footer, every corner bracket and scanline should pulse on the same rhythm — that consistency is what makes the style read as one system instead of nine random widgets.

The nine pieces that built that page are in the grid below, each one openable on its own. They're ordered the way a visitor actually reads the page: first glance, checking the menu, skimming features, watching progress, comparing plans, trusting a review, one more nudge, signing up, then the footer. In the same design pillar, Y2K Web Design leads with chrome gloss instead of amber brackets, and Neumorphism Design Kit drops contrast until only a pair of soft shadows is left.
01 hero-hud
02 nav-hud
03 card-grid-hud
04 feature-list-hud
05 pricing-hud
06 testimonial-hud
07 cta-band-hud
08 footer-hud
09 buttons-inputs-hud
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Lock-on hero
- 02 Status-bar nav
- 03 Target-lock cards
- 04 System-log list
- 05 Locked-target plan
- 06 Face-scan quote
- 07 Alert band
- 08 Terminal footer
- 09 Lock-on button, scan input
01Lock-on hero
What moves
Four corner brackets close in around the headline card on a 0.15-second stagger, like a camera locking focus, while a scanline sweeps once from top to bottom behind it.

<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">Never miss a moment —<br>a dashboard that watches</h1>
<p class="hero__desc">It detects and logs in real time.</p>
<button class="hero__cta" type="button">Start for free</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 · Lock-on hero — Measured area 48.094% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
Use it on a SaaS landing first screen or a product-launch cover, anywhere the first impression should feel like the screen just noticed you.
<div class="relative w-full max-w-md overflow-hidden rounded-control bg-ink px-6 py-8 shadow-glow">
<span class="corner corner-tl" aria-hidden="true"></span>
<span class="corner corner-tr" aria-hidden="true"></span>
<span class="corner corner-bl" aria-hidden="true"></span>
<span class="corner corner-br" aria-hidden="true"></span>
<div class="scan-sweep absolute inset-0 pointer-events-none" aria-hidden="true"></div>
<p class="mb-3 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-accent">
<span class="blink-dot inline-block h-1.5 w-1.5 rounded-full bg-accent"></span>SENTRY / ONLINE
</p>
<h1 class="mb-2 text-2xl font-bold leading-tight">Never miss a moment</h1>
<p class="mb-4 text-sm text-cream/60">It detects and logs in real time.</p>
<button class="rounded-control border border-accent bg-accent/10 px-5 py-2 text-sm font-bold text-accent">Start for free</button>
</div>
02Status-bar nav
What moves
The whole nav sits inside a bracket-framed status bar, and a scanning underline glides between tabs to mark whichever one is active.

<main class="stage">
<nav class="navhud is-demo" aria-label="SENTRY">
<span class="navhud__brand">SENTRY</span>
<ul class="navhud__track">
<li class="navhud__item">Home</li>
<li class="navhud__item">Features</li>
<li class="navhud__item">Pricing</li>
<li class="navhud__item">Contact</li>
<span class="navhud__indicator" aria-hidden="true"></span>
</ul>
<span class="navhud__status"><span class="navhud__dot" aria-hidden="true"></span>LIVE</span>
</nav>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 밑줄 인디케이터가 탭 4곳을 도는 한 주기
$easing: $ease-spring; // 인디케이터가 미끄러지는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-yellow);
.navhud {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
gap: $sp-3;
width: clamp(260px, 84vw, 420px);
padding: $sp-2 $sp-3;
border-radius: $r-control;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
}
.navhud__brand {
flex: none;
color: $subject-cream;
font-weight: 800;
font-size: clamp(12px, 3vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: .04em;
white-space: nowrap;
}
.navhud__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
margin: 0;
padding: 0;
list-style: none;
}
.navhud__item {
padding: $sp-1 0;
color: rgba($subject-cream, .55);
font-weight: 600;
font-size: clamp(10px, 2.6vw, 11px);
line-height: 1;
font-family: inherit;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navhud__indicator {
position: absolute;
left: 0;
bottom: 0;
width: 25%;
height: 2px;
background: $color;
animation: navScan $duration $easing infinite;
}
@keyframes navScan {
0%, 20% { transform: translateX(0); }
25%, 45% { transform: translateX(100%); }
50%, 70% { transform: translateX(200%); }
75%, 95% { transform: translateX(300%); }
100% { transform: translateX(0); }
}
.navhud__status {
flex: none;
display: flex;
align-items: center;
gap: $sp-1;
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .08em;
white-space: nowrap;
}
.navhud__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: $color;
animation: blink $dur-loop steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .2; }
}
@media (prefers-reduced-motion: reduce) {
.navhud__indicator, .navhud__dot {
animation: none !important;
}
.navhud__indicator { transform: translateX(0); }
.navhud__dot { opacity: 1; }
}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);
}
.navhud {
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 12px;
width: clamp(260px, 84vw, 420px);
padding: 8px 12px;
border-radius: 12px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4), 0 0 24px rgba(255, 210, 63, 0.16);
}
.navhud__brand {
flex: none;
color: #fff7e6;
font-weight: 800;
font-size: clamp(12px, 3vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.04em;
white-space: nowrap;
}
.navhud__track {
position: relative;
flex: 1 1 auto;
min-width: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
margin: 0;
padding: 0;
list-style: none;
}
.navhud__item {
padding: 4px 0;
color: rgba(255, 247, 230, 0.55);
font-weight: 600;
font-size: clamp(10px, 2.6vw, 11px);
line-height: 1;
font-family: inherit;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navhud__indicator {
position: absolute;
left: 0;
bottom: 0;
width: 25%;
height: 2px;
background: #ffd23f;
animation: navScan 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes navScan {
0%, 20% {
transform: translateX(0);
}
25%, 45% {
transform: translateX(100%);
}
50%, 70% {
transform: translateX(200%);
}
75%, 95% {
transform: translateX(300%);
}
100% {
transform: translateX(0);
}
}
.navhud__status {
flex: none;
display: flex;
align-items: center;
gap: 4px;
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.08em;
white-space: nowrap;
}
.navhud__dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #ffd23f;
animation: blink 2s steps(1) infinite;
}
@keyframes blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.2;
}
}
@media (prefers-reduced-motion: reduce) {
.navhud__indicator, .navhud__dot {
animation: none !important;
}
.navhud__indicator {
transform: translateX(0);
}
.navhud__dot {
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./NavHud.module.scss";
export type NavHudProps = {
brand?: string;
links?: string[];
status?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function NavHud({
brand = "SENTRY",
links = ["홈", "기능", "요금", "문의"],
status = "LIVE",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: NavHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<nav className={styles.nav} style={vars} aria-label={brand}>
<span className={styles.brand}>{brand}</span>
<ul className={styles.track}>
{links.map((label) => (
<li key={label} className={styles.item}>{label}</li>
))}
<span className={styles.indicator} aria-hidden="true" />
</ul>
<span className={styles.status}>
<span className={styles.dot} aria-hidden="true" />
{status}
</span>
</nav>
);
}02 · Status-bar nav — Measured area 0.389% · moving frames 7/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
Reach for it on a landing-page top bar or a dashboard nav, any spot where the brand, the menu, and a live status all need to share one row.
<nav class="relative flex items-center gap-4 rounded-control bg-ink/95 px-3 py-2 shadow-glow" aria-label="SENTRY">
<span class="shrink-0 whitespace-nowrap pl-2 font-extrabold text-sm">SENTRY</span>
<div class="relative flex min-w-0 flex-1 items-stretch">
<span class="relative z-10 flex-1 whitespace-nowrap py-2 text-center text-xs font-semibold text-cream/70">Home</span>
<span class="relative z-10 flex-1 whitespace-nowrap py-2 text-center text-xs font-semibold text-cream/70">Features</span>
<span class="relative z-10 flex-1 whitespace-nowrap py-2 text-center text-xs font-semibold text-cream/70">Pricing</span>
<span class="relative z-10 flex-1 whitespace-nowrap py-2 text-center text-xs font-semibold text-cream/70">Contact</span>
<span class="nav-indicator absolute bottom-0 left-0 h-[2px] w-1/4 bg-accent"></span>
</div>
<span class="shrink-0 whitespace-nowrap flex items-center gap-1 pr-2 text-[10px] font-bold tracking-widest text-accent">
<span class="blink-dot inline-block h-1.5 w-1.5 rounded-full bg-accent"></span>LIVE
</span>
</nav>
03Target-lock cards
What moves
Every card in the grid gets a viewfinder-style bracket frame, and a scanline sweeps card by card in sequence while a coordinate readout sits in the corner.

<main class="stage">
<div class="cardgrid is-demo">
<article class="cardgrid__card">
<span class="cardgrid__corner cardgrid__corner--tl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--tr" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--bl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--br" aria-hidden="true"></span>
<div class="cardgrid__scan" aria-hidden="true"></div>
<span class="cardgrid__coord">04.12</span>
<span class="cardgrid__icon" aria-hidden="true"></span>
<p class="cardgrid__label">Detect</p>
</article>
<article class="cardgrid__card">
<span class="cardgrid__corner cardgrid__corner--tl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--tr" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--bl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--br" aria-hidden="true"></span>
<div class="cardgrid__scan" aria-hidden="true"></div>
<span class="cardgrid__coord cardgrid__coord--alt">#A1</span>
<span class="cardgrid__icon" aria-hidden="true"></span>
<p class="cardgrid__label">Track</p>
</article>
<article class="cardgrid__card">
<span class="cardgrid__corner cardgrid__corner--tl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--tr" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--bl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--br" aria-hidden="true"></span>
<div class="cardgrid__scan" aria-hidden="true"></div>
<span class="cardgrid__coord">07.03</span>
<span class="cardgrid__icon" aria-hidden="true"></span>
<p class="cardgrid__label">Log</p>
</article>
<article class="cardgrid__card">
<span class="cardgrid__corner cardgrid__corner--tl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--tr" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--bl" aria-hidden="true"></span>
<span class="cardgrid__corner cardgrid__corner--br" aria-hidden="true"></span>
<div class="cardgrid__scan" aria-hidden="true"></div>
<span class="cardgrid__coord cardgrid__coord--alt">#C4</span>
<span class="cardgrid__icon" aria-hidden="true"></span>
<p class="cardgrid__label">Analyze</p>
</article>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 스캔이 카드 4장을 순서대로 도는 한 주기
$easing: $ease-spring; // 브래킷이 조여드는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-orange);
.cardgrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $sp-2;
width: clamp(240px, 84vw, 340px);
}
// 320px 폰 체크: 2x2 고정 그리드라 폭이 줄어도 항상 2행 — coord 줄(HUD 전용 읽음값)까지 4줄이라
// 카드 padding·행간이 129·130 형제와 같은 값이면 2행 합계가 200px 높이를 넘는다
// (131-03 실측: scrollHeight 239px vs innerHeight 200px). padding·margin 을 한 단계씩 좁힌다.
.cardgrid__card {
position: relative;
box-sizing: border-box;
padding: $sp-2;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
overflow: hidden;
}
.cardgrid__card:nth-child(1) .cardgrid__scan { animation-delay: 0s; }
.cardgrid__card:nth-child(2) .cardgrid__scan { animation-delay: -.5s; }
.cardgrid__card:nth-child(3) .cardgrid__scan { animation-delay: -1s; }
.cardgrid__card:nth-child(4) .cardgrid__scan { animation-delay: -1.5s; }
.cardgrid__scan {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to bottom, rgba($color, 0) 0%, rgba($color, .3) 48%, rgba($color, 0) 100%);
background-size: 100% 240%;
background-position: 0 -140%;
animation: cardScan $duration linear infinite;
}
@keyframes cardScan {
0% { background-position: 0 -140%; }
100% { background-position: 0 140%; }
}
.cardgrid__corner {
position: absolute;
width: 12px;
height: 12px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.cardgrid__corner--tl { top: $sp-2; left: $sp-2; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.cardgrid__corner--tr { top: $sp-2; right: $sp-2; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.cardgrid__corner--bl { bottom: $sp-2; left: $sp-2; border-bottom-width: 2px; border-left-width: 2px; transform-origin: bottom left; animation-delay: -.3s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.cardgrid__corner--br { bottom: $sp-2; right: $sp-2; 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; }
}
.cardgrid__coord {
position: relative;
display: block;
margin: 0 0 $sp-1;
color: rgba($color, .85); // 스캔라인이 지나는 밝은 프레임(#4e4123)에서 .7 은 4.30:1 — .85 로 5.49:1
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .04em;
text-align: right;
}
.cardgrid__coord--alt {
color: $subject-sky; // 어두운 카드 면 위 쿨 강조 글자. 이 카드는 호박색 스캔라인이 늘 지나 면이 #352c1f 까지 밝아진다 — $subject-blue 2.59:1, $subject-blue-light 도 그 프레임에서 4.28:1 로 미달이라 한 단계 더 밝은 $subject-sky(7.42:1)
}
.cardgrid__icon {
position: relative;
display: block;
width: 20px;
height: 20px;
margin: 0 0 $sp-2;
border: 1px solid $color;
border-radius: $r-xs;
background: rgba($color, .18);
}
.cardgrid__label {
position: relative;
margin: 0;
color: $subject-cream;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1.3;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.cardgrid__scan, .cardgrid__corner {
animation: none !important;
}
.cardgrid__corner { transform: scale(1); opacity: 1; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.cardgrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
width: clamp(240px, 84vw, 340px);
}
.cardgrid__card {
position: relative;
box-sizing: border-box;
padding: 8px;
border-radius: 16px;
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;
}
.cardgrid__card:nth-child(1) .cardgrid__scan {
animation-delay: 0s;
}
.cardgrid__card:nth-child(2) .cardgrid__scan {
animation-delay: -0.5s;
}
.cardgrid__card:nth-child(3) .cardgrid__scan {
animation-delay: -1s;
}
.cardgrid__card:nth-child(4) .cardgrid__scan {
animation-delay: -1.5s;
}
.cardgrid__scan {
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(to bottom, rgba(255, 210, 63, 0) 0%, rgba(255, 210, 63, 0.3) 48%, rgba(255, 210, 63, 0) 100%);
background-size: 100% 240%;
background-position: 0 -140%;
animation: cardScan 2s linear infinite;
}
@keyframes cardScan {
0% {
background-position: 0 -140%;
}
100% {
background-position: 0 140%;
}
}
.cardgrid__corner {
position: absolute;
width: 12px;
height: 12px;
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;
}
.cardgrid__corner--tl {
top: 8px;
left: 8px;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.cardgrid__corner--tr {
top: 8px;
right: 8px;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
.cardgrid__corner--bl {
bottom: 8px;
left: 8px;
border-bottom-width: 2px;
border-left-width: 2px;
transform-origin: bottom left;
animation-delay: -0.3s;
}
.cardgrid__corner--br {
bottom: 8px;
right: 8px;
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;
}
}
.cardgrid__coord {
position: relative;
display: block;
margin: 0 0 4px;
color: rgba(255, 210, 63, 0.85);
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.04em;
text-align: right;
}
.cardgrid__coord--alt {
color: #6ec8ff;
}
.cardgrid__icon {
position: relative;
display: block;
width: 20px;
height: 20px;
margin: 0 0 8px;
border: 1px solid #ffd23f;
border-radius: 4px;
background: rgba(255, 210, 63, 0.18);
}
.cardgrid__label {
position: relative;
margin: 0;
color: #fff7e6;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1.3;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.cardgrid__scan, .cardgrid__corner {
animation: none !important;
}
.cardgrid__corner {
transform: scale(1);
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./CardGridHud.module.scss";
export type CardGridHudCard = { coord: string; label: string; alt?: boolean };
export type CardGridHudProps = {
cards?: CardGridHudCard[];
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const defaultCards: CardGridHudCard[] = [
{ coord: "04.12", label: "감지" },
{ coord: "#A1", label: "추적", alt: true },
{ coord: "07.03", label: "기록" },
{ coord: "#C4", label: "분석", alt: true },
];
export default function CardGridHud({
cards = defaultCards,
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: CardGridHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.grid} style={vars}>
{cards.map((card) => (
<article key={card.coord} className={styles.card}>
<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" />
<span className={`${styles.coord} ${card.alt ? styles.coordAlt : ""}`}>{card.coord}</span>
<span className={styles.icon} aria-hidden="true" />
<p className={styles.label}>{card.label}</p>
</article>
))}
</div>
);
}03 · Target-lock cards — Measured area 34.317% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
This is the one for a feature showcase grid or a portfolio, anywhere the same card shape repeats across several slots.
<div class="relative overflow-hidden rounded-card bg-ink p-4 shadow-glow">
<span class="corner corner-sm corner-tl" aria-hidden="true"></span>
<span class="corner corner-sm corner-tr" aria-hidden="true"></span>
<span class="corner corner-sm corner-bl" aria-hidden="true"></span>
<span class="corner corner-sm corner-br" aria-hidden="true"></span>
<div class="scan-sweep absolute inset-0 pointer-events-none" style="animation-delay:-.5s"></div>
<span class="relative mb-2 block text-right text-[10px] font-bold text-data/85">#A1</span>
<span class="relative mb-3 block h-5 w-5 rounded border border-accent bg-accent/20"></span>
<p class="relative text-sm font-bold">Track</p>
</div>
04System-log list
What moves
Each row carries a timestamp prefix, and a bright scan bar sweeps down through the rows one at a time, leaving an OK mark behind it.

<main class="stage">
<div class="loglist is-demo">
<ul class="loglist__rows">
<li class="loglist__row"><span class="loglist__ts">00:01</span><span class="loglist__text">User authentication verified</span><span class="loglist__ok">OK</span></li>
<li class="loglist__row"><span class="loglist__ts">00:02</span><span class="loglist__text">Location data synced</span><span class="loglist__ok">OK</span></li>
<li class="loglist__row"><span class="loglist__ts">00:03</span><span class="loglist__text">Anomaly pattern scan</span><span class="loglist__ok">OK</span></li>
<li class="loglist__row"><span class="loglist__ts">00:04</span><span class="loglist__text">Backup server connected</span><span class="loglist__ok">OK</span></li>
</ul>
<p class="loglist__cursorline">READY <span class="loglist__cursor" aria-hidden="true"></span></p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 스캔 바가 줄 4개를 순서대로 내려가는 한 주기
$easing: $ease-spring; // 스캔 바가 밝아지는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-paper);
.loglist {
box-sizing: border-box;
width: clamp(240px, 84vw, 360px);
padding: $sp-4;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
// 320px 폰 체크: 로그 4줄 + 커서줄이 129·130 형제 값 그대로면 200px 높이를 넘는다
// (131-04 실측: scrollHeight 223px vs innerHeight 200px). padding·행간을 한 단계씩 좁힌다.
}
.loglist__rows {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-1;
}
.loglist__row {
position: relative;
display: flex;
align-items: center;
gap: $sp-3;
padding: $sp-1 $sp-3;
border-radius: $r-xs;
animation: rowScan $duration $easing infinite;
}
.loglist__row:nth-child(1) { animation-delay: 0s; }
.loglist__row:nth-child(2) { animation-delay: -.5s; }
.loglist__row:nth-child(3) { animation-delay: -1s; }
.loglist__row:nth-child(4) { animation-delay: -1.5s; }
@keyframes rowScan {
0%, 100% { box-shadow: inset 0 0 0 1px rgba($color, 0), inset 0 0 0 0 rgba($color, 0); }
5% { box-shadow: inset 0 0 0 1px rgba($color, .5), inset 0 0 16px 0 rgba($color, .18); }
15% { box-shadow: inset 0 0 0 1px rgba($color, 0), inset 0 0 0 0 rgba($color, 0); }
}
.loglist__ts {
flex: none;
color: rgba($color, .7);
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
}
.loglist__text {
flex: 1 1 auto;
min-width: 0;
color: $subject-cream;
font-weight: 600;
font-size: clamp(11px, 2.6vw, 12px);
line-height: 1.3;
font-family: inherit;
}
.loglist__ok {
flex: none;
opacity: 0;
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .06em;
animation: rowOk $duration $easing infinite;
}
.loglist__row:nth-child(1) .loglist__ok { animation-delay: 0s; }
.loglist__row:nth-child(2) .loglist__ok { animation-delay: -.5s; }
.loglist__row:nth-child(3) .loglist__ok { animation-delay: -1s; }
.loglist__row:nth-child(4) .loglist__ok { animation-delay: -1.5s; }
@keyframes rowOk {
0%, 8% { opacity: 0; }
14%, 100% { opacity: 1; }
}
.loglist__cursorline {
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;
}
.loglist__cursor {
display: inline-block;
width: 6px;
height: 10px;
background: $color;
vertical-align: -1px;
animation: blink $dur-loop steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .2; }
}
@media (prefers-reduced-motion: reduce) {
.loglist__row, .loglist__ok, .loglist__cursor {
animation: none !important;
}
.loglist__row { box-shadow: inset 0 0 0 1px rgba($color, 0); }
.loglist__ok { opacity: 1; }
.loglist__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: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.loglist {
box-sizing: border-box;
width: clamp(240px, 84vw, 360px);
padding: 16px;
border-radius: 16px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4), 0 0 24px rgba(255, 210, 63, 0.16);
}
.loglist__rows {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
}
.loglist__row {
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 4px 12px;
border-radius: 4px;
animation: rowScan 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.loglist__row:nth-child(1) {
animation-delay: 0s;
}
.loglist__row:nth-child(2) {
animation-delay: -0.5s;
}
.loglist__row:nth-child(3) {
animation-delay: -1s;
}
.loglist__row:nth-child(4) {
animation-delay: -1.5s;
}
@keyframes rowScan {
0%, 100% {
box-shadow: inset 0 0 0 1px rgba(255, 210, 63, 0), inset 0 0 0 0 rgba(255, 210, 63, 0);
}
5% {
box-shadow: inset 0 0 0 1px rgba(255, 210, 63, 0.5), inset 0 0 16px 0 rgba(255, 210, 63, 0.18);
}
15% {
box-shadow: inset 0 0 0 1px rgba(255, 210, 63, 0), inset 0 0 0 0 rgba(255, 210, 63, 0);
}
}
.loglist__ts {
flex: none;
color: rgba(255, 210, 63, 0.7);
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
}
.loglist__text {
flex: 1 1 auto;
min-width: 0;
color: #fff7e6;
font-weight: 600;
font-size: clamp(11px, 2.6vw, 12px);
line-height: 1.3;
font-family: inherit;
}
.loglist__ok {
flex: none;
opacity: 0;
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.06em;
animation: rowOk 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
.loglist__row:nth-child(1) .loglist__ok {
animation-delay: 0s;
}
.loglist__row:nth-child(2) .loglist__ok {
animation-delay: -0.5s;
}
.loglist__row:nth-child(3) .loglist__ok {
animation-delay: -1s;
}
.loglist__row:nth-child(4) .loglist__ok {
animation-delay: -1.5s;
}
@keyframes rowOk {
0%, 8% {
opacity: 0;
}
14%, 100% {
opacity: 1;
}
}
.loglist__cursorline {
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;
}
.loglist__cursor {
display: inline-block;
width: 6px;
height: 10px;
background: #ffd23f;
vertical-align: -1px;
animation: blink 2s steps(1) infinite;
}
@keyframes blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.2;
}
}
@media (prefers-reduced-motion: reduce) {
.loglist__row, .loglist__ok, .loglist__cursor {
animation: none !important;
}
.loglist__row {
box-shadow: inset 0 0 0 1px rgba(255, 210, 63, 0);
}
.loglist__ok {
opacity: 1;
}
.loglist__cursor {
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./FeatureListHud.module.scss";
export type FeatureListHudRow = { ts: string; text: string };
export type FeatureListHudProps = {
rows?: FeatureListHudRow[];
ok?: string;
ready?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const defaultRows: FeatureListHudRow[] = [
{ ts: "00:01", text: "사용자 인증 확인" },
{ ts: "00:02", text: "위치 데이터 동기화" },
{ ts: "00:03", text: "이상 패턴 스캔" },
{ ts: "00:04", text: "백업 서버 연결" },
];
export default function FeatureListHud({
rows = defaultRows,
ok = "OK",
ready = "READY",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: FeatureListHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.list} style={vars}>
<ul className={styles.rows}>
{rows.map((row) => (
<li key={row.ts} className={styles.row}>
<span className={styles.ts}>{row.ts}</span>
<span className={styles.text}>{row.text}</span>
<span className={styles.ok}>{ok}</span>
</li>
))}
</ul>
<p className={styles.cursorline}>
{ready} <span className={styles.cursor} aria-hidden="true" />
</p>
</div>
);
}04 · System-log list — Measured area 9.292% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
It suits a feature list or an onboarding checklist — anywhere you want visitors to feel a sequence of checks finishing in order.
<ul class="flex flex-col gap-1">
<li class="row-scan flex items-center gap-3 rounded px-3 py-2">
<span class="flex-none text-[10px] font-bold text-accent/70">00:01</span>
<span class="min-w-0 flex-1 text-sm font-semibold">User authentication verified</span>
<span class="flex-none text-[10px] font-bold text-accent">OK</span>
</li>
<li class="row-scan flex items-center gap-3 rounded px-3 py-2" style="animation-delay:-.5s">
<span class="flex-none text-[10px] font-bold text-accent/70">00:02</span>
<span class="min-w-0 flex-1 text-sm font-semibold">Location data synced</span>
<span class="flex-none text-[10px] font-bold text-accent">OK</span>
</li>
</ul>
<p class="mt-3 text-[10px] text-accent/70">READY <span class="blink-dot inline-block h-2.5 w-1.5 bg-accent"></span></p>
05Locked-target plan
What moves
Only the recommended plan's brackets pulse like a breathing lock and wear a LOCKED badge, while the other card's opacity drops so it looks like it's fallen out of focus.

<main class="stage">
<div class="pricing is-demo">
<article class="pricing__card pricing__card--dim">
<p class="pricing__name">Light</p>
<p class="pricing__price">Free</p>
<ul class="pricing__feats">
<li>Basic scan</li>
<li>1 channel</li>
</ul>
</article>
<article class="pricing__card pricing__card--locked">
<span class="pricing__badge">LOCKED</span>
<span class="pricing__corner pricing__corner--tl" aria-hidden="true"></span>
<span class="pricing__corner pricing__corner--tr" aria-hidden="true"></span>
<span class="pricing__corner pricing__corner--bl" aria-hidden="true"></span>
<span class="pricing__corner pricing__corner--br" aria-hidden="true"></span>
<p class="pricing__name">Pro</p>
<p class="pricing__price">$9/mo</p>
<ul class="pricing__feats">
<li>Real-time tracking</li>
<li>Unlimited channels</li>
</ul>
</article>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 브래킷 맥동 + 글로우 호흡 한 주기
$easing: $ease-spring; // 잠금이 숨 쉬는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-ink);
.pricing {
display: flex;
align-items: flex-start;
gap: $sp-4;
width: clamp(260px, 84vw, 420px);
}
.pricing__card {
position: relative;
box-sizing: border-box;
min-width: 0;
// flex-basis 고정 160px + flex-wrap:wrap 조합은 320px 폰 체크에서 84vw(268.8px) 폭에
// 카드 2장(160+160+gap)이 안 들어가 줄바꿈되어 세로로 쌓인다 — 높이가 200px 을 훌쩍 넘겼다
// (131-05 실측: docEl.scrollHeight 311px vs innerHeight 200px). 129·130 형제 항목처럼
// wrap 없이 flex:1 로 균등 분배해 항상 한 줄에 나란히 놓는다.
flex: 1 1 0;
padding: $sp-5 $sp-4;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
}
.pricing__card--dim {
opacity: .55;
}
.pricing__card--locked::before {
content: "";
position: absolute;
inset: -1px;
border-radius: inherit;
pointer-events: none;
box-shadow: 0 0 0 1px rgba($color, .6), 0 0 32px rgba($color, .4);
opacity: .4;
animation: glowBreathe $duration $easing infinite;
}
@keyframes glowBreathe {
0%, 100% { opacity: .4; }
50% { opacity: 1; }
}
.pricing__badge {
position: relative;
display: inline-block;
margin: 0 0 $sp-2;
padding: $sp-1 $sp-2;
border-radius: $r-xs;
background: rgba($color, .16);
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .08em;
}
.pricing__corner {
position: absolute;
width: 16px;
height: 16px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.pricing__corner--tl { top: $sp-2; left: $sp-2; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.pricing__corner--tr { top: $sp-2; right: $sp-2; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.pricing__corner--bl { bottom: $sp-2; left: $sp-2; border-bottom-width: 2px; border-left-width: 2px; transform-origin: bottom left; animation-delay: -.3s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.pricing__corner--br { bottom: $sp-2; right: $sp-2; 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; }
}
.pricing__name {
position: relative;
margin: 0 0 $sp-1;
color: $subject-cream;
font-weight: 700;
font-size: clamp(13px, 3vw, 15px);
line-height: 1.2;
font-family: inherit;
}
.pricing__price {
position: relative;
margin: 0 0 $sp-3;
color: rgba($subject-cream, .6);
font-weight: 600;
font-size: clamp(11px, 2.6vw, 12px);
line-height: 1.2;
font-family: inherit;
}
// dim 카드는 .pricing__card--dim 의 opacity .55 가 글자 알파에 곱해진다 — 기본 .6/.75 는 실효 .33/.41 이라 2.89~3.82:1.
// dim 카드에서만 알파를 빼 실효 .55 (5.85:1) 로 올린다. 밝은 카드의 위계(.6/.75)는 그대로 둔다
.pricing__card--dim .pricing__price,
.pricing__card--dim .pricing__feats { color: $subject-cream; }
.pricing__feats {
position: relative;
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-1;
color: rgba($subject-cream, .75);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.5;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.pricing__card--locked::before, .pricing__corner {
animation: none !important;
}
.pricing__card--locked::before { opacity: 1; }
.pricing__corner { transform: scale(1); 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);
}
.pricing {
display: flex;
align-items: flex-start;
gap: 16px;
width: clamp(260px, 84vw, 420px);
}
.pricing__card {
position: relative;
box-sizing: border-box;
min-width: 0;
flex: 1 1 0;
padding: 20px 16px;
border-radius: 16px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4), 0 0 24px rgba(255, 210, 63, 0.16);
}
.pricing__card--dim {
opacity: 0.55;
}
.pricing__card--locked::before {
content: "";
position: absolute;
inset: -1px;
border-radius: inherit;
pointer-events: none;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.6), 0 0 32px rgba(255, 210, 63, 0.4);
opacity: 0.4;
animation: glowBreathe 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes glowBreathe {
0%, 100% {
opacity: 0.4;
}
50% {
opacity: 1;
}
}
.pricing__badge {
position: relative;
display: inline-block;
margin: 0 0 8px;
padding: 4px 8px;
border-radius: 4px;
background: rgba(255, 210, 63, 0.16);
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.08em;
}
.pricing__corner {
position: absolute;
width: 16px;
height: 16px;
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;
}
.pricing__corner--tl {
top: 8px;
left: 8px;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.pricing__corner--tr {
top: 8px;
right: 8px;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
.pricing__corner--bl {
bottom: 8px;
left: 8px;
border-bottom-width: 2px;
border-left-width: 2px;
transform-origin: bottom left;
animation-delay: -0.3s;
}
.pricing__corner--br {
bottom: 8px;
right: 8px;
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;
}
}
.pricing__name {
position: relative;
margin: 0 0 4px;
color: #fff7e6;
font-weight: 700;
font-size: clamp(13px, 3vw, 15px);
line-height: 1.2;
font-family: inherit;
}
.pricing__price {
position: relative;
margin: 0 0 12px;
color: rgba(255, 247, 230, 0.6);
font-weight: 600;
font-size: clamp(11px, 2.6vw, 12px);
line-height: 1.2;
font-family: inherit;
}
.pricing__card--dim .pricing__price,
.pricing__card--dim .pricing__feats {
color: #fff7e6;
}
.pricing__feats {
position: relative;
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
color: rgba(255, 247, 230, 0.75);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.5;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.pricing__card--locked::before, .pricing__corner {
animation: none !important;
}
.pricing__card--locked::before {
opacity: 1;
}
.pricing__corner {
transform: scale(1);
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./PricingHud.module.scss";
export type PricingHudProps = {
dimName?: string;
dimPrice?: string;
dimFeats?: string[];
lockedName?: string;
lockedPrice?: string;
lockedFeats?: string[];
badge?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function PricingHud({
dimName = "라이트",
dimPrice = "무료",
dimFeats = ["기본 스캔", "채널 1개"],
lockedName = "프로",
lockedPrice = "월 9,900원",
lockedFeats = ["실시간 추적", "채널 무제한"],
badge = "LOCKED",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: PricingHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.pricing} style={vars}>
<article className={`${styles.card} ${styles.dim}`}>
<p className={styles.name}>{dimName}</p>
<p className={styles.price}>{dimPrice}</p>
<ul className={styles.feats}>
{dimFeats.map((f) => <li key={f}>{f}</li>)}
</ul>
</article>
<article className={`${styles.card} ${styles.locked}`}>
<span className={styles.badge}>{badge}</span>
<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" />
<p className={styles.name}>{lockedName}</p>
<p className={styles.price}>{lockedPrice}</p>
<ul className={styles.feats}>
{lockedFeats.map((f) => <li key={f}>{f}</li>)}
</ul>
</article>
</div>
);
}05 · Locked-target plan — Measured area 5.45% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
Pull it out for a two-tier pricing table or any free-versus-paid comparison where one option should clearly win the eye.
<div class="glow-breathe relative flex-1 basis-40 rounded-card bg-ink p-5 shadow-glow">
<span class="corner corner-sm corner-tl" aria-hidden="true"></span>
<span class="corner corner-sm corner-tr" aria-hidden="true"></span>
<span class="corner corner-sm corner-bl" aria-hidden="true"></span>
<span class="corner corner-sm corner-br" aria-hidden="true"></span>
<span class="mb-2 inline-block rounded bg-accent/15 px-2 py-1 text-[10px] font-bold tracking-widest text-accent">LOCKED</span>
<p class="mb-1 text-base font-bold">Pro</p>
<p class="mb-3 text-xs text-cream/60">$9/mo</p>
<ul class="flex flex-col gap-1 text-xs text-cream/75">
<li>Real-time tracking</li>
<li>Unlimited channels</li>
</ul>
</div>
06Face-scan quote
What moves
A face-recognition box sweeps once across the avatar, and the quote card wears a bracket frame with a VERIFIED tag.

<main class="stage">
<div class="testimonial is-demo">
<div class="testimonial__avatar" aria-hidden="true">
<span class="testimonial__scanbox"></span>
</div>
<figure class="testimonial__card">
<span class="testimonial__corner testimonial__corner--tl" aria-hidden="true"></span>
<span class="testimonial__corner testimonial__corner--tr" aria-hidden="true"></span>
<span class="testimonial__corner testimonial__corner--bl" aria-hidden="true"></span>
<span class="testimonial__corner testimonial__corner--br" aria-hidden="true"></span>
<span class="testimonial__verified">VERIFIED</span>
<blockquote class="testimonial__quote">Nothing slips through, so I can relax.</blockquote>
<figcaption class="testimonial__author">Lee Do-yoon · Security Team Manager</figcaption>
</figure>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 얼굴 인식 상자가 한 번 훑고 멈추는 한 주기
$easing: $ease-spring; // 스캔 상자가 내려가는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-yellow);
.testimonial {
display: flex;
align-items: center;
gap: $sp-4;
width: clamp(260px, 84vw, 420px);
}
.testimonial__avatar {
position: relative;
flex: none;
width: 64px;
height: 64px;
border-radius: $r-control;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4);
overflow: hidden;
}
.testimonial__scanbox {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 3px;
background: rgba($color, .85);
box-shadow: 0 0 8px 1px rgba($color, .6);
animation: faceScan $duration $easing infinite;
}
@keyframes faceScan {
0% { transform: translateY(0); opacity: 1; }
20% { transform: translateY(61px); opacity: 1; }
28% { transform: translateY(61px); opacity: 0; }
100% { transform: translateY(61px); opacity: 0; }
}
.testimonial__card {
position: relative;
box-sizing: border-box;
min-width: 0;
// flex-wrap:wrap + flex-basis 220px 는 320px 폰 체크(84vw=268.8px)에서 아바타(64px)+gap+220px 가
// 안 들어가 줄바꿈 위험이 있다 (131-06 실측: scrollHeight 203px vs innerHeight 200px). 129 형제처럼
// wrap 없이 flex:1 로 균등 분배해 항상 한 줄에 나란히 놓는다.
flex: 1 1 0;
margin: 0;
padding: $sp-4;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
}
.testimonial__corner {
position: absolute;
width: 14px;
height: 14px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.testimonial__corner--tl { top: $sp-2; left: $sp-2; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.testimonial__corner--tr { top: $sp-2; right: $sp-2; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.testimonial__corner--bl { bottom: $sp-2; left: $sp-2; border-bottom-width: 2px; border-left-width: 2px; transform-origin: bottom left; animation-delay: -.3s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.testimonial__corner--br { bottom: $sp-2; right: $sp-2; border-bottom-width: 2px; border-right-width: 2px; transform-origin: bottom right; animation-delay: -.45s; } // 규칙8: 괄호 꼭짓점 모서리 고정
@keyframes cornerPulse {
0%, 100% { transform: scale(.7); opacity: .5; }
50% { transform: scale(1); opacity: 1; }
}
.testimonial__verified {
position: relative;
display: inline-block;
margin: 0 0 $sp-2;
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .08em;
}
.testimonial__quote {
position: relative;
margin: 0 0 $sp-2;
color: $subject-cream;
font-weight: 600;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1.4;
font-family: inherit;
}
.testimonial__author {
position: relative;
margin: 0;
color: rgba($subject-cream, .6);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.3;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.testimonial__scanbox, .testimonial__corner {
animation: none !important;
}
.testimonial__scanbox { opacity: 0; }
.testimonial__corner { transform: scale(1); opacity: 1; }
}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);
}
.testimonial {
display: flex;
align-items: center;
gap: 16px;
width: clamp(260px, 84vw, 420px);
}
.testimonial__avatar {
position: relative;
flex: none;
width: 64px;
height: 64px;
border-radius: 12px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4);
overflow: hidden;
}
.testimonial__scanbox {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 3px;
background: rgba(255, 210, 63, 0.85);
box-shadow: 0 0 8px 1px rgba(255, 210, 63, 0.6);
animation: faceScan 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes faceScan {
0% {
transform: translateY(0);
opacity: 1;
}
20% {
transform: translateY(61px);
opacity: 1;
}
28% {
transform: translateY(61px);
opacity: 0;
}
100% {
transform: translateY(61px);
opacity: 0;
}
}
.testimonial__card {
position: relative;
box-sizing: border-box;
min-width: 0;
flex: 1 1 0;
margin: 0;
padding: 16px;
border-radius: 16px;
background: #17141a;
box-shadow: 0 0 0 1px rgba(255, 210, 63, 0.4), 0 0 24px rgba(255, 210, 63, 0.16);
}
.testimonial__corner {
position: absolute;
width: 14px;
height: 14px;
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;
}
.testimonial__corner--tl {
top: 8px;
left: 8px;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.testimonial__corner--tr {
top: 8px;
right: 8px;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
.testimonial__corner--bl {
bottom: 8px;
left: 8px;
border-bottom-width: 2px;
border-left-width: 2px;
transform-origin: bottom left;
animation-delay: -0.3s;
}
.testimonial__corner--br {
bottom: 8px;
right: 8px;
border-bottom-width: 2px;
border-right-width: 2px;
transform-origin: bottom right;
animation-delay: -0.45s;
}
@keyframes cornerPulse {
0%, 100% {
transform: scale(0.7);
opacity: 0.5;
}
50% {
transform: scale(1);
opacity: 1;
}
}
.testimonial__verified {
position: relative;
display: inline-block;
margin: 0 0 8px;
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.08em;
}
.testimonial__quote {
position: relative;
margin: 0 0 8px;
color: #fff7e6;
font-weight: 600;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1.4;
font-family: inherit;
}
.testimonial__author {
position: relative;
margin: 0;
color: rgba(255, 247, 230, 0.6);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.3;
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.testimonial__scanbox, .testimonial__corner {
animation: none !important;
}
.testimonial__scanbox {
opacity: 0;
}
.testimonial__corner {
transform: scale(1);
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./TestimonialHud.module.scss";
export type TestimonialHudProps = {
verified?: string;
quote?: string;
author?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function TestimonialHud({
verified = "VERIFIED",
quote = "놓치는 게 없어서 마음이 편해요",
author = "이도윤 · 보안팀 매니저",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#ffd23f",
}: TestimonialHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.testimonial} style={vars}>
<div className={styles.avatar} aria-hidden="true">
<span className={styles.scanbox} />
</div>
<figure className={styles.card}>
<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" />
<span className={styles.verified}>{verified}</span>
<blockquote className={styles.quote}>{quote}</blockquote>
<figcaption className={styles.author}>{author}</figcaption>
</figure>
</div>
);
}06 · Face-scan quote — Measured area 1.751% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
Testimonials and partner quotes are the natural home for it, anywhere you want a line of copy to read as coming from someone the system has already checked.
<div class="flex flex-wrap items-center gap-4">
<div class="relative h-16 w-16 flex-none overflow-hidden rounded-control bg-ink">
<span class="face-scan absolute left-0 top-0 h-[3px] w-full bg-accent/85"></span>
</div>
<div class="relative min-w-[220px] flex-1 rounded-card bg-ink p-4 shadow-glow">
<span class="corner corner-sm corner-tl" aria-hidden="true"></span>
<span class="corner corner-sm corner-tr" aria-hidden="true"></span>
<span class="mb-2 inline-block text-[10px] font-bold tracking-widest text-accent">VERIFIED</span>
<blockquote class="mb-2 text-sm font-semibold leading-relaxed">Nothing slips through, so I can relax.</blockquote>
<p class="text-xs text-cream/60">Lee Do-yoon · Security Team Manager</p>
</div>
</div>
07Alert band
What moves
A diagonal scanline drifts slowly across the full-width band while the corner brackets pulse, and the button gives a brief glitch shudder every two seconds.

<main class="stage">
<div class="ctaband is-demo">
<span class="ctaband__corner ctaband__corner--tl" aria-hidden="true"></span>
<span class="ctaband__corner ctaband__corner--tr" aria-hidden="true"></span>
<span class="ctaband__corner ctaband__corner--bl" aria-hidden="true"></span>
<span class="ctaband__corner ctaband__corner--br" aria-hidden="true"></span>
<div class="ctaband__scan" aria-hidden="true"></div>
<p class="ctaband__eyebrow">ALERT</p>
<h2 class="ctaband__title">Turn on your watch network right now</h2>
<button class="ctaband__cta" type="button">Start free trial</button>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 대각 스캔 드리프트 + 버튼 글리치 한 주기
$easing: $ease-out; // 스캔이 흐르는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-orange);
.ctaband {
position: relative;
box-sizing: border-box;
width: clamp(280px, 90vw, 520px);
padding: $sp-8 $sp-6;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
overflow: hidden;
}
.ctaband__scan {
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(45deg, rgba($color, 0) 0 6px, rgba($color, .12) 6px 7px);
background-size: 200% 200%;
background-position: 0% 0%;
animation: diagDrift $duration linear infinite;
}
@keyframes diagDrift {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
.ctaband__corner {
position: absolute;
width: 20px;
height: 20px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.ctaband__corner--tl { top: $sp-3; left: $sp-3; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.ctaband__corner--tr { top: $sp-3; right: $sp-3; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.ctaband__corner--bl { bottom: $sp-3; left: $sp-3; border-bottom-width: 2px; border-left-width: 2px; transform-origin: bottom left; animation-delay: -.3s; } // 규칙8: 괄호 꼭짓점 모서리 고정
.ctaband__corner--br { bottom: $sp-3; right: $sp-3; 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; }
}
.ctaband__eyebrow {
position: relative;
margin: 0 0 $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;
}
.ctaband__title {
position: relative;
margin: 0 0 $sp-5;
max-width: 22ch;
color: $subject-cream;
font-weight: 700;
font-size: clamp(18px, 4.4vw, 24px);
line-height: 1.3;
font-family: inherit;
}
.ctaband__cta {
position: relative;
box-sizing: border-box;
border: 1px solid $color;
border-radius: $r-control;
background: rgba($color, .14);
color: $color;
padding: $sp-3 $sp-6;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: .02em;
animation: ctaGlitch $duration linear infinite;
}
@keyframes ctaGlitch {
0%, 59%, 64%, 100% { transform: translateX(0); opacity: 1; }
60% { transform: translateX(-1px); opacity: .85; }
61% { transform: translateX(1px); opacity: 1; }
62% { transform: translateX(-1px); opacity: .9; }
63% { transform: translateX(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.ctaband__scan, .ctaband__corner, .ctaband__cta {
animation: none !important;
}
.ctaband__corner { transform: scale(1); opacity: 1; }
.ctaband__cta { transform: translateX(0); opacity: 1; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ff4d1f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.ctaband {
position: relative;
box-sizing: border-box;
width: clamp(280px, 90vw, 520px);
padding: 32px 24px;
border-radius: 16px;
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;
}
.ctaband__scan {
position: absolute;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(45deg, rgba(255, 210, 63, 0) 0 6px, rgba(255, 210, 63, 0.12) 6px 7px);
background-size: 200% 200%;
background-position: 0% 0%;
animation: diagDrift 2s linear infinite;
}
@keyframes diagDrift {
0% {
background-position: 0% 0%;
}
100% {
background-position: 100% 100%;
}
}
.ctaband__corner {
position: absolute;
width: 20px;
height: 20px;
pointer-events: none;
border-color: #ffd23f;
border-style: solid;
border-width: 0;
animation: cornerPulse 2s ease-out infinite;
}
.ctaband__corner--tl {
top: 12px;
left: 12px;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.ctaband__corner--tr {
top: 12px;
right: 12px;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
.ctaband__corner--bl {
bottom: 12px;
left: 12px;
border-bottom-width: 2px;
border-left-width: 2px;
transform-origin: bottom left;
animation-delay: -0.3s;
}
.ctaband__corner--br {
bottom: 12px;
right: 12px;
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;
}
}
.ctaband__eyebrow {
position: relative;
margin: 0 0 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;
}
.ctaband__title {
position: relative;
margin: 0 0 20px;
max-width: 22ch;
color: #fff7e6;
font-weight: 700;
font-size: clamp(18px, 4.4vw, 24px);
line-height: 1.3;
font-family: inherit;
}
.ctaband__cta {
position: relative;
box-sizing: border-box;
border: 1px solid #ffd23f;
border-radius: 12px;
background: rgba(255, 210, 63, 0.14);
color: #ffd23f;
padding: 12px 24px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.02em;
animation: ctaGlitch 2s linear infinite;
}
@keyframes ctaGlitch {
0%, 59%, 64%, 100% {
transform: translateX(0);
opacity: 1;
}
60% {
transform: translateX(-1px);
opacity: 0.85;
}
61% {
transform: translateX(1px);
opacity: 1;
}
62% {
transform: translateX(-1px);
opacity: 0.9;
}
63% {
transform: translateX(0);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.ctaband__scan, .ctaband__corner, .ctaband__cta {
animation: none !important;
}
.ctaband__corner {
transform: scale(1);
opacity: 1;
}
.ctaband__cta {
transform: translateX(0);
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./CtaBandHud.module.scss";
export type CtaBandHudProps = {
eyebrow?: string;
title?: string;
cta?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function CtaBandHud({
eyebrow = "ALERT",
title = "지금 바로 감시망을 켜보세요",
cta = "무료 체험 시작",
duration = 2000,
easing = "ease-out",
color = "#ffd23f",
}: CtaBandHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.band} 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}>{eyebrow}</p>
<h2 className={styles.title}>{title}</h2>
<button type="button" className={styles.cta}>{cta}</button>
</div>
);
}07 · Alert band — Measured area 48.985% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
It works as a sign-up band or a start-free-trial prompt, a second hook partway down the page.
<div class="relative overflow-hidden rounded-card bg-ink px-8 py-12 shadow-glow">
<span class="corner corner-tl" aria-hidden="true"></span>
<span class="corner corner-tr" aria-hidden="true"></span>
<span class="corner corner-bl" aria-hidden="true"></span>
<span class="corner corner-br" aria-hidden="true"></span>
<div class="scan-diag absolute inset-0 pointer-events-none"></div>
<p class="mb-2 text-[11px] font-bold uppercase tracking-widest text-accent">ALERT</p>
<h2 class="mb-5 max-w-2xl text-2xl font-bold leading-snug sm:text-3xl">Turn on your watch network right now</h2>
<button class="glitch-btn rounded-control border border-accent bg-accent/10 px-6 py-3 text-sm font-bold text-accent">Start free trial</button>
</div>
08Terminal footer
What moves
Faint noise flickers to a new spot every frame over the ink-dark background, the link columns read like data panels, and brackets sit on the top corners.

<main class="stage">
<footer class="footerhud is-demo">
<span class="footerhud__corner footerhud__corner--tl" aria-hidden="true"></span>
<span class="footerhud__corner footerhud__corner--tr" aria-hidden="true"></span>
<div class="footerhud__noise" aria-hidden="true"></div>
<div class="footerhud__cols">
<div class="footerhud__col">
<p class="footerhud__head">SYSTEM</p>
<ul>
<li>Dashboard</li>
<li>Notification settings</li>
</ul>
</div>
<div class="footerhud__col">
<p class="footerhud__head">SUPPORT</p>
<ul>
<li>Contact us</li>
<li>Usage guide</li>
</ul>
</div>
<div class="footerhud__col">
<p class="footerhud__head">LEGAL</p>
<ul>
<li>Terms of service</li>
<li>Privacy policy</li>
</ul>
</div>
</div>
<p class="footerhud__copy">SENTRY © 2026</p>
</footer>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 노이즈 플리커 프레임 스텝의 한 주기
$easing: $ease-out; // 브래킷이 맥동하는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-paper);
.footerhud {
position: relative;
box-sizing: border-box;
width: clamp(260px, 88vw, 460px);
padding: $sp-6 $sp-5 $sp-4;
border-radius: $r-card;
background: $subject-ink;
box-shadow: 0 0 0 1px rgba($color, .4), 0 0 24px rgba($color, .16);
overflow: hidden;
}
.footerhud__noise {
position: absolute;
inset: 0;
pointer-events: none;
opacity: .06;
background: repeating-linear-gradient(45deg, rgba($color, .9) 0 1px, rgba($color, 0) 1px 4px);
background-size: 12px 12px;
animation: noiseFlicker $duration steps(8) infinite;
}
@keyframes noiseFlicker {
0% { background-position: 0 0; }
100% { background-position: 120px 84px; }
}
.footerhud__corner {
position: absolute;
width: 16px;
height: 16px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
animation: cornerPulse $duration $easing infinite;
}
.footerhud__corner--tl { top: $sp-2; left: $sp-2; border-top-width: 2px; border-left-width: 2px; transform-origin: top left; } // 규칙8: 괄호 꼭짓점 모서리 고정
.footerhud__corner--tr { top: $sp-2; right: $sp-2; border-top-width: 2px; border-right-width: 2px; transform-origin: top right; animation-delay: -.15s; } // 규칙8: 괄호 꼭짓점 모서리 고정
@keyframes cornerPulse {
0%, 100% { transform: scale(.55); opacity: .35; }
50% { transform: scale(1); opacity: 1; }
}
.footerhud__cols {
position: relative;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: $sp-3;
margin: 0 0 $sp-4;
}
.footerhud__col {
min-width: 0;
}
.footerhud__head {
margin: 0 0 $sp-2;
color: $color;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .1em;
}
.footerhud__col ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: $sp-1;
}
.footerhud__col li {
color: rgba($subject-cream, .7);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.4;
font-family: inherit;
}
.footerhud__copy {
position: relative;
margin: 0;
padding-top: $sp-3;
border-top: 1px solid rgba($color, .18);
color: rgba($subject-cream, .72); // 잉크 면 위 9.18:1 — .4 는 3.66 으로 미달
font-family: "Courier New", monospace;
font-weight: 400;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: .04em;
}
@media (prefers-reduced-motion: reduce) {
.footerhud__noise, .footerhud__corner {
animation: none !important;
}
.footerhud__noise { opacity: .03; }
.footerhud__corner { transform: scale(1); opacity: 1; }
}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);
}
.footerhud {
position: relative;
box-sizing: border-box;
width: clamp(260px, 88vw, 460px);
padding: 24px 20px 16px;
border-radius: 16px;
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;
}
.footerhud__noise {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.06;
background: repeating-linear-gradient(45deg, rgba(255, 210, 63, 0.9) 0 1px, rgba(255, 210, 63, 0) 1px 4px);
background-size: 12px 12px;
animation: noiseFlicker 2s steps(8) infinite;
}
@keyframes noiseFlicker {
0% {
background-position: 0 0;
}
100% {
background-position: 120px 84px;
}
}
.footerhud__corner {
position: absolute;
width: 16px;
height: 16px;
pointer-events: none;
border-color: #ffd23f;
border-style: solid;
border-width: 0;
animation: cornerPulse 2s ease-out infinite;
}
.footerhud__corner--tl {
top: 8px;
left: 8px;
border-top-width: 2px;
border-left-width: 2px;
transform-origin: top left;
}
.footerhud__corner--tr {
top: 8px;
right: 8px;
border-top-width: 2px;
border-right-width: 2px;
transform-origin: top right;
animation-delay: -0.15s;
}
@keyframes cornerPulse {
0%, 100% {
transform: scale(0.55);
opacity: 0.35;
}
50% {
transform: scale(1);
opacity: 1;
}
}
.footerhud__cols {
position: relative;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin: 0 0 16px;
}
.footerhud__col {
min-width: 0;
}
.footerhud__head {
margin: 0 0 8px;
color: #ffd23f;
font-family: "Courier New", monospace;
font-weight: 700;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.1em;
}
.footerhud__col ul {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 4px;
}
.footerhud__col li {
color: rgba(255, 247, 230, 0.7);
font-weight: 500;
font-size: clamp(10px, 2.4vw, 11px);
line-height: 1.4;
font-family: inherit;
}
.footerhud__copy {
position: relative;
margin: 0;
padding-top: 12px;
border-top: 1px solid rgba(255, 210, 63, 0.18);
color: rgba(255, 247, 230, 0.72);
font-family: "Courier New", monospace;
font-weight: 400;
font-size: clamp(9px, 2.2vw, 10px);
line-height: 1;
letter-spacing: 0.04em;
}
@media (prefers-reduced-motion: reduce) {
.footerhud__noise, .footerhud__corner {
animation: none !important;
}
.footerhud__noise {
opacity: 0.03;
}
.footerhud__corner {
transform: scale(1);
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./FooterHud.module.scss";
export type FooterHudColumn = { head: string; links: string[] };
export type FooterHudProps = {
columns?: FooterHudColumn[];
copy?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
const defaultColumns: FooterHudColumn[] = [
{ head: "SYSTEM", links: ["대시보드", "알림 설정"] },
{ head: "SUPPORT", links: ["문의하기", "이용 가이드"] },
{ head: "LEGAL", links: ["이용약관", "개인정보처리방침"] },
];
export default function FooterHud({
columns = defaultColumns,
copy = "SENTRY © 2026",
duration = 2000,
easing = "ease-out",
color = "#ffd23f",
}: FooterHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<footer className={styles.footer} style={vars}>
<span className={`${styles.corner} ${styles.cornerTl}`} aria-hidden="true" />
<span className={`${styles.corner} ${styles.cornerTr}`} aria-hidden="true" />
<div className={styles.noise} aria-hidden="true" />
<div className={styles.cols}>
{columns.map((col) => (
<div key={col.head} className={styles.col}>
<p className={styles.head}>{col.head}</p>
<ul>
{col.links.map((link) => <li key={link}>{link}</li>)}
</ul>
</div>
))}
</div>
<p className={styles.copy}>{copy}</p>
</footer>
);
}08 · Terminal footer — Measured area 29.669% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
Use it for a multi-column footer or a sitemap-style link dump, anywhere a lot of links need one consistent tone.
<footer class="relative overflow-hidden rounded-t-card bg-ink px-6 pt-8 pb-6 shadow-glow">
<span class="corner corner-tl" aria-hidden="true"></span>
<span class="corner corner-tr" aria-hidden="true"></span>
<div class="noise-flicker absolute inset-0 pointer-events-none"></div>
<div class="relative grid grid-cols-1 gap-6 sm:grid-cols-3">
<div class="min-w-0">
<p class="mb-2 text-[10px] font-bold uppercase tracking-widest text-accent">SYSTEM</p>
<ul class="flex flex-col gap-1 text-xs text-cream/70">
<li>Dashboard</li>
<li>Notification settings</li>
</ul>
</div>
</div>
<p class="relative mt-6 border-t border-accent/20 pt-3 text-[10px] text-cream/40">SENTRY © 2026</p>
</footer>
09Lock-on button, scan input
What moves
Press the button and its corner brackets snap inward and lock; focus the input and its underline grows into an amber glow that spreads like a scan.

<main class="stage">
<form class="formhud is-demo">
<button class="formhud__btn" type="button">
<span class="formhud__corner formhud__corner--tl" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--tr" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--bl" aria-hidden="true"></span>
<span class="formhud__corner formhud__corner--br" aria-hidden="true"></span>
Lock on
</button>
<label class="formhud__field">
<input class="formhud__input" type="email" placeholder="Email address" readonly>
<span class="formhud__caret" aria-hidden="true"></span>
</label>
</form>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 버튼 스냅 + 입력창 포커스 글로우 한 주기
$easing: $ease-pop; // 스냅이 튕기는 곡선
$color: $stage-yellow; // 호박색 터미널 강조색
@include stage($stage-ink);
.formhud {
display: flex;
flex-direction: column;
gap: $sp-4;
width: clamp(220px, 78vw, 300px);
}
.formhud__btn {
position: relative;
box-sizing: border-box;
border: 1px solid $color;
border-radius: $r-control;
background: rgba($color, .14);
color: $color;
padding: $sp-3 $sp-5;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: .02em;
animation: btnSnap $duration $easing infinite;
}
@keyframes btnSnap {
0%, 55%, 100% { transform: scale(1); }
75% { transform: scale(.97); }
}
.formhud__corner {
position: absolute;
width: 10px;
height: 10px;
pointer-events: none;
border-color: $color;
border-style: solid;
border-width: 0;
}
.formhud__corner--tl { top: $sp-1; left: $sp-1; border-top-width: 2px; border-left-width: 2px; animation: snapTl $duration $easing infinite; }
.formhud__corner--tr { top: $sp-1; right: $sp-1; border-top-width: 2px; border-right-width: 2px; animation: snapTr $duration $easing infinite; }
.formhud__corner--bl { bottom: $sp-1; left: $sp-1; border-bottom-width: 2px; border-left-width: 2px; animation: snapBl $duration $easing infinite; }
.formhud__corner--br { bottom: $sp-1; right: $sp-1; border-bottom-width: 2px; border-right-width: 2px; animation: snapBr $duration $easing infinite; }
@keyframes snapTl { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(3px, 3px); } }
@keyframes snapTr { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(-3px, 3px); } }
@keyframes snapBl { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(3px, -3px); } }
@keyframes snapBr { 0%, 55%, 100% { transform: translate(0, 0); } 75% { transform: translate(-3px, -3px); } }
.formhud__field {
position: relative;
display: block;
border-radius: $r-sm;
background: $subject-ink;
box-shadow: 0 1px 0 0 rgba($color, .4);
animation: focusGlow $duration $easing infinite;
}
@keyframes focusGlow {
0%, 55%, 100% { box-shadow: 0 1px 0 0 rgba($color, .4); }
75% { box-shadow: 0 1px 0 0 rgba($color, .9), 0 0 0 3px rgba($color, .25), 0 0 20px rgba($color, .35); }
}
.formhud__input {
box-sizing: border-box;
width: 100%;
border: 0;
background: transparent;
padding: $sp-3 $sp-4;
color: $subject-cream;
font-weight: 500;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
outline: none;
}
.formhud__input::placeholder {
color: rgba($subject-cream, .45);
}
.formhud__caret {
position: absolute;
left: $sp-4;
top: 50%;
width: 2px;
height: 14px;
transform: translateY(-50%);
background: $color;
animation: blink $dur-loop steps(1) infinite;
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: .2; }
}
@media (prefers-reduced-motion: reduce) {
.formhud__btn, .formhud__corner, .formhud__field, .formhud__caret {
animation: none !important;
}
.formhud__btn { transform: scale(1); }
.formhud__corner { transform: translate(0, 0); }
.formhud__field { box-shadow: 0 1px 0 0 rgba($color, .4); }
.formhud__caret { 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);
}
.formhud {
display: flex;
flex-direction: column;
gap: 16px;
width: clamp(220px, 78vw, 300px);
}
.formhud__btn {
position: relative;
box-sizing: border-box;
border: 1px solid #ffd23f;
border-radius: 12px;
background: rgba(255, 210, 63, 0.14);
color: #ffd23f;
padding: 12px 20px;
font-weight: 700;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
letter-spacing: 0.02em;
animation: btnSnap 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes btnSnap {
0%, 55%, 100% {
transform: scale(1);
}
75% {
transform: scale(0.97);
}
}
.formhud__corner {
position: absolute;
width: 10px;
height: 10px;
pointer-events: none;
border-color: #ffd23f;
border-style: solid;
border-width: 0;
}
.formhud__corner--tl {
top: 4px;
left: 4px;
border-top-width: 2px;
border-left-width: 2px;
animation: snapTl 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--tr {
top: 4px;
right: 4px;
border-top-width: 2px;
border-right-width: 2px;
animation: snapTr 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--bl {
bottom: 4px;
left: 4px;
border-bottom-width: 2px;
border-left-width: 2px;
animation: snapBl 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
.formhud__corner--br {
bottom: 4px;
right: 4px;
border-bottom-width: 2px;
border-right-width: 2px;
animation: snapBr 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes snapTl {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(3px, 3px);
}
}
@keyframes snapTr {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(-3px, 3px);
}
}
@keyframes snapBl {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(3px, -3px);
}
}
@keyframes snapBr {
0%, 55%, 100% {
transform: translate(0, 0);
}
75% {
transform: translate(-3px, -3px);
}
}
.formhud__field {
position: relative;
display: block;
border-radius: 8px;
background: #17141a;
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
animation: focusGlow 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes focusGlow {
0%, 55%, 100% {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
}
75% {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.9), 0 0 0 3px rgba(255, 210, 63, 0.25), 0 0 20px rgba(255, 210, 63, 0.35);
}
}
.formhud__input {
box-sizing: border-box;
width: 100%;
border: 0;
background: transparent;
padding: 12px 16px;
color: #fff7e6;
font-weight: 500;
font-size: clamp(12px, 2.8vw, 13px);
line-height: 1;
font-family: inherit;
outline: none;
}
.formhud__input::placeholder {
color: rgba(255, 247, 230, 0.45);
}
.formhud__caret {
position: absolute;
left: 16px;
top: 50%;
width: 2px;
height: 14px;
transform: translateY(-50%);
background: #ffd23f;
animation: blink 2s steps(1) infinite;
}
@keyframes blink {
0%, 49% {
opacity: 1;
}
50%, 100% {
opacity: 0.2;
}
}
@media (prefers-reduced-motion: reduce) {
.formhud__btn, .formhud__corner, .formhud__field, .formhud__caret {
animation: none !important;
}
.formhud__btn {
transform: scale(1);
}
.formhud__corner {
transform: translate(0, 0);
}
.formhud__field {
box-shadow: 0 1px 0 0 rgba(255, 210, 63, 0.4);
}
.formhud__caret {
opacity: 1;
}
}import type { CSSProperties } from "react";
import styles from "./ButtonsInputsHud.module.scss";
export type ButtonsInputsHudProps = {
buttonLabel?: string;
placeholder?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function ButtonsInputsHud({
buttonLabel = "락온 시작",
placeholder = "이메일 주소",
duration = 2000,
easing = "cubic-bezier(.2,1.4,.4,1)",
color = "#ffd23f",
}: ButtonsInputsHudProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<form className={styles.form} style={vars}>
<button type="button" className={styles.btn}>
<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" />
{buttonLabel}
</button>
<label className={styles.field}>
<input className={styles.input} type="email" placeholder={placeholder} readOnly />
<span className={styles.caret} aria-hidden="true" />
</label>
</form>
);
}09 · Lock-on button, scan input — Measured area 7.12% · moving frames 11/23Original by GODRICH · SCSS and React ship in the zip · See the password
In the code
It's built for form submit buttons and email inputs, the exact spot where a visitor's last click deserves a clear, physical-feeling response.
<form class="mx-auto flex max-w-md flex-col gap-3 rounded-card bg-ink p-6 shadow-glow sm:flex-row">
<label class="field-glow relative flex-1 rounded-control bg-ink">
<input class="w-full rounded-control bg-transparent px-4 py-3 text-sm text-cream outline-none" type="email" placeholder="Email address">
</label>
<button class="btn-snap relative rounded-control border border-accent bg-accent/10 px-5 py-3 text-sm font-bold text-accent">
<span class="snap-corner snap-tl" aria-hidden="true"></span>
<span class="snap-corner snap-tr" aria-hidden="true"></span>
<span class="snap-corner snap-bl" aria-hidden="true"></span>
<span class="snap-corner snap-br" aria-hidden="true"></span>
Sign up
</button>
</form>
Custom tailwind.config
Every piece above and the full page pull their color, type, radius, and shadow values from this one config — nowhere else. Change a number here and the whole page follows, from the bracket color to the card corners.
theme: {
extend: {
colors: {
ink: "#17141a",
cream: "#fff7e6",
accent: "#ffd23f",
data: "#2f6df6",
line: "rgba(255,210,63,.12)",
},
fontFamily: {
display: ["'Space Grotesk'", "system-ui", "sans-serif"],
body: ["'Inter'", "system-ui", "sans-serif"],
},
borderRadius: { control: "12px", card: "16px", pill: "999px" },
boxShadow: {
glow: "0 0 0 1px rgba(255,210,63,.4), 0 0 24px rgba(255,210,63,.16)",
"glow-strong": "0 0 0 1px rgba(255,210,63,.6), 0 0 32px rgba(255,210,63,.4)",
},
spacing: { gutter: "16px" },
},
},
There are really only two colors doing any work. The amber accent (#ffd23f) carries every bracket, status dot, and button; the blue data color (#2f6df6) is the one deliberate exception, reserved for coordinates and ID numbers that need to read as "this is a number" rather than "this is UI."
Vibe-Coding Prompt
Drop that config into your project first, and the instruction you hand Claude Code or Cursor can stay this short. The full prompt with every rule spelled out lives in the zip's prompt.md.
Background is ink(#17141a) alone, accent is amber(#ffd23f) alone. Give every card, header,
and CTA corner an L-shaped bracket that pulses softly in accent. Use monospace only for
coordinates, timestamps, and status text — leave headings and body copy on the regular
fonts. Stick to three radii: rounded-card, rounded-pill, rounded-control.
I ran that instruction against page/index.html and captured the full page at 1280px wide.

Where it breaks (the trap)
sci-fi hud design — where does it break?
The most common trap is setting a max-width on a big heading and never checking it at more than one screen size. Section 07's alert band had this exact bug: with max-w-sm (384px) on the heading, the headline text wrapped so that a single short word got orphaned alone on its own line — and it did that even on a 1280px desktop, because the cause was the max-width value itself, not the viewport. Testing only narrow screens would never have caught it. Switching to max-w-2xl fixed the heading at all three widths we checked (1280, 768, and 375px). The second trap sits in section 06's face-scan quote: the distance the scan box travels (61px) is hardcoded from the avatar's height (64px) minus the box's own thickness, in raw pixels — resize the avatar and you have to redo that subtraction by hand.
The next snag
Monospace readouts lose their column alignment first when they move into an email body, and that limit is mapped out in Marketing Email Design. The zip bundling all nine files, the react port, and the full prompt is locked, and the password is t2nt5x9v — the download button is below.
A third trap shows up in section 03's card grid, where the scan order is set with nth-child delays. Add or remove a card from the four-card layout and the animation-delay sequence has to be re-matched by hand, or the scan starts overlapping cards or skipping a slot.
Accessibility
What we checked
All nine demos wrap their motion in the prefers-reduced-motion media query, as documented on MDN, so a visitor who has that setting on sees a locked, static end-state — brackets at full size, dots and carets always lit — instead of the scanning and glitching. The bracket, scanline, and noise layers are all marked aria-hidden="true" since they're decoration, and status words like LOCKED, VERIFIED, and LIVE are carried in text, not color alone, so they still read for colorblind visitors.
Where to go next
If a different look catches your eye, the design trend index has the rest, and the about page explains how each piece gets checked before it's posted.
FAQ
Is this cyberpunk ui design copied from a real game or movie HUD?
No. Nothing here reproduces a specific game, film, or product's screen — it borrows general surveillance-console visual language (bracket lock-on, scanlines, monospace readouts) that shows up across dozens of sci-fi interfaces. SENTRY, the service name used throughout, is a placeholder we made up for the demo.
Can this dark tone work on a light site?
Only partway, and we wouldn't recommend forcing it. The HUD/terminal feel depends on brackets and scanlines standing out against a dark ground; flip the background light and you'd need to push the accent color much harder for the same contrast, which starts reading as a warning or error state instead. A kit that assumes a light ground from the very first section is the Pastel Website Design Kit instead.
Does this work without Tailwind, in plain CSS?
Yes — link the tokens.css file that ships in the zip. It's the same values from tailwind.config.js carried over as CSS variables (--color-ink, --color-accent, and so on), so referencing var(--color-accent) directly gets you the same amber without Tailwind in the project at all.









