Let a widget's glow fade out instead of stopping at the tile's edge

`overflow: hidden` on the frame and its body cut a control's 22px
`--gl-glow` dead at the clip edge, and the gauge's own `<svg>` cut its
drop-shadow at the viewBox — the arc's top sits ~3.5 units from it.
`overflow: clip` with a margin wide enough for the blur (1.5rem on the
body, which already sits 1rem inside the frame; 0.5rem is all that leaves
the tile, which is the gutter).

That means the body no longer scrolls by default. `.dui-frame-scroll` was
already wired through from `scrolls()` into every look's Surfaces with no
CSS rule behind it, so the class is what opts markdown, agenda and
notification back into a real scroller.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KYM38KSb4V4v2T71eifnZv
This commit is contained in:
2026-08-30 16:16:06 +02:00
co-authored by Claude Opus 5
parent 4c4920bda8
commit 7f78d541ab
3 changed files with 29 additions and 8 deletions
@@ -19,7 +19,12 @@
min-width: 0; min-width: 0;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
overflow: hidden; /* `clip` rather than `hidden`, so a control's glow is not capped hard at the
tile's edge. The body below sits 1rem inside this box already, which with
this margin is the 22px `--gl-glow` fades over — and 0.5rem is all that
leaves the tile, which is the gutter between two of them. */
overflow: clip;
overflow-clip-margin: 0.5rem;
padding: 1rem; padding: 1rem;
/* A pointer above `md`, a finger below it, and a panel that says it is /* A pointer above `md`, a finger below it, and a panel that says it is
touched gets the finger whatever its size. */ touched gets the finger whatever its size. */
@@ -85,10 +90,12 @@
} }
/* /*
* The body is the query container everything inside is measured against, and * The body is the query container everything inside is measured against.
* the scroller for anything taller than the tile. Centring has to be `safe`: * Clipped rather than scrolling by default — a picture drawn to fit the tile
* plain `center` overflows both edges at once and puts the top of a long body * has nothing to scroll, and a box that scrolls is one that clips whatever
* out of reach. * glow or shadow crosses its edge. `.dui-frame-scroll` (`scrolls()` in
* `core/config.ts`) opts the few widgets that are text or a list back into a
* real scroller, which has to clip at its own edge to work at all.
*/ */
.dui-frame-body { .dui-frame-body {
display: flex; display: flex;
@@ -96,12 +103,20 @@
min-width: 0; min-width: 0;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
/* Centring has to be `safe`: plain `center` overflows both edges at once
and puts the top of a long body out of reach. */
justify-content: safe center; justify-content: safe center;
overflow-y: auto; /* Wide enough for the 22px `--gl-glow` of a control that fills the body. */
overflow: clip;
overflow-clip-margin: 1.5rem;
container-type: size; container-type: size;
font-size: var(--dui-text); font-size: var(--dui-text);
} }
.dui-frame-scroll {
overflow: auto;
}
/* No header, so the fault still has somewhere to be seen. */ /* No header, so the fault still has somewhere to be seen. */
.dui-frame-corner { .dui-frame-corner {
position: absolute; position: absolute;
@@ -68,7 +68,9 @@ export function Gauge({ value, min, max, precision, unit, label }: GaugeProps) {
<div className="flex min-h-0 flex-1 items-center justify-center"> <div className="flex min-h-0 flex-1 items-center justify-center">
<svg <svg
viewBox="0 0 100 78" viewBox="0 0 100 78"
className="h-full max-h-full w-full" // SVG clips to its viewBox by default, which cuts the arc's
// drop-shadow off hard right at the edge rather than letting it fade.
className="h-full max-h-full w-full overflow-visible"
role="img" role="img"
aria-label={`${label}: ${format(value, precision)}${unit ?? ""} of ${max}`} aria-label={`${label}: ${format(value, precision)}${unit ?? ""} of ${max}`}
> >
@@ -152,7 +152,11 @@
.gl-pressable { .gl-pressable {
position: relative; position: relative;
overflow: hidden; /* `clip` rather than `hidden`: the press ripple below is inset 0 and rounds
with the surface regardless, but a segment's `--gl-glow` thumb sits inside
this box too and needs room to fade rather than being cut off at it. */
overflow: clip;
overflow-clip-margin: 1.5rem;
isolation: isolate; isolation: isolate;
} }