Javascript contextual clock

Contextual clock screenshot

Another experiment I did way back to create a contextual clock in html/css/js, originally in jQuery but now updated to vanilla javascript. Source available on Github.

The clock itself is a table of cells containing a single character, each with its own ID. Rows have letters and columns numbers.

<table>  
  <tr>
    <td id='a1'>I</td>
    <td id='a2'>T</td>
    ...

With this we can create the words and numbers as strings of IDs separated by commas. Because arrays are zero indexed I'm explicitly setting the hours so the array key matches the hour (eg. [1] = one):

var itis = "a1,a2,a4,a5";  
var a = "a7";  
var ten = "a9,a10,a11";  
...

var hourwords = new Array();  
hourwords[1] = "d1,d2,d3";  
hourwords[2] = "d4,d5,d6";  
...

Before we update the time it's important to clear the board. To do this we loop through all the <td> elements, ensure each has a numeric key and remove the CSS class 'alight':

function clearBoard(){  
  var alltds = document.getElementsByTagName('td');
  for (var index in alltds) {
    if(!isNaN(parseInt(index, 10)) && alltds[index].classList.contains('alight')){
      alltds[index].classList.remove("alight");
    }
  }
}

To determine which words to light up we create an empty array to hold the words, then use an instance of the Date object to get numerical representations of the hour and the minutes:

var words = new Array();  
var now = new Date();  
var hour = now.getHours();  
var mins = now.getMinutes();  

The following is a little bit of a hacky approach but it avoids complicated if statements. Each case tests which five minute bracket the current minutes falls in and adds the relevant words to our array:

  switch(true){
    case (mins >= 55): words = [five, to]; hour++; break;
    case (mins >= 50): words = [ten, to]; hour++; break;
    case (mins >= 45): words = [quarter, to]; hour++; break;
    case (mins >= 40): words = [twenty, to]; hour++; break;
    case (mins >= 35): words = [twenty, five, to]; hour++; break;
    case (mins >= 30): words = [half, past]; break;
    case (mins >= 25): words = [twenty, five, past]; break;
    case (mins >= 20): words = [twenty, past]; break;
    case (mins >= 15): words = [a, quarter, past]; break;
    case (mins >= 10): words = [ten, past]; break;
    case (mins >= 5):  words = [five, past]; break;
    case (mins < 5):   words = [oclock]; break;
  }

Because Javascript's Date object returns hours in twenty-four hour format we have to do a little maths to change things to twelve hour format.

if(hour > 12){  
  hour = hour - 12;
} else if(hour < 1){ 
  hour = 12; 
}

Now it's just a case of pushing the words 'it is' and the current hour to our words array. Looping through each word then splitting each word by the comma to get each cell ID. With this we can set the class to 'alight'.

words.push(itis, hourwords[hour]); 

words.forEach((word) => {  
  word.split(',').forEach((letter) => {
 document.getElementById(letter).classList.add("alight");
  });
});

To get this all running we fire the showClock() function. To update it we run a function every five seconds to see if the current minutes is a multiple of five and if so, run the showClock() function again.

(function() {
  showClock();
  setInterval(() => {
    if(new Date().getMinutes() % 5 == 0){ 
      showClock();
    }
  }, 5000);
})();

To make the letters glow apply a text-shadow with a light colour against a dark background:

.alight { 
  text-shadow: 0px 0px 12px #ccc; 
  color:       #fff; 
}

Faces following the mouse

Physiognomy - A JS experiment

This is an old experiment I did where I had my face follow the mouse around the browser document. Originally I wrote it using jQuery, but I've updated it to vanilla js. Source available on Github. You can see it here: Physiognomy - A JS experiment.

Step one is to create an image with the nine different head positions:

Head positions in a single image

Next create a function to take the mouse position and image position which will then be fed to another function which calculates which image to show. imagew and imageh are the size of the final element, in other words should be a third of the height and width

function changeFace(mousex, mousey, face_id){  
    var face = document.querySelector(face_id)
    var imagew = 160;
    var imageh = 160;
    var facepos = { // image position
      top:  face.offsetTop,
      left: face.offsetLeft
    };
    face.style.backgroundPosition = calucateBp(imagew, imageh, mousex, mousey, facepos);
}

Next create a function to calculate which the CSS background-position and return it. This looks complicated but should be self explanatory. Each if statement tests where the mouse is relative to the element and sets the background-position accordingly.

