Feature History
2026-08-20 — Notes step out of the nav and out of Recent
The stream keeps every way in except the two that duplicated it.
-
/notesleaves the primary navnav: truedropped from its registry entry. That is the only thing it removes: the footer link, the sitemap entry, thenotescommand and thenkey all still work- The newest note has a panel above the terminal, which is how a reader finds the stream. A nav slot pointing at the same content is a third route to it on one screen
- Nav is back to three: Projects, Posts, Fanfic
-
Notes leave Recent
RecentSectionfilterstype !== 'NOTE'LatestNoteprints the newest note at the top of the same page, so a note row in Recent is the one entry guaranteed to be a repeat — and a burst of notes would push every project, post and story off a five-row list/feed,/searchand/tagsstill carry them; only the homepage’s five-row summary does not
2026-08-20 — The newest note, above the terminal
The homepage notes block moved above the hero and shrank to one note.
-
NotesSection→LatestNote- Two notes below the terminal became one note above it. The newest note is the only thing on the homepage that changes weekly, so it is the only thing that earns a place ahead of the hero
- One, not two: a stack pushes the terminal off the first screen, and the terminal is the site’s opening move
- Order in
<main>is now the hiddenh1, the note panel, the terminal
-
Shaped like a tweet, not styled like one
- Handle, timestamp, body, quiet footer — the form the content is written in
- The timestamp is the permalink, the way it is on a tweet
- No avatar, no fill, no pill, no counts, no colour. Hierarchy is case and tone, as everywhere else
- An unfilled
--border-colorbox against the terminal’s--bg-subtlefill directly below, so the two read as siblings and the panel is the quieter
-
Fixed a one-space indent on every note
.note-bodyiswhite-space: pre-wrapso a note can carry deliberate line breaks. That also means Astro’s template whitespace around the expression renders, which was indenting every note by a spaceNoteBody.astronow closes its<p>tags tight against the expression. One fix, three surfaces: the panel,/notes, and the permalink page
-
The footer stopped needing an empty element
justify-content: space-betweenplus a placeholder<span />becamemargin-left: autoon the link, since the tag list is optional and the link has to sit right whether or not it is there
2026-08-20 — Two lengths of writing
The diary became posts, and a second, shorter collection joined it. One place for pieces worth a title, one for the things that are not.
-
diary→posts- The rename happened while the collection held a single entry: the cost was a registry line and one RSS URL nobody had subscribed to. In six months it would have been a redirect table
/diary→/posts,/diary/:slug→/posts/:slug,/diary/rss.xml→/posts/rss.xml.DiarySection→PostsSection,EntryType.DIARY→POST- The keyboard shortcut moved from
dtow—pwas already projects, and the two are near enough that a wrong key would land on the wrong page - Names were picked as a pair, for contrast:
notesis short,postsis long. “Diary” alongside “notes” would have said personal-and-dated twice and long-form nowhere
-
/notes— the short-form stream- A note is a body and a date. No title, no file
- It is the one collection that is not a folder of markdown:
notes.yamlholds a single array read by thefile()loader, so posting is appending three lines rather than creating, naming and front-mattering a file to publish one sentence — and the filename would have been doing nothing anyway, with no title to derive it from - The trade is that
file()entries have no body torender(). Note bodies are plain text;linkify()splits them into text and link runs soNoteBody.astrocan render real anchors without ever handing a string toset:html /notes/:idexists even though the whole stream fits on one page —/search,/feedand RSS all need something to link to, and a feed reader dedupes by link, so a shared URL would collapse the stream into one item
-
Titleless entries got a single fallback, not four
SiteEntry.titleis now optional, andentryTitle()returns the title where there is one and the head of the body where there is not, cut on a word boundaryRecentSection,/feed,/searchand both RSS feeds call it. The truncation rule lives in one place rather than being reinvented per surfaceCardItem.titlestays required — notes do not render throughCollectionList, and an optional title there is flexibility nothing uses
-
Notes are kept out of
/feed.xml- Short-form posting is bursty. A day of five notes would bury a long post in
every reader subscribed site-wide, so notes are syndicated separately at
/notes/rss.xmland subscribing to the churn is opt-in - The
/feedpage still lists them: burying costs a reader’s unread count, not a page you scroll, and that page promises everything
- Short-form posting is bursty. A day of five notes would bury a long post in
every reader subscribed site-wide, so notes are syndicated separately at
-
The homepage grew a notes block, above Recent
- Two notes, rendered in full — at two sentences each, a card would be more chrome than text
- Deliberately outside the section-reordering block below it. That block’s job is to signal someone is actively here, which only works if it sits in the same place every time
- The terminal hero is untouched and still first
-
Both note permalinks carry an explicit
:hover- Astro scopes
.note-dateto(0,2,0), which outranks the globala:hoverat(0,1,1). Without a matching rule the date would keep its muted colour on an inverted background — invisible, not merely ugly. Third variant of the hazard already documented in CLAUDE.md
- Astro scopes
-
/tagslinks notes without a?tag=/noteshas no filter UI — the stream is one page and Ctrl-F is the search- A
?tag=there would be a parameter nothing reads, soFILTERABLEintags.astronames the three pages that actually honour it
Nav is now four of its five slots: Projects, Posts, Notes, Fanfic. The build is 19 pages, up from 17.
2026-08-20 — One rhythm for every list
Adding the Recent list put four different list treatments on one page. They now share a single row rhythm and a single column geometry.
-
The type column right-aligns against a fixed floor
date [type] titlesized its type column to the longest type on the page, so[diary]left two blank characters before its title while[project]sat snug —HELLO WORLDlooked pushed away from its own bracketjustify-self: endplusmin-width: var(--col-type)ends every]exactly one--space-3before its title, whatever the type name is--col-typeis inch, and a longer type name simply widens the column rather than breaking the alignment, so new collections need no change/feedgets a7chfloor on its date for the same reason: each year is a separate grid, and without it the titles stepped in and out between years
-
Every list row is
padding: var(--space-4) 0.cardwas--space-5and.recent-itemwas--space-3;/searchwas already on--space-4, and the others now match it.recent-listgains theborder-tophairline.gridand.search-resultsalready had, so all four lists are framed the same way
-
.view-alland the section heading moved toglobal.css- The three homepage sections each carried an identical scoped
.view-all(centred, hardcoded2rem) and an identical inlinestyleon theirh2 - The Recent list closes with the same
.btnthe sections do, left-aligned like everything else in a one-column layout
- The three homepage sections each carried an identical scoped
-
Fanfic’s homepage section caught up with the others
- It sliced the raw collection, which is filesystem order — the “latest” three chapters were whatever the loader returned first. Now sorted by date
- Its cards had no date line; they now print one like Projects and Diary
- Its series label used an inline
margin-bottom: 0.75reminstead of the existing.card-eyebrowclass
2026-08-20 — The hero is one line now
The terminal opened with roughly twenty-four lines of pre-baked output — whoami,
cat about.txt, ls skills/, ls sections/, and a five-item git log — above
three macOS window dots. It spent the entire first screen restating what the
sections below already said, and pushed the actual content off the fold.
-
The hero collapsed to a single prompt row
- One
$, one input, one muted hint. Nothing is printed until you ask for it - Command output is capped at
40vhand scrolls, sohelporlscan no longer grow the hero without limit - The window dots are gone from the hero and from
/404. They were the one skeuomorphic element on a site whose rule is hairlines and square corners, and.terminal-header/.terminal-dotwere deleted fromglobal.css
- One
-
The static copy became commands
whoaminow carries the tagline and the about paragraphskillsis a new built-in listing what the work is inls sections/was redundant withls, which already lists every page- Nothing new shadows a route:
aboutstays the/aboutcommand, since built-ins win on a name collision and would have hidden the page
-
Recent updates moved out to
RecentSection.astro- The
git log --oneline -5block is now real markup on the homepage, so it renders without JavaScript and costs the hero nothing - It reads the same
allEntries()that/feedand/searchdo - Dates are ISO here, unlike
/feed: this list is ungrouped and spans years, so a bareApr 7would be ambiguous
- The
-
The hand-positioned cursor is gone
- The old fake caret was absolutely positioned from
inputField.value.lengthinchunits, updated on every keystroke, and drifted whenever the field scrolled. The browser’s own caret now does the job while the field has focus - A blinking block still marks the resting state, shown only while the field is both empty and unfocused, so the two can never appear at once
- The block is centred with
top: 0; bottom: 0; margin-block: autorather than a tunedtopoffset. Absolute positioning drops thevertical-align: text-bottomit inherits from.terminal-cursor, and the first attempt attop: 0.1emleft it riding ~3px high in the input’s 1.7 line-height box. Centring tracks the real caret and survives a change to the line-height or the step size; a magic offset does not
- The old fake caret was absolutely positioned from
-
The tab no longer says what the site is
- The homepage title was
Developer Portfolio | SSani, which described the genre rather than the site. It is now justSSani titlebecame optional onBaseHeadandLayoutso the homepage can omit it.PageShellandCollectionPagestill require it, which keeps the compile-time guard where most pages actually enter the chain- The same filler was in three other places a reader would meet it: the
default meta description,
og:site_name(SSani Portfolio), and the homepage’s visually-hiddenh1.grep -i portfoliooversrc/is now empty
- The homepage title was
2026-08-20 — Quiet Terminal: a full design system replacement
The monochrome soft-brutalist system was replaced outright. It was built on
devices — 3px opaque borders, 6px/10px hard drop shadows, translate(-4px, -4px)
hover lifts, pure #000000/#f4f4f9 — that the reference sites for this pass
(nelson.cloud, carteakey.dev) deliberately avoid. Softening them one at a time
would have left the old rules fighting the new look, so the doctrine was rewritten
rather than patched.
-
One neutral ramp replaces the five-colour palette
- The blue-grey five (
#000000,#2f4550,#586f7c,#b8dbd9,#f4f4f9) became a twelve-step cool grey ramp,--n-0through--n-950, carrying a trace of the old hue so the site still reads as itself - Neither end is pure: the light page is
#e4e7eaand the dark page is#0d0f11. A page should read as paper with ink on it, not as a light box - Body text clears 4.5:1 in both themes and control borders clear 3:1. A new
--ruletoken carries the decorative hairlines, which do not clear 3:1 and must never be used on a control
- The blue-grey five (
-
The two accent families collapsed into one
.terminalused to be pure black in both themes, which forced a parallel set of--terminal-*accents that could survive on black, and a standing warning never to use a page token inside it- The terminal now follows the theme like everything else, so that whole second
family is gone.
--accent-color,--warning-color,--success-color,--error-colorand--muted-colorare deleted — they had no consumers - The
--terminal-*names survive only as aliases onto page tokens, becauseTerminalHero.astrowrites them at runtime onto elements it creates itself
-
A flat type scale in one monospace family
- Quicksand and Nunito were replaced by Cascadia Mono for headings, body, UI and code alike — the ligature-free cut of the Windows Terminal face, chosen over JetBrains Mono, Intel One Mono, IBM Plex Mono, Geist Mono and Space Mono after comparing all six at the site’s real sizes with real content. Cascadia Code is the same design with code ligatures and must not be substituted: they would fire inside prose
h1throughh6are now the same0.875remas body text. A heading is marked by uppercase, weight 700 and letter-spacing, and in.proseby a literal##printed via::before- The fluid
clamp()heading scale is gone, along with every scopedfont-sizeon a heading in404.astro,diary/[slug].astro,fanfic/[slug].astro,feed.astro,site-health.astroandPageShell.astro .metric-valueon/site-healthkeeps1.5remas the one exception: a metric is a quantity being read as a number, not a heading
-
Hairlines, square corners, and nothing that moves
--border-width3px→1px;--border-radius4px→0- All four shadow tokens are retired to
none, kept defined so a stale reference resolves to nothing rather than to an invalid value. One hard shadow survives, on the shortcuts dialog, the only surface that floats - Every
transformhover lift was removed and everytransition: allreplaced with an explicit colour-property list. Hover now inverts foreground and background instead. This is load-bearing: when hover moved an element, keyboard focus had no distinct cue left, so the:focus-visiblering is now the only thing that means focus .badge-newlost its pulsing scale animation and became a static inverted block
-
One column, and lists instead of a card grid
.containerwent frommax-width: 1200pxto--measure80ch, applied to nav, main and footer alike.gridisflex-direction: columnat every width and.cardis a row with a hairline underneath — no border, no fill, no shadow. Class names are unchanged because every list page and homepage section renders through them.tagrenders[like-this]via::before/::afterinstead of as a chip
-
Fallout in
TerminalHero.astro, caught by the post-change sweepgetTypeColor()mapped PROJECT/DIARY/FANFIC onto--terminal-accent/--terminal-success/--terminal-warning. Under the new palette those resolve to just two values, so it rendered a three-way code as two indistinguishable groups with DIARY arbitrarily dimmed. Deleted; the[TYPE]text prefix was always the real differentiator, and the label now renders like every other bracketed label — muted, weight 400- That label also carried an inline
font-weight: 600. Cascadia Mono does offer a 600 on Google Fonts, but this site loads only 400 and 700, so the browser was synthesising a fake semibold — the same fault the old Quicksand 900 had - Links inside the terminal went invisible on hover. The new
a:hoverinverts the background, but an inlinestyle="color:…"outranks it, so the text kept its normal colour on an inverted ground: 1.00:1 in light, 1.27:1 in dark. Fixed by removing the inline colours and letting the elements inherit; the dotted underline now usescurrentColor - The same fault existed in the stylesheet:
.check-item asetcolor: var(--text-primary), tyinga:hoverat (0,1,1) and winning on source order. The declaration only duplicated the basearule, so it was deleted rather than patched
-
Documentation caught up with the change
CLAUDE.md’s design system and key principles were rewritten; the old five-colour and soft-brutalist rules are gone rather than amended/colophonand/usesdescribed the retired system in prose and were rewrittenREADME.md’s design section was replaced. Earlier entries in this changelog still describe the old system, correctly, as history
2026-08-20 — Route Registry, Keyboard Shortcuts, Search, Tags and Feed
A structural pass, prompted by looking at what a fuller personal site looks like
(see ROADMAP.md). The theme throughout: one source of truth per concern, so
the site can grow without every addition being copied into five places.
-
Deduplicated the list pages
projects.astro,diary.astroandfanfic.astrowere the same page written three times, andProjectFilters/ContentFilterswere one component wearing two hats. They becameCollectionPage,CollectionList,CollectionFiltersand aCardItemtype insrc/lib/cards.ts- 676 lines across 5 files became 496 across 7, and a new list page now costs about 30 lines of mapping with no markup and no CSS
-
A route registry
src/config/routes.tsis now the single source for the nav, the footer, the 404 suggestions, the sitemap filter, the terminal’s commands and the keyboard shortcuts. A page missing from it is invisible to all of themstatus: 'draft'andunlisted: truekeep a route out of every listing and out of the sitemap while it still builds and stays reachable- The nav now points at the list pages. It previously linked to homepage
anchors, so “Projects” and
/projectswere different destinations sharing a label
-
The terminal is generated, not hardcoded
helpused to be a fixed HTML string listing three commands while the switch handled five. Commands now derive from the registry: 12 instead of 5, and a new page is typeable the moment it is registered.lsprints every page
-
Keyboard shortcuts
?opens an overlay rendered from the same registry,mtoggles the theme, and each route can claim a key — currentlyh p d f e s t c- Every shortcut is suppressed while typing in a field, so typing
helpin the terminal does not fire four navigations
-
New pages —
/search(server-rendered, works without JavaScript),/tags(with?tag=deep links into the list pages),/feed(everything by year),/colophon,/changelog, and drafts for/now,/now/archive,/aboutand/uses -
A site-wide RSS feed at
/feed.xml, listed first for autodiscovery. Projects have no detail page, so entries now anchor to their card (/projects#jualbagasi) — a bare/projectsgave every project the same link, and feed readers dedupe by link -
The kanban board was removed — page, collection, schema, five cards and its terminal command. Its one unrecorded plan, fanfic reader controls, was carried into
ROADMAP.md -
Corrections found by testing the running site
- Every page title said the brand twice:
SSani | Dev Diary | SSani robots.txtpointed crawlers at a GitHub Pages domain the site does not use- The OpenGraph image was a 404, so every link preview was broken. There is now a real 1200×630 image built from the palette
/site-healthclaimed a skip link that did not exist. There is now a skip link- The homepage and 404 had no
h1; diary entries rendered their title twice. Every page now has exactly one .search-input:focussetoutline: none, overriding the site-wide focus ring with a cue identical to hover
- Every page title said the brand twice:
-
Shared page chrome — the same 20-line header and back-nav block had been copied into ten files.
PageShellnow owns it;site-healthkeeps its own, deliberately. Markdown styling likewise collapsed into one.proseclass
Verified throughout by diffing the rendered text of every built page against the previous build, so the refactors are provably not visual changes. Builds 17 pages.
2026-08-13 — New Five-Colour Palette
The grey + blue/orange/green/red system was replaced with a five-colour blue-grey palette: #000000 black, #2f4550 charcoal-blue, #586f7c blue-slate, #b8dbd9 light-blue, #f4f4f9 ghost-white. The five are now raw tokens in :root and are the only colours in the system.
-
Both theme blocks rewritten
- Light: background
#f4f4f9, subtle#b8dbd9, text#000000, secondary text#2f4550, borders and hard shadows#000000 - Dark: background
#000000, subtle#2f4550, text#f4f4f9, secondary text#b8dbd9, borders and hard shadows#f4f4f9 --bg-subtleis a real tint now rather than a near-invisible grey step, so filter panels, the site-health block and kanban columns read as distinct surfaces
- Light: background
-
The terminal is
#000000in both themes- Previously
#1A1A1Alight /#0A0A0Adark. A single value means terminal accents only ever have to clear one background --terminal-text-mutedmoved from#A0A0A0to#586f7c(5.0:1)
- Previously
-
Accent tokens now mark rank, not hue
- A single hue family cannot carry red-means-danger.
--warning-coloris the loudest tone (#000000light /#f4f4f9dark),--success-colorthe mid tone (#586f7c), and a new--muted-color(#586f7c) the quietest - Kanban’s
on holdcolumn moved from--text-secondaryto--muted-color. Only three tones clear each column background, soon holdanddonenow share#586f7c. They sit at opposite ends of the board and each column is labelled in text - Terminal accent, warning and success all resolve to
#b8dbd9for the same reason; terminal error takes#f4f4f9
- A single hue family cannot carry red-means-danger.
-
Legacy hex values removed everywhere
theme-colormetas inBaseHead.astroare#f4f4f9/#000000var(--token, #fallback)fallbacks inkanban.astroandTerminalHero.astroupdated to palette values- The modal backdrop scrim
rgba(0, 0, 0, 0.7)was kept — it is a scrim, not a palette colour
Contrast was recomputed rather than assumed, then checked in a headless browser against the computed styles. Everything meets 4.5:1 on the background it actually renders on. The thinnest margin is #586f7c on --bg-primary at 4.8:1 light / 5.0:1 dark, used for the ON HOLD and DONE column headings — which sit on .kanban-column-header’s --bg-primary, not on the tinted column body. Build passes: 9 pages.
2026-08-13 — Emoji Removed from UI
Three colour emoji were the only pictographs in a monochrome system. Removed for minimalism. The typographic glyphs →, ←, ↑ and ✓ were deliberately kept — they are plain text characters that take the current text colour and carry meaning.
-
Theme Toggle Is Now a Text Label
🌙/☀️replaced with two spans,.theme-label-dark(“Dark”) and.theme-label-light(“Light”)- The swap is pure CSS, keyed off
[data-theme]on<html>. The oldupdateButtonIcon()JS helper and both its call sites were deleted - CSS rather than JS because the inline
<head>script setsdata-themebefore first paint. Setting the label from the deferred module script instead would briefly show the wrong word on a dark-mode load .theme-togglepicked uptext-transform: uppercase,letter-spacing: 0.05emandfont-weight: 600to match the nav linksmin-width: 5.5rem(88px) stops the nav jumping between the 4-character “Dark” and 5-character “Light”. An initial 4.5rem was too small to bind — measured content widths are 79.9px and 82.5px, so the button still shifted 2.6px until the floor was raised
-
Search Icon Removed
- Dropped the decorative
🔍span from ProjectFilters.astro and ContentFilters.astro. It wasaria-hidden="true", and both inputs keep their placeholder andaria-describedbyhelp text - Deleted the now-unused
.search-iconrule .search-inputright padding went from2.5remback to1rem, since that space existed only to clear the icon
- Dropped the decorative
Verified in headless Chrome, both themes: the label swaps correctly in both directions on a real click, button width is a constant 88px across three reads, .nav-brand and .nav-links positions are unchanged between themes, and a DOM sweep for pictograph code points found none.
2026-08-12 — Design Restraint Pass, Accent Tokens & Contrast Fixes
Design: Restraint Pass
The soft-brutalist devices (3px border + hard shadow) were applied to every element, including things you only read. They now mark interactive elements only.
-
Flattened reading surfaces
- Card tags:
.tag,.project-tag,.content-tagwere three near-identical rules; merged into one grouped selector. Now quiet uppercase letterspaced text in--text-secondary, no border or background .metric: dropped border and background tint; numeral plus label only.health-card: dropped border, shadow and hover transform (it is not clickable). Removes one of three nested border levels on/site-health- Panels
.site-health,.project-filters,.content-filters,.active-filters: dropped 3px border and hard shadow, kept the--bg-subtletint - Kept border + shadow on
.btn,.card,.terminal,.search-input,.theme-toggle,.social-link,.scroll-to-top,.tag-filter,.badge-new,.section-label
- Card tags:
-
Unified Hover Lift
- Was four different values (1px, 2px, 4px) across three shadow sizes
- Now two tiers: controls lift 2px (
--shadow-small4px → 6px), cards lift 4px (--shadow-standard6px → 10px) - Added
:activereturn-to-rest on.theme-toggleand.scroll-to-top, matching.btn
-
Calmer Link Hover
- Base
a:hoverinverted the full background on every link, including prose inside diary and fanfic entries - Now thickens the underline.
.nav-linkand.nav-brandgot explicit hover rules to keep the inversion, since they previously relied on the base rule
- Base
-
Fluid Type Scale
h1/h2/h3now useclamp()and scale with viewport width- Removed the redundant heading overrides in the
max-width: 768pxblock, and the scopedfont-size: 3remon.page-header h1in diary.astro, fanfic.astro and projects.astro that was blocking the scale
-
Design Tokens Added
- Spacing scale
--space-1(0.25rem) through--space-8(4rem) --border-hairline,--shadow-small,--shadow-small-hover,--font-mono
- Spacing scale
Accent Color Tokens
--accent-color, --warning-color, --success-color and --error-color were referenced across the project but defined nowhere. Where an inline fallback existed, a color appeared; where it did not, the declaration was invalid and the text silently inherited white.
-
Two Families, Because There Are Two Backgrounds
- The page background flips with the theme;
.terminalis always dark. One family cannot serve both — a light-mode-safe dark blue is unreadable on the terminal, and a bright blue is 2.1:1 on the light page - Page accents (
--accent-color,--warning-color,--success-color,--error-color) have different values per theme. Light:#1565C0,#B45309,#2E7D32,#C62828. Dark:#64B5F6,#FFB74D,#81C784,#EF5350. All ≥ 4.8:1 - Terminal accents (
--terminal-accent,--terminal-warning,--terminal-success,--terminal-error) hold the same bright value in both themes. All ≥ 5.1:1 on both terminal backgrounds - Added
--terminal-text-muted#A0A0A0(6.8:1 on#1A1A1A, 7.6:1 on#0A0A0A)
- The page background flips with the theme;
-
Usages Repointed and Fallbacks Removed
- Terminal-context sites (
getTypeColor()in TerminalHero.astro, thehelpcommand output, the 404 error line,.terminal-prompt) now use--terminal-* - Page-context sites (
getStatusColor()and.modal-bodyin kanban.astro) use the page names - Deleted every inline
var(--x, #fallback)for these names — the variables are now always defined, so the fallbacks were a second source of truth .terminal-promptwas the last hardcoded hex (#4CAF50) outside the theme blocks
- Terminal-context sites (
Accessibility & Contrast Fixes
-
Undefined Variables Broke Terminal Colors
--text-color,--text-mutedand--card-bgwere used with no fallback in TerminalHero.astro, 404.astro and kanban.astro. An undefined custom property with no fallback makes the declaration invalid, socolorinherited white from.terminal— correct only by accident- All now point at the right token for the background they render on
-
Seven
#666Values Replaced.card-metaplus.page-descriptionon three list pages and four rules across the two[slug]detail pages#666on the dark background is 3.1:1, under the 4.5:1 minimum. Now--text-secondary
-
Invisible Code Blocks in Dark Mode
<pre>and<code>in diary and fanfic entries had a fixed#f5f5f5background. In dark mode that rendered near-white text on near-white. Now--bg-subtle
-
Missing Keyboard Focus Ring
.search-input:focussetoutline: noneand no:focus-visiblerule existed anywhere. Tabbing showed nothing- Added a site-wide
:focus-visibleoutline for links, buttons, inputs and[tabindex]
-
Faux-Bold Headings
h1–h6,.badge-new,.nav-brandand kanban’s.page-titleusedfont-weight: 900. Only Quicksand 400/600/700 are loaded and Quicksand has no 900 weight, so the browser synthesised it. All now700
-
Terminal Header Divider
.terminal-headerborder used--bg-subtle, which is#2A2A2Ain dark mode — 1.4:1 on the#0A0A0Aterminal, effectively invisible. Now--terminal-text-muted
-
404 Buttons Styled for the Wrong Background
.btn-secondarysits inside the terminal but used page tokens. Border and shadow were#2A2A2Aon a dark terminal in light mode. Now terminal tokens throughout, with an inverted hover
-
Stray Chroma Removed
#22c55eon.metric-value.highand.check-iconreplaced with--text-primary
Security
- Escaped Terminal Input
- The hero terminal echoed the typed command into
innerHTMLunescaped at two call sites, so typed HTML was parsed - Self-XSS only — input comes from the visitor’s own keyboard on a static page, with no persistence and no URL parameter, so it could not affect another visitor. Fixed regardless
- Added an
escapeHtml()helper applied to the echoed command and the “command not found” message.addHistoryLine()still accepts HTML, because every other caller passes intentional trusted markup
- The hero terminal echoed the typed command into
2026-08-12 — Documentation Accuracy & Deployment Fix
Documentation Updates
-
LICENSE File Added
- Added MIT license file with Copyright (c) 2026 SSani
- README badge now correctly links to existing ./LICENSE
-
Homepage Screenshot Added
- Added screenshot.png (1440×1250) — real capture of homepage in dark mode from production preview build
- README image reference now renders instead of broken image
-
Corrected Responsive Breakpoints Claim
- Updated from “480px and 768px” to “480px, 768px and 1024px”
- Matches actual media queries in src/styles/global.css
-
Corrected Footer Social Links Claim
- Updated from “GitHub, Twitter, Email” to “GitHub, Email”
- src/layouts/Layout.astro has no Twitter link; Twitter Cards SEO meta tags remain in BaseHead.astro
-
Fixed Project Structure Tree
- Added kanban.astro, src/pages/diary/rss.xml.ts, and src/pages/fanfic/rss.xml.ts
- Merged duplicated src/content/ node
- Added CLAUDE.md and CHANGELOG.md as top-level entries
-
Updated Documentation Table
- Replaced stale references to deleted context.md and four .windsurf/ files (removed in commits 2288d24 and 34a4125)
- Now lists CLAUDE.md and CHANGELOG.md
Deployment & Configuration
-
Unified Deployment Target to Coolify
- Updated CLAUDE.md Deployment section from “Netlify, Vercel, or similar” to Coolify
- Removed “Deploy to hosting” from Possible Future Work (deployment is completed)
- Moved src/content/kanban/publish-portfolio.md from status “plan” to “done”
-
Renamed Package Name
- Updated package.json “name” field from “developer-portfolio” to “ssani-homepage”
Build verified: 9 pages generated, no errors.
2026-07-07 — Bug Fixes & Astro 5 Content Layer Migration
Fixes
-
Terminal Recent Updates Links
- Fixed 404 errors in TerminalHero’s recent-updates feed for PROJECT and KANBAN items
- Added
getEntryHref()helper to correctly map entry types to their URLs: PROJECT →/projects, KANBAN →/kanban, DIARY →/diary/:slug, FANFIC →/fanfic/:slug - Previously generated non-existent
/project/<slug>and/kanban/<slug>routes
-
Fanfic RSS Feed Dates
- Fixed fanfic RSS feed using build-time
new Date()instead of actual content dates - Now sorts by and emits the real
datefield from fanfic frontmatter forpubDate - Removed stale comment claiming no date field existed in schema; newest entries appear first in feed
- Fixed fanfic RSS feed using build-time
-
Canonical Domain Unification
- Updated both
diary/rss.xml.tsandfanfic/rss.xml.tsto use unified canonical domainhttps://ssani.dev - Changed RSS site fallback from
https://ssani-main.github.ioto matchastro.config.mjs
- Updated both
Migration
-
Astro 5 Content Layer
- Migrated all four content collections to Astro 5 Content Layer API
- Updated
src/content/config.tsto useglob()loader fromastro/loaders - Swapped all
entry.slugreferences toentry.idacross components - Updated
kanban.astroto userender(entry)instead ofentry.render() - Verified all routes unchanged:
/diary/hello-world/,/fanfic/harunosukuna-ch1/, etc.
-
SiteHealth Component Relabel
- Relabeled hardcoded “Lighthouse Scores” card to “Lighthouse Targets”
- Reworded subtitle to clarify these are aspirational targets, not live measured metrics
- Prevents confusion with real performance monitoring
2026-02-25 — Interactive Terminal Hero & Kanban Board
-
Interactive Terminal Hero
- Upgraded the static terminal in
TerminalHero.astroto be fully interactive - Added a faux-input cursor and input field
- Users can type
helpto see commands,clearto hide all initial outputs, andkanbanto navigate to the hidden route
- Upgraded the static terminal in
-
Kanban Board
- Created a hidden
/kanbanroute to track work progress without cluttering the main navigation - Added a
kanbancontent schema withstatus(on hold, plan, work, done) - Grouped statuses into a 4-column brutalist responsive layout
- Added a clickable modal overlay to view the full markdown body of each card
- Created a hidden
2024-02-16 — Dynamic Homepage “NEW” Badge
- “NEW” Badge Indicator
- Added
isNewprop to topmost dynamically sorted section onindex.astro - Added conditional badge snippet to
ProjectsSection.astro,DiarySection.astro, andFanficSection.astro - Added pulsing soft-brutalist custom styling for
.badge-newtoglobal.css
- Added
2024-02-16 — Dynamic Section Ordering
- Dynamic Homepage Sections
- Queries all three content collections (projects, diary, fanfic)
- Determines the most recent update date for each collection
- Automatically reorders sections on homepage based on recency
- Most recently updated collection appears first
- Updates instantly when new content is added
2024-02-16 — Custom 404 Page
- Custom 404 Page
- Terminal-style error page matching site aesthetic
- Displays
findcommand with the requested URL showing “ERROR: File not found” - Shows large “404” error code in terminal format
- Navigation buttons: Home, Projects, Diary, Fanfic
- Styled with brutalist design system (hard shadows, borders)
- Responsive layout with centered terminal card
2024-02-16 — Terminal Hero Recent Updates
-
TerminalHero Component Enhanced
- Queries all content collections (projects, diary, fanfic) at build time
- Displays latest 5 updates in terminal-style
git log --onelineformat - Shows content type with color coding: PROJECT (blue), DIARY (green), FANFIC (orange)
- Relative timestamps: “today”, “yesterday”, “X days/weeks/months ago”
- Clickable links to individual content pages
- Gracefully handles long titles with truncation
-
Fanfic Schema Update
- Added required
datefield to fanfic collection schema - Updated all fanfic content files with dates:
debug-run.md: 2024-02-10silicon-souls-ch1.md: 2024-02-14silicon-souls-ch2.md: 2024-02-15
- Added required
2024-02-16 — Tags, Search & Filtering for All Content
-
Content Schema Updates
- Added
tags: string[](optional) to Projects collection - Added
tags: string[](optional) to Diary collection - Added
tags: string[](optional) to Fanfic collection - Updated
src/content/config.tswith new schema fields
- Added
-
ProjectFilters Component
- Real-time search by title, description, or tags
- Tag filter buttons (click to toggle)
- Active filters display with individual remove buttons
- “Clear all” functionality
- Results count indicator
- Client-side JavaScript filtering
-
ContentFilters Component
- Reusable component for Diary and Fanfic
- Same search and tag filtering capabilities
- Dynamic content type support
-
Content Updated with Tags
- Projects: API Gateway (Node.js, API, JWT), Code Sync (TypeScript, React), TaskFlow (PWA), API Gateway Pro (GraphQL)
- Diary: Legacy Systems (PostgreSQL, DevOps), Terminal Fonts (Typography), Why Astro (Performance)
- Fanfic: Debug Run (Sci-Fi), Silicon Souls (AI, Series)
-
List Pages Enhanced
projects.astro— Added ProjectFilters, tag display on cardsdiary.astro— Added ContentFilters, tag display on cardsfanfic.astro— Added ContentFilters, tag display on cards- All cards have
data-*attributes for client-side filtering
-
CSS Updates
.project-filtersand.content-filtersstyles.tag-filterbutton styles with hover/active states.search-inputwith brutalist styling.active-filtersdisplay styling.project-tagand.content-tagcard tag styling
2024-02-16 — List Pages and Detail Pages for Diary/Fanfic
-
List Pages (Show All Entries)
/projects—src/pages/projects.astro— Shows all projects in grid/diary—src/pages/diary.astro— Shows all diary entries in grid/fanfic—src/pages/fanfic.astro— Shows all fanfiction stories in grid- Consistent page header styling with title and description
- Back to home navigation on all list pages
- Brutalist styling with 3-column responsive grids
-
Detail Pages (Individual Entries)
- Diary detail:
src/pages/diary/[slug].astro— Full blog-style pages for each diary entry - Fanfic detail:
src/pages/fanfic/[slug].astro— Full story pages for each fanfic entry - Uses Astro’s
getStaticPaths()for dynamic route generation - Renders markdown content with proper typography styling
- Navigation: Back to list + Home links
- Styled content: code blocks, blockquotes, lists, horizontal rules
- Diary detail:
-
Homepage Section Updates
- All sections now show max 3 items on homepage
- Added “View All” buttons when more than 3 entries exist:
- “View All Projects →” linking to
/projects - “View All Entries →” linking to
/diary - “View All Stories →” linking to
/fanfic
- “View All Projects →” linking to
- Consistent styling with centered buttons
-
Routes Generated (10 pages total)
/(home)/projects(all projects)/diary(all diary entries)/diary/:slug(3 individual entries)/fanfic(all fanfiction)/fanfic/:slug(3 individual stories)
2024-02-16 — Initial Portfolio Setup
-
Astro Project Structure
- Full Astro configuration with TypeScript
- Content collections enabled with Zod schemas
- Static site generation setup
-
Monochrome Soft-Brutalist Design System
- Pure CSS implementation (no Tailwind)
- Custom CSS variables for consistency
- Hard shadows (6px/10px offsets)
- 3px borders with 4px radius
- Typography: Quicksand (headings) + Nunito (body)
-
Terminal-Style Hero Section
- Dark background terminal window
- Simulated command-line interface
- Blinking cursor animation
- Terminal dots (minimize/maximize/close style)
- Typewriter-style prompt aesthetic
-
Content Collections
projects/— Portfolio projects with GitHub/live URLsdiary/— Development diary entries with excerptsfanfic/— Creative writing with series support- Zod schemas for type-safe frontmatter
-
Section Components
- Projects section with 3-column responsive grid
- Diary section with date formatting
- Fanfic section with series/chapter support
- Section separators with centered labels
-
Card Components
- Hard shadow cards with hover lift effect
- Color inversion on hover (-4px, -4px translate)
- Consistent border and shadow styling
- Button inversion hover states
-
Sample Content
- 3 projects: TaskFlow Pro, API Gateway, Code Sync
- 3 diary entries: Astro migration, terminal fonts, legacy systems
- 3 fanfic stories: Silicon Souls chapters 1-2, Debug Run
Resolved Issues
2024-02-16 — Content Schema Validation Error
Symptom: Build failed with error: Invalid content entry for "fanfic" collection. ["chapterNumber"]: Expected type "number", received "null"
Root Cause: In debug-run.md, optional fields chapterNumber and seriesTitle were explicitly set to null. The Zod schema uses .optional() which expects either the correct type or the field to be omitted entirely — not null.
Solution: Removed the chapterNumber: null and seriesTitle: null lines from the frontmatter. Optional fields should be omitted, not set to null.
Files Affected:
/home/user98/Documents/SSani_Main/homepage/src/content/fanfic/debug-run.md— Removed null-valued optional fields
Prevention: When a schema marks a field as .optional(), either:
- Provide a valid value of the specified type
- Omit the field entirely
Never set optional fields to
nullunless the schema explicitly allows it with.nullable()
2026-07-07 — Terminal Recent-Update Links 404 for Projects & Kanban
Symptom: Clicking PROJECT or KANBAN items in the terminal hero’s “git log” feed resulted in 404 errors (non-existent URLs).
Root Cause: The TerminalHero.astro component built hrefs as /${type.toLowerCase()}/${slug}, assuming every content type has a /type/:slug detail route. However, projects and kanban have no individual detail pages — they only have list routes (/projects, /kanban), not per-entry pages.
Solution: Added a getEntryHref() helper function in TerminalHero.astro that maps each entry type to its correct URL: PROJECT → /projects, KANBAN → /kanban, DIARY → /diary/:slug, FANFIC → /fanfic/:slug.
Files Affected:
src/components/TerminalHero.astro— AddedgetEntryHref()helper and updated link generation logic
Prevention: When generating links to content entries, always check if the collection has individual detail pages before creating /:slug links. Only generate detail-page links for collections that have a [slug] route.
2026-07-07 — Fanfic RSS Feed Used Build-Time Dates
Symptom: Every fanfic RSS item had an identical pubDate that shifted with each build (matched the build timestamp, not content dates).
Root Cause: The fanfic/rss.xml.ts feed builder used pubDate: new Date() for all items and sorted by slug instead of date, ignoring the date field defined in the fanfic schema. A stale code comment incorrectly claimed no date field existed.
Solution: Updated feed generation to sort by and emit the actual fanfic.data.date field for each item’s pubDate, ensuring stable and accurate publication dates with newest entries first.
Files Affected:
src/pages/fanfic/rss.xml.ts— Changed to use content date field and sort newest-first
Prevention: Always map RSS pubDate to the content’s actual date field (from frontmatter). Never use new Date() or build-time timestamps for feed publication dates.