import Cocoa import QuartzCore public enum HUDState { case hidden case recording(level: Float, isLagging: Bool = false) case retrying case transcribing case success case error } public final class HUDOverlayController { public static let shared = HUDOverlayController() private var window: NSPanel? private var orbView: SiriOrbView? private var isVisible = false // Large canvas so shadows/halos NEVER clip at window boundaries private let canvasSize: CGFloat = 260 private init() { DispatchQueue.main.async { self.setupWindow() } } private func setupWindow() { let panel = NSPanel( contentRect: NSRect(x: 0, y: 0, width: canvasSize, height: canvasSize), styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false ) panel.isOpaque = false panel.backgroundColor = .clear panel.level = .floating panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary] panel.hasShadow = false panel.ignoresMouseEvents = true panel.alphaValue = 0.0 let container = NSView(frame: panel.contentView!.bounds) container.wantsLayer = true // Siri-like Circular Orb centered with plenty of breathing room for glowing halos let orbSize: CGFloat = 64 let orbOrigin = (canvasSize - orbSize) / 2.0 let orb = SiriOrbView(frame: NSRect(x: orbOrigin, y: orbOrigin, width: orbSize, height: orbSize)) container.addSubview(orb) self.orbView = orb panel.contentView = container self.window = panel reposition() } private func reposition() { guard let window = self.window, let screen = NSScreen.main else { return } let screenRect = screen.visibleFrame // Center horizontally, position near bottom of screen let x = screenRect.midX - (canvasSize / 2.0) let y = screenRect.minY + 12 window.setFrameOrigin(NSPoint(x: x, y: y)) } public func show(state: HUDState) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } if self.window == nil { self.setupWindow() } guard let window = self.window, let orb = self.orbView else { return } self.reposition() switch state { case .hidden: self.hide() case .recording(let level, let isLagging): if !self.isVisible { window.orderFrontRegardless() window.animator().alphaValue = 1.0 self.isVisible = true } orb.updateAudioLevel(level, isLagging: isLagging) case .retrying: if !self.isVisible { window.orderFrontRegardless() window.animator().alphaValue = 1.0 self.isVisible = true } orb.showRetryState() case .transcribing: orb.showTranscribingState() case .success: orb.showSuccessState() DispatchQueue.main.asyncAfter(deadline: .now() + 0.45) { self.hide() } case .error: orb.showErrorState() DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) { self.hide() } } } } public func hide() { DispatchQueue.main.async { [weak self] in guard let self = self, let window = self.window, self.isVisible else { return } NSAnimationContext.runAnimationGroup({ context in context.duration = 0.20 window.animator().alphaValue = 0.0 }, completionHandler: { window.orderOut(nil) self.isVisible = false self.orbView?.reset() }) } } } // MARK: - Premium Apple Intelligence / Siri Multilayer Glowing Orb final class SiriOrbView: NSView { private let ambientAuraLayer = CALayer() private let haloLayer = CALayer() private let acousticRippleLayer = CALayer() private let glassBackdrop = CALayer() private let siriGradientLayer = CAGradientLayer() private let specularHighlight = CAGradientLayer() override init(frame frameRect: NSRect) { super.init(frame: frameRect) wantsLayer = true setupLayers() } required init?(coder: NSCoder) { super.init(coder: coder) wantsLayer = true setupLayers() } private func setupLayers() { guard let layer = self.layer else { return } layer.masksToBounds = false let bounds = self.bounds let radius = bounds.width / 2.0 // 1. Deep Ambient Soft Aura (Diffused outer glow) ambientAuraLayer.frame = bounds.insetBy(dx: -32, dy: -32) ambientAuraLayer.cornerRadius = (bounds.width + 64) / 2.0 ambientAuraLayer.backgroundColor = NSColor(red: 0.0, green: 0.65, blue: 1.0, alpha: 0.22).cgColor ambientAuraLayer.shadowColor = NSColor(red: 0.55, green: 0.0, blue: 1.0, alpha: 0.85).cgColor ambientAuraLayer.shadowRadius = 38 ambientAuraLayer.shadowOpacity = 0.95 ambientAuraLayer.shadowOffset = .zero layer.addSublayer(ambientAuraLayer) // 2. Focused Neon Halo haloLayer.frame = bounds.insetBy(dx: -14, dy: -14) haloLayer.cornerRadius = (bounds.width + 28) / 2.0 haloLayer.backgroundColor = NSColor(red: 0.1, green: 0.8, blue: 1.0, alpha: 0.40).cgColor haloLayer.shadowColor = NSColor(red: 0.0, green: 0.9, blue: 1.0, alpha: 0.85).cgColor haloLayer.shadowRadius = 18 haloLayer.shadowOpacity = 0.9 haloLayer.shadowOffset = .zero layer.addSublayer(haloLayer) // 3. Acoustic Ripple Ring acousticRippleLayer.frame = bounds.insetBy(dx: -4, dy: -4) acousticRippleLayer.cornerRadius = (bounds.width + 8) / 2.0 acousticRippleLayer.borderColor = NSColor(red: 0.0, green: 0.95, blue: 1.0, alpha: 0.35).cgColor acousticRippleLayer.borderWidth = 1.5 layer.addSublayer(acousticRippleLayer) // 4. Dark Glass Spherical Backdrop glassBackdrop.frame = bounds glassBackdrop.cornerRadius = radius glassBackdrop.backgroundColor = NSColor.black.withAlphaComponent(0.85).cgColor glassBackdrop.borderColor = NSColor.white.withAlphaComponent(0.35).cgColor glassBackdrop.borderWidth = 1.0 layer.addSublayer(glassBackdrop) // 5. Multi-Color Liquid Siri Gradient Core siriGradientLayer.frame = bounds.insetBy(dx: 3, dy: 3) siriGradientLayer.cornerRadius = (bounds.width - 6) / 2.0 siriGradientLayer.type = .radial siriGradientLayer.colors = [ NSColor(red: 0.0, green: 0.95, blue: 1.0, alpha: 1.0).cgColor, // Electric Cyan Center NSColor(red: 0.50, green: 0.12, blue: 1.0, alpha: 0.95).cgColor, // Royal Violet NSColor(red: 0.98, green: 0.08, blue: 0.65, alpha: 0.90).cgColor, // Hot Magenta NSColor(red: 0.10, green: 0.20, blue: 0.95, alpha: 0.85).cgColor // Deep Blue ] siriGradientLayer.startPoint = CGPoint(x: 0.35, y: 0.35) siriGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) layer.addSublayer(siriGradientLayer) // 6. 3D Curved Specular Glass Crescent Highlight (Apple Intelligence style) specularHighlight.frame = NSRect(x: bounds.midX - 18, y: bounds.maxY - 18, width: 36, height: 12) specularHighlight.cornerRadius = 6 specularHighlight.colors = [ NSColor.white.withAlphaComponent(0.65).cgColor, NSColor.white.withAlphaComponent(0.0).cgColor ] specularHighlight.startPoint = CGPoint(x: 0.5, y: 1.0) specularHighlight.endPoint = CGPoint(x: 0.5, y: 0.0) layer.addSublayer(specularHighlight) // Start fluid rotation startOrbRotation() } private func startOrbRotation() { let rotate = CABasicAnimation(keyPath: "transform.rotation.z") rotate.fromValue = 0.0 rotate.toValue = 2 * Double.pi rotate.duration = 4.0 rotate.repeatCount = .infinity rotate.isRemovedOnCompletion = false siriGradientLayer.add(rotate, forKey: "siriFluidRotate") } func updateAudioLevel(_ level: Float, isLagging: Bool) { CATransaction.begin() CATransaction.setDisableActions(true) let clamped = CGFloat(min(max(level, 0.0), 1.0)) if isLagging { // Warm amber pulse for weak internet siriGradientLayer.colors = [ NSColor(red: 1.0, green: 0.85, blue: 0.2, alpha: 1.0).cgColor, NSColor(red: 1.0, green: 0.55, blue: 0.0, alpha: 0.95).cgColor, NSColor(red: 0.95, green: 0.25, blue: 0.0, alpha: 0.90).cgColor ] haloLayer.backgroundColor = NSColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.5).cgColor haloLayer.shadowColor = NSColor(red: 1.0, green: 0.4, blue: 0.0, alpha: 0.9).cgColor ambientAuraLayer.shadowColor = NSColor(red: 1.0, green: 0.3, blue: 0.0, alpha: 0.7).cgColor } else { // High-fidelity Siri electric gradient siriGradientLayer.colors = [ NSColor(red: 0.0, green: 0.95, blue: 1.0, alpha: 1.0).cgColor, NSColor(red: 0.50, green: 0.12, blue: 1.0, alpha: 0.95).cgColor, NSColor(red: 0.98, green: 0.08, blue: 0.65, alpha: 0.90).cgColor, NSColor(red: 0.10, green: 0.20, blue: 0.95, alpha: 0.85).cgColor ] haloLayer.backgroundColor = NSColor(red: 0.1, green: 0.8, blue: 1.0, alpha: 0.40).cgColor haloLayer.shadowColor = NSColor(red: 0.0, green: 0.9, blue: 1.0, alpha: 0.85).cgColor ambientAuraLayer.shadowColor = NSColor(red: 0.55, green: 0.0, blue: 1.0, alpha: 0.85).cgColor } // Massive Audio Dynamic Scaling (Orb expands up to 1.65x with voice) let scale = 1.0 + (clamped * 0.65) self.layer?.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) // Dynamic Halo bloom (Expands smoothly up to 55px radius) haloLayer.shadowRadius = 18 + (clamped * 36) haloLayer.shadowOpacity = Float(0.75 + (clamped * 0.25)) ambientAuraLayer.shadowRadius = 38 + (clamped * 42) // Acoustic ripple expands outward let rippleScale = 1.0 + (clamped * 0.35) acousticRippleLayer.transform = CATransform3DMakeScale(rippleScale, rippleScale, 1.0) acousticRippleLayer.opacity = Float(0.3 + (clamped * 0.7)) CATransaction.commit() } func showRetryState() { CATransaction.begin() CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.3) siriGradientLayer.colors = [ NSColor(red: 1.0, green: 0.85, blue: 0.2, alpha: 1.0).cgColor, NSColor(red: 1.0, green: 0.50, blue: 0.0, alpha: 0.95).cgColor, NSColor(red: 0.95, green: 0.20, blue: 0.0, alpha: 0.90).cgColor ] haloLayer.shadowColor = NSColor.orange.cgColor ambientAuraLayer.shadowColor = NSColor.orange.cgColor CATransaction.commit() } func showTranscribingState() { CATransaction.begin() CATransaction.setDisableActions(true) self.layer?.sublayerTransform = CATransform3DMakeScale(1.08, 1.08, 1.0) CATransaction.commit() } func showSuccessState() { CATransaction.begin() CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.2) siriGradientLayer.colors = [ NSColor(red: 0.2, green: 1.0, blue: 0.5, alpha: 1.0).cgColor, NSColor(red: 0.0, green: 0.85, blue: 0.8, alpha: 0.95).cgColor ] haloLayer.shadowColor = NSColor.green.cgColor ambientAuraLayer.shadowColor = NSColor.green.cgColor CATransaction.commit() } func showErrorState() { CATransaction.begin() CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.2) siriGradientLayer.colors = [ NSColor(red: 1.0, green: 0.2, blue: 0.2, alpha: 1.0).cgColor, NSColor(red: 0.8, green: 0.0, blue: 0.4, alpha: 0.95).cgColor ] haloLayer.shadowColor = NSColor.red.cgColor ambientAuraLayer.shadowColor = NSColor.red.cgColor CATransaction.commit() } func reset() { CATransaction.begin() CATransaction.setDisableActions(true) self.layer?.sublayerTransform = CATransform3DIdentity haloLayer.shadowRadius = 18 ambientAuraLayer.shadowRadius = 38 acousticRippleLayer.transform = CATransform3DIdentity acousticRippleLayer.opacity = 0.3 CATransaction.commit() } }