www.git

www

~ Personal site — projects, posts, about. Built with sqzass.
git clone https://github.com/debuman/www.git
commit 70e408e46b8d4af527a21b99dd0924270a7aee0b
parent 228a025383db740cc8734827ffd86995b0f88639
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Tue, 4 Aug 2026 00:47:59 +0900

Add the marks, the portrait, and favicons

The brand mark, the sqzass mark and the profile photo, plus a favicon set the
site did not have at all.

Both logos are black line art, and the shape is in the luminance rather than the
alpha — the PNGs carry a fully opaque alpha channel over a white background.
Reading them as alpha produced a solid rectangle, and cropping to the alpha
bounding box trimmed nothing. They are converted by inverting the luminance,
which gives a real mask and a real bounding box: 2816x1536 of mostly margin
becomes 1121x1074 of drawing.

They ship as CSS masks rather than images. Black lines on a near-black field are
invisible, and a project's own palette has no reason to win an argument with the
page. As masks they take currentColor, so they follow the theme, the hover state
and the light build with no second asset. 5 KB and 26 KB.

The ommp asset is a wide colour lockup with the product name in it, so it is a
cover rather than a mark. That splits the card artwork into three cases —
cover, mark, name — and all three now hold the same 16:7 box, because a card
whose media is shorter than its neighbour's starts its body on a different line
and the row reads as broken.

The portrait gets its own template. In the markdown body it would sit between
paragraphs and need its width decided per document; in the header there is one
place and one size. It rests desaturated: the orange backdrop is the loudest
colour on the site and it does not need to win the page on arrival.

Diffstat:
Mcontent/about/_index.ko.md | 6+++++-
Mcontent/about/_index.md | 6+++++-
Mcontent/projects/_index.ko.md | 4+++-
Mcontent/projects/_index.md | 4+++-
Mcontent/projects/doomsday.ko.md | 1+
Mcontent/projects/doomsday.md | 1+
Mcontent/projects/ommp.ko.md | 3++-
Mcontent/projects/ommp.md | 3++-
Mcontent/projects/sqzass.ko.md | 1+
Mcontent/projects/sqzass.md | 1+
Mcontent/projects/uaf.ko.md | 1+
Mcontent/projects/uaf.md | 1+
Mcontent/projects/udfx.ko.md | 1+
Mcontent/projects/udfx.md | 1+
Mcontent/projects/usfx.ko.md | 1+
Mcontent/projects/usfx.md | 1+
Mstatic/css/main.css | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Astatic/images/apple-touch-icon.png | 0binary
Astatic/images/favicon-dark.png | 0binary
Astatic/images/favicon.png | 0binary
Astatic/images/mark.png | 0binary
Astatic/images/profile.jpg | 0binary
Astatic/images/projects/ommp-cover.png | 0binary
Dstatic/images/projects/ommp.png | 0binary
Astatic/images/projects/sqzass.png | 0binary
Atemplates/about.html | 30++++++++++++++++++++++++++++++
Mtemplates/base.html | 16+++++++++++++---
Mtemplates/project.html | 13++++++++++---
Mtemplates/projects.html | 6++++--
29 files changed, 169 insertion(+), 20 deletion(-)

diff --git a/content/about/_index.ko.md b/content/about/_index.ko.md
@@ -2,7 +2,11 @@
 title = "소개"
 description = "sqzer.com 뒤에 누가 있고 여기서 무엇을 만드는지."
 weight = 30
-template = "page.html"
+template = "about.html"
+
+[extra]
+portrait     = "images/profile.jpg"
+portrait_alt = "sqzer-x"
 
 # 이름, 소속, 사는 곳, 이력 같은 개인적인 세부는 일부러 비워 두었다.
 # 지어내는 것보다 없는 편이 낫고, 채우는 건 본인만 할 수 있다.
diff --git a/content/about/_index.md b/content/about/_index.md
@@ -2,7 +2,11 @@
 title = "About"
 description = "Who is behind sqzer.com, and what gets built here."
 weight = 30
-template = "page.html"
+template = "about.html"
+
+[extra]
+portrait     = "images/profile.jpg"
+portrait_alt = "sqzer-x"
 
 # 개인적인 세부(이름, 소속, 사는 곳, 이력)는 일부러 비워 두었다. 지어내는 것보다
 # 없는 편이 낫고, 채우는 건 본인만 할 수 있다.
diff --git a/content/projects/_index.ko.md b/content/projects/_index.ko.md
@@ -15,6 +15,7 @@ page_template = "project.html"
 url   = "/ko/projects/sqzass/"
 name  = "sqzass"
 lang  = "Rust"
