sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git
commit 455c1a70dadbd8c083c5c1a6c9b6577ca76328ab
parent f9363cb454f1be97f6c89ad6426515c926b1137d
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Sat, 1 Aug 2026 00:17:04 +0900

Make the landing install a channel picker, and use yay for the AUR

The three channels were a big curl block plus a line of prose reading
"With Rust instead: cargo install … · On Arch: paru -S sqzass". That is a list
of commands, not something a visitor can act on: the two alternatives are
small, unclickable, and share one copy button that only ever copies the curl
line.

Now the block has a row of pills — curl / cargo / AUR — and picking one swaps
the command and its copy button. The commands come from `[[extra.install]]` in
front matter, so adding a channel is a data change, not a template change.

Switching is radio inputs and CSS, no JavaScript. The install command is the
most important thing on this page and it should not wait for a script, so all
three are in the DOM and the first panel shows by default; `:has()` only moves
the selection. A browser without `:has()` still shows the curl command and
simply has inert tabs. Radios are also the honest semantics here — a screen
reader announces "curl, radio button, checked, 1 of 3", which is what this
widget is.

The AUR command is `yay`, not `paru`. yay is what most Arch users have and what
comparable projects print; paru was an arbitrary pick on my part. The docs still
mention paru and makepkg as alternatives, where there is room to.

Caught while screenshotting: the panels were selected with `:nth-of-type`, which
counts every sibling `div` — including the tab row — so the AUR tab showed the
cargo command. Panels now live in their own container and are selected with
`:nth-child`. Verified all three tabs, both themes, at 1100px and 500px.

Diffstat:
MREADME.md | 2+-
Mdocs/content/_index.ko.md | 15+++++++++++++--
Mdocs/content/_index.md | 16++++++++++++++--
Mdocs/content/start/installation.ko.md | 2+-
Mdocs/content/start/installation.md | 2+-
Mdocs/static/css/landing.css | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mdocs/templates/landing.html | 45+++++++++++++++++++++++++++++----------------
7 files changed, 135 insertion(+), 31 deletion(-)

diff --git a/README.md b/README.md
@@ -19,7 +19,7 @@ Vercel, Cloudflare Pages, or a plain directory served over HTTP.
 ```bash
 curl -fsSL https://sqzass.sqzer.com/install.sh | sh   # static binary
 cargo install --git https://github.com/sqzer-x/sqzass  # from source
-paru -S sqzass                                         # Arch, from the AUR
+yay -S sqzass                                          # Arch, from the AUR
 ```
 
 Every release also carries the tarballs directly, for Linux on x86_64 and ARM64
diff --git a/docs/content/_index.ko.md b/docs/content/_index.ko.md
@@ -11,8 +11,8 @@ hero_title_b = "25밀리초로 쥐어짭니다."
 hero_sub = "이름 그대로입니다. 사이트 전체를 한 번에 짜냅니다 — Hugo·Zola·Jekyll·Astro와 같은 머신에서 직접 재고, 측정 방법도 전부 공개해 뒀습니다."
 cta_start = "시작하기"
 cta_github = "GitHub에서 보기"
-install_alt = "Rust가 있다면 이렇게도 됩니다:"
-install_aur = "Arch라면:"
+install_pick = "설치 방법"
+install_note = "릴리스마다 체크섬이 붙은 타르볼도 함께 나갑니다 — x86_64·ARM64 리눅스, Apple Silicon macOS."
 
 bento_label = "왜 sqzass인가"
 
@@ -23,6 +23,17 @@ zero_label = "같은 입력을 두 번 빌드했을 때 생기는 바이트 차
 
 foot_benchmark = "벤치마크"
 
+[[extra.install]]
+label = "curl"
+
+[[extra.install]]
+label = "cargo"
+cmd = "cargo install --git https://github.com/sqzer-x/sqzass"
+
+[[extra.install]]
+label = "AUR"
+cmd = "yay -S sqzass"
+
 [[extra.bench]]
 label = "1,000페이지, 최소 마크다운"
 us_ms = "18"
