102 lines
4.9 KiB
Swift
102 lines
4.9 KiB
Swift
import SwiftUI
|
|
|
|
/// Shared visual language for ultracode (orchestration) mode: the signature purple, a
|
|
/// gently-pulsing composer glow, and the small effort-pill label. Kept together so the
|
|
/// home chat bar, the session composer, and the effort menus all read identically.
|
|
enum UltracodeStyle {
|
|
/// SF Symbol that marks ultracode throughout the UI — the same sparkles used for the
|
|
/// agent's thinking, so "extra intelligence" reads consistently.
|
|
static let symbol = "sparkles"
|
|
|
|
/// A soft violet gradient used for the glow stroke, hot corner to cool corner.
|
|
static func strokeGradient(intensity: Double) -> LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
AppTheme.ultracode.opacity(0.55 + 0.40 * intensity),
|
|
AppTheme.ultracode.opacity(0.28 + 0.30 * intensity),
|
|
],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing)
|
|
}
|
|
}
|
|
|
|
/// A cute, restrained purple halo that breathes around a view while ultracode is the active
|
|
/// effort. Mirrors `StreakBadge`'s idiom — a single `@State` flag toggled to kick off an
|
|
/// `easeInOut(...).repeatForever(autoreverses:)` pulse — so the animation feels native to the
|
|
/// app. The halo crests brighter and never collapses to nothing, so even at the trough it
|
|
/// reads as "ultracode is on", not a flicker. Honors Reduce Motion: the glow still shows, it
|
|
/// just holds steady instead of pulsing.
|
|
struct UltracodeGlow: ViewModifier {
|
|
/// Whether ultracode is the selected effort. When false the modifier is inert (no glow,
|
|
/// no animation), so a plain chat composer is untouched.
|
|
var active: Bool
|
|
var cornerRadius: CGFloat = 8
|
|
|
|
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
/// Toggled once the view is on screen (and whenever `active` flips), which starts —
|
|
/// or, under Reduce Motion, simply settles — the repeating pulse.
|
|
@State private var pulsing = false
|
|
|
|
func body(content: Content) -> some View {
|
|
// Animate only while ultracode is on, the pulse has been kicked off, and the user
|
|
// hasn't asked to reduce motion; otherwise the halo holds at a calm mid intensity.
|
|
let animating = active && pulsing && !reduceMotion
|
|
let intensity = animating ? 1.0 : 0.45
|
|
return content
|
|
.overlay {
|
|
if active {
|
|
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
|
.strokeBorder(UltracodeStyle.strokeGradient(intensity: intensity),
|
|
lineWidth: animating ? 1.6 : 1.2)
|
|
.shadow(color: AppTheme.ultracode.opacity(animating ? 0.55 : 0.22),
|
|
radius: animating ? 9 : 4)
|
|
// A wider, fainter outer bloom so the glow feels soft rather than a
|
|
// hard ring — the second shadow reads as ambient light around the box.
|
|
.shadow(color: AppTheme.ultracode.opacity(animating ? 0.30 : 0),
|
|
radius: animating ? 16 : 0)
|
|
.animation(
|
|
reduceMotion
|
|
? .default
|
|
: .easeInOut(duration: 1.8).repeatForever(autoreverses: true),
|
|
value: pulsing)
|
|
.allowsHitTesting(false) // purely decorative — never eats composer taps
|
|
.transition(.opacity)
|
|
}
|
|
}
|
|
.animation(.easeInOut(duration: 0.3), value: active)
|
|
.onAppear { pulsing = active }
|
|
.onChange(of: active) { _, now in pulsing = now }
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
/// Wrap a composer (or any rounded box) in the ultracode purple glow while `active`.
|
|
/// Place it *after* the view's own background so the halo sits around the box.
|
|
func ultracodeGlow(active: Bool, cornerRadius: CGFloat = 8) -> some View {
|
|
modifier(UltracodeGlow(active: active, cornerRadius: cornerRadius))
|
|
}
|
|
}
|
|
|
|
/// The effort-menu trigger label when ultracode is selected: a sparkles glyph + "Ultracode"
|
|
/// in the signature purple, replacing the plain "Effort: xhigh" text so the mode is obvious
|
|
/// at a glance. The sparkle does a slow twinkle to echo the composer glow (steady under
|
|
/// Reduce Motion).
|
|
struct UltracodeEffortLabel: View {
|
|
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
|
@State private var twinkle = false
|
|
|
|
var body: some View {
|
|
let on = twinkle && !reduceMotion
|
|
return HStack(spacing: 4) {
|
|
Image(systemName: UltracodeStyle.symbol)
|
|
.opacity(on ? 1.0 : 0.7)
|
|
.scaleEffect(on ? 1.0 : 0.9)
|
|
.animation(
|
|
reduceMotion ? .default : .easeInOut(duration: 1.2).repeatForever(autoreverses: true),
|
|
value: twinkle)
|
|
Text("Ultracode")
|
|
}
|
|
.foregroundStyle(AppTheme.ultracode)
|
|
.onAppear { twinkle = true }
|
|
}
|
|
}
|