// // StarFieldView.swift // MoonWatch // import SwiftUI struct StarFieldView: View { var starCount: Int = 160 @State private var stars: [Star] = [] private struct Star: Identifiable { let id = UUID() let position: CGPoint // 0...1 normalized let radius: CGFloat let baseOpacity: Double let twinklePhase: Double let twinkleSpeed: Double let warm: Bool } var body: some View { GeometryReader { geo in TimelineView(.animation(minimumInterval: 1.0 / 12.0)) { ctx in Canvas { context, size in let t = ctx.date.timeIntervalSinceReferenceDate for star in stars { let twinkle = (sin(t * star.twinkleSpeed + star.twinklePhase) + 1) / 2 let alpha = star.baseOpacity * (0.35 + 0.65 * twinkle) let center = CGPoint( x: star.position.x * size.width, y: star.position.y * size.height ) let rect = CGRect( x: center.x - star.radius, y: center.y - star.radius, width: star.radius * 2, height: star.radius * 2 ) let color: Color = star.warm ? Color(red: 1.0, green: 0.92, blue: 0.78).opacity(alpha) : Color(red: 0.92, green: 0.95, blue: 1.0).opacity(alpha) context.fill(Path(ellipseIn: rect), with: .color(color)) if star.radius > 1.1 { // Soft halo for the larger stars. let halo = rect.insetBy(dx: -star.radius * 1.6, dy: -star.radius * 1.6) context.fill( Path(ellipseIn: halo), with: .color(color.opacity(0.20)) ) } } } } .onAppear { if stars.isEmpty { stars = Self.generate(count: starCount) } } } .ignoresSafeArea() .allowsHitTesting(false) } private static func generate(count: Int) -> [Star] { var rng = SystemRandomNumberGenerator() return (0.. 0.88 return Star( position: CGPoint( x: CGFloat.random(in: 0...1, using: &rng), y: CGFloat.random(in: 0...1, using: &rng) ), radius: big ? CGFloat.random(in: 1.4...2.2, using: &rng) : CGFloat.random(in: 0.4...1.1, using: &rng), baseOpacity: Double.random(in: 0.35...0.95, using: &rng), twinklePhase: Double.random(in: 0...(2 * .pi), using: &rng), twinkleSpeed: Double.random(in: 0.5...1.7, using: &rng), warm: Double.random(in: 0...1, using: &rng) > 0.75 ) } } } #Preview { ZStack { Color(red: 0.03, green: 0.04, blue: 0.10).ignoresSafeArea() StarFieldView() } }