Magical Mirai Programming Contest 2024

This contest was a challenge to build a web app using the TextAlive API to display a song's lyrics in time with the music.

The finished product is here, and the source here.

A big theme in this project was taking ideas that would be hard to implement as described and reframing them in terms of things that looked effectively the same, but that take advantage of the browser being able to handle the hard parts.

For example, as the song plays, lines are gradually drawn starting from the schematic, then stop, then a text box for lyrics appears. Rather than drawing a line using JavaScript, there is actually a set of lines already hard-coded into the SVG file! These lines have CSS classes applied to hide them until needed. Once a line needs to be drawn, the class that turns off its visibility is replaced with one that animates a clip-path: inset from hiding the whole line at the start to hiding none of it at the end, smoothly over time.

These are the relevant styles for the lines that appear on the right side. There are analogous styles for the other side, which have to be drawn in the other direction. The lines (internally referred to as "pointers") have the pointer-hidden class most of the time, until it is replaced with pointer-right-show which toggles its visibility and starts the animation.

@keyframes draw-pointer-right {
    0% {
        clip-path: inset(0% 100% 0% 0%);
    }

    100% {
        clip-path: inset(0% 0% 0% 0%);
    }
}

.pointer-hidden{
    visibility: hidden;
}

.pointer-right-show {
    animation: draw-pointer-right 2s 1;
}

Once a line is done drawing, its text box also uses a CSS animation to appear. To help with the timing, an event listener is added to the lines that waits for their animation to finish, then swaps in the CSS classes on the text box to make it appear.

Originally, these lines and boxes were all there was to it, but I had a last-minute idea to add some interactivity. The lyric boxes would function as alerts for the user-operator as to where the unit was receiving damage. They would have to hold down their mouse or finger to gradually repair that zone and prevent further damage while it was still active, with a score given at the end. Unfortunately, this was very last minute. Like, a couple of days before the deadline with only so many available hours. But! Fortunately! This same principle of implementing something that looked the same as what was described make it possible to implement at least the core idea, with some of the details scrapped.

Originally, the plan was to maintain a damage counter for each zone. When it was active, it would gradually increase. While repairing, it would gradually decrease. Depending on its value, the colour of the outline would gradually change to red. The code for this would have to account for whether the music was paused, and whether the zone was active, and keeping all of these variables in sync would have taken some time and effort.

But these counters and implementation details would be mostly hidden. What a user would see was a zone getting redder while active, then yellower once they clicked on it. The trick, then, was to apply a CSS class to a zone once it became active that would change its colour over some period. The repair mechanic had to be simplified a bit, with repairs becoming instantaneous by removing this class. This time, a CSS transition was used instead of an animation. These don't need keyframes, just a target value.

Because this was last minute, this had to be kinda hacked together by shoving the shapes of the SVG into SVG Groups so that the classes powering this could be applied to a single element, and as a result the CSS is not very clean. Each SVG shape (most of them are arbitrary polygons drawn with path, but some of the details (okay, just one) are made of rect and circle instead, so all of those elements have to be in the selector) gets a default style. Once the active class is applied to a group, the colour is overriden from the default (defined in the SVG) with a transition-duration used to make this change happen over time. When the class is removed due to the user clicking an active zone, the default style's transition-duration causes the change back to the original colour to be instant.

g > path, g > rect, g > circle {
    transition-property: stroke;
    transition-duration: 0ms;
}

.active > path, .active > rect, .active > circle {
    stroke: #e24c31 !important;
    transition-duration: 5s !important;
}

Because this idea of damage was show-bizzed away into just being a visual effect, there wasn't really any way to assign a score. But, it is still very impressive just how much of this idea could be pulled off using visuals alone.