diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
@@ -9,11 +9,13 @@ interface Props {
description?: string;
section?: string;
/**
- * Does this page put Hangul on screen? It decides which font is preloaded, and it is
- * NOT the same question as the document language. /writing/ is an English document —
- * heading "Writing", year kickers, "30 posts" — that renders 21 Korean titles above
- * the fold, and `/` is an English document whose one tagline is Korean. 31 of the 35
- * built pages need the Korean face; only /papers/, /talk/, /code/ and /404.html do not.
+ * Does this page put Hangul on screen? It decides whether the Korean face is preloaded,
+ * and it is NOT the same question as the document language. /writing/ is an English
+ * document — heading "Writing", year kickers — that renders Korean titles above the
+ * fold; `/` takes the answer from about.md's body and /code/ from each project's name
+ * and body, which no title or description reveals. Pages with no Korean on screen skip the face: /papers/, /talk/
+ * and /404.html always, `/` and /code/ while about.md and the project descriptions stay
+ * English.
* Pass it explicitly wherever the Korean text is not already in title or description.
*/
ko?: boolean;
@@ -21,12 +23,13 @@ interface Props {
const { title, description, section = 'slate', ko } = Astro.props;
/**
- * Preload the face that blocks first paint on THIS route, not a fixed pair.
+ * Preload only the faces that block first paint on THIS route.
*
- * Measured: the Korean face is 118,240B against 21,816 + 21,308 for the Latin pair, and
- * it is discovered last — HTML, then CSS, then layout, then the request. On a Korean
- * article the display face sets exactly one string above the fold, the wordmark, so
- * preloading it ahead of the Korean body text inverts the priority.
+ * Measured: the Korean face is 118,136B against 22,300B for Lora, and it is discovered
+ * last — HTML, then CSS, then layout, then the request. Lora sets every page, so it is
+ * preloaded everywhere; the Korean face only where the page puts Hangul on screen, which
+ * is what `ko` answers and what `needsKo` falls back to asking of the title and
+ * description.
*
* woff2 is already Brotli-compressed, so none of these numbers move on the wire.
*/
@@ -66,22 +69,21 @@ const currentNav = NAV.find((n) => n.href !== '/' && isCurrent(n.href));
const SITE_OWNER = 'Hyukjin An';
const pageName = currentNav?.label ?? SITE_OWNER;
-/* Copyright range. The opening year is fixed; the closing year is the build's, in UTC like
- every other date on this site. Stamping it at build time rather than typing 2026 means the
- notice cannot quietly go stale — and if a year passes with no push, it correctly keeps
- showing the year of the last publication rather than a year nothing was published in. */
/* Profiles, not a link list: rel="me" below only means anything for places that are
this person. GitHub and Instagram are the pair sqzer.com's own footer carries. The
- LinkedIn handle is the author's own correction — what stood here came from the previous
- about.md and was /in/devastator-x, the same handle as a GitHub account that now 404s.
- No automated check would have caught it: LinkedIn answers robots with a 999 whatever
- the URL, so a dead profile and a live one look identical from here. */
+ LinkedIn handle is the author's own, and only the author can vouch for it: LinkedIn
+ answers robots with a 999 whatever the URL, so a dead profile and a live one look
+ identical to any automated check. */
const ELSEWHERE = [
{ label: 'GitHub', href: 'https://github.com/sqzer-x' },
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/sqzer' },
{ label: 'Instagram', href: 'https://www.instagram.com/sqzer_x' },
];
+/* Copyright range. The opening year is fixed; the closing year is the build's, in UTC like
+ every other date on this site. Stamping it at build time rather than typing 2026 means the
+ notice cannot quietly go stale — and if a year passes with no push, it correctly keeps
+ showing the year of the last publication rather than a year nothing was published in. */
const SITE_YEAR_FROM = 2009;
const buildYear = new Date().getUTCFullYear();
---
@@ -94,17 +96,16 @@ const buildYear = new Date().getUTCFullYear();
{description && <meta name="description" content={description} />}
<link rel="canonical" href={new URL(here, Astro.site)} />
<link rel="alternate" type="application/rss+xml" title="sqzer" href="/index.xml" />
- {/* The site had no icon at all: /favicon.ico and /favicon.svg both 404'd, so every tab
- showed the browser's placeholder. It is the author's own portrait, cropped square
- on the head rather than scaled from the 4:5 original — a whole portrait shrunk to
- 16px is a smudge, where a head fills the frame and still reads.
+ {/* The icon is the author's own portrait, cropped square on the head rather than
+ scaled from the 4:5 original — a whole portrait shrunk to 16px is a smudge, where
+ a head fills the frame and still reads.
No SVG icon: this is a photograph, and the .ico carries 16, 32 and 48 for the
sizes that actually get asked for. */}
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="icon" type="image/png" href="/favicon-32.png" sizes="32x32" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
- {/* One Latin face now, so this is unconditional: every page paints Lora, and the
- old branch between a display face and the Korean one has nothing left to pick. */}
+ {/* One Latin face, so this is unconditional: every page paints Lora. The Korean
+ face follows needsKo. */}
<link rel="preload" href="/fonts/latin-9bba8f25.woff2" as="font" type="font/woff2" crossorigin />
{needsKo && <link rel="preload" href="/fonts/ko-02f8cebd.woff2" as="font" type="font/woff2" crossorigin />}
</head>
diff --git a/src/pages/code/index.astro b/src/pages/code/index.astro
@@ -22,7 +22,7 @@ const projects = (await getCollection('code'))
);
/* The description is each file's markdown body, so every entry has to be rendered before
- the template can lay it out. Seven `render()` calls, resolved together. */
+ the template can lay it out. One `render()` call per entry, resolved together. */
projects.forEach(assertRendered);
const entries = await Promise.all(
projects.map(async (p) => ({ entry: p, Content: (await render(p)).Content })),
@@ -30,9 +30,9 @@ const entries = await Promise.all(
/* The same question src/pages/index.astro asks of about.md, and for the same reason: the
metadata on this route is English whatever the content says, so only the content can
- answer whether the 118KB Korean face blocks first paint. It is false for all seven
- projects today — which is what the note in Base.astro claims about /code/ — and it
- stops being a lie the day one of these descriptions is written in Korean. */
+ answer whether the 118KB Korean face blocks first paint. Every name and description is
+ English today, so the answer is false and /code/ fetches no Korean font, as the `ko`
+ note in Base.astro says; the day one of them is written in Korean, it becomes true. */
const ko = projects.some((p) => hasHangul(p.data.name, p.body));
---
<Base
@@ -41,10 +41,6 @@ const ko = projects.some((p) => hasHangul(p.data.name, p.body));
ko={ko}
>
<div class="code-index">
- {/* Flush with the page box, exactly as .index__head is on /writing/: the h1 is the
- page's own name, so it takes the shell's edge rather than a column's. Left as a
- bare <h1> with no class and no font rules of its own, which is what puts it on the
- same left edge and at the same size as the h1 on /papers/ and /talk/. */}
{/* The reference carries no page title on its section pages: the bar's left slot
already prints the page name, so an <h1> repeating it is the same word twice.
Kept in the document as .sr-only rather than deleted — the page still needs one
@@ -77,18 +73,14 @@ const ko = projects.some((p) => hasHangul(p.data.name, p.body));
Capped WITHOUT `margin-inline: auto`, and that is the load-bearing half. `main` caps
at --w-page (1024px) and centres itself; centring a narrower box inside it again
- would step this page's <h1> ~48px right of the <h1> on /papers/ and /talk/, which is
- the edge that was just measured to the pixel (330.5px at a 1700px window, identical
- on all four section indexes). With margin-inline at its 0 default the box keeps
- `main`'s left edge and gives the 96px back on the right — the mirror of /writing/,
- where that same 96px is the date rail on the left. */
+ would step this page's list ~48px right of `main`'s left edge, which is where the
+ header's page name and the text of /papers/ and /talk/ start. With margin-inline at
+ its 0 default the box keeps `main`'s left edge and gives the 96px back on the right —
+ the mirror of /writing/, where that same 96px is the date rail on the left. */
.code-index {
max-width: var(--w-content);
}
- /* --sp-48 is the block rhythm token, and the same clamp(2rem, …, 3rem) that /writing/
- spends between its masthead and its first year. */
-
.code-index__empty {
font-size: var(--fs-body);
}
@@ -103,56 +95,39 @@ const ko = projects.some((p) => hasHangul(p.data.name, p.body));
rule, no wash, no row-sized hit area. Copied as it stands rather than borrowing the
Writing index's row treatment, because that treatment is answering a different
question — a row on /writing/ is one whole link to a page of this site, so the row
- can be the control and the underline can wait for hover. A row here is a name that
- leaves the site, so the name is the control, it carries the underline base.css gives
- every link, and there is nothing else on the row to make clickable. Hairlines
- between seven entries would be furniture the reference does not have and this page
- does not need. */
+ can be the control. A row here is a name that leaves the site, so the name is the
+ control, and there is nothing else on the row to make clickable. Hairlines between
+ entries would be furniture the reference does not have and this page does not
+ need. */
.project + .project {
margin-top: var(--sp-32);
}
- /* h2 is the right element — this is a list of seven named things directly under the
- page's h1, and a screen reader should be able to walk it — but not the right size.
- --fs-h2 is 28.5px, which reads as seven section headings; --fs-h5 is what the
- Writing index already sets its entry titles at, and tokens.css says so in as many
- words: "h5 is really 'index entry title'". Family, weight and colour come from
- base.css's heading rule, and the link keeps the heading's 500 through
- `:is(h1…h6) a { font-weight: inherit }`. */
+ /* h2 is the right element — this is a list of named things directly under the page's
+ h1, and a screen reader should be able to walk it — but not the right size. --fs-h2
+ reaches 28.5px, which reads as a run of section headings; --fs-h5 is what the
+ Writing index sets its entry titles at. Family, weight and colour come from
+ base.css's heading rule, and the link keeps the heading's 500 through the global
+ `a { font-weight: inherit }`. */
.project__name {
font-size: var(--fs-h5);
line-height: var(--lh-medium);
text-wrap: balance;
}
- /* Crimson, no underline. base.css underlines every <a> because prose links have to be
- findable inside running text; here the link IS the entry's headline, so the colour is
- already the whole affordance and the rule under it only adds noise. --crimson measures
- 7.25:1 against --page-bg, over the 4.5:1 normal text needs, so the colour is not
- carrying meaning at a contrast that fails — and it lands within 0.23 of the 7.48:1 the
- reference sets its own outbound links at. The underline comes back on hover and on
- keyboard focus, where a second signal is cheap and colour alone would be the only cue
- that the pointer is over a target. */
- /* The rule that comes back on hover is the header nav's wipe, opening from the centre
- over 0.3s -- see .hover-wipe in base.css. The name already wraps, which is why it
- takes the per-fragment technique rather than an absolute bar: an absolute bar would
- draw one rectangle under the last line at the width of the widest. Measured: at 375
- the longest name fits by 6.45px (321.55px in a 328px column) and nothing wraps, but
- the column is 313px at 360 and 273px at 320, and USFX takes two lines from 360 down,
- UDFX from 320, OMMP from 280. Hovered at 320, USFX draws two rules of 144 and 171px,
- one per line, each opening from its own centre.
- The rule needs to sit one pixel lower than the padding-box bottom, not higher:
- measured at 1700, the auto underline's top row was 148.88 and the unpadded background's
- 147.88. Pushing down is `padding-block-end`, never --wipe-y -- the background is
- clipped to the border box and a position past 100% is simply not painted. This link is
- inline, so the padding costs no layout: A/B'd with it removed, .project__name,
- .project__about and .projects and the document height are identical to three decimals
- at 1700 and at 375, and the only number that moves is the link's own border box, 26 ->
- 25. 0.05em is that pixel at 20px and 0.9px at the 18px floor.
- The colour keeps stepping to --crimson-dark, and both halves move inside the pointer
- gate: a bare :hover latches after a tap on a touch screen and would leave the project
- marked until the next tap elsewhere. :focus-visible stays outside the gate -- it is
- the keyboard path, and it had no underline at all before the gate went in. */
+ /* Crimson, no underline: the colour-only treatment base.css gives <a>, and here
+ the link IS the entry's headline, so the colour is the whole affordance. --crimson
+ measures 7.25:1 against --page-bg, over the 4.5:1 normal text needs, so the colour is
+ not carrying meaning at a contrast that fails — and it lands within 0.23 of the
+ 7.48:1 the reference sets its own outbound links at.
+ Hover and keyboard focus step the colour to --crimson-lift. The hover half sits
+ inside the pointer gate: a bare :hover latches after a tap on a touch screen and would
+ leave the project marked until the next tap elsewhere. :focus-visible stays outside
+ the gate -- it is the keyboard path, and the global focus ring comes with it.
+ The 0.05em of padding-block-end draws nothing. The link is inline, so it costs no
+ layout; it only lowers the bottom of the link's box, and with it the focus ring and
+ the hit area, by 0.05em: a pixel at 20px, 0.9px at the 18px floor. Check the ring
+ before removing it. */
.project__name a { color: var(--crimson); text-decoration: none; padding-block-end: 0.05em; }
/* Hover is a colour step, not a rule. The reference draws its animated underline in one
place only - the header nav - and every link in the body of a page, its writing index,
@@ -163,11 +138,11 @@ const ko = projects.some((p) => hasHangul(p.data.name, p.body));
.project__name a:hover { color: var(--crimson-lift); }
}
- /* Forced colours drop background-image, so the wipe is not painted there at all and the
- colour step is overridden too -- see the note beside .hover-wipe in base.css. The
- underline this rule replaced is what that mode can still draw, so it comes back here
- and only here. Written in this file rather than in base.css because Astro scopes a
- page rule on every compound, which outranks the same selector written globally. */
+ /* Forced colours force `color` to the system link colour at rest and on hover alike, so
+ the colour step above does not show in that mode. An underline is what the mode can
+ still draw, so hover and focus get one here and only here. Written in this file
+ rather than in base.css because Astro scopes a page rule on every compound, which
+ outranks the same selector written globally. */
@media (forced-colors: active) {
.project__name a:focus-visible { text-decoration: underline; }
}
@@ -186,8 +161,8 @@ const ko = projects.some((p) => hasHangul(p.data.name, p.body));
keep-all is a no-op on the English descriptions here and is the house rule
everywhere Korean can appear (.prose, .about, .entry__title); `ko` above already
anticipates a Korean description, so this does too. break-word is the guard against
- a long unbroken token — the defect that made `/` scroll sideways on a phone when
- about.md printed a 40-character URL with no space in it. */
+ a long unbroken token such as a bare URL, which would otherwise set the page's
+ minimum width and scroll it sideways on a phone. */
.project__about {
margin-top: 0.375rem;
word-break: keep-all;
diff --git a/src/pages/index.astro b/src/pages/index.astro
@@ -8,12 +8,12 @@ const entry = await getEntry('about', 'about');
assertRendered(entry!);
const { Content } = await render(entry!);
-/* Ask the body, not the metadata. This used to matter because about.md was an English
- document with one Korean line in it — 14 Hangul syllables that no title or description
- would have revealed, against a 118KB Korean face. The rewritten about.md is English
- throughout, so the answer is false today and the page fetches no Korean font. The test
- stays because the answer is the body's to give: one Korean sentence added here starts
- needing that face again, and nothing else in this file would notice. */
+/* Ask the body, not the metadata. The title and description below are English whatever
+ about.md says, so only the body can tell whether the page needs the 118KB Korean face.
+ about.md is English throughout today, so the answer is false and the page fetches no
+ Korean font. The test stays because the answer is the body's to give: one Korean line
+ added to about.md starts needing that face, and nothing else in this file would
+ notice. */
const ko = hasHangul(entry!.body);
---
<Base
@@ -22,23 +22,23 @@ const ko = hasHangul(entry!.body);
ko={ko}
>
{/* about.md is running prose with no headings, the shape the reference's own home page
- uses, so nothing in the body is a top-level heading any more. The bar's left slot
- prints the name but it is a <span>, not a heading — so the page needs one here or it
- has no h1 at all, which was already true when the body opened with `# whoami`
- (satteriProse demotes it to h2). Visually hidden for the same reason as the other
- section pages: the reference prints no page title either. */}
+ uses. The bar's left slot prints the name but it is a <span>, not a heading — so the
+ page needs one here or it has no h1 at all. A `#` heading in the body would not
+ serve either: satteriProse demotes it to h2. Visually hidden for the same reason as
+ the other section pages: the reference prints no page title either. */}
<h1 class="sr-only">About</h1>
<article class="about"><Content /></article>
</Base>
-<!-- is:global for the same reason the article page uses it: the elements <Content />
- emits carry no scope id, so a scoped selector would miss every one of them. -->
+{/* is:global for the same reason the article page uses it: the elements <Content />
+ emits carry no scope id, so a scoped selector would miss every one of them. */}
<style is:global>
- /* about.md used to print three bare URLs, the longest 40 characters with no space
- in it. Left unbroken it set the page's minimum width and `/` scrolled sideways on
- every phone - 383px of content in a 320px viewport. The article page already
- carries this pair on .prose; the home page had no equivalent rule because it is
- the one page that renders <Content /> without that wrapper. */
+ /* The pair the article page carries on .prose, restated here because this is the one
+ page that renders <Content /> without that wrapper. keep-all is the house rule
+ wherever Korean can appear. The break rules at the end of this block
+ (`.about :is(p, li, h1, h2, h3, h4)` and `.about a`) are the escape for a long
+ unbroken token such as a bare URL: left unbroken it sets the page's minimum width,
+ and `/` scrolls sideways on a phone. */
.about { word-break: keep-all; }
/* The portrait, set the way the reference sets its own: floated to the inline end with
@@ -47,13 +47,13 @@ const ko = hasHangul(entry!.body);
than a class the author would have to remember to type in markdown.
Two things it buys beyond looking composed. It pulls the reading measure in from the
- full 1024px this page gets — 4.3 words a line narrower over the paragraphs it passes,
- which is the direction this page's line length wanted to move anyway. And the 4:5
- portrait keeps its own aspect from the width/height satteriProse stamps on the <img>,
- so nothing reflows when it loads.
+ full 1024px this page gets over the paragraphs it passes, which is the direction this
+ page's line length wanted to move anyway. And the 4:5 portrait keeps its own aspect
+ from the width/height satteriProse stamps on the <img>, so nothing reflows when it
+ loads.
- Below 40rem the float comes off: a 232px picture beside a 300px column leaves neither
- enough room, and the reference drops its own block to full width at a similar point. */
+ Below 40rem the float comes off: the picture and the column beside it would both be
+ too narrow, and the reference drops its own block to full width at a similar point. */
.about > .fig:first-child {
float: inline-end;
inline-size: min(14.5rem, 34%);
@@ -62,8 +62,8 @@ const ko = hasHangul(entry!.body);
}
.about > .fig:first-child img { inline-size: 100%; block-size: auto; }
- /* Clears the float so the footer's rule and anything after the prose cannot ride up
- beside the picture on a short viewport. */
+ /* Clears the float so the footer, and anything else after the prose, cannot ride up
+ beside the picture when the prose beside it ends first. */
.about::after { content: ""; display: block; clear: both; }
@media (max-width: 40rem) {
diff --git a/src/pages/index.xml.ts b/src/pages/index.xml.ts
@@ -5,14 +5,13 @@ import { byHugoOrder, postUrl } from '../lib/order';
import { excerpt } from '../lib/prose';
/**
- * The feed, at the one address the site has always claimed.
+ * The feed, at the one address the site has always claimed, and the one every page's
+ * <head> advertises: Base.astro prints `<link rel="alternate" type="application/rss+xml"
+ * title="sqzer" href="/index.xml">` on every page, so this file is what
+ * answers a reader who subscribes from any of them.
*
- * Every page's <head> has carried `<link rel="alternate" type="application/rss+xml"
- * href="/index.xml">` since the migration, and nothing answered it — a reader who
- * subscribed got a 404 from a link the site itself printed. This is that file.
- *
- * One feed, not one per section: the section feeds the Hugo site shipped were dropped
- * deliberately, and a blog that publishes into a single stream has nothing to split.
+ * One feed, not the per-section feeds the Hugo site shipped: a blog that publishes into
+ * a single stream has nothing to split.
*
* Ordering and addresses come from src/lib/order.ts rather than being restated here, so
* the feed cannot drift from the Writing index. Descriptions reuse the same excerpt()
@@ -42,7 +41,7 @@ export async function GET(context: APIContext) {
items: posts.map((p) => ({
title: p.data.title,
// Dates are stored YYYY-MM-DD and read as UTC everywhere on this site; parsing them
- // in local time moves half the corpus back a day.
+ // in local time would move every pubDate back a day on a build machine east of UTC.
pubDate: new Date(`${p.data.date}T00:00:00Z`),
description: asHtmlText(p.data.deck ?? excerpt(p.body ?? '')),
link: postUrl(p),
diff --git a/src/pages/writing/index.astro b/src/pages/writing/index.astro
@@ -16,15 +16,15 @@ const md = (iso: string) => `${iso.slice(5, 7)}.${iso.slice(8, 10)}`;
const span = years.length ? `${years[years.length - 1]}–${years[0]}` : '';
-/* This page is an English document - heading, year kickers, "30 posts" - that sets 21
- Korean titles, six of them above the fold. Deciding the font preload from the document
+/* This page is an English document - heading, year kickers - that sets Korean titles,
+ several of them above the fold. Deciding the font preload from the document
language would skip the Korean face on the site's densest page and leave those titles
in fallback until a third-wave request lands. Ask the strings that will be painted. */
const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
---
<Base
title="Writing :: sqzer"
- description="Essays and technical notes, 2024–2026."
+ description={span ? `Essays and technical notes, ${span}.` : 'Essays and technical notes.'}
ko={ko}
>
<div class="index">
@@ -61,8 +61,15 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
<style>
/* ────────────────────────────────────────────────────────────────
Page-local tokens.
- Defers to the global token set once Base provides it; until then these stand alone
- using the clamp literals measured from the Gazette.
+ Most are var(hook, literal): the literal is the stand-alone value measured from the
+ Gazette, and the hook is where the global token set can override it. Only some hooks
+ are set. tokens.css sets --w-content, --wp-h1, --wp-h5 and --wp-large, and fonts.css
+ sets --font-sans and --font-serif. Nothing defines --wp-small, --wp-medium,
+ --wp-kicker or the --desk-* and --color-* hooks, so those resolve to the literals
+ here. The colour literals equal tokens.css's --crimson-dark, --crimson,
+ --crimson-wash, --black, --slate, --grey and --sand-light today, but a change there
+ does not reach these page-local tokens. (The row hover and the focused title read
+ --crimson directly, so that one does reach this page.)
──────────────────────────────────────────────────────────────── */
.index {
/* Grid */
@@ -82,14 +89,14 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
the same var(global, measured-literal) shape as the type scale below.
One exception to the shared edge, and it is not this page's doing nor fixable from
- here: the nine articles that carry a contents rail push their own page box right
- between 1440 and 1584px, where a centred gutter cannot seat the rail's 240px floor.
- This page has no rail and nothing to make room for, so it deliberately does not
- follow - shifting it in sympathy would buy an empty 280px left gutter at 1440 for a
- page with nothing to put in it. Measured delta, article title minus entry title, at
- a 16px root: 72px at 1440, 48 at 1488, 24 at 1536, and 0.00px from 1584 up, at 1439
- and below, and at every width for the 21 articles with no rail. Those are
- layout-viewport widths, the same basis the Geometry note uses; a classic 15px
+ here: every article pushes its own page box right between 1440 and 1584px, where a
+ centred gutter cannot seat the contents rail's 240px floor. The article reserves
+ that gutter whether or not it paints a rail in it. This page has no rail and
+ nothing to make room for, so it deliberately does not follow - shifting it in
+ sympathy would buy an empty 280px left gutter at 1440 for a page with nothing to
+ put in it. Measured delta, article title minus entry title, at a 16px root: 72px at
+ 1440, 48 at 1488, 24 at 1536, and 0.00px from 1584 up and at 1439 and below. Those
+ are layout-viewport widths, the same basis the Geometry note uses; a classic 15px
scrollbar puts the band at 1440-1598 in window widths and the worst case at 79.5px,
measured at a 1440px window. See the ══ Geometry ══ note in
src/pages/writing/[year]/[slug].astro for why it was traded. */
@@ -97,12 +104,13 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
--rail-w: 4.5rem; /* 72px date rail */
--rail-gap: 1.5rem; /* 24px */
- /* Type scale, as measured */
+ /* Type scale. The literals are as measured. tokens.css sets three of the hooks, which
+ replace their literals; the trailing notes on those three give both values. */
--fs-small: var(--wp-small, clamp(0.6875rem, 0.688rem + ((1vw - 0.2rem) * 0.256), 0.875rem)); /* 11→14 */
--fs-medium: var(--wp-medium, clamp(0.75rem, 0.75rem + ((1vw - 0.2rem) * 0.342), 1rem)); /* 12→16 */
- --fs-large: var(--wp-large, clamp(1rem, 1rem + ((1vw - 0.2rem) * 0.342), 1.25rem)); /* 16→20 */
- --fs-h5: var(--wp-h5, clamp(1.25rem, 1.25rem + ((1vw - 0.2rem) * 0.342), 1.5rem)); /* 20→24 */
- --fs-h1: var(--wp-h1, clamp(2.125rem, 2.125rem + ((1vw - 0.2rem) * 2.568), 4rem)); /* 34→64 */
+ --fs-large: var(--wp-large, clamp(1rem, 1rem + ((1vw - 0.2rem) * 0.342), 1.25rem)); /* literal 16→20; --wp-large 16→19 */
+ --fs-h5: var(--wp-h5, clamp(1.25rem, 1.25rem + ((1vw - 0.2rem) * 0.342), 1.5rem)); /* literal 20→24; --wp-h5 18→20 */
+ --fs-h1: var(--wp-h1, clamp(2.125rem, 2.125rem + ((1vw - 0.2rem) * 2.568), 4rem)); /* literal 34→64; --wp-h1 27→38 */
--fs-kicker: var(--wp-kicker, clamp(0.63rem, 0.36vw + 0.54rem, 0.88rem)); /* 10.08→14.08 */
--lh-tight: 1.1;
@@ -124,7 +132,9 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
--font-body: var(--font-sans, "Inter", "Pretendard", system-ui, sans-serif);
--font-display: var(--font-serif, "Newsreader", "Pretendard", Georgia, serif);
- /* Swapping this one line to var(--font-body) flips all 30 titles to sans. */
+ /* The one switch for the entry titles' face: var(--font-display) or var(--font-body).
+ fonts.css maps --font-serif and --font-sans to the same Lora stack today, so the
+ choice only shows if the two ever differ. */
--index-title-font: var(--font-display);
container: index / inline-size;
@@ -135,19 +145,12 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
}
/* Masthead.
- Flush with the page box, NOT indented onto the entry column. It used to carry the
- same `padding-left: --rail-w + --rail-gap` the year kickers and the entries carry,
- which put "Writing" 96px in from the left edge — so this was the one section index
- whose title did not start where /papers/, /talk/ and /code/ start theirs. Reading
- the four in a row, /writing/ was visibly the odd one, and the h1 is the last thing
- that should be telling you which page you are on by moving.
-
- The 96px indent still belongs to everything below it: the year kicker and the entry
- titles hang off the 72px date rail, and that column is the page's spine. The
- masthead is not in that column — it is the page's own name, addressed to the shell,
- not a row in the list — so it takes the shell's edge. Measured after the change at a
- 1700px window: 330.5px for the h1 on all four section indexes, against 426.5 here
- before it. */
+ The h1 is .sr-only (see the note beside it in the markup), so none of this paints.
+ It is flush with the page box, NOT indented onto the entry column. The 96px indent
+ belongs to what hangs off the 72px date rail: the year kickers and the entry titles,
+ the column that is the page's spine. The masthead is not in that column — it is the
+ page's own name, addressed to the shell, not a row in the list — so it takes the
+ shell's edge, the edge /papers/, /talk/ and /code/ start from. */
.index__title {
margin: 0;
@@ -187,7 +190,7 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
color: var(--ppc-text);
}
- /* Signature motif, used once per year heading — three times on this page. */
+ /* Signature motif, used once per year heading. */
.year__label::before {
content: "";
display: block;
@@ -242,12 +245,12 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
text-wrap: balance;
}
- /* Focus moving backwards (Shift+Tab) scrolls the link only just into view, and the sticky
- year label above covered it and the row's focus ring completely. The margin is the
- label's own height (padding, rule, rule's gap, one line) plus the row's padding, plus
- 1rem for the ring (3px offset, 2px outline) and for the link's inline box sitting a
- little inside its taller line; with only the ring's 5px, the ring's top still touched
- the label. Below 36rem the date sits above the title in the row, and the container
+ /* Focus moving backwards (Shift+Tab) scrolls the link only just into view, where the
+ sticky year label above would cover it and the row's focus ring completely. The margin
+ is the label's own height (padding, rule, rule's gap, one line) plus the row's padding,
+ plus 1rem for the ring (3px offset, 2px outline) and for the link's inline box sitting
+ a little inside its taller line; with only the ring's 5px, the ring's top would still
+ touch the label. Below 36rem the date sits above the title in the row, and the container
rule adds its line. */
.entry__title a {
color: inherit;
@@ -264,54 +267,30 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
inset: 0;
}
- /* The row is still the trigger, and the hit area above is untouched: the rule is drawn
- by .hover-wipe (base.css) on the link's own background, not by a second pseudo-element,
- because ::before is already spent on the full-row hit box.
-
- This is the site that decided the technique. Korean titles wrap here and nowhere on
- this page is the column wide enough to stop them: 5 entries wrap at 320, 2 at 375, 1
- at 430, none from 768 up. An absolutely positioned bar -- the header's own method --
- would draw one rectangle under the last line at the width of the widest, i.e. a bottom
- border on the title block. `box-decoration-break: clone` gives each line fragment its
- own rule, each opening from its own centre: the worst case at 375 is 235.44 x 48.33
- over 2 fragments of 186.45 and 235.44, and it gets two rules of those widths.
-
- `clone` is documented to disable `text-wrap: balance`, which these titles use, so it
- was A/B'd rather than assumed: every entry's box, fragment count, fragment widths and
- the document height are identical with `clone` and with `slice` at 320, 375, 430 and
- 768. The balancer only diverges in columns narrower than this page ever gets.
-
- No --wipe-y. The background's 100% is the padding-box bottom, which lands 3px under a
- 20px baseline -- the same row `text-underline-offset: 0.14em` was putting it on, so
- the rule does not move at all here, it only acquires the motion.
-
- What does change is colour: from-font black becomes --crimson, which is what "like the
- header" means and is the first accent on this index. A 2px rule is a non-text element
- needing 3:1; crimson measures 7.25:1 on --page-bg. */
+ /* The row is the trigger, through the full-row hit box on `.entry__title a::before`:
+ hovering anywhere on it washes the row and turns the title from --ink to --crimson,
+ which measures 6.42:1 on the wash.
+ Hover is a colour step, not a rule. The reference draws its animated underline in one
+ place only - the header nav - and every link in the body of a page, its writing index,
+ its code list and even its year rail, simply lifts from rgb(160,32,32) to
+ rgb(192,48,48) with no decoration at all. Measured over CDP on all three. */
@media (hover: hover) {
- /* The row wash used to sit outside this gate while the rule under the title sat inside
- it. On a touch screen :hover latches after a tap, so a tapped row kept its wash until
- the next tap elsewhere while the title's rule did not — one half of the row's hover
- state stuck to it and the other half did not. Both halves are the same gesture and
- now share the same gate. */
+ /* The wash and the title's colour are one gesture, so they share one gate. On a touch
+ screen :hover latches after a tap, and either half left outside the gate would stay
+ on a tapped row until the next tap elsewhere while the other half did not. */
.entry:hover { background-color: var(--ppc-wash); }
.entry:hover .entry__title a { color: var(--crimson); }
}
- /* Keyboard parity, which this row never had: the outline below marks the row, and this
- marks the title the same way the pointer does. Outside the gate on purpose.
- The row hit area is re-measured after the change and unmoved: 1024 x 61, with all
- four corners and the centre hit-testing to the link through the ::before above. */
- /* Hover is a colour step, not a rule. The reference draws its animated underline in one
- place only - the header nav - and every link in the body of a page, its writing index,
- its code list and even its year rail, simply lifts from rgb(160,32,32) to
- rgb(192,48,48) with no decoration at all. Measured over CDP on all three. */
+ /* Keyboard parity: the outline on `.entry__title a:focus-visible::before` marks the
+ row, and this marks the title the same way the pointer does. Outside the gate on
+ purpose: it is the keyboard path. */
.entry__title a:focus-visible { color: var(--crimson); }
- /* Forced colours drop background-image, so the wipe is not painted there at all and the
- colour step is overridden too -- see the note beside .hover-wipe in base.css. The
- underline this rule replaced is what that mode can still draw, so it comes back here
- and only here. Written in this file rather than in base.css because Astro scopes a
- page rule on every compound, which outranks the same selector written globally. */
+ /* Forced colours force `color` to the system colours at rest and on hover alike, so
+ the colour step above does not show in that mode. An underline is what the mode can
+ still draw, so hover and focus get one here and only here. Written in this file
+ rather than in base.css because Astro scopes a page rule on every compound, which
+ outranks the same selector written globally. */
@media (forced-colors: active) {
.entry__title a:focus-visible { text-decoration: underline; text-underline-offset: 0.14em; }
}
@@ -338,7 +317,7 @@ const ko = posts.some((p) => hasHangul(p.data.title, p.data.deck));
}
/* Folds when the rail no longer fits.
- The page title is .sr-only and its wrapper is gone, so there is nothing here to un-indent. */
+ The page title is .sr-only and carries no indent, so it has nothing to un-indent here. */
@container index (max-width: 36rem) {
.index__empty,
.year__label { padding-left: 0; }
|