Replace video components with image sequences for smoother playback
- Export month.mov and year.mov to PNG image sequences - Replace Video components with Img sequences in scenes 2 and 3 - Add previous frame layering to prevent flashing during transitions - Update CTAScene: 2x sizing, white background, new text colors - Add LiveActivityPreviewView for exporting 365 activity frames - Enlarge background icons to 1.2x across all scenes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
416
Shared/Views/SettingsView/LiveActivityPreviewView.swift
Normal file
@@ -0,0 +1,416 @@
|
||||
//
|
||||
// LiveActivityPreviewView.swift
|
||||
// Feels
|
||||
//
|
||||
// Preview Live Activity with animated streak counter for promotional videos.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct LiveActivityPreviewView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var currentStreak: Int = 0
|
||||
@State private var isAnimating: Bool = false
|
||||
@State private var animationTimer: Timer?
|
||||
@State private var showRecordingMode: Bool = false
|
||||
|
||||
private let targetStreak = 365
|
||||
private let animationDuration: TimeInterval = 8.0 // Total animation duration in seconds
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 40) {
|
||||
Text("Live Activity Preview")
|
||||
.font(.title2.bold())
|
||||
|
||||
// Mock Live Activity Lock Screen View
|
||||
liveActivityView
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(.systemBackground).opacity(0.9))
|
||||
.cornerRadius(20)
|
||||
.shadow(color: .black.opacity(0.2), radius: 10, x: 0, y: 4)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
// Progress bar showing 0 to 365
|
||||
VStack(spacing: 12) {
|
||||
ProgressView(value: Double(currentStreak), total: Double(targetStreak))
|
||||
.tint(.orange)
|
||||
.scaleEffect(x: 1, y: 2, anchor: .center)
|
||||
|
||||
Text("\(currentStreak) / \(targetStreak) days")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 40)
|
||||
|
||||
Spacer()
|
||||
|
||||
// Control buttons
|
||||
VStack(spacing: 16) {
|
||||
HStack(spacing: 20) {
|
||||
Button(action: resetAnimation) {
|
||||
Label("Reset", systemImage: "arrow.counterclockwise")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.gray.opacity(0.2))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
|
||||
Button(action: toggleAnimation) {
|
||||
Label(isAnimating ? "Pause" : "Start", systemImage: isAnimating ? "pause.fill" : "play.fill")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.orange)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
|
||||
Button(action: { showRecordingMode = true }) {
|
||||
Label("Recording Mode", systemImage: "record.circle")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.red)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
.padding(.top, 40)
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.onDisappear {
|
||||
animationTimer?.invalidate()
|
||||
}
|
||||
.fullScreenCover(isPresented: $showRecordingMode) {
|
||||
LiveActivityRecordingView()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Live Activity View (mimics lock screen appearance)
|
||||
|
||||
private var liveActivityView: some View {
|
||||
HStack(spacing: 16) {
|
||||
// Streak indicator
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: "flame.fill")
|
||||
.font(.title)
|
||||
.foregroundColor(.orange)
|
||||
.symbolEffect(.bounce, value: currentStreak)
|
||||
Text("\(currentStreak)")
|
||||
.font(.title.bold())
|
||||
.contentTransition(.numericText())
|
||||
Text("day streak")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.frame(height: 50)
|
||||
|
||||
// Status
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if currentStreak > 0 {
|
||||
HStack(spacing: 8) {
|
||||
Circle()
|
||||
.fill(moodColor)
|
||||
.frame(width: 24, height: 24)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Today's mood")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(moodName)
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Start your streak!")
|
||||
.font(.headline)
|
||||
Text("Tap to log your mood")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
// MARK: - Mood Display Helpers
|
||||
|
||||
private var moodColor: Color {
|
||||
let progress = Double(currentStreak) / Double(targetStreak)
|
||||
// Transition through mood colors based on progress
|
||||
if progress < 0.2 {
|
||||
return Color(hex: "F44336") // Horrible
|
||||
} else if progress < 0.4 {
|
||||
return Color(hex: "FF9800") // Bad
|
||||
} else if progress < 0.6 {
|
||||
return Color(hex: "FFC107") // Average
|
||||
} else if progress < 0.8 {
|
||||
return Color(hex: "8BC34A") // Good
|
||||
} else {
|
||||
return Color(hex: "4CAF50") // Great
|
||||
}
|
||||
}
|
||||
|
||||
private var moodName: String {
|
||||
let progress = Double(currentStreak) / Double(targetStreak)
|
||||
if progress < 0.2 {
|
||||
return "Horrible"
|
||||
} else if progress < 0.4 {
|
||||
return "Bad"
|
||||
} else if progress < 0.6 {
|
||||
return "Average"
|
||||
} else if progress < 0.8 {
|
||||
return "Good"
|
||||
} else {
|
||||
return "Great"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation Controls
|
||||
|
||||
private func toggleAnimation() {
|
||||
if isAnimating {
|
||||
pauseAnimation()
|
||||
} else {
|
||||
startAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
private func startAnimation() {
|
||||
guard currentStreak < targetStreak else {
|
||||
resetAnimation()
|
||||
return
|
||||
}
|
||||
|
||||
isAnimating = true
|
||||
|
||||
// Calculate interval to reach target in animationDuration seconds
|
||||
let remainingDays = targetStreak - currentStreak
|
||||
let interval = animationDuration / Double(remainingDays)
|
||||
|
||||
animationTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
|
||||
withAnimation(.easeOut(duration: 0.1)) {
|
||||
currentStreak += 1
|
||||
}
|
||||
|
||||
if currentStreak >= targetStreak {
|
||||
timer.invalidate()
|
||||
isAnimating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func pauseAnimation() {
|
||||
animationTimer?.invalidate()
|
||||
animationTimer = nil
|
||||
isAnimating = false
|
||||
}
|
||||
|
||||
private func resetAnimation() {
|
||||
pauseAnimation()
|
||||
withAnimation {
|
||||
currentStreak = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Recording Mode View (Exports 365 PNG frames)
|
||||
|
||||
struct LiveActivityRecordingView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var isExporting: Bool = false
|
||||
@State private var exportProgress: Int = 0
|
||||
@State private var exportPath: String = ""
|
||||
@State private var exportComplete: Bool = false
|
||||
|
||||
private let targetStreak = 365
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(hex: "111111")
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 20) {
|
||||
if exportComplete {
|
||||
Text("Export Complete!")
|
||||
.font(.title2.bold())
|
||||
.foregroundColor(.green)
|
||||
|
||||
Text("365 frames exported to:")
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text(exportPath)
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
|
||||
Button("Dismiss") {
|
||||
dismiss()
|
||||
}
|
||||
.padding()
|
||||
.background(Color.orange)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
} else if isExporting {
|
||||
Text("Exporting frames...")
|
||||
.font(.title2.bold())
|
||||
.foregroundColor(.white)
|
||||
|
||||
ProgressView(value: Double(exportProgress), total: Double(targetStreak))
|
||||
.tint(.orange)
|
||||
.padding(.horizontal, 40)
|
||||
|
||||
Text("\(exportProgress) / \(targetStreak)")
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
} else {
|
||||
Text("Tap anywhere to start export")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if !isExporting && !exportComplete {
|
||||
startExport()
|
||||
}
|
||||
}
|
||||
.statusBarHidden(true)
|
||||
}
|
||||
|
||||
private func getMoodForStreak(_ streak: Int) -> (name: String, color: Color) {
|
||||
let moods: [(String, Color)] = [
|
||||
("Average", Color(hex: "FFC107")),
|
||||
("Good", Color(hex: "8BC34A")),
|
||||
("Great", Color(hex: "4CAF50"))
|
||||
]
|
||||
|
||||
// Must end on Great
|
||||
if streak >= 355 {
|
||||
return moods[2] // Great
|
||||
}
|
||||
|
||||
// Change mood every 10 frames, pseudo-random based on streak
|
||||
let segment = streak / 10
|
||||
let index = (segment * 7 + 3) % 3 // Pseudo-random pattern
|
||||
return moods[index]
|
||||
}
|
||||
|
||||
private func startExport() {
|
||||
isExporting = true
|
||||
|
||||
// Create output directory
|
||||
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
let outputDir = documentsPath.appendingPathComponent("LiveActivityFrames")
|
||||
|
||||
try? FileManager.default.removeItem(at: outputDir)
|
||||
try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)
|
||||
|
||||
exportPath = outputDir.path
|
||||
print("📁 Exporting frames to: \(exportPath)")
|
||||
|
||||
// Export frames on background queue
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
for streak in 1...targetStreak {
|
||||
let mood = getMoodForStreak(streak)
|
||||
|
||||
let cardView = LiveActivityCardView(
|
||||
streak: streak,
|
||||
moodName: mood.name,
|
||||
moodColor: mood.color
|
||||
)
|
||||
|
||||
let renderer = ImageRenderer(content: cardView)
|
||||
renderer.scale = 3.0 // @3x for high resolution
|
||||
|
||||
if let uiImage = renderer.uiImage {
|
||||
let filename = String(format: "frame_%04d.png", streak)
|
||||
let fileURL = outputDir.appendingPathComponent(filename)
|
||||
|
||||
if let pngData = uiImage.pngData() {
|
||||
try? pngData.write(to: fileURL)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportProgress = streak
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportComplete = true
|
||||
print("✅ Export complete! \(targetStreak) frames saved to: \(exportPath)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Standalone Card View for Rendering
|
||||
|
||||
struct LiveActivityCardView: View {
|
||||
let streak: Int
|
||||
let moodName: String
|
||||
let moodColor: Color
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 16) {
|
||||
// Streak indicator
|
||||
VStack(spacing: 4) {
|
||||
Image(systemName: "flame.fill")
|
||||
.font(.title)
|
||||
.foregroundColor(.orange)
|
||||
|
||||
Text("\(streak)")
|
||||
.font(.title.bold())
|
||||
|
||||
Text("day streak")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.frame(height: 50)
|
||||
|
||||
// Status
|
||||
HStack(spacing: 8) {
|
||||
Circle()
|
||||
.fill(moodColor)
|
||||
.frame(width: 24, height: 24)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Today's mood")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text(moodName)
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.background(Color(hex: "2C2C2E"))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
.environment(\.colorScheme, .dark)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LiveActivityPreviewView()
|
||||
}
|
||||
|
||||
#Preview("Recording Mode") {
|
||||
LiveActivityRecordingView()
|
||||
}
|
||||
BIN
feels-promo/public/activity/base.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
feels-promo/public/activity/frame_0001.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0002.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0003.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0004.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0005.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0006.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0007.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0008.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0009.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0010.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0011.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
feels-promo/public/activity/frame_0012.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0013.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0014.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0015.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0016.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0017.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0018.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0019.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0020.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0021.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0022.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0023.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0024.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0025.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0026.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0027.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0028.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0029.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0030.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0031.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0032.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0033.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0034.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0035.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0036.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0037.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0038.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0039.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0040.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0041.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0042.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0043.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0044.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
feels-promo/public/activity/frame_0045.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0046.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0047.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0048.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0049.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0050.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0051.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0052.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0053.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0054.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0055.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0056.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0057.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0058.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0059.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0060.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0061.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0062.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0063.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0064.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0065.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0066.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0067.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0068.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0069.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0070.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0071.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0072.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0073.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0074.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0075.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0076.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0077.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
feels-promo/public/activity/frame_0078.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0079.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0080.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0081.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0082.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0083.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0084.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0085.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0086.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0087.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0088.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
feels-promo/public/activity/frame_0089.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0090.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0091.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0092.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0093.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0094.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0095.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0096.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
feels-promo/public/activity/frame_0097.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
feels-promo/public/activity/frame_0098.png
Normal file
|
After Width: | Height: | Size: 27 KiB |