Compare commits

...

2 Commits

Author SHA1 Message Date
Levi Neuwirth 6286c82389 more prominent related pages 2026-05-03 14:01:14 -04:00
Levi Neuwirth 22b530c26d auto: 2026-05-03T16:57:50Z [skip ci] 2026-05-03 12:57:50 -04:00
11 changed files with 56 additions and 30 deletions

View File

@ -94,6 +94,7 @@ applyAuthorRules authors baseCtx = tagsRules authors $ \name pat -> do
<> paginateContext paginate pageNum <> paginateContext paginate pageNum
<> constField "author" name <> constField "author" name
<> constField "title" name <> constField "title" name
<> constField "portal" "true"
<> baseCtx <> baseCtx
makeItem "" makeItem ""
>>= loadAndApplyTemplate "templates/author-index.html" ctx >>= loadAndApplyTemplate "templates/author-index.html" ctx

View File

@ -409,6 +409,7 @@ photographyMapPageRule =
let ctx = constField "title" "Map · Photography" let ctx = constField "title" "Map · Photography"
<> constField "photography" "true" <> constField "photography" "true"
<> constField "photography-map" "true" <> constField "photography-map" "true"
<> constField "portal" "true"
<> siteCtx <> siteCtx
makeItem "" makeItem ""
>>= loadAndApplyTemplate "templates/photography-map.html" ctx >>= loadAndApplyTemplate "templates/photography-map.html" ctx
@ -589,6 +590,7 @@ photographyContactSheetRule =
listField "photos" photographyCtx (return photos) listField "photos" photographyCtx (return photos)
<> constField "title" "Contact sheet · Photography" <> constField "title" "Contact sheet · Photography"
<> constField "photography" "true" <> constField "photography" "true"
<> constField "portal" "true"
<> siteCtx <> siteCtx
makeItem "" makeItem ""
>>= loadAndApplyTemplate >>= loadAndApplyTemplate

View File

@ -542,7 +542,8 @@ rules = do
essays <- recentFirst =<< loadAll (allEssays .&&. hasNoVersion) essays <- recentFirst =<< loadAll (allEssays .&&. hasNoVersion)
let ctx = let ctx =
listField "essays" essayCtx (return essays) listField "essays" essayCtx (return essays)
<> constField "title" "Essays" <> constField "title" "Essays"
<> constField "portal" "true"
<> siteCtx <> siteCtx
makeItem "" makeItem ""
>>= loadAndApplyTemplate "templates/essay-index.html" ctx >>= loadAndApplyTemplate "templates/essay-index.html" ctx
@ -712,6 +713,7 @@ rules = do
<> libraryIntroFld <> libraryIntroFld
<> constField "title" "Library" <> constField "title" "Library"
<> constField "library" "true" <> constField "library" "true"
<> constField "portal" "true"
<> siteCtx <> siteCtx
makeItem "" makeItem ""
@ -788,6 +790,7 @@ rules = do
<> constField "bibliography-index" "true" <> constField "bibliography-index" "true"
<> constField "bibliography-entries" (T.unpack html) <> constField "bibliography-entries" (T.unpack html)
<> constField "library" "true" -- reuse flag to load library.css + item-card.css <> constField "library" "true" -- reuse flag to load library.css + item-card.css
<> constField "portal" "true"
<> siteCtx <> siteCtx
makeItem "" makeItem ""
>>= loadAndApplyTemplate "templates/bibliography-index.html" ctx >>= loadAndApplyTemplate "templates/bibliography-index.html" ctx

View File

@ -1,6 +1,7 @@
--- ---
title: Projects title: Projects
tags: meta tags: meta
portal: true
--- ---
Index of engineering artifacts. Systems depth is the primary axis of this page; machine-learning and deployed artifacts follow. Index of engineering artifacts. Systems depth is the primary axis of this page; machine-learning and deployed artifacts follow.

View File