+mark  = "images/projects/sqzass.png"
 cover = ""
 blurb = "깨진 참조를 발행하지 않는 정적 사이트 생성기."
 site  = "https://sqzass.sqzer.com"
@@ -24,7 +25,8 @@ repo  = "https://github.com/sqzer-x/sqzass"
 url   = "/ko/projects/ommp/"
 name  = "ommp"
 lang  = "Rust"
-cover = ""
+mark  = ""
+cover = "images/projects/ommp-cover.png"
 blurb = "띄워 둘 데몬도, 끄고 남는 것도 없는 터미널 음악 재생기."
 site  = "https://ommp.sqzer.com"
 repo  = "https://github.com/sqzer-x/ommp"
diff --git a/content/projects/_index.md b/content/projects/_index.md
@@ -23,6 +23,7 @@ page_template = "project.html"
 url   = "/projects/sqzass/"
 name  = "sqzass"
 lang  = "Rust"
+mark  = "images/projects/sqzass.png"
 cover = ""
 blurb = "A static site generator that refuses to publish a broken reference."
 site  = "https://sqzass.sqzer.com"
@@ -32,7 +33,8 @@ repo  = "https://github.com/sqzer-x/sqzass"
 url   = "/projects/ommp/"
 name  = "ommp"
 lang  = "Rust"
-cover = ""
+mark  = ""
+cover = "images/projects/ommp-cover.png"
 blurb = "A terminal music player with no daemon to start and nothing left running."
 site  = "https://ommp.sqzer.com"
 repo  = "https://github.com/sqzer-x/ommp"
diff --git a/content/projects/doomsday.ko.md b/content/projects/doomsday.ko.md
@@ -13,6 +13,7 @@ lang     = "JavaScript"
 license  = "GPL-3.0"
 version  = ""
 platform = "GNOME 45 · 46 · 47"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/doomsday.md b/content/projects/doomsday.md
@@ -13,6 +13,7 @@ lang     = "JavaScript"
 license  = "GPL-3.0"
 version  = ""
 platform = "GNOME 45 · 46 · 47"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/ommp.ko.md b/content/projects/ommp.ko.md
@@ -13,7 +13,8 @@ lang     = "Rust"
 license  = "Apache-2.0"
 version  = ""
 platform = "Linux"
