Compare commits
No commits in common. "42ba2bf972279e520ac6030a33e00bb0e515e9a0" and "370f81217c547253476cf421d9c3a5e462286a0e" have entirely different histories.
42ba2bf972
...
370f81217c
|
|
@ -3,28 +3,10 @@ _site/
|
||||||
_cache/
|
_cache/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
# Defense-in-depth: the auto-snapshot in `make build` stages content/
|
# Defense-in-depth: catch any stray .env / .env.* anywhere in the tree
|
||||||
# wholesale. These patterns prevent any stray credential-shaped file
|
# (the auto-snapshot in the Makefile stages content/ on every build).
|
||||||
# (dropped accidentally during writing) from being staged + pushed.
|
|
||||||
# To intentionally commit one of these (rare), use `git add -f`.
|
|
||||||
**/.env
|
**/.env
|
||||||
**/.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
|
# Editor backup/swap files
|
||||||
*~
|
*~
|
||||||
|
|
@ -76,9 +58,6 @@ data/semantic-meta.json
|
||||||
# IGNORE.txt is for the local build and need not be synced.
|
# IGNORE.txt is for the local build and need not be synced.
|
||||||
IGNORE.txt
|
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
|
# CV/résumé build pipeline (YAML → Jinja → xelatex). The canonical PDFs
|
||||||
# live under static/ and ship with the site; the pipeline itself is
|
# live under static/ and ship with the site; the pipeline itself is
|
||||||
# kept locally for regeneration but not version-controlled here.
|
# kept locally for regeneration but not version-controlled here.
|
||||||
|
|
|
||||||
17
Makefile
17
Makefile
|
|
@ -16,12 +16,17 @@ build:
|
||||||
# either reuses it (no new content/ changes) or appends another
|
# either reuses it (no new content/ changes) or appends another
|
||||||
# snapshot on top, so failures don't disappear from the log.
|
# snapshot on top, so failures don't disappear from the log.
|
||||||
#
|
#
|
||||||
# `git add content/` respects .gitignore, which excludes credential-
|
# Pathspec is explicit (not `git add content/`) so a stray .env,
|
||||||
# shaped patterns (.env, *.key, *.pem, id_rsa*, credentials*, etc.)
|
# credential file, or other non-content artifact dropped under
|
||||||
# so a stray secret dropped under content/ is NOT auto-staged. To
|
# content/ is NOT auto-staged. The :(glob) magic prefix makes `**`
|
||||||
# intentionally commit a normally-ignored file, use `git add -f`
|
# match across path components (git default fnmatch does not).
|
||||||
# manually before running `make build`.
|
# Add new extensions here if a new asset type is introduced.
|
||||||
@git add content/
|
@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 diff --cached --quiet || git commit -m "auto: $$(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]"
|
@git diff --cached --quiet || git commit -m "auto: $$(date -u +%Y-%m-%dT%H:%M:%SZ) [skip ci]"
|
||||||
@mkdir -p data
|
@mkdir -p data
|
||||||
@date +%s > data/build-start.txt
|
@date +%s > data/build-start.txt
|
||||||
|
|
|
||||||
281
build/Now.hs
281
build/Now.hs
|
|
@ -1,281 +0,0 @@
|
||||||
{-# 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
|
|
||||||
|
|
@ -27,7 +27,6 @@ import Compilers (essayCompiler, postCompiler, pageCompiler, poetryCompiler, fi
|
||||||
compositionCompiler, sidecarCompiler)
|
compositionCompiler, sidecarCompiler)
|
||||||
import Catalog (musicCatalogCtx)
|
import Catalog (musicCatalogCtx)
|
||||||
import Commonplace (commonplaceCtx)
|
import Commonplace (commonplaceCtx)
|
||||||
import Now (nowCtx)
|
|
||||||
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
|
import Contexts (siteCtx, essayCtx, postCtx, pageCtx, poetryCtx, fictionCtx, compositionCtx,
|
||||||
contentKindField, recentFirstByDisplay,
|
contentKindField, recentFirstByDisplay,
|
||||||
tagLinksFieldExcludingTopSegment)
|
tagLinksFieldExcludingTopSegment)
|
||||||
|
|
@ -207,10 +206,6 @@ rules = do
|
||||||
-- with dependency tracking by the commonplace page compiler.
|
-- with dependency tracking by the commonplace page compiler.
|
||||||
match "data/commonplace.yaml" $ compile getResourceBody
|
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
|
-- Homepage
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
@ -266,19 +261,6 @@ rules = do
|
||||||
>>= loadAndApplyTemplate "templates/default.html" commonplaceCtx
|
>>= loadAndApplyTemplate "templates/default.html" commonplaceCtx
|
||||||
>>= relativizeUrls
|
>>= 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
|
match "content/colophon.md" $ do
|
||||||
route $ constRoute "colophon.html"
|
route $ constRoute "colophon.html"
|
||||||
compile $ essayCompiler
|
compile $ essayCompiler
|
||||||
|
|
@ -290,7 +272,6 @@ rules = do
|
||||||
.&&. complement "content/index.md"
|
.&&. complement "content/index.md"
|
||||||
.&&. complement "content/commonplace.md"
|
.&&. complement "content/commonplace.md"
|
||||||
.&&. complement "content/colophon.md"
|
.&&. complement "content/colophon.md"
|
||||||
.&&. complement "content/current.md"
|
|
||||||
.&&. complement "content/library.md") $ do
|
.&&. complement "content/library.md") $ do
|
||||||
route $ gsubRoute "content/" (const "")
|
route $ gsubRoute "content/" (const "")
|
||||||
`composeRoutes` setExtension "html"
|
`composeRoutes` setExtension "html"
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,6 @@ 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
|
- **Brown University** — Sc.B. in Computer Science and Mathematics. August 2022 – May 2026
|
||||||
- **DIS Copenhagen / Københavns Universitet** — Semester abroad. Fall 2024
|
- **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
|
## Research
|
||||||
|
|
||||||
### Published / In Submission
|
### Published / In Submission
|
||||||
|
|
@ -56,13 +46,9 @@ 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/).
|
- **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.
|
- **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.
|
||||||
|
|
||||||
## Selected Projects
|
## 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.
|
For a complete index of engineering artifacts — kernels, networking, cryptography, deployed ML — see [/cv/projects/](/cv/projects/).
|
||||||
- **[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
|
## Contact
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
---
|
---
|
||||||
title: Current
|
title: Now
|
||||||
---
|
---
|
||||||
|
An index of my current projects and focuses across domains - research / professional, creative, miscellaneous, you name it.
|
||||||
|
|
||||||
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.
|
## 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!
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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; 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.
|
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.
|
||||||
|
|
||||||
::: {.hp-latin lang="la"}
|
::: {.hp-latin lang="la"}
|
||||||
*Te accipio, hospes benignus.*
|
*Te accipio, hospes benignus.*
|
||||||
|
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
# 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."
|
|
||||||
|
|
@ -16,7 +16,6 @@ executable site
|
||||||
Authors
|
Authors
|
||||||
Catalog
|
Catalog
|
||||||
Commonplace
|
Commonplace
|
||||||
Now
|
|
||||||
Backlinks
|
Backlinks
|
||||||
Dingbat
|
Dingbat
|
||||||
SimilarLinks
|
SimilarLinks
|
||||||
|
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -40,8 +40,6 @@ $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(memento-mori)$<link rel="stylesheet" href="/css/memento-mori.css">$endif$
|
||||||
$if(catalog)$<link rel="stylesheet" href="/css/catalog.css">$endif$
|
$if(catalog)$<link rel="stylesheet" href="/css/catalog.css">$endif$
|
||||||
$if(commonplace)$<link rel="stylesheet" href="/css/commonplace.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(build)$<link rel="stylesheet" href="/css/build.css">$endif$
|
||||||
$if(reading)$<link rel="stylesheet" href="/css/reading.css">$endif$
|
$if(reading)$<link rel="stylesheet" href="/css/reading.css">$endif$
|
||||||
$if(composition)$<link rel="stylesheet" href="/css/score-reader.css">$endif$
|
$if(composition)$<link rel="stylesheet" href="/css/score-reader.css">$endif$
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,10 @@
|
||||||
<div class="nav-primary">
|
<div class="nav-primary">
|
||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
<a href="/library.html">Library</a>
|
<a href="/library.html">Library</a>
|
||||||
<a href="/links.html">Links</a>
|
|
||||||
<a href="/me.html">Me</a>
|
<a href="/me.html">Me</a>
|
||||||
<a href="/new.html">New</a>
|
<a href="/new.html">New</a>
|
||||||
<a href="/current.html">Current</a>
|
<a href="/links.html">Links</a>
|
||||||
<a href="/search.html">Search</a>
|
<a href="/search.html">Search</a>
|
||||||
<a href="/about.html">Vita</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-controls">
|
<div class="nav-controls">
|
||||||
<button type="button" class="nav-portal-toggle" aria-label="Toggle portals" aria-expanded="false">
|
<button type="button" class="nav-portal-toggle" aria-label="Toggle portals" aria-expanded="false">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue