www.git

www

~ Personal site — projects, posts, about. Built with sqzass.
git clone https://github.com/debuman/www.git
commit 5e191b0b5ee3ac0cda2d06c49309a85629077be1
parent 48fd81024925279e6f7e864fee036ceb238dafce
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Wed, 5 Aug 2026 18:47:11 +0900

One size for every post, and a section head that is one line

The card no longer changes size with the post in it. Its height came from
whichever was taller, the picture's ratio or the label's text, so a long title
grew the box. The ratio now sits on the card itself: 8/3, which is not a chosen
number — the picture takes 0.6 of the card's width and is 16:10, so the card is
0.6W ÷ 1.6 = W ÷ 2.667. The picture just fills its cell. The title clamps at
three lines and the description at two, which at 1440 needs 240 of the 247px the
label has spare, so nothing is cut for any title that will realistically arrive.
Swept 1920 down to 900 with titles of 15, 40 and 97 characters: 2.67:1 at every
one, no overflow.

The stacking point moves from 46rem to 56rem, because below about 900px the
fixed box cannot hold four lines and the honest answer there is to stack rather
than to bend the box.

The section head becomes one row: the name, a hairline running the width, and
the door to that section at the far end. It was a centred heading above the
content and a centred door below it, 166px of chrome around the card with the
head and the tail of one section separated by everything between them. Now it is
39px and both ends are in the same place.

Left-anchored, because everything under it already is — the card's label starts
at the left, the ledger's date gutter starts at the left. The centred axis
belongs to the hero and to a single piece of writing; a section head belongs to
the list beneath it, and it now shares that list's left edge exactly.

Project group heads on /projects/ use the same row without a door, so the
hairline runs to the end. Their note moves left to sit under the head rather
than centred beneath a left-anchored line.

Diffstat:
Mstatic/css/main.css | 58++++++++++++++++++++++++++++++++++++++++++++--------------
Mstatic/fonts/display-kr.woff2 | 0binary
Mtemplates/home.html | 20++++++++++++++++----
Mtemplates/projects.html | 5++++-
4 files changed, 64 insertion(+), 19 deletion(-)

diff --git a/static/css/main.css b/static/css/main.css
@@ -426,18 +426,26 @@ main { display: block; padding-bottom: clamp(5rem, 12vw, 9rem); }
    위 여백이 넉넉해야 연이 연으로 읽힌다. */
 /* 갈래 머리. 옆줄은 없다 — 한 가지 일에 한 가지 장치이고, 위아래 여백이
    이미 연을 끊고 있다. 좁아지면 줄이는 게 아니라 뺀다. */
+/* 갈래 머리는 한 줄이다: 이름, 그 갈래로 가는 문까지 이어지는 헤어라인, 그리고
+   문. 헤어라인은 장식이 아니라 둘을 한 줄의 양끝으로 묶는 것이고, 그래서 이름과
+   문 사이에 무엇이 오든 둘은 같은 줄에 있다.
+   아래 내용이 전부 왼쪽 정렬(카드의 라벨, 원장의 날짜 거터)이라 머리도 왼쪽에서
+   시작한다. 가운데축은 히어로와 글 한 편의 것이고, 갈래의 머리는 목록의 것이다. */
 .room-head {
-  margin: clamp(4.5rem, 11vw, 7rem) 0 clamp(1.75rem, 4vw, 2.5rem);
-  text-align: center;
+  display: flex; align-items: center;
+  gap: clamp(0.9rem, 2vw, 1.5rem);
+  margin: clamp(4.5rem, 11vw, 7rem) 0 clamp(1.5rem, 3.5vw, 2.25rem);
 }
 .room-head h2 {
-  margin: 0;
+  margin: 0; flex: none;
   font-size: clamp(1.35rem, 1.15rem + 0.9vw, 1.7rem);
   font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
 }
+/* 남는 자리를 전부 먹는다. 문이 없는 갈래(프로젝트 묶음)에서는 끝까지 간다. */
+.room-rule { flex: 1 1 auto; min-width: 1rem; height: 1px; background: var(--rule); }
 .room-head-sub {
-  margin: -0.75rem auto clamp(1.75rem, 4vw, 2.5rem);
-  max-width: 54ch; text-align: center;
+  margin: -0.75rem 0 clamp(1.75rem, 4vw, 2.5rem);
+  max-width: 54ch;
   font-size: 0.92rem; color: var(--ink-3);
 }
 
@@ -495,14 +503,16 @@ main { display: block; padding-bottom: clamp(5rem, 12vw, 9rem); }
 
 .room { padding-block: 0; }
 
+/* 문은 머리의 오른쪽 끝에 선다. 밑줄은 이 사이트의 문(.door)과 같은 물건이다. */
 .room-door {
-  display: block; text-align: center;
-  margin-top: clamp(2rem, 5vw, 3rem);
+  flex: none;
+  padding-bottom: 0.25rem;
   font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase;
   color: var(--ink-3);
-  transition: color var(--tr-ink);
+  border-bottom: 1px solid var(--rule-2);
+  transition: color var(--tr-ink), border-color var(--tr-ink);
 }
-.room-door:hover { color: var(--accent); }
+.room-door:hover { color: var(--ink); border-bottom-color: var(--accent); }
 
 /* ---------- featured frames ---------- */
 
@@ -748,6 +758,12 @@ html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none;
 .feature {
   display: grid;
   grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
+  /* 카드의 크기는 글이 정하지 않는다. 어느 글이 맨 위에 오든 같은 상자다.
+     8/3 은 지어낸 값이 아니라 두 열에서 따라 나온다: 그림이 카드 폭의 0.6 을
+     차지하고 그 그림이 16:10 이므로 카드 높이 = 0.6W ÷ 1.6 = W ÷ 2.667 이다.
+     여기 비율을 걸어 두면 이름표가 행을 늘리지 못하고, 그림은 자기 칸을 채우기만
+     하면 되므로 aspect-ratio 를 따로 갖지 않는다. */
+  aspect-ratio: 8 / 3;
   border: 1px solid var(--rule);
   border-radius: var(--radius);
   overflow: hidden;
@@ -787,6 +803,9 @@ html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none;
 .feature-label {
   display: flex; flex-direction: column; justify-content: center;
   gap: 1.15rem; min-width: 0;
+  /* 상자가 고정이므로 넘칠 수 있는 쪽은 글이다. 아래 줄 수 제한이 그걸 막고,
+     이건 그래도 남는 경우의 마지막 방어선이다. */
+  overflow: hidden;
   /* 상한은 카드가 자라기를 멈추는 폭(.wrap 이 71rem 에 닿는 1349px)에서 함께
      닿는다. 더 늦게 잡으면 상자는 그대로인데 여백만 계속 자란다. */
   padding: clamp(1.5rem, 3.4vw, 2.4rem);
@@ -807,13 +826,21 @@ html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none;
   font-size: clamp(1.5rem, 1.15rem + 1.95vw, 2.6rem);
   font-weight: var(--w-section); letter-spacing: var(--track-air); line-height: 1.2;
   color: var(--ink-2); transition: color var(--tr-ink);
+  /* 세 줄까지. 1440 에서 이름표 안쪽이 372px 이고 날짜·설명·문·간격이 125px 을
+     쓰므로 제목에 247px 이 남는다 — 세 줄이 178px 이라 어떤 제목이 와도 들어온다. */
+  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
+  overflow: hidden;
   /* 이 시트의 모든 제목이 balance 다. 한때 여기만 pretty 로 바꿔 두고 "balance 는
      제목을 한 줄로 붙인다"고 적었는데, 재 보니 39자 제목에서 둘 다 세 줄이고
      balance 쪽이 줄 길이만 고르게 맞춘다. 틀린 이유로 예외를 만들 뻔했다. */
   text-wrap: balance;
 }
 .feature:hover .feature-title { color: var(--ink); }
-.feature-desc { margin: 0; max-width: 38ch; font-size: 0.96rem; color: var(--ink-3); text-wrap: pretty; }
+.feature-desc {
+  margin: 0; max-width: 38ch; font-size: 0.96rem; color: var(--ink-3); text-wrap: pretty;
+  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
+  overflow: hidden;
+}
 
 /* 글 덩이의 아래 끝. 문(.door)과 같은 물건이다 — 밑줄 그은 마이크로캡스.
    align-self 가 없으면 flex 열에서 가로로 늘어나 밑줄이 라벨 폭을 가로지른다. */
@@ -829,11 +856,14 @@ html[lang="ko"] [data-tooltip]::after { letter-spacing: 0; text-transform: none;
    그걸 갖는다 — 위 규칙에 그렇게 적어 놓고 여기서 두 번째를 켰었다. */
 .feature:hover .feature-cta { color: var(--ink-2); border-bottom-color: var(--rule-3); }
 
-/* 좁아지면 그림이 위로 올라간다. 옆으로 두면 두 칸 다 못 쓸 만큼 좁아진다. */
-@media (max-width: 46rem) {
-  .feature { grid-template-columns: 1fr; }
+/* 좁아지면 그림이 위로 올라간다. 옆으로 두면 두 칸 다 못 쓸 만큼 좁아진다.
+   64rem 인 이유: 그 아래에서는 고정 8/3 상자에 네 줄이 안 들어간다. 상자를
+   늘리는 대신 쌓는다 — 쌓인 카드는 글이 크기를 정해도 되고, 그게 이 폭에서
+   맞는 답이다. */
+@media (max-width: 56rem) {
+  .feature { grid-template-columns: 1fr; aspect-ratio: auto; }
   .feature-media {
-    min-height: 0; aspect-ratio: 16 / 9;
+    aspect-ratio: 16 / 10;
     border-right: 0; border-bottom: 1px solid var(--rule);
   }
 }
diff --git a/static/fonts/display-kr.woff2 b/static/fonts/display-kr.woff2
Binary files differ.
diff --git a/templates/home.html b/templates/home.html
@@ -28,7 +28,17 @@
 {%- for s in site.sections %}
 {%- if s.url == home ~ "posts/" and s.pages %}
 <section class="room wrap">
-  <div class="room-head"><h2>{{ t("writing") }}</h2></div>
+  {#- 갈래 이름과 그 갈래로 가는 문이 한 줄에 선다. 사이의 헤어라인이 둘을
+      같은 줄의 양끝으로 묶는다 — 이름은 왼쪽에서 시작하고 문은 오른쪽 끝에서
+      기다린다. 전에는 이름이 가운데 위에, 문이 가운데 아래에 따로 서서 한
+      갈래의 머리와 꼬리가 166px 떨어져 있었다.
+      아래 내용(카드의 라벨, 원장의 행)이 전부 왼쪽 정렬이므로, 가운데 서 있던
+      것은 오히려 머리 쪽이었다. #}
+  <header class="room-head">
+    <h2>{{ t("writing") }}</h2>
+    <span class="room-rule" aria-hidden="true"></span>
+    <a class="room-door" href="{{ s.url }}">{{ t("all_posts") }}</a>
+  </header>
 
   {%- for p in s.pages %}
   {%- if loop.first %}
@@ -61,7 +71,6 @@
   {%- endfor %}
 
   {{ ledger(s.pages, 4, false, 1, page.language) }}
-  <a class="room-door" href="{{ s.url }}">{{ t("all_posts") }}</a>
 </section>
 {%- endif %}
 {%- endfor %}
@@ -72,7 +81,11 @@
 {%- for s in site.sections %}
 {%- if s.url == home ~ "projects/" and s.pages %}
 <section class="room wrap">
-  <div class="room-head"><h2>{{ s.title }}</h2></div>
+  <header class="room-head">
+    <h2>{{ s.title }}</h2>
+    <span class="room-rule" aria-hidden="true"></span>
+    <a class="room-door" href="{{ s.url }}">{{ t("all_projects") }}</a>
+  </header>
   <div class="tiles">
     {%- for p in s.pages %}
     <article class="tile">
@@ -83,7 +96,6 @@
     </article>
     {%- endfor %}
   </div>
-  <a class="room-door" href="{{ s.url }}">{{ t("all_projects") }}</a>
 </section>
 {%- endif %}
 {%- endfor %}
diff --git a/templates/projects.html b/templates/projects.html
@@ -67,7 +67,10 @@
       분류가 곧 저자의 편집 결정이 된다. #}
   {%- for g in page.extra.group %}
   <section id="{{ g.id }}">
-    <div class="room-head"><h2>{{ g.label }}</h2></div>
+    <header class="room-head">
+      <h2>{{ g.label }}</h2>
+      <span class="room-rule" aria-hidden="true"></span>
+    </header>
     {%- if g.note %}
     <p class="room-head-sub">{{ g.note }}</p>
     {%- endif %}