diff --git a/generate.scm b/generate.scm
index cab16fc..c5d37c8 100644
--- a/generate.scm
+++ b/generate.scm
@@ -40,8 +40,8 @@
,(date-range (field tc 'date-start) (field tc 'date-end)))
(div (@ (class "title-meta"))
,(field tc 'company) " • " ,(field tc 'location))
- (div (@ (class "title-blurb"))
- ,(bullets (field tc 'blurb)))))
+ (p (@ (class "title-blurb"))
+ ,(string-join (field tc 'blurb) " "))))
;; Emit a title card followed by its project cards as a flat list of siblings
(define (render-title-flat tc)
@@ -130,6 +130,43 @@
(list `(h2 (@ (class "sec-heading")) ,cur-label))
cur-items)))))))
+;; ─── Column distribution ───────────────────────────────────────────────────────
+;; Wrap all items in two independent divs, splitting at the midpoint.
+;; Because each col is a separate block formatting context, expanding a card
+;; pushes siblings down within that col only — no cross-column movement,
+;; no hover loop.
+;;
+;; num-columns: change to 3 if your resume gets too long, and update the
+;; CSS grid-template-columns to match.
+
+(define num-columns 3)
+
+(define (heading? item)
+ (and (pair? item) (eq? (car item) 'h2)))
+
+(define (list-ref* lst i)
+ (if (= i 0) (car lst) (list-ref* (cdr lst) (- i 1))))
+
+(define (split-into-columns items n)
+ (let* ((total (length items))
+ (chunk (inexact->exact (ceiling (/ total n)))))
+ (let loop ((i 0) (start 0) (remaining n) (acc '()))
+ (if (= remaining 1)
+ (reverse (cons `(div (@ (class "col")) ,@(list-tail items start)) acc))
+ (let* ((raw-end (min (+ start chunk) total))
+ ;; Walk backwards from the cut point: if the last item
+ ;; in this slice is a heading (orphaned at col bottom),
+ ;; move it to the next column.
+ (end (let nudge ((e raw-end))
+ (if (and (> e (+ start 1))
+ (heading? (list-ref* items (- e 1))))
+ (nudge (- e 1))
+ e))))
+ (loop (+ i 1) end (- remaining 1)
+ (cons `(div (@ (class "col"))
+ ,@(list-tail (list-head items end) start))
+ acc)))))))
+
;; ─── Front matter ─────────────────────────────────────────────────────────────
(define (render-front-matter fm)
@@ -143,40 +180,19 @@
(define (render-resume res)
(let* ((fm (field res 'front-matter))
- (items (sections->flat-items (field res 'sections))))
+ (items (sections->flat-items (field res 'sections)))
+ (cols (split-into-columns items num-columns)))
`(*TOP*
(html (@ (lang "en"))
(head
(meta (@ (charset "UTF-8")))
(meta (@ (name "viewport") (content "width=device-width, initial-scale=1")))
(title ,(field fm 'name))
- (link (@ (rel "stylesheet") (href "style.css")))
- ;; Measure the front-matter height after every resize/zoom and
- ;; set .columns height to exactly fill the remaining space.
- (script "
-function setColHeight() {
- var fm = document.querySelector('.front-matter');
- var col = document.querySelector('.columns');
- if (!fm || !col) return;
- var used = fm.getBoundingClientRect().height
- + parseInt(getComputedStyle(document.body).paddingTop) * 2;
- col.style.height = (window.innerHeight - used) + 'px';
-}
-window.addEventListener('load', setColHeight);
-window.addEventListener('resize', setColHeight);
-// zoom via ctrl+/- fires resize in most browsers, but also poll as fallback
-var _lastW = window.innerWidth, _lastH = window.innerHeight;
-setInterval(function() {
- if (window.innerWidth !== _lastW || window.innerHeight !== _lastH) {
- _lastW = window.innerWidth; _lastH = window.innerHeight;
- setColHeight();
- }
-}, 200);
-"))
+ (link (@ (rel "stylesheet") (href "style.css"))))
(body
,(render-front-matter fm)
(main (@ (class "columns"))
- ,@items))))))
+ ,@cols))))))
(display "\n")
(sxml->xml (render-resume resume))
diff --git a/resume.html b/resume.html
index c93a210..a1773dc 100644
--- a/resume.html
+++ b/resume.html
@@ -1,21 +1,2 @@
-
Jane Doe Education The University of Georgia • Athens, GA
Graduated December 2016 ICPC Participant and Meeting Coordinator Work Experience Acme Corp • Remote
Led a team of 4 engineers on the core platform. Drove adoption of internal developer tooling. Reviewed architecture proposals and mentored junior engineers. Built a Raft-based distributed config store used by 40+ services. Designed the consensus layer using a custom Raft implementation in Go. Added multi-region replication with conflict-free merge semantics. Reduced config propagation time from ~30 s to <500 ms p99. Go Raft gRPC Kubernetes Prometheus Unified internal service catalogue and runbook search into one portal. Indexed 300+ services via OpenAPI scraping and custom metadata YAML. Built a fuzzy-search backend in Go backed by Bleve. Reduced mean time-to-runbook from ~8 min to <30 s. Acme Corp • Remote
Owned the authentication service end-to-end. Reduced API latency by 35% through caching improvements. Rewrote legacy PHP auth service in Go; cut p99 latency from 800 ms to 40 ms. Migrated 12 M users with zero downtime using a dual-write/dark-read strategy. Introduced PKCE OAuth 2.0 flow replacing home-grown session tokens. Added comprehensive integration test suite with 92% coverage. Go PostgreSQL Redis OAuth2 Docker Implemented token-bucket rate limiting across all public API endpoints. Built as a gRPC interceptor; config hot-reloaded from the config service. Reduced abusive traffic incidents by 80% in the first quarter after launch. Added per-tenant override rules managed via an internal admin UI. Startup XYZ • Athens, GA
Built product features across the React frontend and Node.js backend. Maintained CI/CD pipelines and AWS infrastructure. Collaborated directly with design and product teams. Delivered a live analytics dashboard processing 50k events/s. Replaced polling with WebSocket fan-out, cutting server load by 60%. Implemented client-side time-series aggregation using D3.js. Added user-configurable alert thresholds stored in IndexedDB. React Node.js WebSockets D3.js AWS Projects A resume generator written in Scheme that produces HTML via SXML. Defined a small DSL for resume data in resume.scm. generate.scm walks the data and emits SXML serialised to HTML. CSS handles all layout including the two-column split. A 600-line Lisp interpreter in Python supporting tail-call optimisation. Implemented environments as linked frames, closures, and a macro expander. Added a REPL with readline history and pretty-printing. Interests Open-Source Regular contributor to Guile and Chicken Scheme ecosystem libraries.
Rock Climbing Sport climber; currently projecting 5.12a outdoors.
Technical Writing Blog at janedoe.dev — compilers, distributed systems, functional programming.
Favorite Software Guile Scheme Emacs Neovim Nix Guix Tailscale WireGuard mpv zathura tmux Alacritty Syncthing Bspwm Rofi
+Jane Doe Education The University of Georgia • Athens, GA
Graduated December 2016 ICPC Participant and Meeting Coordinator Work Experience Startup XYZ • Athens, GA
Built product features across the React frontend and Node.js backend, maintained CI/CD pipelines and AWS infrastructure, and collaborated directly with design and product teams.
Delivered a live analytics dashboard processing 50k events/s. Replaced polling with WebSocket fan-out, cutting server load by 60%. Implemented client-side time-series aggregation using D3.js. Added user-configurable alert thresholds stored in IndexedDB. React Node.js WebSockets D3.js AWS Acme Corp • Remote
Led a team of 4 engineers on the core platform, driving adoption of internal developer tooling and mentoring junior engineers through architecture proposals and code review.
Built a Raft-based distributed config store used by 40+ services. Designed the consensus layer using a custom Raft implementation in Go. Added multi-region replication with conflict-free merge semantics. Reduced config propagation time from ~30 s to <500 ms p99. Go Raft gRPC Kubernetes Prometheus Unified internal service catalogue and runbook search into one portal. Indexed 300+ services via OpenAPI scraping and custom metadata YAML. Built a fuzzy-search backend in Go backed by Bleve. Reduced mean time-to-runbook from ~8 min to <30 s. Acme Corp • Remote
Owned the authentication service end-to-end, reducing API latency by 35% through targeted caching improvements.
Rewrote legacy PHP auth service in Go; cut p99 latency from 800 ms to 40 ms. Migrated 12 M users with zero downtime using a dual-write/dark-read strategy. Introduced PKCE OAuth 2.0 flow replacing home-grown session tokens. Added comprehensive integration test suite with 92% coverage. Go PostgreSQL Redis OAuth2 Docker Implemented token-bucket rate limiting across all public API endpoints. Built as a gRPC interceptor; config hot-reloaded from the config service. Reduced abusive traffic incidents by 80% in the first quarter after launch. Added per-tenant override rules managed via an internal admin UI. Projects A resume generator written in Scheme that produces HTML via SXML. Defined a small DSL for resume data in resume.scm. generate.scm walks the data and emits SXML serialised to HTML. CSS handles all layout including the two-column split. A 600-line Lisp interpreter in Python supporting tail-call optimisation. Implemented environments as linked frames, closures, and a macro expander. Added a REPL with readline history and pretty-printing. Interests Open-Source Regular contributor to Guile and Chicken Scheme ecosystem libraries.
Rock Climbing Sport climber; currently projecting 5.12a outdoors.
Technical Writing Blog at janedoe.dev — compilers, distributed systems, functional programming.
Favorite Software Guile Scheme Emacs Neovim Nix Guix Tailscale WireGuard mpv zathura tmux Alacritty Syncthing Bspwm Rofi
diff --git a/resume.scm b/resume.scm
index 1331a45..c6136cf 100644
--- a/resume.scm
+++ b/resume.scm
@@ -91,9 +91,7 @@
(list
(make-title
"Senior Software Engineer" "Acme Corp" "Remote" "Jun 2022" "Present"
- '("Led a team of 4 engineers on the core platform."
- "Drove adoption of internal developer tooling."
- "Reviewed architecture proposals and mentored junior engineers.")
+ '("Led a team of 4 engineers on the core platform, driving adoption of internal developer tooling and mentoring junior engineers through architecture proposals and code review.")
(list
(make-project
"Distributed Config Service" "Mar 2023" "Present"
@@ -111,8 +109,7 @@
'("Go" "Bleve" "React" "OpenAPI"))))
(make-title
"Software Engineer II" "Acme Corp" "Remote" "Jan 2020" "May 2022"
- '("Owned the authentication service end-to-end."
- "Reduced API latency by 35% through caching improvements.")
+ '("Owned the authentication service end-to-end, reducing API latency by 35% through targeted caching improvements.")
(list
(make-project
"Auth Service Rewrite" "Jan 2020" "Dec 2020"
@@ -135,9 +132,7 @@
(list
(make-title
"Full-Stack Engineer" "Startup XYZ" "Athens, GA" "Jul 2017" "Dec 2019"
- '("Built product features across the React frontend and Node.js backend."
- "Maintained CI/CD pipelines and AWS infrastructure."
- "Collaborated directly with design and product teams.")
+ '("Built product features across the React frontend and Node.js backend, maintained CI/CD pipelines and AWS infrastructure, and collaborated directly with design and product teams.")
(list
(make-project
"Real-Time Dashboard" "Feb 2019" "Dec 2019"
@@ -197,8 +192,8 @@
(list 'resume
(cons 'front-matter my-front-matter)
(cons 'sections (list my-education
- job-acme
job-startup
+ job-acme
my-hobby-projects
my-interests
my-favorite-software))))
diff --git a/style.css b/style.css
index 0689a36..cf830d8 100644
--- a/style.css
+++ b/style.css
@@ -65,31 +65,26 @@ a { color: var(--accent); text-decoration: none; }
}
/* ─── Column layout ──────────────────────────────────────────────────────────── */
+/* Two independent .col divs. Expanding a card pushes siblings within
+ its own col only — cards never shift between columns, no hover loop.
+ To go to 3 cols: set num-columns=3 in generate.scm, regenerate,
+ and change repeat(2,...) to repeat(3,...) below. */
.columns {
- column-width: 22em;
- column-gap: 2em;
- column-fill: balance;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 0 2em;
+ align-items: start;
}
-/* Prevent any card from being split across a column boundary */
-.sec-heading,
-.title-card,
-.project-card,
-.education-card,
-.interests,
-.favorite-software {
- break-inside: avoid;
-}
+.col { min-width: 0; }
-/* Keep each section heading attached to whatever follows it */
-.sec-heading {
- break-after: avoid;
+@media (max-width: 600px) {
+ .columns { grid-template-columns: 1fr; }
}
/* ─── Section headings ───────────────────────────────────────────────────────── */
.sec-heading {
- font-size: 0.7rem;
- font-weight: 800;
+ font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
@@ -114,8 +109,7 @@ a { color: var(--accent); text-decoration: none; }
.project-name,
.edu-degree {
font-weight: 700;
- font-size: 0.88rem;
-}
+ font-size: 0.78rem;}
.date-range {
font-size: 0.72rem;
@@ -138,14 +132,12 @@ a { color: var(--accent); text-decoration: none; }
margin-bottom: 4px;
}
-.title-blurb ul { padding-left: 12px; }
-.title-blurb li {
- font-size: 0.82rem;
+p.title-blurb {
+ font-size: 0.68rem;
line-height: 1.5;
- margin-bottom: 1px;
color: rgba(255,255,255,0.85);
+ margin-top: 3px;
}
-.title-blurb li::before { content: "- "; color: var(--muted); }
/* ─── Project card ───────────────────────────────────────────────────────────── */
.project-card {
@@ -173,15 +165,15 @@ a { color: var(--accent); text-decoration: none; }
opacity: 0.7;
}
-.project-name { font-size: 0.83rem; }
+.project-name { font-size: 0.72rem; }
.project-summary ul { padding-left: 12px; margin-top: 2px; }
.project-summary li {
- font-size: 0.8rem;
+ font-size: 0.68rem;
line-height: 1.5;
color: rgba(255,255,255,0.85);
}
-.project-summary li::before { content: "- "; color: var(--muted); }
+.project-summary li::before { content: "• "; color: var(--muted); }
.project-details {
max-height: 0;
@@ -196,12 +188,12 @@ a { color: var(--accent); text-decoration: none; }
}
.project-details ul { padding-left: 12px; margin-top: 4px; }
.project-details li {
- font-size: 0.78rem;
+ font-size: 0.68rem;
color: var(--muted);
line-height: 1.5;
margin-bottom: 1px;
}
-.project-details li::before { content: "- "; }
+.project-details li::before { content: "• "; }
.tech-list {
display: flex;
@@ -233,7 +225,7 @@ a { color: var(--accent); text-decoration: none; }
margin-bottom: 1px;
color: rgba(255,255,255,0.85);
}
-.edu-bullets li::before { content: "- "; color: var(--muted); }
+.edu-bullets li::before { content: "• "; color: var(--muted); }
.edu-extras {
max-height: 0;
@@ -254,7 +246,7 @@ a { color: var(--accent); text-decoration: none; }
line-height: 1.5;
margin-bottom: 1px;
}
-.edu-extras li::before { content: "- "; }
+.edu-extras li::before { content: "• "; }
/* ─── Interests — 2-column micro-card grid ───────────────────────────────────── */
.interests {