Clock: hand outlines drawn once, bubbles beside the watch

The hands' drop-shadow outline sat on their own compositor layers, so the GPU
recomputed it every frame a bubble moved. It is now on an inner box, painted
once into the hand's layer. The bubbles rise beside the watch, never behind it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 17:45:31 +02:00
parent e94e39addc
commit 701e90153c
2 changed files with 29 additions and 9 deletions

View File

@@ -19,13 +19,16 @@ import { SKIN } from "../lib/skins";
import { SHOW_DECORATIVE_ANIMATIONS } from "../lib/theme"; import { SHOW_DECORATIVE_ANIMATIONS } from "../lib/theme";
import { Drift, type DriftField } from "./Drift"; import { Drift, type DriftField } from "./Drift";
/** Beside the watch only, never behind it: on a 1080p kiosk the watch covers roughly
* 28-72% of the width, and a bubble rising behind it would drag the watch into every
* frame the Pi redraws. */
const BUBBLES: DriftField = [ const BUBBLES: DriftField = [
[5, 14, 0.5, 9, 0], [5, 14, 0.5, 9, 0],
[12, 22, 0.5, 12, -3], [12, 22, 0.5, 12, -3],
[17, 16, 0.5, 11, -2],
[22, 10, 0.5, 8, -6], [22, 10, 0.5, 8, -6],
[48, 16, 0.5, 11, -2],
[63, 12, 0.5, 10, -7],
[78, 24, 0.5, 14, -5], [78, 24, 0.5, 14, -5],
[83, 12, 0.5, 10, -7],
[88, 12, 0.5, 9, -1], [88, 12, 0.5, 9, -1],
[95, 18, 0.5, 13, -9], [95, 18, 0.5, 13, -9],
]; ];
@@ -69,10 +72,14 @@ export function ClockOverlay({ visible, clock, onClose }: Props) {
<div className="clock-face"> <div className="clock-face">
<Dial /> <Dial />
<div className="clock-hand clock-hand-hour" style={{ transform: `rotate(${angles.hour}deg)` }}> <div className="clock-hand clock-hand-hour" style={{ transform: `rotate(${angles.hour}deg)` }}>
<div /> <div>
<div />
</div>
</div> </div>
<div className="clock-hand clock-hand-minute" style={{ transform: `rotate(${angles.minute}deg)` }}> <div className="clock-hand clock-hand-minute" style={{ transform: `rotate(${angles.minute}deg)` }}>
<div /> <div>
<div />
</div>
</div> </div>
<div className="clock-hand clock-hand-second" style={{ transform: `rotate(${angles.second}deg)` }}> <div className="clock-hand clock-hand-second" style={{ transform: `rotate(${angles.second}deg)` }}>
<div /> <div />

View File

@@ -362,8 +362,12 @@
will-change: transform; will-change: transform;
} }
.clock-hand-hour, /* The outline and shadow, on the hand's inner box rather than the hand: a filter on the
.clock-hand-minute { layer itself would be recomputed on the GPU every frame anything on screen moves (the
bubbles), while one inside it is drawn once into the layer's picture. The shape is
one level further in, so the outline goes round the clipped arrow, not its box. */
.clock-hand-hour > div,
.clock-hand-minute > div {
filter: drop-shadow(0.35vmin 0 0 var(--clock-ink)) drop-shadow(-0.35vmin 0 0 var(--clock-ink)) filter: drop-shadow(0.35vmin 0 0 var(--clock-ink)) drop-shadow(-0.35vmin 0 0 var(--clock-ink))
drop-shadow(0 0.35vmin 0 var(--clock-ink)) drop-shadow(0 -0.35vmin 0 var(--clock-ink)) drop-shadow(0 0.35vmin 0 var(--clock-ink)) drop-shadow(0 -0.35vmin 0 var(--clock-ink))
drop-shadow(0 1vmin 1vmin oklch(15% 0.05 210 / 0.3)); drop-shadow(0 1vmin 1vmin oklch(15% 0.05 210 / 0.3));
@@ -382,8 +386,18 @@
margin-left: -2.7vmin; margin-left: -2.7vmin;
} }
.clock-hand-hour > div { .clock-hand-hour > div,
.clock-hand-minute > div {
bottom: -3.5vmin; bottom: -3.5vmin;
}
.clock-hand-hour > div > div,
.clock-hand-minute > div > div {
position: absolute;
inset: 0;
}
.clock-hand-hour > div > div {
background: oklch(92% 0.06 345); background: oklch(92% 0.06 345);
clip-path: polygon(50% 0%, 100% 22%, 66% 22%, 66% 100%, 34% 100%, 34% 22%, 0% 22%); clip-path: polygon(50% 0%, 100% 22%, 66% 22%, 66% 100%, 34% 100%, 34% 22%, 0% 22%);
} }
@@ -394,8 +408,7 @@
margin-left: -2.5vmin; margin-left: -2.5vmin;
} }
.clock-hand-minute > div { .clock-hand-minute > div > div {
bottom: -3.5vmin;
background: oklch(92% 0.05 200); background: oklch(92% 0.05 200);
clip-path: polygon(50% 0%, 100% 14%, 64% 14%, 64% 100%, 36% 100%, 36% 14%, 0% 14%); clip-path: polygon(50% 0%, 100% 14%, 64% 14%, 64% 100%, 36% 100%, 36% 14%, 0% 14%);
} }