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:
Trey t
2026-02-13 12:07:35 -06:00
parent 67965cbac6
commit 5f5b137e64
655 changed files with 4008 additions and 63 deletions

View File

@@ -12,6 +12,7 @@ struct AdaptiveHomeContent: View {
@Binding var showNewTrip: Bool @Binding var showNewTrip: Bool
@Binding var selectedTab: Int @Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip? @Binding var selectedSuggestedTrip: SuggestedTrip?
@Binding var marketingAutoScroll: Bool
let savedTrips: [SavedTrip] let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator let suggestedTripsGenerator: SuggestedTripsGenerator
@@ -24,6 +25,7 @@ struct AdaptiveHomeContent: View {
showNewTrip: $showNewTrip, showNewTrip: $showNewTrip,
selectedTab: $selectedTab, selectedTab: $selectedTab,
selectedSuggestedTrip: $selectedSuggestedTrip, selectedSuggestedTrip: $selectedSuggestedTrip,
marketingAutoScroll: $marketingAutoScroll,
savedTrips: savedTrips, savedTrips: savedTrips,
suggestedTripsGenerator: suggestedTripsGenerator, suggestedTripsGenerator: suggestedTripsGenerator,
displayedTips: displayedTips displayedTips: displayedTips
@@ -34,6 +36,7 @@ struct AdaptiveHomeContent: View {
showNewTrip: $showNewTrip, showNewTrip: $showNewTrip,
selectedTab: $selectedTab, selectedTab: $selectedTab,
selectedSuggestedTrip: $selectedSuggestedTrip, selectedSuggestedTrip: $selectedSuggestedTrip,
marketingAutoScroll: $marketingAutoScroll,
savedTrips: savedTrips, savedTrips: savedTrips,
suggestedTripsGenerator: suggestedTripsGenerator, suggestedTripsGenerator: suggestedTripsGenerator,
displayedTips: displayedTips displayedTips: displayedTips

View File

@@ -18,6 +18,9 @@ struct HomeView: View {
@State private var selectedSuggestedTrip: SuggestedTrip? @State private var selectedSuggestedTrip: SuggestedTrip?
@State private var displayedTips: [PlanningTip] = [] @State private var displayedTips: [PlanningTip] = []
@State private var showProPaywall = false @State private var showProPaywall = false
#if DEBUG
@State private var marketingAutoScroll = false
#endif
var body: some View { var body: some View {
TabView(selection: $selectedTab) { TabView(selection: $selectedTab) {
@@ -27,6 +30,7 @@ struct HomeView: View {
showNewTrip: $showNewTrip, showNewTrip: $showNewTrip,
selectedTab: $selectedTab, selectedTab: $selectedTab,
selectedSuggestedTrip: $selectedSuggestedTrip, selectedSuggestedTrip: $selectedSuggestedTrip,
marketingAutoScroll: marketingAutoScrollBinding,
savedTrips: savedTrips, savedTrips: savedTrips,
suggestedTripsGenerator: suggestedTripsGenerator, suggestedTripsGenerator: suggestedTripsGenerator,
displayedTips: displayedTips displayedTips: displayedTips
@@ -98,6 +102,13 @@ struct HomeView: View {
let oldName = oldTab < tabNames.count ? tabNames[oldTab] : nil let oldName = oldTab < tabNames.count ? tabNames[oldTab] : nil
AnalyticsManager.shared.track(.tabSwitched(tab: newName, previousTab: oldName)) AnalyticsManager.shared.track(.tabSwitched(tab: newName, previousTab: oldName))
AnalyticsManager.shared.trackScreen(newName) AnalyticsManager.shared.trackScreen(newName)
#if DEBUG
if newTab == 0 && UserDefaults.standard.bool(forKey: "marketingVideoMode") {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
marketingAutoScroll = true
}
}
#endif
} }
.sheet(isPresented: $showNewTrip) { .sheet(isPresented: $showNewTrip) {
TripWizardView() TripWizardView()
@@ -131,6 +142,14 @@ struct HomeView: View {
} }
} }
private var marketingAutoScrollBinding: Binding<Bool> {
#if DEBUG
return $marketingAutoScroll
#else
return .constant(false)
#endif
}
// MARK: - Hero Card // MARK: - Hero Card
private var heroCard: some View { private var heroCard: some View {

View File

@@ -15,6 +15,7 @@ struct HomeContent_Classic: View {
@Binding var showNewTrip: Bool @Binding var showNewTrip: Bool
@Binding var selectedTab: Int @Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip? @Binding var selectedSuggestedTrip: SuggestedTrip?
@Binding var marketingAutoScroll: Bool
let savedTrips: [SavedTrip] let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator let suggestedTripsGenerator: SuggestedTripsGenerator
@@ -124,36 +125,50 @@ struct HomeContent_Classic: View {
.padding(.horizontal, Theme.Spacing.md) .padding(.horizontal, Theme.Spacing.md)
// Horizontal carousel grouped by region // Horizontal carousel grouped by region
ScrollView(.horizontal, showsIndicators: false) { ScrollViewReader { proxy in
HStack(spacing: Theme.Spacing.lg) { ScrollView(.horizontal, showsIndicators: false) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in HStack(spacing: Theme.Spacing.lg) {
VStack(alignment: .leading, spacing: Theme.Spacing.sm) { ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
// Region header VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
HStack(spacing: Theme.Spacing.xs) { // Region header
Image(systemName: regionGroup.region.iconName) HStack(spacing: Theme.Spacing.xs) {
.font(.caption) Image(systemName: regionGroup.region.iconName)
.accessibilityHidden(true) .font(.caption)
Text(regionGroup.region.shortName) .accessibilityHidden(true)
.font(.subheadline) Text(regionGroup.region.shortName)
} .font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme)) }
.foregroundStyle(Theme.textSecondary(colorScheme))
// Trip cards for this region // Trip cards for this region
HStack(spacing: Theme.Spacing.md) { HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in ForEach(regionGroup.trips) { suggestedTrip in
Button { Button {
selectedSuggestedTrip = suggestedTrip selectedSuggestedTrip = suggestedTrip
} label: { } label: {
SuggestedTripCard(suggestedTrip: suggestedTrip) SuggestedTripCard(suggestedTrip: suggestedTrip)
}
.buttonStyle(.plain)
} }
.buttonStyle(.plain)
} }
} }
.id(regionGroup.region)
}
}
}
.contentMargins(.horizontal, Theme.Spacing.md, for: .scrollContent)
.onChange(of: marketingAutoScroll) { _, shouldScroll in
if shouldScroll,
let lastRegion = suggestedTripsGenerator.tripsByRegion.last?.region {
withAnimation(.easeInOut(duration: 3.0)) {
proxy.scrollTo(lastRegion, anchor: .trailing)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
marketingAutoScroll = false
} }
} }
} }
} }
.contentMargins(.horizontal, Theme.Spacing.md, for: .scrollContent)
} }
} else if let error = suggestedTripsGenerator.error { } else if let error = suggestedTripsGenerator.error {
// Error state // Error state

View File

@@ -15,6 +15,7 @@ struct HomeContent_ClassicAnimated: View {
@Binding var showNewTrip: Bool @Binding var showNewTrip: Bool
@Binding var selectedTab: Int @Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip? @Binding var selectedSuggestedTrip: SuggestedTrip?
@Binding var marketingAutoScroll: Bool
let savedTrips: [SavedTrip] let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator let suggestedTripsGenerator: SuggestedTripsGenerator
@@ -123,36 +124,50 @@ struct HomeContent_ClassicAnimated: View {
.padding(.horizontal, Theme.Spacing.md) .padding(.horizontal, Theme.Spacing.md)
// Horizontal carousel grouped by region // Horizontal carousel grouped by region
ScrollView(.horizontal, showsIndicators: false) { ScrollViewReader { proxy in
HStack(spacing: Theme.Spacing.lg) { ScrollView(.horizontal, showsIndicators: false) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in HStack(spacing: Theme.Spacing.lg) {
VStack(alignment: .leading, spacing: Theme.Spacing.sm) { ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
// Region header VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
HStack(spacing: Theme.Spacing.xs) { // Region header
Image(systemName: regionGroup.region.iconName) HStack(spacing: Theme.Spacing.xs) {
.font(.caption) Image(systemName: regionGroup.region.iconName)
.accessibilityHidden(true) .font(.caption)
Text(regionGroup.region.shortName) .accessibilityHidden(true)
.font(.subheadline) Text(regionGroup.region.shortName)
} .font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme)) }
.foregroundStyle(Theme.textSecondary(colorScheme))
// Trip cards for this region // Trip cards for this region
HStack(spacing: Theme.Spacing.md) { HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in ForEach(regionGroup.trips) { suggestedTrip in
Button { Button {
selectedSuggestedTrip = suggestedTrip selectedSuggestedTrip = suggestedTrip
} label: { } label: {
SuggestedTripCard(suggestedTrip: suggestedTrip) SuggestedTripCard(suggestedTrip: suggestedTrip)
}
.buttonStyle(.plain)
} }
.buttonStyle(.plain)
} }
} }
.id(regionGroup.region)
}
}
}
.contentMargins(.horizontal, Theme.Spacing.md, for: .scrollContent)
.onChange(of: marketingAutoScroll) { _, shouldScroll in
if shouldScroll,
let lastRegion = suggestedTripsGenerator.tripsByRegion.last?.region {
withAnimation(.easeInOut(duration: 3.0)) {
proxy.scrollTo(lastRegion, anchor: .trailing)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
marketingAutoScroll = false
} }
} }
} }
} }
.contentMargins(.horizontal, Theme.Spacing.md, for: .scrollContent)
} }
} else if let error = suggestedTripsGenerator.error { } else if let error = suggestedTripsGenerator.error {
// Error state // Error state

View File

@@ -519,6 +519,13 @@ struct SettingsView: View {
Label("Override Pro Status", systemImage: "star.fill") Label("Override Pro Status", systemImage: "star.fill")
} }
Toggle(isOn: Binding(
get: { UserDefaults.standard.bool(forKey: "marketingVideoMode") },
set: { UserDefaults.standard.set($0, forKey: "marketingVideoMode") }
)) {
Label("Marketing Video Mode", systemImage: "video.fill")
}
Button { Button {
showOnboardingPaywall = true showOnboardingPaywall = true
} label: { } label: {

View File

@@ -511,6 +511,45 @@ final class ItineraryTableViewController: UITableViewController {
ItineraryReorderingLogic.travelRow(in: flatItems, forDay: day) ItineraryReorderingLogic.travelRow(in: flatItems, forDay: day)
} }
// MARK: - Marketing Video Auto-Scroll
#if DEBUG
private var displayLink: CADisplayLink?
private var scrollStartTime: CFTimeInterval = 0
private var scrollDuration: CFTimeInterval = 6.0
private var scrollStartOffset: CGFloat = 0
private var scrollEndOffset: CGFloat = 0
func scrollToBottomAnimated(delay: TimeInterval = 1.5, duration: TimeInterval = 6.0) {
guard UserDefaults.standard.bool(forKey: "marketingVideoMode") else { return }
self.scrollDuration = duration
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let self else { return }
let maxOffset = self.tableView.contentSize.height - self.tableView.bounds.height + self.tableView.contentInset.bottom
guard maxOffset > 0 else { return }
self.scrollStartOffset = self.tableView.contentOffset.y
self.scrollEndOffset = maxOffset
self.scrollStartTime = CACurrentMediaTime()
let link = CADisplayLink(target: self, selector: #selector(self.marketingScrollTick))
link.add(to: .main, forMode: .common)
self.displayLink = link
}
}
@objc private func marketingScrollTick() {
let elapsed = CACurrentMediaTime() - scrollStartTime
let t = min(elapsed / scrollDuration, 1.0)
// Ease-in-out curve
let eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
let newOffset = scrollStartOffset + (scrollEndOffset - scrollStartOffset) * eased
tableView.contentOffset = CGPoint(x: 0, y: newOffset)
if t >= 1.0 {
displayLink?.invalidate()
displayLink = nil
}
}
#endif
// MARK: - Drag State Management // MARK: - Drag State Management
// //
// These methods handle the start, update, and end of drag operations, // These methods handle the start, update, and end of drag operations,

View File

@@ -90,6 +90,10 @@ struct ItineraryTableViewWrapper<HeaderContent: View>: UIViewControllerRepresent
travelSegmentIndices: travelSegmentIndices travelSegmentIndices: travelSegmentIndices
) )
#if DEBUG
controller.scrollToBottomAnimated(delay: 5.0)
#endif
return controller return controller
} }

