Three bugs in wheel.half, all found by looking at it: The hub was centred on the edge, so the edge sliced it in half -- half a cancel target is not a target. It is pushed inward by its own radius now, and the wedges start beyond it. The ring is an inset shadow rather than a border, because a border pushes every absolutely positioned child inward by its own width, which put the hub a pixel past the screen edge. Highlighting fired anywhere the pointer went, including well outside the wheel, promising a pick that release would not deliver. The hit test is bounded by the same rIn/rOut the wedges are drawn from, so the lit shape is the region. An edge panel did not follow its edge: it kept the pixel position it opened at and drifted inward when the window grew. It re-derives its anchor on resize, and on visualViewport resize for pinch-zoom and mobile chrome. One GEOMETRY object per variant now drives the wedges, the labels, the hub and the hit test together, so they cannot disagree.
63 lines
2.8 KiB
CSS
63 lines
2.8 KiB
CSS
/* The ring is an INSET shadow, not a border. A border pushes every absolutely
|
|
* positioned child inward by its own width, which put the hub a pixel past the
|
|
* screen edge on an edge panel -- the one place a pixel shows. */
|
|
.cx-wheel {
|
|
position: fixed; pointer-events: auto; border-radius: var(--r-pill);
|
|
background: color-mix(in srgb, var(--elev-3) 92%, transparent);
|
|
box-shadow: var(--shadow-3), inset 0 0 0 var(--bw) var(--line);
|
|
backdrop-filter: blur(6px);
|
|
animation: cx-wheel-in var(--dur-1) var(--ease-standard);
|
|
}
|
|
@keyframes cx-wheel-in { from { opacity: 0; transform: scale(.9); } }
|
|
|
|
/* The lit shape IS the region that picks the item, so the target is legible
|
|
* instead of implied by a box around a label. */
|
|
.cx-wheel-canvas { position: absolute; inset: 0; pointer-events: none; }
|
|
.cx-wheel-wedge {
|
|
fill: transparent;
|
|
stroke: color-mix(in srgb, var(--line) 70%, transparent);
|
|
stroke-width: 1;
|
|
transition: fill var(--dur-1) var(--ease-standard), stroke var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-wheel-wedge.cx-on { fill: var(--accent-soft); stroke: var(--accent); }
|
|
|
|
.cx-wheel-item {
|
|
position: absolute; transform: translate(-50%, -50%);
|
|
display: flex; flex-direction: column; align-items: center; gap: 2px;
|
|
min-width: 52px; padding: var(--sp-1) var(--sp-2);
|
|
background: transparent; border: 0; color: var(--muted);
|
|
font-size: var(--fs-7); cursor: pointer; text-align: center;
|
|
transition: color var(--dur-1) var(--ease-standard),
|
|
transform var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-wheel-item .cx-wheel-icon { font-size: var(--fs-2); line-height: 1; }
|
|
.cx-wheel-item.cx-on {
|
|
color: var(--accent-hi);
|
|
transform: translate(-50%, -50%) scale(1.06);
|
|
}
|
|
|
|
/* Sized to the dead zone exactly (radius 42), so the cancel target you can see
|
|
* is the cancel target you can hit. */
|
|
/* left/top/size come from the geometry so the drawn hub and the cancel region
|
|
* are the same circle. */
|
|
.cx-wheel-hub {
|
|
position: absolute; transform: translate(-50%, -50%);
|
|
border-radius: var(--r-pill);
|
|
border: var(--bw) dashed var(--line);
|
|
display: flex; align-items: center; justify-content: center;
|
|
color: var(--faint);
|
|
transition: border-color var(--dur-1) var(--ease-standard),
|
|
color var(--dur-1) var(--ease-standard),
|
|
background var(--dur-1) var(--ease-standard);
|
|
}
|
|
.cx-wheel-hub-glyph { font-size: var(--fs-2); line-height: 1; }
|
|
.cx-wheel-hub.cx-on {
|
|
border-color: var(--danger); color: var(--danger);
|
|
background: color-mix(in srgb, var(--danger) 12%, transparent);
|
|
}
|
|
|
|
/* Half wheels sit ON the edge: the off-screen half is simply clipped by the
|
|
* viewport, which is what gives the edge-panel silhouette. The flat side gets a
|
|
* straight edge so it reads as pinned rather than cropped by accident. */
|
|
.cx-wheel-half { backdrop-filter: blur(8px); }
|