feat: add marketing video mode and Remotion marketing video project
Add debug-only Marketing Video Mode toggle that enables hands-free screen recording across the app: auto-scrolling Featured Trips carousel, auto-filling trip wizard, smooth trip detail scrolling via CADisplayLink, and trip options auto-sort with scroll. Add Remotion marketing video project with 6 scene compositions using image sequences extracted from screen recordings, varied phone entrance animations, and deduped frames for smooth playback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ struct TripWizardView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
GeometryReader { geometry in
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView(.vertical) {
|
||||
VStack(spacing: Theme.Spacing.lg) {
|
||||
// Step 1: Planning Mode (always visible)
|
||||
@@ -130,11 +131,23 @@ struct TripWizardView: View {
|
||||
}
|
||||
.transition(.opacity)
|
||||
}
|
||||
|
||||
Color.clear
|
||||
.frame(height: 1)
|
||||
.id("wizardBottom")
|
||||
}
|
||||
.padding(Theme.Spacing.md)
|
||||
.frame(width: geometry.size.width)
|
||||
.animation(Theme.Animation.prefersReducedMotion ? .none : .easeInOut(duration: 0.2), value: viewModel.areStepsVisible)
|
||||
}
|
||||
#if DEBUG
|
||||
.onChange(of: viewModel.planningMode) { _, newMode in
|
||||
if newMode == .gameFirst && UserDefaults.standard.bool(forKey: "marketingVideoMode") {
|
||||
marketingAutoFill(proxy: proxy)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.themedBackground()
|
||||
.navigationTitle("Plan a Trip")
|
||||
@@ -335,6 +348,72 @@ struct TripWizardView: View {
|
||||
}
|
||||
return cities.joined(separator: " → ")
|
||||
}
|
||||
|
||||
// MARK: - Marketing Video Auto-Fill
|
||||
|
||||
#if DEBUG
|
||||
private func marketingAutoFill(proxy: ScrollViewProxy) {
|
||||
// Pre-fetch data off the main thread, then run a clean sequential fill
|
||||
Task {
|
||||
let astros = AppDataProvider.shared.teams.first { $0.fullName.contains("Astros") }
|
||||
let allGames = try? await AppDataProvider.shared.allGames(for: [.mlb])
|
||||
let astrosGames = (allGames ?? []).filter {
|
||||
$0.homeTeamId == astros?.id || $0.awayTeamId == astros?.id
|
||||
}
|
||||
let pickedGames = Array(astrosGames.shuffled().prefix(3))
|
||||
let pickedIds = Set(pickedGames.map { $0.id })
|
||||
|
||||
// Sequential fill with generous pauses — no competing animations
|
||||
await MainActor.run {
|
||||
// Step 1: Select MLB
|
||||
viewModel.gamePickerSports = [.mlb]
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(1.5))
|
||||
|
||||
await MainActor.run {
|
||||
// Step 2: Select Astros
|
||||
if let astros {
|
||||
viewModel.gamePickerTeamIds = [astros.id]
|
||||
}
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(1.5))
|
||||
|
||||
await MainActor.run {
|
||||
// Step 3: Pick games + set date range
|
||||
if !pickedIds.isEmpty {
|
||||
viewModel.selectedGameIds = pickedIds
|
||||
if let earliest = pickedGames.map({ $0.dateTime }).min(),
|
||||
let latest = pickedGames.map({ $0.dateTime }).max() {
|
||||
viewModel.startDate = Calendar.current.date(byAdding: .day, value: -1, to: earliest) ?? earliest
|
||||
viewModel.endDate = Calendar.current.date(byAdding: .day, value: 1, to: latest) ?? latest
|
||||
}
|
||||
}
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(1.5))
|
||||
|
||||
await MainActor.run {
|
||||
// Step 4: Balanced route
|
||||
viewModel.routePreference = .balanced
|
||||
viewModel.hasSetRoutePreference = true
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(1.5))
|
||||
|
||||
await MainActor.run {
|
||||
// Step 5: Allow repeat cities
|
||||
viewModel.allowRepeatCities = true
|
||||
viewModel.hasSetRepeatCities = true
|
||||
}
|
||||
try? await Task.sleep(for: .seconds(0.5))
|
||||
|
||||
// Single smooth scroll to bottom after everything is laid out
|
||||
await MainActor.run {
|
||||
withAnimation(.easeInOut(duration: 5.0)) {
|
||||
proxy.scrollTo("wizardBottom", anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
Reference in New Issue
Block a user