console.log

Logging continues unabated. Find out more.

Loading...
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,
    }
  })
}