fix: Lumiere card dropdown clipped by card's backdrop-filter and transform
Build and Deploy Verso / deploy (push) Successful in 14m35s

.lumiere-card has both backdrop-filter and a hover transform, both of
which create a new containing block for position:fixed descendants,
trapping Popper dropdowns inside the card's overflow:hidden bounds.

Fix: move .lumiere-card-actions outside .lumiere-card (sibling inside
.lumiere-card-wrapper, which has position:relative but no filter or
transform). The actions strip is now absolutely positioned at the card
bottom with its own solid background and bottom border-radius.
No backdrop-filter on the strip to avoid re-introducing the same trap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-16 21:35:52 +00:00
parent b78845a926
commit 48d5e15f7f
2 changed files with 30 additions and 14 deletions
@@ -171,17 +171,19 @@ const ProjectCard = memo(function ProjectCard({
<span className="lumiere-card-date">{date}</span>
</div>
</a>
<div className="lumiere-card-actions">
<CopyProjectButtonTooltip project={project} />
<DownloadProjectButtonTooltip project={project} />
{project.compiler === 'quarto' ? (
<DownloadPresentationButtonTooltip project={project} />
) : (
<CompileAndDownloadProjectPDFButtonTooltip project={project} />
)}
<ArchiveProjectButtonTooltip project={project} />
<TrashProjectButtonTooltip project={project} />
</div>
</div>
{/* Actions live outside .lumiere-card so Popper dropdowns are not
clipped by the card's backdrop-filter / transform context. */}
<div className="lumiere-card-actions">
<CopyProjectButtonTooltip project={project} />
<DownloadProjectButtonTooltip project={project} />
{project.compiler === 'quarto' ? (
<DownloadPresentationButtonTooltip project={project} />
) : (
<CompileAndDownloadProjectPDFButtonTooltip project={project} />
)}
<ArchiveProjectButtonTooltip project={project} />
<TrashProjectButtonTooltip project={project} />
</div>
</div>
)
@@ -537,14 +537,28 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
}
}
// Action strip — icon buttons that fade in when the card is hovered
// Action strip — lives *outside* .lumiere-card so the dropdown menus inside
// are not trapped by the card's backdrop-filter and transform (both of which
// create a new containing block for position:fixed, clipping Popper menus).
// position:absolute on the wrapper (which has no transform/filter) lets any
// position:fixed child escape to the viewport as expected.
.lumiere-card-actions {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
padding: 3px 6px 5px;
// Solid background — intentionally no backdrop-filter here, which would
// also create a fixed containing block and trap the Popper dropdown.
background: rgba(255, 255, 255, 0.97);
border-top: 1px solid rgba($lum-teal, 0.10);
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
opacity: 0;
transition: opacity 0.15s ease;
@@ -561,8 +575,8 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
}
}
.lumiere-card:hover .lumiere-card-actions,
.lumiere-card:focus-within .lumiere-card-actions {
.lumiere-card-wrapper:hover .lumiere-card-actions,
.lumiere-card-wrapper:focus-within .lumiere-card-actions {
opacity: 1;
}