import SwiftUI import UIKit import CoreText /// The web UI's design tokens (`internal/web/static/style.css` in /// krz/gitbay), so the two surfaces read as one product. Every pair is /// the stylesheet's light/dark value for the same variable. extension Color { nonisolated private static func paired(_ light: UInt32, _ dark: UInt32) -> Color { Color(UIColor { traits in UIColor(hex: traits.userInterfaceStyle == .dark ? dark : light) }) } /// --accent: links and focus. nonisolated static let gbAccent = paired(0x0000F0, 0x6272FF) /// --ok: open things, passing builds, verified signatures. nonisolated static let gbOK = paired(0x0A7D3C, 0x3FCE7A) /// --bad: closed without merging, failures, bad signatures. nonisolated static let gbBad = paired(0xC62828, 0xFF6B6B) /// --done: merged. nonisolated static let gbDone = paired(0x6B21A8, 0xC084FC) /// --warn: text-sized warnings. nonisolated static let gbWarn = paired(0xC2410C, 0xFF6B3D) /// --mark: the brand orange, bars and underlines. nonisolated static let gbMark = paired(0xE4572E, 0xFF6B3D) /// --diff-add / --diff-del: row grounds in diffs. nonisolated static let gbDiffAdd = paired(0xE4F6EA, 0x0D2A18) nonisolated static let gbDiffDel = paired(0xFDEAEA, 0x2C1113) /// --code-bg: code blocks. nonisolated static let gbCodeBackground = paired(0xF5F5F5, 0x050505) /// --fill-subtle: hunk headers and quiet fills. nonisolated static let gbFillSubtle = paired(0xECECEC, 0x161616) /// --faint: hairlines, row separators, and an empty calendar cell. nonisolated static let gbFaint = paired(0xEAEAEA, 0x1C1C1C) /// --bg: the page ground the accent is mixed toward. Chrome uses the /// system background; this is for blends that must match the web. nonisolated static let gbBackground = paired(0xFFFFFF, 0x0A0A0A) /// --line: control borders and table edges. nonisolated static let gbLine = paired(0xD4D4D4, 0x2E2E2E) } extension UIColor { nonisolated convenience init(hex: UInt32) { self.init( red: CGFloat((hex >> 16) & 0xFF) / 255, green: CGFloat((hex >> 8) & 0xFF) / 255, blue: CGFloat(hex & 0xFF) / 255, alpha: 1 ) } } /// The web's chip shape: --r-pill is 2px, not a capsule. nonisolated let gbChipShape = RoundedRectangle(cornerRadius: 2) /// Atkinson Hyperlegible, the faces the web serves, scaled with Dynamic /// Type. Falls back to the system face wherever the fonts fail to /// register. extension Font { nonisolated static func gbSans(_ style: TextStyle) -> Font { .custom("Atkinson Hyperlegible Next", size: gbSize(style), relativeTo: style) } nonisolated static func gbMono(_ style: TextStyle) -> Font { .custom("Atkinson Hyperlegible Mono", size: gbSize(style), relativeTo: style) } /// Base sizes tracking the system text styles. nonisolated private static func gbSize(_ style: TextStyle) -> CGFloat { switch style { case .largeTitle: 34 case .title: 28 case .title2: 22 case .title3: 20 case .headline: 17 case .body: 17 case .callout: 16 case .subheadline: 15 case .footnote: 13 case .caption: 12 case .caption2: 11 @unknown default: 17 } } } /// Registers the bundled faces once at launch. Registration failure is /// survivable: Font.custom falls back to the system face. enum GitbayFonts { static func register() { let names = [ "AtkinsonNext-Regular", "AtkinsonNext-Bold", "AtkinsonNext-Semibold", "AtkinsonNext-Italic", "AtkinsonMono-Regular", "AtkinsonMono-Semibold", ] let urls = names.compactMap { Bundle.main.url(forResource: $0, withExtension: "ttf") } guard !urls.isEmpty else { return } CTFontManagerRegisterFontURLs(urls as CFArray, .process, true, nil) } } /// The web's notice idiom: a 3px stripe in the state's color down the /// left edge, not a tinted fill. `.error` uses --bad, `.notice` --ok; /// anything the server refused is --warn. struct GBNotice: View { let text: String let color: Color init(_ text: String, _ color: Color = .gbBad) { self.text = text self.color = color } var body: some View { HStack(alignment: .top, spacing: 10) { Rectangle() .fill(color) .frame(width: 3) Text(text) .font(.gbSans(.subheadline)) .frame(maxWidth: .infinity, alignment: .leading) } .fixedSize(horizontal: false, vertical: true) .padding(.vertical, 2) } } /// The web's `.chip` recipe: state color, a 7% tint ground, a 35% tint /// border, 2px radius. struct GBChip: View { let text: String let color: Color init(_ text: String, _ color: Color) { self.text = text self.color = color } var body: some View { Text(text) .font(.gbSans(.caption2).weight(.medium)) .foregroundStyle(color) .padding(.horizontal, 7) .padding(.vertical, 1) .background(color.opacity(0.07), in: gbChipShape) .overlay(gbChipShape.stroke(color.opacity(0.35), lineWidth: 1)) } }