@ -22,6 +22,7 @@ beside each link are stamped automatically by the build's link classifier
## <span class="links-section-ornament" data-ornament="artistic" aria-hidden="true"></span>Artistic { #artistic } ## <span class="links-section-ornament" data-ornament="artistic" aria-hidden="true"></span>Artistic { #artistic }
- [Rate Your Music](https://rateyourmusic.com/~neuwirth)
- [YouTube](https://www.youtube.com/@levineuwirth) - [YouTube](https://www.youtube.com/@levineuwirth)
</section> </section>

View File

@ -1114,36 +1114,46 @@ nav.site-nav {
} }
/* ============================================================ /* ============================================================
RELATED (SIMILAR LINKS) compact vertical list of up to 3 titles. RELATED (SIMILAR LINKS) promoted to a full-width aftermatter
Source file: data/similar-links.json (produced by tools/embed.py). section, sitting just under Backlinks. Source file:
data/similar-links.json (produced by tools/embed.py).
============================================================ */ ============================================================ */
#similar-links .similar-links-list { .meta-footer-similar > h3 {
font-size: 1.05rem;
letter-spacing: 0.09em;
margin-bottom: 1rem;
}
.meta-footer-similar .similar-links-list {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.35rem; gap: 0.55rem;
} }
#similar-links .similar-links-list li::before { .meta-footer-similar .similar-links-list li::before {
content: none; content: none;
} }
#similar-links .similar-links-item a { .meta-footer-similar .similar-links-item a {
font-family: var(--font-serif); font-family: var(--font-serif);
font-size: 0.92rem; font-size: 1.18rem;
color: var(--text-muted); font-weight: 500;
color: var(--text);
text-decoration: none; text-decoration: none;
border-bottom: 1px solid transparent; border-bottom: 1px solid var(--border-muted);
padding-bottom: 0.05em;
transition: color var(--transition-fast), transition: color var(--transition-fast),
border-color var(--transition-fast); border-color var(--transition-fast);
} }
#similar-links .similar-links-item a:hover { .meta-footer-similar .similar-links-item a:hover,
.meta-footer-similar .similar-links-item a:focus-visible {
color: var(--text); color: var(--text);
border-bottom-color: var(--border-muted); border-bottom-color: var(--border);
} }
/* ============================================================ /* ============================================================

View File

@ -11,22 +11,18 @@
$backlinks$ $backlinks$
</div> </div>
$if(similar-links)$ $if(similar-links)$
<div class="meta-footer-grid"> <div class="meta-footer-full meta-footer-similar" id="similar-links">
<div class="meta-footer-section" id="similar-links"> <h3>Related</h3>
<h3>Related</h3> $similar-links$
$similar-links$
</div>
</div> </div>
$endif$ $endif$
</footer> </footer>
$else$ $else$
$if(similar-links)$ $if(similar-links)$
<footer class="page-meta-footer"> <footer class="page-meta-footer">
<div class="meta-footer-grid"> <div class="meta-footer-full meta-footer-similar" id="similar-links">
<div class="meta-footer-section" id="similar-links"> <h3>Related</h3>
<h3>Related</h3> $similar-links$
$similar-links$
</div>
</div> </div>
</footer> </footer>
$endif$ $endif$

View File

@ -3,7 +3,7 @@
<head> <head>
$partial("templates/partials/head.html")$ $partial("templates/partials/head.html")$
</head> </head>
<body$if(reading)$ class="reading-mode$if(poetry)$ poetry$endif$$if(fiction)$ fiction$endif$"$endif$$if(photography)$ data-page-type="photography"$endif$$if(dingbat)$ data-dingbat="$dingbat$"$endif$> <body$if(reading)$ class="reading-mode$if(poetry)$ poetry$endif$$if(fiction)$ fiction$endif$"$endif$$if(photography)$ data-page-type="photography"$endif$$if(dingbat)$ data-dingbat="$dingbat$"$endif$$if(list-page)$ data-portal$endif$$if(portal)$ data-portal$endif$$if(home)$ data-portal$endif$>
<a class="skip-link" href="#markdownBody">Skip to content</a> <a class="skip-link" href="#markdownBody">Skip to content</a>
$partial("templates/partials/nav.html")$ $partial("templates/partials/nav.html")$
$if(search)$ $if(search)$

View File

@ -46,6 +46,7 @@
$if(status)$<a href="#epistemic">Epistemic</a>$endif$ $if(status)$<a href="#epistemic">Epistemic</a>$endif$
$if(bibliography)$<a href="#bibliography">Bibliography</a>$endif$ $if(bibliography)$<a href="#bibliography">Bibliography</a>$endif$
$if(backlinks)$<a href="#backlinks">Backlinks</a>$endif$ $if(backlinks)$<a href="#backlinks">Backlinks</a>$endif$
$if(similar-links)$<a href="#similar-links">Related</a>$endif$
$if(repository)$<a href="$repository$">Repository</a>$endif$ $if(repository)$<a href="$repository$">Repository</a>$endif$
$if(preprint)$<a href="$preprint$">Preprint</a>$endif$ $if(preprint)$<a href="$preprint$">Preprint</a>$endif$
</nav> </nav>

View File

@ -64,14 +64,14 @@
</div> </div>
$endif$ $endif$
<div class="meta-footer-grid"> $if(similar-links)$
<div class="meta-footer-full meta-footer-similar" id="similar-links">
<h3>Related</h3>
$similar-links$
</div>
$endif$
$if(similar-links)$ <div class="meta-footer-grid">
<div class="meta-footer-section" id="similar-links">
<h3>Related</h3>
$similar-links$
</div>
$endif$
$if(status)$ $if(status)$
<div class="meta-footer-section meta-footer-epistemic" id="epistemic"> <div class="meta-footer-section meta-footer-epistemic" id="epistemic">

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -46,6 +46,14 @@ MAX_PARA_CHARS = 1000 # semantic: truncate before embedding
EXCLUDE_URLS = {"/search/", "/build/", "/404.html", "/feed.xml", "/music/feed.xml"} EXCLUDE_URLS = {"/search/", "/build/", "/404.html", "/feed.xml", "/music/feed.xml"}
# Pages whose <body data-portal> are portal/landing pages — they aggregate
# excerpts from many entries and would otherwise dominate every page's
# "Related" set with high but uninformative scores. default.html sets the
# attribute when any of the `list-page`, `portal`, or `home` template flags
# is true, so adding `constField "portal" "true"` to a Hakyll rule (or
# `portal: true` to a content file's frontmatter) is enough to exclude it.
PORTAL_BODY_ATTR = "data-portal"
def atomic_write_bytes(path: Path, data: bytes) -> None: def atomic_write_bytes(path: Path, data: bytes) -> None:
"""Write to path.tmp then os.replace, so an interrupt mid-write """Write to path.tmp then os.replace, so an interrupt mid-write
@ -112,6 +120,9 @@ def extract_page(html_path: Path) -> dict | None:
if url in EXCLUDE_URLS: if url in EXCLUDE_URLS:
return None return None
body_tag = soup.body
if body_tag is not None and body_tag.has_attr(PORTAL_BODY_ATTR):
return None
body = soup.select_one("#markdownBody") body = soup.select_one("#markdownBody")
if body is None: if body is None:
return None return None