Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a1cf8ff
feat: redesign with new typography, /videos route, RSS, SEO
aanogueira May 5, 2026
62bf770
docs: add Minimal Adventurer theme design spec
aanogueira Jul 27, 2026
7faf6e4
docs: add Minimal Adventurer theme implementation plan
aanogueira Jul 27, 2026
0d60ea9
feat(nav): add themed lexicon config
aanogueira Jul 27, 2026
4d96b81
feat(nav): apply themed lexicon to nav and headings
aanogueira Jul 27, 2026
d53b88c
a11y(nav): hide decorative nav glyphs from screen readers
aanogueira Jul 27, 2026
9e3be6a
feat(ascii): add deterministic field math utils
aanogueira Jul 27, 2026
5ef2234
feat(ascii): add burst store for logo interaction
aanogueira Jul 27, 2026
905a0a6
feat(ascii): add AsciiField background component
aanogueira Jul 27, 2026
2d638fc
feat(ascii): theme-aware ink for light and dark
aanogueira Jul 27, 2026
9bec3b7
feat(ascii): render background across all routes
aanogueira Jul 27, 2026
3675e56
fix(ascii): repaint static frame on resize under reduced motion
aanogueira Jul 27, 2026
251d1b8
refactor(logo): self-drawing inline SVG, remove lottie from NavLogo
aanogueira Jul 27, 2026
6683c4c
feat(logo): fire ascii burst on header logo click
aanogueira Jul 27, 2026
ce8ae48
feat(home): use NavLogo, themed wordmark and tagline
aanogueira Jul 27, 2026
4ecfb56
chore(deps): remove lottie-web and animation JSON assets
aanogueira Jul 27, 2026
9bda2fe
refactor(logo): share toggleThemeWithBurst handler and fix logo viewBox
aanogueira Jul 27, 2026
f883591
feat(type): add Pixelify Sans display font for wordmark and hero
aanogueira Jul 27, 2026
25da487
feat(projects): add status rune mapping
aanogueira Jul 27, 2026
8756260
feat(projects): optional status rune on project cards
aanogueira Jul 27, 2026
35cd90e
feat(blog): add reading-time util
aanogueira Jul 27, 2026
5e22112
feat(blog): show reading time as min journey
aanogueira Jul 27, 2026
64154ef
perf(blog): compute reading time in server load to keep raw markdown …
aanogueira Jul 27, 2026
3175e4d
feat(ui): add SectionLabel divider component
aanogueira Jul 27, 2026
050ae58
feat(theme): themed footer and 404 microcopy
aanogueira Jul 27, 2026
5d2922e
chore: remove orphaned lottie-residue logo components
aanogueira Jul 27, 2026
bbc5cd2
chore(deps): sync bun.lock after removing lottie-web
aanogueira Jul 27, 2026
70abb07
style: format theme changes
aanogueira Jul 27, 2026
6af0add
chore: drop unused SectionLabel; hide decorative footer glyph from AT
aanogueira Jul 27, 2026
e6c1293
a11y(theme): hide decorative tagline and status-rune glyphs from scre…
aanogueira Jul 27, 2026
833f047
feat(logo): animate mark with a tracing comet around the loop
aanogueira Jul 27, 2026
8e04cf7
fix(layout): sticky footer + nav loading bar + smoother page transitions
aanogueira Jul 27, 2026
0cf2c08
fix(blog): move reading time to a compact muted line under the descri…
aanogueira Jul 27, 2026
7234d9f
fix(ui): stack blog rows to tighten spacing; scale header nav on smal…
aanogueira Jul 27, 2026
23243c5
fix(logo): keep the mark's trace animating even under reduced-motion
aanogueira Jul 27, 2026
75ce7ff
feat(footer): quick links, GitHub/RSS social, and copyright
aanogueira Jul 27, 2026
c0bb27b
feat(home): entrance reveal β€” background fade-in, centre burst, stagg…
aanogueira Jul 27, 2026
3460c38
fix(blog): restore date-on-left layout (refact0r-style), reading time…
aanogueira Jul 27, 2026
d7e7614
fix(footer): drop nav links, stack centered on small screens
aanogueira Jul 27, 2026
91791e9
fix(blog): remove reading time from list (uniform dates), show it on …
aanogueira Jul 27, 2026
2f724f7
fix(footer): center content to page width, balance as one row
aanogueira Jul 27, 2026
15e9da3
feat(motion): smoother page transitions (quintOut) and richer home en…
aanogueira Jul 27, 2026
dce3778
fix(header): prevent nav overflow β€” drop path hints, no-wrap wordmark…
aanogueira Jul 27, 2026
4b68392
feat(header): collapse nav into a dropdown menu below 1080px (single-…
aanogueira Jul 27, 2026
81ebe29
feat(about): drop Medium/dev.to, single theme-aware CV link, comment …
aanogueira Jul 27, 2026
8e1ab25
feat(logo): clearer continuous self-drawing loop animation
aanogueira Jul 27, 2026
2671154
feat(logo): animate the teal/amber accent strokes in sync with the loop
aanogueira Jul 27, 2026
6518edb
fix(copy): plain About/Contact nav + headings
aanogueira Jul 27, 2026
232d586
fix(copy): simpler home tagline with colored em-dash accent
aanogueira Jul 27, 2026
2afe721
fix(copy): footer credit β€” Built by Andre Nogueira (aanogueira.dev)
aanogueira Jul 27, 2026
9b34480
feat(logo): faster continuous flow, theme-adaptive base, accents join…
aanogueira Jul 27, 2026
920206e
feat(logo): high-contrast black/white loop base (theme-adaptive)
aanogueira Jul 27, 2026
6a0b184
feat(og): themed 1200x630 social share image + Playwright regenerator
aanogueira Jul 28, 2026
3ef0b6c
feat(type): retire 8-bit display font for Fira Mono 500
aanogueira Jul 30, 2026
0fe9a5b
fix(build): declare @iconify-json/ph so CI can resolve ph icons
aanogueira Jul 30, 2026
4e55808
test(e2e): follow renamed nav labels and the collapsed mobile nav
aanogueira Jul 30, 2026
f92aa38
fix(docker): patch Alpine CVEs and document the unfixable Caddy ones
aanogueira Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,10 @@ lighthouse-report.json
# Temporary folders
tmp/
temp/

# Local design references / saved web pages
docs/preview/
.superpowers/

.claude/
.serena/
38 changes: 38 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,41 @@
# Not exploitable in our deployment: the blog serves static files only;
# the JOSE code path is not reached at runtime.
CVE-2026-34986

# ---------------------------------------------------------------------------
# Compiled into the caddy binary shipped by caddy:alpine (currently 2.11.4,
# built with Go 1.26.3). We cannot fix these ourselves: the tag already
# resolves to the newest published image, and every fix below needs a Go
# toolchain / module version that upstream has not rebuilt against yet.
# Alpine *package* CVEs are NOT ignored here - `apk upgrade` in the Dockerfile
# fixes those for real.
#
# Re-check when Caddy publishes an image built on Go >= 1.26.4; drop whichever
# of these it clears.

# golang.org/x/text v0.37.0 -> 0.39.0. norm.Iter infinite loop (DoS) on
# malformed input. Reached only for text Caddy normalises itself; we serve
# pre-built static files and never feed it attacker-controlled text.
CVE-2026-56852

# google.golang.org/grpc v1.81.0 -> 1.82.1. xDS RBAC and HTTP/2 issues.
# We run no gRPC listener and no xDS control plane; this code is dead weight
# linked into the binary.
GHSA-hrxh-6v49-42gf

# Go stdlib crypto/x509 -> 1.26.4. DoS via excessive processing of DNS names
# in a certificate. Only reachable while parsing a peer certificate; the blog
# terminates TLS for inbound clients and initiates no outbound mTLS.
CVE-2026-27145

# Go stdlib os -> 1.26.5. os.Root symlink following allows directory
# traversal. The site root is baked into the image at build time from our own
# `build/` output and is never user-writable, so there is no path for an
# attacker-planted symlink to appear under /usr/share/caddy.
CVE-2026-39822

# Go stdlib mime -> 1.26.4. DoS via maliciously-crafted MIME header. Caddy
# parses request headers, so this is the least remote of the set - accepted as
# a bounded DoS on a static, stateless, CDN-frontable site with no data at
# risk. Revisit first when a rebuilt image lands.
CVE-2026-42504
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@

FROM caddy:alpine

# Pick up Alpine security fixes that landed after the base image was built.
# caddy:alpine is rebuilt infrequently, so its apk packages (curl, c-ares, ...)
# lag behind the fixed versions already published for the same Alpine release.
RUN apk --no-cache upgrade

# Copy pre-built static site from build/ directory
COPY build /usr/share/caddy
COPY Caddyfile /etc/caddy/Caddyfile
Expand Down
576 changes: 203 additions & 373 deletions bun.lock

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions docs/design/2026-07-27-tech-quests-theme-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Tech Quests β€” "Minimal Adventurer" Theme Design

**Date:** 2026-07-27
**Status:** Approved (design) β€” pending implementation plan
**Branch:** `redesign/2026-05`

## Summary

Give `techquests.dev` a cohesive, reusable brand identity: **"Technological Quests"** β€” an
Elder-Scrolls / medieval quest-log framing rendered through a modern terminal / 8-bit hacker
lens. The identity is deliberately **minimal**: themed and a little witty, but quiet and
highly readable.

The guiding principle: **the theme lives in the framing, not the content.** Body copy stays
clean and legible. The medieval↔hacker identity is carried by four cheap, reusable layers:

1. **Lexicon** β€” Quests / Chronicles / Sagas / etc. (labels only; URLs unchanged)
2. **Ornament** β€” ASCII / box-drawing glyphs that read as *both* a terminal UI *and* an
illuminated-manuscript border (`❖ β—ˆ ⟳ βœ“ ☾ β–Έ βš” ❯`)
3. **Accents & type** β€” a pixel display font for the wordmark/hero; existing accent colors
4. **Atmosphere** β€” a faint animated ASCII "contour map" behind all content

This was validated interactively via live mockups (three ASCII styles, then three theme
intensities, then a refined "AΓ—B mix"). The chosen direction is the minimal mix of intensity
level A (Flavored) and B (Adventurer's Terminal).

## Design language

### Typography
- **Body:** `Inter Variable` (unchanged) β€” readability is non-negotiable.
- **Headings / code / nav:** `Fira Mono` (unchanged).
- **Display (new):** `Pixelify Sans` β€” a *legible* pixel font. Used **only** for the wordmark
and the homepage hero `h1`. Not used for body, headings, or long text.
- Load it following the existing font pattern (`src/lib/assets/fonts/*.css`, fontsource
woff2 via jsdelivr), imported in `+layout.svelte`.
- Add `$font-family-display: 'Pixelify Sans', sans-serif;` to `src/variables.scss`.

### Color
Reuse the existing palette in `src/app.scss` / `src/variables.scss` verbatim. It already fits:
- **Dark theme** β†’ "dungeon terminal at night" (cool blue-grey + teal/amber/salmon accents).
- **Light theme** β†’ "parchment scroll" (the warm `hsl(30–45, 10%, ...)` light palette).
- ASCII background ink: dark theme uses the teal `--green`; light theme must use a **dark ink**
variant (a low-lightness warm/neutral tone) so it reads on parchment β€” define per theme.

### Glyph vocabulary
`❖` (section marker / tag), `β—ˆ` (card corner), `⟳ βœ“ ☾` (project state), `β–Έ` (accent/tagline),
`βš”` (footer flavor), `❯` (call-to-action). Chosen because each belongs to both the
box-drawing/terminal world and the manuscript-ornament world.

## Components

### `AsciiField.svelte` (new)
Canvas-based generative ASCII "contour map" background (technique modeled on refact0r's
`AsciiField`).
- **Rendering:** `<canvas>`, 2D context, `alpha: true`; scale to `devicePixelRatio` (cap 2).
- **Pattern:** grid of cells; per-cell signal = summed sine waves + value-noise domain warp;
converted to contour lines via `abs(sin(signal * k))`; cells render only above a threshold,
as `.` `:` `+` chosen by a per-cell hash. Faint alpha.
- **Motion:** continuous slow drift via `requestAnimationFrame`.
- **Interactivity:** eases toward pointer position; cells near the cursor brighten (ripple).
- **Burst:** exposes a mechanism to trigger an expanding ring ripple (fired by the logo click β€”
see `NavLogo`). Implement via a shared Svelte store or a `CustomEvent` on `window`.
- **Placement:** rendered once in `+layout.svelte`; `position: fixed; inset: 0; z-index: -1;
pointer-events: none; aria-hidden="true"`.
- **Accessibility / perf:**
- `prefers-reduced-motion: reduce` β†’ render a single static frame, stop the rAF loop, disable
bursts and pointer reactivity.
- Throttle idle frames; skip sub-threshold cells; no-JS β†’ background simply absent.
- **Homepage:** may use a slightly denser/brighter variant behind the hero (optional prop).

### `NavLogo.svelte` (rebuilt)
Replace the `lottie-web` implementation with a **self-drawing inline SVG**.
- Use the existing infinity/figure-8 path (already present in `static/favicon.svg`, drawn in
the palette: teal `#27607b` = `--green`, amber `#f3b366` = `--yellow`).
- Animate with CSS `stroke-dasharray` / `stroke-dashoffset` (draw-on loop). Colors bound to CSS
variables so it themes automatically (no separate dark/light JSON).
- **Remove** the `lottie-web` dependency and the two `pfpin-{dark,light}.json` (~56 KB) assets.
- On click β†’ toggle theme (current behavior) **and** fire the `AsciiField` burst.
- Under `prefers-reduced-motion`: draw the mark once, no looping animation.
- Used by both the header (`+layout.svelte`) and the homepage hero (`src/routes/+page.svelte`),
replacing the current `pfpstart` Lottie usage there too.

### Wordmark & hero
- Wordmark (`techquests.dev`) and homepage hero `h1` (`Tech Quests`) β†’ `$font-family-display`
(Pixelify Sans), with `.dev` / accent in `--green`.
- Hero tagline β†’ Fira Mono, muted, with a witty themed line (see Microcopy).

### `SectionLabel.svelte` (new, small)
A reusable section divider: `❖ <Label> ─────────` (leading green glyph + label + hairline rule).
Used above section groupings (e.g. "Quests", "Chronicles").

### Project cards
- Hairline border + a single quiet corner glyph `β—ˆ` (brightens to `--green` on hover).
- **Optional state rune**, driven by a new optional frontmatter field `status`:
- `evolving` β†’ `⟳ evolving`, `shipped` β†’ `βœ“ shipped`, `archived` β†’ `☾ archived`
- Rendered muted (`--txt-3`), right-aligned. **Omitted entirely when `status` is absent** β€”
honoring that much work is perpetually evolving and shouldn't be force-labeled.
- Tags prefixed with `❖` in `--green`.

### Blog / video rows
- Reading time relabeled from "N min read" β†’ **"~N min journey"** (accent color), reusing the
existing reading-time helper (currently referenced by `src/lib/utils/blog-utils.test.js`; if
the source helper is missing/inlined, formalize it in `src/lib/utils/` and reuse it).
- Date shown muted alongside.

### Footer
- Themed, monospace, muted: a witty left line + a `send a raven ❯` call-to-action on the right.
- The witty line may rotate from a small hardcoded list (deterministic pick is fine; no need
for randomness that breaks SSR hydration).

## Lexicon (labels only β€” routes/URLs are NOT changed)

| Route (unchanged) | Current label | New label |
|-------------------|---------------|------------------|
| `/projects` | Projects | **Quests** |
| `/blog` | Blog | **Chronicles** |
| `/videos` | Videos | **Sagas** |
| `/about` | About | **The Adventurer** |
| `/contact` | Contact | **Send a Raven** |

- Nav shows the themed label with a faint literal path hint (e.g. `Quests` + muted `/projects`)
for discoverability and SEO clarity.
- Update labels in `src/routes/+layout.svelte` and `src/routes/+page.svelte` nav arrays, plus
section headings across the route pages.
- **Do not** change route directories, slugs, RSS/sitemap URLs, or `<link>`/canonical URLs.

### Microcopy (witty, sparing)
- Homepage tagline (themed variant of the current one), e.g. "field notes of an SRE, logged
from the tech wilds β€” no dragons, just YAML".
- 404 page β†’ "you've wandered off the map".
- Empty states, reading time ("min journey"), footer flavor.
- Keep it light β€” one witty touch per surface, never at the expense of clarity.

## Scope & phasing

**Phase 1 (this pass):** everything in *Components*, *Lexicon*, and *Microcopy* above, applied
across all existing routes (home, projects, blog, videos, about, contact, 404).

**Deferred (future branding passes β€” explicitly out of scope now):**
- Pixel-art / ouroboros redraw of the logo mark.
- Themed OG / social share images and favicons.
- Video intro/outro bumper.
- Pixel-art section icons.

## Accessibility & performance

- **Reduced motion:** ASCII background freezes to one static frame; logo draws once and stops;
no bursts. (The repo already has `prefers-reduced-motion` guards in `app.scss`.)
- **Decorative semantics:** background canvas is `aria-hidden`; ornament glyphs are decorative
(CSS `::before`/`::after` or `aria-hidden`) so screen readers read clean labels.
- **Contrast:** verify themed accent text (amber/green microcopy) meets contrast on both
themes; body text contrast is unchanged.
- **Bundle:** net change is expected to be **smaller** β€” removing `lottie-web` + 56 KB of JSON
outweighs adding one canvas component and one pixel webfont (subset/`font-display: swap`).
- **No-JS / SSR:** background is absent without JS; all content and navigation work unchanged;
avoid non-deterministic values (`Math.random`, `Date.now`) in SSR-rendered markup to prevent
hydration mismatches.

## Files touched (anticipated)

- `src/variables.scss` β€” add `$font-family-display`.
- `src/app.scss` β€” wordmark/hero display font, glyph/accent styles, light-mode ASCII ink var.
- `src/lib/assets/fonts/pixelify-sans.css` (new) + import in `+layout.svelte`.
- `src/lib/components/AsciiField.svelte` (new).
- `src/lib/components/NavLogo.svelte` (rebuilt; drop lottie).
- `src/lib/components/SectionLabel.svelte` (new).
- `src/routes/+layout.svelte` β€” render `AsciiField`, themed nav, footer.
- `src/routes/+page.svelte` β€” hero wordmark/tagline, themed nav, swap `pfpstart` Lottie.
- Route pages (`projects`, `blog`, `videos`, `about`, `contact`, error/404) β€” labels, section
labels, project state rune, "min journey".
- `src/content/projects/*/+*.md` β€” optional `status:` frontmatter where meaningful.
- `package.json` β€” remove `lottie-web`; delete `src/lib/assets/pfpin-*.json`.
- `src/lib/utils/` β€” formalize/relabel reading-time helper if needed.

## Non-goals / constraints

- No change to URLs, routing, RSS, or sitemap.
- No reduction in body-text readability; pixel font stays out of long-form content.
- No heavy "game UI" chrome (HUD panels, XP bars, level badges) β€” that was intensity level C,
rejected in favor of the minimal mix.
Loading