View File

@@ -254,32 +254,49 @@ struct TripDetailView: View {
.ignoresSafeArea(edges: .bottom) .ignoresSafeArea(edges: .bottom)
} else { } else {
// Non-editable scroll view for unsaved trips // Non-editable scroll view for unsaved trips
ScrollView { ScrollViewReader { proxy in
VStack(spacing: 0) { ScrollView {
heroMapSection VStack(spacing: 0) {
.frame(height: 280) heroMapSection
.frame(height: 280)
VStack(spacing: Theme.Spacing.lg) { VStack(spacing: Theme.Spacing.lg) {
tripHeader tripHeader
.padding(.top, Theme.Spacing.lg) .padding(.top, Theme.Spacing.lg)
statsRow statsRow
if let score = trip.score { if let score = trip.score {
scoreCard(score) scoreCard(score)
}
itinerarySection
} }
.padding(.horizontal, Theme.Spacing.lg)
.padding(.bottom, Theme.Spacing.xxl)
itinerarySection Color.clear
.frame(height: 1)
.id("tripDetailBottom")
}
.onDrop(of: [.text, .plainText, .utf8PlainText], isTargeted: .constant(false)) { _ in
draggedTravelId = nil
draggedItem = nil
dropTargetId = nil
return true
} }
.padding(.horizontal, Theme.Spacing.lg)
.padding(.bottom, Theme.Spacing.xxl)
} }
.onDrop(of: [.text, .plainText, .utf8PlainText], isTargeted: .constant(false)) { _ in #if DEBUG
draggedTravelId = nil .onAppear {
draggedItem = nil if UserDefaults.standard.bool(forKey: "marketingVideoMode") {
dropTargetId = nil DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
return true withAnimation(.easeInOut(duration: 6.0)) {
proxy.scrollTo("tripDetailBottom", anchor: .bottom)
}
}
}
} }
#endif
} }
} }
} }

