commit 3ce624089ced1e5d14bcfada4e5e4b697860eb09 Author: Ian Keane Date: Wed Aug 26 12:34:16 2026 -0400 First acceptable result diff --git a/generate.scm b/generate.scm new file mode 100644 index 0000000..1f7a9a3 --- /dev/null +++ b/generate.scm @@ -0,0 +1,143 @@ +;;; generate.scm - Walk resume data and produce HTML via SXML +;;; Run with: guile generate.scm > resume.html + +(use-modules (sxml simple)) +(load "resume.scm") + +;; ─── Utility ────────────────────────────────────────────────────────────────── + +(define (bullets items) + `(ul ,@(map (lambda (s) `(li ,s)) items))) + +(define (date-range start end) + `(span (@ (class "date-range")) ,start " – " ,end)) + +(define (tech-list techs) + `(ul (@ (class "tech-list")) + ,@(map (lambda (t) `(li (@ (class "tech-tag")) ,t)) techs))) + +;; ─── Individual cards (all become flat children of .columns) ────────────────── + +(define (render-project proj) + `(div (@ (class "project-card")) + (div (@ (class "project-header")) + (span (@ (class "project-name")) ,(field proj 'name)) + ,(date-range (field proj 'date-start) (field proj 'date-end))) + (div (@ (class "project-summary")) + ,(bullets (field proj 'summary-bullets))) + (div (@ (class "project-details")) + ,(bullets (field proj 'detail-bullets))) + ,(tech-list (field proj 'technologies)))) + +;; Title card: blurb only, no projects nested inside. +;; Projects are emitted as siblings after the title card. +(define (render-title-card tc) + `(div (@ (class "title-card")) + (div (@ (class "title-header")) + (span (@ (class "title-name")) ,(field tc 'title)) + ,(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))))) + +;; Emit a title card followed by its project cards as a flat list of siblings +(define (render-title-flat tc) + (append + (list (render-title-card tc)) + (map render-project (field tc 'projects)))) + +(define (render-education edu) + `(div (@ (class "education-card")) + (div (@ (class "edu-header")) + (span (@ (class "edu-degree")) ,(field edu 'degree)) + ,(date-range (field edu 'date-start) (field edu 'date-end))) + (div (@ (class "edu-meta")) + ,(field edu 'institution) " • " ,(field edu 'location)) + (div (@ (class "edu-bullets")) + ,(bullets (field edu 'bullets))) + (div (@ (class "edu-extras")) + ,(bullets (field edu 'extracurriculars))))) + +(define (render-interest item) + `(div (@ (class "interest-card")) + (span (@ (class "interest-title")) ,(field item 'title)) + (span (@ (class "interest-description")) ,(field item 'description)))) + +;; ─── Section → flat list of column children ─────────────────────────────────── + +(define (section->items sec) + (case (car sec) + ((job) + (apply append (map render-title-flat (field sec 'titles)))) + ((education) + (list (render-education sec))) + ((hobby-projects) + (map render-project (field sec 'projects))) + ((interests) + (map render-interest (field sec 'items))) + (else '()))) + +(define (section-label sec) + (case (car sec) + ((job) "Work Experience") + ((education) "Education") + ((hobby-projects) "Projects") + ((interests) "Interests") + (else ""))) + +;; Group consecutive sections of same type, emit one heading per group +(define (sections->flat-items sections) + (if (null? sections) '() + (let loop ((rest (cdr sections)) + (cur-type (caar sections)) + (cur-label (section-label (car sections))) + (cur-items (section->items (car sections))) + (acc '())) + (cond + ((null? rest) + (append (reverse acc) + (list `(h2 (@ (class "sec-heading")) ,cur-label)) + cur-items)) + ((eq? (caar rest) cur-type) + ;; same type: accumulate items without a new heading + (loop (cdr rest) cur-type cur-label + (append cur-items (section->items (car rest))) + acc)) + (else + (loop (cdr rest) (caar rest) (section-label (car rest)) + (section->items (car rest)) + (append acc + (list `(h2 (@ (class "sec-heading")) ,cur-label)) + cur-items))))))) + +;; ─── Front matter ───────────────────────────────────────────────────────────── + +(define (render-front-matter fm) + `(header (@ (class "front-matter")) + (h1 (@ (class "fm-name")) ,(field fm 'name)) + (div (@ (class "fm-contact")) + (span ,(field fm 'location)) + (span ,(field fm 'phone)) + (span ,(field fm 'email))))) + +;; ─── Document ───────────────────────────────────────────────────────────────── + +(define (render-resume res) + (let* ((fm (field res 'front-matter)) + (items (sections->flat-items (field res 'sections)))) + `(*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")))) + (body + ,(render-front-matter fm) + (main (@ (class "columns")) + ,@items)))))) + +(display "\n") +(sxml->xml (render-resume resume)) +(newline) diff --git a/resume.html b/resume.html new file mode 100644 index 0000000..db909c4 --- /dev/null +++ b/resume.html @@ -0,0 +1,2 @@ + +Jane Doe

Jane Doe

Springfield, USA(555) 000-0000jane@example.com
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
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

Projects

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
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.
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
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
Software Engineer IIJan 2020 – May 2022
Acme Corp • Remote
  • Owned the authentication service end-to-end.
  • Reduced API latency by 35% through caching improvements.
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
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
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.

Work Experience

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

Education

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.
diff --git a/resume.scm b/resume.scm new file mode 100644 index 0000000..db194e8 --- /dev/null +++ b/resume.scm @@ -0,0 +1,190 @@ +;;; resume.scm - Resume data definitions + +;; ─── Data constructors ──────────────────────────────────────────────────────── + +(define (make-front-matter name email phone location) + (list 'front-matter + (cons 'name name) + (cons 'email email) + (cons 'phone phone) + (cons 'location location))) + +(define (make-project name date-start date-end summary-bullets detail-bullets technologies) + (list 'project + (cons 'name name) + (cons 'date-start date-start) + (cons 'date-end date-end) + (cons 'summary-bullets summary-bullets) + (cons 'detail-bullets detail-bullets) + (cons 'technologies technologies))) + +;; Title card: lives inside a job card +;; blurb – responsibility bullets +;; projects – project-cards worked on under this title +(define (make-title title company location date-start date-end blurb projects) + (list 'title-card + (cons 'title title) + (cons 'company company) + (cons 'location location) + (cons 'date-start date-start) + (cons 'date-end date-end) + (cons 'blurb blurb) + (cons 'projects projects))) + +;; Job card: groups one employer's title-cards +(define (make-job company date-start date-end titles) + (list 'job + (cons 'company company) + (cons 'date-start date-start) + (cons 'date-end date-end) + (cons 'titles titles))) + +;; Education card +;; bullets – honours, activities shown by default +;; extracurriculars – list of strings, revealed on hover/expand +(define (make-education institution location degree date-start date-end bullets extracurriculars) + (list 'education + (cons 'institution institution) + (cons 'location location) + (cons 'degree degree) + (cons 'date-start date-start) + (cons 'date-end date-end) + (cons 'bullets bullets) + (cons 'extracurriculars extracurriculars))) + +(define (make-hobby-projects heading projects) + (list 'hobby-projects + (cons 'heading heading) + (cons 'projects projects))) + +(define (make-interest title description) + (list 'interest + (cons 'title title) + (cons 'description description))) + +(define (make-interests heading items) + (list 'interests + (cons 'heading heading) + (cons 'items items))) + +;; Generic accessor +(define (field rec key) + (cdr (assq key (cdr rec)))) + +;; ─── Resume data ────────────────────────────────────────────────────────────── + +(define my-front-matter + (make-front-matter + "Jane Doe" + "jane@example.com" + "(555) 000-0000" + "Springfield, USA")) + +(define job-acme + (make-job + "Acme Corp" "Jan 2020" "Present" + (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.") + (list + (make-project + "Distributed Config Service" "Mar 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")) + (make-project + "Developer Portal" "Jun 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")))) + (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.") + (list + (make-project + "Auth Service Rewrite" "Jan 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")) + (make-project + "Rate Limiting Layer" "Feb 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"))))))) + +(define job-startup + (make-job + "Startup XYZ" "Jul 2017" "Dec 2019" + (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.") + (list + (make-project + "Real-Time Dashboard" "Feb 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"))))))) + +(define my-education + (make-education + "The University of Georgia" "Athens, GA" + "Bachelor of Science in Computer Science" + "Aug 2011" "Dec 2016" + '("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"))) + +(define my-hobby-projects + (make-hobby-projects + "Hobby Projects" + (list + (make-project + "schemeresume" "Aug 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")) + (make-project + "Tiny Lisp Interpreter" "Jan 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"))))) + +(define my-interests + (make-interests + "Interests" + (list + (make-interest "Open-Source" "Regular contributor to Guile and Chicken Scheme ecosystem libraries.") + (make-interest "Rock Climbing" "Sport climber; currently projecting 5.12a outdoors.") + (make-interest "Technical Writing" "Blog at janedoe.dev — compilers, distributed systems, functional programming.")))) + +;; Flat ordered section list; CSS handles column placement +(define resume + (list 'resume + (cons 'front-matter my-front-matter) + (cons 'sections (list my-education + job-acme + job-startup + my-hobby-projects + my-interests)))) diff --git a/style.css b/style.css new file mode 100644 index 0000000..2ebd4f1 --- /dev/null +++ b/style.css @@ -0,0 +1,280 @@ +/* ─── Reset ──────────────────────────────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +/* ─── Variables ──────────────────────────────────────────────────────────────── */ +:root { + --bg: #222129; + --bg-card: #272327; + --bg-hover: #2e2b33; + --text: #fff; + --muted: rgba(255,255,255,0.5); + --accent: #FFA86A; + --border: rgba(255,255,255,0.1); + --tag-bg: rgba(255,168,106,0.15); + --tag-text: #FFA86A; + --font: "Fira Code", "Monaco", "Consolas", "Ubuntu Mono", monospace; +} + +/* ─── Base ───────────────────────────────────────────────────────────────────── */ +html { font-size: 13px; } + +body { + font-family: var(--font); + font-feature-settings: "liga", "tnum", "zero", "ss01", "locl"; + letter-spacing: -0.02em; + line-height: 1.54; + background: var(--bg); + color: var(--text); + padding: 32px 36px; + max-width: 1000px; + margin: 0 auto; + /* Don't let the page grow beyond one screen */ + height: 100vh; + overflow: hidden; +} + +ul { list-style: none; } +a { color: var(--accent); text-decoration: none; } + +/* ─── Front matter ───────────────────────────────────────────────────────────── */ +.front-matter { + text-align: center; + padding-bottom: 16px; + margin-bottom: 16px; + border-bottom: 1px solid var(--border); +} + +.fm-name { + font-size: 1.8rem; + font-weight: 800; + letter-spacing: -0.03em; + color: var(--accent); + margin-bottom: 4px; +} + +.fm-contact { + display: flex; + justify-content: center; + flex-wrap: wrap; + font-size: 0.8rem; + color: var(--muted); +} +.fm-contact span + span::before { + content: " \2022 "; + margin: 0 6px; +} + +/* ─── Columns: flex column-wrap ──────────────────────────────────────────────── */ +/* + * All cards and headings are direct children of .columns. + * flex-flow: column wrap makes them stack vertically, and once they + * exceed the container height they wrap into the next column — + * exactly like text wrapping in a newspaper column. + * Any card, at any level, can be the wrap point. + * + * max-height is the remaining viewport after front-matter. + * The front-matter is roughly 80px tall with the padding above. + * JS sets this precisely via --col-height, but the CSS default is close. + */ +.columns { + display: flex; + flex-flow: column wrap; + align-content: space-between; + max-height: calc(100vh - 130px); + gap: 0; +} + +/* Every direct child is half the container width */ +.columns > * { + width: calc(50% - 20px); +} + +/* Dividing rule: pseudo-element positioned in the middle */ +.columns::before { + content: ""; + position: absolute; + left: 50%; + top: 0; + bottom: 0; + width: 1px; + background: var(--border); + pointer-events: none; +} +.columns { position: relative; } + +/* Section headings */ +.sec-heading { + font-size: 0.7rem; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--accent); + border-bottom: 1px solid var(--border); + padding-bottom: 3px; + margin-bottom: 6px; + margin-top: 4px; +} + +/* ─── Shared header ──────────────────────────────────────────────────────────── */ +.title-header, +.project-header, +.edu-header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 8px; + margin-bottom: 1px; +} + +.title-name, +.project-name, +.edu-degree { + font-weight: 700; + font-size: 0.88rem; +} + +.date-range { + font-size: 0.72rem; + color: var(--muted); + white-space: nowrap; + flex-shrink: 0; +} + +.title-meta, +.edu-meta { + font-size: 0.76rem; + color: var(--muted); + margin-bottom: 3px; +} + +/* ─── Title card ─────────────────────────────────────────────────────────────── */ +.title-card { + padding: 5px 0 6px; + border-bottom: 1px solid var(--border); + margin-bottom: 4px; +} + +.title-blurb ul { padding-left: 12px; } +.title-blurb li { + font-size: 0.82rem; + line-height: 1.5; + margin-bottom: 1px; + color: rgba(255,255,255,0.85); +} +.title-blurb li::before { content: "- "; color: var(--muted); } + +/* ─── Project card ───────────────────────────────────────────────────────────── */ +.project-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-left: 2px solid rgba(255,168,106,0.35); + border-radius: 3px; + padding: 6px 9px; + margin-bottom: 4px; + cursor: default; + transition: background 0.18s, border-left-color 0.18s; +} + +.project-card:has(.project-details li):hover { + background: var(--bg-hover); + border-left-color: var(--accent); +} + +.project-name { font-size: 0.83rem; } + +.project-summary ul { padding-left: 12px; margin-top: 2px; } +.project-summary li { + font-size: 0.8rem; + line-height: 1.5; + color: rgba(255,255,255,0.85); +} +.project-summary li::before { content: "- "; color: var(--muted); } + +.project-details { + max-height: 0; + overflow: hidden; + opacity: 0; + transition: max-height 0.28s ease, opacity 0.22s ease; +} +.project-card:hover .project-details { + max-height: 500px; + opacity: 1; +} +.project-details ul { padding-left: 12px; margin-top: 4px; } +.project-details li { + font-size: 0.78rem; + color: var(--muted); + line-height: 1.5; + margin-bottom: 1px; +} +.project-details li::before { content: "- "; } + +.tech-list { + display: flex; + flex-wrap: wrap; + gap: 3px; + margin-top: 5px; +} +.tech-tag { + background: var(--tag-bg); + color: var(--tag-text); + font-size: 0.66rem; + padding: 1px 5px; + border-radius: 2px; + letter-spacing: 0; +} + +/* ─── Education card ─────────────────────────────────────────────────────────── */ +.education-card { + padding: 5px 0 6px; + border-bottom: 1px solid var(--border); + margin-bottom: 4px; + cursor: default; +} + +.edu-bullets ul { padding-left: 12px; margin-top: 3px; } +.edu-bullets li { + font-size: 0.82rem; + line-height: 1.5; + margin-bottom: 1px; + color: rgba(255,255,255,0.85); +} +.edu-bullets li::before { content: "- "; color: var(--muted); } + +.edu-extras { + max-height: 0; + overflow: hidden; + opacity: 0; + transition: max-height 0.28s ease, opacity 0.22s ease; +} +.education-card:hover .edu-extras { + max-height: 400px; + opacity: 1; + margin-top: 4px; +} +.edu-extras ul { padding-left: 12px; } +.edu-extras li { + font-size: 0.78rem; + color: var(--muted); + line-height: 1.5; + margin-bottom: 1px; +} +.edu-extras li::before { content: "- "; } + +/* ─── Interests ──────────────────────────────────────────────────────────────── */ +.interest-card { + font-size: 0.82rem; + line-height: 1.54; + padding: 2px 0; +} +.interest-title { font-weight: 700; } +.interest-title::after { content: ": "; color: var(--muted); font-weight: normal; } +.interest-description { color: var(--muted); } + +/* ─── Responsive ─────────────────────────────────────────────────────────────── */ +@media (max-width: 640px) { + body { height: auto; overflow: auto; padding: 20px 16px; } + .columns { flex-flow: column nowrap; max-height: none; } + .columns > * { width: 100%; } + .columns::before { display: none; } +}