fix(planner): eliminate front-face bleed by removing preserve-3d

transform-style:preserve-3d on a parent with box-shadow/transition
causes Chrome to fail backface-visibility:hidden. Replace with
independent per-face rotateY transforms:
  front: 0deg → -180deg (flipped)
  back:  180deg → 0deg (flipped)
No preserve-3d needed — each face is its own compositing layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 12:49:28 +02:00
parent a43a8ec33f
commit 38528a50e5

View File

@@ -123,10 +123,8 @@
onclick={handleFlip} onclick={handleFlip}
onkeydown={handleKeydown} onkeydown={handleKeydown}
> >
<div class="card" class:flipped={isFlipped}>
<!-- FRONT --> <!-- FRONT -->
<div class="card-front"> <div class="card-front" class:flipped={isFlipped}>
<div <div
class="card-front-inner" class="card-front-inner"
style="background: {gradientBackground}; background-size: cover; background-position: center;" style="background: {gradientBackground}; background-size: cover; background-position: center;"
@@ -162,7 +160,7 @@
</div> </div>
<!-- BACK --> <!-- BACK -->
<div class="card-back" aria-hidden={!isFlipped}> <div class="card-back" class:flipped={isFlipped} aria-hidden={!isFlipped}>
<div class="card-back-inner"> <div class="card-back-inner">
<button <button
type="button" type="button"
@@ -205,7 +203,6 @@
</div> <!-- /.card-back-inner --> </div> <!-- /.card-back-inner -->
</div> </div>
</div>
</div> </div>
{:else} {:else}
<EmptyDayTile <EmptyDayTile
@@ -250,38 +247,30 @@
pointer-events: none; pointer-events: none;
} }
/* ── Card flip ── */ /* ── Card flip — independent face transforms, no preserve-3d ──
.card { preserve-3d + box-shadow/transition on parent causes Chrome to
position: relative; fail backface-visibility:hidden. Rotating each face independently
width: 100%; avoids the 3D context entirely. */
height: 100%;
transform-style: preserve-3d;
transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 10px;
will-change: transform;
}
.card.flipped {
transform: rotateY(180deg);
}
.card-front, .card-front,
.card-back { .card-back {
position: absolute; position: absolute;
inset: 0; inset: 0;
backface-visibility: hidden; backface-visibility: hidden;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
/* border-radius and overflow on inner wrappers, not here — transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
overflow:hidden on preserve-3d children flattens the 3D context */ will-change: transform;
} }
.card-front { transform: rotateY(0deg); }
.card-front.flipped { transform: rotateY(-180deg); }
.card-back { transform: rotateY(180deg); pointer-events: none; }
.card-back.flipped { transform: rotateY(0deg); pointer-events: auto; }
.card-front.flipped { pointer-events: none; }
.card-front-inner { .card-front-inner {
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
/* backface-visibility hides visually but NOT pointer events — disable manually */
.card-back { pointer-events: none; }
.card.flipped .card-back { pointer-events: auto; }
.card.flipped .card-front { pointer-events: none; }
/* ── Front face ── */ /* ── Front face ── */
.tile-overlay { .tile-overlay {
@@ -369,9 +358,6 @@
.tag-selected { background: rgba(46,110,57,.45); } .tag-selected { background: rgba(46,110,57,.45); }
/* ── Back face ── */ /* ── Back face ── */
.card-back {
transform: rotateY(180deg);
}
.card-back-inner { .card-back-inner {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -445,7 +431,7 @@
.btn-danger:hover { background: rgba(220,76,62,.08); } .btn-danger:hover { background: rgba(220,76,62,.08); }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.card { transition: none; } .card-front, .card-back { transition: none; }
.scene { transition: none; } .scene { transition: none; }
} }
</style> </style>