86582cea4a
Replaces the History tab end-to-end with a passport-styled experience modeled on Flighty's Passport but with its own identity: - New HistoryStyle palette: runway orange (#FF5722) + midnight navy + warm cream paper. Adaptive light/dark surfaces, mono-digit display numbers, card chrome modifier. Scoped to History so the rest of the app's FlightTheme stays untouched. - New PassportComponents library: HeroStatCard (orange / navy / gold / green / photo variants), YearTabStrip, OCRPassportFooter (the OCR passport-bottom flex text), StatColumn, HistorySectionLabel. Screens rewritten: - HistoryView — ScrollView feed with title header, year tab strip, stacked hero cards (this-year passport, most-flown aircraft, quick links to map/aircraft/year-in-review), and passport-styled flight rows in cards. Search, sort, filter, and add affordances live in the toolbar. - PassportView (was LifetimeStatsView) — stacked colored hero cards for flights, distance, time aloft, top route, top airline, longest flight, plus repeated-airframes list. Year tabs at top scope everything. OCR-passport flex footer at the bottom. - AircraftStatsView (new) — Total / Newest / Oldest header tiles, ranked list of types with the airframe photo as the row background, "Repeat Offender" hero card with the most-flown tail's photo full-bleed. - HistoryRouteMapView — satellite map style (.imagery), brighter arcs in runway orange with the most-recent leg in fluorescent yellow, persistent bottom navy drawer showing the passport summary + active filter chips + replay button. - YearInReviewView — horizontal TabView paged card deck, each card a full-bleed hero composition optimized for screenshot share. Cover card with year number set in 140pt monospaced bold. - HistoryDetailView — restyled with passport palette. Aircraft card uses a labeled grid (Type/Tail #/First Flight/Age/Repeats/ICAO24) with em-dashes for missing data. New Detailed Timetable card with Scheduled vs Actual columns, late times in red. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
128 lines
4.8 KiB
Swift
128 lines
4.8 KiB
Swift
import SwiftUI
|
|
|
|
/// Design system scoped to the History tab. The rest of the app uses
|
|
/// `FlightTheme`; the redesigned passport-style history uses its own
|
|
/// warm aerospace palette so the visual identity doesn't bleed into
|
|
/// Search or Live.
|
|
///
|
|
/// Palette commitment:
|
|
/// - Runway orange as identity color (vivid, hi-vis, aviation-coded)
|
|
/// - Midnight navy as the dark surface
|
|
/// - Warm cream paper as the light surface (passport-stock feel)
|
|
/// - Stamp green + foil gold as accents on dressed elements
|
|
enum HistoryStyle {
|
|
|
|
// MARK: - Palette
|
|
|
|
static let runwayOrange = Color(red: 1.00, green: 0.34, blue: 0.13) // #FF5722
|
|
static let runwayOrangeDeep = Color(red: 0.85, green: 0.27, blue: 0.07) // #D9461A
|
|
static let runwayOrangeSoft = Color(red: 1.00, green: 0.55, blue: 0.35) // #FF8C59
|
|
|
|
static let midnightNavy = Color(red: 0.04, green: 0.08, blue: 0.14) // #0A1424
|
|
static let inkNavy = Color(red: 0.08, green: 0.14, blue: 0.25) // #142440
|
|
static let nightSky = Color(red: 0.06, green: 0.12, blue: 0.22) // #0F1E38
|
|
|
|
static let creamPaper = Color(red: 0.96, green: 0.93, blue: 0.85) // #F4ECD8
|
|
static let creamPaperDeep = Color(red: 0.91, green: 0.87, blue: 0.77) // #E8DDC4
|
|
static let creamPaperSoft = Color(red: 0.98, green: 0.96, blue: 0.91) // #FAF5E8
|
|
|
|
static let stampGreen = Color(red: 0.18, green: 0.35, blue: 0.24) // #2D5A3D
|
|
static let goldFoil = Color(red: 0.78, green: 0.66, blue: 0.32) // #C8A951
|
|
|
|
// MARK: - Adaptive surfaces (dark/light aware)
|
|
|
|
/// Top-level page background.
|
|
static func background(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? midnightNavy : creamPaper
|
|
}
|
|
|
|
/// Standard card / panel.
|
|
static func card(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? inkNavy : creamPaperSoft
|
|
}
|
|
|
|
/// Secondary card (less prominence).
|
|
static func cardSubtle(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? nightSky : creamPaperDeep
|
|
}
|
|
|
|
/// Primary text color on the background.
|
|
static func ink(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? Color(red: 0.96, green: 0.93, blue: 0.85) : Color(red: 0.06, green: 0.10, blue: 0.18)
|
|
}
|
|
|
|
static func inkSecondary(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? Color.white.opacity(0.65) : Color.black.opacity(0.55)
|
|
}
|
|
|
|
static func inkTertiary(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? Color.white.opacity(0.4) : Color.black.opacity(0.35)
|
|
}
|
|
|
|
static func hairline(_ scheme: ColorScheme) -> Color {
|
|
scheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.08)
|
|
}
|
|
|
|
// MARK: - Hero card gradients
|
|
|
|
static let heroOrangeGradient = LinearGradient(
|
|
colors: [runwayOrange, runwayOrangeDeep],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing
|
|
)
|
|
|
|
static let heroNavyGradient = LinearGradient(
|
|
colors: [inkNavy, midnightNavy],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing
|
|
)
|
|
|
|
static let heroGoldGradient = LinearGradient(
|
|
colors: [goldFoil, Color(red: 0.55, green: 0.46, blue: 0.20)],
|
|
startPoint: .top, endPoint: .bottom
|
|
)
|
|
|
|
static let heroGreenGradient = LinearGradient(
|
|
colors: [stampGreen, Color(red: 0.10, green: 0.22, blue: 0.15)],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing
|
|
)
|
|
|
|
// MARK: - Typography
|
|
|
|
/// Display weight used for hero numbers like "47,200 mi".
|
|
static func displayNumber(_ size: CGFloat) -> Font {
|
|
.system(size: size, weight: .heavy, design: .default)
|
|
.monospacedDigit()
|
|
}
|
|
|
|
static func label(_ size: CGFloat = 11) -> Font {
|
|
.system(size: size, weight: .semibold, design: .default)
|
|
}
|
|
|
|
/// OCR-passport flavor text font — monospaced, slightly condensed feel.
|
|
static let ocrFont: Font = .system(size: 11, weight: .regular, design: .monospaced)
|
|
|
|
static let cardTitleFont: Font = .system(size: 13, weight: .semibold, design: .default)
|
|
}
|
|
|
|
// MARK: - View modifiers
|
|
|
|
extension View {
|
|
/// Bevel-style card chrome used across history surfaces.
|
|
func historyCard(_ scheme: ColorScheme, padding: CGFloat = 16, cornerRadius: CGFloat = 18) -> some View {
|
|
self
|
|
.padding(padding)
|
|
.background(HistoryStyle.card(scheme), in: RoundedRectangle(cornerRadius: cornerRadius))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: cornerRadius)
|
|
.stroke(HistoryStyle.hairline(scheme), lineWidth: 0.5)
|
|
)
|
|
}
|
|
|
|
/// Tracking + uppercase wrapping for section labels and "FLIGHTS" etc.
|
|
func historyLabel() -> some View {
|
|
self
|
|
.font(HistoryStyle.label())
|
|
.tracking(1.2)
|
|
.textCase(.uppercase)
|
|
}
|
|
}
|