-logo     = "images/projects/ommp.png"
+mark     = ""
+logo     = ""
 
 actions = [
   { label = "웹사이트", url = "https://ommp.sqzer.com",          style = "primary" },
diff --git a/content/projects/ommp.md b/content/projects/ommp.md
@@ -13,7 +13,8 @@ lang     = "Rust"
 license  = "Apache-2.0"
 version  = ""
 platform = "Linux"
-logo     = "images/projects/ommp.png"
+mark     = ""
+logo     = ""
 
 actions = [
   { label = "Website", url = "https://ommp.sqzer.com",            style = "primary" },
diff --git a/content/projects/sqzass.ko.md b/content/projects/sqzass.ko.md
@@ -13,6 +13,7 @@ lang     = "Rust"
 license  = "MIT"
 version  = "0.1.0"
 platform = "Linux · macOS"
+mark     = "images/projects/sqzass.png"
 logo     = ""
 
 actions = [
diff --git a/content/projects/sqzass.md b/content/projects/sqzass.md
@@ -13,6 +13,7 @@ lang     = "Rust"
 license  = "MIT"
 version  = "0.1.0"
 platform = "Linux · macOS"
+mark     = "images/projects/sqzass.png"
 logo     = ""
 
 actions = [
diff --git a/content/projects/uaf.ko.md b/content/projects/uaf.ko.md
@@ -13,6 +13,7 @@ lang     = "Python"
 license  = ""
 version  = ""
 platform = "Linux"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/uaf.md b/content/projects/uaf.md
@@ -13,6 +13,7 @@ lang     = "Python"
 license  = ""
 version  = ""
 platform = "Linux"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/udfx.ko.md b/content/projects/udfx.ko.md
@@ -13,6 +13,7 @@ lang     = "PowerShell"
 license  = ""
 version  = ""
 platform = "Windows"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/udfx.md b/content/projects/udfx.md
@@ -13,6 +13,7 @@ lang     = "PowerShell"
 license  = ""
 version  = ""
 platform = "Windows"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/usfx.ko.md b/content/projects/usfx.ko.md
@@ -13,6 +13,7 @@ lang     = "Python"
 license  = ""
 version  = "1.2.0"
 platform = "크로스 플랫폼"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/content/projects/usfx.md b/content/projects/usfx.md
@@ -13,6 +13,7 @@ lang     = "Python"
 license  = ""
 version  = "1.2.0"
 platform = "Cross-platform"
+mark     = ""
 logo     = ""
 
 actions = [
diff --git a/static/css/main.css b/static/css/main.css
@@ -205,9 +205,25 @@ main { display: block; padding-bottom: clamp(4rem, 10vw, 8rem); }
   font-weight: 700;
   letter-spacing: -0.03em;
 }
-/* 마크는 그림이 아니라 글자다 — 프롬프트 기호. 로고 파일이 없어도 성립하고
-   currentColor 를 타므로 테마를 따라간다. */
-.brand-mark { color: var(--live); }
+/* 마크는 잉크로 그린다. 원본이 투명 배경의 검은 선화라 <img> 로 넣으면 다크
+   배경에서 사라지는데, 마스크로 두면 배경색이 currentColor 가 되어 글자와 같이
+   변한다. 원본 비율 240:131 을 박아 두어 로딩 중에도 자리가 안 움직인다. */
+.brand-mark {
+  width: 1.6em;
+  aspect-ratio: 128 / 123;
+  flex: none;
+  align-self: center;
+  background-color: var(--live);
+  -webkit-mask: url("/images/mark.png") center / contain no-repeat;
+  mask: url("/images/mark.png") center / contain no-repeat;
+  transition: background-color var(--tr-ink);
+}
+.brand:hover .brand-mark { background-color: var(--ink); }
+/* 마스크를 모르는 브라우저에서는 색만 칠한 네모가 남는다. 워드마크가 이미
+   이름을 말하고 있으므로 마크를 지우는 편이 낫다. */
+@supports not ((mask: url("")) or (-webkit-mask: url(""))) {
+  .brand-mark { display: none; }
+}
 .brand-name { color: var(--ink); }
 
 .nav {
@@ -324,6 +340,37 @@ main { display: block; padding-bottom: clamp(4rem, 10vw, 8rem); }
   color: var(--ink-3);
 }
 
+/* 소개의 머리. 초상과 제목이 나란히 서고, 좁아지면 초상이 위로 간다.
+   align-items 가 end 인 이유는 제목의 베이스라인이 초상의 아래변에 맞아야
+   둘이 같은 바닥을 딛고 선 것으로 보이기 때문이다. */
+.about-head {
+  display: grid;
+  grid-template-columns: 11rem minmax(0, 1fr);
+  gap: clamp(1.25rem, 3vw, 2.25rem);
+  align-items: end;
+}
+.about-intro { min-width: 0; }
+
+.portrait { margin: 0; }
+.portrait img {
+  display: block;
+  width: 100%;
+  aspect-ratio: 4 / 5;
+  object-fit: cover;
+  border: 1px solid var(--rule);
+  border-radius: var(--radius);
+  /* 사진의 주황 배경이 이 페이지에서 제일 센 색이다. 쉬는 동안 한 걸음 물러나
+     있다가 다가가면 제 색으로 돌아온다. */
+  filter: saturate(0.75) brightness(0.94);
+  transition: filter var(--tr-light);
+}
+.about-head:hover .portrait img { filter: none; }
+
+@media (max-width: 34rem) {
+  .about-head { grid-template-columns: minmax(0, 1fr); }
+  .portrait { max-width: 9rem; }
+}
+
 .crumbs { margin-bottom: 0.9rem; font-family: var(--mono); font-size: 0.75rem; }
 .crumbs a { color: var(--ink-3); transition: color var(--tr-ink); }
 .crumbs a:hover { color: var(--live); }
@@ -631,9 +678,23 @@ main { display: block; padding-bottom: clamp(4rem, 10vw, 8rem); }
   overflow: hidden;
 }
 .bento-media img { width: 100%; height: 100%; object-fit: cover; }
-/* 이미지가 없으면 낮춘다. 워드마크 하나를 담자고 16:7 을 비워 두면 카드가
-   비어 있는 것으로 읽힌다. 이미지가 들어오면 원래 비율로 돌아온다. */
-.bento-media:not(:has(img)) { aspect-ratio: 16 / 4.5; }
+
+/* 선화 마크. 메달리온과 같은 방식이라 상세 페이지와 카드가 같은 그림을 같은
+   규칙으로 그린다. */
+.bento-mark {
+  width: 46%;
+  aspect-ratio: 1;
+  background-color: var(--ink-4);
+  -webkit-mask: var(--mark-src) center / contain no-repeat;
+  mask: var(--mark-src) center / contain no-repeat;
+  transition: background-color var(--tr-light);
+}
+.bento-card:hover .bento-mark { background-color: var(--ink-3); }
+
+/* 그림이 아무것도 없을 때만 낮춘다. 이름 하나를 담자고 16:7 을 비워 두면 카드가
+   비어 있는 것으로 읽힌다. 커버든 마크든 들어오면 원래 비율이고, 그래야 카드
+   여러 장의 본문이 같은 높이에서 시작한다. */
+.bento-media:not(:has(img)):not(:has(.bento-mark)) { aspect-ratio: 16 / 4.5; }
 
 .bento-glyph {
   font-family: var(--mono);
@@ -785,6 +846,21 @@ main { display: block; padding-bottom: clamp(4rem, 10vw, 8rem); }
   overflow: hidden;
 }
 .medallion img { width: 76%; height: 76%; object-fit: contain; }
+
+/* 선화 마크. 인라인으로 오는 것은 --mark-src 하나뿐이고 그건 장식이 아니라
+   데이터다 — 어떤 파일인지는 프로젝트마다 다르고, 어떻게 그릴지는 전부 여기 있다.
+   currentColor 를 타므로 테마를 따라가고, 원본이 무슨 색이든 페이지와 싸우지 않는다. */
+.medallion-mark::before {
+  content: "";
+  width: 76%;
+  height: 76%;
+  background-color: var(--ink-3);
+  -webkit-mask: var(--mark-src) center / contain no-repeat;
+  mask: var(--mark-src) center / contain no-repeat;
+  transition: background-color var(--tr-light);
+}
+.hero:hover .medallion-mark::before { background-color: var(--ink-2); }
+
 .medallion-glyph {
   font-family: var(--mono);
   font-weight: 700;
diff --git a/static/images/apple-touch-icon.png b/static/images/apple-touch-icon.png
Binary files differ.
diff --git a/static/images/favicon-dark.png b/static/images/favicon-dark.png
Binary files differ.
diff --git a/static/images/favicon.png b/static/images/favicon.png
Binary files differ.
diff --git a/static/images/mark.png b/static/images/mark.png
Binary files differ.
diff --git a/static/images/profile.jpg b/static/images/profile.jpg
Binary files differ.
diff --git a/static/images/projects/ommp-cover.png b/static/images/projects/ommp-cover.png
Binary files differ.
diff --git a/static/images/projects/ommp.png b/static/images/projects/ommp.png
Binary files differ.
diff --git a/static/images/projects/sqzass.png b/static/images/projects/sqzass.png
Binary files differ.
diff --git a/templates/about.html b/templates/about.html
@@ -0,0 +1,30 @@
+{#- 소개. 초상이 붙는다는 점만 page.html 과 다르다.
+
+    초상을 마크다운 본문에 넣지 않는 이유: 본문 안에 있으면 문단 사이에 끼어
+    흐름을 끊고, 폭과 비율을 문서마다 다시 정해야 한다. 머리에 두면 자리가
+    한 곳이고 규격도 하나다. #}
+{% extends "base.html" %}
+
+{% block content %}
+<div class="wrap wrap-read">
+  <header class="page-head about-head" data-reveal>
+    {%- if page.extra.portrait %}
+    {#- alt 는 사람을 말한다. 장식이 아니라 "누구인지"가 이 페이지의 내용이다. #}
+    <figure class="portrait">
+      <img src="{{ asset(page.extra.portrait) }}" alt="{{ page.extra.portrait_alt }}"
+           width="720" height="900" loading="eager" decoding="async">
+    </figure>
+    {%- endif %}
+    <div class="about-intro">
+      <h1 class="page-title">{{ page.title }}</h1>
+      {%- if page.description %}
+      <p class="page-lede">{{ page.description }}</p>
+      {%- endif %}
+    </div>
+  </header>
+
+  <div class="prose">
+    {{ page.content | safe }}
+  </div>
+</div>
+{% endblock %}
diff --git a/templates/base.html b/templates/base.html
@@ -29,6 +29,13 @@
 <link rel="alternate" hreflang="{{ lang.code }}" href="{{ site.origin }}{{ lang.url }}">
 {%- endfor %}
 
+{#- 파비콘에는 CSS 가 닿지 않는다. 검은 선화는 어두운 탭에서 안 보이므로 흰 선
+    버전을 따로 준다. media 를 모르는 브라우저는 마지막 것을 쓰므로, 밝은 탭이
+    기본인 쪽을 뒤에 둔다. #}
+<link rel="icon" type="image/png" media="(prefers-color-scheme: dark)" href="{{ asset("images/favicon-dark.png") }}">
+<link rel="icon" type="image/png" href="{{ asset("images/favicon.png") }}">
+<link rel="apple-touch-icon" href="{{ asset("images/apple-touch-icon.png") }}">
+
 {#- 소셜 카드. sqzass 는 <head> 에 아무것도 넣지 않으므로 전부 우리 마크업이다.
     summary_large_image 는 이미지가 있을 때만 뜻이 있다 — 없이 선언하면 빈 상자가 나온다. #}
 <meta property="og:type" content="{{ "website" if page.is_section else "article" }}">
@@ -59,10 +66,13 @@
 
 <header class="masthead">
   <div class="masthead-in">
-    {#- 로고 이미지는 없다. 워드마크가 곧 마크다 — mono 700 에 음수 트래킹.
-        alt 를 비우지 않는 이유는 이게 이미지가 아니라 텍스트이기 때문이다. #}
+    {#- 마크는 <img> 가 아니라 CSS 마스크다. 원본이 투명 배경의 검은 선화라
+        그대로 넣으면 다크 배경에서 사라진다. 마스크로 두면 currentColor 를
+        타서 테마와 호버를 글자와 똑같이 따라간다.
+        aria-hidden 인 이유는 바로 옆 텍스트가 이미 같은 이름을 말하기 때문이다 —
+        여기 이름을 또 넣으면 스크린리더가 두 번 읽는다. #}
     <a class="brand" href="{{ home }}">
-      <span class="brand-mark" aria-hidden="true">$</span>
+      <span class="brand-mark" aria-hidden="true"></span>
       <span class="brand-name">{{ site.title }}</span>
     </a>
 
diff --git a/templates/project.html b/templates/project.html
@@ -50,10 +50,17 @@
       {%- endif %}
     </div>
 
-    {#- 옆칸. 로고가 있으면 메달리온, 없으면 이름을 크게 깐다. 규격이 같아서
-        나중에 SVG 를 넣어도 레이아웃이 움직이지 않는다. #}
+    {#- 옆칸. 세 가지가 같은 규격의 액자 안에 들어간다.
+
+        `mark` 는 선화다. 마스크로 깔아 currentColor 를 타게 하므로 테마를
+        따라가고, 프로젝트마다 다른 원본 색이 페이지와 싸우지 않는다.
+        `logo` 는 색이 의미를 지는 그림이라 <img> 로 그대로 둔다.
+        둘 다 없으면 이름이 그 자리를 지킨다 — 나중에 그림을 넣어도 레이아웃이
+        움직이지 않는 이유가 이것이다. #}
     <div class="hero-aside" aria-hidden="true">
-      {%- if page.extra.logo %}
+      {%- if page.extra.mark %}
+      <span class="medallion medallion-mark" style="--mark-src: url('/{{ page.extra.mark }}')"></span>
+      {%- elif page.extra.logo %}
       <span class="medallion"><img src="{{ asset(page.extra.logo) }}" alt="" loading="eager" decoding="async"></span>
       {%- else %}
       <span class="medallion medallion-glyph">{{ page.title }}</span>
diff --git a/templates/projects.html b/templates/projects.html
@@ -29,12 +29,14 @@
   <section class="bento" aria-label="{{ t("selected") }}">
     {%- for p in page.extra.featured %}
     <article class="bento-card" data-reveal>
+      {#- 카드 그림은 세 갈래이고 상세 페이지의 메달리온과 같은 규칙이다.
+          커버 사진 → 마크(선화) → 이름. 뒤로 갈수록 조용해질 뿐 자리는 같다. #}
       <div class="bento-media">
         {%- if p.cover %}
         <img src="{{ asset(p.cover) }}" alt="" loading="lazy" decoding="async">
+        {%- elif p.mark %}
+        <span class="bento-mark" aria-hidden="true" style="--mark-src: url('/{{ p.mark }}')"></span>
         {%- else %}
-        {#- 커버가 없을 때 회색 상자를 놓지 않는다. 이름 자체가 그림이 된다 —
-            mono 로 크게 깔고 뒤로 물린다. 나중에 이미지를 넣으면 이게 물러난다. #}
         <span class="bento-glyph" aria-hidden="true">{{ p.name }}</span>
         {%- endif %}
       </div>