Files
Flights/Flights/Views/Styles/HistoryStyle.swift
T
Trey T 86582cea4a History tab: passport redesign
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>
2026-05-29 11:13:24 -05:00

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)
}
}