Bunch of behavioral fixes

This commit is contained in:
Ian Keane 2026-08-26 13:09:47 -04:00
parent 3ce624089c
commit 30859c5e84
4 changed files with 183 additions and 86 deletions

View file

@ -18,8 +18,11 @@
;; ─── Individual cards (all become flat children of .columns) ────────────────── ;; ─── Individual cards (all become flat children of .columns) ──────────────────
;; Project card — optionally wraps in an anchor if url is set
(define (render-project proj) (define (render-project proj)
`(div (@ (class "project-card")) (let* ((url (field proj 'url))
(card `(div (@ (class ,(string-append "project-card" (if url " clickable" "")))
,@(if url `((onclick ,(string-append "window.open('" url "','_blank')"))) '()))
(div (@ (class "project-header")) (div (@ (class "project-header"))
(span (@ (class "project-name")) ,(field proj 'name)) (span (@ (class "project-name")) ,(field proj 'name))
,(date-range (field proj 'date-start) (field proj 'date-end))) ,(date-range (field proj 'date-start) (field proj 'date-end)))
@ -27,7 +30,8 @@
,(bullets (field proj 'summary-bullets))) ,(bullets (field proj 'summary-bullets)))
(div (@ (class "project-details")) (div (@ (class "project-details"))
,(bullets (field proj 'detail-bullets))) ,(bullets (field proj 'detail-bullets)))
,(tech-list (field proj 'technologies)))) ,(tech-list (field proj 'technologies)))))
card))
;; Title card: blurb only, no projects nested inside. ;; Title card: blurb only, no projects nested inside.
;; Projects are emitted as siblings after the title card. ;; Projects are emitted as siblings after the title card.
@ -64,6 +68,15 @@
(span (@ (class "interest-title")) ,(field item 'title)) (span (@ (class "interest-title")) ,(field item 'title))
(span (@ (class "interest-description")) ,(field item 'description)))) (span (@ (class "interest-description")) ,(field item 'description))))
(define (render-interests sec)
`(div (@ (class "interests"))
,@(map render-interest (field sec 'items))))
(define (render-favorite-software sec)
`(div (@ (class "favorite-software"))
,@(map (lambda (s) `(span (@ (class "sw-item")) ,s))
(field sec 'items))))
;; ─── Section → flat list of column children ─────────────────────────────────── ;; ─── Section → flat list of column children ───────────────────────────────────
(define (section->items sec) (define (section->items sec)
@ -75,7 +88,9 @@
((hobby-projects) ((hobby-projects)
(map render-project (field sec 'projects))) (map render-project (field sec 'projects)))
((interests) ((interests)
(map render-interest (field sec 'items))) (list (render-interests sec)))
((favorite-software)
(list (render-favorite-software sec)))
(else '()))) (else '())))
(define (section-label sec) (define (section-label sec)
@ -84,6 +99,7 @@
((education) "Education") ((education) "Education")
((hobby-projects) "Projects") ((hobby-projects) "Projects")
((interests) "Interests") ((interests) "Interests")
((favorite-software) "Favorite Software")
(else ""))) (else "")))
;; Group consecutive sections of same type, emit one heading per group ;; Group consecutive sections of same type, emit one heading per group
@ -96,16 +112,20 @@
(acc '())) (acc '()))
(cond (cond
((null? rest) ((null? rest)
(append (reverse acc) ;; flush the last group — acc is already in forward order
(append acc
(list `(h2 (@ (class "sec-heading")) ,cur-label)) (list `(h2 (@ (class "sec-heading")) ,cur-label))
cur-items)) cur-items))
((eq? (caar rest) cur-type) ((eq? (caar rest) cur-type)
;; same type: accumulate items without a new heading ;; same section type: merge items under one heading
(loop (cdr rest) cur-type cur-label (loop (cdr rest) cur-type cur-label
(append cur-items (section->items (car rest))) (append cur-items (section->items (car rest)))
acc)) acc))
(else (else
(loop (cdr rest) (caar rest) (section-label (car rest)) ;; new section type: flush current group into acc, start new
(loop (cdr rest)
(caar rest)
(section-label (car rest))
(section->items (car rest)) (section->items (car rest))
(append acc (append acc
(list `(h2 (@ (class "sec-heading")) ,cur-label)) (list `(h2 (@ (class "sec-heading")) ,cur-label))
@ -118,7 +138,6 @@
(h1 (@ (class "fm-name")) ,(field fm 'name)) (h1 (@ (class "fm-name")) ,(field fm 'name))
(div (@ (class "fm-contact")) (div (@ (class "fm-contact"))
(span ,(field fm 'location)) (span ,(field fm 'location))
(span ,(field fm 'phone))
(span ,(field fm 'email))))) (span ,(field fm 'email)))))
;; ─── Document ───────────────────────────────────────────────────────────────── ;; ─── Document ─────────────────────────────────────────────────────────────────
@ -132,7 +151,29 @@
(meta (@ (charset "UTF-8"))) (meta (@ (charset "UTF-8")))
(meta (@ (name "viewport") (content "width=device-width, initial-scale=1"))) (meta (@ (name "viewport") (content "width=device-width, initial-scale=1")))
(title ,(field fm 'name)) (title ,(field fm 'name))
(link (@ (rel "stylesheet") (href "style.css")))) (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);
"))
(body (body
,(render-front-matter fm) ,(render-front-matter fm)
(main (@ (class "columns")) (main (@ (class "columns"))

File diff suppressed because one or more lines are too long

View file

@ -2,21 +2,21 @@
;; ─── Data constructors ──────────────────────────────────────────────────────── ;; ─── Data constructors ────────────────────────────────────────────────────────
(define (make-front-matter name email phone location) (define (make-front-matter name email location)
(list 'front-matter (list 'front-matter
(cons 'name name) (cons 'name name)
(cons 'email email) (cons 'email email)
(cons 'phone phone)
(cons 'location location))) (cons 'location location)))
(define (make-project name date-start date-end summary-bullets detail-bullets technologies) (define (make-project name date-start date-end summary-bullets detail-bullets technologies . rest)
(list 'project (list 'project
(cons 'name name) (cons 'name name)
(cons 'date-start date-start) (cons 'date-start date-start)
(cons 'date-end date-end) (cons 'date-end date-end)
(cons 'summary-bullets summary-bullets) (cons 'summary-bullets summary-bullets)
(cons 'detail-bullets detail-bullets) (cons 'detail-bullets detail-bullets)
(cons 'technologies technologies))) (cons 'technologies technologies)
(cons 'url (if (null? rest) #f (car rest)))))
;; Title card: lives inside a job card ;; Title card: lives inside a job card
;; blurb responsibility bullets ;; blurb responsibility bullets
@ -62,6 +62,12 @@
(cons 'title title) (cons 'title title)
(cons 'description description))) (cons 'description description)))
;; Favorite software section: flat list of names, displayed in N columns
(define (make-favorite-software heading items)
(list 'favorite-software
(cons 'heading heading)
(cons 'items items)))
(define (make-interests heading items) (define (make-interests heading items)
(list 'interests (list 'interests
(cons 'heading heading) (cons 'heading heading)
@ -77,7 +83,6 @@
(make-front-matter (make-front-matter
"Jane Doe" "Jane Doe"
"jane@example.com" "jane@example.com"
"(555) 000-0000"
"Springfield, USA")) "Springfield, USA"))
(define job-acme (define job-acme
@ -163,13 +168,15 @@
'("Defined a small DSL for resume data in resume.scm." '("Defined a small DSL for resume data in resume.scm."
"generate.scm walks the data and emits SXML serialised to HTML." "generate.scm walks the data and emits SXML serialised to HTML."
"CSS handles all layout including the two-column split.") "CSS handles all layout including the two-column split.")
'("Scheme" "SXML" "HTML" "CSS")) '("Scheme" "SXML" "HTML" "CSS")
"https://github.com/janedoe/schemeresume")
(make-project (make-project
"Tiny Lisp Interpreter" "Jan 2025" "Mar 2025" "Tiny Lisp Interpreter" "Jan 2025" "Mar 2025"
'("A 600-line Lisp interpreter in Python supporting tail-call optimisation.") '("A 600-line Lisp interpreter in Python supporting tail-call optimisation.")
'("Implemented environments as linked frames, closures, and a macro expander." '("Implemented environments as linked frames, closures, and a macro expander."
"Added a REPL with readline history and pretty-printing.") "Added a REPL with readline history and pretty-printing.")
'("Python" "Lisp"))))) '("Python" "Lisp")
"https://github.com/janedoe/tinylisp"))))
(define my-interests (define my-interests
(make-interests (make-interests
@ -179,6 +186,12 @@
(make-interest "Rock Climbing" "Sport climber; currently projecting 5.12a outdoors.") (make-interest "Rock Climbing" "Sport climber; currently projecting 5.12a outdoors.")
(make-interest "Technical Writing" "Blog at janedoe.dev — compilers, distributed systems, functional programming.")))) (make-interest "Technical Writing" "Blog at janedoe.dev — compilers, distributed systems, functional programming."))))
(define my-favorite-software
(make-favorite-software
"Favorite Software"
'("Guile Scheme" "Emacs" "Neovim" "Nix" "Guix" "Tailscale" "WireGuard"
"mpv" "zathura" "tmux" "Alacritty" "Syncthing" "Bspwm" "Rofi")))
;; Flat ordered section list; CSS handles column placement ;; Flat ordered section list; CSS handles column placement
(define resume (define resume
(list 'resume (list 'resume
@ -187,4 +200,5 @@
job-acme job-acme
job-startup job-startup
my-hobby-projects my-hobby-projects
my-interests)))) my-interests
my-favorite-software))))

131
style.css
View file

@ -16,7 +16,7 @@
} }
/* ─── Base ───────────────────────────────────────────────────────────────────── */ /* ─── Base ───────────────────────────────────────────────────────────────────── */
html { font-size: 13px; } html, body { height: 100%; margin: 0; }
body { body {
font-family: var(--font); font-family: var(--font);
@ -25,12 +25,10 @@ body {
line-height: 1.54; line-height: 1.54;
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
padding: 32px 36px; padding: 28px 32px;
max-width: 1000px; box-sizing: border-box;
margin: 0 auto; overflow-y: auto;
/* Don't let the page grow beyond one screen */ overflow-x: hidden;
height: 100vh;
overflow: hidden;
} }
ul { list-style: none; } ul { list-style: none; }
@ -39,9 +37,11 @@ a { color: var(--accent); text-decoration: none; }
/* ─── Front matter ───────────────────────────────────────────────────────────── */ /* ─── Front matter ───────────────────────────────────────────────────────────── */
.front-matter { .front-matter {
text-align: center; text-align: center;
padding-bottom: 16px; padding-bottom: 14px;
margin-bottom: 16px; margin-bottom: 14px;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
flex-shrink: 0;
width: 100%;
} }
.fm-name { .fm-name {
@ -64,45 +64,30 @@ a { color: var(--accent); text-decoration: none; }
margin: 0 6px; margin: 0 6px;
} }
/* ─── Columns: flex column-wrap ──────────────────────────────────────────────── */ /* ─── Column layout ──────────────────────────────────────────────────────────── */
/*
* 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 { .columns {
display: flex; column-width: 22em;
flex-flow: column wrap; column-gap: 2em;
align-content: space-between; column-rule: 1px solid var(--border);
max-height: calc(100vh - 130px); column-fill: balance;
gap: 0;
} }
/* Every direct child is half the container width */ /* Prevent any card from being split across a column boundary */
.columns > * { .sec-heading,
width: calc(50% - 20px); .title-card,
.project-card,
.education-card,
.interests,
.favorite-software {
break-inside: avoid;
} }
/* Dividing rule: pseudo-element positioned in the middle */ /* Keep each section heading attached to whatever follows it */
.columns::before { .sec-heading {
content: ""; break-after: avoid;
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 1px;
background: var(--border);
pointer-events: none;
} }
.columns { position: relative; }
/* Section headings */ /* ─── Section headings ───────────────────────────────────────────────────────── */
.sec-heading { .sec-heading {
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 800; font-weight: 800;
@ -180,6 +165,14 @@ a { color: var(--accent); text-decoration: none; }
border-left-color: var(--accent); border-left-color: var(--accent);
} }
.project-card.clickable { cursor: pointer; }
.project-card.clickable:hover .project-name::after {
content: " ↗";
font-size: 0.7rem;
color: var(--accent);
opacity: 0.7;
}
.project-name { font-size: 0.83rem; } .project-name { font-size: 0.83rem; }
.project-summary ul { padding-left: 12px; margin-top: 2px; } .project-summary ul { padding-left: 12px; margin-top: 2px; }
@ -261,20 +254,50 @@ a { color: var(--accent); text-decoration: none; }
} }
.edu-extras li::before { content: "- "; } .edu-extras li::before { content: "- "; }
/* ─── Interests ──────────────────────────────────────────────────────────────── */ /* ─── Interests — 2-column micro-card grid ───────────────────────────────────── */
.interest-card { .interests {
font-size: 0.82rem; display: grid;
line-height: 1.54; grid-template-columns: 1fr 1fr;
padding: 2px 0; gap: 4px;
}
.interest-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 3px;
padding: 5px 8px;
}
.interest-title {
display: block;
font-weight: 700;
font-size: 0.78rem;
color: var(--text);
}
.interest-description {
display: block;
font-size: 0.73rem;
color: var(--muted);
line-height: 1.4;
}
/* ─── Favorite software — 4-column tag grid ─────────────────────────────────── */
.favorite-software {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
}
.sw-item {
background: var(--tag-bg);
color: var(--tag-text);
font-size: 0.72rem;
padding: 3px 6px;
border-radius: 2px;
text-align: center;
} }
.interest-title { font-weight: 700; }
.interest-title::after { content: ": "; color: var(--muted); font-weight: normal; }
.interest-description { color: var(--muted); }
/* ─── Responsive ─────────────────────────────────────────────────────────────── */ /* ─── Responsive ─────────────────────────────────────────────────────────────── */
@media (max-width: 640px) { @media (max-width: 400px) {
body { height: auto; overflow: auto; padding: 20px 16px; } html, body { height: auto; overflow: auto; }
.columns { flex-flow: column nowrap; max-height: none; } .columns { column-width: auto; columns: 1; }
.columns > * { width: 100%; }
.columns::before { display: none; }
} }