195 lines
5.8 KiB
Swift
195 lines
5.8 KiB
Swift
import CoreText
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
enum SybilFontRegistry {
|
|
static func registerIfNeeded() {
|
|
_ = registeredFonts
|
|
}
|
|
|
|
private static let registeredFonts: Void = {
|
|
for fontName in ["Inter", "Orbitron"] {
|
|
guard let url = Bundle.main.url(forResource: fontName, withExtension: "ttf", subdirectory: "Fonts") ??
|
|
Bundle.main.url(forResource: fontName, withExtension: "ttf")
|
|
else {
|
|
continue
|
|
}
|
|
|
|
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
|
|
}
|
|
}()
|
|
}
|
|
|
|
extension Font {
|
|
static func sybil(_ textStyle: Font.TextStyle, weight: Font.Weight = .regular) -> Font {
|
|
SybilFontRegistry.registerIfNeeded()
|
|
return .custom("Inter", size: Self.sybilPointSize(for: textStyle), relativeTo: textStyle)
|
|
.weight(weight)
|
|
}
|
|
|
|
static func sybil(size: CGFloat, weight: Font.Weight = .regular) -> Font {
|
|
SybilFontRegistry.registerIfNeeded()
|
|
return .custom("Inter", size: size)
|
|
.weight(weight)
|
|
}
|
|
|
|
private static func sybilPointSize(for textStyle: Font.TextStyle) -> CGFloat {
|
|
switch textStyle {
|
|
case .largeTitle:
|
|
return 34
|
|
case .title:
|
|
return 28
|
|
case .title2:
|
|
return 22
|
|
case .title3:
|
|
return 20
|
|
case .headline:
|
|
return 17
|
|
case .subheadline:
|
|
return 15
|
|
case .callout:
|
|
return 16
|
|
case .caption:
|
|
return 12
|
|
case .caption2:
|
|
return 11
|
|
case .footnote:
|
|
return 13
|
|
case .body:
|
|
return 17
|
|
@unknown default:
|
|
return 17
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SybilTheme {
|
|
static let background = Color(red: 0.02, green: 0.02, blue: 0.05)
|
|
static let surface = Color(red: 0.05, green: 0.04, blue: 0.10)
|
|
static let surfaceStrong = Color(red: 0.07, green: 0.06, blue: 0.14)
|
|
static let card = Color(red: 0.06, green: 0.05, blue: 0.12)
|
|
static let border = Color(red: 0.24, green: 0.20, blue: 0.38)
|
|
static let primary = Color(red: 0.57, green: 0.38, blue: 0.96)
|
|
static let primarySoft = Color(red: 0.43, green: 0.25, blue: 0.76)
|
|
static let accent = Color(red: 0.31, green: 0.88, blue: 0.95)
|
|
static let text = Color(red: 0.96, green: 0.94, blue: 1.0)
|
|
static let textMuted = Color(red: 0.65, green: 0.62, blue: 0.76)
|
|
static let searchCard = Color(red: 0.07, green: 0.06, blue: 0.14)
|
|
static let userBubble = Color(red: 0.29, green: 0.13, blue: 0.65)
|
|
static let danger = Color(red: 0.96, green: 0.32, blue: 0.40)
|
|
|
|
static var backgroundGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.12, green: 0.08, blue: 0.28),
|
|
Color(red: 0.04, green: 0.03, blue: 0.09),
|
|
background
|
|
],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
}
|
|
|
|
static var brandGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 1.0, green: 0.55, blue: 0.97),
|
|
Color(red: 0.60, green: 0.43, blue: 1.0),
|
|
Color(red: 0.40, green: 0.87, blue: 1.0)
|
|
],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
|
|
static var panelGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.07, green: 0.06, blue: 0.15).opacity(0.94),
|
|
Color(red: 0.03, green: 0.03, blue: 0.08).opacity(0.96)
|
|
],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
)
|
|
}
|
|
|
|
static var primaryGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.43, green: 0.27, blue: 0.92),
|
|
Color(red: 0.45, green: 0.09, blue: 0.63)
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
}
|
|
|
|
static var selectedRowGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
primary.opacity(0.56),
|
|
primarySoft.opacity(0.48)
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
}
|
|
|
|
static var userBubbleGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.38, green: 0.16, blue: 0.80),
|
|
Color(red: 0.25, green: 0.08, blue: 0.55)
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
}
|
|
|
|
static var composerGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.04, green: 0.04, blue: 0.10).opacity(0.98),
|
|
Color(red: 0.09, green: 0.06, blue: 0.16).opacity(0.94)
|
|
],
|
|
startPoint: .topLeading,
|
|
endPoint: .bottomTrailing
|
|
)
|
|
}
|
|
|
|
static var toolCallGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(red: 0.01, green: 0.15, blue: 0.17).opacity(0.70),
|
|
Color(red: 0.03, green: 0.09, blue: 0.15).opacity(0.78)
|
|
],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
|
|
static var failedToolCallGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
danger.opacity(0.18),
|
|
Color(red: 0.15, green: 0.03, blue: 0.07).opacity(0.72)
|
|
],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
}
|
|
|
|
struct SybilWordmark: View {
|
|
var size: CGFloat = 30
|
|
|
|
var body: some View {
|
|
Text("SYBIL")
|
|
.font(.custom("Orbitron", size: size))
|
|
.fontWeight(.black)
|
|
.tracking(0)
|
|
.foregroundStyle(SybilTheme.brandGradient)
|
|
.accessibilityLabel("Sybil")
|
|
}
|
|
}
|