Keycaps
A mechanical keyboard’s top-left corner, seen at a tilt. Escape is picked out, and the other keys fade into the plate.
Made with Claude Opus 5.5
- Technique
- flat shapes
- Shape
- Any screen
- Added
- 27 September 2026
Colours
- #1C1B1Abackground
- #DAD8CEforeground
- #CF6A4Caccent
Export
FormatThis browser can’t make WebP files.
Shapecropped from 16:9
Crop
SizeThis browser can’t draw a file that large.
Notes
Each keycap is four stacked rounded rectangles: the skirt, a top face set back so its front wall shows, a dish shaded at its back wall, and the floor of the dish. The keys follow the ANSI tenkeyless layout.
Sources
Source code
wallpapers/keycaps/design.py, 88 lines
"""A keyboard corner seen from above: keycaps as four stacked rounded rectangles, the Escape key picked out and the rest fading with distance from it."""
import math
from walldye import (
ACCENT,
ACCENT_2,
ACCENT_3,
ACCENT_HI,
BG_ALT,
BG_DEEP,
UI,
UI_ALT,
Canvas,
Colour,
P,
Vec,
design,
mix,
)
from walldye.geom import Affine
U = 150 # one key unit
ANGLE = 11 # board tilt, clockwise
# Tenkeyless ANSI: (row y, key widths), both in units; negative widths are gaps.
ROWS = (
(0.0, (1, -1, 1, 1, 1, 1, -0.5, 1, 1, 1, 1, -0.5, 1, 1, 1, 1, -0.25, 1, 1, 1)),
(1.5, (1,) * 13 + (2, -0.25, 1, 1, 1)),
(2.5, (1.5,) + (1,) * 12 + (1.5, -0.25, 1, 1, 1)),
(3.5, (1.75,) + (1,) * 11 + (2.25,)),
(4.5, (2.25,) + (1,) * 10 + (2.75, -1.25, 1)),
(5.5, (1.25,) * 3 + (6.25,) + (1.25,) * 4 + (-0.25, 1, 1, 1)),
)
BOARD = (18.25, 6.5) # key area in units
# Keycap layers in a 110-wide drawing of one unit cell: (left, top, width cut, height cut, radius).
# The top face sits back from the cell centre so its front wall shows, and the dish is shadowed
# at its back wall.
LAYERS = (
(5, 5, 10, 10, 12), # skirt
(17, 11, 34, 34, 10), # top face
(24, 17, 48, 48, 9), # dish
(25, 21, 50, 53, 8), # dish floor
)
def shading(skirt: Colour, top: Colour, lift: Colour) -> tuple[Colour, ...]:
"""One plastic's paints for LAYERS, bottom to top."""
return (skirt, top, mix(top, skirt, 0.4), mix(top, lift, 0.15))
# The accent Escape key, then three UI sets that sink towards the board with distance from it.
PAINTS = (
*shading(mix(ACCENT_2, ACCENT_3, 0.45), ACCENT, mix(ACCENT, ACCENT_HI, 0.4)),
*shading(BG_ALT, UI, UI_ALT),
*shading(mix(BG_ALT, BG_DEEP, 0.15), mix(UI, BG_ALT, 0.25), UI_ALT),
*shading(mix(BG_ALT, BG_DEEP, 0.3), mix(UI, BG_ALT, 0.5), UI_ALT),
)
@design(aspects="any")
def draw(s: Canvas) -> None:
# The board runs off the right and bottom edges; wide screens keep the 16:9 crop on the
# right and leave the extra width empty, and portrait screens show more rows.
esc = Vec(max(420, s.w - 1500), s.h - (420 if s.landscape else 720))
frame = Affine.frame(esc, deg=ANGLE)
k = U / 110
with s.group(transform=frame):
s.path(
P().rrect(-U / 2 - 40, -U / 2 - 40, BOARD[0] * U + 80, BOARD[1] * U + 80, 30),
fill=BG_DEEP,
stroke=UI,
stroke_width=2,
)
with s.buckets(PAINTS, "fill") as caps:
for y, widths in ROWS:
x = 0.0
for w in widths:
if w > 0 and s.inset(-(w / 2 + 1) * U).contains(
frame(((x + w / 2 - 0.5) * U, y * U))
):
dist = math.hypot(x + w / 2 - 0.5, y)
tone = 0 if x == y == 0 else 1 + (dist > 3) + (dist > 6)
px, py = (x - 0.5) * U, (y - 0.5) * U
for i, (left, top, dw, dh, r) in enumerate(LAYERS):
caps[4 * tone + i].rrect(
px + left * k, py + top * k, w * U - dw * k, U - dh * k, r * k
)
x += abs(w)"""A keyboard corner seen from above: keycaps as four stacked rounded rectangles, the Escape key picked out and the rest fading with distance from it."""
import math
from walldye import (
ACCENT,
ACCENT_2,
ACCENT_3,
ACCENT_HI,
BG_ALT,
BG_DEEP,
UI,
UI_ALT,
Canvas,
Colour,
P,
Vec,
design,
mix,
)
from walldye.geom import Affine
U = 150 # one key unit
ANGLE = 11 # board tilt, clockwise
# Tenkeyless ANSI: (row y, key widths), both in units; negative widths are gaps.
ROWS = (
(0.0, (1, -1, 1, 1, 1, 1, -0.5, 1, 1, 1, 1, -0.5, 1, 1, 1, 1, -0.25, 1, 1, 1)),
(1.5, (1,) * 13 + (2, -0.25, 1, 1, 1)),
(2.5, (1.5,) + (1,) * 12 + (1.5, -0.25, 1, 1, 1)),
(3.5, (1.75,) + (1,) * 11 + (2.25,)),
(4.5, (2.25,) + (1,) * 10 + (2.75, -1.25, 1)),
(5.5, (1.25,) * 3 + (6.25,) + (1.25,) * 4 + (-0.25, 1, 1, 1)),
)
BOARD = (18.25, 6.5) # key area in units
# Keycap layers in a 110-wide drawing of one unit cell: (left, top, width cut, height cut, radius).
# The top face sits back from the cell centre so its front wall shows, and the dish is shadowed
# at its back wall.
LAYERS = (
(5, 5, 10, 10, 12), # skirt
(17, 11, 34, 34, 10), # top face
(24, 17, 48, 48, 9), # dish
(25, 21, 50, 53, 8), # dish floor
)
def shading(skirt: Colour, top: Colour, lift: Colour) -> tuple[Colour, ...]:
"""One plastic's paints for LAYERS, bottom to top."""
return (skirt, top, mix(top, skirt, 0.4), mix(top, lift, 0.15))
# The accent Escape key, then three UI sets that sink towards the board with distance from it.
PAINTS = (
*shading(mix(ACCENT_2, ACCENT_3, 0.45), ACCENT, mix(ACCENT, ACCENT_HI, 0.4)),
*shading(BG_ALT, UI, UI_ALT),
*shading(mix(BG_ALT, BG_DEEP, 0.15), mix(UI, BG_ALT, 0.25), UI_ALT),
*shading(mix(BG_ALT, BG_DEEP, 0.3), mix(UI, BG_ALT, 0.5), UI_ALT),
)
@design(aspects="any")
def draw(s: Canvas) -> None:
# The board runs off the right and bottom edges; wide screens keep the 16:9 crop on the
# right and leave the extra width empty, and portrait screens show more rows.
esc = Vec(max(420, s.w - 1500), s.h - (420 if s.landscape else 720))
frame = Affine.frame(esc, deg=ANGLE)
k = U / 110
with s.group(transform=frame):
s.path(
P().rrect(-U / 2 - 40, -U / 2 - 40, BOARD[0] * U + 80, BOARD[1] * U + 80, 30),
fill=BG_DEEP,
stroke=UI,
stroke_width=2,
)
with s.buckets(PAINTS, "fill") as caps:
for y, widths in ROWS:
x = 0.0
for w in widths:
if w > 0 and s.inset(-(w / 2 + 1) * U).contains(
frame(((x + w / 2 - 0.5) * U, y * U))
):
dist = math.hypot(x + w / 2 - 0.5, y)
tone = 0 if x == y == 0 else 1 + (dist > 3) + (dist > 6)
px, py = (x - 0.5) * U, (y - 0.5) * U
for i, (left, top, dw, dh, r) in enumerate(LAYERS):
caps[4 * tone + i].rrect(
px + left * k, py + top * k, w * U - dw * k, U - dh * k, r * k
)
x += abs(w)
Run it yourself
$ git clone https://github.com/nickolaj-jepsen/walldye && cd walldye$ uv run walldye render keycaps --theme fireproof -o keycaps-fireproof-16x9.svg