diff --git a/docs/static/css/landing.css b/docs/static/css/landing.css
@@ -324,21 +324,30 @@ main { display: block; }
color: transparent;
}
-/* 설치 한 줄 — 페이지에서 제일 중요한 것. CTA 바로 아래에서 눈에 띈다. */
-/* 채널 선택 + 명령. fieldset이라 기본 테두리·여백을 먼저 지운다.
- align-items: flex-start라서 탭 행도 명령 상자도 제 내용만큼만 넓어진다. */
+/* 설치 명령 — 페이지에서 제일 중요한 것. CTA 바로 아래에서 눈에 띈다.
+
+ 탭과 명령을 카드 하나로 묶는다. 둘을 떼어 놓으면 화면에 떠 있는 조각이
+ 둘이 되고, 무엇이 무엇을 바꾸는지가 근접성으로만 설명된다. */
.install {
- display: flex;
+ display: inline-flex;
flex-direction: column;
- align-items: flex-start;
- gap: 0.5rem;
+ align-items: stretch;
max-width: 100%;
- border: 0;
+ border: 1px solid var(--line);
+ border-radius: 15px;
+ background: var(--bg-hi);
padding: 0;
margin: 1.6rem 0 0;
min-width: 0;
+ overflow: hidden;
+}
+.pick-tabs {
+ position: relative;
+ display: flex;
+ gap: 0.15rem;
+ padding: 0.32rem;
+ border-bottom: 1px solid var(--line);
}
-.pick-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.pick-in {
position: absolute;
width: 1px;
@@ -347,54 +356,73 @@ main { display: block; }
pointer-events: none;
}
.pick-tab {
+ position: relative;
+ z-index: 1;
cursor: pointer;
font-family: var(--mono);
- font-size: 0.82rem;
+ font-size: 0.8rem;
line-height: 1;
color: var(--muted);
- padding: 0.42rem 0.72rem;
- border: 1px solid var(--line);
- border-radius: 999px;
- background: transparent;
- transition: color 0.15s, border-color 0.15s, background 0.15s;
-}
-.pick-tab:hover { color: var(--ink); border-color: var(--ink-3); }
-.pick-in:checked + .pick-tab {
- color: var(--accent-ink);
- border-color: rgba(255, 140, 90, 0.55);
- background: rgba(255, 120, 70, 0.1);
+ padding: 0.44rem 0.8rem;
+ border-radius: 9px;
+ transition: color 0.18s;
}
+.pick-tab:hover { color: var(--ink); }
+.pick-in:checked + .pick-tab { color: var(--accent-ink); }
+/* JS가 없을 때의 선택 표시. 표시자가 살아나면 이 배경은 넘겨준다. */
+.pick-in:checked + .pick-tab { background: rgba(255, 120, 70, 0.12); }
+.install.is-enhanced .pick-in:checked + .pick-tab { background: transparent; }
/* 라디오가 화면에 없으므로 포커스 링은 라벨이 받는다. 없으면 키보드로 못 쓴다. */
.pick-in:focus-visible + .pick-tab {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
+/* 선택을 따라 미끄러지는 표시자. 이동하는 동안 진행 방향으로 눌렸다 펴진다 —
+ 히어로의 squeeze와 같은 동작이고, 이 제품이 하는 일 자체다. */
+.pick-marker {
+ position: absolute;
+ top: 0.32rem;
+ left: 0;
+ height: calc(100% - 0.64rem);
+ border-radius: 9px;
+ background: rgba(255, 120, 70, 0.14);
+ box-shadow: inset 0 0 0 1px rgba(255, 140, 90, 0.4);
+ opacity: 0;
+ pointer-events: none;
+ transform-origin: center;
+}
+.install.is-enhanced .pick-marker { opacity: 1; }
+/* 패널을 격자 한 칸에 겹쳐 쌓는다. 카드 폭이 가장 긴 명령에 맞춰 고정되므로
+ 탭을 옮길 때마다 상자가 늘었다 줄었다 하지 않는다.
+ 숨김은 display가 아니라 visibility다 — 폭 계산에는 남아 있어야 한다. */
+.pick-panels {
+ display: grid;
+ min-width: 0;
+ max-width: 100%;
+}
.pick-panel {
- display: none;
+ grid-area: 1 / 1;
+ display: flex;
align-items: center;
- gap: 0.4rem;
+ gap: 0.6rem;
min-width: 0;
- max-width: 100%;
- border: 1px solid rgba(255, 140, 90, 0.45);
- border-radius: 13px;
- background: var(--bg-hi);
- box-shadow: 0 4px 22px rgba(255, 110, 60, 0.16);
- padding: 0.72rem 0.55rem 0.72rem 1.05rem;
+ padding: 0.78rem 0.6rem 0.78rem 1.05rem;
+ visibility: hidden;
+ opacity: 0;
+ transform-origin: left center;
}
-/* 패널만 따로 감싼 건 `nth-child`를 정확히 세기 위해서다. fieldset 바로 아래에
- 두면 legend와 탭 행까지 형제로 세어 한 칸씩 밀린다 — 실제로 그렇게 밀려서
- AUR 탭에 cargo 명령이 나왔다.
-
- 첫 패널이 기본이다. `:has()`를 모르는 브라우저에서도 curl 명령은 그대로
+/* 복사 버튼은 카드 오른쪽 끝에 붙는다. 명령 길이를 따라가면 탭을 옮길 때마다
+ 버튼이 300px씩 이동하는데, 누르려던 것이 도망가는 셈이다. */
+.pick-panel > .inst-copy { margin-left: auto; }
+/* 첫 패널이 기본이다. `:has()`를 모르는 브라우저에서도 curl 명령은 그대로
보이고 탭만 안 먹는다 — 이 페이지에서 가장 중요한 내용이 사라지면 안 된다.
채널이 셋인 건 packaging/README.md의 결정이고, 늘리면 여기도 는다. */
-.pick-panels { min-width: 0; max-width: 100%; }
-.pick-panel:first-child { display: flex; }
-.install:has(#inst-2:checked) .pick-panel { display: none; }
-.install:has(#inst-2:checked) .pick-panel:nth-child(2) { display: flex; }
-.install:has(#inst-3:checked) .pick-panel { display: none; }
-.install:has(#inst-3:checked) .pick-panel:nth-child(3) { display: flex; }
+.pick-panel:first-child { visibility: visible; opacity: 1; }
+.install:has(#inst-2:checked) .pick-panel,
+.install:has(#inst-3:checked) .pick-panel { visibility: hidden; opacity: 0; }
+.install:has(#inst-2:checked) .pick-panel:nth-child(2),
+.install:has(#inst-3:checked) .pick-panel:nth-child(3) { visibility: visible; opacity: 1; }
.install-note { margin: 0.7rem 0 0; font-size: 0.82rem; color: var(--muted); }
diff --git a/docs/static/js/landing.js b/docs/static/js/landing.js
@@ -37,6 +37,54 @@
var EASE = [0.16, 1, 0.3, 1];
+ /* 설치 채널 전환.
+
+ 선택 자체는 radio + CSS가 이미 하고 있다. 여기서 더하는 건 두 가지뿐이다 —
+ 선택을 따라 미끄러지는 표시자와, 명령이 바뀔 때의 squeeze. 둘 다 없어도
+ 기능은 그대로다(is-enhanced를 붙이기 전까지 CSS 배경이 선택을 나타낸다).
+
+ squeeze는 장식이 아니라 이 제품의 동작이다. 히어로의 강조 줄이 옆으로
+ 눌려 들어오는 것과 같은 스프링을 쓴다. */
+ var install = document.querySelector(".install");
+ if (install) {
+ var marker = install.querySelector(".pick-marker");
+ var tabs = [].slice.call(install.querySelectorAll(".pick-tab"));
+ var radios = [].slice.call(install.querySelectorAll(".pick-in"));
+ var panels = [].slice.call(install.querySelectorAll(".pick-panel"));
+
+ var place = function (animated) {
+ var i = radios.findIndex(function (r) { return r.checked; });
+ if (i < 0) return;
+ var tab = tabs[i];
+ var x = tab.offsetLeft;
+ var w = tab.offsetWidth;
+ /* x와 scaleX를 따로 준다. transform 문자열을 두 번 애니메이션하면 뒤엣것이
+ 앞엣것을 덮어 이동이 사라진다. */
+ if (!animated) {
+ M.animate(marker, { width: w + "px", x: x, scaleX: 1 }, { duration: 0 });
+ install.classList.add("is-enhanced");
+ return;
+ }
+ /* 이동하는 동안 눌렸다 펴진다. */
+ M.animate(marker, { width: w + "px", x: x }, { duration: 0.34, ease: EASE });
+ M.animate(marker, { scaleX: [0.72, 1] },
+ { type: "spring", stiffness: 260, damping: 18 });
+
+ M.animate(panels[i], { opacity: [0, 1], scaleX: [1.05, 1] },
+ { type: "spring", stiffness: 240, damping: 22 });
+ };
+
+ place(false);
+ radios.forEach(function (r) {
+ r.addEventListener("change", function () { place(true); });
+ });
+ /* 폰트가 늦게 붙으면 탭 폭이 달라진다. 그때 표시자가 어긋난 채로 남는다. */
+ if (document.fonts && document.fonts.ready) {
+ document.fonts.ready.then(function () { place(false); });
+ }
+ window.addEventListener("resize", function () { place(false); });
+ }
+
/* 히어로 로드 시퀀스 */
var heroEls = document.querySelectorAll(".hero [data-reveal]");
heroEls.forEach(function (el) { el.style.opacity = "0"; });
diff --git a/docs/templates/landing.html b/docs/templates/landing.html
@@ -89,6 +89,9 @@
<fieldset class="install" data-reveal>
<legend class="sr-only">{{ page.extra.install_pick }}</legend>
<div class="pick-tabs">
+ {#- JS가 움직이는 표시자. 스크립트가 없으면 라벨 자신의 배경이 선택을
+ 나타내고 이건 숨어 있다 — 선택 상태가 JS에 기대면 안 된다. #}
+ <span class="pick-marker" aria-hidden="true"></span>
{%- for m in page.extra.install %}
<input class="pick-in" type="radio" name="install-channel" id="inst-{{ loop.index }}"{% if loop.first %} checked{% endif %}>
<label class="pick-tab" for="inst-{{ loop.index }}">{{ m.label }}</label>
|