Footer

Generative vector backdrops

One of the goals of the colour scheme was to better integrate visualisation and texture into the layouts. The footer is an opportunity to test this out and have a bit of fun with the expressive possibilities of the system.

Next, deconstruct the SVG elements into parameterised fragments that can be produced by a grammar.

The two logical places for this to be embedded would be:

  • Embedded in the source templates (Ruby to HTML generator)
  • Embedded in the browser runtime (Javascript to HTML generator)

If I write the SVG generator as a grammar, I can defer that decision—try out one or other or both, it doesn’t really matter.

From the perspective of readers, it might be more interesting if the page changed every time it was refreshed, rather than staying locked in static markup between multiple publishing runs of the site. So let’s stuff it into a web component for now.

First stop is a nearby vector graphics app (Sketch), to dump out some interesting geometric SVG paths.

const footerContainer = document.querySelector(".footer-container");
const decayedBackdrop = document.createElement('canvas');
const decayedCanvas = decayedBackdrop.getContext('2d');
const transferBuffer = new Image();

transferBuffer.width = containerWidth * hiDpiRatio
transferBuffer.height = containerHeight * hiDpiRatio
transferBuffer.onload = function() {
  decayedCanvas.drawImage(
    transferBuffer,
    0,
    0,
    transferBuffer.width,
    transferBuffer.height
  );
}


const decoration = document.querySelector(".decoration");

const horizontalSpacing = [...Array(600).keys()];

const fillOpacity = [100];

const rowStops = {
  rowstop1: Math.floor(Math.random() * 600).toString(),
  rowstop2: Math.floor(Math.random() * 600).toString(),
  rowstop3: Math.floor(Math.random() * 600).toString()
};

const cuboid = `
<g id="cuboid" transform="scale({@transform} {@transform})">
    <polygon id="Shape" fill="{spot_colour}" points="85.8242857 81.3218571 85.8242857 81.2617143 97.9731429 88.2984286 97.9731429 88.3585714 55.3318571 112.957 55.3318571 112.957 12.7507143 88.2984286 24.8995714 81.2617143 24.8995714 81.3218571 55.3318571 98.8835714 55.392 98.8835714"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="97.9731429 39.1015714 97.9731429 88.2984286 85.8242857 81.2617143 85.8242857 81.2617143 85.8242857 46.1382857 73.1341429 38.8008571 55.392 49.0852857 55.3318571 49.0852857 55.3318571 14.443"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="24.8995714 46.1382857 24.8995714 81.2617143 12.7507143 88.2984286 12.6905714 88.2984286 12.6905714 88.2984286 12.6905714 39.1015714 12.7507143 39.1015714 55.3318571 14.443 55.3318571 49.0852857 37.5897143 38.8008571"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="98.0332857 88.2984286 97.9731429 88.2984286 97.9731429 39.1015714 98.0332857 39.1015714 110.543 31.8242857 110.543 95.5757143 55.3318571 127.391286 55.3318571 112.957 97.9731429 88.3585714"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="85.8242857 81.2617143 85.8242857 81.3218571 55.392 98.8835714 55.3318571 98.8835714 55.3318571 63.7 85.8242857 46.1382857"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="55.3318571 112.957 55.3318571 127.391286 0.180857143 95.5757143 0.180857143 31.8242857 12.6905714 39.1015714 12.6905714 39.1015714 12.6905714 88.2984286 12.6905714 88.2984286 12.7507143 88.2984286"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="55.3318571 63.7 55.3318571 98.8835714 24.8995714 81.3218571 24.8995714 81.2617143 24.8995714 46.1382857"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="55.3318571 49.0852857 55.392 49.0852857 73.1341429 38.8008571 85.8242857 46.1382857 55.3318571 63.7 24.8995714 46.1382857 37.5897143 38.8008571"></polygon>
    <polygon id="Shape" fill="{spot_colour}" points="97.9731429 39.1015714 55.3318571 14.443 12.7507143 39.1015714 12.6905714 39.1015714 0.180857143 31.8242857 55.3318571 0.00871428571 110.543 31.8242857 98.0332857 39.1015714"></polygon>
</g>
`;

const grammar = calyx.grammar({
  "start": '<svg viewbox="0 0 600 150" width="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">{shapeset}</svg>',
  "shapeset": "{row1}{row2}{row3}{shape}{shape}{circle}{shape}{shape}{circle}{shape}{shape}{shape}{shape}",
  "row1": ['<rect x="0" y="0" width="{rowstop1}" height="50" fill="{spot_colour}" /><ellipse cx="{rowstop1}" cy="25" rx="25" ry="25" fill="{spot_colour}" fill-opacity="{opacity}"/>'],
  "row2": ['<rect x="0" y="50" width="{rowstop2}" height="50" fill="{spot_colour}" /><ellipse cx="{rowstop2}" cy="75" rx="25" ry="25" fill="{spot_colour}" fill-opacity="{opacity}"/>'],
  "row3": ['<rect x="0" y="100" width="{rowstop3}" height="50" fill="{spot_colour}" /><ellipse cx="{rowstop3}" cy="125" rx="25" ry="25" fill="{spot_colour}" fill-opacity="{opacity}"/>'],
  "shape": ["{block}", "{anglebar}", "{cuboid}"],
  "anglebar": '<rect x="{wpos}" y="0" width="{hpos}" height="150" transform="skewX(45)" fill="{spot_colour}" fill-opacity="{opacity}"/>',
  "block": '<rect x="{wpos}" y="{rstop}" fill="{spot_colour}" width="{@size}" height="{csize}" fill-opacity="{opacity}" />',
  "cuboid": cuboid,
  "circle": '<ellipse cx="{wpos}" cy="{csize}" fill="{spot_colour}" rx="50" ry="50" fill-opacity="{opacity}" />',
  "rstop": ["0", "50", "100"],
  "transform": ["1.5", "2", "2.5", "3", "3", "4", "5", "6"],
  "csize": [50,100].map(n => n.toString()),
  "size": [10,15,20,25,30,35,40,45,50].map(n => n.toString()),
  "opacity": fillOpacity.map(n => `${n}%`),
  "wpos": horizontalSpacing.map(n => n.toString()),
  "hpos": [10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,100,110,120,130].map(n => n.toString())
});

decoration.innerHTML = grammar.generate(rowStops).toString()