'Twas Brillig

So, decided to hack my own slideshow script, to dynamically turn (some of) these WP posts into slideshows…

(Published code on GitLab, even tho it's kinda idiosyncratic? It's called Slithy, obviously.)


Back to lecturing? Maybe. Twenty plus years ago, when I taught/lectured for a few years, we used mechanical/optical "transparencies" projection in class. Nu, prehistory. More recently, I used Google Slides to keep it simple, and portable. But today…


  1. The nice thing about standards is that you have so many to choose from.Tanenbaum. Also, xkcd.
  2. XOXO: historical perspective. And cute name. ;o) (BTW, WP already uses [class~=xoxo] — unrelated. WTF?)
  3. S5 microformat: much too noisy, I'll only use maybe a couple of features — and why conform if I'm writing my own script? Anyway, it's a useful thread to start pulling.


  1. Markup: must not break WordPress/my theme (Stencil) — keep posts rendering normally, and don't/minimize DOM changes. And no divitis!
  2. Nesting: tree, flattened.
  3. Unique URLs: for History API.
  4. Keyboard shortcuts
  5. Nice to have?


  1. Wrapping each slide with <section> is unavoidable (or is it?), but, testing in DevTools shows this doesn't disrupt post's layout — no CSS changes necessary. ;o)
  2. Can be done dynamically, in the browser. Not a trivial one-liner, pro'ly, but, "luckily", since WP seems to force wrap text nodes (and comments!) in <p>s, all first level (.entry-content>*) nodes are ELEMENT_NODEs — much nicer to handle (since most jQuery operations don't support text and comment nodes).
  3. Essentially, wrap each <h2> heading, and all following siblings up to (.nextUntil()) the next heading, or EOF, in a <section class="slide">. And move heading's attributes to it.
  4. Did I say not a trivial one-liner?! Nu:
    $ '.entry-content>h2'
    	.each (i,e)->
    		$ e
    			.nextUntil 'h2' # Following siblings.
    			.addBack() # Include self.
    			.wrapAll '<section>'

