New Website

January 25, 2026

I recently got interested in photography again after a many year hiatus. This was instigated by more frequent travel and by a large photo editing project I embarked on last year.

In 2011 and 2013, I did a 365 project where I made and posted a new photo every day. I averaged something like 30 photos a day—and kept everything—so there were a lot of mediocre photos to delete. It was a daunting editing task. To make it manageable, each day in 2025 I edited the photos from the corresponding day in 2011 and 2013. It was a great way to familiarize myself with my old photos, find some gems I forgot about, and free up a lot of space on my drives. It also made me miss the life-documenting aspect of carrying a camera all the time.

That process also piqued a desire for a new personal website where I could display some of my favorite photos and some artwork that I’ve produced over the years. I also wanted to set up a blog because I’ve had an impulse to write more too.

Implementation Details

The source code for this site is here.

I have another website, nybble, which is focused on in-depth technical content. For that project, I used the Haunt static site generator, which is written in Guile Scheme.

For this site, I wanted to create image galleries—something Haunt isn’t set up to do out of the box. Since I’ve been interested in Common Lisp for a long time, but have never written anything significant with it, I decided to use it to roll my own static site generator.

I used spinneret to generate HTML, and 3bmd to convert Markdown to HTML. I also wrote a tiny library, image-dimensions, to quickly return the width and height of an image. I wanted to include that data in the gallery HTML, so things didn’t jump around as the images loaded.

Extracting the dimensions of a PNG is easy because the data is always located in the same place at the beginning of the image. JPEGs require a little more work—you have to step through a sequence of marked segments until you find the frame segment that contains the dimensions.

One thing I like in Common Lisp is that mapping functions like mapcar and mapc can take a function with an arbitrary number of parameters, and then a matching number of lists to provide the arguments to the function. As soon as one of the lists is exhausted, the expression terminates.

I wanted the site generator to take a list of images (either photos or drawings) and create a unique page for each one, with links to the previous and next images. Using mapcar with successive subsets of the image list provides an easy way to get a series of (previous current next) triplets. Here’s a simplified example:

(let ((nums '(0 1 2 3 4 5)))
  (mapcar #'list nums (cdr nums) (cddr nums)))

=> ((0 1 2) (1 2 3) (2 3 4) (3 4 5))

To make this loop, you just need to attach the last image at the front and the first image at the back, like so:

(let* ((nums '(0 1 2 3 4 5))
       (nums (append (last nums) nums (list (car nums)))))
  (mapcar #'list nums (cdr nums) (cddr nums)))

=> ((5 0 1) (0 1 2) (1 2 3) (2 3 4) (3 4 5) (4 5 0))

Here’s the actual function in the site generator. It uses a locally defined function, write-image-page, to receive the (previous current next) triplets and write the corresponding page. It uses mapc instead of mapcar because there’s no need to accumulate a result—I’m only interested in the side effects.

(defun build-image-pages (path images)
  "Build a web page in PATH for each image in IMAGES."
  (flet ((write-image-page (prev curr next)
           (write-html (str:concat path (name-with-extension curr ".html"))
                       (lambda () (image-page prev curr next)))))
    ;; attach the last image to the front, and the first image to the end
    (let ((images (append (last images) images (list (car images)))))
      ;; zip the list with itself to get prev-curr-next triplets
      (mapc #'write-image-page
            images
            (cdr images)
            (cddr images)))))

And here’s the function that generates the HTML for the image page:

(defun image-page (prev image next)
  "Build a web page for IMAGE with links to the PREV and NEXT images."
  (with-page (:title "Steve Sprang" :script "/js/gallery-keys.js")
    (:nav.image-nav
     (:a#prev :href (name-with-extension prev ".html") "prev")
     (:span "/")
     (:a#next :href (name-with-extension next ".html") "next"))
    (:div.presenter
     (multiple-value-bind (width height)
         (image-dimensions image)
       (:img.presented :src (file-namestring image) :alt ""
                       :width width :height height)))))

Most of this project is straightforward because this site isn’t complicated, but it’s always amazing how much time I can spend tweaking things! In the future, I want to add support for an Atom feed and implement a mechanism to attach alt text to the gallery photos for better accessibility.

■ ■ ■