From 0b0cc6fda507c64b1c31e2768550b992b1eee775 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Wed, 26 Aug 2026 17:54:57 -0400 Subject: [PATCH] More visual tweaks --- generate.scm | 70 ++++++++++++++++++++++++++++++++-------------------- resume.html | 21 +--------------- resume.scm | 13 +++------- style.css | 54 +++++++++++++++++----------------------- 4 files changed, 71 insertions(+), 87 deletions(-) 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

Jane Doe

Springfield, USAjane@example.com

Education

Bachelor of Science in Computer ScienceAug 2011 – Dec 2016
The University of Georgia • Athens, GA
  • Graduated December 2016
  • ICPC Participant and Meeting Coordinator
  • WUOG 90.5 FM — Radio DJ and Operations Staff Member
  • UGA Game Development Club — founding member
  • Intramural soccer and volleyball

Work Experience

Senior Software EngineerJun 2022 – Present
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.
Distributed Config ServiceMar 2023 – Present
  • 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
Developer PortalJun 2022 – Feb 2023
  • 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.
  • Go
  • Bleve
  • React
  • OpenAPI
Software Engineer IIJan 2020 – May 2022
Acme Corp • Remote
  • Owned the authentication service end-to-end.
  • Reduced API latency by 35% through caching improvements.
Auth Service RewriteJan 2020 – Dec 2020
  • 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
Rate Limiting LayerFeb 2021 – May 2022
  • 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.
  • Go
  • gRPC
  • Redis
  • React
Full-Stack EngineerJul 2017 – Dec 2019
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.
Real-Time DashboardFeb 2019 – Dec 2019
  • 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

schemeresumeAug 2026 – Present
  • 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.
  • Scheme
  • SXML
  • HTML
  • CSS
Tiny Lisp InterpreterJan 2025 – Mar 2025
  • 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.
  • Python
  • Lisp

Interests

Open-SourceRegular contributor to Guile and Chicken Scheme ecosystem libraries.
Rock ClimbingSport climber; currently projecting 5.12a outdoors.
Technical WritingBlog at janedoe.dev — compilers, distributed systems, functional programming.

Favorite Software

Guile SchemeEmacsNeovimNixGuixTailscaleWireGuardmpvzathuratmuxAlacrittySyncthingBspwmRofi
+Jane Doe

Jane Doe

Springfield, USAjane@example.com

Education

Bachelor of Science in Computer ScienceAug 2011 – Dec 2016
The University of Georgia • Athens, GA
  • Graduated December 2016
  • ICPC Participant and Meeting Coordinator
  • WUOG 90.5 FM — Radio DJ and Operations Staff Member
  • UGA Game Development Club — founding member
  • Intramural soccer and volleyball

Work Experience

Full-Stack EngineerJul 2017 – Dec 2019
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.

Real-Time DashboardFeb 2019 – Dec 2019
  • 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
Senior Software EngineerJun 2022 – Present
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.

Distributed Config ServiceMar 2023 – Present
  • 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
Developer PortalJun 2022 – Feb 2023
  • 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.
  • Go
  • Bleve
  • React
  • OpenAPI
Software Engineer IIJan 2020 – May 2022
Acme Corp • Remote

Owned the authentication service end-to-end, reducing API latency by 35% through targeted caching improvements.

Auth Service RewriteJan 2020 – Dec 2020
  • 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
Rate Limiting LayerFeb 2021 – May 2022
  • 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.
  • Go
  • gRPC
  • Redis
  • React

Projects

schemeresumeAug 2026 – Present
  • 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.
  • Scheme
  • SXML
  • HTML
  • CSS
Tiny Lisp InterpreterJan 2025 – Mar 2025
  • 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.
  • Python
  • Lisp

Interests

Open-SourceRegular contributor to Guile and Chicken Scheme ecosystem libraries.
Rock ClimbingSport climber; currently projecting 5.12a outdoors.
Technical WritingBlog at janedoe.dev — compilers, distributed systems, functional programming.

Favorite Software

Guile SchemeEmacsNeovimNixGuixTailscaleWireGuardmpvzathuratmuxAlacrittySyncthingBspwmRofi
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 {