diff --git a/docs/content/_index.md b/docs/content/_index.md
@@ -11,8 +11,8 @@ hero_title_b = "squeezed into 25 ms."
 hero_sub = "The name is the promise: press a whole site out in one go. Measured against Hugo, Zola, Jekyll and Astro on the same machine, with the method published."
 cta_start = "Get started"
 cta_github = "View on GitHub"
-install_alt = "With Rust instead:"
-install_aur = "On Arch:"
+install_pick = "Install with"
+install_note = "Every release also ships tarballs with checksums — Linux on x86_64 and ARM64, and Apple-silicon macOS."
 
 bento_label = "Why sqzass"
 
@@ -23,6 +23,18 @@ zero_label = "bytes of difference between two builds of the same input — check
 
 foot_benchmark = "Benchmark"
 
+[[extra.install]]
+label = "curl"
+# cmd 없음 = 템플릿이 이 사이트의 origin으로 조립한다. 프리뷰 배포에서도 맞는 URL이 나온다.
+
+[[extra.install]]
+label = "cargo"
+cmd = "cargo install --git https://github.com/sqzer-x/sqzass"
+
+[[extra.install]]
+label = "AUR"
+cmd = "yay -S sqzass"
+
 [[extra.bench]]
 label = "1,000 pages, minimal markdown"
 us_ms = "18"
diff --git a/docs/content/start/installation.ko.md b/docs/content/start/installation.ko.md
@@ -35,7 +35,7 @@ AUR에 [`sqzass`](https://aur.archlinux.org/packages/sqzass)가 있습니다. 
 태그에서 빌드합니다.
 
 ```bash
-paru -S sqzass          # yay도, 클론해서 makepkg -si도 됩니다
+yay -S sqzass          # paru도, 클론해서 makepkg -si도 됩니다
 ```
 
 `sqzass-bin`은 만들지 않았습니다. Arch에서 Rust 도구를 소스에서 빌드하는 건
diff --git a/docs/content/start/installation.md b/docs/content/start/installation.md
@@ -35,7 +35,7 @@ cargo install --git https://github.com/sqzer-x/sqzass
 the release tag:
 
 ```bash
-paru -S sqzass          # or yay, or makepkg -si on a clone
+yay -S sqzass          # or paru, or makepkg -si on a clone
 ```
 
 There is no `sqzass-bin`. Building a Rust tool from source is normal on Arch,
diff --git a/docs/static/css/landing.css b/docs/static/css/landing.css
@@ -325,19 +325,80 @@ main { display: block; }
 }
 
 /* 설치 한 줄 — 페이지에서 제일 중요한 것. CTA 바로 아래에서 눈에 띈다. */
+/* 채널 선택 + 명령. fieldset이라 기본 테두리·여백을 먼저 지운다.
+   align-items: flex-start라서 탭 행도 명령 상자도 제 내용만큼만 넓어진다. */
 .install {
-  display: inline-flex;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 0.5rem;
+  max-width: 100%;
+  border: 0;
+  padding: 0;
+  margin: 1.6rem 0 0;
+  min-width: 0;
+}
+.pick-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; }
+.pick-in {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  opacity: 0;
+  pointer-events: none;
+}
+.pick-tab {
+  cursor: pointer;
+  font-family: var(--mono);
+  font-size: 0.82rem;
+  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);
+}
+/* 라디오가 화면에 없으므로 포커스 링은 라벨이 받는다. 없으면 키보드로 못 쓴다. */
+.pick-in:focus-visible + .pick-tab {
+  outline: 2px solid var(--accent);
+  outline-offset: 2px;
+}
+
+.pick-panel {
+  display: none;
   align-items: center;
   gap: 0.4rem;
+  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;
-  margin: 1.6rem 0 0;
 }
