033 · Genesis Test Lab

Space HUD 테마

우주정거장 인물소개 화면에 쓴 디자인 요소를 뜯어 정리했다. 팔레트·효과·애니메이션 전부 의존성 0의 순수 CSS이며, 각 블록은 그대로 복사해 쓸 수 있다.

← 카탈로그로

01 · Palette

어두운 바탕에 두 개의 강조색

우주정거장 내부는 따뜻한 실내 조명(amber)차가운 데이터·창밖(cyan) 두 광원이 대비를 만든다. 이 둘만 강조색으로 쓰고 나머지는 전부 무채색으로 눌러야 화면이 정리된다. 강조색을 셋 이상 쓰면 HUD 가 아니라 크리스마스 트리가 된다.

ink
#05070c
최심부 배경
panel
#0d1219
패널·코드블록
line
#26303f
경계선
dim
#8b97a8
보조 텍스트
txt
#e8edf5
본문
amber
#ffb547
실내 조명·직급
cyan
#5fd4ff
데이터·HUD 코드
violet
#a78bfa
홀로그램 보조
tokens.css
:root{
  --ink:#05070c;    /* 배경 최심부 */
  --panel:#0d1219;  /* 패널 */
  --line:#26303f;   /* 경계선 */
  --dim:#8b97a8;    /* 보조 텍스트 */
  --txt:#e8edf5;    /* 본문 */
  --amber:#ffb547;  /* 강조 1 — 따뜻한 실내광 */
  --cyan:#5fd4ff;   /* 강조 2 — 차가운 데이터광 */
  --violet:#a78bfa; /* 홀로그램 보조 */
}

02 · Portal

회전 글로우 링 + 눈금 틱

인물 초상을 담는 원형 프레임. 핵심은 conic-gradient 를 mask 로 도넛만 남기는 것이다. 안쪽을 마스크로 뚫어야 내용물을 가리지 않고 테두리만 빛난다. filter:blur 로 번지게 하면 발광처럼 보인다.

