fix(dashboard): extend rail edge fades to the track clipping edge

The rail track bleeds past the viewport (padding plus negative margin) so
card focus rings are not clipped, but the edge fades were anchored to the
viewport. That left a strip of the partly visible card beyond each fade.

Share the bleed through a --rail-bleed custom property used by the track
padding/margin, its scroll padding and the fade offsets, so they cannot
drift apart. Raise the bleed from 4px to 8px: the card focus ring needs 5px
(2px outline + 3px offset) and 7px with the hover lift, so the first/last
card's ring was losing a pixel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-29 09:03:13 +02:00
1 parent 76dd8c099e
commit 46407b49a1
3 files changed
+24 -8

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
type: fix
area: dashboard
---
Dashboard rails no longer show a thin strip of the partly visible card past the soft edge fade, and the focus outline of the first or last card in a rail is no longer cut off.
+4
View File
@@ -143,6 +143,10 @@ hooks: `dashboard-hero`, `dashboard-hero-slide` (`data-hero-kind`),
optional `aspectRatio` (default `'2 / 3'`), optional `testId`.
2. Behavior: horizontal flex track with `scroll-snap-type: x mandatory`.
3. Chevron buttons fade in on hover (desktop only via `@media (hover: none)`).
Edge fades follow the chevrons' visibility. The track bleeds
`--rail-bleed` past the viewport on every side so card focus rings and
hover lift are not clipped; the fades are offset by the same variable so
they reach the track's clipping edge and no card strip shows beyond them.
4. Cards are keyboard-focusable router links; `scroll-snap-align: start`
means arrow-key nav lands on card boundaries.
5. Image handling: `loading="lazy"`, `decoding="async"`, fallback icon tile
@@ -3,6 +3,11 @@
--rail-card-width: var(--cover-rail-width, 172px);
--rail-gap: var(--cover-gap, 14px);
--rail-radius: 12px;
// How far the scrolling track bleeds past the viewport on every side:
// room for a card's focus ring (2px outline + 3px offset) plus its 2px
// hover lift, so neither is clipped. The track clips partially visible
// cards at this outer edge, so the edge fades extend by the same amount.
--rail-bleed: 8px;
}
.rail {
@@ -81,13 +86,14 @@
// Soft edge gradients hint at horizontal scroll without any interaction.
// Anchored to the viewport so they appear/disappear in sync with the
// chevrons' `.rail__chev--visible` state.
// chevrons' `.rail__chev--visible` state, but offset by the track bleed
// so each fade ends exactly where the track clips the partial card.
&::before,
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
top: calc(-1 * var(--rail-bleed));
bottom: calc(-1 * var(--rail-bleed));
width: 32px;
pointer-events: none;
z-index: 1;
@@ -96,7 +102,7 @@
}
&::before {
left: 0;
left: calc(-1 * var(--rail-bleed));
background: linear-gradient(
to right,
var(--app-content-bg, #161a22) 0%,
@@ -105,7 +111,7 @@
}
&::after {
right: 0;
right: calc(-1 * var(--rail-bleed));
background: linear-gradient(
to left,
var(--app-content-bg, #161a22) 0%,
@@ -135,10 +141,10 @@
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-padding-inline-start: 4px;
scroll-padding-inline-start: var(--rail-bleed);
scrollbar-width: none;
padding: 4px;
margin: -4px;
padding: var(--rail-bleed);
margin: calc(-1 * var(--rail-bleed));
&::-webkit-scrollbar {
display: none;