feat(site): redesign promotion experience
This commit is contained in:
parent
6930906583
commit
13dd160c16
39 changed files with 5764 additions and 5738 deletions
|
|
@ -1,534 +1,274 @@
|
|||
/* ============================================================
|
||||
designpaca 소개 페이지 — 기반 스타일
|
||||
토큰은 tokens.css 에만 있다. 이 파일에 새 값을 만들지 않는다.
|
||||
============================================================ */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after { box-sizing: border-box; }
|
||||
|
||||
img,
|
||||
picture,
|
||||
video,
|
||||
canvas,
|
||||
svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
scroll-behavior: smooth;
|
||||
background: var(--surface);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
color: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--step-0);
|
||||
font-weight: var(--weight-body);
|
||||
line-height: var(--leading-normal);
|
||||
letter-spacing: var(--tracking-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 {
|
||||
word-break: keep-all;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* ---- 대기광 -------------------------------------------------
|
||||
유리는 뒤에 무언가 있어야 유리로 보인다. 페이지 전체에 깔리는
|
||||
고정 광원 두 개가 그 역할을 한다. 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, var(--glow-2) 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;
|
||||
}
|
||||
|
||||
/* ---- 스크롤 ------------------------------------------------
|
||||
기본 스크롤바는 OS 색이라 어두운 페이지 옆에 밝은 막대가 선다.
|
||||
표준 속성으로 먼저 칠하고, 아직 지원이 없는 곳은 -webkit- 으로 받는다. */
|
||||
html {
|
||||
scrollbar-color: var(--line-strong) transparent;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--line-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 2px solid var(--surface);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--ink-muted); }
|
||||
|
||||
/* 고정 헤더 높이만큼 앵커 목적지를 내린다. 없으면 점프한 섹션의
|
||||
제목이 헤더 뒤로 들어간다. */
|
||||
:target,
|
||||
section[id] { scroll-margin-top: calc(var(--header-h) + var(--space-4)); }
|
||||
|
||||
/* ---- 컨트롤 ------------------------------------------------
|
||||
버튼처럼 생긴 것은 전부 .btn 을 쓴다. 높이·패딩·웨이트를
|
||||
컴포넌트마다 다시 정하면 그때부터 SSOT 가 아니다. */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
min-height: var(--control-h);
|
||||
padding-inline: var(--control-pad-x);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--step-0);
|
||||
font-weight: var(--weight-medium);
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
transition:
|
||||
background-color var(--dur-instant) var(--ease-out),
|
||||
color var(--dur-instant) var(--ease-out),
|
||||
transform var(--dur-instant) var(--ease-out);
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
.btn-sm { min-height: var(--control-h-sm); }
|
||||
|
||||
/* 채운 버튼 — 페이지에 하나뿐인 주 행동에만 */
|
||||
.btn-solid {
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
.btn-solid:hover { background: color-mix(in oklab, var(--accent) 86%, var(--ink)); color: var(--accent-ink); }
|
||||
|
||||
/* 테두리 버튼 — 나머지 전부 */
|
||||
.btn-quiet {
|
||||
background: var(--glass-tint-2);
|
||||
border-color: var(--glass-edge);
|
||||
color: var(--ink);
|
||||
}
|
||||
.btn-quiet:hover { background: var(--glass-tint); color: var(--ink); }
|
||||
|
||||
/* ---- 그리드 ------------------------------------------------ */
|
||||
.page {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
[full-start] minmax(var(--gutter), 1fr)
|
||||
[wide-start] minmax(0, var(--wide-pad))
|
||||
[main-start] minmax(0, var(--measure)) [main-end]
|
||||
minmax(0, var(--wide-pad)) [wide-end]
|
||||
minmax(var(--gutter), 1fr) [full-end];
|
||||
}
|
||||
.page > * { grid-column: main; }
|
||||
.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 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p,
|
||||
figure,
|
||||
blockquote,
|
||||
dl,
|
||||
dd {
|
||||
margin: 0;
|
||||
line-height: var(--leading-tight);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-weight: var(--weight-medium);
|
||||
line-height: var(--leading-heading);
|
||||
letter-spacing: var(--tracking-display);
|
||||
font-weight: var(--weight-strong);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
h1 { font-size: var(--display); }
|
||||
h2 { font-size: var(--step-3); }
|
||||
|
||||
/* 섹션 제목만 본문 정렬선 왼쪽으로 나간다. 같은 양을 페이지 전체에서 반복한다 */
|
||||
@media (min-width: 1100px) {
|
||||
.section > h2,
|
||||
.section > .eyebrow,
|
||||
.section > .head { margin-left: calc(var(--space-6) * -1); }
|
||||
}
|
||||
h3 { font-size: var(--step-1); }
|
||||
|
||||
|
||||
p { margin: 0 0 var(--space-3); text-wrap: pretty; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
/* 제목 다음에 오는 것은 반드시 떨어진다.
|
||||
h2 는 margin: 0 이고 p 도 margin-top: 0 이라 제목과 본문이 그대로 붙었다.
|
||||
섹션마다 0 이거나 24px 로 갈려 있었다 — 값을 각자 붙이면 이렇게 된다.
|
||||
|
||||
`:where()` 로 감싸면 특이도가 0 이라 위의 `p` 규칙에 진다. 실제로 한 번 그렇게
|
||||
써서 아무 효과가 없었다. 특이도를 갖추고, `p` 뒤에 둔다.
|
||||
|
||||
제목을 `.head` 로 감싼 섹션에서는 인접 형제가 끊긴다 — 수치 섹션이 그래서
|
||||
혼자 0px 이었다. 제목 묶음도 같은 규칙에 넣는다. */
|
||||
h1 + *,
|
||||
h2 + *,
|
||||
h3 + *,
|
||||
.head + * { margin-top: var(--space-4); }
|
||||
|
||||
.lead { font-size: var(--step-1); color: var(--ink-muted); }
|
||||
|
||||
.eyebrow {
|
||||
font-size: var(--step-0);
|
||||
letter-spacing: var(--tracking-label);
|
||||
color: var(--accent);
|
||||
font-weight: var(--weight-medium);
|
||||
margin: 0 0 var(--space-3);
|
||||
}
|
||||
|
||||
code, kbd, samp, pre {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mono-size);
|
||||
strong,
|
||||
b {
|
||||
font-weight: var(--weight-strong);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-underline-offset: 0.2em;
|
||||
text-decoration-thickness: from-font;
|
||||
transition: color var(--dur-instant) var(--ease-out);
|
||||
text-decoration-thickness: 0.07em;
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a,
|
||||
button,
|
||||
summary {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
img,
|
||||
svg,
|
||||
video,
|
||||
canvas {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
code,
|
||||
.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mono-size);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.nums {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.skip {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
top: var(--space-3);
|
||||
left: var(--gutter);
|
||||
min-height: var(--control-h);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding-inline: var(--control-pad-x);
|
||||
background: var(--ink);
|
||||
color: var(--ink-inverse);
|
||||
transform: translateY(calc(-100% - var(--space-5)));
|
||||
transition: transform var(--dur-quick) var(--ease-out);
|
||||
}
|
||||
|
||||
.skip:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
a:hover { color: var(--accent); }
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline: 2px solid var(--focus);
|
||||
outline-offset: 3px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
유리
|
||||
backdrop-filter 만 쓰면 흐린 유리에서 끝난다. 두께와 굴절이
|
||||
있어야 유리로 읽힌다. 그래서 세 겹을 쌓는다.
|
||||
1) 배경 흐림 + 채도 (backdrop-filter)
|
||||
2) 가장자리 빛 (border + inset shadow)
|
||||
3) 위쪽 반사 (::before 그라디언트)
|
||||
굴절은 지원되는 브라우저에서만 backdrop-filter 에 SVG 를 얹는다.
|
||||
============================================================ */
|
||||
.glass {
|
||||
[id] {
|
||||
scroll-margin-top: calc(var(--header-h) + var(--space-5));
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(calc(100% - (2 * var(--gutter))), var(--container));
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.reading-shell {
|
||||
width: min(calc(100% - (2 * var(--gutter))), var(--container-reading));
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
background: var(--glass-tint);
|
||||
border: 1px solid var(--glass-edge);
|
||||
border-radius: var(--radius-frame);
|
||||
box-shadow: var(--glass-hi), var(--glass-bloom), var(--shadow-2);
|
||||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||||
min-height: var(--control-h);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--control-pad-x);
|
||||
border: var(--line-width) solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
line-height: 1.2;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: transform var(--dur-instant) var(--ease-out);
|
||||
}
|
||||
/* 위쪽 모서리에 닿는 빛. 유리판의 두께가 여기서 보인다 */
|
||||
.glass::before {
|
||||
|
||||
.btn::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -1px;
|
||||
z-index: -1;
|
||||
border-radius: inherit;
|
||||
box-shadow: var(--shadow-small);
|
||||
opacity: 0;
|
||||
transition: opacity var(--dur-instant) var(--ease-out);
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:focus-visible {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn:hover::after,
|
||||
.btn:focus-visible::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0) scale(0.985);
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-solid {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-quiet {
|
||||
border-color: var(--line-strong);
|
||||
background: var(--surface-raised);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
border-color: transparent;
|
||||
padding-inline: var(--space-2);
|
||||
color: var(--ink);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--line-strong);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
min-height: var(--control-h-sm);
|
||||
padding-inline: var(--space-4);
|
||||
font-size: var(--step--1);
|
||||
}
|
||||
|
||||
.frame {
|
||||
border: var(--line-width) solid var(--line-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-raised);
|
||||
}
|
||||
|
||||
.grain {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.grain::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
background: var(--glass-sheen);
|
||||
}
|
||||
/* backdrop-filter 에 SVG 굴절을 얹는 것은 Chrome 에서 실제로 동작한다.
|
||||
한때 "Chrome 이 선언을 버린다" 고 적어뒀지만 재측정에서 틀린 것으로 드러났다 —
|
||||
computed 값은 url(#id) 로 유지되고 배경도 굴절된다.
|
||||
여기에 아직 얹지 않은 이유는 다른 것이다: Chrome 전용이고, feImage 크기를
|
||||
요소 크기에 맞춰 리사이즈마다 다시 만들어야 한다. 헤더처럼 크기가 변하는
|
||||
요소에는 비용이 맞지 않는다. 자세한 기법은 references/svg-filters.md. */
|
||||
|
||||
/* ---- 테두리를 도는 빛 --------------------------------------
|
||||
`conic-gradient` 의 각도를 `@property` 로 애니메이션하면 매 프레임 배경을
|
||||
다시 칠한다 — 컴포지터에서 못 돌고 메인 스레드가 페인트한다(하드 게이트 #8).
|
||||
|
||||
대신 **정사각형 원뿔 레이어를 마스크 안에서 회전**시킨다. 움직이는 것은
|
||||
`rotate` 뿐이라 GPU 에서 돈다. 정사각형이라 회전해도 밝기가 균일하고,
|
||||
요소보다 크게(200%) 잡아 납작한 헤더에서도 모서리까지 닿는다. */
|
||||
.rim {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
padding: 1px;
|
||||
-webkit-mask:
|
||||
linear-gradient(black 0 0) content-box,
|
||||
linear-gradient(black 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(black 0 0) content-box,
|
||||
linear-gradient(black 0 0);
|
||||
mask-composite: exclude;
|
||||
}
|
||||
.rim::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 200%;
|
||||
aspect-ratio: 1;
|
||||
translate: -50% -50%;
|
||||
background: conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0 58%,
|
||||
color-mix(in oklab, var(--accent) 70%, white) 74%,
|
||||
transparent 86%
|
||||
);
|
||||
opacity: 0.55;
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.rim::before {
|
||||
animation: rim-spin 11s linear infinite;
|
||||
}
|
||||
}
|
||||
@keyframes rim-spin {
|
||||
to { rotate: 360deg; }
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.72' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.45'/%3E%3C/svg%3E");
|
||||
mix-blend-mode: multiply;
|
||||
opacity: 0.025;
|
||||
}
|
||||
|
||||
/* 가장자리를 도는 빛. 유리판 테두리의 굴절을 흉내낸다 */
|
||||
.glass::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
padding: 1px;
|
||||
background: var(--glass-rim);
|
||||
/* 아래 black 은 색이 아니라 마스크의 알파다. 팔레트와 무관하다 */
|
||||
-webkit-mask:
|
||||
linear-gradient(black 0 0) content-box,
|
||||
linear-gradient(black 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(black 0 0) content-box,
|
||||
linear-gradient(black 0 0);
|
||||
mask-composite: exclude;
|
||||
details > summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 투명도를 줄여달라는 요청에 불투명한 판을 깔면 그냥 상자가 된다.
|
||||
배경에 가까운 톤으로 낮추고 형태는 테두리와 그림자가 유지한다. */
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.glass {
|
||||
background: color-mix(in oklab, var(--surface) 84%, var(--ink));
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
/* 떠 있는 헤더는 배경과 더 가깝게. 여기 판이 깔리면 즉시 눈에 띈다 */
|
||||
.site-header .glass {
|
||||
background: color-mix(in oklab, var(--surface) 93%, var(--ink));
|
||||
}
|
||||
}
|
||||
@supports not (backdrop-filter: blur(1px)) {
|
||||
.glass { background: var(--surface-raised); }
|
||||
}
|
||||
|
||||
/* 유리 위에 얹는 두 번째 판. 안쪽으로 갈수록 밝아진다 */
|
||||
.glass-2 { background: var(--glass-tint-2); }
|
||||
|
||||
/* 액자. 이중 베젤을 버렸다 — 껍데기와 속의 radius 가 서로 달라
|
||||
그것만으로 형태 어휘가 두 종 늘어난다(하드 게이트 #3).
|
||||
그리고 안이 불투명한 이미지면 backdrop-filter 는 아무것도 하지 않는다.
|
||||
유리로 읽히게 하는 것은 테두리 광과 위쪽 반사지 흐림이 아니다. */
|
||||
.frame {
|
||||
position: relative;
|
||||
background: var(--surface-sunken);
|
||||
border: 1px solid var(--glass-edge);
|
||||
border-radius: var(--radius-frame);
|
||||
box-shadow: var(--glass-hi), var(--core-hi), var(--shadow-2);
|
||||
overflow: clip;
|
||||
}
|
||||
/* 이미지를 담는 액자만. 인라인 여백을 지운다 */
|
||||
.frame > :is(img, picture, canvas) { display: block; width: 100%; height: auto; }
|
||||
|
||||
/* ---- 진입 연출 ----------------------------------------------
|
||||
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; }
|
||||
}
|
||||
|
||||
/* ---- 상호작용 모션 ------------------------------------------
|
||||
모션은 이유가 있어야 붙인다. 여기 있는 것은 전부 피드백이다.
|
||||
"지금 이 카드를 보고 있다", "이 버튼은 눌린다"를 말한다.
|
||||
transform 과 opacity 만 움직인다(하드 게이트 8). */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
/* 유리 카드는 커서 아래에서 아주 조금 떠오른다 */
|
||||
.lift {
|
||||
transition:
|
||||
transform var(--dur-quick) var(--ease-out),
|
||||
box-shadow var(--dur-quick) var(--ease-out);
|
||||
}
|
||||
.lift:hover {
|
||||
transform: translate3d(0, -3px, 0);
|
||||
box-shadow: var(--glass-hi), var(--shadow-3);
|
||||
}
|
||||
|
||||
/* 링크 밑줄이 커서 쪽에서 자란다 */
|
||||
.link-grow {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
.link-grow::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -3px;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform var(--dur-quick) var(--ease-out);
|
||||
}
|
||||
.link-grow:hover::after { transform: scaleX(1); }
|
||||
|
||||
/* 쇼케이스 판은 들여다보면 조금 다가온다 */
|
||||
.zoom-in { transition: transform var(--dur-normal) var(--ease-out); }
|
||||
.zoom-in:hover { transform: scale(1.012); }
|
||||
}
|
||||
|
||||
/* ---- 패럴랙스 -----------------------------------------------
|
||||
scroll-driven animation 으로만 만든다. scroll 리스너는 쓰지 않는다.
|
||||
움직이는 것은 transform 뿐이라 지원되지 않는 브라우저에서는
|
||||
요소가 제자리에 그대로 있다. 콘텐츠가 사라질 수 없다. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@supports (animation-timeline: view()) {
|
||||
.par-slow,
|
||||
.par-fast,
|
||||
.par-deep,
|
||||
.par-lag {
|
||||
animation: par linear both;
|
||||
animation-timeline: view();
|
||||
animation-range: cover 0% cover 100%;
|
||||
will-change: transform;
|
||||
}
|
||||
.par-slow { --par-from: 26px; --par-to: -26px; }
|
||||
.par-fast { --par-from: 58px; --par-to: -58px; }
|
||||
.par-deep { --par-from: 92px; --par-to: -92px; }
|
||||
/* 짝이 되는 요소보다 조금 늦게 따라온다.
|
||||
옆의 것은 고정이고 이것만 움직여야 '저것 때문에 이것'으로 읽힌다. */
|
||||
.par-lag { --par-from: var(--par-lag, 14px); --par-to: calc(var(--par-lag, 14px) * -1); }
|
||||
@keyframes par {
|
||||
from { transform: translate3d(0, var(--par-from), 0); }
|
||||
to { transform: translate3d(0, var(--par-to), 0); }
|
||||
}
|
||||
}
|
||||
|
||||
/* 히어로는 페이지 맨 위라 view() 의 entry 구간이 이미 지나 있다.
|
||||
그래서 문서 스크롤을 직접 타임라인으로 쓴다 — 시작점이 확정된다.
|
||||
|
||||
글자는 올라가고 배경은 내려간다. 서로 반대로 움직여야 시차가 보인다.
|
||||
같은 방향으로 속도만 다르게 하면 그냥 느린 스크롤로 읽힌다. */
|
||||
@supports (animation-timeline: scroll()) {
|
||||
.hero-grid,
|
||||
.hero-bg {
|
||||
animation: linear both;
|
||||
animation-timeline: scroll(root block);
|
||||
animation-range: 0 92vh;
|
||||
will-change: transform;
|
||||
}
|
||||
.hero-grid { animation-name: hero-rise; }
|
||||
.hero-bg { animation-name: hero-sink; }
|
||||
|
||||
@keyframes hero-rise {
|
||||
from { transform: translate3d(0, 0, 0); }
|
||||
to { transform: translate3d(0, -5.5rem, 0); }
|
||||
}
|
||||
@keyframes hero-sink {
|
||||
from { transform: translate3d(0, 0, 0); }
|
||||
to { transform: translate3d(0, 3.5rem, 0); }
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
.btn {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 배경 레이어 --------------------------------------------
|
||||
패럴랙스는 깊이 차가 보여야 성립한다. 배경이 단색이면 앞의 것이
|
||||
몇 px 어긋나 봐야 아무도 못 느낀다 — 기준면이 없기 때문이다.
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
기준면은 `BgPhoto` 컴포넌트가 만든다.
|
||||
|
||||
CSS 로 그린 격자를 한동안 같이 깔았는데 걷어냈다. 사진이 들어온 뒤로는
|
||||
배경이 두 겹이 되어 서로 경쟁했고, 격자는 배경이라기보다 표로 읽혔다.
|
||||
배경도 한 섹션에 하나여야 한다. */
|
||||
.has-bg {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
/* `clip-path: inset(0)` 는 두 가지를 동시에 한다.
|
||||
1) 배경이 섹션 밖으로 넘치지 않게 자른다
|
||||
2) `position: fixed` 자식의 **컨테이닝 블록**이 된다
|
||||
|
||||
2번이 핵심이다. 그래서 배경이 뷰포트에 고정되면서도 이 섹션 안에서만 보인다 —
|
||||
콘텐츠만 그 위를 지나가고 배경은 원경처럼 멈춰 있다.
|
||||
|
||||
`background-attachment: fixed` 를 쓰지 않은 이유는 iOS Safari 가 GPU 메모리
|
||||
때문에 그것을 throttle 해서 스크롤 중 배경이 튀기 때문이다.
|
||||
|
||||
주의: 여기에 `transform`·`filter`·`mask` 를 추가하면 안 된다. 그것들도
|
||||
컨테이닝 블록을 만들지만, 그 순간 fixed 가 섹션 기준이 되어 고정이 사라진다. */
|
||||
clip-path: inset(0);
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 1ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 유틸 -------------------------------------------------- */
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
font-display: swap 을 쓰는 한 반드시 일어난다.
|
||||
|
||||
그래서 폴백 폰트를 실제 폰트의 치수에 맞춘다.
|
||||
아래 숫자는 이 페이지에서 직접 잰 값이다. 추정치가 아니다.
|
||||
아래 폭은 이 페이지의 실제 카피를 100px/400으로 렌더해 잰 값이다.
|
||||
|
||||
한글 Pretendard 1434px vs 맑은 고딕 1620px -> 88.5%
|
||||
Apple SD Gothic Neo 1510px -> 94.9%
|
||||
영문 Switzer 1580px vs Arial 1501px -> 105.3%
|
||||
한글 Wanted Sans 3305.28px vs 맑은 고딕 3879.55px -> 85.2%
|
||||
Apple SD Gothic Neo 3526.11px -> 93.7%
|
||||
영문 Wanted Sans 3572.08px vs Arial 3490.73px -> 102.3%
|
||||
|
||||
재는 법은 references/typography.md §4 에 있다.
|
||||
OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의한다. 하나로 뭉치면
|
||||
|
|
@ -21,9 +21,9 @@
|
|||
@font-face {
|
||||
font-family: "KO Fallback Win";
|
||||
src: local("Malgun Gothic"), local("맑은 고딕");
|
||||
size-adjust: 88.5%;
|
||||
ascent-override: 108%;
|
||||
descent-override: 27%;
|
||||
size-adjust: 85.2%;
|
||||
ascent-override: 111%;
|
||||
descent-override: 29%;
|
||||
line-gap-override: 0%;
|
||||
}
|
||||
|
||||
|
|
@ -31,8 +31,8 @@
|
|||
@font-face {
|
||||
font-family: "KO Fallback Mac";
|
||||
src: local("Apple SD Gothic Neo"), local("AppleSDGothicNeo-Regular");
|
||||
size-adjust: 94.9%;
|
||||
ascent-override: 100%;
|
||||
size-adjust: 93.7%;
|
||||
ascent-override: 102%;
|
||||
descent-override: 25%;
|
||||
line-gap-override: 0%;
|
||||
}
|
||||
|
|
@ -41,8 +41,8 @@
|
|||
@font-face {
|
||||
font-family: "EN Fallback";
|
||||
src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
|
||||
size-adjust: 105.3%;
|
||||
ascent-override: 110%;
|
||||
descent-override: 28%;
|
||||
size-adjust: 102.3%;
|
||||
ascent-override: 92%;
|
||||
descent-override: 24%;
|
||||
line-gap-override: 0%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,255 +1,90 @@
|
|||
/* ============================================================
|
||||
designpaca 소개 페이지 — 디자인 토큰
|
||||
프리셋: dark-instrument · 컨셉: 검사대 위의 물체
|
||||
|
||||
다크를 "멋있어서" 고르지 않았다. 제품이 CLI 이고 사용자가 이
|
||||
화면을 터미널 옆에 두고 본다. 프리셋이 요구하는 근거다.
|
||||
|
||||
유리는 액자에만 쓴다. 액자 안은 3D, 액자 밖은 글자 —
|
||||
R2(lusion.co) 실측에서 가져온 원리다. 둘을 겹치면 3D 는 얼룩이
|
||||
되고 글자는 대비를 잃는다. 실제로 한 번 그렇게 만들었다.
|
||||
|
||||
전체 결정 근거는 design.md.
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
/* Wanted Sans Variable is the brand face for Korean and Latin.
|
||||
The system stack remains only as a failure-safe fallback. */
|
||||
--font-body: "Wanted Sans Variable", "Wanted Sans", "KO Fallback Win", "KO Fallback Mac", "EN Fallback", sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
--mono-size: 0.9375em;
|
||||
|
||||
/* ---- 바탕: 잉크. 순흑이 아니다 --------------------------- */
|
||||
--surface: #0B0E12;
|
||||
--surface-raised: #11151B;
|
||||
--surface-sunken: #070A0D;
|
||||
/* 밝은 밴드 하나. 규칙으로 정의된 반전이다 */
|
||||
--surface-inverse: #F4F2EC;
|
||||
/* Major-third scale with one display size outside the scale. */
|
||||
--step--1: clamp(0.75rem, 0.73rem + 0.08vw, 0.8125rem);
|
||||
--step-0: clamp(1rem, 0.96rem + 0.18vw, 1.125rem);
|
||||
--step-1: clamp(1.2rem, 1.13rem + 0.3vw, 1.375rem);
|
||||
--step-2: clamp(1.5rem, 1.34rem + 0.68vw, 1.875rem);
|
||||
--step-3: clamp(2rem, 1.71rem + 1.2vw, 2.75rem);
|
||||
--display: clamp(3rem, 2.35rem + 2.1vw, 3.875rem);
|
||||
--leading-heading: 1.16;
|
||||
--leading-normal: 1.72;
|
||||
--measure-narrow: 31rem;
|
||||
--tracking-display: -0.012em;
|
||||
--weight-body: 400;
|
||||
--weight-medium: 470;
|
||||
--weight-strong: 520;
|
||||
--mono-size: 0.8125rem;
|
||||
|
||||
--ink: #ECEFF2; /* 15.9:1 */
|
||||
--ink-muted: #98A2AE; /* 7.1:1 */
|
||||
--ink-inverse: #14181E;
|
||||
--ink-muted-inverse: #565E68;
|
||||
/* Cold paper, graphite, one signal red. */
|
||||
--surface: #f2f1eb;
|
||||
--surface-raised: #faf9f4;
|
||||
--surface-soft: #e7e8e1;
|
||||
--surface-inverse: #17231f;
|
||||
--ink: #171b19;
|
||||
--ink-muted: #5e655f;
|
||||
--ink-inverse: #f6f5ef;
|
||||
--ink-muted-inverse: #bfc8c1;
|
||||
--line: #cdd0c7;
|
||||
--line-strong: #9ba39c;
|
||||
--accent: #c43f20;
|
||||
--accent-ink: #faf9f4;
|
||||
--focus: #1267d6;
|
||||
|
||||
--line: #222932;
|
||||
--line-strong: #39424E;
|
||||
--line-inverse: #D8D4CA;
|
||||
|
||||
/* 강조: 시안 틸. 유리 너머에서도 살아남는 채도 */
|
||||
--accent: #4FD6C4; /* 잉크 위 10.4:1 */
|
||||
--accent-ink: #04211D;
|
||||
--accent-inverse: #0E7A6E;
|
||||
--accent-ink-inverse: #FFFFFF;
|
||||
/* 셰이더가 쓰는 두 번째 광색. 강조색이 아니라 광원이다.
|
||||
보라기를 뺐다 — #6E7BFF 는 R 이 높아 보라-파랑으로 읽히고,
|
||||
그것이 antipatterns.md 의 슬롭 지문이자 dark-instrument 가
|
||||
"어두운 배경일수록 빠지기 쉬운 함정"으로 지목한 색이다. */
|
||||
--glow-2: #3D7BD6;
|
||||
|
||||
/* ---- 유리 ------------------------------------------------
|
||||
유리는 액자에만 쓴다. 헤더 · 검사대 프레임 · 게이트 배지.
|
||||
콘텐츠 카드에는 쓰지 않는다 — 유리는 무언가를 담을 때만
|
||||
의미가 있고, 아무 데나 바르면 그냥 반투명한 상자다.
|
||||
(R2 lusion.co 실측: 3D 를 액자에 가두고 텍스트는 액자 밖에 둔다) */
|
||||
--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);
|
||||
/* 표면 반사와 테두리 광. 값이 아니라 토큰으로 둬야 밝은 밴드에서
|
||||
같은 자리를 뒤집을 수 있다 (하드 게이트 #1) */
|
||||
--glass-sheen: linear-gradient(
|
||||
180deg,
|
||||
rgb(255 255 255 / 0.10) 0%,
|
||||
rgb(255 255 255 / 0.02) 22%,
|
||||
transparent 55%
|
||||
);
|
||||
/* 테두리 광.
|
||||
conic-gradient 를 쓰면 납작한 형태(예: 1180x54 헤더)에서 각도가 급변해
|
||||
변마다 광이 뚝 끊긴다 — 실제로 헤더 아래쪽만 절반 밝았다.
|
||||
선형으로 바꾸면 네 변이 같은 그라디언트를 공유해 테두리를 따라 이어진다. */
|
||||
--glass-rim: linear-gradient(
|
||||
100deg,
|
||||
rgb(255 255 255 / 0.34) 0%,
|
||||
rgb(255 255 255 / 0.10) 26%,
|
||||
rgb(255 255 255 / 0.06) 58%,
|
||||
rgb(255 255 255 / 0.28) 100%
|
||||
);
|
||||
/* 테두리에서 아주 얕게 번지는 빛. 강조색을 실어 유리가 차갑게만 보이지 않게 한다 */
|
||||
--glass-bloom: 0 0 22px -8px color-mix(in oklab, var(--accent) 42%, transparent);
|
||||
--core-hi: inset 0 1px 0 rgb(255 255 255 / 0.06);
|
||||
|
||||
--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);
|
||||
|
||||
/* ---- 형태 ------------------------------------------------
|
||||
두 종류뿐이다. 하드 게이트 #3 은 3종부터 실패로 본다.
|
||||
|
||||
3px — 요소. R1 zed.dev 실측에서 border-radius 2px 가 621회로
|
||||
압도적이었다. 개발자 도구는 각져야 정밀해 보인다
|
||||
14px — 액자. R2 lusion.co 가 3D 캔버스를 감싼 값이 15px 이다
|
||||
|
||||
pill(999px) 을 없앴다. 계기판에 알약은 없다. */
|
||||
--radius-sm: 3px;
|
||||
--radius-frame: 14px;
|
||||
|
||||
/* ---- 웨이트 ----------------------------------------------
|
||||
세 개뿐이다. 코드 곳곳에 500·560·600·620·660 이 흩어져 있었고
|
||||
그것이 이 페이지의 SSOT 가 깨진 가장 큰 자리였다.
|
||||
|
||||
R1(zed.dev) 모바일 실측: font-weight 400 이 832회, 나머지 전부 합쳐 12회다.
|
||||
위계를 웨이트로 만들지 않는다 — 크기와 색으로 만든다.
|
||||
가변폰트는 660 같은 중간값도 그려주지만, 표준 축(400/500/600)을 벗어나면
|
||||
힌팅이 흐려지고 폴백 폰트에서는 아예 다른 굵기로 떨어진다. */
|
||||
--weight-body: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-strong: 600;
|
||||
|
||||
/* ---- 컨트롤 ----------------------------------------------
|
||||
버튼처럼 생긴 것은 전부 이 값을 쓴다. 예외를 만들지 마라.
|
||||
R1 실측: Download / Clone 두 버튼이 h36·pad-x10·r4·w400·14px 로
|
||||
픽셀 단위까지 같았다. 우리는 높이도 패딩도 웨이트도 제각각이었다. */
|
||||
--control-h: 2.75rem; /* 44px — 터치 타깃 */
|
||||
--control-h-sm: 2.25rem; /* 36px — 헤더 등 조밀한 자리 */
|
||||
--control-pad-x: var(--space-3);
|
||||
|
||||
/* ---- 간격 ------------------------------------------------
|
||||
작은 값은 고정, 큰 값만 화면에 반응한다.
|
||||
고정으로 두면 모바일에서 섹션 상하 112px 이 그대로 들어가
|
||||
화면 높이의 26% 를 여백이 먹는다. 실제로 그랬다. */
|
||||
/* One rhythm. No ad-hoc intermediate padding values. */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 1rem;
|
||||
--space-4: 1.5rem;
|
||||
--space-5: clamp(1.75rem, 1.2rem + 1.6vw, 2.5rem);
|
||||
--space-6: clamp(2.5rem, 1.6rem + 2.6vw, 4rem);
|
||||
--space-7: clamp(3.5rem, 1.9rem + 4.6vw, 7rem);
|
||||
--space-8: clamp(5rem, 2.6rem + 6.8vw, 10rem);
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-5: 1.5rem;
|
||||
--space-6: 2rem;
|
||||
--space-7: clamp(2.5rem, 2rem + 2vw, 4rem);
|
||||
--space-8: clamp(4rem, 2.8rem + 4vw, 7rem);
|
||||
--space-9: clamp(6rem, 4rem + 6vw, 10rem);
|
||||
--gutter: clamp(1.25rem, 0.72rem + 2.2vw, 3rem);
|
||||
--container: 80rem;
|
||||
--container-reading: 68rem;
|
||||
|
||||
/* 좌우 여백. R1(zed.dev) 모바일 실측이 32px 이었다 — 우리는 24px 였다.
|
||||
모바일 전 구간 32px 을 깔고 넓은 화면에서만 40px 까지 연다. */
|
||||
--gutter: clamp(2rem, 1.4rem + 1.8vw, 2.5rem);
|
||||
--radius-sm: 0.1875rem;
|
||||
--line-width: 1px;
|
||||
|
||||
/* 배경 격자의 한 칸. 간격 스케일에서 가져온다 —
|
||||
장식용 격자가 아니라 이 페이지가 실제로 쓰는 눈금이어야
|
||||
"모든 값이 토큰에서 나온다"는 주장이 배경에서도 성립한다. */
|
||||
--grid-cell: var(--space-6);
|
||||
--control-h: 2.75rem;
|
||||
--control-h-sm: 2.75rem;
|
||||
--control-pad-x: 1.125rem;
|
||||
--header-h: 4.5rem;
|
||||
|
||||
/* main 칼럼 바깥의 여유 폭(wide 트랙). 넓은 화면에서 제목과 넓은 블록이
|
||||
본문보다 밖으로 나갈 자리를 만든다.
|
||||
이 값을 고정으로 두면 모바일에서도 좌우 80px 씩 살아남아, 390px 화면에서
|
||||
본문이 x=112 부터 시작하고 폭이 278px 로 줄어든다 — 제목이 네 줄로 접힌다.
|
||||
좁은 화면에서는 0 으로 닫는다. */
|
||||
--wide-pad: clamp(0rem, -3.7rem + 15.2vw, 10rem);
|
||||
--dur-instant: 100ms;
|
||||
--dur-entry: 60ms;
|
||||
--dur-quick: 180ms;
|
||||
--dur-normal: 320ms;
|
||||
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* 헤더가 차지하는 높이. 떠 있는 헤더라 문서 흐름에서 빠져 있고,
|
||||
그래서 히어로 상단 여백과 앵커 목적지가 이 값을 알아야 한다.
|
||||
간격 토큰을 clamp 로 바꾸는 순간 이 관계가 끊겨 h1 이 헤더 뒤로
|
||||
들어갔다 — 값이 아니라 관계를 토큰으로 둬야 하는 이유다. */
|
||||
--header-h: calc(var(--space-3) + var(--space-2) * 2 + var(--control-h-sm));
|
||||
--shadow-small: 0 1px 1px rgb(23 27 25 / 0.05), 0 8px 24px rgb(23 27 25 / 0.06);
|
||||
|
||||
/* ---- 모션 ------------------------------------------------ */
|
||||
--dur-instant: 120ms;
|
||||
--dur-quick: 280ms;
|
||||
--dur-normal: 560ms;
|
||||
--dur-slow: 900ms;
|
||||
|
||||
--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);
|
||||
--line-inverse: rgb(246 245 239 / 0.28);
|
||||
}
|
||||
|
||||
/* ---- 라이트 선호: 유리를 얇게 하고 바탕을 올린다 --------------
|
||||
테마를 뒤집지 않는다. 이 페이지는 어두운 유리가 주제다.
|
||||
다만 라이트를 쓰는 사람에게 대비를 조금 더 준다. */
|
||||
@media (prefers-color-scheme: light) {
|
||||
html[lang="en"] {
|
||||
--measure-narrow: 56ch;
|
||||
--leading-normal: 1.62;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
:root {
|
||||
--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);
|
||||
--header-h: 6.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 타입: 한국어판 --------------------------------------- */
|
||||
[lang="ko"] {
|
||||
--font-sans: "Pretendard Variable", Pretendard,
|
||||
"KO Fallback Win", "KO Fallback Mac", system-ui, sans-serif;
|
||||
|
||||
--step--1: 0.8125rem;
|
||||
--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.06;
|
||||
--leading-normal: 1.7;
|
||||
--measure: 33em;
|
||||
|
||||
--tracking-display: -0.02em;
|
||||
--tracking-body: 0;
|
||||
--tracking-label: 0.08em;
|
||||
|
||||
word-break: keep-all;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* ---- 타입: 영어판 ------------------------------------------ */
|
||||
[lang="en"] {
|
||||
--font-sans: "Switzer", "EN Fallback", system-ui, -apple-system, sans-serif;
|
||||
|
||||
--step--1: 0.8125rem;
|
||||
--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: 0.98;
|
||||
--leading-normal: 1.55;
|
||||
--measure: 65ch;
|
||||
|
||||
--tracking-display: -0.04em;
|
||||
--tracking-body: 0;
|
||||
--tracking-label: 0.08em;
|
||||
}
|
||||
|
||||
/* ---- 밝은 밴드: 규칙으로 정의된 단 한 번의 반전 --------------
|
||||
사용처는 조판 섹션 하나뿐이다. 한글이 종이 위에 있어야 하는
|
||||
주장을 하는 자리라 거기서만 바탕이 밝아진다. */
|
||||
.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);
|
||||
/* 밝은 밴드에서는 테두리 광의 방향이 뒤집힌다. 흰빛 대신 그림자로 이어붙인다 */
|
||||
--glass-rim: linear-gradient(
|
||||
100deg,
|
||||
rgb(20 24 30 / 0.16) 0%,
|
||||
rgb(20 24 30 / 0.05) 26%,
|
||||
rgb(20 24 30 / 0.04) 58%,
|
||||
rgb(20 24 30 / 0.14) 100%
|
||||
);
|
||||
--glass-bloom: 0 0 18px -10px color-mix(in oklab, var(--ink) 30%, transparent);
|
||||
background: var(--surface);
|
||||
color: var(--ink);
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root {
|
||||
--dur-instant: 1ms;
|
||||
--dur-entry: 1ms;
|
||||
--dur-quick: 1ms;
|
||||
--dur-normal: 1ms;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue