diff --git a/.gitignore b/.gitignore
index 08dea64..2529e62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.
diff --git a/Makefile b/Makefile
index bafec16..07e2389 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/build/Now.hs b/build/Now.hs
new file mode 100644
index 0000000..2381b42
--- /dev/null
+++ b/build/Now.hs
@@ -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
+ [ ""
+ , escapeHtml (titleCaseWords s)
+ , ""
+ ]
+
+-- | 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
+ [ "
"
+ , ""
+ , renderStatusChip (neStatus e)
+ , ""
+ , ""
+ , ""
+ , maybe "" (\n -> "
" ++ escapeHtml n ++ "
") (neNote e)
+ , "
"
+ , ""
+ ]
+
+renderShippedEntry :: NowShipped -> String
+renderShippedEntry s = concat
+ [ ""
+ , ""
+ , renderStatusChip "shipped"
+ , ""
+ , ""
+ , ""
+ , maybe "" (\n -> "
" ++ escapeHtml n ++ "
") (nsNote s)
+ , "
"
+ , ""
+ ]
+
+renderTitle :: Maybe String -> String -> String
+renderTitle mu title = case mu of
+ Just url -> "" ++ escapeHtml title ++ ""
+ Nothing -> "" ++ escapeHtml title ++ ""
+
+renderSection :: String -> [NowEntry] -> String
+renderSection sec es = concat
+ [ ""
+ , ""
+ , escapeHtml (titleCaseWords sec)
+ , "
"
+ , ""
+ , concatMap renderEntry es
+ , "
"
+ , ""
+ ]
+
+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
+ [ ""
+ , "Recently Shipped
"
+ , ""
+ , concatMap renderShippedEntry sorted
+ , "
"
+ , ""
+ ]
+ 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
diff --git a/build/Site.hs b/build/Site.hs
index c06e8d4..79ae90a 100644
--- a/build/Site.hs
+++ b/build/Site.hs
@@ -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"
diff --git a/data/now.yaml b/data/now.yaml
new file mode 100644
index 0000000..7ceda9d
--- /dev/null
+++ b/data/now.yaml
@@ -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 2026–2027."
+
+ - 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."
diff --git a/levineuwirth.cabal b/levineuwirth.cabal
index 77eef3a..4183bcc 100644
--- a/levineuwirth.cabal
+++ b/levineuwirth.cabal
@@ -16,6 +16,7 @@ executable site
Authors
Catalog
Commonplace
+ Now
Backlinks
Dingbat
SimilarLinks
diff --git a/static/css/now.css b/static/css/now.css
new file mode 100644
index 0000000..7a38aca
--- /dev/null
+++ b/static/css/now.css
@@ -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;
+ }
+}
diff --git a/templates/current.html b/templates/current.html
new file mode 100644
index 0000000..7a366ae
--- /dev/null
+++ b/templates/current.html
@@ -0,0 +1,15 @@
+
+
+
+ $if(body)$$body$
$endif$
+
+ $now-entries-html$
+ $now-shipped-html$
+
diff --git a/templates/partials/head.html b/templates/partials/head.html
index acf7622..2cdd472 100644
--- a/templates/partials/head.html
+++ b/templates/partials/head.html
@@ -40,6 +40,8 @@ $if(list-page)$$endif$
$if(memento-mori)$$endif$
$if(catalog)$$endif$
$if(commonplace)$$endif$
+$if(now)$$endif$
+$if(now)$$endif$
$if(build)$$endif$
$if(reading)$$endif$
$if(composition)$$endif$
diff --git a/templates/partials/nav.html b/templates/partials/nav.html
index a60e6f2..4d1526e 100644
--- a/templates/partials/nav.html
+++ b/templates/partials/nav.html
@@ -6,10 +6,12 @@