CasinoGames/Baccarat/Views/ResultBannerView.swift

164 lines
5.4 KiB
Swift

//
// ResultBannerView.swift
// Baccarat
//
// Animated result banner showing the winner and winnings.
//
import SwiftUI
/// An animated banner showing the round result.
struct ResultBannerView: View {
let result: GameResult
let winnings: Int
@State private var showBanner = false
@State private var showText = false
@State private var showWinnings = false
var body: some View {
ZStack {
// Background overlay
Color.black.opacity(showBanner ? 0.5 : 0)
.ignoresSafeArea()
.animation(.easeIn(duration: 0.3), value: showBanner)
// Banner
VStack(spacing: 20) {
// Result text
Text(result.displayText)
.font(.system(size: 36, weight: .black, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [.white, result.color],
startPoint: .top,
endPoint: .bottom
)
)
.shadow(color: result.color.opacity(0.8), radius: 10)
.scaleEffect(showText ? 1.0 : 0.5)
.opacity(showText ? 1.0 : 0)
// Winnings display
if winnings != 0 {
HStack(spacing: 8) {
if winnings > 0 {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.green)
Text("\(winnings)")
.foregroundStyle(.green)
} else {
Image(systemName: "minus.circle.fill")
.foregroundStyle(.red)
Text("\(abs(winnings))")
.foregroundStyle(.red)
}
}
.font(.system(size: 28, weight: .bold, design: .rounded))
.scaleEffect(showWinnings ? 1.0 : 0.5)
.opacity(showWinnings ? 1.0 : 0)
}
}
.padding(40)
.background(
RoundedRectangle(cornerRadius: 24)
.fill(
LinearGradient(
colors: [
Color(white: 0.15),
Color(white: 0.08)
],
startPoint: .top,
endPoint: .bottom
)
)
.overlay(
RoundedRectangle(cornerRadius: 24)
.strokeBorder(
LinearGradient(
colors: [
result.color.opacity(0.8),
result.color.opacity(0.3)
],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: 3
)
)
)
.shadow(color: result.color.opacity(0.3), radius: 30)
.scaleEffect(showBanner ? 1.0 : 0.8)
.opacity(showBanner ? 1.0 : 0)
}
.onAppear {
withAnimation(.spring(duration: 0.4, bounce: 0.3)) {
showBanner = true
}
withAnimation(.spring(duration: 0.4, bounce: 0.3).delay(0.2)) {
showText = true
}
withAnimation(.spring(duration: 0.4, bounce: 0.3).delay(0.4)) {
showWinnings = true
}
}
}
}
/// Confetti particle for celebrations.
struct ConfettiPiece: View {
let color: Color
@State private var position: CGPoint = .zero
@State private var rotation: Double = 0
@State private var opacity: Double = 1
var body: some View {
Rectangle()
.fill(color)
.frame(width: 8, height: 12)
.rotationEffect(.degrees(rotation))
.position(position)
.opacity(opacity)
.onAppear {
let screenWidth = 400.0
let startX = Double.random(in: 0...screenWidth)
position = CGPoint(x: startX, y: -20)
withAnimation(.easeIn(duration: Double.random(in: 2...4))) {
position = CGPoint(
x: startX + Double.random(in: -100...100),
y: 800
)
rotation = Double.random(in: 360...1080)
opacity = 0
}
}
}
}
/// A confetti celebration overlay.
struct ConfettiView: View {
let colors: [Color] = [.red, .blue, .green, .yellow, .orange, .purple, .pink]
var body: some View {
ZStack {
ForEach(0..<50, id: \.self) { _ in
ConfettiPiece(color: colors.randomElement() ?? .yellow)
}
}
.allowsHitTesting(false)
}
}
#Preview {
ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15)
.ignoresSafeArea()
ResultBannerView(result: .playerWins, winnings: 500)
}
}