소개 페이지 전면 재디자인 — 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:
parent
6e05e69ca4
commit
be343cdab5
22 changed files with 1695 additions and 439 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue