The drag replicas cast the shadow the pickup lift already promised

DragReplicaStyle spells the pathfinder's constant ambient shadow once
(black 16%, radius 2.5, y 1.5) and all three replica faces — card,
lane, trashed lane — wear it via one modifier; the ghost fans compose
from the same faces so every copy inherits it, dimmed by its own
opacity. 03 § Motion's "pickup lift (scale + shadow)" supplies the
scale from the system session but no shadow on the preview image — the
replica is the only place it can come from. BoardMetrics.replicaPadding
was already written to keep exactly this shadow from clipping and was
simply unused until now.

Drag-perf card 24397310.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-03 13:33:52 -04:00
parent 1ffb64913b
commit 43e6ad229e
4 changed files with 23 additions and 0 deletions
+1
View File
@@ -512,6 +512,7 @@ struct CardFaceView: View, Equatable {
)
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.secondary))
.overlay(alignment: .leading) { accentStripe }
.dragReplicaShadow()
}
// MARK: - Context menus
+20
View File
@@ -78,3 +78,23 @@ struct DragCountBadge: View {
}
}
}
/// The pickup lift's shadow figures 03-board-ui.md § Motion's "the pickup lift (scale + shadow as
/// it detaches from the card)": the system drag session supplies the scale and the tracking, but
/// paints no shadow onto its preview image, so the shadow can only come from the replica view
/// itself. Values are the pathfinder's own constant ambient shadow, reused verbatim rather than
/// re-derived (`~/Documents/indie/Kanban` CardView.swift:623) its drags read subtly raised, and
/// that read is the point.
enum DragReplicaStyle {
static let shadowColor = Color.black.opacity(0.16)
static let shadowRadius: CGFloat = 2.5
static let shadowY: CGFloat = 1.5
}
extension View {
/// The pickup lift's shadow (`DragReplicaStyle`), for a drag replica's face shared by the
/// card, lane, and trashed-lane replicas so the figures are spelled once.
func dragReplicaShadow() -> some View {
shadow(color: DragReplicaStyle.shadowColor, radius: DragReplicaStyle.shadowRadius, y: DragReplicaStyle.shadowY)
}
}
+1
View File
@@ -650,6 +650,7 @@ struct LaneView: View, Equatable {
.frame(width: replicaWidth, height: replicaHeight, alignment: .topLeading)
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background))
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
.dragReplicaShadow()
}
/// The replica's size **the lane's own**, floored for a lane that has not measured itself yet
+1
View File
@@ -279,6 +279,7 @@ struct TrashLaneRowView: View {
alignment: .leading
)
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.tertiary))
.dragReplicaShadow()
}
// MARK: - The row's two rows of menu