Created
November 15, 2025 14:36
-
-
Save cjhodge/5f4a0cdea365a54e55c0bfbdd3fdd501 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // SpeedLinesView.swift | |
| // | |
| // | |
| // Created by Chris Hodge on 10/14/25. | |
| // | |
| import SwiftUI | |
| struct SpeedLinesView: View { | |
| @State private var staticLineRefresh = 0 | |
| var body: some View { | |
| GeometryReader { geometry in | |
| ZStack { | |
| // Blue background | |
| Color.blue | |
| .ignoresSafeArea() | |
| // Static long teardrop lines - refresh based on state | |
| ForEach(0..<40, id: \.self) { index in | |
| StaticSpeedLine( | |
| angle: Double(index) * 9.0 + Double.random(in: -3...3), | |
| opacity: Double.random(in: 0.2...0.6), | |
| width: CGFloat.random(in: 15...50), | |
| length: CGFloat.random(in: 600...1500), | |
| startDistance: CGFloat.random(in: 60...150), | |
| isBlue: Double.random(in: 0...1) < 0.3, | |
| screenWidth: geometry.size.width, | |
| screenHeight: geometry.size.height, | |
| refreshId: staticLineRefresh | |
| ) | |
| } | |
| } | |
| .onAppear { | |
| Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in | |
| staticLineRefresh += 1 | |
| } | |
| } | |
| } | |
| } | |
| } | |
| struct StaticSpeedLine: View { | |
| let angle: Double | |
| let opacity: Double | |
| let width: CGFloat | |
| let length: CGFloat | |
| let startDistance: CGFloat | |
| let isBlue: Bool | |
| let screenWidth: CGFloat | |
| let screenHeight: CGFloat | |
| let refreshId: Int | |
| var body: some View { | |
| let centerX = screenWidth / 2 | |
| let centerY = screenHeight / 2 | |
| let radians = angle * .pi / 180 | |
| let lineColor = isBlue ? Color(red: 0.7, green: 0.85, blue: 1.0) : Color.white | |
| // Create long teardrop shape | |
| Path { path in | |
| let height = width | |
| // Start at the narrow end (left) | |
| path.move(to: CGPoint(x: 0, y: height / 2)) | |
| // Curve up to the wide end | |
| path.addQuadCurve( | |
| to: CGPoint(x: length, y: 0), | |
| control: CGPoint(x: length * 0.6, y: 0) | |
| ) | |
| // Curve across the wide end | |
| path.addQuadCurve( | |
| to: CGPoint(x: length, y: height), | |
| control: CGPoint(x: length * 1.05, y: height / 2) | |
| ) | |
| // Curve back down to the narrow end | |
| path.addQuadCurve( | |
| to: CGPoint(x: 0, y: height / 2), | |
| control: CGPoint(x: length * 0.6, y: height) | |
| ) | |
| } | |
| .fill( | |
| LinearGradient( | |
| gradient: Gradient(colors: [ | |
| lineColor.opacity(0), | |
| lineColor.opacity(opacity * 0.6), | |
| lineColor.opacity(opacity), | |
| lineColor.opacity(0) | |
| ]), | |
| startPoint: .leading, | |
| endPoint: .trailing | |
| ) | |
| ) | |
| .frame(width: length, height: width) | |
| .rotationEffect(.degrees(angle)) | |
| .offset(x: cos(radians) * (startDistance + length / 2), y: sin(radians) * (startDistance + length / 2)) | |
| .position(x: centerX, y: centerY) | |
| } | |
| } | |
| #Preview { | |
| SpeedLinesView() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment