Skip to content

Instantly share code, notes, and snippets.

@tompng
Created January 19, 2026 15:47
Show Gist options
  • Select an option

  • Save tompng/1e5e072b83a51fea96b870eef4b95ac9 to your computer and use it in GitHub Desktop.

Select an option

Save tompng/1e5e072b83a51fea96b870eef4b95ac9 to your computer and use it in GitHub Desktop.
Render RubyKaigi 2026 logo to canvas
<canvas width="1600" height="1600" style="background:black"></canvas>
<script>
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const outer = [...new Array(30)].map((_, i) => {
const rot = 0.00
const th1 = Math.PI * 2 * (i - i % 6) / 30 + rot
const th2 = th1 + Math.PI * 2 / 5
const th = th1 + [0, 0.335, 0.34, 0.5, 0.66, 0.665][i % 6] * (th2 - th1)
const r = [1, 0.71, 0.62, 0.65, 0.62, 0.71][i % 6]
return { x: r * Math.cos(th), y: r * Math.sin(th) }
})
const outerXs = outer.map(p => p.x)
const outerYs = outer.map(p => p.y)
const offset = 16
const xmin = Math.min(...outerXs), xmax = Math.max(...outerXs)
const ymin = Math.min(...outerYs), ymax = Math.max(...outerYs)
const scale = Math.min((canvas.width - offset * 2) / (xmax - xmin), (canvas.height - offset * 2) / (ymax - ymin))
console.log(canvas.width / (xmax - xmin), canvas.height / (ymax - ymin))
ctx.translate((canvas.width - scale * (xmax - xmin)) / 2, (canvas.height - scale * (ymax - ymin)) / 2)
ctx.scale(scale, scale)
ctx.translate(-xmin, -ymin)
ctx.translate(0.003, -0.001)
const rubyParam = { x: 0.026, y: -0.01, centerWidth: 0.555, topLen: 0.18 }
rubyParam.bottomLen = rubyParam.centerWidth / 2 * (outer[18].y - rubyParam.y) / (outer[18].x - (rubyParam.x - rubyParam.centerWidth / 2))
rubyParam.topWidth = rubyParam.centerWidth + 2 * rubyParam.topLen * (outer[24].x - (rubyParam.x - rubyParam.centerWidth / 2)) / (outer[24].y - rubyParam.y)
ctx.fillStyle = 'white'
const rubyCoords = [
{ x: rubyParam.x - rubyParam.centerWidth / 2, y: rubyParam.y },
{ x: rubyParam.x - rubyParam.topWidth / 2, y: rubyParam.y - rubyParam.topLen },
{ x: rubyParam.x + rubyParam.topWidth / 2, y: rubyParam.y - rubyParam.topLen },
{ x: rubyParam.x + rubyParam.centerWidth / 2, y: rubyParam.y },
{ x: rubyParam.x, y: rubyParam.y + rubyParam.bottomLen },
]
ctx.beginPath()
ctx.moveTo(outer[0].x, outer[0].y)
for (let i = 1; i < outer.length; i++) {
ctx.lineTo(outer[i].x, outer[i].y)
}
ctx.closePath()
ctx.clip()
function normalize(v) {
const l = Math.hypot(v.x, v.y)
return { x: v.x / l, y: v.y / l }
}
ctx.beginPath()
for (let i = 0; i < 5; i++) {
const distAdjust = [1, 1.01, 1.01, 0.997, 1][i]
const a = rubyCoords[i]
const start = rubyCoords[(i + 1) % 5]
const c = rubyCoords[(i + 2) % 5]
const lineDir = normalize({ x: c.x - start.x, y: c.y - start.y })
const dir = normalize({ x: start.x - a.x, y: start.y - a.y })
const dot = lineDir.x * dir.x + lineDir.y * dir.y
const dist = 0.02134 / Math.sqrt(1 - dot * dot)
const len = 1.5
for (let j = 0; ; j++) {
const t = j * dist * distAdjust
const dtmin = dist / 10
const dt = dist * 0.5 * Math.exp(-j/16.0) + dtmin
const sx = start.x + dir.x * t, sy = start.y + dir.y * t
if (Math.hypot(sx, sy) > 1.2) break
ctx.moveTo(sx, sy)
ctx.lineTo(start.x + dir.x * (t+dt), start.y + dir.y * (t+dt))
ctx.bezierCurveTo(
start.x + dir.x * (t + dtmin + (dt - dtmin) / 4) + len * lineDir.x / 4,
start.y + dir.y * (t + dtmin + (dt - dtmin) / 4) + len * lineDir.y / 4,
start.x + dir.x * (t + dtmin + (dt - dtmin) / 8) + len * lineDir.x / 2,
start.y + dir.y * (t + dtmin + (dt - dtmin) / 8) + len * lineDir.y / 2,
start.x + dir.x * (t + dtmin) + len * lineDir.x,
start.y + dir.y * (t + dtmin) + len * lineDir.y
)
ctx.lineTo(start.x + dir.x * t + len * lineDir.x, start.y + dir.y * t + len * lineDir.y)
ctx.closePath()
}
}
ctx.fill()
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment