diff --git a/app/globals.css b/app/globals.css index 33a4cc9..e5ac6a0 100644 --- a/app/globals.css +++ b/app/globals.css @@ -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; } diff --git a/components/board/category-lane.tsx b/components/board/category-lane.tsx index fd2b2f6..f8fd4c5 100644 --- a/components/board/category-lane.tsx +++ b/components/board/category-lane.tsx @@ -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" )} > diff --git a/components/board/group-card.tsx b/components/board/group-card.tsx index 0a97b67..2cc8a8d 100644 --- a/components/board/group-card.tsx +++ b/components/board/group-card.tsx @@ -406,7 +406,11 @@ export function GroupCard({ group }: { group: GroupDTO }) { Archive (all to-dos done) )} - + {/* 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. */} + {completedCount}/{group.todos.length} done @@ -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. */} +
{editingTodo && ( diff --git a/components/board/todo-progress-pie.tsx b/components/board/todo-progress-pie.tsx index 47b99ef..ee43a90 100644 --- a/components/board/todo-progress-pie.tsx +++ b/components/board/todo-progress-pie.tsx @@ -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%)`,