Logging continues unabated. Find out more.
Get occasional updates about new fonts, designs and other interesting things.
import badge from "/scratchpad/_lib/badge.asset.mjs"
import * as random from "/scratchpad/_lib/random.asset.mjs"
import sequence from "/scratchpad/_lib/sequence.asset.mjs"
import posterize from "/scratchpad/posterize/posterize.asset.mjs"
const colors = {
light: "#f9f0d9",
mid: "#e7dbc4",
dark: "#51433c",
}
export default (canvas) => {
const scale = window.devicePixelRatio || 1
canvas.width = canvas.offsetWidth * scale
canvas.height = canvas.offsetHeight * scale
const ctx = canvas.getContext("2d", {
willReadFrequently: true,
})
ctx.fillStyle = colors.mid
ctx.beginPath()
ctx.rect(0, 0, canvas.width, canvas.height)
ctx.fill()
const inner = Math.min(canvas.width, canvas.height)
const radius = (inner * random.wobble(0.7, 0.2)) / 2
const features = {
radius,
ringCount: random.integer(80, 160),
pathCount: random.integer(40, 160),
radialBase: inner / 400,
radialShift: inner / 8,
limitBase: inner / 480,
limitShift: inner / 96,
pointStart: inner / 1600,
pointShift: inner / 4800,
radialJump: [inner / 120, inner / 40],
innerWidth: inner / 2000,
outerWidth: [inner / 180, inner / 60],
extrusionAngle: random.number(Math.PI * 2),
extrusionLength: Math.floor(random.wobble(inner / 3, inner / 6)),
scratchWobble: inner / 5,
}
features.center = {
x:
canvas.width / 2 -
random.number(
(Math.cos(features.extrusionAngle) * features.extrusionLength) / 2
),
y:
canvas.height / 2 -
random.number(
(Math.sin(features.extrusionAngle) * features.extrusionLength) / 2
),
}
features.rings = createRings(features)
return sequence([
base.bind(null, ctx, features),
rings.bind(null, ctx, { features, rings: features.rings.slice(-20) }),
() =>
posterize({
ctx,
radius: inner / 120,
ramp: inner / 80,
colors: [colors.dark],
background: colors.mid,
}),
rings.bind(null, ctx, features),
cracks.bind(null, ctx, features),
sprinkles.bind(null, ctx, features),
() =>
posterize({
ctx,
radius: 1,
ramp: 8,
colors: [colors.light, colors.dark],
background: colors.mid,
overshoot: 0.5,
}),
])
}
const sprinkles = function (ctx, features) {
ctx.fillStyle = colors.dark
ctx.strokeStyle = colors.dark
const cx = features.center.x
const cy = features.center.y
ctx.save()
ctx.beginPath()
for (var j = 0; j < random.integer(5, 20); j++) {
const a = random.number(Math.PI * 2)
const d = random.number(features.radius)
const r = random.wobble(2, 1)
for (var i = 0; i < random.integer(5, 60); i++) {
const x = cx + d * Math.cos(a) + random.wobble(features.radius / 10)
const y = cy + d * Math.sin(a) + random.wobble(features.radius / 10)
ctx.moveTo(x + r, y)
ctx.arc(x, y, r, 0, Math.PI * 2)
}
}
ctx.fill()
ctx.restore()
}
const cracks = (ctx, features) => {
ctx.fillStyle = colors.dark
ctx.strokeStyle = colors.dark
const pointWobble = features.radius / 40
const cx = features.center.x
const cy = features.center.y
ctx.save()
ctx.beginPath()
for (var j = 0; j < random.integer(10, 100); j++) {
const a = random.wobble(Math.PI * 2)
const r1 = random.number(features.radius)
const r2 = Math.min(
r1 + random.number(features.radius / 2),
features.radius
)
const r3 = Math.abs((r1 + r2) / 2)
const x1 = cx + r1 * Math.cos(a) + random.wobble(pointWobble)
const y1 = cy + r1 * Math.sin(a) + random.wobble(pointWobble)
const x2 = cx + r2 * Math.cos(a) + random.wobble(pointWobble)
const y2 = cy + r2 * Math.sin(a) + random.wobble(pointWobble)
const x3 = cx + r3 * Math.cos(a) + random.wobble(pointWobble)
const y3 = cy + r3 * Math.sin(a) + random.wobble(pointWobble)
const x4 = random.wobble(x3, pointWobble)
const y4 = random.wobble(y3, pointWobble)
ctx.moveTo(x1, y1)
ctx.lineTo(x3, y3)
ctx.lineTo(x2, y2)
ctx.lineTo(x4, y4)
ctx.lineTo(x1, y1)
}
ctx.fill()
ctx.stroke()
ctx.restore()
}
const base = (ctx, features) => {
ctx.save()
const theta = features.extrusionAngle
const count = features.extrusionLength
let shift = 0
let scale = 0.1
const { paths } = features.rings.slice(-1)[0]
const w2 = ctx.canvas.width / 2
const h2 = ctx.canvas.height / 2
for (let i = count; i >= 0; i--) {
scale = random.wobble(0.05, 0.05)
shift = random.wobble(shift, Math.PI / 200)
const f = i / count
let s = 1.005 - scale * f + 0.01
const x = Math.cos(theta + shift * f) * i
const y = Math.sin(theta + shift * f) * i
ctx.save()
if (i === 0) {
shift = 0
s = 1
ctx.fillStyle = colors.mid
} else {
ctx.fillStyle = colors.dark
}
ctx.beginPath()
ctx.translate(w2, h2)
ctx.scale(s, s)
ctx.translate(-w2, -h2)
ctx.translate(x, y)
for (let j = 0; j < paths.length; j++) {
const path = paths[j]
ctx[j === 0 ? "moveTo" : "bezierCurveTo"](path)
}
ctx.fill()
ctx.restore()
}
for (let i = 0; i < 100; i++) {
baseScratches(ctx, features)
}
ctx.restore()
}
const rings = (ctx, features) => {
ctx.save()
// Render each ring
for (let i = 0; i < features.rings.length; i++) {
const { paths, style } = features.rings[i]
ctx.beginPath()
Object.entries(style).forEach(([key, val]) => (ctx[key] = val))
for (let j = 0; j < paths.length; j++) {
const path = paths[j]
ctx[j === 0 ? "moveTo" : "bezierCurveTo"](path)
}
ctx.closePath()
ctx.stroke()
}
// Small hatches perpendicular to rings
ctx.globalAlpha = 1
ctx.strokeStyle = colors.dark
ctx.lineWidth = random.wobble(features.radius / 200, features.radius / 500)
const hatchLength = features.radius / 150
ctx.beginPath()
features.rings
.slice(
random.integer(features.ringCount),
random.integer(features.ringCount)
)
.forEach(({ paths }) => {
for (let j = 0; j < paths.length; j++) {
if (random.maybe(0.3)) continue
const [x1, y1, x2, y2] = paths[j]
let t = Math.atan2(y2 - y1, x2 - x1) + Math.PI / 2
let d = random.wobble(hatchLength, hatchLength / 4)
t = t || 0
ctx.moveTo(x1 - Math.cos(t) * d, y1 - Math.sin(t) * d)
ctx.lineTo(x1 + Math.cos(t) * d, y1 + Math.sin(t) * d)
}
})
ctx.stroke()
ctx.restore()
}
const baseScratches = (ctx, features) => {
const { extrusionAngle, radius, scratchWobble, center } = features
ctx.save()
ctx.lineWidth = random.number(radius / 60)
ctx.strokeStyle = colors.mid
ctx.lineCap = "round"
const x1 =
center.x - random.wobble(Math.cos(extrusionAngle) * radius, radius * 2)
const y1 =
center.y - random.wobble(Math.sin(extrusionAngle) * radius, radius * 2)
const x2 = x1 + Math.cos(extrusionAngle) * radius * 3
const y2 = y1 + Math.sin(extrusionAngle) * radius * 3
ctx.beginPath()
ctx.moveTo(x1, y1)
ctx.bezierCurveTo(
random.wobble(x1 + (x2 - x1) * 0.33, scratchWobble),
random.wobble(y1 + (y2 - y1) * 0.33, scratchWobble),
random.wobble(x1 + (x2 - x1) * 0.66, scratchWobble),
random.wobble(y1 + (y2 - y1) * 0.66, scratchWobble),
x2,
y2
)
ctx.stroke()
ctx.restore()
}
const createRings = (features) => {
const { ringCount, pathCount } = features
let cx = features.center.x
let cy = features.center.y
let radius = features.radius * 1.1
let sx, sy, rw, r, a0
let outerIndex = ringCount - random.integer(3)
const radii = Array.from({ length: pathCount }, () => random.wobble(12))
const angls = Array.from({ length: pathCount }, () => random.wobble(0.01))
sx = sy = rw = r = a0 = 0
return Array.from({ length: ringCount }, (e, ring) => {
const factor = ring / (ringCount - 1)
const style = {}
// Outer rings
if (ring >= outerIndex) {
style.lineWidth = random.number(features.outerWidth)
style.strokeStyle = colors.dark
style.globalAlpha = random.maybe() ? 0 : random.wobble(0.75, 0.15)
}
// Inner rings
else {
const mult = Math.sin(factor * Math.PI)
style.strokeStyle = colors.dark
style.globalAlpha = 1
style.lineWidth = random.wobble(
features.innerWidth * 1.5 + mult * features.innerWidth * 2,
features.innerWidth * (1 + mult)
)
if (random.maybe(0.082)) {
style.strokeStyle = colors.light
style.lineWidth *= random.integer(2, 12)
}
}
rw = random.wobble(rw, 4)
r = random.wobble(rw + factor * radius, 8)
a0 = random.wobble(a0, 1 - (ring + 1) / pathCount)
const paths = Array.from({ length: pathCount }, (e, i) => {
angls[i] = random.wobble(angls[i], 0.001)
const diff = 0.004 / Math.abs(0.004 - angls[i])
const limit = (features.limitBase + features.limitShift) * diff * factor
const amount =
features.radialBase / pathCount +
(features.radialShift / pathCount) * factor
radii[i] = random.wobble(radii[i], amount, -limit, limit)
const r1 = r + radii[i]
const r2 = r + radii[(i + 1) % radii.length]
const a1 = angls[i] + Math.PI * 2 * ((i - 1) / pathCount) + a0
const a2 =
angls[(i + 1) % angls.length] + Math.PI * 2 * (i / pathCount) + a0
const x1 = cx + r1 * Math.cos(a1)
const y1 = cy + r1 * Math.sin(a1)
let x4 = cx + r2 * Math.cos(a2)
let y4 = cy + r2 * Math.sin(a2)
const wx2 = random.wobble(features.pointStart, features.pointShift)
const wy2 = random.wobble(features.pointStart, features.pointShift)
const wx3 = random.wobble(features.pointStart, features.pointShift)
const wy3 = random.wobble(features.pointStart, features.pointShift)
const x2 = x1 + (r1 * Math.cos(a1 + Math.PI / 2)) / (pathCount / wx2)
const y2 = y1 + (r1 * Math.sin(a1 + Math.PI / 2)) / (pathCount / wy2)
const x3 = x4 - (r2 * Math.cos(a2 + Math.PI / 2)) / (pathCount / wx3)
const y3 = y4 - (r2 * Math.sin(a2 + Math.PI / 2)) / (pathCount / wy3)
cx += random.wobble(features.pointShift)
cy += random.wobble(features.pointShift)
if (i === 0) {
sx = x1
sy = y1
return [x1, y1]
} else {
if (i === pathCount - 1) {
x4 = sx || 0
y4 = sy || 0
}
return [x2, y2, x3, y3, x4, y4]
}
})
return {
style,
paths,
}
})
}