PORTAL
회전 링
눈금 틱 36
HOLO
홀로 시머
portal.css
/* 도넛 글로우 — 안쪽을 mask 로 뚫는 게 핵심 */
.ring::before{
  content:""; position:absolute; inset:-9px; border-radius:50%;
  background:conic-gradient(from 0deg,transparent 0deg,
    var(--cyan) 40deg,#fff 70deg,var(--amber) 110deg,transparent 170deg);
  filter:blur(6px);
  animation:ringSpin 9s linear infinite;
  mask:radial-gradient(circle,transparent 0 49.2%,#000 49.6%);
}
@keyframes ringSpin{ to{ rotate:360deg } }

/* 눈금 틱 — transform-origin 을 반지름만큼 내려 방사 배치 */
.ticks i{
  position:absolute; left:50%; top:0;
  width:2px; height:9px; background:rgba(255,255,255,.34);
  transform-origin:50% 77px;   /* = 원 반지름 */
}
// JS: 36개를 10도 간격으로
for(let i=0;i<36;i++){ t.style.rotate = i*10+'deg'; }

/* 홀로 시머 — 넓은 그라디언트를 가로로 흘린다 */
.holo::after{
  background:linear-gradient(115deg,transparent 34%,
    rgba(167,139,250,.28) 47%,rgba(95,212,255,.3) 53%,transparent 66%);
  background-size:280% 100%;
  animation:holo 3.4s linear infinite;
}
@keyframes holo{ to{ background-position:-280% 0 } }

03 · Atmosphere

비네트 · 스캔라인 · 라이트 스윕

배경이 그냥 이미지로 보이지 않게 만드는 세 겹. 비네트가 시선을 중앙으로 모으고, 스캔라인이 화면에 질감을 주고, 라이트 스윕이 정지된 화면에 생기를 넣는다. 전부 pointer-events:none 인 오버레이라 클릭을 방해하지 않는다.

3겹 합성
배경 드리프트
HUD FRAME
코너 브래킷
atmosphere.css
/* 비네트 — 가장자리를 눌러 시선을 중앙으로 */
.vignette{
  position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(ellipse at 50% 45%,
    transparent 30%,rgba(0,0,0,.55) 72%,rgba(0,0,0,.88) 100%);
}
/* 스캔라인 — 1px 선을 3px 간격으로. overlay 로 은은하게 */
.grain{
  opacity:.05; mix-blend-mode:overlay;
  background-image:repeating-linear-gradient(0deg,#fff 0 1px,transparent 1px 3px);
}
/* 라이트 스윕 — 화면을 가로지르는 빛 */
@keyframes sweep{ 0%{left:-40%} 55%,100%{left:120%} }

/* 배경 드리프트 — 아주 느린 확대. 정지컷을 살아있게 */
@keyframes bgDrift{ from{transform:scale(1.06)} to{transform:scale(1.13)} }
.bg.on{ animation:bgDrift 14s ease-out forwards }

/* 코너 브래킷 — 네 모서리에 ㄱ 자 */
.hudbox b{ position:absolute; width:16px; height:16px;
  border:2px solid var(--cyan) }
.hudbox b:nth-child(1){ top:-1px; left:-1px; border-right:0; border-bottom:0 }

04 · Typography in motion

글자별 stagger · 그려지는 선

이름이 한 글자씩 blur 가 풀리며 올라온다. 글자당 0.085초씩 밀면 3글자 이름이 약 0.25초에 완성돼 읽는 리듬과 맞는다. 아래 데모를 클릭하면 다시 재생된다.

클릭 → 재생
그려지는 구분선
한글 자간 주의 — 영문 기준 자간(.26em)을 한글에 그대로 쓰면 "전 동 화 소 음 진 동"처럼 글자가 흩어져 읽기 어려워진다. 글자 수에 따라 11자↑ .09em / 8자↑ .14em / 그 외 .2em 으로 줄이는 게 안전하다.
typo-motion.css
/* 글자별 등장 — blur 가 풀리며 올라온다 */
.name span{
  display:inline-block;
  opacity:0; translate:0 34px; filter:blur(9px);
  animation:charIn .62s cubic-bezier(.2,.9,.25,1) forwards;
}
@keyframes charIn{ to{ opacity:1; translate:0 0; filter:blur(0) } }

// JS: 글자당 0.085초씩 지연
[...name].forEach((ch,k)=>{
  s.textContent = ch;
  s.style.animationDelay = (0.55 + k*0.085) + 's';
});

/* 좌우로 그려지는 구분선 */
.bar{ width:0; height:2px;
  background:linear-gradient(90deg,transparent,var(--amber),transparent) }
@keyframes barGrow{ to{ width:330px } }

/* 한글 자간 — 길이에 따라 자동 조절 */
ko.style.setProperty('--ts',
  body.length >= 11 ? '.09em' : body.length >= 8 ? '.14em' : '.2em');

05 · Circular video crop

세로 영상을 원형에 담고, 얼굴을 자동 정렬

720×1280 세로 영상을 원형 프레임에 넣으면 object-fit:cover 가 위아래를 잘라낸다. 어디를 남길지는 object-position, 얼마나 당길지는 transform:scale 이 정한다. 사람마다 손으로 맞추면 절대 균일해지지 않으므로 얼굴 검출로 역산했다.

circular-crop.css
.disc{ width:100%; aspect-ratio:1; border-radius:50%;
  overflow:hidden; clip-path:circle(50%) }
.disc video{ width:100%; height:100%; object-fit:cover }

/* 인물마다 다른 두 값 — 자동 계산한다 */
video.style.objectPosition = `50% ${focus}%`;
video.style.scale = scale;

/* 원 안쪽 하단을 눌러 이름과 자연스럽게 연결 */
.disc .fade{ position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(to bottom,transparent 58%,rgba(5,7,12,.75) 100%) }
focus / scale 역산식R 은 영상 세로/가로 비. cover 로 영상은 원 지름 D 폭에 맞춰지고 세로가 D·R 이 되어 넘친다.
t0 = -D·(R-1)·p/100 · y_screen = D/2 + (t0 + y·D·R - D/2)·s
목표 얼굴 위치·크기를 넣고 p(focus)와 s(scale)에 대해 풀면 모든 인원의 얼굴이 같은 자리·같은 크기에 놓인다. p 가 음수로 나와도 정상이다 — object-position 은 음수 % 를 받는다.

06 · Rules

이 테마를 유지하는 규칙

1. 강조색은 둘까지. amber(따뜻한 실내광) + cyan(차가운 데이터광). 셋째를 넣는 순간 HUD 가 아니게 된다.
2. 움직임은 느리게. 링 9초, 배경 14초, 홀로 3.4초. 빠른 모션은 싸구려로 보인다.
3. 정보는 모서리로. 중앙은 인물, 코드·상태는 네 모서리. HUD 문법이다.
4. 영문은 각지게, 한글은 굵게. Orbitron(라벨·코드) + Gothic A1 900(이름).
5. 오버레이는 클릭을 막지 않는다. 비네트·스캔라인 전부 pointer-events:none.
6. 모션 축소 설정을 존중한다. @media (prefers-reduced-motion:reduce) 로 전부 끈다.

a11y.css
@media (prefers-reduced-motion:reduce){
  *{ animation:none !important; transition:none !important }
}