소개 페이지 전면 재디자인 — Ethereal Glass

design-taste-frontend, high-end-visual-design, redesign-existing-projects 를
로드해 리디자인 모드로 진행했다. 이전 페이지는 디자인 스킬을 파는 자리가
문서처럼 생겼다는 지적을 받았다.

Design Read
- redesign-overhaul / 개발자 대상 / 정밀기기 언어
- DESIGN_VARIANCE 8 · MOTION_INTENSITY 6 · VISUAL_DENSITY 3

방향
- 다크 잉크 바탕에 three.js 셰이더 광원. 유리 패널이 그 위에 뜬다
- 리퀴드 글래스는 이 브리프에서 장식이 아니라 제품 시연이다.
  SVG 필터와 three.js 의 결합 그 자체이기 때문이다
- 밝은 밴드는 조판 섹션 하나뿐. 한글이 종이 위에 있어야 하는 자리다

제품 증명
- 히어로 배경에 셰이더(초기 번들 밖, 첫 페인트 후 지연 로드)
- 재료 섹션에 SVG 필터 3종을 실제로 먹인 타일: 그레인, 굴절, 듀오톤
- 도그푸딩 산출물 2개를 실제 스크린샷으로. 가짜 목업이 아니다

감사에서 걸려 고친 것
- backdrop-filter 에 SVG 를 얹으면 Chrome 이 선언 전체를 버린다.
  CSS.supports 는 참을 주면서 computed 는 none 이 된다
- main 에서 그리드 클래스가 빠져 subgrid 가 붕괴, 가로 444px 오버플로
- 진입 연출이 두 번 콘텐츠를 삼켰다. opacity 를 버리고 transform 만 남겼다
- eyebrow 4개, 제목 em-dash 2건

수치는 불리해진 것도 그대로 싣는다
- 반투명 패널 1개 -> 29개. 유리가 주제가 됐으므로
- three.js 185KB(gzip). 데모라서 JS 예산을 올렸고 그 사실을 페이지에 적었다
This commit is contained in:
Yun Chan 2026-08-20 11:45:38 +09:00
parent 6e05e69ca4
commit be343cdab5
22 changed files with 1695 additions and 439 deletions

View file

