Walldye

Planetrise

inspired by William Anders, Earthrise1

Under a small crescent moon, latitude circles crowd toward the lit rim of a planet filling the lower half.

Made with Claude Opus 5.5

Technique
line art
Shape
Any screen
Added
27 September 2026

Colours

  • #1C1B1Abackground
  • #DAD8CEforeground
  • #CF6A4Caccent

Export

Format
Shape
Size

Sources

  1. William Anders, Earthrise, 1968. ↑

Source code

wallpapers/planetrise/design.py, 89 lines

"""The limb of a vast planet hatched with latitude circles, rimmed by a glowing atmosphere, with a small crescent moon above."""

import math

import numpy as np

from walldye import (
    ACCENT,
    ACCENT_2,
    ACCENT_5,
    BG,
    BG_ALT,
    BG_DEEP,
    MASK_WHITE,
    Canvas,
    P,
    Path,
    Vec,
    design,
    mix,
)
from walldye.field import runs

R_PER_W = 1.875  # planet radius per unit of canvas width, so the limb spans every screen
TILT = math.radians(52)  # north pole tilted up from facing the viewer, at most
POLE_BELOW = 233  # the pole projects at least this far below the bottom edge
LAT_STEP = 1.5  # degrees between latitude circles
MAX_LAT = 72  # the tight polar circles near the bottom edge would read as a ripple target
MOON_R = 19
GLOW = 70


def latitudes(s: Canvas, c: Vec, r: float, tilt: float) -> Path:
    """The visible front arcs of the planet's latitude circles, cut to the canvas."""
    pole = np.array([0, -math.sin(tilt), math.cos(tilt)])
    u = np.array([1.0, 0, 0])
    v = np.cross(pole, u)
    t = np.linspace(0, 2 * math.pi, round(1440 * r / 3600))
    ring = np.outer(u, np.cos(t)) + np.outer(v, np.sin(t))
    hatch = P()
    for lat in np.radians(np.arange(-88, MAX_LAT + 1, LAT_STEP)):
        p = (pole * r * math.sin(lat))[:, None] + r * math.cos(lat) * ring
        x, y = c.x + p[0], c.y + p[1]
        vis = (p[2] > 0) & (y < s.h + 20) & (x > -20) & (x < s.w + 20)
        for a, b in runs(vis):
            if b - a > 2:
                hatch.poly(np.column_stack([x[a:b], y[a:b]]))
    return hatch


@design(aspects="any")
def draw(s: Canvas) -> None:
    r = R_PER_W * s.w
    top = s.pick(landscape=(0.5833, 0.5093), portrait=(0.5833, 0.7))
    c = top + (0, r)
    disc = P().circle(c, r)
    # tall screens show more of the planet; tilting less keeps the pole's circles out of frame
    tilt = min(TILT, math.asin(1 - (s.h - top.y + POLE_BELOW) / r))

    # lit from the right like the moon, with a faint floor so the left limb still glows
    with s.mask() as side:
        lit = side.linear_gradient([(0, MASK_WHITE, 0.35), (0.75, MASK_WHITE)], (0, 0), (s.w, 0))
        side.fill(P().rect(0, 0, s.w, s.h), lit)
    glow = s.radial_gradient([(r / (r + GLOW), ACCENT_5), (1, BG, 0)], c, r + GLOW)
    s.path(P().circle(c, r + GLOW), fill=glow, mask=side.ref)
    # stacked translucent rings: the atmosphere melts outward over ~10 px instead of ending on a hard edge
    for i in range(8):
        s.stroke(disc, ACCENT_5, 2.5 * (i + 1), opacity=0.2)
    s.fill(disc, BG_DEEP)

    with s.mask() as fade:
        down = fade.linear_gradient([(0, MASK_WHITE), (1, MASK_WHITE, 0.25)], top, (top.x, s.h))
        fade.fill(P().rect(0, 0, s.w, s.h), down)
    with s.group(mask=fade.ref):
        s.stroke(latitudes(s, c, r, tilt), BG_ALT, 1.2)

    sun = s.linear_gradient([(0, ACCENT_2), (0.7, ACCENT)], (0, 0), (s.w, 0))
    s.stroke(disc, sun, 2)

    m = s.pick(landscape=(0.2396, 0.2222), portrait=(0.3, 0.2))
    s.fill(P().circle(m, MOON_R), mix(BG, BG_ALT, 0.5))
    lit_side = (
        P()
        .M(m.x, m.y - MOON_R)
        .A(MOON_R, MOON_R, 0, 0, 1, m.x, m.y + MOON_R)
        .A(MOON_R * 0.45, MOON_R, 0, 0, 0, m.x, m.y - MOON_R)
        .Z()
    )
    s.fill(lit_side, ACCENT)

Run it yourself

$ git clone https://github.com/nickolaj-jepsen/walldye && cd walldye$ uv run walldye render planetrise --theme fireproof -o planetrise-fireproof-16x9.svg