Fix vaporwave glitch band clipping the progress pie badge

- Move overflow:hidden off the group card onto a dedicated .vw-glitch-clip
  overlay so the card can stay overflow-visible and the corner progress
  pie is no longer cropped by the card or its rounded corner.
- Add overflow-x-clip to CategoryLane so badges that poke past the card's
  right edge are discarded instead of spawning a stray horizontal scrollbar.
- Nudge TodoProgressPie to -right-2.5 and give the "N/N done" label pr-2.5
  so both clear each other within the lane's 10px margin without spilling.
This commit is contained in:
Brian Fertig 2026-08-29 18:29:10 -06:00
parent 6f7b72c882
commit fca66959c2
4 changed files with 61 additions and 10 deletions

View File

@ -1720,17 +1720,34 @@ html.theme-vaporwave .vw-palm--right-small {
that inline value (animations sit above inline styles in the cascade) --
the card stops following the pointer. opacity-50 is exactly the class the
card gets while dragging, so the hiccup stands down for the duration.
(overflow: hidden keeps the tracking band clipped to the card while it
sweeps; the hover lift uses the separate `translate` property, so the
jitter doesn't fight it.) */
(The tracking band sweeps inside a .vw-glitch-clip clip box rendered by
GroupCard rather than relying on overflow: hidden on the card itself --
the card must stay overflow-visible so the progress pie, which pokes
outside the bottom-right corner, isn't cropped by the card or its
rounded-2xl corner. The hover lift uses the separate `translate`
property, so the jitter doesn't fight it.) */
html.theme-vaporwave [data-group-card]:not(.opacity-50) {
overflow: hidden;
animation: vw-card-glitch 10s linear infinite;
}
html.theme-vaporwave [data-group-card]:not(.opacity-50) > div:first-child h3 {
animation: vw-title-glitch 10s linear infinite;
}
html.theme-vaporwave [data-group-card]:not(.opacity-50)::after {
/* The band's clip box: a full-card overlay (in GroupCard) that carries the
overflow clipping, so the card itself never needs it. inset-0 +
border-radius: inherit matches the card's own padding-box clip exactly,
sweep included. Hidden in every other theme. */
.vw-glitch-clip {
display: none;
}
html.theme-vaporwave .vw-glitch-clip {
display: block;
position: absolute;
inset: 0;
overflow: hidden;
border-radius: inherit;
pointer-events: none;
}
html.theme-vaporwave [data-group-card]:not(.opacity-50) .vw-glitch-clip::after {
content: "";
position: absolute;
left: 0;
@ -1756,14 +1773,14 @@ html.theme-vaporwave [data-group-card]:not(.opacity-50)::after {
first delay elapses, and under reduced motion, everything just sits still.) */
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50),
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50) > div:first-child h3,
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50)::after {
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n + 2) [data-group-card]:not(.opacity-50) .vw-glitch-clip::after {
animation-duration: 13s;
animation-delay: 3s;
}
/* Phase C: the longest period, offset furthest. */
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50),
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50) > div:first-child h3,
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50)::after {
html.theme-vaporwave [data-category-lane] .overflow-y-auto > div:nth-child(3n) [data-group-card]:not(.opacity-50) .vw-glitch-clip::after {
animation-duration: 17s;
animation-delay: 7s;
}

View File

@ -162,7 +162,16 @@ export function CategoryLane({ category }: { category: CategoryDTO }) {
// leaves enough clearance that a card's hover lift
// (-translate-y-0.5 in GroupCard) doesn't tuck its top stroke
// under the lane header above it.
"flex-1 space-y-2 overflow-y-auto rounded-t-lg px-2.5 pt-1.5 pb-2 transition-colors",
//
// overflow-x-clip: the lane is a fixed-width (w-72) panel whose
// cards always span it, so horizontal scrolling is never wanted.
// The only things that can spill horizontally are corner badges
// like the group card's progress pie, which deliberately poke
// past the card's right edge. `clip` (not `hidden`) keeps
// overflow-y-auto fully functional while simply discarding any
// spill, so a future badge that strays a px too far can never
// grow a stray horizontal scrollbar here.
"flex-1 space-y-2 overflow-y-auto overflow-x-clip rounded-t-lg px-2.5 pt-1.5 pb-2 transition-colors",
isDropTargetLane && "bg-primary/10"
)}
>

View File

@ -406,7 +406,11 @@ export function GroupCard({ group }: { group: GroupDTO }) {
<TooltipContent side="top">Archive (all to-dos done)</TooltipContent>
</Tooltip>
)}
<span className="ml-auto text-[11px] font-medium tabular-nums text-muted-foreground">
{/* pr-2.5: the corner progress pie (TodoProgressPie) hugs
the card's right edge right here, so the label keeps a
little room of its own -- together the two clear each
other without either spilling past the lane's edge. */}
<span className="ml-auto pr-2.5 text-[11px] font-medium tabular-nums text-muted-foreground">
{completedCount}/{group.todos.length} done
</span>
</div>
@ -421,6 +425,14 @@ export function GroupCard({ group }: { group: GroupDTO }) {
)}
</>
)}
{/* Vaporwave's VHS tracking band sweeps inside this clip box (see
.vw-glitch-clip in app/globals.css) instead of the card itself
using overflow: hidden -- the card must stay overflow-visible so
the progress pie can poke past the bottom-right corner without
being clipped by it or the card's rounded corner. display:none
(and thus inert) in every other theme. */}
<div className="vw-glitch-clip" aria-hidden />
</div>
{editingTodo && (

View File

@ -40,7 +40,20 @@ export function TodoProgressPie({
// context (see hover:-translate-y-0.5 in GroupCard), so
// without an explicit z-index here it paints on top and crops
// the badge's overlapping edge.
className="absolute -right-1.5 -bottom-1.5 z-10 size-7 shrink-0 cursor-default rounded-full border-2 shadow-sm outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
// -right-2.5: the badge's horizontal position is a budget, not
// a taste call. From the card's right border there are exactly
// 10px of lane (CategoryLane's px-2.5) before the lane's own
// clip edge -- or its vertical scrollbar, in classic-scrollbar
// browsers, which sits at the same 10px mark and paints over
// anything past it. So the badge may stick out at most ~7px
// (10px offset from the card's *padding* edge minus the 3px
// border) to stay clear of both. In exchange the "N/N done"
// footer text carries pr-2.5, which hands this badge the room
// it needs to clear the text (see the span in GroupCard).
// Moving it further out looks like it "fits" until it doesn't:
// past the 10px mark the lane clips the badge's edge or grows
// a stray horizontal scrollbar.
className="absolute -right-2.5 -bottom-1.5 z-10 size-7 shrink-0 cursor-default rounded-full border-2 shadow-sm outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
style={{
borderColor: accentColor,
background: `conic-gradient(${accentColor} ${pct}%, ${emptyColor} ${pct}% 100%)`,