Dashron V5 — Full Synthwave

Today V5 of Dashron.com is live, with a brand new design. Let’s dig into the details.

Design

The new Dashron.com V5 design was heavily inspired by synthwave visuals. I dug through countless band posters, concert pamphlets, movies and videos games to get a feel for the core elements of the style. The iconic synthwave sun, neon gradients, lots of purple. It took some time to get right, but I’m very happy with the results.

Some additional design notes:

  • There are no images in this design. Everything is a hand-writtten SVG.
  • The background is a modified version of this codepen. I felt the scroll was too distracting, so I slowed it down.
  • The “Dashron” font is called “Razor”, and is found here. The neon glow css is from this article.
  • The rest of the fonts are called “Poppins” and found here. I like how the bold header gives old VHS case vibes.
  • CSS Gradient was a big help in fine tuning the sun and header gradients.

Authoring

The authoring flow remains the same as described in Dashron.com V4. I write content in WordPress and it’s automatically picked up by my node server.

Backend

Roads still powers the backend, but roads-starter is out of the picture. The organization of roads-starter doesn’t fit my needs with maintaining multiple sites. Dungeon Dashboard pioneered the new infrastructure, Dashron V5 adopted it, and I will open source it after building a third site. Hopefully soon!

Templates

V4 used Handlebars for templates, but I have moved V5 over to React. You might wonder why I use React when I have no front end JavaScript. There are two main reasons for this.

First, I adore JSX.

  • This entire project is typescript, so having type hints/safety from the API calls to the HTML is wonderful. If I change an API call I’m immediately notified of everything I need to fix.
  • Having clear imports for components makes debugging far easier than organization based on a convention I will surely forget.
  • I prefer to put rendering logic into the template rather than the controllers, keeping it in the same file as the html and far away from the data retrieval. This is difficult with handlebars, as it has a limited amount of functionality. With React, I can transform anything into HTML, any way I want.

Second, I actually am using front end React on Dungeon Dashboard, so I kept it here for simplicity sake.

CSS

V4 used Bulma.io for rendering, but I found it far too limiting. For Dungeon Dashboard I moved to using Tailwind & Daisy. Tailwind is a class-based CSS system, which is far closer to CSS than most frameworks. It has been particularly impressive for a handful of reasons:

  • In limiting my choices, it improves my consistency. e.g. font-size uses xs through 9xl, which maps to a change rems. I don’t need to come up with rems on a whim.
  • It groups CSS in an opinionated way that will improve my design. e.g. font-size increases both font-size and line-height for better readability.
  • Tailwind makes long, gross class lists. This is often seen as a negative, but in the world of React it encourages you to rethink your component organization for the better. I am now more encouraged to think about which parts of my site are reusable, and which should be split into their own component.
  • Just as I like my rendering logic closer to my templates, having my CSS closer to my HTML has reduced interruptions, as I no longer have to jump between many files at once for minor tweaks.
  • Responsive design is a joy in Tailwind. It’s trivial to indicate that certain styles only apply in certain screen sizes.
  • Tailwind lets me break out of their restrictions any time I need without losing access to their responsive design patterns.

On top of this I use Daisy, a css component library built specifically to work with Tailwind. Daisy offers well designed UI components without having to depend on huge react component libraries. I just throw in the proper classes and everything looks great.

Overall, I’m very happy with the new setup.

Don’t hesitate to reach out if you have any questions or interest in the work I’m doing!

Posted toTechon10/25/2023

Creating a better Zoom background with the help of GitHub Copilot

My office has a big, blank wall. It’s the boring background of all my Zoom calls.

A big ol’ blank wall

This wall is in dire need of… something. My first thought was my Buster Keaton poster:

A poster of Buster Keaton at a film editing table

But that’s too static. I want to change the background frequently and my basement can’t handle the amount of posters I would have to buy. So I bought a projector to show anything I want. Here’s what I’ve got so far:

Frames from The Night of the Hunter projected onto the wall

Here’s how I got there, but if you’re more interested in coding with GitHub Copilot you can jump ahead to the code.

The Plan

I love movies, so I want to turn the blank wall into a silver screen. I want the ballet from Singing in the Rain, the safe-cracking from Thief, the graffiti from Candyman. Moments from Lone Wolf and Cub, Rear Window, Raiders of the Lost Ark, Deep Red and so much more.

While considering my options, I remembered the Very Slow Movie Player (VSMP). The VSMP is a small e-ink screen that plays movies at super slow speed. What if I did that, but projected the results onto my blank wall? I could have a much more dynamic background and, when bored, explore the details of these scenes one frame at a time. Besides, who wouldn’t want to watch Gene Kelly dancing on loop for an entire meeting.

