Compare commits

..

3 Commits

Author SHA1 Message Date
Levi Neuwirth 42ba2bf972 Current rework 2026-04-26 19:42:47 -04:00
Levi Neuwirth 53e053e9a7 auto: 2026-04-26T23:24:06Z [skip ci] 2026-04-26 19:24:06 -04:00
Levi Neuwirth 5cb6795a7a auto: 2026-04-26T20:47:12Z [skip ci] 2026-04-26 16:47:12 -04:00
13 changed files with 658 additions and 26 deletions

25
.gitignore vendored
View File

@ -3,10 +3,28 @@ _site/
_cache/
.DS_Store
.env
# Defense-in-depth: catch any stray .env / .env.* anywhere in the tree
# (the auto-snapshot in the Makefile stages content/ on every build).
# Defense-in-depth: the auto-snapshot in `make build` stages content/
# wholesale. These patterns prevent any stray credential-shaped file
# (dropped accidentally during writing) from being staged + pushed.
# To intentionally commit one of these (rare), use `git add -f`.
**/.env
**/.env.*
**/*.env
**/*.key
**/*.pem
**/*.p12
**/*.pfx
**/id_rsa*
**/id_dsa*
**/id_ecdsa*
**/id_ed25519*
**/.netrc
**/.npmrc
**/.pypirc
**/credentials
**/credentials.json
**/credentials.yaml
**/credentials.yml
# Editor backup/swap files
*~
@ -58,6 +76,9 @@ data/semantic-meta.json
# IGNORE.txt is for the local build and need not be synced.
IGNORE.txt
# Working notes / planning docs at the repo root (not site content).
checklist.md
# CV/résumé build pipeline (YAML → Jinja → xelatex). The canonical PDFs
# live under static/ and ship with the site; the pipeline itself is
# kept locally for regeneration but not version-controlled here.

View File

@ -16,17 +16,12 @@ build:
# either reuses it (no new content/ changes) or appends another
# snapshot on top, so failures don't disappear from the log.
#
# Pathspec is explicit (not `git add content/`) so a stray .env,
# credential file, or other non-content artifact dropped under
# content/ is NOT auto-staged. The :(glob) magic prefix makes `**`
# match across path components (git default fnmatch does not).
# Add new extensions here if a new asset type is introduced.
@git add ':(glob)content/**/*.md' ':(glob)content/**/*.html' ':(glob)content/**/*.bib' \
':(glob)content/**/*.png' ':(glob)content/**/*.jpg' ':(glob)content/**/*.jpeg' \
':(glob)content/**/*.svg' ':(glob)content/**/*.gif' ':(glob)content/**/*.pdf' \
':(glob)content/**/*.mp3' ':(glob)content/**/*.ogg' ':(glob)content/**/*.flac' \
':(glob)content/**/*.yaml' ':(glob)content/**/*.yml' ':(glob)content/**/*.json' \
':(glob)content/**/*.css' ':(glob)content/**/*.tex'
# `git add content/` respects .gitignore, which excludes credential-
# shaped patterns (.env, *.key, *.pem, id_rsa*, credentials*, etc.)
# so a stray secret dropped under content/ is NOT auto-staged. To
# intentionally commit a normally-ignored file, use `git add -f`
# manually before running `make build`.
@git add content/
@git diff --cached --quiet || git commit -m "auto: $$(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]"
@mkdir -p data
@date +%s > data/build-start.txt

281
build/Now.hs Normal file
View File

@ -0,0 +1,281 @@
{-# LANGUAGE GHC2021 #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Now page: loads data/now.yaml and renders the active-projects view
-- and the recently-shipped archive for /current.html. Page-level
-- "Last updated" stamp is exposed as a context field; relative time
-- ("4 days ago") is computed at build time from getCurrentTime.
module Now
( nowCtx
) where
import Data.Aeson (FromJSON (..), withObject, (.:), (.:?), (.!=))
import Data.Char (toUpper)
import Data.List (nub, sortBy)
import Data.Maybe (fromMaybe)
import Data.Ord (Down (..), comparing)
import Data.Time.Calendar (Day, diffDays)
import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Data.Time.Format (defaultTimeLocale, formatTime, parseTimeM)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Yaml as Y
import Hakyll hiding (escapeHtml)
import Contexts (siteCtx)
import Utils (escapeHtml)
-- ---------------------------------------------------------------------------
-- Entry types
-- ---------------------------------------------------------------------------
data NowEntry = NowEntry
{ neTitle :: String
, neSection :: String
, neStatus :: String
, neUpdated :: String
, neLink :: Maybe String
, neNote :: Maybe String
, nePriority :: Int
}
instance FromJSON NowEntry where
parseJSON = withObject "NowEntry" $ \o -> NowEntry
<$> o .: "title"
<*> o .: "section"
<*> o .: "status"
<*> o .: "updated"
<*> o .:? "link"
<*> o .:? "note"
<*> o .:? "priority" .!= 0
data NowShipped = NowShipped
{ nsTitle :: String
, nsCompleted :: String
, nsLink :: Maybe String
, nsNote :: Maybe String
}
instance FromJSON NowShipped where
parseJSON = withObject "NowShipped" $ \o -> NowShipped
<$> o .: "title"
<*> o .: "completed"
<*> o .:? "link"
<*> o .:? "note"
data NowDoc = NowDoc
{ nLastUpdated :: String
, nEntries :: [NowEntry]
, nShipped :: [NowShipped]
}
instance FromJSON NowDoc where
parseJSON = withObject "NowDoc" $ \o -> NowDoc
<$> o .: "last-updated"
<*> o .:? "entries" .!= []
<*> o .:? "shipped" .!= []
-- ---------------------------------------------------------------------------
-- Helpers
-- ---------------------------------------------------------------------------
-- | Section ordering follows first-appearance in entries. Reorder the
-- YAML to reorder the page; no separate ordering key required.
sectionOrder :: [NowEntry] -> [String]
sectionOrder = nub . map neSection
-- | Status ordering — "how close to shipping." Lower rank sorts first.
-- Statuses not listed sort below all known ones (rank 99) so a typo
-- surfaces visibly at the bottom of its section instead of silently
-- ranking next-to-the-top.
statusRanks :: [(String, Int)]
statusRanks =
[ ("in-review", 1)
, ("revising", 2)
, ("drafting", 3)
, ("building", 4)
, ("early-stage", 5)
, ("paused", 6)
]
statusRank :: String -> Int
statusRank s = fromMaybe 99 (lookup s statusRanks)
-- | Three-tier sort key for active entries:
-- 1. priority — manual override; higher floats up (default 0)
-- 2. statusRank — how close to shipping (lower is closer)
-- 3. updated — recency tiebreaker within the same rank
-- Sectioning is applied to the *unsorted* list so section ordering
-- continues to follow YAML source order; sorting happens within each
-- section's filtered slice.
entrySortKey :: NowEntry -> (Down Int, Int, Down String)
entrySortKey e =
( Down (nePriority e)
, statusRank (neStatus e)
, Down (neUpdated e)
)
-- | "early-stage" → "Early Stage", "research" → "Research".
titleCaseWords :: String -> String
titleCaseWords = unwords . map cap . wordsOnDash
where
cap [] = []
cap (x:xs) = toUpper x : xs
wordsOnDash s = case break (== '-') s of
(a, []) -> [a]
(a, _:rest) -> a : wordsOnDash rest
-- ---------------------------------------------------------------------------
-- HTML rendering
-- ---------------------------------------------------------------------------
renderStatusChip :: String -> String
renderStatusChip s = concat
[ "<span class=\"now-status now-status--", escapeHtml s, "\">"
, escapeHtml (titleCaseWords s)
, "</span>"
]
-- | Active-entry card. Reuses the .item-card / .item-card-* classes from
-- item-card.css so the Now page picks up the existing typographic
-- register; the .now-* classes layer status-chip + spacing on top.
renderEntry :: NowEntry -> String
renderEntry e = concat
[ "<li class=\"item-card now-card\">"
, "<span class=\"item-card-kind now-kind\">"
, renderStatusChip (neStatus e)
, "</span>"
, "<div class=\"item-card-main\">"
, "<div class=\"item-card-header\">"
, renderTitle (neLink e) (neTitle e)
, "<time class=\"item-card-date\" datetime=\"", escapeHtml (neUpdated e), "\">"
, escapeHtml (neUpdated e)
, "</time>"
, "</div>"
, maybe "" (\n -> "<p class=\"item-card-abstract is-full\">" ++ escapeHtml n ++ "</p>") (neNote e)
, "</div>"
, "</li>"
]
renderShippedEntry :: NowShipped -> String
renderShippedEntry s = concat
[ "<li class=\"item-card now-card now-card--shipped\">"
, "<span class=\"item-card-kind now-kind\">"
, renderStatusChip "shipped"
, "</span>"
, "<div class=\"item-card-main\">"
, "<div class=\"item-card-header\">"
, renderTitle (nsLink s) (nsTitle s)
, "<time class=\"item-card-date\" datetime=\"", escapeHtml (nsCompleted s), "\">"
, escapeHtml (nsCompleted s)
, "</time>"
, "</div>"
, maybe "" (\n -> "<p class=\"item-card-abstract is-full\">" ++ escapeHtml n ++ "</p>") (nsNote s)
, "</div>"
, "</li>"
]
renderTitle :: Maybe String -> String -> String
renderTitle mu title = case mu of
Just url -> "<a class=\"item-card-title\" href=\"" ++ escapeHtml url ++ "\">" ++ escapeHtml title ++ "</a>"
Nothing -> "<span class=\"item-card-title\">" ++ escapeHtml title ++ "</span>"
renderSection :: String -> [NowEntry] -> String
renderSection sec es = concat
[ "<section class=\"now-section library-section\">"
, "<h2 class=\"now-section-heading\">"
, escapeHtml (titleCaseWords sec)
, "</h2>"
, "<ul class=\"item-card-list\">"
, concatMap renderEntry es
, "</ul>"
, "</section>"
]
renderEntries :: [NowEntry] -> String
renderEntries [] = ""
renderEntries entries = concatMap renderOne (sectionOrder entries)
where
renderOne sec =
let inSec = filter ((== sec) . neSection) entries
sorted = sortBy (comparing entrySortKey) inSec
in renderSection sec sorted
renderShippedAll :: [NowShipped] -> String
renderShippedAll [] = ""
renderShippedAll items = concat
[ "<section class=\"now-section now-section--shipped library-section\">"
, "<h2 class=\"now-section-heading\">Recently Shipped</h2>"
, "<ul class=\"item-card-list\">"
, concatMap renderShippedEntry sorted
, "</ul>"
, "</section>"
]
where
sorted = sortBy (comparing (Down . nsCompleted)) items
-- ---------------------------------------------------------------------------
-- Date formatters — runs at build time
-- ---------------------------------------------------------------------------
-- | "2026-04-26" → "26 April 2026". Falls back to the raw ISO string
-- if the date is unparseable, so a typo in @last-updated@ surfaces
-- in the rendered page rather than blowing up the build.
formatWriterly :: String -> String
formatWriterly iso =
case parseTimeM True defaultTimeLocale "%Y-%m-%d" iso :: Maybe Day of
Nothing -> iso
Just d -> formatTime defaultTimeLocale "%-d %B %Y" d
relativeTime :: Day -> String -> String
relativeTime today iso =
case parseTimeM True defaultTimeLocale "%Y-%m-%d" iso :: Maybe Day of
Nothing -> ""
Just d -> bucket (diffDays today d)
where
bucket n
| n < 0 = ""
| n == 0 = "today"
| n == 1 = "yesterday"
| n < 7 = show n ++ " days ago"
| n < 28 = pluralize (n `div` 7) "week"
| n < 365 = pluralize (n `div` 30) "month"
| otherwise = pluralize (n `div` 365) "year"
pluralize 1 unit = "1 " ++ unit ++ " ago"
pluralize k unit = show k ++ " " ++ unit ++ "s ago"
-- ---------------------------------------------------------------------------
-- Load
-- ---------------------------------------------------------------------------
-- | UTF-8 round-trip String → ByteString. Hakyll's @getResourceBody@
-- hands us a 'String' (Unicode codepoints); the yaml library wants
-- a UTF-8 'ByteString'. 'Data.ByteString.Char8.pack' would truncate
-- each 'Char' to 8 bits — fine for ASCII, silent corruption for any
-- codepoint above 0x7F (e.g. em-dash 0x2014 → control char 0x14).
loadNow :: Compiler NowDoc
loadNow = do
rawItem <- load (fromFilePath "data/now.yaml") :: Compiler (Item String)
case Y.decodeEither' (TE.encodeUtf8 (T.pack (itemBody rawItem))) of
Left err -> fail ("now.yaml: " ++ show err)
Right doc -> return doc
-- ---------------------------------------------------------------------------
-- Context
-- ---------------------------------------------------------------------------
nowCtx :: Context String
nowCtx =
constField "now" "true"
<> field "now-last-updated" (\_ -> nLastUpdated <$> loadNow)
<> field "now-last-updated-display" (\_ -> formatWriterly . nLastUpdated <$> loadNow)
<> field "now-last-updated-relative" (\_ -> do
doc <- loadNow
nowT <- unsafeCompiler getCurrentTime
let today = utctDay nowT
rel = relativeTime today (nLastUpdated doc)
if null rel
then noResult "no relative time"
else return rel
)
<> field "now-entries-html" (\_ -> renderEntries . nEntries <$> loadNow)
<> field "now-shipped-html" (\_ -> renderShippedAll . nShipped <$> loadNow)
<> siteCtx

View File

@ -27,6 +27,7 @@ import Compilers (essayCompiler, postCompiler, pageCompiler, poetryCompiler, fi
compositionCompiler, sidecarCompiler)
import Catalog (musicCatalogCtx)
import Commonplace (commonplaceCtx)
import Now (nowCtx)
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
contentKindField, recentFirstByDisplay,
tagLinksFieldExcludingTopSegment)
@ -206,6 +207,10 @@ rules = do
-- with dependency tracking by the commonplace page compiler.
match "data/commonplace.yaml" $ compile getResourceBody
-- Now YAML — same pattern as commonplace. Loaded by Now.nowCtx for
-- /current.html. Re-compiles current.html when the YAML changes.
match "data/now.yaml" $ compile getResourceBody
-- ---------------------------------------------------------------------------
-- Homepage
-- ---------------------------------------------------------------------------
@ -261,6 +266,19 @@ rules = do
>>= loadAndApplyTemplate "templates/default.html" commonplaceCtx
>>= relativizeUrls
-- ---------------------------------------------------------------------------
-- Now — research-first status page driven by data/now.yaml. Same
-- structural pattern as the commonplace page: markdown body
-- (optional intro prose) + structured YAML rendered into HTML by
-- Now.nowCtx, then assembled by templates/current.html.
-- ---------------------------------------------------------------------------
match "content/current.md" $ do
route $ constRoute "current.html"
compile $ pageCompiler
>>= loadAndApplyTemplate "templates/current.html" nowCtx
>>= loadAndApplyTemplate "templates/default.html" nowCtx
>>= relativizeUrls
match "content/colophon.md" $ do
route $ constRoute "colophon.html"
compile $ essayCompiler
@ -272,6 +290,7 @@ rules = do
.&&. complement "content/index.md"
.&&. complement "content/commonplace.md"
.&&. complement "content/colophon.md"
.&&. complement "content/current.md"
.&&. complement "content/library.md") $ do
route $ gsubRoute "content/" (const "")
`composeRoutes` setExtension "html"

