import SwiftUI // MARK: - Centered Icon View struct CaseraIconView: View { var houseProgress: CGFloat = 1.0 var windowScale: CGFloat = 1.0 var checkmarkScale: CGFloat = 1.0 var foregroundColor: Color = Color(red: 1.0, green: 0.96, blue: 0.92) var backgroundColor: Color? = nil // nil uses default gradient, otherwise uses theme color var body: some View { GeometryReader { geo in let size = min(geo.size.width, geo.size.height) let center = CGPoint(x: geo.size.width / 2, y: geo.size.height / 2) ZStack { // Background - use provided color or default gradient if let bgColor = backgroundColor { RoundedRectangle(cornerRadius: size * 0.195) .fill( LinearGradient( colors: [bgColor, bgColor.opacity(0.85)], startPoint: .top, endPoint: .bottom ) ) .frame(width: size * 0.906, height: size * 0.906) .position(center) } else { RoundedRectangle(cornerRadius: size * 0.195) .fill( LinearGradient( colors: [ Color(red: 1.0, green: 0.64, blue: 0.28), Color(red: 0.96, green: 0.51, blue: 0.20) ], startPoint: .top, endPoint: .bottom ) ) .frame(width: size * 0.906, height: size * 0.906) .position(center) } // House outline HousePath(progress: houseProgress) .stroke(foregroundColor, style: StrokeStyle( lineWidth: size * 0.055, lineCap: .round, lineJoin: .round )) .frame(width: size, height: size) .position(center) // Window RoundedRectangle(cornerRadius: size * 0.023) .fill(foregroundColor) .frame(width: size * 0.102, height: size * 0.102) .scaleEffect(windowScale) .position(x: center.x, y: center.y - size * 0.09) // Checkmark CheckmarkShape() .stroke(foregroundColor, style: StrokeStyle( lineWidth: size * 0.0625, lineCap: .round, lineJoin: .round )) .frame(width: size, height: size) .scaleEffect(checkmarkScale) .position(center) } } .aspectRatio(1, contentMode: .fit) } } // MARK: - House Path (both sides) struct HousePath: Shape { var progress: CGFloat = 1.0 var animatableData: CGFloat { get { progress } set { progress = newValue } } func path(in rect: CGRect) -> Path { let w = rect.width let h = rect.height let cx = rect.midX let cy = rect.midY var path = Path() // Left side: roof peak -> down left wall -> foot path.move(to: CGPoint(x: cx, y: cy - h * 0.27)) path.addLine(to: CGPoint(x: cx - w * 0.232, y: cy - h * 0.09)) path.addQuadCurve( to: CGPoint(x: cx - w * 0.266, y: cy + h * 0.02), control: CGPoint(x: cx - w * 0.266, y: cy - h * 0.055) ) path.addLine(to: CGPoint(x: cx - w * 0.266, y: cy + h * 0.195)) path.addQuadCurve( to: CGPoint(x: cx - w * 0.207, y: cy + h * 0.254), control: CGPoint(x: cx - w * 0.266, y: cy + h * 0.254) ) path.addLine(to: CGPoint(x: cx - w * 0.154, y: cy + h * 0.254)) // Right side: roof peak -> down right wall -> foot path.move(to: CGPoint(x: cx, y: cy - h * 0.27)) path.addLine(to: CGPoint(x: cx + w * 0.232, y: cy - h * 0.09)) path.addQuadCurve( to: CGPoint(x: cx + w * 0.266, y: cy + h * 0.02), control: CGPoint(x: cx + w * 0.266, y: cy - h * 0.055) ) path.addLine(to: CGPoint(x: cx + w * 0.266, y: cy + h * 0.195)) path.addQuadCurve( to: CGPoint(x: cx + w * 0.207, y: cy + h * 0.254), control: CGPoint(x: cx + w * 0.266, y: cy + h * 0.254) ) path.addLine(to: CGPoint(x: cx + w * 0.154, y: cy + h * 0.254)) return path.trimmedPath(from: 0, to: progress) } } // MARK: - Checkmark Shape struct CheckmarkShape: Shape { var progress: CGFloat = 1.0 var animatableData: CGFloat { get { progress } set { progress = newValue } } func path(in rect: CGRect) -> Path { let w = rect.width let h = rect.height let cx = rect.midX let cy = rect.midY var path = Path() // Checkmark: starts bottom-left, goes to bottom-center, then up-right path.move(to: CGPoint(x: cx - w * 0.158, y: cy + h * 0.145)) path.addLine(to: CGPoint(x: cx - w * 0.041, y: cy + h * 0.263)) path.addLine(to: CGPoint(x: cx + w * 0.193, y: cy + h * 0.01)) return path.trimmedPath(from: 0, to: progress) } } // MARK: - Animations struct FullIntroAnimationView: View { @State private var houseProgress: CGFloat = 0 @State private var windowScale: CGFloat = 0 @State private var checkScale: CGFloat = 0 var body: some View { CaseraIconView( houseProgress: houseProgress, windowScale: windowScale, checkmarkScale: checkScale ) .onAppear { animate() } } func animate() { withAnimation(.easeOut(duration: 0.6)) { houseProgress = 1.0 } withAnimation(.spring(response: 0.4, dampingFraction: 0.6).delay(0.5)) { windowScale = 1.0 } withAnimation(.easeOut(duration: 0.25).delay(0.9)) { checkScale = 1.2 } withAnimation(.easeInOut(duration: 0.15).delay(1.15)) { checkScale = 1.0 } } } struct PulsatingCheckmarkView: View { @State private var checkScale: CGFloat = 1.0 var body: some View { CaseraIconView(checkmarkScale: checkScale) .onAppear { withAnimation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true)) { checkScale = 1.3 } } } } struct PulsingIconView: View { @State private var scale: CGFloat = 1.0 var backgroundColor: Color? = nil var body: some View { CaseraIconView(backgroundColor: backgroundColor) .scaleEffect(scale) .onAppear { withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { scale = 1.08 } } } } struct BouncyIconView: View { @State private var offset: CGFloat = -300 @State private var scale: CGFloat = 0.5 var body: some View { CaseraIconView() .scaleEffect(scale) .offset(y: offset) .onAppear { withAnimation(.spring(response: 0.6, dampingFraction: 0.5)) { offset = 0 scale = 1.0 } } } } struct WigglingIconView: View { @State private var angle: Double = 0 var body: some View { CaseraIconView() .rotationEffect(.degrees(angle)) .onAppear { withAnimation(.easeInOut(duration: 0.1).repeatForever(autoreverses: true)) { angle = 5 } } } } // MARK: - Playground UI struct PlaygroundContentView: View { @State private var selectedAnimation = 0 @State private var animationKey = UUID() let animations = ["Full Intro", "Pulsating", "Pulse", "Bounce", "Wiggle"] var body: some View { VStack(spacing: 20) { Text("MyCrib Icon Animations") .font(.title) .fontWeight(.bold) ZStack { RoundedRectangle(cornerRadius: 20) .fill(Color(.systemGray6)) .frame(height: 250) currentAnimation .frame(width: 150, height: 150) .id(animationKey) } .padding(.horizontal) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 10) { ForEach(0..