diff --git a/src/HUDOverlay.swift b/src/HUDOverlay.swift index ec9d7ab..f35cf91 100644 --- a/src/HUDOverlay.swift +++ b/src/HUDOverlay.swift @@ -14,10 +14,10 @@ public final class HUDOverlayController { public static let shared = HUDOverlayController() private var window: NSPanel? - private var orbView: AppleIntelligenceOrbView? + private var orbView: MinimalSolidOrbView? private var isVisible = false - // Large transparent canvas so audio-reactive halo never clips + // Transparent canvas with plenty of margin private let canvasSize: CGFloat = 200 private init() { @@ -44,10 +44,10 @@ public final class HUDOverlayController { let container = NSView(frame: panel.contentView!.bounds) container.wantsLayer = true - // Professional Apple Intelligence Orb centered in canvas - let orbDiameter: CGFloat = 54 + // Solid Minimal Orb centered in canvas + let orbDiameter: CGFloat = 52 let orbOrigin = (canvasSize - orbDiameter) / 2.0 - let orb = AppleIntelligenceOrbView(frame: NSRect(x: orbOrigin, y: orbOrigin, width: orbDiameter, height: orbDiameter)) + let orb = MinimalSolidOrbView(frame: NSRect(x: orbOrigin, y: orbOrigin, width: orbDiameter, height: orbDiameter)) container.addSubview(orb) self.orbView = orb @@ -127,15 +127,15 @@ public final class HUDOverlayController { } } -// MARK: - Professional Apple Intelligence Orb View +// MARK: - Minimal Solid-Color Orb with Single High-Amplitude Dynamic Stroke (Zero Static Rings) -final class AppleIntelligenceOrbView: NSView { - private let glowLayer = CALayer() - private let coreGradientLayer = CAGradientLayer() - private let strokeBorderLayer = CALayer() +final class MinimalSolidOrbView: NSView { + private let coreSolidLayer = CALayer() + private let dynamicStrokeLayer = CALayer() - private let minStrokeWidth: CGFloat = 2.0 - private let maxStrokeWidth: CGFloat = 7.5 + // Wide dynamic range: from a delicate 1.5px ring up to a bold 11.5px pulse! + private let minStrokeWidth: CGFloat = 1.5 + private let maxStrokeWidth: CGFloat = 11.5 override init(frame frameRect: NSRect) { super.init(frame: frameRect) @@ -156,85 +156,62 @@ final class AppleIntelligenceOrbView: NSView { let bounds = self.bounds let radius = bounds.width / 2.0 - // 1. Atmospheric Ambient Glow (Expands and blooms with decibel level) - glowLayer.frame = bounds.insetBy(dx: -10, dy: -10) - glowLayer.cornerRadius = (bounds.width + 20) / 2.0 - glowLayer.backgroundColor = NSColor(red: 0.0, green: 0.70, blue: 1.0, alpha: 0.25).cgColor - glowLayer.shadowColor = NSColor(red: 0.0, green: 0.85, blue: 1.0, alpha: 0.85).cgColor - glowLayer.shadowRadius = 12 - glowLayer.shadowOpacity = 0.45 - glowLayer.shadowOffset = .zero - layer.addSublayer(glowLayer) + // 1. Chic, Minimal Solid-Color Core (Apple Obsidian Charcoal Solid Disc) + // 100% solid, perfectly flat, clean and matte + coreSolidLayer.frame = bounds + coreSolidLayer.cornerRadius = radius + coreSolidLayer.backgroundColor = NSColor(red: 0.09, green: 0.10, blue: 0.13, alpha: 0.98).cgColor + layer.addSublayer(coreSolidLayer) - // 2. Deep Sapphire/Midnight Blue Radial Core (Clean, minimal, luxury Apple design) - coreGradientLayer.frame = bounds - coreGradientLayer.cornerRadius = radius - coreGradientLayer.type = .radial - coreGradientLayer.colors = [ - NSColor(red: 0.05, green: 0.55, blue: 0.95, alpha: 1.0).cgColor, // Vibrant Cerulean Center - NSColor(red: 0.12, green: 0.18, blue: 0.55, alpha: 0.98).cgColor, // Deep Royal Blue - NSColor(red: 0.06, green: 0.08, blue: 0.22, alpha: 0.98).cgColor // Midnight Obsidian Edge - ] - coreGradientLayer.startPoint = CGPoint(x: 0.40, y: 0.40) - coreGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0) - layer.addSublayer(coreGradientLayer) - - // 3. Audio-Reactive Neon Stroke (Widening & Brightening dynamically with voice) - strokeBorderLayer.frame = bounds - strokeBorderLayer.cornerRadius = radius - strokeBorderLayer.borderWidth = minStrokeWidth - strokeBorderLayer.borderColor = NSColor(red: 0.30, green: 0.90, blue: 1.0, alpha: 0.75).cgColor - strokeBorderLayer.backgroundColor = NSColor.clear.cgColor - layer.addSublayer(strokeBorderLayer) + // 2. Single Dynamic Audio-Reactive Stroke Ring (NO SECOND RING / NO STATIC BACKGROUND) + // Solid Electric Amethyst Violet (#B066FF) + dynamicStrokeLayer.frame = bounds + dynamicStrokeLayer.cornerRadius = radius + dynamicStrokeLayer.borderWidth = minStrokeWidth + dynamicStrokeLayer.borderColor = NSColor(red: 0.70, green: 0.38, blue: 1.0, alpha: 0.75).cgColor + dynamicStrokeLayer.backgroundColor = NSColor.clear.cgColor + dynamicStrokeLayer.shadowColor = NSColor(red: 0.65, green: 0.25, blue: 1.0, alpha: 0.85).cgColor + dynamicStrokeLayer.shadowOffset = .zero + dynamicStrokeLayer.shadowRadius = 4.0 + dynamicStrokeLayer.shadowOpacity = 0.40 + layer.addSublayer(dynamicStrokeLayer) } - /// Updates audio response: - /// In silence (level ~ 0.0): crisp minimal 2px stroke, subtle soft glow. - /// When speaking: stroke powerfully thickens up to 7.5px, glow blooms to 42px, smooth gentle scale breathing. + /// Updates audio response with boosted logarithmic curve for wide dynamic amplitude: + /// In quiet: minimal 1.5px stroke. + /// When speaking: expands dramatically up to 11.5px with bright electric violet glow. func updateAudioLevel(_ level: Float, isLagging: Bool) { CATransaction.begin() CATransaction.setDisableActions(true) let clamped = CGFloat(min(max(level, 0.0), 1.0)) + // Perceptual boost power curve so lower/normal voice levels trigger a bold visible response + let dynamicFactor = pow(clamped, 0.70) if isLagging { - // Amber warm warning for degraded internet - coreGradientLayer.colors = [ - NSColor(red: 1.0, green: 0.75, blue: 0.15, alpha: 1.0).cgColor, - NSColor(red: 0.85, green: 0.40, blue: 0.05, alpha: 0.98).cgColor, - NSColor(red: 0.25, green: 0.10, blue: 0.02, alpha: 0.98).cgColor - ] - let amberStroke = NSColor(red: 1.0, green: 0.80, blue: 0.25, alpha: 0.7 + (0.3 * clamped)).cgColor - strokeBorderLayer.borderColor = amberStroke - glowLayer.shadowColor = NSColor.orange.cgColor + // Warm amber gold highlight for degraded network + let amber = NSColor(red: 1.0, green: 0.68, blue: 0.15, alpha: 0.75 + (0.25 * dynamicFactor)).cgColor + dynamicStrokeLayer.borderColor = amber + dynamicStrokeLayer.shadowColor = amber } else { - // Standard crisp Apple Intelligence sapphire palette - coreGradientLayer.colors = [ - NSColor(red: 0.05, green: 0.55 + (0.40 * clamped), blue: 0.95 + (0.05 * clamped), alpha: 1.0).cgColor, - NSColor(red: 0.12, green: 0.18, blue: 0.55, alpha: 0.98).cgColor, - NSColor(red: 0.06, green: 0.08, blue: 0.22, alpha: 0.98).cgColor - ] - let neonStroke = NSColor( - red: 0.30 + (0.60 * clamped), - green: 0.90 + (0.10 * clamped), + // Solid Electric Amethyst Violet palette: brightens to glowing neon with volume + let violetStroke = NSColor( + red: 0.70 + (0.20 * dynamicFactor), + green: 0.38 + (0.50 * dynamicFactor), blue: 1.0, - alpha: 0.65 + (0.35 * clamped) + alpha: 0.75 + (0.25 * dynamicFactor) ).cgColor - strokeBorderLayer.borderColor = neonStroke - glowLayer.shadowColor = NSColor(red: 0.0, green: 0.85, blue: 1.0, alpha: 0.90).cgColor + dynamicStrokeLayer.borderColor = violetStroke + dynamicStrokeLayer.shadowColor = NSColor(red: 0.65, green: 0.25, blue: 1.0, alpha: 0.85).cgColor } - // 1. Dynamic Stroke Expansion: thickens from 2.0px to 7.5px noticeably - let dynamicWidth = minStrokeWidth + (clamped * (maxStrokeWidth - minStrokeWidth)) - strokeBorderLayer.borderWidth = dynamicWidth - - // 2. Dynamic Bloom Halo: blooms from 12px up to 42px - glowLayer.shadowRadius = 12.0 + (clamped * 30.0) - glowLayer.shadowOpacity = Float(0.45 + (0.50 * clamped)) + // 1. High Dynamic Amplitude Stroke Expansion (1.5px to 11.5px) + let dynamicWidth = minStrokeWidth + (dynamicFactor * (maxStrokeWidth - minStrokeWidth)) + dynamicStrokeLayer.borderWidth = dynamicWidth - // 3. Subtle organic breathing scale (1.0 to 1.14x max, smooth and stable without shaking) - let subtleScale = 1.0 + (clamped * 0.14) - self.layer?.sublayerTransform = CATransform3DMakeScale(subtleScale, subtleScale, 1.0) + // 2. Soft Dynamic Bloom Glow (From 4px to 26px radius, strictly tracking voice) + dynamicStrokeLayer.shadowRadius = 4.0 + (dynamicFactor * 22.0) + dynamicStrokeLayer.shadowOpacity = Float(0.40 + (0.55 * dynamicFactor)) CATransaction.commit() } @@ -243,21 +220,23 @@ final class AppleIntelligenceOrbView: NSView { CATransaction.begin() CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.20) - strokeBorderLayer.borderWidth = 4.0 - strokeBorderLayer.borderColor = NSColor.orange.cgColor - glowLayer.shadowColor = NSColor.orange.cgColor - glowLayer.shadowRadius = 24 - glowLayer.shadowOpacity = 0.9 + let amber = NSColor(red: 1.0, green: 0.65, blue: 0.10, alpha: 1.0).cgColor + dynamicStrokeLayer.borderWidth = 5.0 + dynamicStrokeLayer.borderColor = amber + dynamicStrokeLayer.shadowColor = amber + dynamicStrokeLayer.shadowRadius = 18 + dynamicStrokeLayer.shadowOpacity = 0.90 CATransaction.commit() } func showTranscribingState() { CATransaction.begin() CATransaction.setDisableActions(true) - strokeBorderLayer.borderWidth = 3.0 - strokeBorderLayer.borderColor = NSColor.cyan.cgColor - glowLayer.shadowRadius = 18 - glowLayer.shadowOpacity = 0.8 + let softLilac = NSColor(red: 0.80, green: 0.50, blue: 1.0, alpha: 1.0).cgColor + dynamicStrokeLayer.borderWidth = 3.5 + dynamicStrokeLayer.borderColor = softLilac + dynamicStrokeLayer.shadowRadius = 10 + dynamicStrokeLayer.shadowOpacity = 0.65 CATransaction.commit() } @@ -266,11 +245,11 @@ final class AppleIntelligenceOrbView: NSView { CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.18) let green = NSColor(red: 0.15, green: 0.95, blue: 0.45, alpha: 1.0).cgColor - strokeBorderLayer.borderWidth = 4.0 - strokeBorderLayer.borderColor = green - glowLayer.shadowColor = green - glowLayer.shadowRadius = 24 - glowLayer.shadowOpacity = 0.95 + dynamicStrokeLayer.borderWidth = 5.0 + dynamicStrokeLayer.borderColor = green + dynamicStrokeLayer.shadowColor = green + dynamicStrokeLayer.shadowRadius = 20 + dynamicStrokeLayer.shadowOpacity = 0.95 CATransaction.commit() } @@ -279,22 +258,21 @@ final class AppleIntelligenceOrbView: NSView { CATransaction.setDisableActions(false) CATransaction.setAnimationDuration(0.18) let red = NSColor(red: 1.0, green: 0.20, blue: 0.20, alpha: 1.0).cgColor - strokeBorderLayer.borderWidth = 4.0 - strokeBorderLayer.borderColor = red - glowLayer.shadowColor = red - glowLayer.shadowRadius = 24 - glowLayer.shadowOpacity = 0.95 + dynamicStrokeLayer.borderWidth = 5.0 + dynamicStrokeLayer.borderColor = red + dynamicStrokeLayer.shadowColor = red + dynamicStrokeLayer.shadowRadius = 20 + dynamicStrokeLayer.shadowOpacity = 0.95 CATransaction.commit() } func reset() { CATransaction.begin() CATransaction.setDisableActions(true) - self.layer?.sublayerTransform = CATransform3DIdentity - strokeBorderLayer.borderWidth = minStrokeWidth - strokeBorderLayer.borderColor = NSColor(red: 0.30, green: 0.90, blue: 1.0, alpha: 0.75).cgColor - glowLayer.shadowRadius = 12 - glowLayer.shadowOpacity = 0.45 + dynamicStrokeLayer.borderWidth = minStrokeWidth + dynamicStrokeLayer.borderColor = NSColor(red: 0.70, green: 0.38, blue: 1.0, alpha: 0.75).cgColor + dynamicStrokeLayer.shadowRadius = 4.0 + dynamicStrokeLayer.shadowOpacity = 0.40 CATransaction.commit() } }