View File

@ -17,6 +17,16 @@ These are probably what you're looking for. A summary of the key points follows
- **Brown University** — Sc.B. in Computer Science and Mathematics. August 2022 May 2026
- **DIS Copenhagen / Københavns Universitet** — Semester abroad. Fall 2024
## Research Interests
My work clusters into three threads:
- **Machine learning** — order-invariant ICD-10-CM embeddings (under review at *JAMA Network Open*, deployed calculator), the NeuroPose 3D-kinematics system in Liqi Shu's lab at Brown Neurology, and ongoing research engineering at [NeuroAI](https://neuroai.health). Undergraduate work has been clinically focused; graduate study broadens the scope.
- **Computer systems and high-performance computing** — the Weenix kernel, a TCP/IP networking stack from scratch in Go, and micro-architectural performance work (SIMD, hardware counters via PAPI, RAPL energy, cross-ISA ports across AVX2 / ARM NEON-SVE / RISC-V V) on Brown's OSCAR HPC cluster.
- **Reinforcement learning and applied AI** — a Magic: The Gathering RL project on OSCAR; production agentic-systems work at xAI on `grok-code-fast-1`; reasoning, evaluation, and red-teaming contracts with Anthropic, Mistral, and OpenAI.
Computer vision and security thread through all three but do not stand on their own.
## Research
### Published / In Submission
@ -46,9 +56,13 @@ See [resume (PDF)](/resume.pdf).
- **Shu Laboratory, Brown Department of Neurology***Undergraduate Researcher and Technical Lead.* October 2023 Present. Technical lead on [NeuroPose](/essays/neuropose/); co-lead developer on the [ICD-10-CM embedding model](/essays/beyond-comorbidity-indices/).
- **Independent Research Contracting***Anthropic, Mistral, OpenAI.* 2025 Present Expert reasoning contributions in code and mathematics for agentic workflows, agentic task design and evaluation, AI safety, and red-teaming.
## Projects
## Selected Projects
For a complete index of engineering artifacts — kernels, networking, cryptography, deployed ML — see [/cv/projects/](/cv/projects/).
- **[Weenix](/essays/weenix/)** — Unix-like kernel in ~7,000 lines of C: virtual memory, VFS, system calls, threading, device drivers, interrupt handlers, and file systems. Custom linker support for running userspace x86-64 ELF binaries; extended with pipes and userspace preemption.
- **[Networking Stack from Scratch](/essays/networking-stack/)** — TCP/IP, RIP, UDP, and DNS in Go, supporting file transmission of up to 1 GB across 8-node networks. Extended with a fully RFC-compliant SSH implementation (2,000+ additional lines) supporting sustained sessions of arbitrary length.
- **[SIMD / PQC Performance Study](/essays/where-does-simd-help-post-quantum-cryptography/)** — Hand-written AVX2 assembly for ML-KEM / Kyber. 35×56× speedup over compiler-optimized C for core NTT arithmetic; 5.4×7.1× end-to-end KEM speedup, with a full statistical-analysis pipeline on Brown's OSCAR cluster.
For the complete index — additional artifacts, deployed ML, and smaller tools — see [/cv/projects/](/cv/projects/).
## Contact

View File

@ -1,12 +1,5 @@
---
title: Now
title: Current
---
An index of my current projects and focuses across domains - research / professional, creative, miscellaneous, you name it.
## Working on
### Levshell
I am working on a comprehensive rewrite of Levshell, my productivity and research-focused shell for Linux (running Wayland). This is a major focus of mine over the summer of 2026 as I prepare to begin my graduate program in Computer Science.
### "Pmacs"
I'm writing an IDE that is heavily inspired by Emacs, but written in a more modern programming language (Zig) with more powerful features, and, yes, parallelism!
A working index of what I am building, writing, and thinking through right now — kept current rather than comprehensive. The page is rebuilt whenever an entry moves; the stamp above the first section is the canonical mark of how fresh the picture is.

View File

@ -7,7 +7,7 @@ For as long as I can remember, I have believed that the deepest understanding co
You have found the working library of a mind that takes that spirit seriously. Here live research papers and living essays, compositions and scores, poetry and prose, and the countless smaller investigations that refuse to fit neatly into any one category. The documents here are far from immutable; they grow, are revised, accumulate footnotes and second thoughts. I welcome you to all of it.
This website is *not* an academic homepage, nor a blog, nor a portfolio — though it borrows from each. It is something I built because no existing format could hold what I wanted to make. I carry a copy of *The Brothers Karamazov* everywhere; I compose symphonies and concerti for orchestras; I study computation and mathematics; I am about to begin my graduate work in computer science. These facts coexist in one life, and this is the place where they coexist on one shelf.
This website is *not* an academic homepage, nor a blog, nor a portfolio — though it borrows from each. It is something I built because no existing format could hold what I wanted to make. I carry a copy of *The Brothers Karamazov* everywhere; I compose symphonies and concerti for orchestras; I study computation and mathematics; in autumn I begin graduate study in computer science at the Technical University of Denmark, where my research spans machine learning and computer systems. These facts coexist in one life, and this is the place where they coexist on one shelf.
::: {.hp-latin lang="la"}
*Te accipio, hospes benignus.*

