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

View File

@@ -18,6 +18,9 @@ struct HomeView: View {
@State private var selectedSuggestedTrip: SuggestedTrip?
@State private var displayedTips: [PlanningTip] = []
@State private var showProPaywall = false
#if DEBUG
@State private var marketingAutoScroll = false
#endif
var body: some View {
TabView(selection: $selectedTab) {
@@ -27,6 +30,7 @@ struct HomeView: View {
showNewTrip: $showNewTrip,
selectedTab: $selectedTab,
selectedSuggestedTrip: $selectedSuggestedTrip,
marketingAutoScroll: marketingAutoScrollBinding,
savedTrips: savedTrips,
suggestedTripsGenerator: suggestedTripsGenerator,
displayedTips: displayedTips
@@ -98,6 +102,13 @@ struct HomeView: View {
let oldName = oldTab < tabNames.count ? tabNames[oldTab] : nil
AnalyticsManager.shared.track(.tabSwitched(tab: newName, previousTab: oldName))
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) {
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
private var heroCard: some View {

View File

@@ -15,6 +15,7 @@ struct HomeContent_Classic: View {
@Binding var showNewTrip: Bool
@Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip?
@Binding var marketingAutoScroll: Bool
let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator
@@ -124,36 +125,50 @@ struct HomeContent_Classic: View {
.padding(.horizontal, Theme.Spacing.md)
// Horizontal carousel grouped by region
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: Theme.Spacing.lg) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
// Region header
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: regionGroup.region.iconName)
.font(.caption)
.accessibilityHidden(true)
Text(regionGroup.region.shortName)
.font(.subheadline)
}
.foregroundStyle(Theme.textSecondary(colorScheme))
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: Theme.Spacing.lg) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
// Region header
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: regionGroup.region.iconName)
.font(.caption)
.accessibilityHidden(true)
Text(regionGroup.region.shortName)
.font(.subheadline)
}
.foregroundStyle(Theme.textSecondary(colorScheme))
// Trip cards for this region
HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
SuggestedTripCard(suggestedTrip: suggestedTrip)
// Trip cards for this region
HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
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 {
// Error state

View File

@@ -15,6 +15,7 @@ struct HomeContent_ClassicAnimated: View {
@Binding var showNewTrip: Bool
@Binding var selectedTab: Int
@Binding var selectedSuggestedTrip: SuggestedTrip?
@Binding var marketingAutoScroll: Bool
let savedTrips: [SavedTrip]
let suggestedTripsGenerator: SuggestedTripsGenerator
@@ -123,36 +124,50 @@ struct HomeContent_ClassicAnimated: View {
.padding(.horizontal, Theme.Spacing.md)
// Horizontal carousel grouped by region
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: Theme.Spacing.lg) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
// Region header
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: regionGroup.region.iconName)
.font(.caption)
.accessibilityHidden(true)
Text(regionGroup.region.shortName)
.font(.subheadline)
}
.foregroundStyle(Theme.textSecondary(colorScheme))
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: Theme.Spacing.lg) {
ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
// Region header
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: regionGroup.region.iconName)
.font(.caption)
.accessibilityHidden(true)
Text(regionGroup.region.shortName)
.font(.subheadline)
}
.foregroundStyle(Theme.textSecondary(colorScheme))
// Trip cards for this region
HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
SuggestedTripCard(suggestedTrip: suggestedTrip)
// Trip cards for this region
HStack(spacing: Theme.Spacing.md) {
ForEach(regionGroup.trips) { suggestedTrip in
Button {
selectedSuggestedTrip = suggestedTrip
} label: {
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 {
// Error state

View File

@@ -519,6 +519,13 @@ struct SettingsView: View {
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 {
showOnboardingPaywall = true
} label: {

View File

@@ -511,6 +511,45 @@ final class ItineraryTableViewController: UITableViewController {
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
//
// These methods handle the start, update, and end of drag operations,

View File

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

View File

@@ -254,32 +254,49 @@ struct TripDetailView: View {
.ignoresSafeArea(edges: .bottom)
} else {
// Non-editable scroll view for unsaved trips
ScrollView {
VStack(spacing: 0) {
heroMapSection
.frame(height: 280)
ScrollViewReader { proxy in
ScrollView {
VStack(spacing: 0) {
heroMapSection
.frame(height: 280)
VStack(spacing: Theme.Spacing.lg) {
tripHeader
.padding(.top, Theme.Spacing.lg)
VStack(spacing: Theme.Spacing.lg) {
tripHeader
.padding(.top, Theme.Spacing.lg)
statsRow
statsRow
if let score = trip.score {
scoreCard(score)
if let score = trip.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
draggedTravelId = nil
draggedItem = nil
dropTargetId = nil
return true
#if DEBUG
.onAppear {
if UserDefaults.standard.bool(forKey: "marketingVideoMode") {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
withAnimation(.easeInOut(duration: 6.0)) {
proxy.scrollTo("tripDetailBottom", anchor: .bottom)
}
}
}
}
#endif
}
}
}

View File

@@ -231,8 +231,9 @@ struct TripOptionsView: View {
}
var body: some View {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 16) {
VStack(spacing: 16) {
// Hero header
VStack(spacing: 8) {
Image(systemName: "point.topright.arrow.triangle.backward.to.point.bottomleft.scurvepath.fill")
@@ -291,8 +292,27 @@ struct TripOptionsView: View {
}
}
.padding(.bottom, Theme.Spacing.xxl)
Color.clear.frame(height: 1).id("tripOptionsBottom")
}
.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) {
if let trip = selectedTrip {
TripDetailView(trip: trip)

View File

@@ -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

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