So I bought a projector, specifically a cheap, refurbished Epson Brightlink 585wi. I can connect directly via HDMI, so I’ll host this on a raspberry pi and connect the two.

The Content

An easy way to achieve frame-by-frame playback is to extract frames from the video as images. Often people use VLC or MakeMKV to extract the video from a Blu-ray, and FFMPEG to convert the video into images. FFMPEG is a remarkable library that should always be your first stop when working with video. I used the following command:

./ffmpeg.exe -ss 00:00:53 -i ./hunter.mp4 -vf fps=1/5 ./images/hunter%d.png

Here’s more details on all the parameters

  • ss: This parameter indicates where you will start recording. For Night of the Hunter I started 53 seconds in to skip past black screen and intro credits.
  • i: The input file.
  • vf: This is a “filtergraph” or a series of filters that modify the video. It’s incredibly powerful but I’m only using it to reduce the frame rate. I only want one frame every 5 seconds, hence 1/5th of a frame per second.
  • ./images/hunter%d.png: This is a filename template. FFMPEG will create the images using this pattern, replacing %d with an ever growing number for each image created.

The Code

I’ve got all my images and a way to get them on the wall. Now I need a way to show the images and cycle through each frame. Being a web developer, I built a simple website and web server to manage everything.

For this project I wanted to work fast and save time, so I turned to GitHub Copilot. Copilot has an interesting feature where you write a comment describing how your code should work and it will write the code. After using it for a while, I think it’s witchcraft. Let me show you.

I started with basic express boilerplate (note that this is all in TypeScript).

import * as express from 'express';
import * as fs from 'fs';
const app = express();
const port = 8080;
app.listen(port, () => {
    console.log('server started');
});

Next I built the landing page. This page will show all the possible scenes I can play, stored on the server as folders of images. To build this page I added the following comment between the port definition and app.listen

/**
 * This endpoint should scan the list of folders in the images directory and list them as html links
 */

…and GitHub Copilot wrote the following for me automatically:

app.get("/", (req, res) => {
    fs.readdir("./images", (err, files) => {
        if (err) {
            res.send("Error reading directory");
        } else {
            // build a list of links
            let links = "";
            for (let i = 0; i < files.length; i++) {
                links += `<a href="/${files[i]}">${files[i]}</a><br>`;
            }
            // send the list of links
            res.send(links);
        });
    });
});

Witchcraft.

I didn’t have to look up how readdir worked, it had error handling, it correctly guessed the directory ./images, and set up links anticipating the next endpoint I needed to build. That’s so much time saved.

Then I pushed Copilot a little harder. I added another comment to create a new page:

/*
 * This endpoint should return an html page containing links to all the images in the requested folder
 */

And GitHub Copilot created the following:

app.get("/:folder", (req, res) => {
    fs.readdir(`./images/${req.params.folder}`, (err, files) => {
        if (err) {
            res.send("Error reading file");
        } else {
            // build a list of links
            let links = "";
            for (let i = 0; i < files.length; i++) {
                links += `<img src="/${req.params.folder}/${encodeURIComponent(files[i])}" /><br>`;
            }
            // send the list of links
            res.send(links);
        }
    });
});

The URL is right, the folder structure is right, and the output is right. Now, there may be a security vulnerability in using req.params.folder directly in readdir. That scares me in a different way than the cauldron-based magic that wrote this code. Security seems to be a common concern with Copilot, the programmer still needs to be on guard. 

Lastly, I wrote:

/*
 * This endpoint should return the requested image 
 */

And sure enough, it created an endpoint that will load the image and serve it to the client when accessed.

Now I can see all the images associated with this project, but they’re all shown at once. I need to cycle through them one at a time. I was sure this was going to be too much for GitHub, but I tried it anyway:

/* 
 * Make all images on the page invisible, then show the first image and cycle through the images every 5 seconds
 */

And GitHub gave me:

<script>
        document.querySelectorAll("img").forEach(img => {
            img.style.visibility = "hidden";
        });
        document.querySelector("img").style.visibility = "visible";
        setInterval(() => {
            let current = document.querySelector("img");
            let next = current.nextElementSibling;
            if (next == null) {
                next = current.parentElement.firstElementChild;
            }
            current.style.visibility = "hidden";
            next.style.visibility = "visible";
        }, 5000);
</script>

And I was proven wrong. Copilot was definitely up to the task and wrote a viable solution before I even tried to think of the right approach.

The Clean Up

With the latest code the website was flashing between images. It was very jarring, so now I fade the scene from image to image. I didn’t bother with Copilot here, and in the end it required a bunch of trial and error. Here’s my final process:

  1. Start with one image on the page, absolutely positioned.
  2. When you want to fade in the new image, add it to the page absolutely positioned with a CSS fade in animation.
  3. When the animation is complete, update the first image to show the contents of the new image, and remove the new image.
  4. After a set amount of time, repeat the process starting at step 2.

I made other changes along the way to improve the project, and you can see the code here: https://github.com/Dashron/projector-art.

The Final Results

Frames from The Night of the Hunter projected onto the wall

In the end GitHub Copilot didn’t write everything for me, but it still saved me a lot of time. I think I’m going to put that extra time to good use, and watch a bunch of movies about witches.

Check out the code here

This article is better than it was thanks to Eric, my favorite casual wordsmith.

Posted toTechon2/8/2022

Dashron.com V4

Late at night on December 16th I launched the new version of Dashron.com. This new version is a full redesign and a rewrite. The site was in desperate need of an update.

Code Organization

The previous version of dashron.com used roads-starter to help with code organization. roads-starter was a library that offered objects to help organize code and reduce duplication. roads-starter wasn’t cutting it for my larger side projects so I rewrote it to use code generation. I believe the new technique will be more maintainable.

CSS Framework

I am not a designer, and have not spent time improving those skills. For the redesign I wanted to build on the experience of stronger designers, so I selected the bulma css framework. I’ve been happy with bulma and will continue using it for future projects.

Authoring

The old site never had a good authoring flow. Behind the scenes I had one big text box that accepted markdown. This was limiting, not very inviting, and thus a barrier to creating content. The new site has no API or database. It consumes the WordPress API which allows me to author everything using the excellent WordPress editor.

You’ll also notice a new top navigation. In this new navigation is a series of topics I invest my time in. I will be writing about all these topics, and am excited to get back into creating content.

Posted toTechon12/18/2021

2020 Movie notes

In 2020 I watched 154 movies, a number I doubt I will break for some time. 121 of those movies were first time watches, leaving only 33 repeats.

Covid significantly disrupted the movie industry this year, leading to the majority of those 121 first time watches being older movies.

My favorite movie of 2020 was Palm Springs, but there were many 2020 releases I did not have time to see. It’s much easier to watch a fun movie with a newborn around than something that requires my full attention.

Special mentions this year for 2020 releases go to Soul and the new Bill and Ted.

But with the strangeness of 2020, I’m going to also give special mentions to a couple of non-2020 movies, Night of the Hunter and Uncut Gems.

Check out my full year in review here, with written reviews for 134 of those movies: https://letterboxd.com/dashron/year/2020

Posted tomovieson1/30/2021

2019 Movie notes

Pushed myself this year to watch and record my thoughts on over 100 first-watch movies.

In the end, I watched 137 movies, 101 of which were first time watches.

Favorites in no order:

  • The Lighthouse – bizzare, wonderful, great acting.
  • Eight Diagram Pole Fighter – new fav martial arts film.
  • Candyman – new fav classic horror

More notes can be found on my letterboxd here.

Posted tomovieson12/31/2020

2018 Movie notes

2018 was the first full year I recorded every movie I watched on letterboxd.com, and I loved every minute of it.

My favorite movie of the year was Eighth Grade, with special mentions deserved for Hereditary and Annihilation.

If you use letterboxd I would love to connect there too. I always enjoy chatting with friends about movies. You can find me at https://letterboxd.com/dashron/

Posted tomovieson1/2/2019

Common Hypermedia Patterns with JSON Hyper-Schema

I did a deep dive into JSON Hyper-Schema, and wrote a guide to help others learn the specification without having to read the specification.

In this third and final part I build upon the previous articles and explain how JSON Hyper-Schema works with common hypermedia patterns.

Read part three on the APIs you won’t hate blog

Posted toTechon8/22/2018

Roads API at REST Fest

In this talk I gave a brief overview of my latest project, Roads API. This framework attempts to simplify many aspects of the API development process.

Or watch on Vimeo

Posted toTechon4/21/2018

Getting started with JSON Hyper-Schema: Part 2

I did a deep dive into JSON Hyper-Schema, and wrote a guide to help others learn the specification without having to read the specification.

In part two I build upon the foundation of part one with the addition of resource representations, arbitrary request bodies, HTTP headers and HTTP methods

Read part two on the APIs you won’t hate blog

Posted toTechon4/3/2018

Getting started with JSON Hyper-Schema

I did a deep dive into JSON Hyper-Schema, and wrote a guide to help others learn the specification without having to read the specification.

In part one I describe the basics: Why its useful, JSON Schema and the foundations of JSON Hyper-Schema.

Read part one on the APIs you won’t hate blog

Posted toTechon12/21/2017