View File

@@ -231,8 +231,9 @@ struct TripOptionsView: View {
} }
var body: some View { var body: some View {
ScrollViewReader { proxy in
ScrollView { ScrollView {
LazyVStack(spacing: 16) { VStack(spacing: 16) {
// Hero header // Hero header
VStack(spacing: 8) { VStack(spacing: 8) {
Image(systemName: "point.topright.arrow.triangle.backward.to.point.bottomleft.scurvepath.fill") Image(systemName: "point.topright.arrow.triangle.backward.to.point.bottomleft.scurvepath.fill")
@@ -291,8 +292,27 @@ struct TripOptionsView: View {
} }
} }
.padding(.bottom, Theme.Spacing.xxl) .padding(.bottom, Theme.Spacing.xxl)
Color.clear.frame(height: 1).id("tripOptionsBottom")
} }
.themedBackground() .themedBackground()
#if DEBUG
.onAppear {
if UserDefaults.standard.bool(forKey: "marketingVideoMode") && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
Theme.Animation.withMotion(.easeInOut(duration: 0.3)) {
sortOption = .mostGames
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
withAnimation(.easeInOut(duration: 20.0)) {
proxy.scrollTo("tripOptionsBottom", anchor: .bottom)
}
}
}
}
#endif
} // ScrollViewReader
.navigationDestination(isPresented: $showTripDetail) { .navigationDestination(isPresented: $showTripDetail) {
if let trip = selectedTrip { if let trip = selectedTrip {
TripDetailView(trip: trip) TripDetailView(trip: trip)

View File

@@ -28,6 +28,7 @@ struct TripWizardView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
GeometryReader { geometry in GeometryReader { geometry in
ScrollViewReader { proxy in
ScrollView(.vertical) { ScrollView(.vertical) {
VStack(spacing: Theme.Spacing.lg) { VStack(spacing: Theme.Spacing.lg) {
// Step 1: Planning Mode (always visible) // Step 1: Planning Mode (always visible)
@@ -130,11 +131,23 @@ struct TripWizardView: View {
} }
.transition(.opacity) .transition(.opacity)
} }
Color.clear
.frame(height: 1)
.id("wizardBottom")
} }
.padding(Theme.Spacing.md) .padding(Theme.Spacing.md)
.frame(width: geometry.size.width) .frame(width: geometry.size.width)
.animation(Theme.Animation.prefersReducedMotion ? .none : .easeInOut(duration: 0.2), value: viewModel.areStepsVisible) .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() .themedBackground()
.navigationTitle("Plan a Trip") .navigationTitle("Plan a Trip")
@@ -335,6 +348,72 @@ struct TripWizardView: View {
} }
return cities.joined(separator: "") 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 // MARK: - Preview

4
marketing-videos/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
dist/
out/
.DS_Store

2838
marketing-videos/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"name": "sportstime-marketing-videos",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "npx remotion studio",
"render": "npx remotion render app-store-preview out/AppStorePreview.mp4",
"build": "npx remotion render app-store-preview out/AppStorePreview.mp4"
},
"dependencies": {
"@remotion/cli": "^4.0.421",
"@remotion/transitions": "^4.0.421",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"remotion": "^4.0.421"
},
"devDependencies": {
"@types/react": "^18.3.3",
"typescript": "^5.5.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More