@ -5,13 +5,12 @@
*,
*::before,
*::after {
box-sizing: border-box;
}
*::after { box-sizing: border-box; }
html {
-webkit-text-size-adjust: 100%;
scroll-behavior: smooth;
background: var(--surface);
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
@ -28,12 +27,14 @@ body {
font-synthesis-weight: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
overflow-x: hidden;
}
/* 한글은 단어 중간에서 끊지 않는다 */
[lang="ko"] body,
[lang="ko"] p,
[lang="ko"] li,
[lang="ko"] dt,
[lang="ko"] dd,
[lang="ko"] h1,
[lang="ko"] h2,
[lang="ko"] h3 {
@ -41,9 +42,37 @@ body {
overflow-wrap: break-word;
}
/* ---- 그리드 ------------------------------------------------
main 폭은 3단계에서 정한 --measure . 고정값을 쓰지 않는다.
----------------------------------------------------------- */
/* ---- 대기광 -------------------------------------------------
유리는 뒤에 무언가 있어야 유리로 보인다. 페이지 전체에 깔리는
고정 광원 개가 역할을 한다. fixed 스크롤 리페인트가 없다. */
body::before {
content: "";
position: fixed;
inset: 0;
z-index: -2;
pointer-events: none;
background:
radial-gradient(58vw 46vw at 14% -6%,
color-mix(in oklab, var(--accent) 26%, transparent), transparent 68%),
radial-gradient(52vw 44vw at 92% 8%,
color-mix(in oklab, #6E7BFF 20%, transparent), transparent 66%),
radial-gradient(70vw 60vw at 62% 108%,
color-mix(in oklab, var(--accent) 14%, transparent), transparent 70%);
}
/* ---- 필름 그레인 ------------------------------------------- */
body::after {
content: "";
position: fixed;
inset: 0;
z-index: 4;
pointer-events: none;
opacity: 0.05;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
mix-blend-mode: overlay;
}
/* ---- 그리드 ------------------------------------------------ */
.page {
display: grid;
grid-template-columns:
@ -57,15 +86,19 @@ body {
.page > .wide { grid-column: wide; }
.page > .full { grid-column: full; }
/* 섹션 안에서도 같은 그리드를 쓴다 — 정렬선이 페이지 전체에서 하나여야 한다 */
.section {
display: grid;
grid-template-columns: subgrid;
grid-column: full;
padding-block: var(--space-7);
position: relative;
}
.section > * { grid-column: main; }
.section > .wide { grid-column: wide; }
.section > .full { grid-column: full; }
.page > *,
.section > * { min-width: 0; }
/* ---- 타이포 ------------------------------------------------ */
h1, h2, h3 {
@ -75,23 +108,19 @@ h1, h2, h3 {
font-weight: 600;
text-wrap: balance;
}
h1 { font-size: var(--display); font-weight: 620; }
h1 { font-size: var(--display); font-weight: 660; }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-1); }
p { margin: 0 0 var(--space-3); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }
.lead {
font-size: var(--step-1);
color: var(--ink-muted);
}
.lead { font-size: var(--step-1); color: var(--ink-muted); }
/* eyebrow 는 새 크기를 만들지 않는다 — 본문 크기 + 자간 + muted */
.eyebrow {
font-size: var(--step-0);
letter-spacing: var(--tracking-label);
color: var(--ink-muted);
color: var(--accent);
font-weight: 500;
margin: 0 0 var(--space-3);
}
@ -109,48 +138,130 @@ a {
}
a:hover { color: var(--accent); }
/* 포커스는 어떤 이펙트보다 우선한다 */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
border-radius: var(--radius-sm);
}
hr {
border: 0;
border-top: 1px solid var(--line);
margin-block: var(--space-6);
/* ============================================================
유리
backdrop-filter 쓰면 흐린 유리에서 끝난다. 두께와 굴절이
있어야 유리로 읽힌다. 그래서 겹을 쌓는다.
1) 배경 흐림 + 채도 (backdrop-filter)
2) 가장자리 (border + inset shadow)
3) 위쪽 반사 (::before 그라디언트)
굴절은 지원되는 브라우저에서만 backdrop-filter SVG 얹는다.
============================================================ */
.glass {
position: relative;
isolation: isolate;
background: var(--glass-tint);
border: 1px solid var(--glass-edge);
border-radius: var(--radius-md);
box-shadow: var(--glass-hi), var(--shadow-2);
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
/* 위쪽 모서리에 닿는 빛. 유리판의 두께가 여기서 보인다 */
.glass::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
background:
linear-gradient(
180deg,
rgb(255 255 255 / 0.10) 0%,
rgb(255 255 255 / 0.02) 22%,
transparent 55%
);
}
/* backdrop-filter SVG 필터를 얹는 것은 쓰지 않는다.
Chrome CSS.supports 로는 true 주면서 렌더에서 선언 전체를 버린다
(실측: computed backdrop-filter none 된다).
굴절은 재료 섹션의 SVG 타일이 진짜로 보여준다. 여기서는 두께를 만든다. */
/* 가장자리를 도는 빛. 유리판 테두리의 굴절을 흉내낸다 */
.glass::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
padding: 1px;
background: conic-gradient(
from 210deg,
rgb(255 255 255 / 0.32),
transparent 22%,
transparent 58%,
rgb(255 255 255 / 0.20) 74%,
transparent 92%
);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
mask-composite: exclude;
}
/* ---- 표 ---------------------------------------------------- */
table {
border-collapse: collapse;
width: 100%;
font-variant-numeric: tabular-nums;
/* 투명도를 줄여달라는 사용자에게는 불투명 판으로 준다 */
@media (prefers-reduced-transparency: reduce) {
.glass {
background: var(--surface-raised);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
th, td {
text-align: left;
padding: var(--space-2) var(--space-3) var(--space-2) 0;
border-bottom: 1px solid var(--line);
vertical-align: baseline;
}
th {
font-weight: 500;
color: var(--ink-muted);
font-size: var(--step-0);
letter-spacing: var(--tracking-label);
}
/* 좁은 화면에서 표가 페이지를 밀지 않게 하드 게이트 #5.
min-width:0 없으면 grid item 기본 min-width:auto 때문에
overflow-x:auto 걸려 있어도 페이지 자체가 넓어진다. */
.table-scroll {
overflow-x: auto;
min-width: 0;
@supports not (backdrop-filter: blur(1px)) {
.glass { background: var(--surface-raised); }
}
/* 같은 이유로 그리드·플렉스 자식은 전부 줄어들 수 있어야 한다 */
.page > *,
.section > * { min-width: 0; }
/* 유리 위에 얹는 두 번째 판. 안쪽으로 갈수록 밝아진다 */
.glass-2 { background: var(--glass-tint-2); }
/* 이중 베젤: 유리판이 트레이 안에 앉는다 */
.shell {
background: var(--glass-tint);
border: 1px solid var(--glass-edge);
border-radius: var(--radius-shell);
padding: var(--shell-pad);
box-shadow: var(--glass-hi), var(--shadow-2);
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
}
.core {
background: var(--surface-sunken);
border-radius: var(--radius-md);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.06);
}
@media (prefers-reduced-transparency: reduce) {
.shell { background: var(--surface-raised); backdrop-filter: none; }
}
/* ---- 진입 연출 ----------------------------------------------
opacity 콘텐츠를 숨기지 않는다.
페이지에서 데였다. 처음엔 scroll-driven animation
범위 끝에 하단 요소가 도달하지 못해 6단계 3개가 영영 숨었고,
번째는 IntersectionObserver 부드러운 스크롤 중간 지점을
건너뛰어 50개 49개가 숨은 채로 남았다.
그래서 규칙을 바꿨다. 움직이는 것은 위치뿐이다. 최악의 경우에도
요소가 14px 아래에 있을 뿐이고 글자는 항상 읽힌다. */
@media (prefers-reduced-motion: no-preference) {
.reveal {
transform: translateY(14px);
transition: transform var(--dur-slow) var(--ease-soft);
transition-delay: var(--reveal-delay, 0ms);
}
.reveal.is-in { transform: none; }
}
/* ---- 유틸 -------------------------------------------------- */
.visually-hidden {
@ -164,3 +275,5 @@ th {
.muted { color: var(--ink-muted); }
.mono { font-family: var(--font-mono); font-size: var(--mono-size); }
.nums { font-variant-numeric: tabular-nums; }
.table-scroll { overflow-x: auto; min-width: 0; }

View file

@ -1,72 +1,93 @@
/* ============================================================
designpaca 소개 페이지 디자인 토큰
프리셋: swiss-minimal / 비율 1.25 / 강조색 1개 / radius 2종
방향 재설정 2026-08-20: Ethereal Glass
페이지는 SVG 필터와 three.js 파는 제품의 데모다.
리퀴드 글래스는 기술의 결합 자체이므로 여기서는
장식이 아니라 제품 시연이다. 다크 기반을 고른 것도 같은 이유다.
유리는 어두운 바탕 위에서만 굴절이 읽힌다.
============================================================ */
:root {
/* ---- 폰트 패밀리 ---------------------------------------- */
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--mono-size: 0.9375em; /* 부모 대비. 새 스케일 단계를 만들지 않는다 */
--mono-size: 0.9375em;
/* ---- 색: 라이트 (기본) ----------------------------------- */
--surface: #FAF9F6; /* 웜 오프화이트. 순백 아님 (A-07) */
--surface-raised: #F2F0EA;
--surface-inverse: #14161A; /* 다크 밴드. 순흑 아님 */
--ink: #16181C; /* 대비 16.88:1 */
--ink-muted: #5A5F66; /* 대비 6.11:1 */
--ink-inverse: #E9E7E2; /* 밴드 위 14.66:1 */
--ink-muted-inverse: #9AA0A6; /* 밴드 위 6.86:1 */
--line: #DFDCD4; /* 1.30:1 — 장식 구분선 전용 */
--line-inverse: #2A2E34; /* 1.33:1 — 장식 구분선 전용 */
--line-strong: var(--ink-muted); /* 입력·컨트롤 테두리. WCAG 1.4.11 (3:1) */
--accent: #0E6B63; /* 딥 틸. 대비 6.04:1 */
--accent-ink: #FFFFFF; /* 강조 위 글자. 6.36:1 */
--accent-inverse: #3FA89D; /* 밴드 위 강조. 6.30:1 */
--accent-ink-inverse: #0B1A18; /* 밴드 위 강조 위의 글자. 6.43:1 */
/* ---- 바탕: 잉크. 순흑이 아니다 --------------------------- */
--surface: #0B0E12;
--surface-raised: #11151B;
--surface-sunken: #070A0D;
/* 밝은 밴드 하나. 규칙으로 정의된 반전이다 */
--surface-inverse: #F4F2EC;
/* ---- 형태: radius 2종 (게이트 #3) ------------------------ */
--radius-sm: 3px; /* 버튼·입력·배지 */
--radius-md: 6px; /* 카드·코드 블록 */
--ink: #ECEFF2; /* 15.9:1 */
--ink-muted: #98A2AE; /* 7.1:1 */
--ink-inverse: #14181E;
--ink-muted-inverse: #565E68;
--line: #222932;
--line-strong: #39424E;
--line-inverse: #D8D4CA;
/* 강조: 시안 틸. 유리 너머에서도 살아남는 채도 */
--accent: #4FD6C4; /* 잉크 위 10.4:1 */
--accent-ink: #04211D;
--accent-inverse: #0E7A6E;
--accent-ink-inverse: #FFFFFF;
/* ---- 유리 ------------------------------------------------
backdrop-filter 만으로는 흐린 유리다. 굴절은 SVG 만든다. */
--glass-tint: rgb(255 255 255 / 0.045);
--glass-tint-2: rgb(255 255 255 / 0.085);
--glass-edge: rgb(255 255 255 / 0.14);
--glass-blur: 22px;
--glass-sat: 150%;
/* 안쪽 위 모서리에 닿는 빛. 유리 두께를 만든다 */
--glass-hi:
inset 0 1px 0 rgb(255 255 255 / 0.22),
inset 0 -1px 0 rgb(255 255 255 / 0.05);
--shadow-1: 0 1px 2px rgb(0 0 0 / 0.5);
--shadow-2: 0 2px 6px rgb(0 0 0 / 0.4), 0 12px 32px rgb(0 0 0 / 0.5);
--shadow-3: 0 6px 16px rgb(0 0 0 / 0.45), 0 32px 80px rgb(0 0 0 / 0.6);
--inset-hi: var(--glass-hi);
/* ---- 형태 ------------------------------------------------ */
--radius-sm: 10px;
--radius-md: 20px;
--shell-pad: 6px;
--radius-shell: calc(var(--radius-md) + var(--shell-pad));
/* ---- 간격 ------------------------------------------------ */
--space-1: 0.25rem; /* 4 */
--space-2: 0.5rem; /* 8 */
--space-3: 1rem; /* 16 */
--space-4: 1.5rem; /* 24 */
--space-5: 2.5rem; /* 40 */
--space-6: 4rem; /* 64 */
--space-7: 6rem; /* 96 — 일반 섹션 상하 */
--space-8: 8rem; /* 128 — 다크 밴드 상하. 본문 간격의 8배 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2.5rem;
--space-6: 4rem;
--space-7: 7rem;
--space-8: 10rem;
/* ---- 모션 ------------------------------------------------ */
--dur-instant: 100ms; /* 호버·포커스 */
--dur-quick: 200ms; /* 작은 등장·퇴장 */
--dur-normal: 320ms; /* 패널·전환. 2단계 상한 */
--dur-slow: 600ms; /* 이 프로젝트에서는 사용하지 않는다 */
--dur-instant: 120ms;
--dur-quick: 280ms;
--dur-normal: 560ms;
--dur-slow: 900ms;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--ease-out: cubic-bezier(0.32, 0.72, 0, 1);
--ease-soft: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.64, 0, 0.78, 0);
--ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
/* bounce·elastic 없음 (E-02) */
}
/* ---- 색: 다크 테마 — 라이트와 동등하게 정의 ------------------ */
@media (prefers-color-scheme: dark) {
/* ---- 라이트 선호: 유리를 얇게 하고 바탕을 올린다 --------------
테마를 뒤집지 않는다. 페이지는 어두운 유리가 주제다.
다만 라이트를 쓰는 사람에게 대비를 조금 준다. */
@media (prefers-color-scheme: light) {
:root {
--surface: #14161A;
--surface-raised: #1C1F24;
--surface-inverse: #FAF9F6; /* 다크에서는 밴드가 밝아진다 */
--ink: #E9E7E2;
--ink-muted: #9AA0A6;
--ink-inverse: #16181C;
--ink-muted-inverse: #5A5F66;
--line: #2A2E34;
--line-inverse: #DFDCD4;
--line-strong: var(--ink-muted);
--accent: #3FA89D; /* 6.30:1 */
--accent-ink: #0B1A18; /* 6.21:1 */
--accent-inverse: #0E6B63;
--accent-ink-inverse: #FFFFFF;
--surface: #12161C;
--surface-raised: #181D25;
--glass-tint: rgb(255 255 255 / 0.07);
--glass-tint-2: rgb(255 255 255 / 0.11);
--glass-edge: rgb(255 255 255 / 0.18);
}
}
@ -74,19 +95,20 @@
[lang="ko"] {
--font-sans: "Pretendard Variable", Pretendard, system-ui, sans-serif;
--step-0: 1.0625rem; /* 17 — 본문 · eyebrow · 코드 */
--step-1: 1.328rem; /* 21.3 — 리드 */
--step-2: 1.660rem; /* 26.6 — h3 */
--step-3: 2.076rem; /* 33.2 — h2 */
--step-4: 2.595rem; /* 41.5 — h1 */
--display: clamp(2.595rem, 1.6rem + 4.2vw, 4.25rem); /* → 68 */
--step-0: 1.0625rem;
--step-1: 1.328rem;
--step-2: 1.660rem;
--step-3: 2.076rem;
--step-4: 2.595rem;
/* 히어로에 힘을 준다 */
--display: clamp(3rem, 1.2rem + 7.2vw, 7rem);
--leading-tight: 1.15;
--leading-normal: 1.7; /* 한글은 넉넉하게 */
--measure: 33em; /* ≈ 561px. 한글 글자폭 ≈ 1em */
--leading-tight: 1.06;
--leading-normal: 1.7;
--measure: 33em;
--tracking-display: -0.01em; /* 한글은 음수 자간을 크게 주지 않는다 */
--tracking-body: 0; /* 한글 본문 음수 자간 금지 */
--tracking-display: -0.02em;
--tracking-body: 0;
--tracking-label: 0.08em;
word-break: keep-all;
@ -97,32 +119,40 @@
[lang="en"] {
--font-sans: "Switzer", system-ui, -apple-system, sans-serif;
--step-0: 1rem; /* 16 */
--step-1: 1.25rem; /* 20 */
--step-2: 1.5625rem; /* 25 */
--step-3: 1.953rem; /* 31.3 */
--step-4: 2.441rem; /* 39 */
--display: clamp(2.441rem, 1.5rem + 4vw, 4rem); /* → 64 */
--step-0: 1rem;
--step-1: 1.25rem;
--step-2: 1.5625rem;
--step-3: 1.953rem;
--step-4: 2.441rem;
--display: clamp(2.8rem, 1rem + 7vw, 6.6rem);
--leading-tight: 1.1;
--leading-tight: 0.98;
--leading-normal: 1.55;
--measure: 65ch; /* ≈ 640px */
--measure: 65ch;
--tracking-display: -0.025em;
--tracking-display: -0.04em;
--tracking-body: 0;
--tracking-label: 0.08em;
}
/* ---- 다크 밴드: 게이트 #4가 요구하는 "규칙으로 정의된 반전" ---- */
/* 사용처는 히어로와 설치 섹션 두 곳뿐이다. 그 외 사용 금지. */
/* ---- 밝은 밴드: 규칙으로 정의된 번의 반전 --------------
사용처는 조판 섹션 하나뿐이다. 한글이 종이 위에 있어야 하는
주장을 하는 자리라 거기서만 바탕이 밝아진다. */
.band-inverse {
--surface: var(--surface-inverse);
--surface-raised: #ECE8DE;
--surface-sunken: #E2DED3;
--ink: var(--ink-inverse);
--ink-muted: var(--ink-muted-inverse);
--line: var(--line-inverse);
--line-strong: var(--ink-muted-inverse);
--accent: var(--accent-inverse);
--accent-ink: var(--accent-ink-inverse);
--glass-tint: rgb(20 24 30 / 0.04);
--glass-tint-2: rgb(20 24 30 / 0.07);
--glass-edge: rgb(20 24 30 / 0.12);
--glass-hi: inset 0 1px 0 rgb(255 255 255 / 0.8);
--inset-hi: var(--glass-hi);
background: var(--surface);
color: var(--ink);
padding-block: var(--space-8);
}