11import Observation
2+ import OpenClawChatUI
23import SwiftUI
34
45enum AppAppearancePreference : String , CaseIterable , Identifiable {
@@ -79,11 +80,9 @@ enum OpenClawBrand {
7980 static let uiAccentForeground = adaptiveUIColor ( light: ( 183 , 56 , 51 ) , dark: ( 255 , 107 , 102 ) )
8081 static let uiAccentHot = adaptiveUIColor ( light: ( 204 , 75 , 69 ) , dark: ( 232 , 92 , 86 ) )
8182 static let uiAccentHotForeground = adaptiveUIColor ( light: ( 166 , 55 , 50 ) , dark: ( 255 , 123 , 115 ) )
82- static let uiAccentPressed = adaptiveUIColor ( light: ( 148 , 40 , 36 ) , dark: ( 148 , 40 , 36 ) )
8383 static let uiTeal = adaptiveUIColor ( light: ( 0 , 196 , 176 ) , dark: ( 0 , 196 , 176 ) )
8484 static let uiVoid = adaptiveUIColor ( light: ( 246 , 247 , 249 ) , dark: ( 11 , 12 , 17 ) )
8585 static let uiObsidian = adaptiveUIColor ( light: ( 255 , 255 , 255 ) , dark: ( 19 , 21 , 28 ) )
86- static let uiTextPrimary = adaptiveUIColor ( light: ( 11 , 12 , 17 ) , dark: ( 242 , 239 , 232 ) )
8786 static let uiTextSecondary = adaptiveUIColor ( light: ( 90 , 94 , 110 ) , dark: ( 168 , 170 , 191 ) )
8887 static let uiOK = adaptiveUIColor ( light: ( 19 , 122 , 62 ) , dark: ( 48 , 209 , 88 ) )
8988 static let uiWarn = adaptiveUIColor ( light: ( 154 , 87 , 0 ) , dark: ( 255 , 214 , 10 ) )
@@ -94,18 +93,41 @@ enum OpenClawBrand {
9493 static let accentForeground = Color ( uiColor: Self . uiAccentForeground)
9594 static let accentHot = Color ( uiColor: Self . uiAccentHot)
9695 static let accentHotForeground = Color ( uiColor: Self . uiAccentHotForeground)
97- static let accentPressed = Color ( uiColor: Self . uiAccentPressed)
9896 static let teal = Color ( uiColor: Self . uiTeal)
9997 static let void = Color ( uiColor: Self . uiVoid)
10098 static let obsidian = Color ( uiColor: Self . uiObsidian)
101- static let textPrimary = Color ( uiColor: Self . uiTextPrimary)
10299 static let textSecondary = Color ( uiColor: Self . uiTextSecondary)
103100 static let danger = Color ( uiColor: Self . uiDanger)
104101 static let ok = Color ( uiColor: Self . uiOK)
105102 static let warn = Color ( uiColor: Self . uiWarn)
106103 static let info = Color ( uiColor: Self . uiInfo)
107104 static let graphite = void
108105 static let graphiteElevated = obsidian
106+ static let activationCanvas = Color ( uiColor: adaptiveUIColor ( light: ( 255 , 255 , 255 ) , dark: ( 18 , 14 , 15 ) ) )
107+ static let activationSurface = Color ( uiColor: adaptiveUIColor ( light: ( 255 , 253 , 252 ) , dark: ( 33 , 29 , 30 ) ) )
108+ static let activationSecondaryActionTop = Color ( uiColor: adaptiveUIColor (
109+ light: ( 255 , 255 , 255 ) ,
110+ dark: ( 36 , 32 , 33 ) ) )
111+ static let activationSecondaryActionBottom = Color ( uiColor: adaptiveUIColor (
112+ light: ( 248 , 248 , 250 ) ,
113+ dark: ( 30 , 27 , 28 ) ) )
114+ static let activationInsetSurface = Color ( uiColor: adaptiveUIColor ( light: ( 246 , 241 , 238 ) , dark: ( 44 , 37 , 38 ) ) )
115+ static let activationNeutralSurface = Color ( uiColor: adaptiveUIColor ( light: ( 242 , 242 , 247 ) , dark: ( 34 , 34 , 37 ) ) )
116+ static let activationNeutralInsetSurface = Color ( uiColor: adaptiveUIColor (
117+ light: ( 247 , 247 , 249 ) ,
118+ dark: ( 42 , 42 , 45 ) ) )
119+ static let activationNeutralStroke = Color ( uiColor: adaptiveUIColor (
120+ light: ( 0 , 0 , 0 ) ,
121+ dark: ( 255 , 255 , 255 ) ) . withAlphaComponent ( 0.08 ) )
122+ static let activationNeutralDivider = Color ( uiColor: adaptiveUIColor (
123+ light: ( 0 , 0 , 0 ) ,
124+ dark: ( 255 , 255 , 255 ) ) . withAlphaComponent ( 0.09 ) )
125+ static let activationPrimaryAction = Color ( uiColor: adaptiveUIColor ( light: ( 209 , 54 , 51 ) , dark: ( 238 , 82 , 76 ) ) )
126+ static let activationPrimaryActionText = Color . white
127+ static let activationHairline = Color ( uiColor: adaptiveUIColor (
128+ light: ( 136 , 44 , 40 ) ,
129+ dark: ( 255 , 210 , 205 ) ) . withAlphaComponent ( 0.13 ) )
130+ static let activationGlow = Color ( uiColor: adaptiveUIColor ( light: ( 228 , 78 , 67 ) , dark: ( 255 , 111 , 96 ) ) )
109131
110132 static var sheetBackground : LinearGradient {
111133 LinearGradient (
@@ -118,6 +140,59 @@ enum OpenClawBrand {
118140 endPoint: . bottomTrailing)
119141 }
120142
143+ static var activationCanvasGradient : LinearGradient {
144+ LinearGradient (
145+ colors: [
146+ activationCanvas,
147+ activationCanvas,
148+ ] ,
149+ startPoint: . topLeading,
150+ endPoint: . bottomTrailing)
151+ }
152+
153+ static var activationInsetGradient : LinearGradient {
154+ LinearGradient (
155+ colors: [
156+ activationInsetSurface. opacity ( 0.94 ) ,
157+ activationInsetSurface,
158+ activationSurface. opacity ( 0.36 ) ,
159+ ] ,
160+ startPoint: . top,
161+ endPoint: . bottom)
162+ }
163+
164+ static var activationNeutralGradient : LinearGradient {
165+ LinearGradient (
166+ colors: [
167+ activationNeutralInsetSurface,
168+ activationNeutralSurface,
169+ ] ,
170+ startPoint: . top,
171+ endPoint: . bottom)
172+ }
173+
174+ static var activationPrimaryGradient : LinearGradient {
175+ LinearGradient (
176+ colors: [
177+ Color ( red: 1.0 , green: 0.42 , blue: 0.34 ) ,
178+ activationGlow,
179+ activationPrimaryAction,
180+ Color ( red: 0.66 , green: 0.12 , blue: 0.12 ) ,
181+ ] ,
182+ startPoint: . topLeading,
183+ endPoint: . bottomTrailing)
184+ }
185+
186+ static var activationDisabledGradient : LinearGradient {
187+ LinearGradient (
188+ colors: [
189+ activationNeutralInsetSurface,
190+ activationNeutralSurface. opacity ( 0.92 ) ,
191+ ] ,
192+ startPoint: . top,
193+ endPoint: . bottom)
194+ }
195+
121196 private static func adaptiveUIColor(
122197 light: ( red: CGFloat , green: CGFloat , blue: CGFloat ) ,
123198 dark: ( red: CGFloat , green: CGFloat , blue: CGFloat ) ) -> UIColor
@@ -133,6 +208,21 @@ enum OpenClawBrand {
133208 }
134209}
135210
211+ struct OpenClawActivationGlyph : View {
212+ let size : CGFloat
213+
214+ var body : some View {
215+ OpenClawMascotView ( floats: false )
216+ . frame ( width: self . size, height: self . size)
217+ . shadow (
218+ color: OpenClawBrand . activationGlow. opacity ( 0.18 ) ,
219+ radius: self . size * 0.12 ,
220+ x: 0 ,
221+ y: self . size * 0.05 )
222+ . accessibilityHidden ( true )
223+ }
224+ }
225+
136226extension View {
137227 func openClawSheetChrome( ) -> some View {
138228 self
@@ -142,4 +232,222 @@ extension View {
142232 . ignoresSafeArea ( )
143233 }
144234 }
235+
236+ func openClawCraftSurface( cornerRadius: CGFloat = 24 , shadow: Bool = true ) -> some View {
237+ self . modifier ( OpenClawCraftSurfaceModifier ( cornerRadius: cornerRadius, shadow: shadow) )
238+ }
239+ }
240+
241+ private struct OpenClawCraftSurfaceModifier : ViewModifier {
242+ let cornerRadius : CGFloat
243+ let shadow : Bool
244+
245+ func body( content: Content ) -> some View {
246+ content
247+ . background {
248+ RoundedRectangle ( cornerRadius: self . cornerRadius, style: . continuous)
249+ . fill ( OpenClawBrand . activationSurface)
250+ . shadow (
251+ color: self . shadow ? Color . black. opacity ( 0.07 ) : . clear,
252+ radius: 16 ,
253+ x: 0 ,
254+ y: 8 )
255+ }
256+ . overlay ( alignment: . top) {
257+ RoundedRectangle ( cornerRadius: self . cornerRadius, style: . continuous)
258+ . stroke ( Color . white. opacity ( 0.36 ) , lineWidth: 0.5 )
259+ . blendMode ( . plusLighter)
260+ }
261+ . overlay {
262+ RoundedRectangle ( cornerRadius: self . cornerRadius, style: . continuous)
263+ . stroke ( OpenClawBrand . activationHairline, lineWidth: 0.5 )
264+ }
265+ }
266+ }
267+
268+ struct OpenClawPrimaryActionButtonStyle : ButtonStyle {
269+ @Environment ( \. isEnabled) private var isEnabled
270+ var height : CGFloat = 54
271+ var cornerRadius : CGFloat = 18
272+
273+ private var resolvedCornerRadius : CGFloat {
274+ self . height / 2
275+ }
276+
277+ func makeBody( configuration: Configuration ) -> some View {
278+ configuration. label
279+ . font ( OpenClawType . subheadSemiBold)
280+ . foregroundStyle ( self . isEnabled ? OpenClawBrand . activationPrimaryActionText : Color . secondary)
281+ . tint ( self . isEnabled ? OpenClawBrand . activationPrimaryActionText : Color . secondary)
282+ . frame ( maxWidth: . infinity)
283+ . frame ( height: self . height)
284+ . background {
285+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
286+ . fill ( self . isEnabled ? Self . primaryFill : OpenClawBrand . activationDisabledGradient)
287+ . shadow (
288+ color: self . isEnabled ? OpenClawBrand . activationPrimaryAction. opacity ( 0.08 ) : . clear,
289+ radius: 1 ,
290+ x: 0 ,
291+ y: 1 )
292+ . shadow (
293+ color: self . isEnabled ? OpenClawBrand . activationPrimaryAction. opacity ( 0.08 ) : . clear,
294+ radius: 2 ,
295+ x: 0 ,
296+ y: 2 )
297+ }
298+ . overlay ( alignment: . top) {
299+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
300+ . fill (
301+ LinearGradient (
302+ colors: [
303+ Color . white. opacity ( self . isEnabled ? 0.14 : 0.06 ) ,
304+ Color . white. opacity ( 0 ) ,
305+ ] ,
306+ startPoint: . top,
307+ endPoint: . center) )
308+ . frame ( height: self . height * 0.48 )
309+ . allowsHitTesting ( false )
310+ }
311+ . overlay ( alignment: . bottom) {
312+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
313+ . fill (
314+ LinearGradient (
315+ colors: [
316+ Color . white. opacity ( 0 ) ,
317+ Color . white. opacity ( self . isEnabled ? 0.08 : 0.03 ) ,
318+ ] ,
319+ startPoint: . top,
320+ endPoint: . bottom) )
321+ . frame ( height: self . height * 0.34 )
322+ . allowsHitTesting ( false )
323+ }
324+ . overlay {
325+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
326+ . stroke (
327+ self . isEnabled
328+ ? OpenClawBrand . activationPrimaryAction. opacity ( 0.72 )
329+ : OpenClawBrand . activationNeutralStroke,
330+ lineWidth: 0.75 )
331+ }
332+ . scaleEffect ( configuration. isPressed && self . isEnabled ? 0.98 : 1 )
333+ . animation ( . smooth( duration: 0.14 ) , value: configuration. isPressed)
334+ }
335+
336+ private static var primaryFill : LinearGradient {
337+ LinearGradient (
338+ colors: [
339+ Color ( red: 0.93 , green: 0.27 , blue: 0.25 ) ,
340+ Color ( red: 0.91 , green: 0.25 , blue: 0.24 ) ,
341+ ] ,
342+ startPoint: . top,
343+ endPoint: . bottom)
344+ }
345+ }
346+
347+ struct OpenClawSecondaryActionButtonStyle : ButtonStyle {
348+ @Environment ( \. isEnabled) private var isEnabled
349+ var height : CGFloat = 50
350+ var cornerRadius : CGFloat = 18
351+ var shadowOpacity : Double = 0.035
352+
353+ private var resolvedCornerRadius : CGFloat {
354+ self . height / 2
355+ }
356+
357+ func makeBody( configuration: Configuration ) -> some View {
358+ configuration. label
359+ . font ( OpenClawType . subheadSemiBold)
360+ . foregroundStyle ( self . isEnabled ? OpenClawBrand . activationPrimaryAction : . secondary)
361+ . frame ( maxWidth: . infinity)
362+ . frame ( height: self . height)
363+ . background {
364+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
365+ . fill ( Self . secondaryFill)
366+ . shadow (
367+ color: self . isEnabled ? Color . black. opacity ( self . shadowOpacity) : . clear,
368+ radius: 1 ,
369+ x: 0 ,
370+ y: 1 )
371+ . shadow (
372+ color: self . isEnabled ? Color . black. opacity ( self . shadowOpacity) : . clear,
373+ radius: 2 ,
374+ x: 0 ,
375+ y: 2 )
376+ }
377+ . overlay ( alignment: . top) {
378+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
379+ . fill (
380+ LinearGradient (
381+ colors: [
382+ Color . white. opacity ( 0.42 ) ,
383+ Color . white. opacity ( 0 ) ,
384+ ] ,
385+ startPoint: . top,
386+ endPoint: . center) )
387+ . frame ( height: self . height * 0.48 )
388+ . allowsHitTesting ( false )
389+ }
390+ . overlay {
391+ RoundedRectangle ( cornerRadius: self . resolvedCornerRadius, style: . continuous)
392+ . stroke ( OpenClawBrand . activationHairline, lineWidth: 0.75 )
393+ }
394+ . scaleEffect ( configuration. isPressed && self . isEnabled ? 0.98 : 1 )
395+ . animation ( . smooth( duration: 0.14 ) , value: configuration. isPressed)
396+ }
397+
398+ private static var secondaryFill : LinearGradient {
399+ LinearGradient (
400+ colors: [
401+ OpenClawBrand . activationSecondaryActionTop,
402+ OpenClawBrand . activationSecondaryActionBottom,
403+ ] ,
404+ startPoint: . top,
405+ endPoint: . bottom)
406+ }
407+ }
408+
409+ struct OpenClawCloseButtonStyle : ButtonStyle {
410+ @Environment ( \. isEnabled) private var isEnabled
411+ var minWidth : CGFloat = 36
412+ var height : CGFloat = 36
413+
414+ func makeBody( configuration: Configuration ) -> some View {
415+ configuration. label
416+ . font ( OpenClawType . subheadSemiBold)
417+ . foregroundStyle (
418+ self . isEnabled
419+ ? OpenClawBrand . activationPrimaryAction
420+ : Color . secondary)
421+ . fixedSize ( horizontal: true , vertical: false )
422+ . frame ( minWidth: self . minWidth)
423+ . frame ( height: self . height)
424+ . padding ( . horizontal, 7 )
425+ . background {
426+ Capsule ( style: . continuous)
427+ . fill ( OpenClawBrand . activationNeutralGradient)
428+ . shadow (
429+ color: self . isEnabled ? Color . black. opacity ( 0.045 ) : . clear,
430+ radius: 1 ,
431+ x: 0 ,
432+ y: 1 )
433+ . shadow (
434+ color: self . isEnabled ? Color . black. opacity ( 0.03 ) : . clear,
435+ radius: 4 ,
436+ x: 0 ,
437+ y: 2 )
438+ }
439+ . overlay ( alignment: . top) {
440+ Capsule ( style: . continuous)
441+ . stroke ( Color . white. opacity ( 0.55 ) , lineWidth: 0.5 )
442+ . blendMode ( . plusLighter)
443+ }
444+ . overlay {
445+ Capsule ( style: . continuous)
446+ . stroke ( OpenClawBrand . activationNeutralStroke, lineWidth: 0.6 )
447+ }
448+ . contentShape ( Capsule ( style: . continuous) )
449+ . opacity ( configuration. isPressed && self . isEnabled ? 0.66 : 1 )
450+ . scaleEffect ( configuration. isPressed && self . isEnabled ? 0.98 : 1 )
451+ . animation ( . smooth( duration: 0.14 ) , value: configuration. isPressed)
452+ }
145453}
0 commit comments