-.install > code {
+/* 패널만 따로 감싼 건 `nth-child`를 정확히 세기 위해서다. fieldset 바로 아래에
+   두면 legend와 탭 행까지 형제로 세어 한 칸씩 밀린다 — 실제로 그렇게 밀려서
+   AUR 탭에 cargo 명령이 나왔다.
+
+   첫 패널이 기본이다. `: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; }
+
+.install-note { margin: 0.7rem 0 0; font-size: 0.82rem; color: var(--muted); }
+
+.pick-panel > code {
   font-family: var(--mono);
   font-size: 0.92rem;
   font-weight: 700;
@@ -346,11 +407,18 @@ main { display: block; }
   overflow-x: auto;
   scrollbar-width: none;
 }
-.install-alt { margin: 0.7rem 0 0; font-size: 0.9rem; color: var(--muted); }
-/* 좁은 화면에서는 두 대안이 각자 줄을 갖도록 구분자에서 끊는다. */
-.alt-sep { margin: 0 0.35rem; opacity: 0.55; }
-@media (max-width: 34rem) {
-  .alt-sep { display: block; height: 0; margin: 0.35rem 0 0; overflow: hidden; }
+/* 화면에서는 안 보이고 스크린리더에는 읽히는 것. fieldset의 legend가 그렇다 —
+   눈으로는 버튼 행 자체가 무엇을 고르는지 말해 주지만, 소리로는 아니다. */
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip-path: inset(50%);
+  white-space: nowrap;
+  border: 0;
 }
 .inline { font-family: var(--mono); font-size: 0.85em; color: var(--accent-ink); }
 .inst-copy {
diff --git a/docs/templates/landing.html b/docs/templates/landing.html
@@ -80,22 +80,35 @@
         <a class="btn btn-primary" href="{{ home }}start/">{{ page.extra.cta_start }}</a>
         <a class="btn btn-ghost" href="https://github.com/sqzer-x/sqzass">{{ page.extra.cta_github }}</a>
       </p>
-      {#- 제일 중요한 것: 설치 한 줄. CTA 바로 아래에 크게 둔다. #}
-      <div class="install" data-reveal>
-        <code>curl -fsSL {{ site.origin }}{{ site.base_path }}/install.sh | sh</code>
-        <button class="inst-copy" type="button" data-copy-text="curl -fsSL {{ site.origin }}{{ site.base_path }}/install.sh | sh"
-                aria-label="{{ t("copy") }}" data-copied="{{ t("copied") }}">
-          <svg class="i-copy" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
-          <svg class="i-check" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
-        </button>
-      </div>
-      {#- 나머지 두 채널. 큰 블록은 curl 하나로 두고, 여기서 한 줄에 붙인다 —
-          셋을 나란히 크게 놓으면 "무엇을 골라야 하나"가 첫 질문이 된다. #}
-      <p class="install-alt" data-reveal>
-        {{ page.extra.install_alt }} <code class="inline">cargo install --git https://github.com/sqzer-x/sqzass</code>
-        <span class="alt-sep" aria-hidden="true">·</span>
-        {{ page.extra.install_aur }} <code class="inline">paru -S sqzass</code>
-      </p>
+      {#- 제일 중요한 것: 설치 명령. CTA 바로 아래에 크게 둔다.
+
+          채널 전환은 radio + CSS만으로 한다. JS 없이도 세 명령이 전부 DOM에 있고
+          선택이 동작한다 — 이 페이지에서 가장 중요한 내용이 스크립트 로딩에
+          기대면 안 된다. radio를 쓴 건 접근성 때문이기도 하다: 스크린리더가
+          "curl, 라디오 버튼, 선택됨, 3개 중 1"로 읽는 게 이 UI의 실제 의미다. #}
+      <fieldset class="install" data-reveal>
+        <legend class="sr-only">{{ page.extra.install_pick }}</legend>
+        <div class="pick-tabs">
+          {%- 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>
+          {%- endfor %}
+        </div>
+        <div class="pick-panels">
+        {%- for m in page.extra.install %}
+        {%- set cmd = m.cmd if m.cmd else "curl -fsSL " ~ site.origin ~ site.base_path ~ "/install.sh | sh" %}
+        <div class="pick-panel">
+          <code>{{ cmd }}</code>
+          <button class="inst-copy" type="button" data-copy-text="{{ cmd }}"
+                  aria-label="{{ t("copy") }}" data-copied="{{ t("copied") }}">
+            <svg class="i-copy" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
+            <svg class="i-check" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
+          </button>
+        </div>
+        {%- endfor %}
+        </div>
+      </fieldset>
+      <p class="install-note" data-reveal>{{ page.extra.install_note }}</p>
       <div class="lede" data-reveal>
         {{ page.content | safe }}
       </div>