Draw the panel as a panel

Five things a wall panel showed that a wall panel should not.

**A tile's body no longer clips.** It scrolled, and a box that scrolls
also cuts whatever crosses its edge — which took the glow off a lit
button at exactly the width where the button filled its tile, and off a
gauge's arc at exactly the height where the dial filled its own. Only
what is written or listed asks for a scroller now; everything else is a
picture drawn to fit, and what overflows is left to the frame, which
clips at the tile's edge where a shadow has already faded out. The
slider's phantom scrollbar goes with it.

**The selector is a selector.** Named for what it does rather than what
it is, and the choice it is holding is held in the dashboard's own
primary — a pill that slides between the options rather than a grey one
that had to be looked for. The stored type is untouched, so no document
changes meaning.

**The arrangement is held off the panel's edges**, by the same distance
it holds between two widgets. The ground is not held off with it: a
background covers the whole panel, and only what is arranged on it has
a margin. No stored panel loses a row to it.

**The rail is drawn on the panel.** It was chrome bolted to the edge of
the screen beside the canvas — in the app's own design rather than the
dashboard's, and on a scaled canvas not even lined up with it. It now
takes a column out of the canvas the way the margin does, scaled with
it and wearing its look. Which cell each widget sits in is unchanged;
only how big a cell is.

Two of these were the same mistake twice: an unlayered rule stating
`position` for everything wearing a class — `.gl-surface` on a rail
placed by a utility, and a blanket lift over every child of a pressable
on a pill placed by `layoutId`. Both now say it one element at a time.
This commit is contained in:
2026-08-24 10:44:53 +02:00
parent fea57064f9
commit 6238728dce
18 changed files with 389 additions and 173 deletions
@@ -95,8 +95,11 @@
/* --- surfaces ------------------------------------------------------------ */
/* No `position` of its own: this is worn by a tile, a rail and a notice alike,
and each of those is placed by whatever puts it there. Stating it here won
over the utility that placed them — an unlayered rule beats a layered one —
and collapsed the rail into the flow. */
.gl-surface {
position: relative;
border: 1px solid var(--gl-border);
border-radius: var(--gl-radius);
background: var(--gl-bg);
@@ -181,7 +184,12 @@
opacity: 0;
}
.gl-pressable > :not(.gl-glow) {
/* Lifted one at a time rather than by a blanket rule over every child: that
one restated `position` on things already placed — a sliding pill, a switch's
thumb — and dropped them back into the flow. */
.gl-switch-thumb,
.gl-segment-label,
.gl-lift {
position: relative;
z-index: 1;
}
@@ -316,7 +324,7 @@
}
.gl-segment[data-active] {
color: var(--foreground);
color: var(--primary-foreground);
}
.gl-segment-thumb {
@@ -324,13 +332,11 @@
inset: 0;
z-index: 0;
border-radius: 9999px;
background: var(--gl-bg-strong);
box-shadow: var(--gl-inset);
background: color-mix(in srgb, var(--primary) 85%, transparent);
box-shadow: var(--gl-glow), var(--gl-inset);
}
.gl-segment-label {
position: relative;
z-index: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;