Skip to content

A drop shadow that dims what it covers - #81

Merged
ralyodio merged 1 commit into
mainfrom
feat/shadow
Sep 8, 2026
Merged

A drop shadow that dims what it covers#81
ralyodio merged 1 commit into
mainfrom
feat/shadow

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

The last of #64. A shadow cast by a region onto whatever is behind it, and the point of it is that it dims rather than paints: a shadow that filled its band with a flat colour would erase the dashboard underneath, which is the opposite of what a shadow is for. Every covered cell keeps its character and its hue and only loses some of its light.

ui.ctx.overlay(root => {
  drawShadow(root, dialogRect);
  // ... then draw the dialog into the same rect
});

dimRect is the effect on its own, for anyone who wants it without the geometry. A color option paints instead, for a shadow falling on empty background where there is nothing to dim.

Two bugs the ports found

The colour encoding has a sentinel. Zero is not black — it means "the terminal's own colour" — so the first version mixed towards it and drained every cell it touched instead of darkening it. Rust disagreeing with the fixture is what surfaced it; the TypeScript looked perfectly plausible on its own. It is rgb(0, 0, 0) now, in every port, and the fixture that captured the wrong behaviour was regenerated.

The corner was dimmed twice. Splitting the shadow into "the band down the side" and "the band along the bottom" overlaps where they meet, and a cell dimmed twice is visibly darker than the rest — it reads as a smudge rather than an edge. The shadow is now the moved region minus the original, cut into two rectangles that do not touch. A test counts the shades.

One decision

Dimming goes towards black rather than towards the theme's background. On a light theme the background is the light, so dimming towards it would make the shadow brighter than the page it falls on.

Verified

  • 339 TypeScript tests under both bun test and node --test, typecheck clean
  • 90 widget scenes matching the reference in Rust, Go, Python, Zig and C++ (was 86)
  • 11/11 ctest, the widget galleries, next build for the site

Six ports, four fixtures, all additive. Closes #64.

🤖 Generated with Claude Code

https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

The last of #64. A shadow cast by a region onto whatever is behind it, and the
point of it is that it dims rather than paints: a shadow that filled its band
with a flat colour would erase the dashboard underneath, which is the opposite
of what a shadow is for. Every covered cell keeps its character and its hue and
only loses some of its light.

  app.render(({ ui, theme }) => {
    ui.ctx.overlay(root => {
      drawShadow(root, dialogRect);
      // ... then draw the dialog into the same rect
    });
  });

`dimRect` is the effect on its own, for anyone who wants it without the
geometry. A `color` option paints instead, for a shadow falling on empty
background where there is nothing to dim.

Two bugs the ports found, both worth writing down.

The colour encoding has a sentinel. Zero is not black -- it means "the
terminal's own colour" -- so the first version mixed towards it and *drained*
every cell it touched instead of darkening it. Rust disagreeing with the
fixture is what surfaced it; the TypeScript looked plausible on its own.
It is `rgb(0, 0, 0)` now, in every port, and the fixture that captured the
wrong behaviour was regenerated.

The corner was dimmed twice. Splitting the shadow into "the band down the side"
and "the band along the bottom" overlaps where they meet, and a cell dimmed
twice is visibly darker than the rest -- it reads as a smudge rather than an
edge. The shadow is now the moved region minus the original, cut into two
rectangles that do not touch. A test counts the shades.

Dimming goes towards black rather than towards the theme's background: on a
light theme the background *is* the light, so dimming towards it would make the
shadow brighter than the page it falls on.

Six ports, four fixtures, all additive.

Verified: 339 TS tests under bun and node; 90 widget scenes matching the
reference in Rust, Go, Python, Zig and C++; 11/11 ctest; the galleries; the
site builds.

Closes #64

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio merged commit 3be30d0 into main Sep 8, 2026
18 checks passed
@ralyodio
ralyodio deleted the feat/shadow branch September 8, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Smaller gaps: canvas coordinates, calendar, paragraph scroll, Clear/Fill/Shadow

1 participant