91
data/now.yaml Normal file
View File

@ -0,0 +1,91 @@
# Now — research-first status feed for /current.html.
#
# `last-updated` is the page-level temporal stamp. Bump it whenever
# you push any change here, even a one-line note edit; the value is
# rendered prominently and a relative-time tail ("4 days ago") is
# computed at build time.
#
# `entries:` are active items. Each carries:
# - title : display name
# - section : free-form section key (research, engineering, …);
# sections render in first-appearance order
# - status : in-review | revising | drafting | building | early-stage |
# paused. Free-form, but the listed values are the canonical
# ladder (closest-to-shipping → furthest) and have CSS accents.
# - updated : YYYY-MM-DD when this entry last moved (NOT page-stamp)
# - link : optional URL (artifact, preprint, repo, etc.)
# - note : optional one-sentence what's-happening-now line
# - priority : optional integer (default 0). Manual override on the sort
# order: positive floats up, negative sinks down. Use sparingly
# — the status ladder already captures most of the signal;
# priority is for "this is the headline regardless of where
# its status puts it" (or vice versa).
#
# Sort within each section: priority desc → status rank asc → updated desc.
# Section order: first-appearance in this file. Rearrange to taste.
#
# `shipped:` are recently-completed items. Each carries:
# - title, completed (date), optional link, optional note
# Sorted newest-first at render time. Curate by hand — items don't
# auto-prune. Move things off the list when they stop earning the slot.
last-updated: 2026-04-26
entries:
- title: "Order-Invariant ICD-10-CM Embedding (JAMA submission)"
section: research
status: in-review
updated: 2026-04-10
link: /essays/beyond-comorbidity-indices/
note: "Under review at JAMA Network Open. Calculator deployed at levineuwirth.github.io/icd_embeddings."
- title: "Semantic-embeddings citation work"
section: research
status: drafting
updated: 2026-04-22
link: https://neuroai.health
note: "Early-stage manuscript with NeuroAI. Preprint expected summer 2026."
- title: "SIMD / PQC, Phase 2 & 3"
section: research
status: building
updated: 2026-04-19
link: /essays/where-does-simd-help-post-quantum-cryptography/
note: "Hardware performance counters (PAPI), RAPL energy, and cross-ISA ports (ARM NEON/SVE, RISC-V V) on Brown's OSCAR cluster."
- title: "NeuroPose clinical-implications manuscript"
section: research
status: drafting
updated: 2026-04-08
link: /essays/neuropose/
note: "In preparation; target submission 20262027."
- title: "Magic: The Gathering reinforcement learning"
section: research
status: early-stage
updated: 2026-04-15
note: "Early-stage RL on Brown's OSCAR HPC cluster; expected late 2026."
- title: "CHASE 2026 conference submission"
section: research
status: in-review
updated: 2026-04-05
note: "IEEE/ACM Conference on Connected Health (CHASE), August 2026, in review."
- title: "Levshell"
section: engineering
status: building
updated: 2026-04-26
note: "Comprehensive rewrite of my Wayland-targeted productivity and research shell. Major focus over the summer of 2026."
- title: "Pmacs"
section: engineering
status: building
updated: 2026-04-20
note: "Emacs-inspired IDE in Zig with first-class parallelism."
shipped:
- title: "Where Does SIMD Help Post-Quantum Cryptography?"
completed: 2026-04-15
link: /essays/where-does-simd-help-post-quantum-cryptography/
note: "Phase 1 technical report and reproducible artifact. Brown CS Department."

View File

@ -16,6 +16,7 @@ executable site
Authors
Catalog
Commonplace
Now
Backlinks
Dingbat
SimilarLinks

198
static/css/now.css Normal file
View File

@ -0,0 +1,198 @@
/* now.css /current.html.
*
* The Now page is a research-first status surface curated like the
* Library, with explicit per-item temporality. It composes on top of
* item-card.css (gated separately in head.html via $if(now)$) and
* library.css's section primitives are deliberately not pulled in
* Now uses its own header treatment without dingbats or shelf
* dividers, since sections here are project-states rather than
* content shelves.
*/
/* ============================================================
PAGE-LEVEL "LAST UPDATED" MASTHEAD
Title leads, date follows in display-weight Spectral italic.
Descending visual hierarchy BIG sans title medium serif
italic date tiny italic relative. The date carries enough
typographic weight to be the page's thesis without competing
with the title for the "what page is this" anchor.
============================================================ */
.now-header {
margin-bottom: 3rem;
}
/* The page title remains the primary anchor sized & weighted
by .page-title in components.css. Generous bottom margin gives
the masthead-date its own breathing room below. */
.now-header .page-title {
margin-top: 0;
margin-bottom: 1.1rem;
}
.now-stamp {
margin: 0;
display: flex;
flex-direction: column;
gap: 0.15rem;
line-height: 1.15;
}
.now-stamp-label {
font-family: var(--font-sans);
font-size: 0.72rem;
font-variant: all-small-caps;
letter-spacing: 0.12em;
color: var(--text-faint);
}
/* The masthead datum. Spectral italic at ~1.85rem so it carries
presence as the page's thesis without competing with the
2.6rem sans title above it. Old-style numerals keep the
literary register; tabular-nums stay aligned if the day
width changes. */
.now-stamp-date {
font-family: var(--font-serif);
font-size: 1.85rem;
font-style: italic;
font-weight: 400;
color: var(--text);
line-height: 1.1;
margin-top: 0.05rem;
font-feature-settings: "onum" 1, "tnum" 1;
}
/* Small italic footer line under the masthead qualifies
the absolute date with a human-readable "how recent." */
.now-stamp-relative {
font-family: var(--font-serif);
font-size: 0.92rem;
font-style: italic;
color: var(--text-faint);
margin-top: 0.3rem;
}
/* ============================================================
INTRO PROSE
Optional body content from current.md, rendered between the
stamp and the first section. Sits in normal page measure; no
drop cap, no special treatment.
============================================================ */
.now-intro {
margin: 0 0 2.5rem;
font-family: var(--font-serif);
font-size: 1rem;
color: var(--text-muted);
line-height: 1.6;
}
.now-intro p:last-child {
margin-bottom: 0;
}
/* ============================================================
SECTION HEADINGS
Spectral small-caps in a quieter accent than library shelves.
No ornaments sections are project-states, not content shelves,
and shouldn't carry the same identity weight.
============================================================ */
.now-section {
margin-bottom: 2.75rem;
}
.now-section-heading {
font-family: var(--font-serif);
font-size: 1.15rem;
font-variant: all-small-caps;
font-feature-settings: "smcp" 1;
letter-spacing: 0.09em;
color: var(--text-muted);
text-transform: none;
font-weight: 400;
margin: 0 0 0.85rem 0;
}
/* The recently-shipped section uses a slightly fainter heading +
a thin top divider to separate it visually from active work. */
.now-section--shipped {
margin-top: 3.5rem;
padding-top: 2.25rem;
border-top: 1px solid var(--border);
}
.now-section--shipped .now-section-heading {
color: var(--text-faint);
font-style: italic;
letter-spacing: 0.11em;
}
/* ============================================================
STATUS CHIP
Replaces the kind badge slot in item-card. Same min-width as
.item-card-kind so titles align across active and shipped
sections. Tabular sans-caps, very low-contrast frame.
============================================================ */
.now-card .now-kind {
/* Override item-card-kind text-only treatment with chip layout */
display: flex;
align-items: flex-start;
margin-top: 0.25em;
}
.now-status {
display: inline-block;
font-family: var(--font-sans);
font-size: 0.66rem;
font-variant: all-small-caps;
letter-spacing: 0.08em;
line-height: 1;
padding: 0.32em 0.55em 0.28em;
border: 1px solid var(--border);
border-radius: 2px;
color: var(--text-muted);
background: transparent;
white-space: nowrap;
}
/* Per-status accents each is intentionally restrained. Active
states (in-review, drafting, building) get slightly stronger
ink; passive states (paused, shipped) recede. */
.now-status--in-review { color: var(--text); border-color: var(--text-muted); }
.now-status--revising { color: var(--text); border-color: var(--text-muted); }
.now-status--drafting { color: var(--text); }
.now-status--building { color: var(--text); }
.now-status--early-stage { color: var(--text-muted); border-style: dashed; }
.now-status--paused { color: var(--text-faint); border-style: dashed; opacity: 0.75; }
.now-status--shipped { color: var(--text-faint); border-color: var(--text-faint); }
.now-card--shipped {
opacity: 0.92;
}
/* ============================================================
MOBILE
Mirror item-card.css's mobile rules for header stacking,
then shrink the chip column so titles get real room.
============================================================ */
@media (max-width: 540px) {
.now-card .now-kind {
margin-top: 0;
}
.now-status {
font-size: 0.62rem;
padding: 0.28em 0.45em 0.24em;
}
.now-stamp-date {
font-size: 1.55rem;
}
.now-stamp-relative {
font-size: 0.88rem;
}
}

15
templates/current.html Normal file
View File

@ -0,0 +1,15 @@
<main id="markdownBody" data-pagefind-body>
<header class="now-header">
<h1 class="page-title">$title$</h1>
<p class="now-stamp">
<span class="now-stamp-label">Last updated</span>
<time class="now-stamp-date" datetime="$now-last-updated$">$now-last-updated-display$</time>
$if(now-last-updated-relative)$<span class="now-stamp-relative">$now-last-updated-relative$</span>$endif$
</p>
</header>
$if(body)$<div class="now-intro">$body$</div>$endif$
$now-entries-html$
$now-shipped-html$
</main>

View File

@ -40,6 +40,8 @@ $if(list-page)$<link rel="stylesheet" href="/css/item-card.css">$endif$
$if(memento-mori)$<link rel="stylesheet" href="/css/memento-mori.css">$endif$
$if(catalog)$<link rel="stylesheet" href="/css/catalog.css">$endif$
$if(commonplace)$<link rel="stylesheet" href="/css/commonplace.css">$endif$
$if(now)$<link rel="stylesheet" href="/css/item-card.css">$endif$
$if(now)$<link rel="stylesheet" href="/css/now.css">$endif$
$if(build)$<link rel="stylesheet" href="/css/build.css">$endif$
$if(reading)$<link rel="stylesheet" href="/css/reading.css">$endif$
$if(composition)$<link rel="stylesheet" href="/css/score-reader.css">$endif$

View File

@ -6,10 +6,12 @@
<div class="nav-primary">
<a href="/">Home</a>
<a href="/library.html">Library</a>
<a href="/links.html">Links</a>
<a href="/me.html">Me</a>
<a href="/new.html">New</a>
<a href="/links.html">Links</a>
<a href="/current.html">Current</a>
<a href="/search.html">Search</a>
<a href="/about.html">Vita</a>
</div>
<div class="nav-controls">
<button type="button" class="nav-portal-toggle" aria-label="Toggle portals" aria-expanded="false">