Initial commit: Flights iOS app

Flight search app built on FlightConnections.com API data.
Features: airport search with autocomplete, browse by country/state/map,
flight schedules by route and date, multi-airline support with per-airline
schedule loading. Includes 4,561-airport GPS database for map browsing.
Adaptive light/dark mode UI inspired by Flighty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-08 15:01:07 -05:00
commit 3790792040
46 changed files with 5116 additions and 0 deletions

View File

@@ -0,0 +1,723 @@
import AppKit
import Foundation
let canvasSize = CGSize(width: 1024, height: 1024)
enum Palette {
static let accent = NSColor(hex: 0x6366F1)
static let accentLight = NSColor(hex: 0x818CF8)
static let boarding = NSColor(hex: 0x3B82F6)
static let mint = NSColor(hex: 0x10B981)
static let slate = NSColor(hex: 0x0F172A)
static let sky = NSColor(hex: 0x38BDF8)
static let cloud = NSColor(hex: 0xE2E8F0)
static let mist = NSColor(hex: 0xF8FAFC)
static let ink = NSColor(hex: 0x111827)
}
struct IconOption {
let fileName: String
let label: String
let draw: (CGRect) -> Void
}
extension NSColor {
convenience init(hex: UInt32, alpha: CGFloat = 1.0) {
self.init(
calibratedRed: CGFloat((hex >> 16) & 0xFF) / 255.0,
green: CGFloat((hex >> 8) & 0xFF) / 255.0,
blue: CGFloat(hex & 0xFF) / 255.0,
alpha: alpha
)
}
}
extension CGRect {
var center: CGPoint { CGPoint(x: midX, y: midY) }
}
func roundedRectPath(_ rect: CGRect, radius: CGFloat) -> NSBezierPath {
NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
}
func circleRect(center: CGPoint, radius: CGFloat) -> CGRect {
CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
}
func withGraphicsState(_ draw: () -> Void) {
NSGraphicsContext.saveGraphicsState()
draw()
NSGraphicsContext.restoreGraphicsState()
}
func applyShadow(
color: NSColor,
blur: CGFloat,
x: CGFloat = 0,
y: CGFloat = 0,
draw: () -> Void
) {
withGraphicsState {
let shadow = NSShadow()
shadow.shadowColor = color
shadow.shadowBlurRadius = blur
shadow.shadowOffset = NSSize(width: x, height: y)
shadow.set()
draw()
}
}
func fillPath(_ path: NSBezierPath, colors: [NSColor], angle: CGFloat) {
guard let gradient = NSGradient(colors: colors) else { return }
gradient.draw(in: path, angle: angle)
}
func strokePath(_ path: NSBezierPath, color: NSColor, lineWidth: CGFloat) {
color.setStroke()
path.lineWidth = lineWidth
path.stroke()
}
func fillCircle(center: CGPoint, radius: CGFloat, color: NSColor) {
let path = NSBezierPath(ovalIn: circleRect(center: center, radius: radius))
color.setFill()
path.fill()
}
func fillRoundedRect(_ rect: CGRect, radius: CGFloat, colors: [NSColor], angle: CGFloat) {
let path = roundedRectPath(rect, radius: radius)
fillPath(path, colors: colors, angle: angle)
}
func drawSymbol(
_ name: String,
in rect: CGRect,
pointSize: CGFloat,
tint: NSColor,
weight: NSFont.Weight = .regular,
scale: NSImage.SymbolScale = .large,
rotationDegrees: CGFloat = 0,
alpha: CGFloat = 1.0
) {
guard
let symbol = NSImage(systemSymbolName: name, accessibilityDescription: nil),
let configured = symbol.withSymbolConfiguration(.init(pointSize: pointSize, weight: weight, scale: scale))
else {
return
}
let tinted = NSImage(size: configured.size)
tinted.lockFocus()
let imageRect = NSRect(origin: .zero, size: configured.size)
configured.draw(in: imageRect, from: NSRect.zero, operation: NSCompositingOperation.sourceOver, fraction: 1.0)
tint.withAlphaComponent(alpha).set()
imageRect.fill(using: NSCompositingOperation.sourceAtop)
tinted.unlockFocus()
withGraphicsState {
let transform = NSAffineTransform()
transform.translateX(by: rect.midX, yBy: rect.midY)
transform.rotate(byDegrees: rotationDegrees)
transform.translateX(by: -rect.midX, yBy: -rect.midY)
transform.concat()
tinted.draw(in: rect, from: NSRect.zero, operation: NSCompositingOperation.sourceOver, fraction: 1.0)
}
}
func quadraticPath(start: CGPoint, control: CGPoint, end: CGPoint) -> NSBezierPath {
let cp1 = CGPoint(
x: start.x + ((control.x - start.x) * 2.0 / 3.0),
y: start.y + ((control.y - start.y) * 2.0 / 3.0)
)
let cp2 = CGPoint(
x: end.x + ((control.x - end.x) * 2.0 / 3.0),
y: end.y + ((control.y - end.y) * 2.0 / 3.0)
)
let path = NSBezierPath()
path.move(to: start)
path.curve(to: end, controlPoint1: cp1, controlPoint2: cp2)
return path
}
func quadraticPoint(start: CGPoint, control: CGPoint, end: CGPoint, t: CGFloat) -> CGPoint {
let oneMinusT = 1 - t
let x = (oneMinusT * oneMinusT * start.x) + (2 * oneMinusT * t * control.x) + (t * t * end.x)
let y = (oneMinusT * oneMinusT * start.y) + (2 * oneMinusT * t * control.y) + (t * t * end.y)
return CGPoint(x: x, y: y)
}
func quadraticAngle(start: CGPoint, control: CGPoint, end: CGPoint, t: CGFloat) -> CGFloat {
let dx = (2 * (1 - t) * (control.x - start.x)) + (2 * t * (end.x - control.x))
let dy = (2 * (1 - t) * (control.y - start.y)) + (2 * t * (end.y - control.y))
return atan2(dy, dx) * 180 / .pi
}
func drawRouteArc(
start: CGPoint,
control: CGPoint,
end: CGPoint,
color: NSColor,
lineWidth: CGFloat,
dash: [CGFloat] = [22, 18]
) {
let path = quadraticPath(start: start, control: control, end: end)
color.setStroke()
path.lineWidth = lineWidth
path.lineCapStyle = .round
path.setLineDash(dash, count: dash.count, phase: 0)
path.stroke()
}
func drawRing(center: CGPoint, radius: CGFloat, width: CGFloat, color: NSColor, alpha: CGFloat = 1.0) {
let path = NSBezierPath(ovalIn: circleRect(center: center, radius: radius))
strokePath(path, color: color.withAlphaComponent(alpha), lineWidth: width)
}
func drawGlobe(
center: CGPoint,
radius: CGFloat,
fillColors: [NSColor],
landTint: NSColor,
gridColor: NSColor,
highlightColor: NSColor,
landAlpha: CGFloat = 0.35,
strokeColor: NSColor? = nil,
glowColor: NSColor? = nil
) {
let globeRect = circleRect(center: center, radius: radius)
let globePath = NSBezierPath(ovalIn: globeRect)
if let glowColor {
applyShadow(color: glowColor, blur: 48) {
fillCircle(center: center, radius: radius * 0.96, color: glowColor.withAlphaComponent(0.35))
}
}
fillPath(globePath, colors: fillColors, angle: 90)
withGraphicsState {
globePath.addClip()
let continentsRect = globeRect.insetBy(dx: radius * 0.12, dy: radius * 0.12)
drawSymbol(
"globe.americas.fill",
in: continentsRect.offsetBy(dx: radius * 0.03, dy: radius * -0.02),
pointSize: radius * 1.3,
tint: landTint,
weight: .regular,
rotationDegrees: 0,
alpha: landAlpha
)
let meridians: [CGFloat] = [0.42, 0.72]
for scale in meridians {
let ellipse = NSBezierPath(
ovalIn: CGRect(
x: center.x - radius * scale,
y: center.y - radius,
width: radius * scale * 2,
height: radius * 2
)
)
strokePath(ellipse, color: gridColor.withAlphaComponent(0.9), lineWidth: 5)
}
let parallels: [CGFloat] = [0.30, 0.62, 0.86]
for scale in parallels {
let ellipse = NSBezierPath(
ovalIn: CGRect(
x: center.x - radius,
y: center.y - radius * scale,
width: radius * 2,
height: radius * scale * 2
)
)
strokePath(ellipse, color: gridColor.withAlphaComponent(0.68), lineWidth: 4)
}
let specular = NSBezierPath(
ovalIn: CGRect(
x: center.x - radius * 0.62,
y: center.y + radius * 0.16,
width: radius * 0.70,
height: radius * 0.36
)
)
highlightColor.withAlphaComponent(0.18).setFill()
specular.fill()
}
if let strokeColor {
strokePath(globePath, color: strokeColor, lineWidth: 6)
}
}
func drawTopographicLines(rect: CGRect, color: NSColor, count: Int, inset: CGFloat) {
for index in 0..<count {
let y = rect.minY + inset + (CGFloat(index) * ((rect.height - inset * 2) / CGFloat(count)))
let path = NSBezierPath()
path.move(to: CGPoint(x: rect.minX + inset, y: y))
path.curve(
to: CGPoint(x: rect.maxX - inset, y: y + 24),
controlPoint1: CGPoint(x: rect.minX + rect.width * 0.28, y: y - 38),
controlPoint2: CGPoint(x: rect.minX + rect.width * 0.68, y: y + 64)
)
path.lineWidth = 3
path.setLineDash([18, 14], count: 2, phase: CGFloat(index) * 6)
color.setStroke()
path.stroke()
}
}
func drawStarField(rect: CGRect, stars: [(CGFloat, CGFloat, CGFloat, CGFloat)]) {
for (x, y, size, alpha) in stars {
fillCircle(
center: CGPoint(x: rect.minX + x * rect.width, y: rect.minY + y * rect.height),
radius: size,
color: NSColor.white.withAlphaComponent(alpha)
)
}
}
func drawOrbitalRoute(rect: CGRect) {
fillRoundedRect(
rect,
radius: 220,
colors: [
NSColor(hex: 0x08111F),
NSColor(hex: 0x1D2A64),
NSColor(hex: 0x4F46E5)
],
angle: 42
)
fillCircle(center: CGPoint(x: 214, y: 860), radius: 170, color: Palette.accentLight.withAlphaComponent(0.14))
fillCircle(center: CGPoint(x: 832, y: 248), radius: 200, color: Palette.sky.withAlphaComponent(0.18))
drawGlobe(
center: CGPoint(x: 332, y: 368),
radius: 302,
fillColors: [NSColor(hex: 0x183B6C), NSColor(hex: 0x0C1834)],
landTint: NSColor.white,
gridColor: NSColor.white.withAlphaComponent(0.34),
highlightColor: NSColor.white,
landAlpha: 0.18,
strokeColor: NSColor.white.withAlphaComponent(0.12),
glowColor: Palette.sky.withAlphaComponent(0.2)
)
let start = CGPoint(x: 272, y: 464)
let control = CGPoint(x: 500, y: 940)
let end = CGPoint(x: 830, y: 744)
drawRouteArc(
start: start,
control: control,
end: end,
color: NSColor.white.withAlphaComponent(0.50),
lineWidth: 16
)
fillCircle(center: start, radius: 22, color: Palette.mint)
applyShadow(color: NSColor.black.withAlphaComponent(0.28), blur: 24, y: -8) {
let planeCenter = quadraticPoint(start: start, control: control, end: end, t: 0.58)
let planeAngle = quadraticAngle(start: start, control: control, end: end, t: 0.58) - 6
drawSymbol(
"airplane",
in: CGRect(x: planeCenter.x - 92, y: planeCenter.y - 64, width: 184, height: 128),
pointSize: 150,
tint: NSColor.white,
weight: .semibold,
rotationDegrees: planeAngle
)
}
}
func drawWindowBadge(rect: CGRect) {
fillRoundedRect(
rect,
radius: 220,
colors: [
NSColor(hex: 0xE0F2FE),
NSColor(hex: 0xC7D2FE),
NSColor(hex: 0x93C5FD)
],
angle: 65
)
drawRing(center: CGPoint(x: 300, y: 832), radius: 116, width: 30, color: NSColor.white, alpha: 0.20)
fillCircle(center: CGPoint(x: 796, y: 226), radius: 180, color: Palette.accent.withAlphaComponent(0.12))
let badgeCenter = CGPoint(x: 512, y: 528)
let badgeRadius: CGFloat = 356
applyShadow(color: NSColor.black.withAlphaComponent(0.18), blur: 38, y: -14) {
drawGlobe(
center: badgeCenter,
radius: badgeRadius,
fillColors: [NSColor(hex: 0x5046E5), NSColor(hex: 0x1E1B4B)],
landTint: NSColor.white,
gridColor: NSColor.white.withAlphaComponent(0.55),
highlightColor: NSColor.white,
landAlpha: 0.15,
strokeColor: NSColor.white.withAlphaComponent(0.14),
glowColor: Palette.accent.withAlphaComponent(0.2)
)
}
fillCircle(center: CGPoint(x: 406, y: 662), radius: 104, color: NSColor.white.withAlphaComponent(0.10))
let orbit = NSBezierPath(ovalIn: circleRect(center: badgeCenter, radius: badgeRadius + 58))
orbit.lineWidth = 22
orbit.setLineDash([170, 44], count: 2, phase: 82)
NSColor.white.withAlphaComponent(0.58).setStroke()
orbit.stroke()
let planeRect = CGRect(x: 674, y: 680, width: 220, height: 160)
applyShadow(color: NSColor.black.withAlphaComponent(0.28), blur: 24, y: -10) {
drawSymbol(
"airplane",
in: planeRect,
pointSize: 170,
tint: NSColor.white,
weight: .semibold,
rotationDegrees: 32
)
}
}
func drawAtlasTile(rect: CGRect) {
fillRoundedRect(
rect,
radius: 220,
colors: [
NSColor(hex: 0xF8FAFC),
NSColor(hex: 0xEEF2FF),
NSColor(hex: 0xDBEAFE)
],
angle: 90
)
drawTopographicLines(rect: rect, color: Palette.accent.withAlphaComponent(0.14), count: 10, inset: 84)
let roundelRect = CGRect(x: 176, y: 176, width: 672, height: 672)
let roundelPath = NSBezierPath(roundedRect: roundelRect, xRadius: 190, yRadius: 190)
applyShadow(color: NSColor.black.withAlphaComponent(0.12), blur: 24, y: -10) {
fillPath(
roundelPath,
colors: [
NSColor(hex: 0x0F172A),
NSColor(hex: 0x1E293B),
NSColor(hex: 0x312E81)
],
angle: 50
)
}
drawGlobe(
center: CGPoint(x: 512, y: 512),
radius: 244,
fillColors: [NSColor(hex: 0x1D4ED8), NSColor(hex: 0x1E3A8A)],
landTint: NSColor.white,
gridColor: NSColor.white.withAlphaComponent(0.55),
highlightColor: NSColor.white,
landAlpha: 0.20,
strokeColor: NSColor.white.withAlphaComponent(0.10),
glowColor: Palette.boarding.withAlphaComponent(0.10)
)
let orbit = NSBezierPath()
orbit.move(to: CGPoint(x: 228, y: 384))
orbit.curve(
to: CGPoint(x: 786, y: 670),
controlPoint1: CGPoint(x: 322, y: 804),
controlPoint2: CGPoint(x: 650, y: 870)
)
orbit.lineWidth = 18
orbit.lineCapStyle = .round
orbit.setLineDash([28, 22], count: 2, phase: 0)
Palette.accentLight.withAlphaComponent(0.82).setStroke()
orbit.stroke()
fillCircle(center: CGPoint(x: 786, y: 670), radius: 18, color: Palette.mint)
applyShadow(color: NSColor.black.withAlphaComponent(0.28), blur: 24, y: -6) {
drawSymbol(
"airplane",
in: CGRect(x: 356, y: 408, width: 320, height: 230),
pointSize: 246,
tint: NSColor.white,
weight: .bold,
rotationDegrees: 28
)
}
}
func drawNightRing(rect: CGRect) {
fillRoundedRect(
rect,
radius: 220,
colors: [
NSColor(hex: 0x020617),
NSColor(hex: 0x111827),
NSColor(hex: 0x172554)
],
angle: 50
)
drawStarField(
rect: rect,
stars: [
(0.18, 0.82, 5, 0.70),
(0.29, 0.74, 3, 0.42),
(0.77, 0.81, 4, 0.58),
(0.67, 0.69, 3, 0.36),
(0.86, 0.34, 5, 0.62),
(0.15, 0.30, 4, 0.44)
]
)
let center = CGPoint(x: 512, y: 512)
fillCircle(center: center, radius: 350, color: Palette.sky.withAlphaComponent(0.07))
drawRing(center: center, radius: 338, width: 28, color: Palette.sky, alpha: 0.92)
drawRing(center: center, radius: 378, width: 6, color: NSColor.white, alpha: 0.20)
drawGlobe(
center: center,
radius: 252,
fillColors: [NSColor(hex: 0x0F766E), NSColor(hex: 0x0F172A)],
landTint: NSColor.white,
gridColor: NSColor.white.withAlphaComponent(0.44),
highlightColor: NSColor.white,
landAlpha: 0.16,
strokeColor: NSColor.white.withAlphaComponent(0.10),
glowColor: Palette.sky.withAlphaComponent(0.18)
)
fillCircle(center: CGPoint(x: 512, y: 512), radius: 178, color: Palette.mint.withAlphaComponent(0.10))
applyShadow(color: NSColor.black.withAlphaComponent(0.30), blur: 20, y: -6) {
drawSymbol(
"airplane",
in: CGRect(x: 668, y: 676, width: 194, height: 140),
pointSize: 148,
tint: NSColor.white,
weight: .semibold,
rotationDegrees: 36
)
}
}
func drawRouteCard(rect: CGRect) {
fillRoundedRect(
rect,
radius: 220,
colors: [
NSColor(hex: 0xF8FAFC),
NSColor(hex: 0xEEF2FF),
NSColor(hex: 0xE0E7FF)
],
angle: 70
)
let backCard = CGRect(x: 176, y: 204, width: 684, height: 614)
let frontCard = CGRect(x: 132, y: 246, width: 760, height: 560)
let backPath = roundedRectPath(backCard, radius: 88)
let frontPath = roundedRectPath(frontCard, radius: 96)
applyShadow(color: NSColor.black.withAlphaComponent(0.10), blur: 18, y: -8) {
fillPath(backPath, colors: [Palette.accentLight.withAlphaComponent(0.30), Palette.boarding.withAlphaComponent(0.18)], angle: 12)
}
applyShadow(color: NSColor.black.withAlphaComponent(0.12), blur: 24, y: -10) {
fillPath(frontPath, colors: [NSColor.white, NSColor(hex: 0xEEF2FF)], angle: 90)
}
strokePath(frontPath, color: Palette.cloud.withAlphaComponent(0.88), lineWidth: 4)
let globeCenter = CGPoint(x: 314, y: 584)
drawGlobe(
center: globeCenter,
radius: 156,
fillColors: [Palette.accentLight, Palette.accent],
landTint: NSColor.white,
gridColor: NSColor.white.withAlphaComponent(0.52),
highlightColor: NSColor.white,
landAlpha: 0.18,
strokeColor: NSColor.white.withAlphaComponent(0.10),
glowColor: Palette.accent.withAlphaComponent(0.12)
)
let start = CGPoint(x: 346, y: 438)
let control = CGPoint(x: 548, y: 754)
let end = CGPoint(x: 780, y: 538)
drawRouteArc(
start: start,
control: control,
end: end,
color: Palette.ink.withAlphaComponent(0.32),
lineWidth: 16,
dash: [20, 18]
)
fillCircle(center: start, radius: 20, color: Palette.mint)
fillCircle(center: end, radius: 20, color: Palette.boarding)
let planeCenter = quadraticPoint(start: start, control: control, end: end, t: 0.58)
let planeAngle = quadraticAngle(start: start, control: control, end: end, t: 0.58) - 10
applyShadow(color: NSColor.black.withAlphaComponent(0.18), blur: 16, y: -4) {
drawSymbol(
"airplane",
in: CGRect(x: planeCenter.x - 102, y: planeCenter.y - 72, width: 204, height: 144),
pointSize: 166,
tint: Palette.ink,
weight: .bold,
rotationDegrees: planeAngle
)
}
let detailPill = roundedRectPath(CGRect(x: 226, y: 318, width: 360, height: 28), radius: 14)
fillPath(detailPill, colors: [Palette.cloud.withAlphaComponent(0.92), Palette.cloud.withAlphaComponent(0.92)], angle: 0)
let accentPill = roundedRectPath(CGRect(x: 226, y: 272, width: 220, height: 28), radius: 14)
fillPath(
accentPill,
colors: [Palette.accent.withAlphaComponent(0.16), Palette.boarding.withAlphaComponent(0.16)],
angle: 0
)
let actionChip = roundedRectPath(CGRect(x: 620, y: 270, width: 164, height: 54), radius: 27)
fillPath(actionChip, colors: [Palette.accent, Palette.boarding], angle: 0)
drawSymbol(
"airplane.departure",
in: CGRect(x: 662, y: 282, width: 82, height: 34),
pointSize: 46,
tint: NSColor.white,
weight: .bold
)
}
func renderBitmap(size: CGSize = canvasSize, draw: (CGRect) -> Void) -> NSBitmapImageRep {
let rep = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: Int(size.width),
pixelsHigh: Int(size.height),
bitsPerSample: 8,
samplesPerPixel: 4,
hasAlpha: true,
isPlanar: false,
colorSpaceName: .deviceRGB,
bytesPerRow: 0,
bitsPerPixel: 0
)!
rep.size = size
let graphicsContext = NSGraphicsContext(bitmapImageRep: rep)!
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = graphicsContext
draw(CGRect(origin: .zero, size: size))
graphicsContext.flushGraphics()
NSGraphicsContext.restoreGraphicsState()
return rep
}
func savePNG(_ rep: NSBitmapImageRep, to url: URL) throws {
guard let data = rep.representation(using: .png, properties: [:]) else {
throw NSError(domain: "IconGenerator", code: 1)
}
try data.write(to: url)
}
func drawComparisonSheet(options: [IconOption], imageDirectory: URL, outputURL: URL) throws {
let sheetSize = CGSize(width: 2190, height: 1620)
let rep = renderBitmap(size: sheetSize) { rect in
fillRoundedRect(
rect,
radius: 120,
colors: [NSColor(hex: 0xF8FAFC), NSColor(hex: 0xEEF2FF)],
angle: 90
)
let titleAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 82, weight: .bold),
.foregroundColor: Palette.slate
]
let subtitleAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 34, weight: .medium),
.foregroundColor: Palette.ink.withAlphaComponent(0.70)
]
NSAttributedString(string: "Flights App Icon Concepts", attributes: titleAttributes)
.draw(at: CGPoint(x: 112, y: 1470))
NSAttributedString(
string: "1 to 5. Same palette, different attitude.",
attributes: subtitleAttributes
)
.draw(at: CGPoint(x: 118, y: 1412))
let cardWidth: CGFloat = 354
let cardHeight: CGFloat = 474
let horizontalGap: CGFloat = 52
let verticalGap: CGFloat = 50
let startX: CGFloat = 112
let startY: CGFloat = 856
for (index, option) in options.enumerated() {
let row = index / 3
let column = index % 3
let x = startX + CGFloat(column) * (cardWidth + horizontalGap)
let y = startY - CGFloat(row) * (cardHeight + verticalGap)
let cardRect = CGRect(x: x, y: y, width: cardWidth, height: cardHeight)
let cardPath = roundedRectPath(cardRect, radius: 52)
applyShadow(color: NSColor.black.withAlphaComponent(0.10), blur: 20, y: -8) {
fillPath(cardPath, colors: [NSColor.white, NSColor(hex: 0xF8FAFC)], angle: 90)
}
let imageURL = imageDirectory.appendingPathComponent(option.fileName)
if let image = NSImage(contentsOf: imageURL) {
image.draw(in: CGRect(x: x + 27, y: y + 116, width: 300, height: 300))
}
let labelAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.monospacedSystemFont(ofSize: 30, weight: .bold),
.foregroundColor: Palette.accent
]
let titleAttributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 34, weight: .semibold),
.foregroundColor: Palette.slate
]
NSAttributedString(string: "\(index + 1).", attributes: labelAttributes)
.draw(at: CGPoint(x: x + 28, y: y + 58))
NSAttributedString(string: option.label, attributes: titleAttributes)
.draw(at: CGPoint(x: x + 82, y: y + 54))
}
}
try savePNG(rep, to: outputURL)
}
let options: [IconOption] = [
.init(fileName: "01_orbital-route.png", label: "Orbital Route", draw: drawOrbitalRoute),
.init(fileName: "02_window-badge.png", label: "Window Badge", draw: drawWindowBadge),
.init(fileName: "03_atlas-tile.png", label: "Atlas Tile", draw: drawAtlasTile),
.init(fileName: "04_night-ring.png", label: "Night Ring", draw: drawNightRing),
.init(fileName: "05_route-card.png", label: "Route Card", draw: drawRouteCard)
]
let fileManager = FileManager.default
let root = URL(fileURLWithPath: fileManager.currentDirectoryPath)
let outputDirectory = root.appendingPathComponent("design/icon-options", isDirectory: true)
try fileManager.createDirectory(at: outputDirectory, withIntermediateDirectories: true)
for option in options {
let rep = renderBitmap(draw: option.draw)
try savePNG(rep, to: outputDirectory.appendingPathComponent(option.fileName))
}
try drawComparisonSheet(
options: options,
imageDirectory: outputDirectory,
outputURL: outputDirectory.appendingPathComponent("comparison-sheet.png")
)
print("Generated \(options.count) icons in \(outputDirectory.path)")

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

View File

@@ -0,0 +1,14 @@
Generated app icon concepts for `Flights`.
Files:
- `01_orbital-route.png`: dark, route-led, strongest "fly everywhere" story.
- `02_window-badge.png`: bright and premium, centered globe with orbital motion.
- `03_atlas-tile.png`: editorial/lightweight, dark inset tile on an airy base.
- `04_night-ring.png`: bold and futuristic, orbit-ring treatment on a dark field.
- `05_route-card.png`: clean iOS card language using the app's UI motif.
- `comparison-sheet.png`: all five options on one board.
Regenerate:
```sh
swift -module-cache-path /tmp/swift-module-cache design/generate_icon_options.swift
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB