This content originally appeared on DEV Community and was authored by Ben Evans
No Images – No JavaScript – Pure HTML – Too much CSS!
In January, I watched the very cool YouTube documentary about how The Backrooms came to exist: https://www.youtube.com/watch?v=o3cTIn2Z_Ck
Because of this, I fell in love with The Backrooms’ origin story and had the idea of building on my previous 3D maze, mapping out the rooms to fit the photograph, and, this time, with the determination to make it work on Safari!
So I set to work.
As a proof of concept, I expanded the maze from a 7×7 grid to a 12×12, made everything beige, adding a ceiling and some wallpaper to the walls. Then I lined up the camera angle with the original 4Chan post’s image. All looked good; it was probably possible! But knowing full well that I’d made it even less likely to work on Safari, the next step was to try and solve that issue.
From trying to fix the original, I knew that Safari didn’t like the sheer size in pixels of the old maze. If I made the rooms tiny and if not too much went out of view, then it almost worked. So this time I scaled everything down. I made each ‘room’, or square of the grid, only 3x3rem; this helped, but it still crashed. Only when I narrowed down the path from a 12×12 grid to a 4×12 grid did things start to work.
I then spent the next three months working on a formula (I’m not very good at maths) to work out the position of the camera on the grid and remove cells that were not in view.
This is the formula to mark the position, not actually required for the final thing:
%test {
background: rgba( magenta,.9);
}
@for $x from 1 through $grid {
&:has(#x-#{$x}:checked) {
@for $y from 1 through $grid {
&:has(#z-#{$y}:checked) {
$yn1: ($y - 1);
$sum: ($yn1 * $grid + $x);
floor:nth-of-type(1) tile:nth-of-type(#{$sum}) {
@extend %test;
}
}
}
}
}
And it worked on Safari!
I then tidied everything up, and in doing so, learned a few useful things. Because of the size of these grids, I needed to make everything more efficient. When it came to mapping out the levels, I got this down to a fine art. I created @each
loops, so I could easily just add a batch of coordinates from the map.
$east: ( 16: 4 2, 20: 8 2, 28: 4 3, 31: 7 3, 33: 9 3, 40: 4 4, 43: 7 4, 46: 10 4, 47: 11 4, 49: 1 5, 59: 11 5, 61: 1 6, 64: 4 6, 67: 7 6, 73: 1 7, 76: 4 7, 79: 7 7, 82: 10 7, 83: 11 7, 88: 4 8, 94: 10 8, 95: 11 8, 106: 10 9, 107: 11 9, 109: 1 10, 112: 4 10, 118: 10 10, 119: 11 10, 121: 1 11, 124: 4 11, 129: 9 11, 131: 11 11 );
@each $key, $values in $east {
$first-value: nth($values, 1);
$second-value: nth($values, 2);
body:has(#level-1:checked):has(#x-#{$first-value}:checked):has(#z-#{$second-value}:checked):has(.x-rotation:checked) {
.downb {
@extend %d-none-i;
}
.downb[for="x-0"] {
@extend %d-block-i;
}
}
}
Getting the first and second values was super handy and simpler than I had imagined.
I also had to pay attention to how the SASS compiled the output and discovered the beautiful benefit of @extend
. I probably should have already known this, but now I really understand.
And if, like me, you don’t already know:
A loop would normally compile to each loop listing one after the other, for example, this:
z floor tile{
$short-wood-right: 1, 2, 3;
@each $i in $short-wood-right {
&:nth-of-type(#{$i}) span:before {
transform: translate3d(2.97rem, .5rem, 5.5rem);
}
}
}
Would output like this:
z floor tile:nth-of-type(1) span:before {
transform: translate3d(2.97rem, .5rem, 5.5rem);
}
z floor tile:nth-of-type(2) span:before {
transform: translate3d(2.97rem, .5rem, 5.5rem);
}
z floor tile:nth-of-type(3) span:before {
transform: translate3d(2.97rem, .5rem, 5.5rem);
}
Whereas by using @extend
, this:
%wood {transform: translate3d(2.97rem, .5rem, 5.5rem);}
z floor tile{
$short-wood-right: 1, 2, 3;
@each $i in $short-wood-right {
&:nth-of-type(#{$i}) span:before {
@extend %wood;
}
}
}
Would output like this:
z floor tile:nth-of-type(1) span:before, z floor tile:nth-of-type(2) span:before, z floor tile:nth-of-type(3) span:before {
transform: translate3d(2.97rem, .5rem, 5.5rem);
}
Which is much lighter to load, especially with as many loops as I am using.
So I tidied everything up, improved the wallpaper, added two floors, put some wall furnishings in, created some kind of storyline with a beginning and end, and added some scary elements. It didn’t seem quite complete without any sound, so I threw together some atmospheric loops and, much as it goes against the whole CSS only thing, I had to use a tiny bit to play the sounds but I made this the absolute bare minimum:
<script>t = i => document.querySelectorAll('audio').forEach(a => { a.id == i ? (a.paused && a.play()) : (a.pause(), a.currentTime = 0) })</script>
If anyone can get that to be any smaller, then please let me know.
In the end, the CSS was so big that I couldn’t fit the uncompiled SASS version into CodePen, so it’s just the outputted CSS. And I think I added back in so much detail that it probably doesn’t work on iPhones anymore. But, anyway, here it is; please let me know what you think I could do to improve the gameplay or the code, and the first person to share a screenshot of the end wins! Good luck in there
This content originally appeared on DEV Community and was authored by Ben Evans