On demand

  1. Don't want to mess about with the theme (WP is dead!). Or plugins. Loading a script to dynamically patch the DOM is easy enough: added this at the bottom of single.php, right before calling get_footer():
    // Inject my slideshow hacks, and *modern* jQuery as dependency.
    wp_deregister_script('jquery'); // Just to be sure?
    (Didn't even update entire theme, just uploaded files with scp, or SFTP with Thunar.)
  2. Even tho won't interfere with regular posts, I'd rather only hack the DOM when necessary: given I'd tag slideshow posts anyway, and WP already inserts CSS classes for taxonomies, this becomes super easy:
    unless ($ '.post.tag-slides').length then return


  1. I like deep trees (outlines), but they don't render well as WP posts, so I flatten them.
  2. Structure/flow can be overlaid conveniently enough (especially using WPsuck now) with HTML attributes… and source ordering.
  3. Outline trees have equivalent (isomorphic in a sense?) binary trees; source order implicitly means sequence, so I'd only need to explicitly markup nesting. To facilitate moving slides around, while editing, I'll go with adding [data-parent="foo"] to every (nested under #foo) slide.
  4. Hmm, [id]s. Nu.

Keyboard shortcuts

  1. I won't animate transitions. Other scripts went with a horizontal mental model/metaphor, a strip of slides going left to right. Mine's tree structured, and needs four keys to navigate: (1) next (or down, DFS — depth first search), (2) previous, (3) skip children, going to next sibling, and (4) up to parent.
  2. I'll map these similar to how a file manager's tree view does — to keyboard arrows right (also space, page down), up (and page up), down, and left, respectively.
    Nah, makes no sense, can't remember these. Right/left for next/prev and up/down for, nu, up/skip, then.
  3. Also, map home and end to first and last slide.
  4. keyCode, key, or which? Effing mess. Comments, instead?
  5. Browser already handles keys for going back/forward in history.

Nice to have?

  1. Might want corresponding visual controls, à la Reveal.js.
  2. Handle URL fragments and hyperlinks to [id]s inside slides, too?
  3. Swipe?
  4. Hide/show controls and progress? On hover?
  5. Stylesheet for print.
  6. PHP: avoid loading script and everything altogether, if no slides.

Develop locally

  1. Firefox will load scripts referenced from local (file:///) documents! (Don't need Python's -m http.server, nor npm i http-server. ;o) So, temporarily adding these to the end of an HTML file (WPsuck!):
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
    <script>$('body').addClass('post tag-slides entry-content').prepend('<h1 class="entry-title">Slithy</h1>')</script>
    <script src="./Code/dist/slithy.js"></script>
    <link href="./Code/dist/slithy.css" rel="stylesheet">
    Then, simply:
    $ sensible-browser ../Slithy.html
  2. Pro'ly should've done this with GreaseMonkey (et al) instead?
  3. coffee --watch didn't work for me… over SFTP/gvfs mount? There are tons of alternatives…
  4. $ stylus --out dist --compress slithy.styl


  1. # Button to start the show.
    $ '<button title="Slideshow" accesskey="s">📽</button>'
    	.insertAfter $ 'h1.entry-title'
    	.click ->
    		$ 'body'
    		.toggleClass 'slideshow'

Show just one

  1. Essentially:
    .slideshow // Only apply when in slideshow mode.
    		display none // Show just one, .current, and hide others.
    			display block
    			position fixed // "Full screen", ignore site's layout.
    			top 0
    			bottom 0
    			left 0
    			right 0
    			overflow auto // fixed doesn't scroll.
    			background white // Mask everything else.
  2. Alternatively, use CSS Scroll Snap? Nu, another research project.


  1. Slides must all have IDs: because bookmarks (of individual slides, as URL fragments, obviously).
  2. Also, tho History API doesn't require URL changes, reloading won't preserve position without them.
  3. So, if slides aren't explicitly ID'ed (for internal hyperlinks), then generate dynamically: based on headings, so they're quasi constant; abbreviated; unless identical headings. (Code’s a bit dense. Nu.)

Hyper navigation

  1. Bookmarks: page might've been loaded with fragment (hash) in URL already:
    $ '<button title="Slideshow" accesskey="s">📽</button>'
    	.click (ev)->
    		if ($ 'body.slideshow').length # Is view now in slideshow mode?
    			# Show slide indicated in URL, otherwise first.
    			unless h or ($ '.current').length then slide 0
    			else slide h
  2. History API? Assigning location.hash doesn't reload the page, and seems to push a new history entry — and trigger popstate. (hashchange didn't work?)

Et cetera.

And… It works. In like 120 lines of code.

Migrated Elitist (this devlog) from WordPress to my own SSG… and broke Slithy, and TOC, by tweaking DOM and stylesheet (Stencil).

Styling fixes

Borked TOC, navigation…

  1. And move heading's attributes to it — breaks TOC, if it hasn't already done its thing. Peeking at the DOM proves I have a race condition!
    <section id="mrk" data-parent="rq" class="slide"><h2 id="m">Markup</h2><ol>…
  2. Both scripts used to be inserted at <body>'s end by WP (in wp_footer(): many plugins use this hook to reference JavaScript files), but now I'm inserting them in <head>, with attribute "defer" to guarantee DOMContentLoaded… hmm, spec says scripts with the defer attribute will execute in the order in which they appear in the document. How come, then? Aha! they both use jQuery's .ready(), to similarly guarantee DOM is fully loaded — safer than relying on document to use "defer" — and I'm guessing this is where…
  3. Better solution? Change Slithy to not move headings' attributes? Can't — need [id] on slide (<section>) for navigation. And [data-parent]. Patch TOC, then, to accomodate intervening nesting level?
  4. No. Slithy expects [id]s on all slides — which TOC provides for it by generating those missing! Implicit order dependence.

The real world is a special case