소개 페이지 전면 재디자인 — 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

@ -8,51 +8,80 @@ interface Props {
const { command, copyLabel, copiedLabel, note } = Astro.props;
---
<div class="cmd">
<code class="cmd-text">{command}</code>
<button type="button" class="cmd-copy" data-copy={command} data-copied={copiedLabel}>
{copyLabel}
</button>
{/* 셸 안에 코어. 명령어는 기계의 부품처럼 앉는다 */}
<div class="cmd-shell shell">
<div class="cmd core">
<code class="cmd-text">{command}</code>
<button type="button" class="cmd-copy" data-copy={command} data-copied={copiedLabel}>
<span class="cmd-copy-label">{copyLabel}</span>
<span class="cmd-copy-icon" aria-hidden="true">
<svg viewBox="0 0 16 16" width="12" height="12" fill="none">
<path d="M5.5 10.5L10.5 5.5M10.5 5.5H6.5M10.5 5.5V9.5"
stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
</button>
</div>
</div>
{note && <p class="cmd-note muted">{note}</p>}
<style>
.cmd-shell { max-width: 30em; min-width: 0; }
.cmd {
display: flex;
min-width: 0;
align-items: stretch;
gap: 0;
border: 1px solid var(--line-strong);
border-radius: var(--radius-sm);
overflow: hidden;
max-width: 34em;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
}
.cmd-text {
flex: 1 1 0;
min-width: 0;
padding: var(--space-3);
overflow-x: auto;
white-space: nowrap;
color: var(--ink);
}
.cmd-text::before {
content: "$ ";
color: var(--ink-muted);
}
/* 버튼 안에 아이콘이 자기 원을 갖는다 */
.cmd-copy {
flex: 0 0 auto;
/* 독립 컨트롤이므로 44px 이상 */
min-width: 5.5em;
display: inline-flex;
align-items: center;
gap: var(--space-2);
min-height: 44px;
white-space: nowrap;
padding-inline: var(--space-3);
padding: 0 var(--space-2) 0 var(--space-3);
border: 0;
border-left: 1px solid var(--line-strong);
border-radius: var(--radius-sm);
background: var(--accent);
color: var(--accent-ink);
font: inherit;
font-size: var(--step-0);
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: opacity var(--dur-instant) var(--ease-out);
transition:
transform var(--dur-instant) var(--ease-out),
filter var(--dur-instant) var(--ease-out);
}
.cmd-copy:hover {
opacity: 0.88;
.cmd-copy:hover { filter: brightness(1.08); }
.cmd-copy:active { transform: scale(0.975); }
.cmd-copy-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border-radius: var(--radius-sm);
background: color-mix(in oklab, var(--accent-ink) 18%, transparent);
transition: transform var(--dur-quick) var(--ease-out);
}
.cmd-copy:hover .cmd-copy-icon { transform: translate(2px, -2px); }
.cmd-note {
margin-top: var(--space-2);
font-size: var(--step-0);
@ -62,17 +91,19 @@ const { command, copyLabel, copiedLabel, note } = Astro.props;
<script>
// 이 페이지의 유일한 상호작용이다. 라이브러리를 쓰지 않는다.
document.querySelectorAll<HTMLButtonElement>(".cmd-copy").forEach((btn) => {
const label = btn.querySelector<HTMLSpanElement>(".cmd-copy-label");
if (!label) return;
btn.addEventListener("click", async () => {
const text = btn.dataset["copy"] ?? "";
const done = btn.dataset["copied"] ?? "OK";
const original = btn.textContent;
const original = label.textContent;
try {
await navigator.clipboard.writeText(text);
} catch {
// 클립보드 권한이 없으면 선택 상태로 만들어 사용자가 직접 복사하게 둔다
const range = document.createRange();
const code = btn.previousElementSibling;
const code = btn.parentElement?.querySelector(".cmd-text");
if (code) {
const range = document.createRange();
range.selectNodeContents(code);
const sel = getSelection();
sel?.removeAllRanges();
@ -80,11 +111,9 @@ const { command, copyLabel, copiedLabel, note } = Astro.props;
}
return;
}
btn.textContent = done;
btn.setAttribute("aria-live", "polite");
setTimeout(() => {
btn.textContent = original;
}, 1600);
label.textContent = done;
label.setAttribute("aria-live", "polite");
setTimeout(() => { label.textContent = original; }, 1600);
});
});
</script>