diff --git a/llgal.org b/llgal.org
new file mode 100644
index 0000000..f696dc9
--- /dev/null
+++ b/llgal.org
@@ -0,0 +1,162 @@
+#+TITLE: Static photo albums with llgal
+#+DATE: 2023-01-29
+* Prologue
+I used to create photo albums on [[https://flickr.com/photos/tlakh/][flickr]] for our vacations. I even had
+a paid pro account for a few years. While I have backups of all my
+photos, it still felt off to show these albums using somebody else's
+computer. They might disappear any day. Since I have the storage
+capacity and some time to waste I set out to self-host photo albums.
+* Nextcloud Photos
+My first layer of photo backups is a self-hosted [[https://nextcloud.com/][Nextcloud]]. I am using
+the Nextcloud android app to automatically upload pictures when on
+WiFi. So I might as well use the built-in Nextcloud Photos 2.0[fn::Why
+is there not way to link to a description of this thing? There is a
+GitHub page and it's described on [[https://nextcloud.com/athome/][https://nextcloud.com/athome/]] but
+you can't direct-link to it‽].
+
+Unfortunately it's not quite there yet. In increasing order of
+annoyance:
+1) Video buffering is not a thing.
+2) Videos do not get a preview image.
+3) It is very confused about ordering. The order in the photo app is
+ correct but when added to the gallery sometimes two photos switch
+ places. There is some mumbling about mtime vs. ctime vs. exiff data
+ in some forum, but that's not the problem here. First of all, no
+ matter how Nextcloud sorts the picture it would always arrive at
+ the same ordering. I did not edit the photos, so mtime and ctime
+ are the same. But also note that the order in the photo app is
+ correct. It only gets confused when photos are added to an
+ album. The photo album tells a story. It is really bad if the
+ "arrival at home" photo is shown before the "last night of the
+ vacation" photo.
+4) The absolute deal-breaker is that it does not work though. The
+ whole idea is to share a link to the album for anonymous users. You
+ can do that, and it shows you the album with thumbnails. But as
+ soon as you click on a photo or start the slide show it wants you
+ to login. I suspect you need to enable sharing via link to every
+ photo in the album to make it work. There does not seem to be a way
+ to do this in bulk so I did not try it. I have also found a
+ discussion online where people argue for years along the lines of
+ "it does not work" - "yes it does" - "no it does not". That was
+ tiresome and they are still going on. There was some mumbling that
+ you need to enable federation in Nextcloud to make anonymous albums
+ work, but I have that on and it still does not work.
+* llgal
+I then remembered that I had used a static photo album generator
+before: [[http://bgoglin.free.fr/llgal/][llgal]]. But I never tried to make it "pretty" or convert all my
+flickr albums to it.
+** Setup
+llgal is in OpenBSD ports, so it can be installed with
+~pkg_add~. I store all my albums as sub-folders in
+~/var/www/img.sha256.net/~ and then run ~llgal~. I was experimenting a
+bit with the command line flags to figure out what I need and then
+setup a =~[[file:llgal/llgalrc.txt][/.llgal/llgal.rc]]= file.
+
+These are the things I configured:
+#+begin_export ascii
+thumbnails_per_row = 3
+thumbnail_height_max = 240
+thumbnail_width_max = 240
+MVI_link_to_target = 1
+DIR_link_to_target = 1
+slide_width_max = 700
+slide_height_max = 700
+show_all_exif_tags = 1
+credits_text = "Copyright © 2014 - 2023 Florian Obser. All rights reserved."
+exclude = "^js$"
+sort_criteria = "revtime"
+recursive = 1
+link_subgalleries = 1
+#+end_export
+** Video thumbnails
+llgal does not create thumbnails for video files (yet) so I had to
+hack around that a bit.
+First we use ~ffmpeg~ to create the thumbnails:
+#+begin_src shell
+ for i in *.mp4; do
+ ffmpeg -ss 1 -i ${i} -vframes 1 -vf "scale=240:-1" \
+ .llgal/my_thump_${i}.jpg
+ done
+#+end_src
+We grab one frame (=-vframes 1=), one second into the video (=-ss
+1=), and scale it to 240 pixels wide while keeping the aspect ratio
+(=-vf "scale=240:-1"=). We store the thumbnail in the ~.llgal~
+sub-directory, where ~llgal~ stores its own thumbnails and scaled
+images. The config file has this:
+#+begin_export ascii
+# Additional prefix of user-provided scaled images
+# user_scaled_image_filenameprefix = "my"
+
+# Additional prefix of user-provided thumbnails
+# user_thumbnail_image_filenameprefix = "my"
+#+end_export
+I have not worked out what that does, but I am using the "my" prefix
+because of this.
+
+llgal does not pick up these thumbnails but we can use the /captions/
+file to show them. First we are creating /captions files in all
+albums: ~llgal --gc~. This creates =.llgal/captions= in all albums.
+
+It creates lines for all photos and videos that we can edit, for
+videos it looks like this:
+#+begin_export ascii
+MVI: VID_20230120_124000.mp4 ---- Open movie VID_20230120_124000.mp4 ----
+#+end_export
+And we can change it to this to show the thumbnail[fn::I am using an
+emacs macro for that. YMMV.]:
+#+begin_export ascii
+MVI: VID_20230120_124000.mp4 ----
Open movie ----
+#+end_export
+I am using the same trick to have thumbnails for the top-level [[https://img.sha256.net/index.html][album
+overview]]. The only difference is that it is a =DIR= line instead of
+=MVI=.
+** Keyboard and swipe navigation.
+llgal links to [[http://santana2000.com/][santana2000.com]] as an example. Those albums use
+[[https://hammerjs.github.io/][hammer.js]] for swipe navigation and [[https://craig.is/killing/mice][mousetrap]] for keyboard navigation.
+I have copied =slidetemplate.html= from =/usr/local/share/llgal= to
+=~/.llgal= and edited it: [[file:llgal/slidetemplate.html.txt][slidetemplate.html]].
+We need to include the JavaScript files in the header:
+#+begin_src html
+
+
+#+end_src
+Set the =id= for the navigation links:
+#+begin_src html
+
+
+
+
+
+
+
+#+end_src
+And then hook up JavaScript to the navigation links:
+#+begin_src html
+
+
+
+#+end_src
+This seems to disable pinch-zooming on the image itself on android. I
+have not figured out why that is. The hammerjs documentation talks
+about pinch, but my understanding is that it should just work. It does
+not though...
+* Epilogue
+This was not a lot of work, please enjoy the first [[https://img.sha256.net/2023-01%20Malta/index.html][album from our trip
+to Malta in 2023]].
+Time permitting I will convert more albums from flickr and google
+photos in the future.
diff --git a/llgal/llgalrc.txt b/llgal/llgalrc.txt
new file mode 100644
index 0000000..31499ac
--- /dev/null
+++ b/llgal/llgalrc.txt
@@ -0,0 +1,383 @@
+# llgal configuration options.
+# These options may be defined in /etc/llgal/llgalrc, a per-user
+# ${HOME}/.llgal/llgalrc file or a directory-specific .llgal/llgalrc.
+
+# Most of these options may later be overridden by command line options.
+# Default are shown in examples below.
+# Syntax is one of these:
+# variable = "string"
+# variable = decimal number
+# variable = 0 or 1 for disabling or enabled
+# For now, only static values are supported.
+# Brackets indicate corresponding command line options.
+
+##### Name of generic llgal files #####
+
+# Name of the captions header file that is inserted at the beginning
+# of generated captions files.
+# captions_header_filename = "captions.header"
+
+# Name of the CSS file.
+# Such a file will be stored under $local_llgal_dir.
+# css_filename = "llgal.css"
+
+# Name of the film tile image.
+# Such a file will be stored under $local_llgal_dir.
+# Note that this filename must match the one that is used in the CSS file.
+# filmtile_filename = "tile.png"
+
+# Name for image link to the index, previous or next slide
+# index_link_image_filename = "index.png"
+# prev_slide_link_image_filename = "prev.png"
+# next_slide_link_image_filename = "next.png"
+
+# Name of the index template.
+# Such a template file will be used during gallery generation.
+# indextemplate_filename = "indextemplate.html"
+
+# Name of the slide template.
+# Such a template file will be used during gallery generation.
+# slidetemplate_filename = "slidetemplate.html"
+
+##### Location of llgal files if available on the web #####
+
+# Location of the CSS file if available on the web [--uc ].
+# If ending with '/', css_filename will be added.
+# css_location = ""
+
+# Location of llgal image files if available on the web.
+# If ending with '/', their generic filename will be added.
+# These locations may be set alltogether with --ui .
+# filmtile_location = ""
+# index_link_image_location = ""
+# prev_slide_link_image_location = ""
+# next_slide_link_image_location = ""
+
+##### Location and name of generated files #####
+
+# Local subdirectory where llgal files generated files will be stored.
+# This option is only available in system- and user-wide configuration
+# files.
+# Note that HTML files are generated in place while captions and CSS
+# are kept in this subdirectory.
+# Gallery specific HTML templates and llgalrc configuration file
+# might also be defined here.
+# local_llgal_dir = ".llgal"
+
+# Name of the generated index file [-i ]
+# index_filename = "index"
+
+# Prefix of generated HTML slide filenames
+# Note that this prefix is used to decide what HTML to delete when --clean
+# is passed. Setting this option to an empty string will make llgal remove
+# all HTML files.
+# slide_filenameprefix = "slide_"
+
+# Prefix used to determine the filenames of scaled image that are
+# shown in slides (in case of --sx or --sy).
+# These files will be stored under $local_llgal_dir.
+# scaled_image_filenameprefix = "scaled_"
+
+# Prefix used to determine thumbnail filenames from original images.
+# These files will be stored under $local_llgal_dir.
+# thumbnail_image_filenameprefix = "thumb_"
+
+# Name of the captions file that will be generated when llgal is called
+# with --gc. This file will be stored under $local_llgal_dir.
+# captions_filename = "captions"
+
+# Additional prefix of user-provided scaled images
+# user_scaled_image_filenameprefix = "my"
+
+# Additional prefix of user-provided thumbnails
+# user_thumbnail_image_filenameprefix = "my"
+
+# Character to use to replace / in the thumbnail/scaled of subdir images
+# path_separator_replacement = "@"
+
+##### Index #####
+
+# Cellpadding in the index table [-p ]
+# Must be >= 0, setting to < 0 resets to default
+# index_cellpadding = 3
+
+# Display links and text as regular text instead of thumbnails
+# in the main gallery thumbnail index [-L]
+# list_links = 0
+
+# Pixels per row of thumbnails in index [--wx ]
+# Must be > 0, 0 means unlimited, setting to < 0 resets to default
+# pixels_per_row = 0
+
+# Thumbnails per row in index [-w ]
+# Must be > 0, 0 means unlimited, setting to < 0 resets to default
+thumbnails_per_row = 3
+
+# Do not output any absolute thumbnail sizes in HTML code and assume the
+# CSS style sheet will take care of it (in table.index [td.image-slide [img]])
+# slide_dimensions_from_css = 0
+
+# Maximal height of thumbnails [--ty ]
+# Must be > 0, setting to < 0 resets to default
+# Changing this value does not affect the maximal width (see thumbnail_width_max)
+thumbnail_height_max = 240
+
+# Maximal width of thumbnails [--tx ]
+# Must be > 0, 0 means unlimited, setting to < 0 resets to default
+# Changing this value does not affect the maximal height (see thumbnail_height_max)
+thumbnail_width_max = 240
+
+# Write captions under thumbnails [-u]
+# show_caption_under_thumbnails = 0
+
+# Show a film effect in the index of thumbnails [--fe]
+# show_film_effect = 0
+
+# Make thumbnail links point to the target object instead of
+# the corresponding slide
+MVI_link_to_target = 1
+# FIL_link_to_target = 0
+DIR_link_to_target = 1
+# LNK_link_to_target = 0
+
+##### Slides #####
+
+# Make no slides [-s]
+# make_no_slides = 0
+
+# Use filename as slide filename [-n]
+# make_slide_filename_from_filename = 0
+
+# Also use extension in the slide filename to differenciate
+# slide filename of images that have same filenames.
+# make_slide_filename_from_extension = 0
+
+# Do not output any absolute image size in the HTML code and assume the
+# CSS style sheet will take care of it (in table.slide td.image-slide img)
+# slide_dimensions_from_css = 0
+
+# Maximal width of slides [--sx ]
+# Must be > 0, 0 means unlimited, setting to < 0 resets to default
+slide_width_max = 700
+
+# Maximal height of slides [--sy ]
+# Must be > 0, 0 means unlimited, setting to < 0 resets to default
+slide_height_max = 700
+
+# Default size of non-image slides.
+# Must be > 0, setting to < 0 resets to default
+# Note that the --sx and --sy options may lead to reduction of these values.
+# text_slide_width = 400
+# text_slide_height = 300
+
+# Use an image instead of a text label for the link to the index,
+# previous or next slide [--li]
+# index_link_image = 0
+# next_slide_link_image = 0
+# prev_slide_link_image = 0
+
+# Use a thumbnail preview instead of a text label for the link
+# to the previous or next slide [--lt]
+# next_slide_link_preview = 0
+# prev_slide_link_preview = 0
+
+# Generate slide titles from captions [-k]
+# make_slide_title_from_caption = 0
+
+# Generate links between last and first slides or galleries.
+# link_between_last_and_first = 1
+
+# Display a table of EXIF tags under each image slide [--exif]
+# The tags are given with their name in exiftool -list and separated
+# with a comma.
+# show_exif_tags = ""
+
+# Display a table of all available EXIF tags under each image slide [--exif]
+show_all_exif_tags = 1
+
+##### Captions #####
+
+# This line will be added to the captions file llgal will generate when
+# called with --gc. If the user doesn't want llgal to remove this captions
+# file when called with --clean, it just needs to remove this line from
+# the file.
+# captions_removal_line = "REMOVE THIS LINE IF LLGAL SHOULD NOT REMOVE THIS FILE"
+
+# Generate captions from comment stored in images [--cc []]
+# make_caption_from_image_comment = ""
+
+# Also generate captions from timestamp stored in images [--ct]
+# make_caption_from_image_timestamp = 0
+
+# Generate captions from filenames without their extension [--cf]
+# make_caption_from_filename = 0
+
+# Generate captions from filenames with their extension
+# make_caption_from_extension = 0
+
+# Show dimensions and/or size of the images and movies [-a, --ad, --as]
+# show_dimensions = 0
+# show_size = 0
+
+# Change the format of the counter shown on the slides
+# %n is replaced by the slide number, %0n gets leading zeros,
+# and %t is replaced by the number of slides.
+# Setting to "" disables the slide counter [--nc]
+# slide_counter_format = " (%0n/%t)"
+
+##### Text #####
+
+# Default title of the gallery.
+# May be overridden with [--title ] or TITLE: in the captions file.
+# index_title_default = "Index of pictures"
+
+# Change text in links to parent directory.
+# parent_gallery_link_text = "Back to parent gallery"
+
+# Change text in links to previous gallery.
+# prev_gallery_link_text = "Previous gallery "
+
+# Change text in links to next directory.
+# next_gallery_link_text = "Next gallery "
+
+# Label of the link from a slide to the index
+# index_link_text = "Index"
+
+# Label of the link from a slide to the previous one
+# prev_slide_link_text = "←"
+
+# Label of the link from a slide to the next one
+# next_slide_link_text = "Next>>"
+
+# Text prefixing the filename when generating link text
+# for movies without a captions file.
+# MVI_link_text = "Open movie "
+
+# Text prefixing the filename when generating link text
+# for files without a captions file.
+# FIL_link_text = "Download file "
+
+# Text prefixing the filename when generating link text
+# for directories without a captions file.
+# DIR_link_text = "Open subgallery "
+
+# Text shown as image alternative for full-size images in slides
+# alt_full_text = ""
+
+# Text shown as image alternative for scaled images in slides
+# alt_scaled_text = "Scaled image "
+
+# Text shown as image alternative for thumbnails in the index
+# alt_thumbnail_text = "Thumbnail "
+
+# Text shown as an image alternative for the film tile in the index
+# alt_film_tile_text = "Film tile"
+
+# Text shown when the mouse pointer is over a scaled image in a slide
+# over_scaled_text = "Click to see full size "
+
+# Text shown when the mouse pointer is over a thumbnail
+# over_thumbnail_text = "Click to enlarge "
+
+# Text shown when the mouse pointer is over a link from a slide to the index
+# over_index_link_text = "Return to the index"
+
+# Text shown when the mouse pointer is over a link from a slide to the previous one
+# over_prev_slide_link_text = "Previous slide "
+
+# Text shown when the mouse pointer is over a link from a slide to the next one
+# over_next_slide_link_text = "Next slide "
+
+# Unit to be used when printing sizes [--asu ]
+# show_size_unit = "kB"
+
+# Set timestamp format in captions (when enabled) using strftime format [--ct ]
+# timestamp_format_in_caption = "%Y-%m-%d %H:%M:%S"
+
+# Credits line at the bottom of the index
+credits_text = "Copyright © 2014 - 2023 Florian Obser. All rights reserved."
+
+##### What files to insert in the gallery #####
+
+# Extensions that are matched when searching images (|-separated list)
+# image_extensions = "jpg|jpeg|gif|png|tif|tiff|bmp|webp"
+
+# Extensions that are matched when searching movies (|-separated list)
+# movie_extensions = "mpg|mpeg|avi|mov|ogm|wmv|mp4|3gp|webm"
+
+# Add all files to the list of entries
+# not only images and movies [-A]
+# add_all_files = 0
+
+# Add subdirectories to the list of entries [-S]
+# add_subdirs = 0
+
+# Exclude files whose name matches [--exclude ]
+# This option may be used several times.
+exclude = "^js$"
+
+# Include files whose name matches and were previously excluded [--include ].
+# This option may be used several times.
+# The order of includes and excludes is respected.
+# include = "regexp"
+
+# Sort criteria when scanning files in the working directory [--sort]
+sort_criteria = "revtime"
+
+##### Sections #####
+
+# Add a new subdirectory to the list of sections [-P]
+# section_dir = "subdir"
+
+# Add all subdirectories to the list of sections [--Pall]
+# recursive_sections = 0
+
+# Add the subdirectory name as a title at the beginning of each section [--Ps]
+# entitle_sections = 0
+
+# Add a horizontal line at the beginning of each section in the index [--Ps]
+# separate_sections = 0
+
+##### Recursion #####
+
+# Run recursively in subdirectories [-R]
+recursive = 1
+
+# Add links between subgalleries [--Rl]
+link_subgalleries = 1
+
+# Add links to the parent directory [--parent-gal]
+# parent_gallery_link = 0
+
+##### Various #####
+
+# Additional configuration file [--config ]
+# This option may be used multiple times.
+# additional_configuration_file = "my_llgal.rc"
+
+# Additional template directories [--templates].
+# This option may be used multiple times.
+# additional_template_dir = "path"
+
+# Codeset to be set in HTML headers [--codeset ]
+# codeset = "codeset"
+
+# Language to be used for generated text in HTML pages [--lang ]
+# If set, the LANGUAGE environment variable might prevent this option from working.
+# language = "locale"
+
+# Command to be used to generate scaled images and thumbnails
+# thumbnail_create_command = "convert -scale x -- "
+# scaled_create_command = "convert -scale x -- "
+
+# Force regeneration of thumbnails and scaled images [-f].
+# force_image_regeneration = 0
+
+# Print notice messages [-v]
+# verbose = 0
+
+# Give access rights for www access [--www]
+# www_access_rights = 0
+
+# Extension of generate webpages [--php]
+# www_extension = "html"
diff --git a/llgal/slidetemplate.html.txt b/llgal/slidetemplate.html.txt
new file mode 100644
index 0000000..f8050e3
--- /dev/null
+++ b/llgal/slidetemplate.html.txt
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+