import SwiftUI struct CardStackView: View { let cards: [Card] let location: CardLocation let layout: CardLayout let cardFaceStyle: CardFaceStyle let cardBackDesign: CardBackDesign @Bindable var viewModel: GameViewModel var body: some View { ZStack(alignment: .top) { // Empty slot placeholder — also a drop target CardView(card: nil, cardFaceStyle: cardFaceStyle, cardBackDesign: cardBackDesign, size: layout.cardSize()) .dropTarget(location) .accessibilityLabel(emptySlotLabel) // Stacked cards ForEach(Array(cards.enumerated()), id: \.element.id) { index, card in let isInDrag = viewModel.draggedCards.contains(card) let isLast = index == cards.count - 1 CardView(card: card, cardFaceStyle: cardFaceStyle, cardBackDesign: cardBackDesign, size: layout.cardSize(), isHighlighted: isHinted(card)) .offset(y: yOffset(for: index)) .opacity(isInDrag ? 0.3 : 1.0) .onTapGesture { viewModel.tapCard(at: location, cardIndex: index) } .simultaneousGesture( card.isFaceUp ? dragGesture(for: card, at: index) : nil ) .overlay { if isLast { Color.clear .dropTarget(location) .offset(y: yOffset(for: index)) } } } } .frame(width: layout.cardWidth) .accessibilityElement(children: .contain) .accessibilityLabel(stackAccessibilityLabel) } private var emptySlotLabel: Text { switch location { case .tableau(let i): Text("Empty tableau column \(i + 1)") case .foundation(let i): Text("Empty foundation \(i + 1)") case .freeCell(let i): Text("Empty free cell \(i + 1)") default: Text("Empty slot") } } private var stackAccessibilityLabel: Text { switch location { case .tableau(let i): let count = cards.count return Text("Tableau column \(i + 1), \(count) card\(count == 1 ? "" : "s")") default: return Text("") } } private func yOffset(for index: Int) -> CGFloat { var offset: CGFloat = 0 for i in 0.. some Gesture { LongPressGesture(minimumDuration: 0.15) .sequenced(before: DragGesture(coordinateSpace: .named("board"))) .onChanged { value in if case .second(true, let drag?) = value { if viewModel.draggedCards.isEmpty { let cardsToMove = Array(cards[index...]) viewModel.beginDrag(cards: cardsToMove, from: location) } viewModel.dragPosition = drag.location } } .onEnded { value in if case .second(true, let drag?) = value { viewModel.endDrag(at: drag.location) } else { viewModel.cancelDrag() } } } private func isHinted(_ card: Card) -> Bool { guard viewModel.isShowingHint, let hint = viewModel.currentHint else { return false } return hint.cards.contains(card) } }