Get occasional updates about new fonts, designs and other interesting things.
import posterize from "/scratchpad/posterize/posterize.asset.mjs"
import palette from "/scratchpad/_lib/palette.asset.mjs"
import * as random from "/scratchpad/_lib/random.asset.mjs"
const connect = (ctx, points) => {
points.forEach(([x, y], i) => {
ctx[i === 0 ? "moveTo" : "lineTo"](x, y)
})
}
const setup = (canvas) => {
canvas.width = canvas.height =
canvas.offsetWidth * window.devicePixelRatio * 0.6
}
const draw = (canvas, isLongPause) => {
const ctx = canvas.getContext("2d")
const { width: w, height: h } = canvas
ctx.clearRect(0, 0, w, h)
ctx.save()
const face = [
// Left cheek
[random.wobble(w * 0.15, w * 0.10), random.wobble(h * 0.48, h * 0.12)],
[random.wobble(w * 0.20, w * 0.10), random.wobble(h * 0.25, h * 0.08)],
[random.wobble(w * 0.30, w * 0.10), random.wobble(h * 0.15, h * 0.08)],
// Crown
[random.wobble(w * 0.50, w * 0.10), random.wobble(h * 0.15, h * 0.05)],
[random.wobble(w * 0.70, w * 0.10), random.wobble(h * 0.15, h * 0.08)],
[random.wobble(w * 0.80, w * 0.10), random.wobble(h * 0.25, h * 0.08)],
// Right cheek
[random.wobble(w * 0.85, w * 0.10), random.wobble(h * 0.48, h * 0.12)],
[random.wobble(w * 0.80, w * 0.10), random.wobble(h * 0.75, h * 0.15)],
// Chin
[random.wobble(w * 0.50, w * 0.10), random.wobble(h * 0.85, h * 0.05)],
[random.wobble(w * 0.20, w * 0.10), random.wobble(h * 0.75, h * 0.15)],
]
const eye1 = [
[random.wobble(w * 0.33, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.33, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.33, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.33, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.33, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
]
const eye2 = [
[random.wobble(w * 0.67, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.67, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.67, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.67, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
[random.wobble(w * 0.67, w * 0.05), random.wobble(h * 0.38, h * 0.05)],
]
const mouth = [
[random.wobble(w * 0.35, w * 0.05), random.wobble(h * 0.60, h * 0.08)],
[random.wobble(w * 0.45, w * 0.05), random.wobble(h * 0.65, h * 0.04)],
[random.wobble(w * 0.50, w * 0.05), random.wobble(h * 0.65, h * 0.08)],
[random.wobble(w * 0.55, w * 0.05), random.wobble(h * 0.65, h * 0.04)],
[random.wobble(w * 0.65, w * 0.20), random.wobble(h * 0.60, h * 0.08)],
[random.wobble(w * 0.50, w * 0.05), random.wobble(h * 0.60, h * 0.08)],
[random.wobble(w * 0.35, w * 0.12), random.wobble(h * 0.60, h * 0.08)],
]
connect(ctx, face)
ctx.fillStyle = palette.canvas
ctx.fill()
posterize({
ctx,
radius: w * 0.1,
ramp: w * 0.1,
colors: [palette.canvas],
x: w * 0.1,
y: h * 0.1,
width: w * 0.8,
height: h * 0.8,
})
const shift = (arr) => {
const dx = random.wobble(w * 0.04)
const dy = random.wobble(w * 0.12)
arr.forEach((p) => {
p[0] = p[0] + dx
p[1] = p[1] + dy
})
}
shift(eye1)
shift(eye2)
ctx.beginPath()
connect(ctx, eye1)
connect(ctx, eye2)
ctx.lineJoin = "round"
ctx.lineCap = "round"
ctx.strokeStyle = palette.dark
ctx.lineWidth = random.wobble(w * 0.04, w * 0.02)
ctx.stroke()
if (isLongPause) {
mouth.forEach((p) => {
p[0] = (p[0] - w / 2) / 2 + w / 2
p[1] = (p[1] - h / 2) / 2 + h / 2
})
}
ctx.beginPath()
connect(ctx, mouth)
ctx.lineWidth = random.wobble(w * 0.06, w * 0.03)
ctx.stroke()
// Posterize around each shape
;[mouth, eye1, eye2].forEach((shape) => {
const xs = shape.map((p) => p[0])
const ys = shape.map((p) => p[1])
const minX = Math.min(xs) - w * 0.04
const maxX = Math.max(xs) + w * 0.04
const minY = Math.min(ys) - w * 0.04
const maxY = Math.max(ys) + w * 0.04
posterize({
ctx,
radius: w * 0.03,
ramp: w * 0.04,
colors: [palette.canvas],
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
})
})
}
export default async (canvas) => {
let timeout
setup(canvas)
;(function loop() {
const isLongPause = random.maybe(0.03)
draw(canvas, isLongPause)
timeout = window.setTimeout(loop, isLongPause ? 1000 : 100)
})()
return () => window.clearTimeout(timeout)
}