function calucateBp(imagew, imageh, mousex, mousey, facepos){  
    // TOP LEFT
    if(mousex < facepos.left && mousey < facepos.top){ 
      return '0px 0px'; 
    }
    // TOP
    if((facepos.left + imagew) > mousex && mousex > facepos.left && mousey < facepos.top){ 
      return '-'+ imagew +'px 0px'; 
    }
    // TOP RIGHT
    if(mousex > (facepos.left + imagew) && mousey < facepos.top){ 
      return '-'+ (2 * imagew) +'px 0px'; 
    }
    // LEFT
    if(mousex < facepos.left && mousey < (facepos.top + imageh) && mousey > facepos.top){ 
      return '0px -'+ imageh +'px'; 
    }
    // FRONT
    if((facepos.left + imagew) > mousex && mousex > facepos.left && mousey < (facepos.top + imageh) && mousey > facepos.top){
      return '-'+ imagew +'px -'+ imageh +'px' 
    }
    // RIGHT
    if(mousex > (facepos.left + imagew) && mousey < (facepos.top + imageh) && mousey > facepos.top){ 
      return '-'+ (2 * imagew) +'px -'+ imageh +'px'; 
    }
    // BOTTOM LEFT
    if(mousex < facepos.left && mousey > (facepos.top + imageh)){ 
      return '0px -'+ (2 * imageh) +'px'; 
    }
    // BOTTOM
    if((facepos.left + imagew) > mousex && mousex > facepos.left && mousey > (facepos.top + imageh)){ 
      return '-'+ imagew +'px -'+ (2 * imageh) +'px'; 
    }
    // BOTTOM RIGHT
    if(mousex > (facepos.left + imagew) && mousey > (facepos.top + imageh)){ 
      return '-'+ (2 * imagew) +'px -'+ (2 * imageh) +'px'; 
    }
}

To make it all work create an event listener for onmousemove passing in the x and y coordinates of the mouse and the ID of the element to apply.

  document.addEventListener('mousemove', (e) => {
    changeFace(e.pageX, e.pageY, '#jerry');
    changeFace(e.pageX, e.pageY, '#george');            
    changeFace(e.pageX, e.pageY, '#elaine');            
    changeFace(e.pageX, e.pageY, '#kramer');            
  });

Note .offsetTop and .offsetLeft return the offset relative to the parent element, not the document. Some extra maths are involved to allow for this if the element isn't at the root of the DOM.

Always uses dashes in filenames

In these days of cloud storage, file synchronisation, and sharing on social media it's more important than ever to properly name files.

Spaces make sense in written language but computers don't like them. Try to use a space in a filename in a URL and it'll replace it with %20. Try to do anything on the command line and you'll have to wrap the filename in double quotes.

The two common conventions are underscores (_) and hyphens (-). One convention used commonly in Unix/Linux filenames is to use underscores to separate words in the file title, and hyphens to separate the title from version number. For example Apache modules take the form of file_name-version.extension, eg. mod_ftp-0.9.6.tar.gz.

Turns out if your filename is ever to be processed by a regular expressions (such as by the Google indexer!) the underscore will be removed and filename concatenated. When using the the \w operator to find words the underscore is ignored, but the hypen is not.

Always use dashes in filenames instead of spaces or underscores.

Screen On The Green - A Raspberry Pi Cinema

screenshot of screen on the green website

I have the pleasure of being one of a small group who run a cinema night once a month in the small town of Writtle, Essex. It's my job to take care of anything remotely technical.

We didn't have any money when we started so had to go cap-in-hand to the parish council who were great and bought the screen and the projector, we use an old Hifi system I had for sound.

We didn't want to use DVDs as we wanted a pre-show slideshow, trailers and an intermission which would've been impractical using DVDs, especially since we would need Blu-ray. Another option was to use a laptop which would've worked fine, but the Raspberry Pi with it's h.264 capable GPU seemed a perfect fit.

Initially we used a Raspberry Pi 3b+ but with the recent release of the A+ we've switched due it's lower power consumption.

In order to use the hardware h.264 decoding we had to use Omxplayer which unfortunately doesn't have playlist support so I had to use a little bash magic.

First we need a playlist file containing a list of video files in the order we want them to play:

cards/30min-preroll.mp4  
trailers/theapartment.mp4  
trailers/12angrymen.mp4  
trailers/incredibleshrinkingman.mp4  
cards/comingsoon.mp4  
cards/feature.mp4  
cards/intro.mp4  
film/amelie-pt1.mp4  
cards/intermission-start.mp4  
ads/ads.mp4  
cards/intermission-end.mp4  
film/amelie-pt2.mp4  
cards/thanks-for-coming.mp4  

A simple bash script to read the playlist and line-by-line and invoke Omxplayer. The -o both parameter sets the output of audio to both the HDMI cable and phones out. -b blanks the screen first.

#!/bin/bash

PLAYER=(/usr/bin/omxplayer -o both -b)  
PLAYLIST="sotg.pls"

clear  # clear the screen

if [ -e "$PLAYLIST" ]  
then  
    IFS=$'\012'  # Set the line ending
    for file in $(cat "$PLAYLIST")
    do
        ${PLAYER[@]} "$file"
    done
fi  

In order to split the film in two for the intermission I find a suitable spot, note the time, and convert it to seconds. For example, 45m 34s in would be (45 * 60) + 34 which is 2734. I then use this in a couple of FFMpeg commands to trim the single film file in to two parts:

#!/bin/bash

FN="amelie"  
TIME=2734

ffmpeg -ss 0 -t $TIME -i $FN.mp4 -strict -2 -af "volume=12dB" $FN-pt1.mp4  
ffmpeg -ss $TIME -i $FN.mp4 -strict -2 -af "volume=12dB" $FN-pt2.mp4  

-ss sets the start time for the trimmed file, and -t sets the end time, which we can omit on the second command as we want to trim to the end. -af "volume=12dB" boosts the volume while we're at it.

CSS Selectors

I can never remember the syntax for this stuff.

Select by attribute presence

<p>This paragraph has a <a href='#'>link</a> in it.</p>  
[href] { background : cyan; }

This paragraph has a link in it.


Combine with element

<p title='wigwam'>This paragraph has a title attribute.</p>  
p[title] { background : cyan; }  

This paragraph has a title attribute.

Putting a space between p and [title] would specify the children of paragraph with title attributes.

<p>This paragraph has a couple of <span title='wigwam'>spans</span> with a <span title='wigwam'>title</span> attributes.</p>  
p [title] { background : cyan; }  

This paragraph has a couple of spans with a title attributes.


Target an attribute and its contents

The content of an attribute can also be specified by having the attribute equal something

<p>This paragraph has a couple of <span title='t1'>spans</span> with different <span  
title='t2'>title</span> attributes.</p>  
[title="t1"] { background : cyan; }
[title=t2] { background : yellow; }

This paragraph has a couple of spans with different title attributes.


Target a specific word in the attribute

Using the tilde (~) it is possible to target by a single string in a space separated list.

<p>This paragraph has a couple of <span title='one two three'>spans</span> with different <span title='one three four'>title</span> attributes.</p>  
[title~="one"] { font-weight : bold; }<br/>
[title~="two"] { background : cyan; }

This paragraph has a couple of spans with different title attributes.

It is also possible to target a substring at the end using the dollar ($) sign.

<p>This paragraph has a couple of <span title='twotone'>spans</span> with different <span title='donedown'>title</span> attributes.</p>  
span[title$="one"] { background : cyan; }  

This paragraph has a couple of spans with different title attributes.

To target a substring at the beginning use the caret (^) sign.

<p>This paragraph has a couple of <span title='twotone'>spans</span> with different <span title='onedown'>title</span> attributes.</p>  
span[title^="one"] { background : cyan; }  

This paragraph has a couple of spans with different title attributes.

It is also possible to target an exact match followed by anything separated with a dash using the pipe (|) symbol.

<p>This paragraph has a couple of <span title='one-up'>spans</span> with different <span title='one-down'>title</span> attributes.</p>  
span[title|="one"] { background : cyan; }  

This paragraph has a couple of spans with different title attributes.

Lastly we can do a full text search using the asterisk (*)

<p>This paragraph has a couple of <span title='donedown'>spans</span> with different <span title='doneup'>title</span> attributes.</p>  
span[title*="one"] { background : cyan; }  

This paragraph has a couple of spans with different title attributes.


Attribute stacking

Attribute selection can also be stacked to give even finer control of selection.

<p>This paragraph has a couple of <span class='onetwothree' title='onetwothree'>spans</span> with different <span class='twothreefour' title='onetwothree'>title</span> attributes.</p>  
span[class~='one'][title*="two"] { background : cyan; }  

This paragraph has a couple of spans with different title attributes.


Outputting attributes

An element's attribute can also be used in pseudo-elements for output.

<p>This paragraph has a <a href='https://lewiswalsh.com'>link</a> with a dynamically printed href.</p>  
a:after { content: " (" attr(href) ")"; }  

This paragraph has a link with a dynamically printed href.


Case sensitivity

By default all CSS attribute selectors are case-sensitive. To make the selection case-insensitive add an 'i' to the end of the selection.

[title*="one" i] { }