// SPDX-License-Identifier: MIT // Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors import SwiftUI enum BorderBeamModifierMath { /// How far around the border the beam's head is, from 0 up to 1. static func head(time: Double, duration: Double) -> Double { guard duration > 0, time.isFinite else { return 0 } return wrapped(time / duration) } /// The trim ranges that cover `start` to `end` on a closed path, in two parts across the seam. static func trims(from start: Double, to end: Double) -> [ClosedRange] { let from = wrapped(start) let to = wrapped(end) if from == to { return [] } if from < to { return [from...to] } return to > 0 ? [from...1, 0...to] : [from...1] } private static func wrapped(_ value: Double) -> Double { value - floor(value) } } /// A light beam that travels around a rounded border at a steady speed, with a fading tail. /// /// With Reduce Motion on, the beam stays still. struct BorderBeamModifier: ViewModifier { var cornerRadius: CGFloat = 20 var lineWidth: CGFloat = 2 var head: Color = .cyan var tail: Color = .purple var duration: Double = 3.5 var length: Double = 0.3 @Environment(\.accessibilityReduceMotion) private var reduceMotion func body(content: Content) -> some View { content.overlay { TimelineView(.animation(minimumInterval: 1.0 / 60.0, paused: reduceMotion)) { timeline in let time = reduceMotion ? 0 : timeline.date.timeIntervalSinceReferenceDate Canvas { context, size in drawBeam(in: context, size: size, position: BorderBeamModifierMath.head(time: time, duration: duration)) } } .allowsHitTesting(false) .accessibilityHidden(true) } } private func drawBeam(in context: GraphicsContext, size: CGSize, position: Double) { let width = max(lineWidth, 0) let rect = CGRect(origin: .zero, size: size).insetBy(dx: width / 2, dy: width / 2) let beam = min(max(length, 0), 1) guard width > 0, beam > 0, rect.width > 0, rect.height > 0 else { return } let radius = min(max(cornerRadius - width / 2, 0), min(rect.width, rect.height) / 2) let border = RoundedRectangle(cornerRadius: radius, style: .continuous).path(in: rect) let slices = 32 var segments: [(path: Path, shading: GraphicsContext.Shading)] = [] for slice in 0.. some View { modifier( BorderBeamModifier( cornerRadius: cornerRadius, lineWidth: lineWidth, head: head, tail: tail, duration: duration, length: length ) ) } } // MARK: - Demo struct BorderBeamModifierDemo: View { var body: some View { ScrollView { VStack(spacing: 32) { VStack(alignment: .leading, spacing: 12) { Image(systemName: "bolt.fill") .font(.title) .foregroundStyle(.cyan) .accessibilityHidden(true) Text("Introducing Beam") .font(.system(.title, design: .rounded, weight: .bold)) Text("A quiet way to draw the eye to what matters most on the screen.") .font(.body) .foregroundStyle(.white.opacity(0.7)) } .foregroundStyle(.white) .padding(24) .frame(maxWidth: .infinity, alignment: .leading) .background(.white.opacity(0.07), in: .rect(cornerRadius: 28, style: .continuous)) .borderBeam(cornerRadius: 28, lineWidth: 2.5) Text("Get early access") .font(.headline) .foregroundStyle(.white) .padding(.horizontal, 28) .padding(.vertical, 14) .background(.white.opacity(0.07), in: .capsule) .borderBeam(cornerRadius: 200, head: .orange, tail: .pink, duration: 2.5, length: 0.4) } .padding(24) } .defaultScrollAnchor(.center, for: .alignment) .background(Color(red: 0.05, green: 0.05, blue: 0.1).ignoresSafeArea()) } } #Preview { BorderBeamModifierDemo() }