Walldye

Twisted ribbons

inspired by Microsoft and Six N. Five, Bloom1

A flat ribbon that twists about its center line looks narrowest where it is edge-on.

Made with Claude Opus 5.5

Technique
flat shapes
Shape
Any screen
Added
27 September 2026

Export

Shape
cropped from 16:9
Crop
Size
Format

Colors

  • #1C1B1Abackground
  • #DAD8CEforeground
  • #CF6A4Caccent

Notes

The top ribbon’s width follows the cosine of its turn, and each stretch between two edge-on points gets its own gradient, lit from the upper left. The layered ribbons are modeled on Microsoft’s Bloom wallpaper for Windows 11.

Sources

  1. Microsoft and Six N. Five, Bloom, 2021. ↑

Source code

wallpapers/twisted-ribbons/design.py, 116 lines

"""Four gradient-shaded ribbons, the top one twisting to show its underside."""

import itertools
import math

import numpy as np
from numpy.typing import NDArray
from scipy.interpolate import PchipInterpolator

from walldye import (
    ACCENT,
    ACCENT_2,
    BG,
    BG_DEEP,
    UI,
    UI_ALT,
    Canvas,
    Color,
    P,
    by_regime,
    design,
    mix,
    smoothstep,
)
from walldye.geom import bezier_points, ribbon

N = 480  # samples along each ribbon
W = 160  # ribbon width
# the sweep's control points as drawn for 1920 x 1080, around the twist at LENS
SWEEP = ((-300, 1300), (500, 1000), (1100, 650), (1500, -200))
LENS = 1010, 530
TAPER = 880  # arc length past the twist over which the top ribbon turns face up again
# offsets across the sweep (in 0.8 widths) in paint order: each ribbon shadows the one below it,
# and the twisted ribbon (0) lies on top so nothing hides its underside
STACK = (-2, -1, 1, 0)
GATHER = 0.8
SHADOW = (10, 16)
SHADE = by_regime(BG_DEEP, UI_ALT)  # BG_DEEP is paler than BG on paper, so it cannot shade there


def left(p: NDArray[np.float64]) -> NDArray[np.float64]:
    """Unit normals of the sampled curve `p`, pointing left of the direction of travel on
    screen."""
    d = np.gradient(p, axis=0)
    return np.stack([d[:, 1], -d[:, 0]], 1) / np.linalg.norm(d, axis=1)[:, None]


def tone(v: float, under: bool, accent: bool) -> Color:
    """The ribbon color for brightness `v` in [0, 1]: the accent ladder on the twisted
    ribbon's underside, else a step from BG toward UI, dimmer on an underside."""
    if under and accent:
        return mix(ACCENT_2, ACCENT, v**1.4)
    return mix(BG, UI, 0.08 + 0.5 * v * (0.55 if under else 1))


@design(aspects="any")
def draw(s: Canvas) -> None:
    t = np.linspace(0, 1, N)
    lens = s.frac(LENS[0] / 1920, LENS[1] / 1080)
    # wide screens stretch the sweep only partly, so it still rises steeply out of the top edge
    kx, ky = ((s.w / 1920) ** 0.5, 1.0) if s.landscape else (s.w / 1920, s.h / 1080)
    ctrl = [lens + ((x - LENS[0]) * kx, (y - LENS[1]) * ky) for x, y in SWEEP]
    base = bezier_points(ctrl, N - 1)
    nb = left(base)
    for off in STACK:
        accent = off == 0
        # the lower folds gather up under the twisted one, as if the cloth is drawn through the top edge
        gather = 1 - GATHER * smoothstep(0.5, 1, t) * (off < 0)
        c = base + nb * (off * 0.8 * W * gather)[:, None]
        arc = np.concatenate([[0], np.cumsum(np.linalg.norm(np.diff(c, axis=0), axis=1))])
        width = W * (1 + 0.04 * off)
        if accent:
            # underside run: short lead-in, long taper toward the upper right
            sl = arc[np.argmin(np.linalg.norm(c - lens, axis=1))]
            phi = PchipInterpolator(
                [0, sl - 130, sl, sl + 270, sl + TAPER],
                [0.2, math.pi / 2, math.pi, 1.5 * math.pi, 2 * math.pi + 0.25],
            )(np.minimum(arc, sl + TAPER))
        else:
            phi = 0.25 + 0.1 * smoothstep(0.5, 1, t)
        cos = np.cos(phi)
        # pin each fold to one sample of zero width so neighboring runs meet in a clean point
        pins = [
            i if abs(cos[i]) < abs(cos[i + 1]) else i + 1
            for i in np.flatnonzero(np.diff(np.sign(cos)))
        ]
        cos[pins] = 0
        # ribbon() puts its first half right of travel, so a twist (cos < 0) swaps the sides
        edge = ribbon(c, width * cos)
        a, b = edge[:N], edge[N:][::-1]

        # the shadow pulls in near a pinch so it can't leave a dark halo around the point
        near = np.ones(N)
        for i in pins:
            near = np.minimum(near, smoothstep(0, 70, np.abs(arc - arc[i])))
        shadow = ribbon(c, width * cos * near) + SHADOW
        s.fill(P().poly(shadow, closed=True), SHADE, opacity=0.45)

        # light from the upper left: brightness follows the facing angle of whichever side is visible
        back = cos < 0
        lit = 0.5 + 0.5 * np.cos(phi - np.where(back, math.pi, 0) + 0.5)
        lit *= smoothstep(-0.1, 0.45, t)
        edges = [0, *pins, N - 1]
        for run in (np.arange(i, j + 1) for i, j in itertools.pairwise(edges)):
            under = back[run[len(run) // 2]]
            # shading is a gradient along the run's chord; projected offsets are forced monotonic
            p0, p1 = c[run[0]], c[run[-1]]
            u = p1 - p0
            pos = np.maximum.accumulate(np.clip((c[run] - p0) @ u / (u @ u), 0, 1))
            picks = np.linspace(0, len(run) - 1, 16).astype(int)
            grad = s.linear_gradient(
                [(pos[i], tone(lit[run[i]], under, accent)) for i in picks], p0, p1
            )
            s.fill(P().poly(np.vstack([a[run], b[run][::-1]]), closed=True), grad)
        # the upper silhouette stays one line through any twist, so the eye follows a single ribbon
        s.stroke(P().poly(np.where(back[:, None], a, b)), UI_ALT, 1.2)

Run it yourself

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