git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit0455ddb75021d905924d02b74b55d2e436baa9c8
parente85fccfc41
authorLucas Galante <[email protected]>
date2026-08-28 10:27
feat: the droplet bubble's vignette is the droplet's own shape

A rounded-rect pool inset in the drop's box was an approximation, and on a
27px bubble it showed: the drop has a flat attach line and a bowed bottom,
and a pill has neither. Droplet modules now use cce-ui@7d280c9's
Prim::DropletScrim, which evaluates the same silhouette SDF as the drop
itself under the same spec — the vignette's edge IS the drop's edge, not a
stand-in for it, and it needs no clip because the shader cannot draw
outside the shape it is evaluating.

Plain rounded boxes keep the Glow pool, which is already their exact shape.

Traced against a light desktop in a shadow session: the pool's top edge
holds flat across the bubble (the attach line) while its bottom runs from
row 20 mid-span to 18 at the ends — the bow, which the rounded rect drew
straight.

Co-Authored-By: Claude Opus 5 <[email protected]>

 CLAUDE.md   | 14 +++++++++-----
 src/main.rs | 33 +++++++++++++++++++++++----------
 2 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 0ff291a..db18191 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -215,11 +215,15 @@ the halo rims every letterform, this darkens the ground they sit on. It rests
 at the configured opacity and `text_contrast` deepens it from there, so it is
 a constant when that knob is off.
 
-One pool per bubble, not per text run: the core is inset by exactly the
-feather, so the gradient's outer edge lands on the bubble's own edge and a
-segment reads as one darkened lozenge rather than a pill inside a pill. It is
-still clipped to the box (`clip_rounded`) — the inset makes spill unlikely,
-not impossible.
+One pool per bubble, not per text run, so a segment reads as one darkened
+lozenge rather than a pill inside a pill. Its shape is the bubble's ACTUAL
+shape, which means two paths: a droplet module gets `Prim::DropletScrim`
+(cce-ui shader mode 12 — the droplet's own SDF under the same `DropletSpec`,
+filled flat and feathered inward, so the vignette's edge is the drop's edge by
+construction), while a plain rounded box gets `Prim::Glow` with its core inset
+by exactly the feather, which lands the gradient's outer edge on the box edge.
+The rounded-rect pool is clipped to its box; the droplet one needs no clip,
+since the shader cannot draw outside the silhouette it is evaluating.
 
 A box with no measured text gets no pool at all (the tray is icons; there is
 nothing to ground). That, and the pool's color, come from
diff --git a/src/main.rs b/src/main.rs
index 3b7e209..f758cb7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1631,16 +1631,16 @@ impl cce_ui::engine::Application for StatusApp {
             }
         }
 
-        // The pool, one per module box — it fills the bubble rather than
+        // The pool, one per module box, filling the bubble rather than
         // hugging the run inside it, so a segment reads as one darkened
-        // lozenge instead of a pill within a pill. `Prim::Glow` is a
-        // feathered aura, solid through its core rect and falling off to
-        // nothing across `reach`, so insetting the core by exactly the
-        // feather lands the gradient's outer edge on the bubble's own edge.
+        // lozenge instead of a pill within a pill.
         //
-        // Still clipped to the box: the inset makes spill unlikely, not
-        // impossible, and a droplet's silhouette is narrower than its box at
-        // the corners.
+        // Two shapes, because "the bubble" is two different things: a droplet
+        // module gets a pool of the droplet's own silhouette (below), while a
+        // plain rounded box gets `Prim::Glow` — a feathered aura, solid
+        // through its core rect and falling off across `reach`, so insetting
+        // the core by exactly the feather lands the gradient's outer edge on
+        // the box's own edge.
         if self.text_scrim > 0.0 {
             // Rests at the configured opacity and deepens with the measured
             // demand; `halo_now` is already zero when text_contrast is off,
@@ -1670,9 +1670,22 @@ impl cce_ui::engine::Application for StatusApp {
                     pc.glow(core, (radius - feather).max(0.0), feather, [c[0], c[1], c[2], alpha]);
                 });
             };
-            for &(x, y, w, h, _) in &self.droplet_boxes {
-                pool_in(&mut pc, x, y, w, h, h * 0.5);
+            // A droplet bubble gets a pool of its OWN silhouette, not a
+            // rounded-rect stand-in: cce-ui's `Prim::DropletScrim` runs the
+            // droplet's shader path with the same spec, filled flat and
+            // feathered inward, so the vignette's edge is the drop's edge by
+            // construction rather than by approximation.
+            if let Some(spec) = self.droplet {
+                for &(x, y, w, h, _) in &self.droplet_boxes {
+                    let rect = Rect { x, y, width: w, height: h };
+                    let Some(color) = dominant_run_color(runs, x, y, w, h) else { continue };
+                    let c = treatment_rgb(color);
+                    let feather = scrim_feather(w, h, feather_cfg);
+                    pc.droplet_scrim(rect, [c[0], c[1], c[2], alpha], spec, feather);
+                }
             }
+            // Everything else is genuinely a rounded rect, so a rounded-rect
+            // pool IS its exact shape.
             for rb in &self.rounded_boxes {
                 pool_in(&mut pc, rb.x, rb.y, rb.w, rb.h, rb.radius);
             }