Zdog animation fun

I’ve been having fun trying out David DeSandro‘s ace javascript pseudo-3D animation library Zdog.

<canvas style="display: block; margin: 40px auto;" class="zdog-canvas" width="480" height="480"></canvas>

			// create illo
			const illo = new Zdog.Illustration({
			  // set canvas with selector
			  element: '.zdog-canvas',
			  scale: 2.5,

			const faceGroup = new Zdog.Group({
  				addTo: illo

			// face
			const face = new Zdog.Hemisphere({
			  addTo: faceGroup,
			  diameter: 180,
			  color: '#ee3',
			  fill: true,

         const face2 = new Zdog.Hemisphere({
           addTo: faceGroup,
           diameter: 180,
           scale: { z: -1 },
           color: '#ff5',
           fill: true,

			// left eye
			const eyeLeft = new Zdog.Ellipse({
			  addTo: faceGroup,
			  diameter: 30,
			  translate: { x: 40, y: -10, z: 50 },
			  rotate: { y: -0.5 },
			  scale: { x: 0.65 },
			  color: '#333',
			  fill: true,

			// right eye
			const eyeRight = eyeLeft.copy({
			  translate: { x: -40, y: -10, z: 50 },
			  rotate: { y: 0.5 },

			// mouth
			const mouth = eyeLeft.copy({
			  diameter: 30,
			  // stroke: 10,
			  translate: { y: 40, z: 40 },
			  rotate: { x: -0.5 },
			  scale: { x: 3 },
			  color: '#333',
           // fill: false,

			// update & render
			// illo.updateRenderGraph();

			let i = 0.1,
				y = 0.005;

			function animate() {
			  // rotate illo each frame
			  // illo.rotate.x = Math.sin(i) * 4.5;
			  i += 0.05;
			  illo.rotate.y = Math.sin(i);
			  illo.rotate.z = Math.cos(i) * 0.5;
			  illo.rotate.x = Math.cos(y+=0.005) * 1.2;
			  mouth.scale.y = Math.cos(i);
			  // animate next frame
			  requestAnimationFrame( animate );

			// start animation

I was thinking of those transparent bouncy balls with things inside them.

Also I used CodePen’s prefill embed feature to embed the pen here and it worked a treat!


Fun fact

CERN’s 30th anniversary of the World Wide Web was held on my 48th birthday. The web has been around for my entire adult life, to the day!


How to apply a negative timing offset to Zoom VTT captions

This seems like it must be a fairly common captioning requirement:

My sister works for a charity and ran an online seminar (I refuse to call it a webinar!) using Zoom. She had made captions for the 90 minute event by taking machine generated captions and painstakingly correcting them. She planned to put the video & captions up on the charity’s YouTube channel.

The problem

The first 40 minutes of the video were not useful and she wanted to trim them. This is easy in YouTube Studio but she found that the captions she had uploaded alongside the video didn’t get trimmed, so all the timings were out of sync.

It seems like a real oversight that YouTube Studio doesn’t do this automatically, but it’s in beta so maybe that feature is coming soon. For now I had to help my sister fix the problem she had spent several hours working on with no progress.

She sent me the VTT caption file which is a text file that looks like this:

00:08:53.340 --> 00:08:54.420
Hi, can you hear me.

00:09:17.550 --> 00:09:18.810
Yes, I can hear you.


The solution

I searched DuckDuckGo and found, a free online tool for working with captions. It can apply timing offsets so it seemed like the perfect solution… until I tried to apply a negative offset which it didn’t seem to support.

My workaround was to apply a positive offset of 60 minutes minus our required negative offset, and then fix the hours using find & replace.

The first caption after the video was edited needed to started at about 00:00:10.000 but in the caption list it started at 00:35:55.140, so I offset the captions by 00:24:04.860.

Once the captions were offset I fixed the hours by doing a find & replace with regular expression in Sublime Text. I searched for ^01: using the regex symbol ^ to only match characters at the start of a line. I replaced ^01: with 00:, then replaced ^02: with 01:

After that I also had to replace --> 01 with --> 00, and -->02 with --> 01 I sent it back to my sister and… it worked!

End result

One very happy & relieved sister!


htaccess debugging workflow

Writing complex mod_rewrite redirection rules with .htacess can lead to a downwards spiral of frustration, paranoia and utter bewilderment.

Today I’m working on a particularly tricky scenario so I decided to get more methodical about it.

Here’s a summary of the workflow I’ve been using. I have to remind myself to be deliberately slow and methodical – if I get impatient and rush ahead I’m much more likely to miss something unexpected and end up going in circles.

My setup uses a locally installed Apache server and the Firefox Selenium IDE plugin:

  • Make a new directory in the server root, to keep everything in one place, with an index.php echoing some debugging info:
    File: <?= __FILE__ ?>
    Script name: <?= $_SERVER[ 'SCRIPT_NAME' ] ?>
    Request URI: <?= $_SERVER[ 'REQUEST_URI' ] ?>
    Query string: <?= $_SERVER[ 'QUERY_STRING' ] ?>
    Cookie check: <?= $_COOKIE[ 'myCookie' ] ?>
  • Break the task up into a list of manageable blocks e.g.
    1. Set a cookie
    2. Append flag to URL if cookie is present
    3. If flag present remove it and set query string
  • Make directories named after each block e.g.
    • /set-a-cookie
    • /append-flag-to-url-if-cookie-is-present
    • /if-flag-present-remove-it-and-set-query-string
  • Each directory contains an .htaccess file and index.php
  • Put a comment at the top of each .htaccess identifying the block it addresses e.g. # set-a-cookie. That way when you start copying & pasting you can keep track of what each block does
  • The index file simply pulls in the root index.php:
    <?php require( '../index.php' );
  • Make a new Selenium test suite and begin constructing a test case for the first block. These tests are generally pretty simple: open a URL and check that the location is redirected as expected.
  • Remember to test for multiple scenarios such as requests for:
    • with trailing slashes
    • without trailing slashes
    • with query strings
    • index.php
    • static files
  • Start working on the .htaccess file, running the tests each time you make a change. Once a block is working move to the next one
  • Repeat until done

Work slowly and try not to make too many changes at once. mod_rewrite may seem like voodoo but don’t let it give you the heebie-jeebies!


How to avoid accidentally posting test content on live sites

It’s quite unprofessional, let alone embarrassing, to post some test content on a development site only to discover you’re actually in the wrong tab and you just posted to the live site.

To avoid this I use the Stylish Add-on for Firefox to inject custom CSS into live sites I’m working on.

For example, I have the following rule for

@-moz-document domain("") {
   html {
      border-left: 30px solid red !important; 

The result is that, in my browser, the live site has a hard-to-ignore red border that acts as a clear visual reminder:

Screenshot of website
Caution: Live site!


Since writing this I’m working with four distinct versions of the site: local development, remote development, remote live and remote legacy (backup of the previous version).

I’ve refined my Stylish rules accordingly to add a custom footer for each. The new rule for the live site is:

@namespace url(;

@-moz-document domain("") {
  body::after {
    z-index: 99999;
    line-height: 40px;
    text-align: center;
    width: 100%;
    font-size: 16px;
    color: #fff;
    background: #800;
    content: "live";
    left: 0;
    position: fixed;
    bottom: 0;

which looks like this:


Whereas the local development version looks like this:

Red = Caution, Green = Go!




Firefox Metabookmarks 3

More progress this week: Metabookmarks is now on github!

I’ve sidelined the custom protocol for now and I’m planning to use a chrome:// content link instead, specified in the chrome.manifest file. This allows arbitrary content files to be bundled inside the plugin file and accessed via chrome:// URLs, and hopefully will get me well on the way.

Using cfx means the content directory and the chrome.manifest files have to be manually added to the plugin.xpi archive. This gets tedious quickly so I wrote a build script to automate the process.

The build script also uses the excellent Extension Auto-Installer add-on to ‘push’ the update to Firefox immediately.

So, my pencils are nice & sharp, the desk is tidy and I’m ready to start some serious hacking!


Firefox Metabookmarks 2

So far so good. The Firefox add-on SDK is really straightforward and I’ve tried a couple of simple examples.

I want to be able to write URLs in the form bookmark:tag which will open a page showing a list of bookmarks matching tag. I need to let Firefox recognise a new bookmark: protocol.

This Stack Overflow post seems like a good starting point.


Firefox Metabookmarks

I’m planning to build a Firefox extension to display a page of bookmarks for a particular tag on a single, bookmarkable page (hence Metabookmarks)

For example: bookmarks://project_javascript would display a page of nicely presented links that have been tagged with project_javascript

I’ve never attempted to build a Firefox extension before so I imagine this could take some time & effort. It’s not as if I don’t have other things going on in my life too…

I’m bookmarking resources with the tag project_metabookmarks, and also here:


Hacking Firefox bookmarks

I have a few thousand bookmarks in Firefox, many of which are tagged with project_ tags. I use these to bookmark material for future reference on subjects which I want read more about at some future date…

It turns out I’ve now got rather a lot of these project tags, and between them many hundreds of bookmarks. The bookmarks sit there, accumulating, waiting to fulfill their destiny and provide me with sweet, sweet knowledge.

To encourage myself to make some decisions about which are worth pursuing I wanted to get a list of these various project tags, together with a count of how many bookmarks I have saved for each.

Firefox stores bookmarks in a SQLite database called places.sqlite which can be perused with any SQLite capable database manager. I used the SQLite Manager add-on because it does exactly what I needed.

Find tags by pattern

SELECT title 
FROM moz_bookmarks 
WHERE title LIKE 'project_%' 
AND parent = 4

Count bookmarks for a given tag

SELECT tag.title, COUNT( 
FROM moz_bookmarks AS tag
JOIN moz_bookmarks AS bookmark 
ON bookmark.parent =
WHERE tag.title = 'project_javascript' 
AND tag.parent = 4

Find tags and count by pattern

After a few attempts I managed to combine these two into the final query that gives me the numbers I wanted:

SELECT tag.title, COUNT(tag.title)
FROM moz_bookmarks AS tag
LEFT JOIN moz_bookmarks AS bookmark
ON bookmark.parent =
WHERE tag.title LIKE 'project_%'
AND tag.parent = 4
GROUP BY tag.title
ORDER BY tag.title ASC


Learn Linux with this one weird tip

The first HTML I wrote was on the creamy-white PowerMac 5000s at Brighton University in the late ‘90s, but since then, until recently, I’ve only ever used Windows. It’s made a lot of sense for me to transition from Windows to Linux as my primary operating system over the last year and a half.

There are some amazing development tools out there like node & Grunt which are a breeze to install on Mac & Linux, but where Windows support seems marginal to say the least. To quote one tutorial:

…if you use Windows, my apologies but you’re on your own

Switching has at times been pretty tedious, but last week as I SSH’d into a server, grepped an errant line of code and edited the file in Vim I realised that it’s finally paying off. Maybe I’ll even get to grips with the old imposter phenomenon.

So what’s the one weird tip that I came here for?

Make notes, and plenty of ’em. Oh and start an A-Z card index for any useful commands you want to use more than once.

Yes you got a second weird tip for free, you’re welcome.