Synthwave slides

One of the less fun parts of trying to run a company is having to create all kinds of marketing materials, such as product and investor slides.

I already use reveal.js for all my slideshow needs, so there's one thing that will make the job bearable:

A scrolling synthwave-style background made with HTML+CSS.

The synthwave style

As you may have noticed, the 80s are back (again), but it's called synthwave now. When this style first appeared, it spoke to me on a level I haven't felt since the 2000s. That's why this page looks the way it does, and also why all my marketing videos have synthwave background music.

Let's see what google images thinks about synthwave backgrounds:

Google image screenshot

Decomposed to its key elements:

Since this is going to be a background for slides, I'm going to ditch the sun and also dial back some of the gradients. A cityscape sounds easier to do right (since it's just a bunch of rectangles) than mountains, so we are going with that too. It also gives more of a cyberpunk feel instead of the distinct Californian/Nevadan desert style.

Foundations

For the rapid prototyping phase, I just made a quick html:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<html>
    <head>
        <title>Synthwave bg</title>
        <style>
            body {
                margin: 0;
            }
            .background {
                z-index: -1;
                position: absolute;
                top: 0;
                bottom: 0;
                left: 0;
                right: 0;
                overflow: hidden;
                background: black;
                background: linear-gradient(indigo, purple);
            }
            .content_container {
                display: flex;
                align-items: center;
                justify-content: center;
                margin: 0;
                height: 100vh;
                color: white;
                font-family: sans-serif;
            }
            .test_content {
                text-align: center
            }
            h1 {
                font-size: 50pt;
            }
            p {
                font-size: 24pt;
            }
        </style>
    </head>
    <body>
        <div class="background"></div>
        <div class="content_container">
            <div class="test_content">
                <h1>Test text</h1>
                <p>To see if visibility is good</p>
            </div>
        </div>
    </body>
</html>

Please don't judge me too harshly. While I did some HTML and CSS was back in 2007 for Stickman Warfare (archive site), but I outsourced that to much more capable people as quickly as I could. I also did some CSS work for AJDB, and of course this site, but I'm in no way a frontend guy.

Consider this a "for fun" thing than a best practices guide. At least I can center a div though :D

Anyway, the above html looks like this:

Test screenshot

The grid

We can now experiment with a background div and its corresponding css.

Looking through some cute code, the chic way of making grids is gradients and background-size magic to make it repeat. Let's give it a try.

First off, let's make a test-div inside the background div:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<style>
    /* ... */
    .grid {
        background:
            linear-gradient(
                transparent 45%,
                white 48%,
                white 52%,
                transparent 55%
            ),
            linear-gradient(to right,
                transparent 45%,
                white 48%,
                white 52%,
                transparent 55%
            )
        ;
        position: absolute;
        width: 100%;
        height: 100%;
    }
</style>
<!-- ... -->
<div class="background">
    <div class="grid"></div>
</div>

As you can see, we can use gradients to draw a line, and we can also use multiple gradients to compose the background. We can later modify this gradient to have an even softer glow around the lines too, but let's do that in the finishing steps.

This gives us a single grid unit:

Test screenshot

It's more of a cross I guess. But this is where a somewhat disgusting magic comes into play: the background-size CSS property.

1
2
3
4
.grid {
    /* ... */
    background-size: 50px 50px;
}

This sets the size of the background "image" (generated by the gradients), and the auto-repeat does the rest:

Test screenshot

Visibility is not that good, eh? We are on it.

3d-ifying the grid

Apparently there are 3d transforms in CSS now. And this is what we are going to do to our grid.

For this to work, we need to create a container div with the perspective property. This is going to be our viewport for the perspective transformations, basically. We need the grid to be in the lower half of the screen, with the vanishing point of the perspective being the top of this div:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<style>
    /* ... */
    .ground {
        perspective: 50vh;
        perspective-origin: top;
        position: absolute;
        top: 50%;
        height: 50%;
        left: 0;
        width: 100%;
    }
</style>
<!-- ... -->
<div class="background">
    <div class="ground">
        <div class="grid"></div>
    </div>
</div>

At this point, nothing happens yet, because the grid itself is not transformed at all:

Test screenshot

But once we do transform it, the magic finally happens:

1
2
3
4
.grid {
    /* ... */
    transform: rotateX(90deg);
}

Test screenshot

We are getting somewhere, but as you can see, we are running out of grid. This is because the size of our grid is finite, and transformation is done after it got "rendered". We just have to adjust our transformations a bit:

1
2
3
4
5
6
7
8
.grid {
    /* ... */
    width: 200%;
    height: 100%;
    background-size: 50px 30px;
    transform: rotateX(90deg) translateX(-50%);
    transform-origin: 0 10%;
}

BTW, all those percentages create a "responsive" grid, for better or worse:

Test screenshot Test screenshot

Now we just have to make it vanish on the horizon, since it starts to look pretty pixelated towards the center (due to the lack of mipmapping and anisotropic filtering that we usually have in proper 3D).

We will use one of the oldest 3D tricks in the book: distance fog. We also adjust the global background gradient a bit for now:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<style>
    .background {
        /* ... */
        background: linear-gradient(indigo, #626 50%);
    }
    /* ... */
    .distance_fog {
        position: absolute;
        width: 100%;
        height: 30%;
        top: 45%;
        background:
            linear-gradient(
                transparent,
                #404 30%,
                #404 40%,
                transparent
            );
    }
</style>
<!-- ... -->
<div class="background">
    <div class="ground">
        <div class="grid"></div>
    </div>
    <div class="distance_fog"></div>
</div>

Test screenshot

I love how every color is a synthwave color as long as it doesn't contain too much green.

Cityscape

Next on our list is some buildings on the horizon.

To be honest, first I wanted to create a python or js script to generate a bunch of rectangles, but this seemed more and more pointless and inelegant the more I thought about it.

Also I've been using Inkscape lately, and I kind of love it, so I decided to just make a city horizon SVG. It's still a bunch of rectangles, but it's π“ͺ𝓻𝓽𝓲𝓼π“ͺ𝓷π“ͺ𝓡 now.

It is bright purple on transparent, so we can more easily colorize it with the fancy filter: brightness(...) CSS property. The reason for this is that I want multiple layers of this stuff so it looks extra 3d.

After this bit of manual CSS-ing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<style>
    /* ... */
    .city_skyline_1,
    .city_skyline_2,
    .city_skyline_3 {
        position: absolute;
        width: 100%;
        height: 20%;
        bottom: 45%;
        background: url('./bg.svg');
        background-size: contain;
    }
    .city_skyline_1 {
        background-position-x: 100px;
        filter: brightness(10%);
    }
    .city_skyline_2 {
        background-position-x: 300px;
        filter: brightness(5%);
    }
    .city_skyline_3 {
        background-position-x: 500px;
        filter: brightness(0%);
    }
</style>
<!-- ... -->
<div class="ground"><!-- ... --></div>
<div class="city_skyline_1"></div>
<div class="city_skyline_2"></div>
<div class="city_skyline_3"></div>
<div class="distance_fog"></div>

We get this image straight out of a Microsoft Excel 97 template:

Test screenshot

Finishing touches

Since this is going to be used in a somewhat serious manner, the colors, gradients and such need to be tweaked a bit so it harmonizes more, and also doesn't clash with the foreground, because let us not forget, this is a background, not the star of the presentation.

Test screenshot

The final result can be downloaded as html. Licence is CC BY 4.0.

Integrating into reveal.js

Reveal.js has its own system for backgrounds: it creates a background div for each slide, and then transitions between them. This works great if you have different backgrounds for different slides, especially if some of them are GIFs or videos, but for our purposes it's actually overkill.

Thankfully, we don't need to use this system, we can just directly insert our divs into the HTML, and it will render correctly. This of course breaks PDF export, but that's pretty janky anyway, and can be done another day.

So what I did was just copy-paste the CSS part into the theme CSS, copy-paste the background div before the reveal div, and it worked out of the box.

One last step to do was to subscribe to the slide change event, and do some background offset magic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Reveal.on( 'slidechanged', event => {
    function setOffset(selector, multiplier, offset) {
        var element = document.querySelector(selector);
        element.style.backgroundPositionX = (
            element.offsetWidth
            * 0.01
            * multiplier
            * -event.indexh
            + offset
        ) + 'px';
    }
    setOffset(".grid", 1.5, 0);
    setOffset(".city_skyline_1", 2, 100);
    setOffset(".city_skyline_2", 2.5, 300);
    setOffset(".city_skyline_3", 3, 500);
})

One last-last step to do is set transition: background-position-x 400ms; on all affected classes.

The end result is quite pretty if I say so myself:


If this doesn't convince investors and customers that we are incredibly high tier, nothing will.



If you need Augmented Reality problem solving, or want help implementing an AR or VR idea, drop us a mail at info@voidcomputing.hu