diff --git a/Info.plist b/Info.plist index 2ccc146..de5787b 100644 --- a/Info.plist +++ b/Info.plist @@ -6,6 +6,10 @@ NSFaceIDUsageDescription Rosetta uses Face ID to unlock your account securely without entering your password. + NSPhotoLibraryUsageDescription + Rosetta needs access to your photo library to send images in chats. + NSCameraUsageDescription + Rosetta needs access to your camera to take and send photos in chats. UIBackgroundModes remote-notification diff --git a/Rosetta.xcodeproj/project.pbxproj b/Rosetta.xcodeproj/project.pbxproj index 3a72572..2b3ab9e 100644 --- a/Rosetta.xcodeproj/project.pbxproj +++ b/Rosetta.xcodeproj/project.pbxproj @@ -272,7 +272,7 @@ CODE_SIGN_ENTITLEMENTS = Rosetta/Rosetta.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = QN8Z263QGX; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -288,7 +288,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.rosetta.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -311,7 +311,7 @@ CODE_SIGN_ENTITLEMENTS = Rosetta/Rosetta.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 14; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = QN8Z263QGX; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -327,7 +327,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.rosetta.dev; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Rosetta/Assets.xcassets/updates-avatar.imageset/Contents.json b/Rosetta/Assets.xcassets/updates-avatar.imageset/Contents.json new file mode 100644 index 0000000..1db9fae --- /dev/null +++ b/Rosetta/Assets.xcassets/updates-avatar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "updates.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rosetta/Assets.xcassets/updates-avatar.imageset/updates.png b/Rosetta/Assets.xcassets/updates-avatar.imageset/updates.png new file mode 100644 index 0000000..98647a1 Binary files /dev/null and b/Rosetta/Assets.xcassets/updates-avatar.imageset/updates.png differ diff --git a/Rosetta/Core/Crypto/CryptoManager.swift b/Rosetta/Core/Crypto/CryptoManager.swift index 3140a31..b138d68 100644 --- a/Rosetta/Core/Crypto/CryptoManager.swift +++ b/Rosetta/Core/Crypto/CryptoManager.swift @@ -90,15 +90,15 @@ final class CryptoManager: @unchecked Sendable { return "\(iv.base64EncodedString()):\(ciphertext.base64EncodedString())" } - // MARK: - Desktop-Compatible Encryption (PBKDF2-SHA1 + zlibDeflate + AES-256-CBC) + // MARK: - Desktop-Compatible Encryption (PBKDF2-SHA256 + zlibDeflate + AES-256-CBC) - /// Desktop parity: CryptoJS PBKDF2 defaults to HMAC-SHA1, pako.deflate() is zlib-wrapped. + /// Desktop parity: CryptoJS v4 PBKDF2 defaults to HMAC-SHA256, pako.deflate() is zlib-wrapped. /// Use ONLY for cross-platform data: aesChachaKey, avatar blobs sent to server. nonisolated func encryptWithPasswordDesktopCompat(_ data: Data, password: String) throws -> String { let compressed = try CryptoPrimitives.zlibDeflate(data) let key = CryptoPrimitives.pbkdf2( password: password, salt: "rosetta", iterations: 1000, - keyLength: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1) + keyLength: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256) ) let iv = try CryptoPrimitives.randomBytes(count: 16) let ciphertext = try CryptoPrimitives.aesCBCEncrypt(compressed, key: key, iv: iv) @@ -110,39 +110,53 @@ final class CryptoManager: @unchecked Sendable { guard parts.count == 2, let iv = Data(base64Encoded: parts[0]), let ciphertext = Data(base64Encoded: parts[1]) else { + print("πŸ” [decrypt] ❌ Malformed: parts=\(encrypted.components(separatedBy: ":").count) encrypted.prefix=\(encrypted.prefix(60))…") throw CryptoError.invalidData("Malformed encrypted string") } - // SHA256 first: all existing iOS data was encrypted with SHA256. - // SHA1 fallback: desktop CryptoJS default + encryptWithPasswordDesktopCompat. + print("πŸ” [decrypt] iv=\(iv.count)bytes ciphertext=\(ciphertext.count)bytes passwordUTF8=\(Array(password.utf8).count)bytes passwordChars=\(password.count)") + + // SHA256 first: desktop CryptoJS v4 + both iOS encrypt functions use SHA256. + // SHA1 fallback: legacy messages encrypted before CryptoJS v4 migration. // ⚠️ SHA256 MUST be first β€” wrong-key AES-CBC can randomly produce valid // PKCS7 padding (~1/256 chance) and garbage may survive zlib inflate, // causing false-positive decryption with corrupt data. let prfOrder: [CCPseudoRandomAlgorithm] = [ - CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), // iOS encryptWithPassword - CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), // Desktop / encryptWithPasswordDesktopCompat + CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), // Desktop CryptoJS v4 + iOS + CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1), // Legacy fallback ] // 1) Preferred path: AES-CBC + inflate (handles both rawDeflate and zlibDeflate) for prf in prfOrder { - if let result = try? decryptWithPassword( - ciphertext: ciphertext, iv: iv, password: password, - prf: prf, expectsCompressed: true - ) { + let prfName = prf == CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256) ? "SHA256" : "SHA1" + do { + let result = try decryptWithPassword( + ciphertext: ciphertext, iv: iv, password: password, + prf: prf, expectsCompressed: true + ) + print("πŸ” [decrypt] βœ… \(prfName)+compressed succeeded, result=\(result.count)bytes, first4=\(result.prefix(4).map { String(format: "%02x", $0) }.joined())") return result + } catch { + print("πŸ” [decrypt] ⚠️ \(prfName)+compressed failed: \(error)") } } // 2) Fallback: AES-CBC without compression (very old/legacy payloads) for prf in prfOrder { - if let result = try? decryptWithPassword( - ciphertext: ciphertext, iv: iv, password: password, - prf: prf, expectsCompressed: false - ) { + let prfName = prf == CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256) ? "SHA256" : "SHA1" + do { + let result = try decryptWithPassword( + ciphertext: ciphertext, iv: iv, password: password, + prf: prf, expectsCompressed: false + ) + print("πŸ” [decrypt] βœ… \(prfName)+uncompressed succeeded, result=\(result.count)bytes, first4=\(result.prefix(4).map { String(format: "%02x", $0) }.joined())") return result + } catch { + print("πŸ” [decrypt] ⚠️ \(prfName)+uncompressed failed: \(error)") } } + print("πŸ” [decrypt] ❌ ALL paths failed") throw CryptoError.decryptionFailed } @@ -166,6 +180,7 @@ private extension CryptoManager { prf: CCPseudoRandomAlgorithm, expectsCompressed: Bool ) throws -> Data { + let prfName = prf == CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256) ? "SHA256" : "SHA1" let key = CryptoPrimitives.pbkdf2( password: password, salt: "rosetta", @@ -173,7 +188,9 @@ private extension CryptoManager { keyLength: 32, prf: prf ) + print("πŸ” [decrypt-inner] \(prfName) pbkdf2Key=\(key.prefix(8).map { String(format: "%02x", $0) }.joined()) passwordUTF8Len=\(Array(password.utf8).count)") let decrypted = try CryptoPrimitives.aesCBCDecrypt(ciphertext, key: key, iv: iv) + print("πŸ” [decrypt-inner] \(prfName) aesDecrypted=\(decrypted.count)bytes first16=\(decrypted.prefix(16).map { String(format: "%02x", $0) }.joined(separator: " "))") if expectsCompressed { return try CryptoPrimitives.rawInflate(decrypted) } diff --git a/Rosetta/Core/Crypto/CryptoPrimitives.swift b/Rosetta/Core/Crypto/CryptoPrimitives.swift index d11e299..ea9d464 100644 --- a/Rosetta/Core/Crypto/CryptoPrimitives.swift +++ b/Rosetta/Core/Crypto/CryptoPrimitives.swift @@ -79,15 +79,21 @@ enum CryptoPrimitives { keyLength: Int, prf: CCPseudoRandomAlgorithm ) -> Data { + // Desktop parity: CryptoJS PBKDF2 encodes the full UTF-8 string + // including embedded null bytes (U+0000). Swift's withCString + + // strlen() truncates at the first null byte, producing a different + // PBKDF2 key. Use explicit UTF-8 byte arrays to match JavaScript. + let passwordBytes = Array(password.utf8) + let saltBytes = Array(salt.utf8) var derivedKey = Data(repeating: 0, count: keyLength) derivedKey.withUnsafeMutableBytes { keyPtr in guard let keyBase = keyPtr.bindMemory(to: UInt8.self).baseAddress else { return } - password.withCString { passPtr in - salt.withCString { saltPtr in + passwordBytes.withUnsafeBufferPointer { passPtr in + saltBytes.withUnsafeBufferPointer { saltPtr in _ = CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), - passPtr, strlen(passPtr), - saltPtr, strlen(saltPtr), + passPtr.baseAddress, passPtr.count, + saltPtr.baseAddress, saltPtr.count, prf, UInt32(iterations), keyBase, diff --git a/Rosetta/Core/Crypto/XChaCha20Engine.swift b/Rosetta/Core/Crypto/XChaCha20Engine.swift index 9e678c6..94bf0ce 100644 --- a/Rosetta/Core/Crypto/XChaCha20Engine.swift +++ b/Rosetta/Core/Crypto/XChaCha20Engine.swift @@ -12,7 +12,7 @@ enum XChaCha20Engine { /// Decrypts ciphertext+tag using XChaCha20-Poly1305. static func decrypt(ciphertextWithTag: Data, key: Data, nonce: Data) throws -> Data { - guard ciphertextWithTag.count > poly1305TagSize else { + guard ciphertextWithTag.count >= poly1305TagSize else { throw CryptoError.invalidData("Ciphertext too short for Poly1305 tag") } guard key.count == 32, nonce.count == 24 else { diff --git a/Rosetta/Core/Data/Models/AttachmentCache.swift b/Rosetta/Core/Data/Models/AttachmentCache.swift new file mode 100644 index 0000000..285eaf7 --- /dev/null +++ b/Rosetta/Core/Data/Models/AttachmentCache.swift @@ -0,0 +1,68 @@ +import UIKit +import os + +// MARK: - AttachmentCache + +/// Local disk cache for downloaded/decrypted attachment images and files. +/// +/// Desktop parity: `readFile("m/...")` and `writeFile(...)` in `DialogProvider.tsx`. +/// Attachments are cached after download+decrypt so subsequent opens are instant. +/// +/// Cache directory: `Documents/AttachmentCache/` +/// Key format: attachment ID (8-char random string). +final class AttachmentCache: @unchecked Sendable { + + static let shared = AttachmentCache() + + private static let logger = Logger(subsystem: "com.rosetta.messenger", category: "AttachmentCache") + + private let cacheDir: URL + + private init() { + let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + cacheDir = docs.appendingPathComponent("AttachmentCache", isDirectory: true) + try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true) + } + + // MARK: - Images + + /// Saves a decoded image to cache. + func saveImage(_ image: UIImage, forAttachmentId id: String) { + guard let data = image.jpegData(compressionQuality: 0.95) else { return } + let url = cacheDir.appendingPathComponent("img_\(id).jpg") + try? data.write(to: url, options: .atomic) + } + + /// Loads a cached image for an attachment ID, or `nil` if not cached. + func loadImage(forAttachmentId id: String) -> UIImage? { + let url = cacheDir.appendingPathComponent("img_\(id).jpg") + guard FileManager.default.fileExists(atPath: url.path) else { return nil } + return UIImage(contentsOfFile: url.path) + } + + // MARK: - Files + + /// Saves raw file data to cache, returns the file URL. + @discardableResult + func saveFile(_ data: Data, forAttachmentId id: String, fileName: String) -> URL { + let safeFileName = fileName.replacingOccurrences(of: "/", with: "_") + let url = cacheDir.appendingPathComponent("file_\(id)_\(safeFileName)") + try? data.write(to: url, options: .atomic) + return url + } + + /// Returns cached file URL, or `nil` if not cached. + func fileURL(forAttachmentId id: String, fileName: String) -> URL? { + let safeFileName = fileName.replacingOccurrences(of: "/", with: "_") + let url = cacheDir.appendingPathComponent("file_\(id)_\(safeFileName)") + return FileManager.default.fileExists(atPath: url.path) ? url : nil + } + + // MARK: - Cleanup + + /// Removes all cached attachments. + func clearAll() { + try? FileManager.default.removeItem(at: cacheDir) + try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true) + } +} diff --git a/Rosetta/Core/Data/Models/ChatMessage.swift b/Rosetta/Core/Data/Models/ChatMessage.swift index 9099c6e..54f2a6e 100644 --- a/Rosetta/Core/Data/Models/ChatMessage.swift +++ b/Rosetta/Core/Data/Models/ChatMessage.swift @@ -11,6 +11,12 @@ struct ChatMessage: Identifiable, Codable, Sendable { var isRead: Bool var attachments: [MessageAttachment] + /// Desktop parity: stores the decrypted plainKeyAndNonce (latin1) for on-demand + /// attachment download & decryption. Computed during message decryption in + /// `handleIncomingMessage()` and during outgoing send in `sendMessageWithAttachments()`. + /// `nil` for messages without attachments or legacy persisted messages. + var attachmentPassword: String? + func isFromMe(myPublicKey: String) -> Bool { fromPublicKey == myPublicKey } diff --git a/Rosetta/Core/Data/Repositories/AvatarRepository.swift b/Rosetta/Core/Data/Repositories/AvatarRepository.swift index 7222369..50957a8 100644 --- a/Rosetta/Core/Data/Repositories/AvatarRepository.swift +++ b/Rosetta/Core/Data/Repositories/AvatarRepository.swift @@ -21,7 +21,13 @@ final class AvatarRepository { private(set) var avatarVersion: UInt = 0 /// In-memory cache for decoded UIImages β€” keyed by normalized public key. - private let cache = NSCache() + /// Bounded: 50MB / 200 images max to prevent memory pressure on low-end devices. + private let cache: NSCache = { + let c = NSCache() + c.totalCostLimit = 50 * 1024 * 1024 // 50MB + c.countLimit = 200 + return c + }() /// JPEG compression quality (0.8 = reasonable size for avatars). private let compressionQuality: CGFloat = 0.8 @@ -36,14 +42,22 @@ final class AvatarRepository { let url = avatarURL(for: key) ensureDirectoryExists() try? data.write(to: url, options: .atomic) - cache.setObject(image, forKey: key as NSString) + cache.setObject(image, forKey: key as NSString, cost: data.count) avatarVersion += 1 } /// Saves an avatar from a base64-encoded image string (used when receiving from network). - /// Desktop parity: re-encodes with `AVATAR_PASSWORD_TO_ENCODE` β€” iOS stores as plain JPEG. + /// Desktop parity: desktop sends data URI format (`data:image/png;base64,...`). + /// Handles both raw base64 and data URI formats for cross-platform compatibility. func saveAvatarFromBase64(_ base64: String, publicKey: String) { - guard let data = Data(base64Encoded: base64), + let rawBase64: String + if base64.hasPrefix("data:") { + // Desktop format: "data:image/png;base64,iVBOR..." β€” strip prefix + rawBase64 = String(base64.drop(while: { $0 != "," }).dropFirst()) + } else { + rawBase64 = base64 + } + guard let data = Data(base64Encoded: rawBase64), let image = UIImage(data: data) else { return } saveAvatar(publicKey: publicKey, image: image) } @@ -65,15 +79,19 @@ final class AvatarRepository { let image = UIImage(data: data) else { return nil } - cache.setObject(image, forKey: key as NSString) + cache.setObject(image, forKey: key as NSString, cost: data.count) return image } /// Returns bundled avatar for system accounts, nil for regular accounts. + /// Desktop parity: `useSystemAccounts.ts` imports `updates.png` and `safe.png`. private func systemAccountAvatar(for publicKey: String) -> UIImage? { if publicKey == SystemAccounts.safePublicKey { return UIImage(named: "safe-avatar") } + if publicKey == SystemAccounts.updatesPublicKey { + return UIImage(named: "updates-avatar") + } return nil } @@ -96,6 +114,11 @@ final class AvatarRepository { avatarVersion += 1 } + /// Clears in-memory cache only (used on memory warning). Disk avatars preserved. + func clearCache() { + cache.removeAllObjects() + } + /// Clears entire avatar cache (used on full data reset). func clearAll() { cache.removeAllObjects() diff --git a/Rosetta/Core/Data/Repositories/DialogRepository.swift b/Rosetta/Core/Data/Repositories/DialogRepository.swift index b180698..2766ee2 100644 --- a/Rosetta/Core/Data/Repositories/DialogRepository.swift +++ b/Rosetta/Core/Data/Repositories/DialogRepository.swift @@ -1,5 +1,6 @@ import Foundation import Observation +import UserNotifications /// Account-scoped dialog store with disk persistence. @Observable @@ -79,6 +80,7 @@ final class DialogRepository { .map { ($0.opponentKey, $0) } ) _sortedKeysCache = nil + updateAppBadge() } func reset(clearPersisted: Bool = false) { @@ -87,6 +89,7 @@ final class DialogRepository { dialogs.removeAll() _sortedKeysCache = nil storagePassword = "" + UNUserNotificationCenter.current().setBadgeCount(0) guard !currentAccount.isEmpty else { return } let accountToReset = currentAccount @@ -141,7 +144,19 @@ final class DialogRepository { lastMessageDelivered: .waiting ) - dialog.lastMessage = decryptedText + // Desktop parity: constructLastMessageTextByAttachments() returns + // "Photo"/"Avatar"/"File" for attachment-only messages. + if decryptedText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + let firstAttachment = packet.attachments.first { + switch firstAttachment.type { + case .image: dialog.lastMessage = "Photo" + case .file: dialog.lastMessage = "File" + case .avatar: dialog.lastMessage = "Avatar" + default: dialog.lastMessage = decryptedText + } + } else { + dialog.lastMessage = decryptedText + } dialog.lastMessageTimestamp = normalizeTimestamp(packet.timestamp) dialog.lastMessageFromMe = fromMe dialog.lastMessageDelivered = fromMe ? (fromSync ? .delivered : .waiting) : .delivered @@ -165,7 +180,12 @@ final class DialogRepository { schedulePersist() // Desktop parity: re-evaluate request status based on last N messages. - updateRequestStatus(opponentKey: opponentKey) + // Skip for outgoing messages β€” iHaveSent is already set to true above, + // and the message hasn't been added to MessageRepository yet (race condition: + // updateRequestStatus reads MessageRepository BEFORE upsertFromMessagePacket). + if !fromMe { + updateRequestStatus(opponentKey: opponentKey) + } } func ensureDialog( @@ -314,7 +334,17 @@ final class DialogRepository { return } - dialog.lastMessage = lastMsg.text + if lastMsg.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, + let firstAttachment = lastMsg.attachments.first { + switch firstAttachment.type { + case .image: dialog.lastMessage = "Photo" + case .file: dialog.lastMessage = "File" + case .avatar: dialog.lastMessage = "Avatar" + default: dialog.lastMessage = lastMsg.text + } + } else { + dialog.lastMessage = lastMsg.text + } dialog.lastMessageTimestamp = lastMsg.timestamp dialog.lastMessageFromMe = lastMsg.fromPublicKey == currentAccount dialog.lastMessageDelivered = lastMsg.deliveryStatus @@ -436,6 +466,8 @@ final class DialogRepository { private func schedulePersist() { guard !currentAccount.isEmpty else { return } + updateAppBadge() + let snapshot = Array(dialogs.values) let fileName = Self.dialogsFileName(for: currentAccount) let storagePassword = self.storagePassword @@ -451,6 +483,12 @@ final class DialogRepository { } } + /// Update app icon badge with total unread message count. + private func updateAppBadge() { + let total = dialogs.values.reduce(0) { $0 + $1.unreadCount } + UNUserNotificationCenter.current().setBadgeCount(total) + } + private static func dialogsFileName(for accountPublicKey: String) -> String { ChatPersistenceStore.accountScopedFileName(prefix: "dialogs", accountPublicKey: accountPublicKey) } diff --git a/Rosetta/Core/Data/Repositories/MessageRepository.swift b/Rosetta/Core/Data/Repositories/MessageRepository.swift index babeeb9..e03f371 100644 --- a/Rosetta/Core/Data/Repositories/MessageRepository.swift +++ b/Rosetta/Core/Data/Repositories/MessageRepository.swift @@ -120,7 +120,13 @@ final class MessageRepository: ObservableObject { /// - Parameter fromSync: When `true`, outgoing messages are created as `.delivered` /// because the server already processed them during sync β€” ACKs will never arrive again. - func upsertFromMessagePacket(_ packet: PacketMessage, myPublicKey: String, decryptedText: String, fromSync: Bool = false) { + func upsertFromMessagePacket( + _ packet: PacketMessage, + myPublicKey: String, + decryptedText: String, + attachmentPassword: String? = nil, + fromSync: Bool = false + ) { let fromMe = packet.fromPublicKey == myPublicKey let dialogKey = fromMe ? packet.toPublicKey : packet.fromPublicKey let messageId = packet.messageId.isEmpty ? UUID().uuidString : packet.messageId @@ -138,6 +144,10 @@ final class MessageRepository: ObservableObject { messages[existingIndex].text = decryptedText messages[existingIndex].timestamp = timestamp messages[existingIndex].attachments = packet.attachments + // Preserve or update attachment password for on-demand download + if let attachmentPassword, !attachmentPassword.isEmpty { + messages[existingIndex].attachmentPassword = attachmentPassword + } if fromMe, messages[existingIndex].deliveryStatus == .error { messages[existingIndex].deliveryStatus = fromSync ? .delivered : .waiting } @@ -156,7 +166,8 @@ final class MessageRepository: ObservableObject { timestamp: timestamp, deliveryStatus: outgoingStatus, isRead: incomingRead || fromMe, - attachments: packet.attachments + attachments: packet.attachments, + attachmentPassword: attachmentPassword ) ) } @@ -211,7 +222,11 @@ final class MessageRepository: ObservableObject { // MARK: - Typing func markTyping(from opponentKey: String) { - typingDialogs.insert(opponentKey) + // Guard: only publish if not already in set β€” prevents duplicate re-renders + // when multiple typing packets arrive within the 3s window. + if !typingDialogs.contains(opponentKey) { + typingDialogs.insert(opponentKey) + } typingResetTasks[opponentKey]?.cancel() typingResetTasks[opponentKey] = Task { @MainActor [weak self] in try? await Task.sleep(for: .seconds(3)) @@ -330,6 +345,15 @@ final class MessageRepository: ObservableObject { } return $0.id < $1.id } + // Desktop parity: cap to MESSAGE_MAX_LOADED (40) newest messages per dialog. + // Keeps memory bounded β€” evicted IDs stay in allKnownMessageIds for dedup. + if messages.count > ProtocolConstants.messageMaxCached { + let evicted = messages.prefix(messages.count - ProtocolConstants.messageMaxCached) + for message in evicted { + messageToDialog.removeValue(forKey: message.id) + } + messages = Array(messages.suffix(ProtocolConstants.messageMaxCached)) + } } messagesByDialog[dialogKey] = messages schedulePersist() @@ -349,9 +373,12 @@ final class MessageRepository: ObservableObject { let knownIdsFile = Self.knownIdsFileName(for: currentAccount) let storagePassword = self.storagePassword let password = storagePassword.isEmpty ? nil : storagePassword + // During sync bursts, increase debounce to reduce disk I/O (full dict serialization). + let isSyncing = SessionManager.shared.syncBatchInProgress + let debounceMs = isSyncing ? 2000 : 400 persistTask?.cancel() persistTask = Task(priority: .utility) { - try? await Task.sleep(for: .milliseconds(400)) + try? await Task.sleep(for: .milliseconds(debounceMs)) guard !Task.isCancelled else { return } await ChatPersistenceStore.shared.save(snapshot, fileName: messagesFile, password: password) await ChatPersistenceStore.shared.save(idsSnapshot, fileName: knownIdsFile, password: password) diff --git a/Rosetta/Core/Network/Protocol/ProtocolManager.swift b/Rosetta/Core/Network/Protocol/ProtocolManager.swift index 3073483..407380d 100644 --- a/Rosetta/Core/Network/Protocol/ProtocolManager.swift +++ b/Rosetta/Core/Network/Protocol/ProtocolManager.swift @@ -104,10 +104,50 @@ final class ProtocolManager: @unchecked Sendable { } } - /// Immediately reconnect after returning from background, bypassing backoff. + /// Verify connection health after returning from background. + /// If connection appears alive, sends a WebSocket ping to confirm. + /// If ping fails or times out (2s), forces immediate reconnection. func reconnectIfNeeded() { guard savedPublicKey != nil, savedPrivateHash != nil else { return } - if connectionState == .authenticated || connectionState == .handshaking { return } + + // Don't interrupt active handshake + if connectionState == .handshaking { return } + + if connectionState == .authenticated && client.isConnected { + // Connection looks alive β€” verify with ping (2s timeout) + Self.logger.info("Verifying connection with ping...") + + let pingTimeoutTask = Task { [weak self] in + try? await Task.sleep(nanoseconds: 2_000_000_000) + guard !Task.isCancelled, let self else { return } + Self.logger.info("Ping timeout β€” connection dead, force reconnecting") + self.handshakeComplete = false + self.heartbeatTask?.cancel() + Task { @MainActor in + self.connectionState = .connecting + } + self.client.forceReconnect() + } + + client.sendPing { [weak self] error in + pingTimeoutTask.cancel() + guard let self else { return } + if let error { + Self.logger.info("Ping failed: \(error.localizedDescription) β€” force reconnecting") + self.handshakeComplete = false + self.heartbeatTask?.cancel() + Task { @MainActor in + self.connectionState = .connecting + } + self.client.forceReconnect() + } else { + Self.logger.info("Ping succeeded β€” connection alive") + } + } + return + } + + // Not authenticated or not connected β€” force reconnect immediately Self.logger.info("Force reconnect from foreground") connectionState = .connecting client.forceReconnect() diff --git a/Rosetta/Core/Network/Protocol/WebSocketClient.swift b/Rosetta/Core/Network/Protocol/WebSocketClient.swift index d65056a..3bb88eb 100644 --- a/Rosetta/Core/Network/Protocol/WebSocketClient.swift +++ b/Rosetta/Core/Network/Protocol/WebSocketClient.swift @@ -60,9 +60,10 @@ final class WebSocketClient: NSObject, @unchecked Sendable, URLSessionWebSocketD guard !isManuallyClosed else { return } reconnectTask?.cancel() reconnectTask = nil - guard !isConnected else { return } + // Always tear down and reconnect β€” connection may be zombie after background webSocketTask?.cancel(with: .goingAway, reason: nil) webSocketTask = nil + isConnected = false disconnectHandledForCurrentSocket = false Self.logger.info("Force reconnect triggered") connect() @@ -87,13 +88,27 @@ final class WebSocketClient: NSObject, @unchecked Sendable, URLSessionWebSocketD func sendText(_ text: String) { guard let task = webSocketTask else { return } - task.send(.string(text)) { error in + task.send(.string(text)) { [weak self] error in if let error { Self.logger.error("Send text error: \(error.localizedDescription)") + self?.handleDisconnect(error: error) } } } + /// Sends a WebSocket-level ping to verify the connection is alive. + /// Completion receives nil on success (pong received) or an Error on failure. + func sendPing(completion: @escaping (Error?) -> Void) { + guard let task = webSocketTask else { + completion(NSError(domain: "WebSocket", code: -1, + userInfo: [NSLocalizedDescriptionKey: "No active WebSocket task"])) + return + } + task.sendPing { error in + completion(error) + } + } + // MARK: - URLSessionWebSocketDelegate nonisolated func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { diff --git a/Rosetta/Core/Services/SessionManager.swift b/Rosetta/Core/Services/SessionManager.swift index e522556..b34b6fd 100644 --- a/Rosetta/Core/Services/SessionManager.swift +++ b/Rosetta/Core/Services/SessionManager.swift @@ -99,6 +99,10 @@ final class SessionManager { ) RecentSearchesRepository.shared.setAccount(account.publicKey) + // Desktop parity: send release notes as a system message from "Rosetta Updates" + // account if the app version changed since the last notice. + sendReleaseNotesIfNeeded(publicKey: account.publicKey) + // Generate private key hash for handshake let hash = crypto.generatePrivateKeyHash(privateKeyHex: privateKeyHex) privateKeyHash = hash @@ -215,12 +219,13 @@ final class SessionManager { // Desktop parity: attachment password = plainKeyAndNonce interpreted as UTF-8 string // (same derivation as aesChachaKey: key.toString('utf-8') in useDialog.ts) - guard let latin1String = String(data: encrypted.plainKeyAndNonce, encoding: .isoLatin1) else { - throw CryptoError.encryptionFailed - } + // Must use UTF-8 decoding with replacement characters (U+FFFD) to match Node.js behavior. + let latin1String = String(decoding: encrypted.plainKeyAndNonce, as: UTF8.self) - // Encrypt avatar blob with the plainKeyAndNonce password (desktop: encodeWithPassword) - let avatarData = Data(avatarBase64.utf8) + // Desktop parity: avatar blob is a full data URI (e.g. "data:image/png;base64,iVBOR...") + // not just raw base64. Desktop's AvatarProvider stores and sends data URIs. + let dataURI = "data:image/jpeg;base64,\(avatarBase64)" + let avatarData = Data(dataURI.utf8) let encryptedBlob = try CryptoManager.shared.encryptWithPasswordDesktopCompat( avatarData, password: latin1String @@ -232,8 +237,10 @@ final class SessionManager { content: Data(encryptedBlob.utf8) ) - // Desktop parity: preview = "tag::blurhash" (blurhash optional, skip for now) - let preview = "\(tag)::" + // Desktop parity: preview = "tag::blurhash" (same as IMAGE attachments) + let avatarImage = AvatarRepository.shared.loadAvatar(publicKey: currentPublicKey) + let blurhash = avatarImage?.blurHash(numberOfComponents: (4, 4)) ?? "" + let preview = "\(tag)::\(blurhash)" // Build aesChachaKey (same as regular messages) let aesChachaPayload = Data(latin1String.utf8) @@ -279,7 +286,8 @@ final class SessionManager { packet, myPublicKey: currentPublicKey, decryptedText: " ", fromSync: offlineAsSend ) MessageRepository.shared.upsertFromMessagePacket( - packet, myPublicKey: currentPublicKey, decryptedText: " ", fromSync: offlineAsSend + packet, myPublicKey: currentPublicKey, decryptedText: " ", + attachmentPassword: latin1String, fromSync: offlineAsSend ) if offlineAsSend { @@ -299,6 +307,187 @@ final class SessionManager { Self.logger.info("πŸ“€ Avatar sent to \(toPublicKey.prefix(12))… tag=\(tag)") } + /// Sends a message with image/file attachments. + /// + /// Desktop parity: `prepareAttachmentsToSend()` in `DialogProvider.tsx` β†’ + /// for each attachment: encrypt blob β†’ upload to transport β†’ set preview = tag β†’ clear blob. + /// + /// Flow per attachment: + /// 1. Build data URI (desktop: `FileReader.readAsDataURL()`) + /// 2. Encrypt with `encryptWithPasswordDesktopCompat(dataURI, password: latin1OfPlainKeyAndNonce)` + /// 3. Upload encrypted blob to transport β†’ get server tag + /// 4. Set preview: IMAGE β†’ `"tag::"`, FILE β†’ `"tag::size::filename"` + /// 5. Clear blob (not sent over WebSocket) + func sendMessageWithAttachments( + text: String, + attachments: [PendingAttachment], + toPublicKey: String, + opponentTitle: String = "", + opponentUsername: String = "" + ) async throws { + guard let privKey = privateKeyHex, let hash = privateKeyHash else { + Self.logger.error("πŸ“€ Cannot send β€” missing keys") + throw CryptoError.decryptionFailed + } + + let messageId = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased() + let timestamp = Int64(Date().timeIntervalSince1970 * 1000) + + // Encrypt message text (use single space if empty β€” desktop parity) + let messageText = text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? " " : text + let encrypted = try MessageCrypto.encryptOutgoing( + plaintext: messageText, + recipientPublicKeyHex: toPublicKey + ) + + // Derive attachment password from plainKeyAndNonce (desktop: key.toString('utf-8')) + // Must use UTF-8 decoding with replacement characters (U+FFFD for invalid sequences) + // to match Node.js Buffer.toString('utf-8') behavior exactly. + // Previously used .isoLatin1 which produced different PBKDF2 keys for bytes > 0x7F. + let latin1String = String(decoding: encrypted.plainKeyAndNonce, as: UTF8.self) + + // Process each attachment: encrypt β†’ upload β†’ build metadata + var messageAttachments: [MessageAttachment] = [] + for attachment in attachments { + // Build data URI (desktop: FileReader.readAsDataURL) + let dataURI = buildDataURI(attachment) + + // Encrypt blob with desktop-compatible encryption + let encryptedBlob = try CryptoManager.shared.encryptWithPasswordDesktopCompat( + Data(dataURI.utf8), + password: latin1String + ) + + // Upload to transport server + let tag = try await TransportManager.shared.uploadFile( + id: attachment.id, + content: Data(encryptedBlob.utf8) + ) + + // Build preview string (format depends on type) + // Desktop parity: preview = "tag::blurhash" for images, "tag::size::filename" for files + let preview: String + switch attachment.type { + case .image: + // Generate blurhash from thumbnail (desktop: encode(imageData, width, height, 4, 4)) + let blurhash = attachment.thumbnail?.blurHash(numberOfComponents: (4, 4)) ?? "" + preview = "\(tag)::\(blurhash)" + case .file: + // Desktop: preview = "tag::size::filename" + preview = "\(tag)::\(attachment.fileSize ?? 0)::\(attachment.fileName ?? "file")" + default: + preview = "\(tag)::" + } + + messageAttachments.append(MessageAttachment( + id: attachment.id, + preview: preview, + blob: "", // Desktop parity: blob cleared after upload + type: attachment.type + )) + + Self.logger.info("πŸ“€ Attachment uploaded: type=\(String(describing: attachment.type)), tag=\(tag)") + } + + // Build aesChachaKey (for sync/backup β€” same as regular messages) + let aesChachaPayload = Data(latin1String.utf8) + let aesChachaKey = try CryptoManager.shared.encryptWithPasswordDesktopCompat( + aesChachaPayload, + password: privKey + ) + + // Build PacketMessage with attachments + var packet = PacketMessage() + packet.fromPublicKey = currentPublicKey + packet.toPublicKey = toPublicKey + packet.content = encrypted.content + packet.chachaKey = encrypted.chachaKey + packet.timestamp = timestamp + packet.privateKey = hash + packet.messageId = messageId + packet.aesChachaKey = aesChachaKey + packet.attachments = messageAttachments + + // Ensure dialog exists + let existingDialog = DialogRepository.shared.dialogs[toPublicKey] + let title = !opponentTitle.isEmpty ? opponentTitle : (existingDialog?.opponentTitle ?? "") + let username = !opponentUsername.isEmpty ? opponentUsername : (existingDialog?.opponentUsername ?? "") + DialogRepository.shared.ensureDialog( + opponentKey: toPublicKey, + title: title, + username: username, + myPublicKey: currentPublicKey + ) + + // Optimistic UI update + let isConnected = ProtocolManager.shared.connectionState == .authenticated + let offlineAsSend = !isConnected + + // For outgoing messages, store attachment password so we can view our own sent images + let displayText = messageText == " " ? " " : messageText + DialogRepository.shared.updateFromMessage( + packet, myPublicKey: currentPublicKey, decryptedText: displayText, fromSync: offlineAsSend + ) + MessageRepository.shared.upsertFromMessagePacket( + packet, + myPublicKey: currentPublicKey, + decryptedText: displayText, + attachmentPassword: latin1String, + fromSync: offlineAsSend + ) + + if offlineAsSend { + MessageRepository.shared.updateDeliveryStatus(messageId: messageId, status: .error) + DialogRepository.shared.updateDeliveryStatus( + messageId: messageId, opponentKey: toPublicKey, status: .error + ) + } + + // Saved Messages: local-only, no server send + if toPublicKey == currentPublicKey { + MessageRepository.shared.updateDeliveryStatus(messageId: messageId, status: .delivered) + DialogRepository.shared.updateDeliveryStatus( + messageId: messageId, opponentKey: toPublicKey, status: .delivered + ) + return + } + + ProtocolManager.shared.sendPacket(packet) + registerOutgoingRetry(for: packet) + Self.logger.info("πŸ“€ Message with \(attachments.count) attachment(s) sent to \(toPublicKey.prefix(12))…") + } + + /// Builds a data URI from attachment data (desktop: `FileReader.readAsDataURL()`). + private func buildDataURI(_ attachment: PendingAttachment) -> String { + let base64 = attachment.data.base64EncodedString() + switch attachment.type { + case .image: + return "data:image/jpeg;base64,\(base64)" + case .file: + let mimeType = mimeTypeForFileName(attachment.fileName ?? "file") + return "data:\(mimeType);base64,\(base64)" + default: + return "data:application/octet-stream;base64,\(base64)" + } + } + + /// Returns MIME type for a file name based on extension. + private func mimeTypeForFileName(_ name: String) -> String { + let ext = (name as NSString).pathExtension.lowercased() + switch ext { + case "jpg", "jpeg": return "image/jpeg" + case "png": return "image/png" + case "gif": return "image/gif" + case "pdf": return "application/pdf" + case "zip": return "application/zip" + case "mp4": return "video/mp4" + case "mp3": return "audio/mpeg" + case "doc", "docx": return "application/msword" + case "txt": return "text/plain" + default: return "application/octet-stream" + } + } + /// Sends typing indicator with throttling (desktop parity: max once per 3s per dialog). func sendTypingIndicator(toPublicKey: String) { guard toPublicKey != currentPublicKey, @@ -657,7 +846,13 @@ final class SessionManager { let fromMe = packet.fromPublicKey == myKey - guard Self.isSupportedDirectMessagePacket(packet, ownKey: myKey) else { return } + // DEBUG: log every call to processIncomingMessage + print("πŸ” [processIncoming] msgId=\(packet.messageId.prefix(8))… from=\(packet.fromPublicKey.prefix(12))… to=\(packet.toPublicKey.prefix(12))… fromMe=\(fromMe) content=\(packet.content.count)chars chacha=\(packet.chachaKey.count)chars aesChacha=\(packet.aesChachaKey.count)chars attachments=\(packet.attachments.count)") + + guard Self.isSupportedDirectMessagePacket(packet, ownKey: myKey) else { + print("πŸ” [processIncoming] ❌ SKIPPED by isSupportedDirectMessagePacket β€” myKey=\(myKey.prefix(12))…") + return + } let opponentKey = fromMe ? packet.toPublicKey : packet.fromPublicKey let wasKnownBefore = MessageRepository.shared.hasMessage(packet.messageId) @@ -668,13 +863,33 @@ final class SessionManager { privateKeyHex: currentPrivateKeyHex ) - guard let result = decryptResult else { return } + guard let result = decryptResult else { + print("πŸ” [processIncoming] ❌ decryptIncomingMessage returned nil for msgId=\(packet.messageId.prefix(8))… isOwnMessage=\(fromMe) privateKeyHex=\(currentPrivateKeyHex != nil ? "present" : "NIL") content.isEmpty=\(packet.content.isEmpty) chachaKey.isEmpty=\(packet.chachaKey.isEmpty) aesChachaKey.isEmpty=\(packet.aesChachaKey.isEmpty)") + return + } let text = result.text // Desktop parity: decrypt MESSAGES-type attachment blobs inline. var processedPacket = packet + // Store attachment password for on-demand image/file download in UI. + var resolvedAttachmentPassword: String? + + // Debug: log attachment info for incoming messages + if !processedPacket.attachments.isEmpty { + Self.logger.info("πŸ“Ž Message \(packet.messageId.prefix(8))… has \(processedPacket.attachments.count) attachment(s), rawKeyData=\(result.rawKeyData != nil ? "\(result.rawKeyData!.count) bytes" : "nil")") + for (i, att) in processedPacket.attachments.enumerated() { + Self.logger.info(" πŸ“Ž[\(i)] type=\(att.type.rawValue) id=\(att.id) preview=\(att.preview.prefix(60))…") + } + } + if let keyData = result.rawKeyData { + // Desktop parity: Buffer.toString('utf-8') β€” replaces invalid UTF-8 bytes with U+FFFD. + // Must match sending side encoding for correct PBKDF2 key derivation. let attachmentPassword = String(decoding: keyData, as: UTF8.self) + resolvedAttachmentPassword = attachmentPassword + print("πŸ”‘ [attachPwd] rawKeyData(\(keyData.count)bytes)=\(keyData.map { String(format: "%02x", $0) }.joined(separator: " "))") + print("πŸ”‘ [attachPwd] passwordUTF8(\(Array(attachmentPassword.utf8).count)bytes)=\(Array(attachmentPassword.utf8).map { String(format: "%02x", $0) }.joined(separator: " "))") + print("πŸ”‘ [attachPwd] passwordChars=\(attachmentPassword.count)") for i in processedPacket.attachments.indices where processedPacket.attachments[i].type == .messages { let blob = processedPacket.attachments[i].blob if !blob.isEmpty, @@ -726,6 +941,7 @@ final class SessionManager { processedPacket, myPublicKey: myKey, decryptedText: text, + attachmentPassword: resolvedAttachmentPassword, fromSync: syncBatchInProgress ) @@ -879,10 +1095,14 @@ final class SessionManager { ) -> (text: String, rawKeyData: Data?)? { let isOwnMessage = packet.fromPublicKey == myPublicKey - guard let privateKeyHex, !packet.content.isEmpty else { return nil } + guard let privateKeyHex, !packet.content.isEmpty else { + print("πŸ” [decrypt] ❌ Early return: privateKeyHex=\(privateKeyHex != nil ? "present" : "NIL") content.isEmpty=\(packet.content.isEmpty)") + return nil + } // Own sync packets: prefer aesChachaKey (PBKDF2+AES encrypted key+nonce). if isOwnMessage, !packet.aesChachaKey.isEmpty { + print("πŸ” [decrypt] Trying AES-CHACHA path (own sync) for msgId=\(packet.messageId.prefix(8))…") do { let decryptedPayload = try CryptoManager.shared.decryptWithPassword( packet.aesChachaKey, @@ -893,15 +1113,20 @@ final class SessionManager { ciphertext: packet.content, plainKeyAndNonce: keyAndNonce ) - return (text, keyAndNonce) + print("πŸ” [decrypt] βœ… AES-CHACHA path succeeded, text=\(text.prefix(30))… rawKeyData=\(decryptedPayload.count) bytes") + return (text, decryptedPayload) } catch { - // Fall through to ECDH path + print("πŸ” [decrypt] ⚠️ AES-CHACHA path failed: \(error). Falling through to ECDH…") } } // ECDH path (works for opponent messages, may work for own if chachaKey targets us) - guard !packet.chachaKey.isEmpty else { return nil } + guard !packet.chachaKey.isEmpty else { + print("πŸ” [decrypt] ❌ chachaKey is empty, no ECDH path available. isOwnMessage=\(isOwnMessage)") + return nil + } + print("πŸ” [decrypt] Trying ECDH path for msgId=\(packet.messageId.prefix(8))… chachaKey=\(packet.chachaKey.prefix(30))…") do { let text = try MessageCrypto.decryptIncoming( ciphertext: packet.content, @@ -912,8 +1137,10 @@ final class SessionManager { encryptedKey: packet.chachaKey, myPrivateKeyHex: privateKeyHex ) + print("πŸ” [decrypt] βœ… ECDH path succeeded, text=\(text.prefix(30))… rawKeyData=\(rawKeyData != nil ? "\(rawKeyData!.count) bytes" : "nil")") return (text, rawKeyData) } catch { + print("πŸ” [decrypt] ❌ ECDH path failed: \(error)") return nil } } @@ -1295,6 +1522,59 @@ final class SessionManager { Self.logger.info("Push token sent to server") } + // MARK: - Release Notes (Desktop Parity) + + /// Desktop parity: sends release notes as a local system message from + /// the "Rosetta Updates" account when the app version changes. + /// Desktop equivalent: `useUpdateMessage.ts` β†’ `useSendSystemMessage("updates")`. + private func sendReleaseNotesIfNeeded(publicKey: String) { + let key = "lastReleaseNoticeVersion_\(publicKey)" + let lastVersion = UserDefaults.standard.string(forKey: key) ?? "" + let currentVersion = ReleaseNotes.appVersion + + guard lastVersion != currentVersion else { return } + + let noticeText = ReleaseNotes.releaseNoticeText + guard !noticeText.isEmpty else { return } + + let now = Int64(Date().timeIntervalSince1970 * 1000) + let messageId = "release_notes_\(currentVersion)" + + // Create synthetic PacketMessage β€” local-only, never sent to server. + var packet = PacketMessage() + packet.fromPublicKey = Account.updatesPublicKey + packet.toPublicKey = publicKey + packet.timestamp = now + packet.messageId = messageId + + // Insert message into MessageRepository (delivered immediately). + MessageRepository.shared.upsertFromMessagePacket( + packet, + myPublicKey: publicKey, + decryptedText: noticeText + ) + + // Create/update dialog in DialogRepository. + DialogRepository.shared.updateFromMessage( + packet, + myPublicKey: publicKey, + decryptedText: noticeText, + isNewMessage: true + ) + + // Set system account display info (title, username, verified badge). + // Desktop parity: both system accounts use verified=1 (blue rosette badge). + DialogRepository.shared.updateUserInfo( + publicKey: Account.updatesPublicKey, + title: SystemAccounts.updatesTitle, + username: "updates", + verified: 1 + ) + + UserDefaults.standard.set(currentVersion, forKey: key) + Self.logger.info("Release notes v\(currentVersion) sent to Updates chat") + } + // MARK: - Idle Detection Setup private func setupIdleDetection() { @@ -1306,10 +1586,9 @@ final class SessionManager { ) { [weak self] _ in Task { @MainActor [weak self] in self?.lastUserInteractionTime = Date() - // Immediate reconnect when returning from background - if ProtocolManager.shared.connectionState != .authenticated { - ProtocolManager.shared.reconnectIfNeeded() - } + // Always verify connection on foreground β€” don't trust cached state. + // reconnectIfNeeded() pings to check if "authenticated" connection is alive. + ProtocolManager.shared.reconnectIfNeeded() } } } diff --git a/Rosetta/Core/Utils/BlurHash.swift b/Rosetta/Core/Utils/BlurHash.swift new file mode 100644 index 0000000..ce225c2 --- /dev/null +++ b/Rosetta/Core/Utils/BlurHash.swift @@ -0,0 +1,189 @@ +import UIKit + +// MARK: - BlurHash Encoder + +/// Pure Swift BlurHash encoder matching the reference implementation at https://blurha.sh/ +/// Desktop parity: uses `encode(imageData.data, width, height, 4, 4)` from the `blurhash` npm package. +/// +/// BlurHash encodes a compact representation of a placeholder for an image (typically 20-30 chars). +/// Used in attachment `preview` field: `"tag::blurhash"`. +enum BlurHashEncoder { + + /// Encodes a UIImage into a BlurHash string. + /// + /// - Parameters: + /// - image: Source image (will be downscaled internally for performance). + /// - numberOfComponents: AC components (x, y). Desktop uses (4, 4). + /// - Returns: BlurHash string, or `nil` if encoding fails. + static func encode(image: UIImage, numberOfComponents components: (Int, Int) = (4, 4)) -> String? { + let (componentX, componentY) = components + guard componentX >= 1, componentX <= 9, componentY >= 1, componentY <= 9 else { return nil } + + // Downscale for performance (BlurHash doesn't need high resolution) + let pixelWidth = 32 + let pixelHeight = 32 + + guard let cgImage = image.cgImage else { return nil } + let width = pixelWidth + let height = pixelHeight + + let bytesPerRow = width * 4 + var pixels = [UInt8](repeating: 0, count: bytesPerRow * height) + + guard let context = CGContext( + data: &pixels, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { return nil } + + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + + // Calculate DC and AC components + var factors = [(Float, Float, Float)]() + for j in 0.. 1 { + let actualMaximum = factors.dropFirst().reduce(Float(0)) { current, factor in + max(current, abs(factor.0), abs(factor.1), abs(factor.2)) + } + let quantisedMaximum = max(0, min(82, Int(floor(actualMaximum * 166 - 0.5)))) + maximumValue = Float(quantisedMaximum + 1) / 166 + // Size flag encodes number of components + max AC + let sizeFlag = (componentX - 1) + (componentY - 1) * 9 + + var result = "" + result += encode83(value: sizeFlag, length: 1) + result += encode83(value: quantisedMaximum, length: 1) + result += encode83(value: dcValue, length: 4) + + for factor in factors.dropFirst() { + result += encode83(value: encodeAC(factor, maximumValue: maximumValue), length: 2) + } + return result + } else { + let sizeFlag = (componentX - 1) + (componentY - 1) * 9 + var result = "" + result += encode83(value: sizeFlag, length: 1) + result += encode83(value: 0, length: 1) + result += encode83(value: dcValue, length: 4) + return result + } + } + + // MARK: - Basis Function + + private static func multiplyBasisFunction( + pixels: [UInt8], width: Int, height: Int, bytesPerRow: Int, + componentX: Int, componentY: Int + ) -> (Float, Float, Float) { + var r: Float = 0 + var g: Float = 0 + var b: Float = 0 + let normalization: Float = (componentX == 0 && componentY == 0) ? 1 : 2 + + for y in 0.. Linear + + private static func sRGBToLinear(_ value: UInt8) -> Float { + let v = Float(value) / 255 + if v <= 0.04045 { + return v / 12.92 + } else { + return pow((v + 0.055) / 1.055, 2.4) + } + } + + private static func linearToSRGB(_ value: Float) -> Int { + let v = max(0, min(1, value)) + if v <= 0.0031308 { + return Int(v * 12.92 * 255 + 0.5) + } else { + return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) + } + } + + // MARK: - DC / AC Encoding + + private static func encodeDC(_ value: (Float, Float, Float)) -> Int { + let r = linearToSRGB(value.0) + let g = linearToSRGB(value.1) + let b = linearToSRGB(value.2) + return (r << 16) + (g << 8) + b + } + + private static func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int { + let r = max(0, min(18, Int(floor(signPow(value.0 / maximumValue) * 9 + 9.5)))) + let g = max(0, min(18, Int(floor(signPow(value.1 / maximumValue) * 9 + 9.5)))) + let b = max(0, min(18, Int(floor(signPow(value.2 / maximumValue) * 9 + 9.5)))) + return r * 19 * 19 + g * 19 + b + } + + private static func signPow(_ value: Float) -> Float { + return copysign(pow(abs(value), 0.5), value) + } + + // MARK: - Base83 Encoding + + private static let base83Characters: [Character] = Array( + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" + ) + + private static func encode83(value: Int, length: Int) -> String { + var result = "" + for i in 1...length { + let digit = (value / pow83(length - i)) % 83 + result.append(base83Characters[digit]) + } + return result + } + + private static func pow83(_ exponent: Int) -> Int { + var result = 1 + for _ in 0.. String? { + return BlurHashEncoder.encode(image: self, numberOfComponents: components) + } +} diff --git a/Rosetta/Core/Utils/EmojiParser.swift b/Rosetta/Core/Utils/EmojiParser.swift new file mode 100644 index 0000000..3bccfc0 --- /dev/null +++ b/Rosetta/Core/Utils/EmojiParser.swift @@ -0,0 +1,70 @@ +import Foundation + +/// Cross-platform emoji shortcode parser. +/// +/// Desktop and Android use `:emoji_CODE:` format where CODE is the hex-encoded +/// unified Unicode codepoint (e.g., `:emoji_1f631:` β†’ 😱). +/// Multi-codepoint emoji use `-` separator (e.g., `:emoji_1f468-200d-1f4bb:` β†’ πŸ‘¨β€πŸ’»). +/// +/// Desktop reference: `TextParser.tsx` pattern `/:emoji_([a-zA-Z0-9_-]+):/` +/// Android reference: `AppleEmojiPicker.kt` β†’ `unifiedToEmoji()` +enum EmojiParser { + + /// Regex matching `:emoji_CODE:` shortcodes. + /// Matches hex codes with optional `-` separators for multi-codepoint sequences. + private static let shortcodePattern = try! NSRegularExpression( + pattern: ":emoji_([a-fA-F0-9]+(?:-[a-fA-F0-9]+)*):", + options: [] + ) + + /// Replaces all `:emoji_CODE:` shortcodes in the text with native Unicode emoji. + /// Returns the original text unchanged if no shortcodes are found. + static func replaceShortcodes(in text: String) -> String { + let range = NSRange(text.startIndex..., in: text) + let matches = shortcodePattern.matches(in: text, options: [], range: range) + + guard !matches.isEmpty else { return text } + + var result = text + // Process matches in reverse order so ranges remain valid after replacements. + for match in matches.reversed() { + guard let fullRange = Range(match.range, in: result), + let codeRange = Range(match.range(at: 1), in: result) else { continue } + + let unified = String(result[codeRange]) + let emoji = unifiedToEmoji(unified) + + if !emoji.isEmpty { + result.replaceSubrange(fullRange, with: emoji) + } + } + + return result + } + + /// Converts a unified hex code to a native emoji string. + /// Supports multi-codepoint sequences separated by `-`. + /// + /// Android parity: `unifiedToEmoji()` in `AppleEmojiPicker.kt`. + /// Examples: + /// - `"1f631"` β†’ "😱" + /// - `"1f468-200d-1f4bb"` β†’ "πŸ‘¨β€πŸ’»" + /// - `"1f1fa-1f1f8"` β†’ "πŸ‡ΊπŸ‡Έ" + static func unifiedToEmoji(_ unified: String) -> String { + unified + .split(separator: "-") + .compactMap { part -> String? in + guard let codePoint = UInt32(part, radix: 16), + let scalar = Unicode.Scalar(codePoint) else { return nil } + return String(scalar) + } + .joined() + } + + /// Converts a native emoji character to its `:emoji_CODE:` shortcode. + /// Used when inserting emoji from a picker into the message text. + static func emojiToShortcode(_ emoji: String) -> String { + let codes = emoji.unicodeScalars.map { String(format: "%x", $0.value) } + return ":emoji_\(codes.joined(separator: "-")):" + } +} diff --git a/Rosetta/Core/Utils/ReleaseNotes.swift b/Rosetta/Core/Utils/ReleaseNotes.swift new file mode 100644 index 0000000..d084b29 --- /dev/null +++ b/Rosetta/Core/Utils/ReleaseNotes.swift @@ -0,0 +1,56 @@ +import Foundation + +/// Desktop parity: version constants and release notes. +/// Desktop equivalent: `app/version.ts` β€” `APP_VERSION`, `RELEASE_NOTICE`. +enum ReleaseNotes { + + /// Current release notes entries, newest first. + /// Each entry contains a version string and a list of changes. + static let entries: [Entry] = [ + Entry( + version: appVersion, + changes: [ + "Performance optimizations for chat list rendering", + "Improved empty chat placeholder centering", + "Added release notes in Updates settings", + "Optimized dialog sort cache for faster UI updates", + "Draft messages with cross-session persistence", + "Chat pinning, muting, and swipe actions", + "Biometric authentication (Face ID / Touch ID)", + "Online status and typing indicators", + "Push notifications support", + "End-to-end encrypted messaging with XChaCha20-Poly1305" + ] + ) + ] + + /// Current app version from bundle (matches desktop `APP_VERSION`). + static var appVersion: String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" + } + + /// Current build number from bundle. + static var buildNumber: String { + Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1" + } + + /// Formatted release notice text for the current version (desktop parity: `RELEASE_NOTICE`). + /// Sent as a system message from the "Rosetta Updates" account. + static var releaseNoticeText: String { + guard let latest = entries.first else { return "" } + var lines = ["**Update v\(latest.version)**"] + for change in latest.changes { + lines.append("- \(change)") + } + return lines.joined(separator: "\n") + } + + // MARK: - Entry + + struct Entry: Identifiable { + let version: String + let changes: [String] + + var id: String { version } + } +} diff --git a/Rosetta/DesignSystem/Components/DeliveryCheckmark.swift b/Rosetta/DesignSystem/Components/DeliveryCheckmark.swift new file mode 100644 index 0000000..afeafa9 --- /dev/null +++ b/Rosetta/DesignSystem/Components/DeliveryCheckmark.swift @@ -0,0 +1,134 @@ +import SwiftUI + +// MARK: - Single Checkmark (Delivered) + +/// Single checkmark shape for delivery status. +/// Exact SVG path from design β€” renders at any size as a vector. +/// Original viewBox: 0 0 19 14, fill-rule: evenodd. +struct SingleCheckmarkShape: Shape { + func path(in rect: CGRect) -> Path { + let sx = rect.width / 19.0 + let sy = rect.height / 14.0 + + var path = Path() + + path.move(to: CGPoint(x: 17.9693 * sx, y: 1.30564 * sy)) + path.addCurve( + to: CGPoint(x: 17.9283 * sx, y: 0.20733 * sy), + control1: CGPoint(x: 18.261 * sx, y: 0.991184 * sy), + control2: CGPoint(x: 18.2427 * sx, y: 0.498997 * sy) + ) + path.addCurve( + to: CGPoint(x: 16.83 * sx, y: 0.248345 * sy), + control1: CGPoint(x: 17.6138 * sx, y: -0.0843367 * sy), + control2: CGPoint(x: 17.1217 * sx, y: -0.0661078 * sy) + ) + path.addLine(to: CGPoint(x: 6.04743 * sx, y: 11.8603 * sy)) + path.addLine(to: CGPoint(x: 1.36254 * sx, y: 6.39613 * sy)) + path.addCurve( + to: CGPoint(x: 0.268786 * sx, y: 6.3141 * sy), + control1: CGPoint(x: 1.08454 * sx, y: 6.07256 * sy), + control2: CGPoint(x: 0.596911 * sx, y: 6.03611 * sy) + ) + path.addCurve( + to: CGPoint(x: 0.186756 * sx, y: 7.40785 * sy), + control1: CGPoint(x: -0.0547816 * sx, y: 6.5921 * sy), + control2: CGPoint(x: -0.0912399 * sx, y: 7.07973 * sy) + ) + path.addLine(to: CGPoint(x: 5.43676 * sx, y: 13.5329 * sy)) + path.addCurve( + to: CGPoint(x: 6.01097 * sx, y: 13.8017 * sy), + control1: CGPoint(x: 5.57803 * sx, y: 13.7015 * sy), + control2: CGPoint(x: 5.78767 * sx, y: 13.7972 * sy) + ) + path.addCurve( + to: CGPoint(x: 6.59431 * sx, y: 13.5556 * sy), + control1: CGPoint(x: 6.22972 * sx, y: 13.8063 * sy), + control2: CGPoint(x: 6.44392 * sx, y: 13.7151 * sy) + ) + path.addLine(to: CGPoint(x: 17.9693 * sx, y: 1.30564 * sy)) + path.closeSubpath() + + return path + } +} + +// MARK: - Double Checkmarks (Read) + +/// Double checkmark shape for read status. +/// Exact SVG path from design β€” two overlapping checkmarks. +/// Original viewBox: 0 0 22 12, fill-rule: evenodd. +struct DoubleCheckmarkShape: Shape { + func path(in rect: CGRect) -> Path { + let sx = rect.width / 22.0 + let sy = rect.height / 12.0 + + var path = Path() + + // Left checkmark + path.move(to: CGPoint(x: 16.0745 * sx, y: 1.13501 * sy)) + path.addCurve( + to: CGPoint(x: 16.0378 * sx, y: 0.180235 * sy), + control1: CGPoint(x: 16.3354 * sx, y: 0.86165 * sy), + control2: CGPoint(x: 16.3191 * sx, y: 0.433785 * sy) + ) + path.addCurve( + to: CGPoint(x: 15.0553 * sx, y: 0.21589 * sy), + control1: CGPoint(x: 15.7565 * sx, y: -0.0733151 * sy), + control2: CGPoint(x: 15.3162 * sx, y: -0.0574685 * sy) + ) + path.addLine(to: CGPoint(x: 5.40975 * sx, y: 10.3103 * sy)) + path.addLine(to: CGPoint(x: 1.21886 * sx, y: 5.56025 * sy)) + path.addCurve( + to: CGPoint(x: 0.240444 * sx, y: 5.48894 * sy), + control1: CGPoint(x: 0.97018 * sx, y: 5.27897 * sy), + control2: CGPoint(x: 0.533969 * sx, y: 5.24727 * sy) + ) + path.addCurve( + to: CGPoint(x: 0.167063 * sx, y: 6.43975 * sy), + control1: CGPoint(x: -0.049005 * sx, y: 5.7306 * sy), + control2: CGPoint(x: -0.0816189 * sx, y: 6.15451 * sy) + ) + path.addLine(to: CGPoint(x: 4.86346 * sx, y: 11.7643 * sy)) + path.addCurve( + to: CGPoint(x: 5.37713 * sx, y: 11.998 * sy), + control1: CGPoint(x: 4.98984 * sx, y: 11.9109 * sy), + control2: CGPoint(x: 5.17737 * sx, y: 11.9941 * sy) + ) + path.addCurve( + to: CGPoint(x: 5.89895 * sx, y: 11.7841 * sy), + control1: CGPoint(x: 5.57281 * sx, y: 12.002 * sy), + control2: CGPoint(x: 5.76442 * sx, y: 11.9228 * sy) + ) + path.addLine(to: CGPoint(x: 16.0745 * sx, y: 1.13501 * sy)) + path.closeSubpath() + + // Right checkmark + path.move(to: CGPoint(x: 21.8145 * sx, y: 1.13501 * sy)) + path.addCurve( + to: CGPoint(x: 21.7778 * sx, y: 0.180235 * sy), + control1: CGPoint(x: 22.0754 * sx, y: 0.86165 * sy), + control2: CGPoint(x: 22.0591 * sx, y: 0.433785 * sy) + ) + path.addCurve( + to: CGPoint(x: 20.7953 * sx, y: 0.21589 * sy), + control1: CGPoint(x: 21.4965 * sx, y: -0.0733151 * sy), + control2: CGPoint(x: 21.0563 * sx, y: -0.0574685 * sy) + ) + path.addLine(to: CGPoint(x: 10.6198 * sx, y: 10.865 * sy)) + path.addCurve( + to: CGPoint(x: 10.6565 * sx, y: 11.8198 * sy), + control1: CGPoint(x: 10.3589 * sx, y: 11.1383 * sy), + control2: CGPoint(x: 10.3752 * sx, y: 11.5662 * sy) + ) + path.addCurve( + to: CGPoint(x: 11.639 * sx, y: 11.7841 * sy), + control1: CGPoint(x: 10.9378 * sx, y: 12.0733 * sy), + control2: CGPoint(x: 11.3781 * sx, y: 12.0575 * sy) + ) + path.addLine(to: CGPoint(x: 21.8145 * sx, y: 1.13501 * sy)) + path.closeSubpath() + + return path + } +} diff --git a/Rosetta/DesignSystem/Components/VerifiedBadge.swift b/Rosetta/DesignSystem/Components/VerifiedBadge.swift index ab48fbd..3c6655e 100644 --- a/Rosetta/DesignSystem/Components/VerifiedBadge.swift +++ b/Rosetta/DesignSystem/Components/VerifiedBadge.swift @@ -2,13 +2,12 @@ import SwiftUI // MARK: - VerifiedBadge -/// Displays a verified rosette badge for accounts with server-assigned verification. +/// Displays a verified badge for accounts with server-assigned verification. /// -/// Verification levels (from server `verified` field): -/// - `0` β€” not verified (badge hidden) -/// - `1` β€” public figure, brand, or organization -/// - `2` β€” official Rosetta administration account -/// - `3+` β€” group administrator +/// Uses Tabler Icons SVG paths for desktop parity: +/// - Level 1: rosette with checkmark (blue) β€” `IconRosetteDiscountCheckFilled` +/// - Level 2: shield with checkmark (green) β€” `IconShieldCheckFilled` +/// - Level 3+: shield with checkmark (blue) β€” group admin /// /// Tapping the badge presents a dialog explaining the verification level. struct VerifiedBadge: View { @@ -21,9 +20,9 @@ struct VerifiedBadge: View { var body: some View { if verified > 0 { - Image(systemName: iconName) - .font(.system(size: size)) - .foregroundStyle(resolvedColor) + SVGPathShape(pathData: iconPath, viewBox: CGSize(width: 24, height: 24)) + .fill(resolvedColor) + .frame(width: size, height: size) .onTapGesture { showExplanation = true } .accessibilityLabel("Verified account") .alert("Verified Account", isPresented: $showExplanation) { @@ -38,15 +37,15 @@ struct VerifiedBadge: View { /// Desktop parity: different icon per verification level. /// Level 1 = rosette (public figure), Level 2 = shield (Rosetta admin), - /// Level 3+ = arrow badge (group admin). - private var iconName: String { + /// Level 3+ = shield (group admin). + private var iconPath: String { switch verified { case 2: - return "checkmark.shield.fill" + return TablerIconPath.shieldCheckFilled case 3...: - return "arrow.down.app.fill" + return TablerIconPath.shieldCheckFilled default: - return "checkmark.seal.fill" + return TablerIconPath.rosetteDiscountCheckFilled } } @@ -54,13 +53,11 @@ struct VerifiedBadge: View { private var resolvedColor: Color { if let badgeTint { return badgeTint } if verified == 2 { - return colorScheme == .dark - ? RosettaColors.success // green - : RosettaColors.success + return RosettaColors.success // green } return colorScheme == .dark - ? RosettaColors.primaryBlue // #248AE6 - : Color(hex: 0xACD2F9) // soft blue (light theme) + ? RosettaColors.primaryBlue // #248AE6 + : Color(hex: 0xACD2F9) // soft blue (light theme) } private var annotationText: String { @@ -75,22 +72,40 @@ struct VerifiedBadge: View { } } +// MARK: - Tabler Icon Paths + +/// SVG path data from Tabler Icons β€” exact match with desktop's verified badge icons. +/// Desktop reference: `@tabler/icons-react` β†’ `IconRosetteDiscountCheckFilled`, `IconShieldCheckFilled`. +/// ViewBox: 24Γ—24 for both icons. +private enum TablerIconPath { + + /// Rosette with checkmark β€” verification level 1 (public figure/brand/organization). + /// Desktop: `IconRosetteDiscountCheckFilled` from `@tabler/icons-react`. + static let rosetteDiscountCheckFilled = "M12.01 2.011a3.2 3.2 0 0 1 2.113 .797l.154 .145l.698 .698a1.2 1.2 0 0 0 .71 .341l.135 .008h1a3.2 3.2 0 0 1 3.195 3.018l.005 .182v1c0 .27 .092 .533 .258 .743l.09 .1l.697 .698a3.2 3.2 0 0 1 .147 4.382l-.145 .154l-.698 .698a1.2 1.2 0 0 0 -.341 .71l-.008 .135v1a3.2 3.2 0 0 1 -3.018 3.195l-.182 .005h-1a1.2 1.2 0 0 0 -.743 .258l-.1 .09l-.698 .697a3.2 3.2 0 0 1 -4.382 .147l-.154 -.145l-.698 -.698a1.2 1.2 0 0 0 -.71 -.341l-.135 -.008h-1a3.2 3.2 0 0 1 -3.195 -3.018l-.005 -.182v-1a1.2 1.2 0 0 0 -.258 -.743l-.09 -.1l-.697 -.698a3.2 3.2 0 0 1 -.147 -4.382l.145 -.154l.698 -.698a1.2 1.2 0 0 0 .341 -.71l.008 -.135v-1l.005 -.182a3.2 3.2 0 0 1 3.013 -3.013l.182 -.005h1a1.2 1.2 0 0 0 .743 -.258l.1 -.09l.698 -.697a3.2 3.2 0 0 1 2.269 -.944zm3.697 7.282a1 1 0 0 0 -1.414 0l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.32 1.497l2 2l.094 .083a1 1 0 0 0 1.32 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" + + /// Shield with checkmark β€” verification level 2 (Rosetta administration). + /// Desktop: `IconShieldCheckFilled` from `@tabler/icons-react`. + static let shieldCheckFilled = "M11.998 2l.118 .007l.059 .008l.061 .013l.111 .034a.993 .993 0 0 1 .217 .112l.104 .082l.255 .218a11 11 0 0 0 7.189 2.537l.342 -.01a1 1 0 0 1 1.005 .717a13 13 0 0 1 -9.208 16.25a1 1 0 0 1 -.502 0a13 13 0 0 1 -9.209 -16.25a1 1 0 0 1 1.005 -.717a11 11 0 0 0 7.531 -2.527l.263 -.225l.096 -.075a.993 .993 0 0 1 .217 -.112l.112 -.034a.97 .97 0 0 1 .119 -.021l.115 -.007zm3.71 7.293a1 1 0 0 0 -1.415 0l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.32 1.497l2 2l.094 .083a1 1 0 0 0 1.32 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" +} + // MARK: - Preview #Preview { VStack(spacing: 16) { HStack { - Text("Level 1") - VerifiedBadge(verified: 1, size: 16) + Text("Level 1 (rosette)") + VerifiedBadge(verified: 1, size: 20) } HStack { - Text("Level 2") + Text("Level 2 (shield)") VerifiedBadge(verified: 2, size: 20) } HStack { Text("Not verified") - VerifiedBadge(verified: 0, size: 16) + VerifiedBadge(verified: 0, size: 20) } } .padding() + .background(Color.black) + .foregroundStyle(.white) } diff --git a/Rosetta/DesignSystem/SVGPath.swift b/Rosetta/DesignSystem/SVGPath.swift new file mode 100644 index 0000000..5b15fdb --- /dev/null +++ b/Rosetta/DesignSystem/SVGPath.swift @@ -0,0 +1,376 @@ +import SwiftUI + +// MARK: - SVGPathShape + +/// A SwiftUI Shape that renders an SVG path string within the given rect. +/// Scales the path from the original viewBox to fit the rect. +struct SVGPathShape: Shape { + let pathData: String + let viewBox: CGSize + + func path(in rect: CGRect) -> Path { + guard viewBox.width > 0, viewBox.height > 0 else { return Path() } + var parser = SVGPathParser(pathData: pathData) + var output = Path(parser.parse()) + output = output.applying(.init( + scaleX: rect.width / viewBox.width, + y: rect.height / viewBox.height + )) + return output + } +} + +// MARK: - SVGPathToken + +enum SVGPathToken { + case command(Character) + case number(CGFloat) +} + +// MARK: - SVGPathTokenizer + +/// Converts an SVG path data string into a sequence of command and number tokens. +/// Handles decimal points, negative signs, scientific notation, and implicit separators +/// (consecutive decimals like `.5.3` are split into `0.5` and `0.3`). +struct SVGPathTokenizer { + static func tokenize(_ source: String) -> [SVGPathToken] { + var tokens: [SVGPathToken] = [] + let chars = Array(source) + var index = 0 + + while index < chars.count { + let ch = chars[index] + + if ch.isWhitespace || ch == "," { index += 1; continue } + if ch.isLetter { tokens.append(.command(ch)); index += 1; continue } + + if ch.isNumber || ch == "-" || ch == "+" || ch == "." { + let start = index + var hasDot = (ch == ".") + index += 1 + + while index < chars.count { + let c = chars[index] + let prev = chars[index - 1] + + if c.isNumber { index += 1; continue } + + // Second decimal point starts a new number (e.g., `.5.3` β†’ `0.5`, `0.3`) + if c == "." { + if hasDot { break } + hasDot = true + index += 1 + continue + } + + if c == "e" || c == "E" { index += 1; continue } + if (c == "-" || c == "+"), (prev == "e" || prev == "E") { index += 1; continue } + break + } + + let fragment = String(chars[start.. CGPath { + while index < tokens.count { + let command = readCommandOrReuse() + switch command { + case "M", "m": parseMove(command) + case "L", "l": parseLine(command) + case "H", "h": parseHorizontal(command) + case "V", "v": parseVertical(command) + case "C", "c": parseCubic(command) + case "A", "a": parseArc(command) + case "Z", "z": + cgPath.closeSubpath() + current = subpathStart + default: + skipToNextCommand() + } + } + return cgPath.copy() ?? CGMutablePath() + } + + // MARK: - Token Reading + + private var isAtCommand: Bool { + guard index < tokens.count else { return false } + if case .command = tokens[index] { return true } + return false + } + + private mutating func readCommandOrReuse() -> Character { + guard index < tokens.count else { return lastCommand } + if case let .command(command) = tokens[index] { + index += 1 + lastCommand = command + return command + } + return lastCommand + } + + private mutating func readNumber() -> CGFloat? { + guard index < tokens.count else { return nil } + if case let .number(value) = tokens[index] { + index += 1 + return value + } + return nil + } + + private func resolvedPoint(x: CGFloat, y: CGFloat, relative: Bool) -> CGPoint { + relative ? CGPoint(x: current.x + x, y: current.y + y) : CGPoint(x: x, y: y) + } + + private mutating func readPoint(relative: Bool) -> CGPoint? { + guard let x = readNumber(), let y = readNumber() else { return nil } + return resolvedPoint(x: x, y: y, relative: relative) + } + + // MARK: - Command Parsers + + private mutating func parseMove(_ command: Character) { + let relative = command.isLowercase + guard let first = readPoint(relative: relative) else { return } + cgPath.move(to: first) + current = first + subpathStart = first + + // Subsequent coordinate pairs after M/m are treated as L/l + while !isAtCommand, let point = readPoint(relative: relative) { + cgPath.addLine(to: point) + current = point + } + + lastCommand = relative ? "l" : "L" + } + + private mutating func parseLine(_ command: Character) { + let relative = command.isLowercase + while !isAtCommand, let point = readPoint(relative: relative) { + cgPath.addLine(to: point) + current = point + } + } + + private mutating func parseHorizontal(_ command: Character) { + let relative = command.isLowercase + while !isAtCommand, let value = readNumber() { + current = CGPoint(x: relative ? current.x + value : value, y: current.y) + cgPath.addLine(to: current) + } + } + + private mutating func parseVertical(_ command: Character) { + let relative = command.isLowercase + while !isAtCommand, let value = readNumber() { + current = CGPoint(x: current.x, y: relative ? current.y + value : value) + cgPath.addLine(to: current) + } + } + + private mutating func parseCubic(_ command: Character) { + let relative = command.isLowercase + while !isAtCommand { + guard let x1 = readNumber(), + let y1 = readNumber(), + let x2 = readNumber(), + let y2 = readNumber(), + let x = readNumber(), + let y = readNumber() + else { return } + + let c1 = resolvedPoint(x: x1, y: y1, relative: relative) + let c2 = resolvedPoint(x: x2, y: y2, relative: relative) + let end = resolvedPoint(x: x, y: y, relative: relative) + + cgPath.addCurve(to: end, control1: c1, control2: c2) + current = end + } + } + + // MARK: - Arc + + /// Parses SVG elliptical arc commands (A/a). + /// Parameters: rx ry x-rotation large-arc-flag sweep-flag x y + private mutating func parseArc(_ command: Character) { + let relative = command.isLowercase + while !isAtCommand { + guard let rxRaw = readNumber(), + let ryRaw = readNumber(), + let xRotationDeg = readNumber(), + let largeArcFlag = readNumber(), + let sweepFlag = readNumber(), + let x = readNumber(), + let y = readNumber() + else { return } + + let end = resolvedPoint(x: x, y: y, relative: relative) + arcToBezier( + from: current, + to: end, + rx: abs(rxRaw), + ry: abs(ryRaw), + xRotation: xRotationDeg * .pi / 180, + largeArc: largeArcFlag != 0, + sweep: sweepFlag != 0 + ) + current = end + } + } + + /// Converts an SVG elliptical arc to cubic BΓ©zier curves. + /// + /// Uses the endpoint-to-center parameterization from the W3C SVG spec: + /// https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter + /// + /// Splits arcs into ≀90Β° segments and approximates each with a cubic BΓ©zier. + private mutating func arcToBezier( + from p1: CGPoint, + to p2: CGPoint, + rx rxIn: CGFloat, + ry ryIn: CGFloat, + xRotation phi: CGFloat, + largeArc: Bool, + sweep: Bool + ) { + // Degenerate: identical endpoints + if p1.x == p2.x && p1.y == p2.y { return } + + // Degenerate: zero radius β†’ straight line + if rxIn == 0 || ryIn == 0 { + cgPath.addLine(to: p2) + return + } + + var rx = rxIn + var ry = ryIn + let cosPhi = cos(phi) + let sinPhi = sin(phi) + + // Step 1: Compute (x1β€², y1β€²) + let dx = (p1.x - p2.x) / 2 + let dy = (p1.y - p2.y) / 2 + let x1p = cosPhi * dx + sinPhi * dy + let y1p = -sinPhi * dx + cosPhi * dy + + // Ensure radii are large enough + let x1p2 = x1p * x1p + let y1p2 = y1p * y1p + var rx2 = rx * rx + var ry2 = ry * ry + let lambda = x1p2 / rx2 + y1p2 / ry2 + if lambda > 1 { + let s = sqrt(lambda) + rx *= s; ry *= s + rx2 = rx * rx; ry2 = ry * ry + } + + // Step 2: Compute (cxβ€², cyβ€²) + let num = max(0, rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) + let den = rx2 * y1p2 + ry2 * x1p2 + var sq: CGFloat = den > 0 ? sqrt(num / den) : 0 + if largeArc == sweep { sq = -sq } + let cxp = sq * rx * y1p / ry + let cyp = -sq * ry * x1p / rx + + // Step 3: Compute center (cx, cy) from (cxβ€², cyβ€²) + let cx = cosPhi * cxp - sinPhi * cyp + (p1.x + p2.x) / 2 + let cy = sinPhi * cxp + cosPhi * cyp + (p1.y + p2.y) / 2 + + // Step 4: Compute θ₁ and Δθ + let theta1 = vectorAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry) + var dtheta = vectorAngle( + (x1p - cxp) / rx, (y1p - cyp) / ry, + (-x1p - cxp) / rx, (-y1p - cyp) / ry + ) + if !sweep && dtheta > 0 { dtheta -= 2 * .pi } + if sweep && dtheta < 0 { dtheta += 2 * .pi } + + // Split into ≀90Β° segments and approximate each with cubic BΓ©zier + let segments = max(1, Int(ceil(abs(dtheta) / (.pi / 2)))) + let segAngle = dtheta / CGFloat(segments) + + for i in 0.. CGFloat { + let dot = ux * vx + uy * vy + let len = sqrt(ux * ux + uy * uy) * sqrt(vx * vx + vy * vy) + guard len > 0 else { return 0 } + var a = acos(max(-1, min(1, dot / len))) + if ux * vy - uy * vx < 0 { a = -a } + return a + } + + private mutating func skipToNextCommand() { + while index < tokens.count { + if case .command = tokens[index] { return } + index += 1 + } + } +} diff --git a/Rosetta/Features/Auth/SetPasswordView.swift b/Rosetta/Features/Auth/SetPasswordView.swift index 298fe47..2ec8253 100644 --- a/Rosetta/Features/Auth/SetPasswordView.swift +++ b/Rosetta/Features/Auth/SetPasswordView.swift @@ -298,6 +298,7 @@ private struct SecureToggleField: UIViewRepresentable { tf.spellCheckingType = .no tf.textContentType = .init(rawValue: "") tf.inputAccessoryView = UIView(frame: .zero) + tf.returnKeyType = .done tf.backgroundColor = .clear tf.setContentHuggingPriority(.required, for: .vertical) tf.setContentCompressionResistancePriority(.required, for: .vertical) @@ -390,6 +391,11 @@ private struct SecureToggleField: UIViewRepresentable { } } + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + func textFieldDidBeginEditing(_ textField: UITextField) { parent.focusedField = parent.field } diff --git a/Rosetta/Features/Chats/ChatDetail/AttachmentPanelView.swift b/Rosetta/Features/Chats/ChatDetail/AttachmentPanelView.swift new file mode 100644 index 0000000..be49052 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/AttachmentPanelView.swift @@ -0,0 +1,716 @@ +import SwiftUI +import Photos +import PhotosUI + +// MARK: - AttachmentPanelView + +/// Figma-style bottom sheet for selecting photos and files to attach to a message. +/// +/// Figma nodes: 3994:39103 (panel), 4758:50706 (tab bar). +/// +/// Structure: +/// - Toolbar: X button (left) + "Recents" title (centered) +/// - Content: Photo grid (Gallery) or File picker (File) +/// - Tab bar: [Gallery] [File] [Avatar] β€” glass capsule background +/// - Send button: appears when items selected, shows count +/// +/// Desktop parity: `DialogInput.tsx` attachment menu (paperclip) + file dialog. +struct AttachmentPanelView: View { + + let onSend: ([PendingAttachment], String) -> Void + let onSendAvatar: () -> Void + + @Environment(\.dismiss) private var dismiss + + @State private var selectedTab: AttachmentTab = .gallery + @State private var selectedAssets: [PHAsset] = [] + @State private var showCamera = false + @State private var showFilePicker = false + @State private var capturedImage: UIImage? + @State private var captionText: String = "" + @State private var previewAsset: IdentifiableAsset? + + private var hasSelection: Bool { !selectedAssets.isEmpty } + + var body: some View { + ZStack(alignment: .bottom) { + // Dark surface background (#1C1C1E β€” NOT pure black, so sheet rounded + // corners are visible against the app's black background behind) + Color(hex: 0x1C1C1E).ignoresSafeArea() + + VStack(spacing: 0) { + // Grabber + Toolbar + toolbar + + // Content + switch selectedTab { + case .gallery: + PhotoGridView( + selectedAssets: $selectedAssets, + maxSelection: PendingAttachment.maxAttachmentsPerMessage, + onCameraTap: { showCamera = true }, + onPhotoPreview: { asset in + previewAsset = IdentifiableAsset(asset: asset) + } + ) + case .file: + fileTabContent + case .avatar: + // Avatar is an action tab β€” handled in tabButton tap + Spacer() + } + + Spacer(minLength: 0) + } + + // Bottom: Tab bar + Send button + bottomBar + } + .sheet(isPresented: $showCamera) { + CameraPickerView { image in + capturedImage = image + handleCapturedImage(image) + } + .ignoresSafeArea() + } + .sheet(isPresented: $showFilePicker) { + DocumentPickerView { urls in + handlePickedFiles(urls) + } + } + .fullScreenCover(item: $previewAsset) { item in + PhotoPreviewView( + asset: item.asset, + isSelected: selectedAssets.contains(where: { $0.localIdentifier == item.id }), + selectionNumber: selectedAssets.firstIndex(where: { $0.localIdentifier == item.id }).map { $0 + 1 }, + captionText: $captionText, + onSend: { image in + let caption = captionText + let attachment = PendingAttachment.fromImage(image) + onSend([attachment], caption) + dismiss() + }, + onToggleSelect: { + if let idx = selectedAssets.firstIndex(where: { $0.localIdentifier == item.id }) { + selectedAssets.remove(at: idx) + } else if selectedAssets.count < PendingAttachment.maxAttachmentsPerMessage { + selectedAssets.append(item.asset) + } + } + ) + .background(TransparentFullScreenBackground()) + } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.hidden) + .attachmentCornerRadius(20) + .preferredColorScheme(.dark) + } + + // MARK: - Toolbar (Telegram-style: dark surface header) + + /// Dark surface header matching Telegram attachment panel. + /// Custom grabber + 44pt close button (Figma: node 4764-50752) + centered title. + private var toolbar: some View { + VStack(spacing: 0) { + // Custom drag indicator (replaces system .presentationDragIndicator) + Capsule() + .fill(Color.white.opacity(0.3)) + .frame(width: 36, height: 5) + .padding(.top, 5) + .padding(.bottom, 14) + + // Close button + centered title + HStack { + // Close button (same glass style as GlassBackButton, but with X icon) + Button { + dismiss() + } label: { + CloseIconShape() + .fill(.white) + .frame(width: 14, height: 14) + .frame(width: 44, height: 44) + } + .background { closeButtonGlass } + .clipShape(Circle()) + + Spacer() + + // Title with dropdown chevron (Telegram-style, ~20pt semibold) + HStack(spacing: 5) { + Text(tabTitle) + .font(.system(size: 20, weight: .semibold)) + .foregroundStyle(.white) + Image(systemName: "chevron.down") + .font(.system(size: 13, weight: .bold)) + .foregroundStyle(.white.opacity(0.45)) + } + + Spacer() + + // Right side: selection badge (deselects all) or invisible spacer + if hasSelection { + Button { + withAnimation(.easeInOut(duration: 0.25)) { + selectedAssets.removeAll() + } + } label: { + selectionBadge + } + .transition(.scale.combined(with: .opacity)) + } else { + // Invisible spacer to balance close button width (keeps title centered) + Color.clear + .frame(width: 44, height: 44) + } + } + .padding(.horizontal, 8) + .padding(.bottom, 10) + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: hasSelection) + } + } + + /// Selection count badge β€” blue capsule with βœ“ + count. Tapping deselects all. + private var selectionBadge: some View { + HStack(spacing: 2) { + Image(systemName: "checkmark") + .font(.system(size: 11, weight: .bold)) + Text("\(selectedAssets.count)") + .font(.system(size: 12, weight: .bold)) + } + .foregroundStyle(.white) + .frame(width: 44, height: 28) + .background(Color(hex: 0x008BFF), in: Capsule()) + } + + /// Glass circle background matching GlassBackButton (ButtonStyles.swift lines 22–34). + @ViewBuilder + private var closeButtonGlass: some View { + if #available(iOS 26, *) { + Circle() + .fill(Color.white.opacity(0.08)) + .glassEffect(.regular, in: .circle) + } else { + Circle() + .fill(.thinMaterial) + .overlay { Circle().strokeBorder(Color.white.opacity(0.18), lineWidth: 0.5) } + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } + + /// Title text changes based on selected tab. + private var tabTitle: String { + switch selectedTab { + case .gallery: return "Recents" + case .file: return "File" + case .avatar: return "Avatar" + } + } + + // MARK: - File Tab Content + + private var fileTabContent: some View { + VStack(spacing: 20) { + Spacer() + Image(systemName: "doc.fill") + .font(.system(size: 48)) + .foregroundStyle(.white.opacity(0.3)) + + Text("Select a file to send") + .font(.system(size: 16)) + .foregroundStyle(.white.opacity(0.5)) + + Button { + showFilePicker = true + } label: { + Text("Browse Files") + .font(.system(size: 15, weight: .medium)) + .foregroundStyle(.white) + .padding(.horizontal, 24) + .padding(.vertical, 10) + .background(Color(hex: 0x008BFF), in: Capsule()) + } + + Spacer() + } + .frame(maxWidth: .infinity) + .task { + // Auto-open file picker when File tab is selected + try? await Task.sleep(for: .milliseconds(300)) + showFilePicker = true + } + } + + // MARK: - Bottom Bar + + private var bottomBar: some View { + VStack(spacing: 0) { + if hasSelection { + // Caption input bar (replaces tab bar when photos selected) + captionInputBar + .padding(.horizontal, 16) + .padding(.bottom, 12) + .transition(.opacity.combined(with: .move(edge: .bottom))) + } else { + // Tab bar (Figma: node 4758:50706 β€” glass capsule) + tabBar + .padding(.horizontal, 25) + .padding(.bottom, 12) + .transition(.opacity.combined(with: .move(edge: .bottom))) + } + } + .animation(.easeInOut(duration: 0.25), value: hasSelection) + .background( + LinearGradient( + stops: [ + .init(color: .clear, location: 0), + .init(color: .black.opacity(0.6), location: 0.3), + .init(color: .black, location: 0.8), + ], + startPoint: .top, + endPoint: .bottom + ) + .ignoresSafeArea(edges: .bottom) + ) + } + + // MARK: - Caption Input Bar (matches ChatDetail composer style) + + /// Caption input bar shown when photos are selected. + /// Matches ChatDetailView's composer capsule: `.thinMaterial` glass rounded rect. + /// Layout: [text field] [emoji] [send button] + private var captionInputBar: some View { + HStack(spacing: 0) { + // Caption text field + TextField("Add a caption...", text: $captionText) + .font(.system(size: 16)) + .foregroundStyle(.white) + .tint(Color(hex: 0x008BFF)) + .padding(.leading, 6) + + // Emoji icon (exact ChatDetail match: TelegramVectorIcon emojiMoon) + TelegramVectorIcon( + pathData: TelegramIconPath.emojiMoon, + viewBox: CGSize(width: 19, height: 19), + color: RosettaColors.Adaptive.textSecondary + ) + .frame(width: 19, height: 19) + .frame(width: 20, height: 36) + .padding(.trailing, 8) + + // Send button (exact ChatDetail match: 38Γ—36 capsule with sendPlane icon) + Button { + sendSelectedPhotos() + } label: { + TelegramVectorIcon( + pathData: TelegramIconPath.sendPlane, + viewBox: CGSize(width: 22, height: 19), + color: .white + ) + .frame(width: 22, height: 19) + .frame(width: 38, height: 36) + .background { Capsule().fill(Color(hex: 0x008BFF)) } + } + } + .padding(3) + .frame(minHeight: 42, alignment: .bottom) + .background { captionBarBackground } + } + + /// Glass background matching ChatDetailView's composer (`.thinMaterial` + stroke + shadow). + @ViewBuilder + private var captionBarBackground: some View { + if #available(iOS 26, *) { + RoundedRectangle(cornerRadius: 21, style: .continuous) + .fill(.clear) + .glassEffect(.regular, in: RoundedRectangle(cornerRadius: 21, style: .continuous)) + } else { + let shape = RoundedRectangle(cornerRadius: 21, style: .continuous) + shape.fill(.thinMaterial) + .overlay { shape.strokeBorder(Color.white.opacity(0.18), lineWidth: 0.5) } + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } + + // MARK: - Tab Bar (Figma: glass capsule, 3 tabs) + + /// Glass capsule tab bar matching RosettaTabBar pattern. + /// Tabs: Gallery | File | Avatar. + /// Colors from RosettaTabBar: selected=#008BFF, unselected=white. + /// Background: .regularMaterial (iOS < 26) / .glassEffect (iOS 26+). + private var tabBar: some View { + HStack(spacing: 0) { + tabButton(.gallery, icon: "photo.fill", label: "Gallery") + tabButton(.file, icon: "doc.fill", label: "File") + tabButton(.avatar, icon: "person.crop.circle.fill", label: "Avatar") + } + .padding(4) + .background { tabBarBackground } + .clipShape(Capsule()) + .tabBarShadow() + } + + /// Glass background matching RosettaTabBar (lines 136–149). + @ViewBuilder + private var tabBarBackground: some View { + if #available(iOS 26, *) { + // iOS 26+ β€” native liquid glass + Capsule() + .fill(.clear) + .glassEffect(.regular, in: .capsule) + } else { + // iOS < 26 β€” frosted glass material (matches RosettaTabBar) + Capsule() + .fill(.regularMaterial) + .overlay( + Capsule() + .strokeBorder(Color.white.opacity(0.08), lineWidth: 0.5) + ) + } + } + + /// Individual tab button matching RosettaTabBar dimensions exactly. + /// Icon: 22pt regular (frame height 28), Label: 10pt, VStack spacing: 2, padding: 6pt. + private func tabButton(_ tab: AttachmentTab, icon: String, label: String) -> some View { + let isSelected = selectedTab == tab + + return Button { + if tab == .avatar { + // Avatar is an action tab β€” immediately sends avatar + dismisses + onSendAvatar() + dismiss() + } else { + withAnimation(.easeInOut(duration: 0.2)) { + selectedTab = tab + } + } + } label: { + VStack(spacing: 2) { + Image(systemName: icon) + .font(.system(size: 22, weight: .regular)) + .frame(height: 28) + Text(label) + .font(.system(size: 10, weight: isSelected ? .bold : .medium)) + } + // RosettaTabBar colors: selected=#008BFF, unselected=white + .foregroundStyle(isSelected ? Color(hex: 0x008BFF) : .white) + .frame(minWidth: 66, maxWidth: .infinity) + .padding(.vertical, 6) + .background( + // Selected tab: thin material pill (matches RosettaTabBar selection style) + isSelected + ? AnyShapeStyle(.thinMaterial) + : AnyShapeStyle(.clear), + in: Capsule() + ) + } + .buttonStyle(.plain) + } + + // MARK: - Actions + + private func sendSelectedPhotos() { + let assets = selectedAssets + let caption = captionText + let manager = PHImageManager.default() + + Task { + var attachments: [PendingAttachment] = [] + for asset in assets { + if let image = await loadFullImage(asset: asset, manager: manager) { + attachments.append(PendingAttachment.fromImage(image)) + } + } + await MainActor.run { + onSend(attachments, caption) + dismiss() + } + } + } + + private func handleCapturedImage(_ image: UIImage) { + let attachment = PendingAttachment.fromImage(image) + onSend([attachment], "") + dismiss() + } + + private func handlePickedFiles(_ urls: [URL]) { + var attachments: [PendingAttachment] = [] + for url in urls.prefix(PendingAttachment.maxAttachmentsPerMessage) { + guard url.startAccessingSecurityScopedResource() else { continue } + defer { url.stopAccessingSecurityScopedResource() } + + if let data = try? Data(contentsOf: url) { + attachments.append(PendingAttachment.fromFile( + data: data, + fileName: url.lastPathComponent + )) + } + } + if !attachments.isEmpty { + onSend(attachments, "") + dismiss() + } + } + + private func loadFullImage(asset: PHAsset, manager: PHImageManager) async -> UIImage? { + await withCheckedContinuation { continuation in + let options = PHImageRequestOptions() + options.deliveryMode = .highQualityFormat + options.isNetworkAccessAllowed = true + options.isSynchronous = false + + manager.requestImage( + for: asset, + targetSize: PHImageManagerMaximumSize, + contentMode: .default, + options: options + ) { image, info in + let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false + if !isDegraded { + continuation.resume(returning: image) + } + } + } + } +} + +// MARK: - AttachmentTab + +private enum AttachmentTab: Hashable { + case gallery + case file + case avatar +} + +// MARK: - IdentifiableAsset + +/// Wrapper to make PHAsset usable with SwiftUI `.fullScreenCover(item:)`. +struct IdentifiableAsset: Identifiable { + let id: String + let asset: PHAsset + + init(asset: PHAsset) { + self.id = asset.localIdentifier + self.asset = asset + } +} + +// MARK: - Close Icon (Figma SVG: node 4764-50752) + +/// Custom X icon matching Figma close button design. +/// SVG source: 20Γ—20 viewport, fill path. Rendered at ~14pt inside 44pt circle. +private struct CloseIconShape: Shape { + func path(in rect: CGRect) -> Path { + let sx = rect.width / 19.6289 + let sy = rect.height / 19.6289 + + func pt(_ x: CGFloat, _ y: CGFloat) -> CGPoint { + CGPoint(x: x * sx, y: y * sy) + } + + var p = Path() + p.move(to: pt(17.7734, 0.3174)) + p.addCurve(to: pt(18.1396, 0.0732), control1: pt(17.8711, 0.2035), control2: pt(17.9932, 0.1221)) + p.addCurve(to: pt(18.5547, 0), control1: pt(18.2699, 0.0244), control2: pt(18.4082, 0)) + p.addCurve(to: pt(18.9453, 0.0732), control1: pt(18.6849, 0), control2: pt(18.8151, 0.0244)) + p.addCurve(to: pt(19.3115, 0.3174), control1: pt(19.0918, 0.1221), control2: pt(19.2139, 0.2035)) + p.addCurve(to: pt(19.5557, 0.6836), control1: pt(19.4255, 0.415), control2: pt(19.5068, 0.5371)) + p.addCurve(to: pt(19.6289, 1.0742), control1: pt(19.6045, 0.8138), control2: pt(19.6289, 0.944)) + p.addCurve(to: pt(19.5557, 1.4893), control1: pt(19.6289, 1.2207), control2: pt(19.6045, 1.3591)) + p.addCurve(to: pt(19.3115, 1.8555), control1: pt(19.5068, 1.6357), control2: pt(19.4255, 1.7578)) + p.addLine(to: pt(11.3525, 9.8145)) + p.addLine(to: pt(19.3115, 17.7734)) + p.addCurve(to: pt(19.5557, 18.1396), control1: pt(19.4255, 17.8711), control2: pt(19.5068, 17.9932)) + p.addCurve(to: pt(19.6289, 18.5547), control1: pt(19.6045, 18.2699), control2: pt(19.6289, 18.4082)) + p.addCurve(to: pt(19.5557, 18.9453), control1: pt(19.6289, 18.6849), control2: pt(19.6045, 18.8151)) + p.addCurve(to: pt(19.3115, 19.3115), control1: pt(19.5068, 19.0918), control2: pt(19.4255, 19.2139)) + p.addCurve(to: pt(18.9453, 19.5557), control1: pt(19.2139, 19.4255), control2: pt(19.0918, 19.5068)) + p.addCurve(to: pt(18.5547, 19.6289), control1: pt(18.8151, 19.6045), control2: pt(18.6849, 19.6289)) + p.addCurve(to: pt(18.1396, 19.5557), control1: pt(18.4082, 19.6289), control2: pt(18.2699, 19.6045)) + p.addCurve(to: pt(17.7734, 19.3115), control1: pt(17.9932, 19.5068), control2: pt(17.8711, 19.4255)) + p.addLine(to: pt(9.8145, 11.3525)) + p.addLine(to: pt(1.8555, 19.3115)) + p.addCurve(to: pt(1.4893, 19.5557), control1: pt(1.7578, 19.4255), control2: pt(1.6357, 19.5068)) + p.addCurve(to: pt(1.0742, 19.6289), control1: pt(1.3591, 19.6045), control2: pt(1.2207, 19.6289)) + p.addCurve(to: pt(0.6836, 19.5557), control1: pt(0.944, 19.6289), control2: pt(0.8138, 19.6045)) + p.addCurve(to: pt(0.3174, 19.3115), control1: pt(0.5371, 19.5068), control2: pt(0.415, 19.4255)) + p.addCurve(to: pt(0.0732, 18.9453), control1: pt(0.2035, 19.2139), control2: pt(0.1221, 19.0918)) + p.addCurve(to: pt(0, 18.5547), control1: pt(0.0244, 18.8151), control2: pt(0, 18.6849)) + p.addCurve(to: pt(0.0732, 18.1396), control1: pt(0, 18.4082), control2: pt(0.0244, 18.2699)) + p.addCurve(to: pt(0.3174, 17.7734), control1: pt(0.1221, 17.9932), control2: pt(0.2035, 17.8711)) + p.addLine(to: pt(8.2764, 9.8145)) + p.addLine(to: pt(0.3174, 1.8555)) + p.addCurve(to: pt(0.0732, 1.4893), control1: pt(0.2035, 1.7578), control2: pt(0.1221, 1.6357)) + p.addCurve(to: pt(0, 1.0742), control1: pt(0.0244, 1.3591), control2: pt(0, 1.2207)) + p.addCurve(to: pt(0.0732, 0.6836), control1: pt(0, 0.944), control2: pt(0.0244, 0.8138)) + p.addCurve(to: pt(0.3174, 0.3174), control1: pt(0.1221, 0.5371), control2: pt(0.2035, 0.415)) + p.addCurve(to: pt(0.6836, 0.0732), control1: pt(0.415, 0.2035), control2: pt(0.5371, 0.1221)) + p.addCurve(to: pt(1.0742, 0), control1: pt(0.8138, 0.0244), control2: pt(0.944, 0)) + p.addCurve(to: pt(1.4893, 0.0732), control1: pt(1.2207, 0), control2: pt(1.3591, 0.0244)) + p.addCurve(to: pt(1.8555, 0.3174), control1: pt(1.6357, 0.1221), control2: pt(1.7578, 0.2035)) + p.addLine(to: pt(9.8145, 8.2764)) + p.addLine(to: pt(17.7734, 0.3174)) + p.closeSubpath() + + return p + } +} + +// MARK: - Presentation Corner Radius (iOS 16.4+) + +/// Sets a custom corner radius on the sheet presentation. +/// `.presentationCornerRadius` is iOS 16.4+, so this modifier guards availability. +private struct AttachmentCornerRadiusModifier: ViewModifier { + let radius: CGFloat + func body(content: Content) -> some View { + if #available(iOS 16.4, *) { + content.presentationCornerRadius(radius) + } else { + content + } + } +} + +private extension View { + func attachmentCornerRadius(_ radius: CGFloat) -> some View { + modifier(AttachmentCornerRadiusModifier(radius: radius)) + } +} + +// MARK: - Tab Bar Shadow Modifier + +/// Shadow for iOS < 26 tab bar (matches RosettaTabBar's TabBarShadowModifier). +private struct AttachmentTabBarShadowModifier: ViewModifier { + func body(content: Content) -> some View { + if #available(iOS 26, *) { + content + } else { + content + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } +} + +private extension View { + func tabBarShadow() -> some View { + modifier(AttachmentTabBarShadowModifier()) + } +} + +// MARK: - Preview Background Modifier (iOS 16.4+) + +/// Sets a custom background on the sheet presentation. +/// `.presentationBackground` is iOS 16.4+, so this modifier guards availability. +private struct PreviewBackgroundModifier: ViewModifier { + let style: S + func body(content: Content) -> some View { + if #available(iOS 16.4, *) { + content.presentationBackground(style) + } else { + content + } + } +} + +private extension View { + func previewBackground(_ style: S) -> some View { + modifier(PreviewBackgroundModifier(style: style)) + } +} + +// MARK: - CameraPickerView + +/// UIKit camera wrapper for taking photos. +struct CameraPickerView: UIViewControllerRepresentable { + let onCapture: (UIImage) -> Void + + func makeUIViewController(context: Context) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.sourceType = .camera + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(onCapture: onCapture) + } + + final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + let onCapture: (UIImage) -> Void + + init(onCapture: @escaping (UIImage) -> Void) { + self.onCapture = onCapture + } + + func imagePickerController( + _ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any] + ) { + if let image = info[.originalImage] as? UIImage { + onCapture(image) + } + picker.dismiss(animated: true) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true) + } + } +} + +// MARK: - DocumentPickerView + +/// UIKit document picker wrapper for selecting files. +struct DocumentPickerView: UIViewControllerRepresentable { + let onPick: ([URL]) -> Void + + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) + picker.allowsMultipleSelection = true + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(onPick: onPick) + } + + final class Coordinator: NSObject, UIDocumentPickerDelegate { + let onPick: ([URL]) -> Void + + init(onPick: @escaping ([URL]) -> Void) { + self.onPick = onPick + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + onPick(urls) + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + // No-op β€” user cancelled + } + } +} + +// MARK: - Transparent FullScreen Background + +/// UIKit hack to make `.fullScreenCover` background transparent. +/// Walks the view hierarchy to find the presentation container and sets +/// its background to clear, allowing the presenting view to show through. +struct TransparentFullScreenBackground: UIViewRepresentable { + func makeUIView(context: Context) -> UIView { + let view = UIView() + view.backgroundColor = .clear + DispatchQueue.main.async { + view.superview?.superview?.backgroundColor = .clear + } + return view + } + + func updateUIView(_ uiView: UIView, context: Context) {} +} + diff --git a/Rosetta/Features/Chats/ChatDetail/AttachmentPreviewStrip.swift b/Rosetta/Features/Chats/ChatDetail/AttachmentPreviewStrip.swift new file mode 100644 index 0000000..7125879 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/AttachmentPreviewStrip.swift @@ -0,0 +1,123 @@ +import SwiftUI + +// MARK: - AttachmentPreviewStrip + +/// Horizontal strip above the compositor showing selected attachments before send. +/// +/// Desktop parity: `DialogInput.tsx` renders `DialogAttachment` components in a +/// flex row above the input field when `attachments.length > 0`. +/// +/// Each item shows a thumbnail (images) or file card (files) with a red X remove button. +struct AttachmentPreviewStrip: View { + + @Binding var pendingAttachments: [PendingAttachment] + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(pendingAttachments) { attachment in + attachmentPreview(attachment) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 6) + } + } + + @ViewBuilder + private func attachmentPreview(_ attachment: PendingAttachment) -> some View { + ZStack(alignment: .topTrailing) { + switch attachment.type { + case .image: + imagePreview(attachment) + case .file: + filePreview(attachment) + default: + EmptyView() + } + + // Remove button (Figma: red circle with X) + Button { + withAnimation(.easeInOut(duration: 0.2)) { + pendingAttachments.removeAll { $0.id == attachment.id } + } + } label: { + Image(systemName: "xmark") + .font(.system(size: 9, weight: .bold)) + .foregroundStyle(.white) + .frame(width: 18, height: 18) + .background(Color.red, in: Circle()) + } + .offset(x: 4, y: -4) + } + } + + @ViewBuilder + private func imagePreview(_ attachment: PendingAttachment) -> some View { + if let thumbnail = attachment.thumbnail { + Image(uiImage: thumbnail) + .resizable() + .scaledToFill() + .frame(width: 70, height: 70) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } else { + RoundedRectangle(cornerRadius: 8) + .fill(Color.white.opacity(0.1)) + .frame(width: 70, height: 70) + .overlay { + Image(systemName: "photo") + .foregroundStyle(.white.opacity(0.4)) + } + } + } + + @ViewBuilder + private func filePreview(_ attachment: PendingAttachment) -> some View { + VStack(spacing: 4) { + Image(systemName: fileIcon(for: attachment.fileName ?? "file")) + .font(.system(size: 22)) + .foregroundStyle(Color(hex: 0x008BFF)) + + Text(attachment.fileName ?? "file") + .font(.system(size: 9)) + .foregroundStyle(.white.opacity(0.8)) + .lineLimit(2) + .multilineTextAlignment(.center) + + Text(formatFileSize(attachment.fileSize ?? 0)) + .font(.system(size: 8)) + .foregroundStyle(.white.opacity(0.4)) + } + .frame(width: 70, height: 70) + .background(Color.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 8)) + } + + // MARK: - Helpers + + /// Returns an appropriate SF Symbol name for the file extension. + private func fileIcon(for fileName: String) -> String { + let ext = (fileName as NSString).pathExtension.lowercased() + switch ext { + case "pdf": return "doc.fill" + case "zip", "rar", "7z": return "doc.zipper" + case "jpg", "jpeg", "png", "gif", "webp": return "photo.fill" + case "mp4", "mov", "avi": return "film.fill" + case "mp3", "wav", "aac": return "waveform" + case "doc", "docx": return "doc.text.fill" + case "xls", "xlsx": return "tablecells.fill" + case "txt": return "doc.plaintext.fill" + default: return "doc.fill" + } + } + + /// Formats byte count to human-readable string. + private func formatFileSize(_ bytes: Int) -> String { + if bytes < 1024 { + return "\(bytes) B" + } else if bytes < 1024 * 1024 { + return String(format: "%.1f KB", Double(bytes) / 1024) + } else { + return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) + } + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/CameraPreviewView.swift b/Rosetta/Features/Chats/ChatDetail/CameraPreviewView.swift new file mode 100644 index 0000000..77e80fd --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/CameraPreviewView.swift @@ -0,0 +1,195 @@ +import AVFoundation +import SwiftUI + +// MARK: - CameraPreviewView + +/// Live camera preview using AVCaptureSession, wrapped for SwiftUI. +/// +/// Figma: Camera tile in attachment panel shows a real-time rear camera feed. +/// Uses `.medium` preset for minimal memory/battery usage (it's just a preview). +/// +/// Graceful fallback: on Simulator or when camera unavailable, shows a dark +/// placeholder with a camera icon. +struct CameraPreviewView: UIViewRepresentable { + + func makeUIView(context: Context) -> CameraPreviewUIView { + let view = CameraPreviewUIView() + return view + } + + func updateUIView(_ uiView: CameraPreviewUIView, context: Context) {} + + static func dismantleUIView(_ uiView: CameraPreviewUIView, coordinator: ()) { + uiView.stopSession() + } +} + +// MARK: - CameraPreviewUIView + +/// UIKit view hosting AVCaptureVideoPreviewLayer for live camera feed. +/// +/// Permission request is deferred to `didMoveToWindow()` β€” the system dialog +/// needs an active window to present on. Requesting in `init()` is too early. +final class CameraPreviewUIView: UIView { + + private let captureSession = AVCaptureSession() + private var previewLayer: AVCaptureVideoPreviewLayer? + private var isSessionRunning = false + private var isSetUp = false + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = UIColor(white: 0.08, alpha: 1) + clipsToBounds = true + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + previewLayer?.frame = bounds + } + + override func didMoveToWindow() { + super.didMoveToWindow() + if window != nil { + // First time: request permission + set up session + // Subsequent times: just resume + if !isSetUp { + isSetUp = true + requestAccessAndSetup() + } else { + startSession() + } + } else { + stopSession() + } + } + + // MARK: - Permission + Session Setup + + private func requestAccessAndSetup() { + #if targetEnvironment(simulator) + addPlaceholder() + return + #else + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + configureSession() + case .notDetermined: + // System dialog will present over the current window + AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in + DispatchQueue.main.async { + if granted { + self?.configureSession() + } else { + self?.addDeniedPlaceholder() + } + } + } + default: + addDeniedPlaceholder() + } + #endif + } + + /// Configures AVCaptureSession with rear camera input + preview layer. + /// Called only after camera authorization is confirmed. + private func configureSession() { + guard let device = AVCaptureDevice.default( + .builtInWideAngleCamera, for: .video, position: .back + ) else { + addPlaceholder() + return + } + + do { + let input = try AVCaptureDeviceInput(device: device) + captureSession.beginConfiguration() + captureSession.sessionPreset = .medium + if captureSession.canAddInput(input) { + captureSession.addInput(input) + } + captureSession.commitConfiguration() + + let preview = AVCaptureVideoPreviewLayer(session: captureSession) + preview.videoGravity = .resizeAspectFill + preview.frame = bounds + layer.addSublayer(preview) + previewLayer = preview + + startSession() + } catch { + addPlaceholder() + } + } + + func startSession() { + guard !isSessionRunning, !captureSession.inputs.isEmpty else { return } + isSessionRunning = true + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.captureSession.startRunning() + } + } + + func stopSession() { + guard isSessionRunning else { return } + isSessionRunning = false + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.captureSession.stopRunning() + } + } + + // MARK: - Placeholders + + /// Generic placeholder (simulator / no camera hardware). + private func addPlaceholder() { + let iconConfig = UIImage.SymbolConfiguration(pointSize: 32, weight: .regular) + let icon = UIImageView(image: UIImage(systemName: "camera.fill", withConfiguration: iconConfig)) + icon.tintColor = UIColor.white.withAlphaComponent(0.5) + icon.translatesAutoresizingMaskIntoConstraints = false + addSubview(icon) + NSLayoutConstraint.activate([ + icon.centerXAnchor.constraint(equalTo: centerXAnchor), + icon.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + } + + /// Placeholder shown when camera access is denied β€” includes "Tap to enable" hint. + private func addDeniedPlaceholder() { + let stack = UIStackView() + stack.axis = .vertical + stack.alignment = .center + stack.spacing = 4 + stack.translatesAutoresizingMaskIntoConstraints = false + + let iconConfig = UIImage.SymbolConfiguration(pointSize: 24, weight: .regular) + let icon = UIImageView(image: UIImage(systemName: "camera.fill", withConfiguration: iconConfig)) + icon.tintColor = UIColor.white.withAlphaComponent(0.4) + + let label = UILabel() + label.text = "Enable Camera" + label.font = .systemFont(ofSize: 10, weight: .medium) + label.textColor = UIColor.white.withAlphaComponent(0.4) + + stack.addArrangedSubview(icon) + stack.addArrangedSubview(label) + addSubview(stack) + NSLayoutConstraint.activate([ + stack.centerXAnchor.constraint(equalTo: centerXAnchor), + stack.centerYAnchor.constraint(equalTo: centerYAnchor), + ]) + + // Tap opens Settings + let tap = UITapGestureRecognizer(target: self, action: #selector(openSettings)) + addGestureRecognizer(tap) + } + + @objc private func openSettings() { + guard let url = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift b/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift index 5211fa8..7a84183 100644 --- a/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift +++ b/Rosetta/Features/Chats/ChatDetail/ChatDetailView.swift @@ -38,6 +38,23 @@ private struct KeyboardPaddedView: View { } } +/// Shifts empty state content up by half the keyboard height to keep it +/// centered in the visible area above keyboard. Uses `.offset` (visual-only) +/// instead of frame height changes that would leak layout to the compositor overlay. +/// Observation-isolated: keyboard changes re-render only this wrapper. +private struct EmptyStateKeyboardOffset: View { + @ObservedObject private var keyboard = KeyboardTracker.shared + let content: Content + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + var body: some View { + content.offset(y: -keyboard.keyboardPadding / 2) + } +} + struct ChatDetailView: View { let route: ChatRoute var onPresentedChange: ((Bool) -> Void)? = nil @@ -62,6 +79,9 @@ struct ChatDetailView: View { /// Captured on chat open β€” ID of the first unread incoming message (for separator). @State private var firstUnreadMessageId: String? @State private var isSendingAvatar = false + @State private var showAttachmentPanel = false + @State private var pendingAttachments: [PendingAttachment] = [] + @State private var showOpponentProfile = false private var currentPublicKey: String { SessionManager.shared.currentPublicKey @@ -108,11 +128,11 @@ struct ChatDetailView: View { } private var canSend: Bool { - !trimmedMessage.isEmpty + !trimmedMessage.isEmpty || !pendingAttachments.isEmpty } private var shouldShowSendButton: Bool { - !messageText.isEmpty + !messageText.isEmpty || !pendingAttachments.isEmpty } private var sendButtonProgress: CGFloat { @@ -239,6 +259,117 @@ struct ChatDetailView: View { var body: some View { content + .navigationDestination(isPresented: $showOpponentProfile) { + OpponentProfileView(route: route) + } + } +} + +// MARK: - Toolbar Content (observation-isolated) + +/// Reads `DialogRepository` in its own observation scope for title/subtitle/verified. +/// Dialog mutations (from ANY chat) no longer cascade to ChatDetailView body, +/// preventing all visible message cells from re-evaluating. +private struct ChatDetailPrincipal: View { + let route: ChatRoute + @ObservedObject var viewModel: ChatDetailViewModel + + private var dialog: Dialog? { + DialogRepository.shared.dialogs[route.publicKey] + } + + private var badgeSpacing: CGFloat { + if #available(iOS 26, *) { return 3 } else { return 4 } + } + + private var badgeSize: CGFloat { + if #available(iOS 26, *) { return 12 } else { return 14 } + } + + private var titleText: String { + if route.isSavedMessages { return "Saved Messages" } + if let dialog, !dialog.opponentTitle.isEmpty { return dialog.opponentTitle } + if !route.title.isEmpty { return route.title } + if let dialog, !dialog.opponentUsername.isEmpty { return "@\(dialog.opponentUsername)" } + if !route.username.isEmpty { return "@\(route.username)" } + return String(route.publicKey.prefix(12)) + } + + private var effectiveVerified: Int { + if let dialog { return dialog.effectiveVerified } + if route.verified > 0 { return route.verified } + return 0 + } + + private var subtitleText: String { + if route.isSavedMessages { return "" } + if route.isSystemAccount { return "official account" } + if viewModel.isTyping { return "typing..." } + if let dialog, dialog.isOnline { return "online" } + return "offline" + } + + private var subtitleColor: Color { + if viewModel.isTyping { return RosettaColors.primaryBlue } + if dialog?.isOnline == true { return RosettaColors.online } + return RosettaColors.Adaptive.textSecondary + } + + var body: some View { + VStack(spacing: 1) { + HStack(spacing: badgeSpacing) { + Text(titleText) + .font(.system(size: 15, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.text) + .lineLimit(1) + + if !route.isSavedMessages && effectiveVerified > 0 { + VerifiedBadge(verified: effectiveVerified, size: badgeSize) + } + } + + if !subtitleText.isEmpty { + Text(subtitleText) + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(subtitleColor) + .lineLimit(1) + } + } + } +} + +/// Reads `DialogRepository` and `AvatarRepository` in its own observation scope +/// for avatar initials/color/image. Isolated from ChatDetailView body. +private struct ChatDetailToolbarAvatar: View { + let route: ChatRoute + let size: CGFloat + + private var dialog: Dialog? { + DialogRepository.shared.dialogs[route.publicKey] + } + + private var titleText: String { + if route.isSavedMessages { return "Saved Messages" } + if let dialog, !dialog.opponentTitle.isEmpty { return dialog.opponentTitle } + if !route.title.isEmpty { return route.title } + if let dialog, !dialog.opponentUsername.isEmpty { return "@\(dialog.opponentUsername)" } + if !route.username.isEmpty { return "@\(route.username)" } + return String(route.publicKey.prefix(12)) + } + + var body: some View { + let avatar = AvatarRepository.shared.loadAvatar(publicKey: route.publicKey) + let initials = route.isSavedMessages ? "S" : RosettaColors.initials(name: titleText, publicKey: route.publicKey) + let colorIndex = RosettaColors.avatarColorIndex(for: titleText, publicKey: route.publicKey) + + AvatarView( + initials: initials, + colorIndex: colorIndex, + size: size, + isOnline: false, + isSavedMessages: route.isSavedMessages, + image: avatar + ) } } @@ -265,52 +396,25 @@ private extension ChatDetailView { } ToolbarItem(placement: .principal) { - Button { dismiss() } label: { - VStack(spacing: 1) { - HStack(spacing: 3) { - Text(titleText) - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(RosettaColors.Adaptive.text) - .lineLimit(1) - - if !route.isSavedMessages && effectiveVerified > 0 { - VerifiedBadge(verified: effectiveVerified, size: 12) - } + Button { openProfile() } label: { + ChatDetailPrincipal(route: route, viewModel: viewModel) + .padding(.horizontal, 12) + .frame(minWidth: 120) + .frame(height: 44) + .background { + glass(shape: .capsule, strokeOpacity: 0.22, strokeColor: .white) } - - if !subtitleText.isEmpty { - Text(subtitleText) - .font(.system(size: 12, weight: .medium)) - .foregroundStyle( - isTyping - ? RosettaColors.primaryBlue - : (dialog?.isOnline == true) - ? RosettaColors.online - : RosettaColors.Adaptive.textSecondary - ) - .lineLimit(1) - } - } - .padding(.horizontal, 12) - .frame(minWidth: 120) - .frame(height: 44) - .background { - glass(shape: .capsule, strokeOpacity: 0.22, strokeColor: .white) - } } + .buttonStyle(.plain) } ToolbarItem(placement: .navigationBarTrailing) { - AvatarView( - initials: avatarInitials, - colorIndex: avatarColorIndex, - size: 35, - isOnline: false, - isSavedMessages: route.isSavedMessages, - image: opponentAvatar - ) - .frame(width: 36, height: 36) - .background { glass(shape: .circle, strokeOpacity: 0.22, strokeColor: .white) } + Button { openProfile() } label: { + ChatDetailToolbarAvatar(route: route, size: 35) + .frame(width: 36, height: 36) + .background { glass(shape: .circle, strokeOpacity: 0.22, strokeColor: .white) } + } + .buttonStyle(.plain) } } else { // iOS < 26 β€” capsule back button, larger avatar, .thinMaterial @@ -321,53 +425,25 @@ private extension ChatDetailView { } ToolbarItem(placement: .principal) { - Button { dismiss() } label: { - VStack(spacing: 1) { - HStack(spacing: 4) { - Text(titleText) - .font(.system(size: 15, weight: .semibold)) - .foregroundStyle(RosettaColors.Adaptive.text) - .lineLimit(1) - - if !route.isSavedMessages && effectiveVerified > 0 { - VerifiedBadge(verified: effectiveVerified, size: 14) - } + Button { openProfile() } label: { + ChatDetailPrincipal(route: route, viewModel: viewModel) + .padding(.horizontal, 16) + .frame(minWidth: 120) + .frame(height: 44) + .background { + glass(shape: .capsule, strokeOpacity: 0.22, strokeColor: .white) } - - if !subtitleText.isEmpty { - Text(subtitleText) - .font(.system(size: 12, weight: .medium)) - .foregroundStyle( - isTyping - ? RosettaColors.primaryBlue - : (dialog?.isOnline == true) - ? RosettaColors.online - : RosettaColors.Adaptive.textSecondary - ) - .lineLimit(1) - } - } - .padding(.horizontal, 16) - .frame(minWidth: 120) - .frame(height: 44) - .background { - glass(shape: .capsule, strokeOpacity: 0.22, strokeColor: .white) - } } .buttonStyle(.plain) } ToolbarItem(placement: .navigationBarTrailing) { - AvatarView( - initials: avatarInitials, - colorIndex: avatarColorIndex, - size: 38, - isOnline: false, - isSavedMessages: route.isSavedMessages, - image: opponentAvatar - ) - .frame(width: 44, height: 44) - .background { glass(shape: .circle, strokeOpacity: 0.22, strokeColor: .white) } + Button { openProfile() } label: { + ChatDetailToolbarAvatar(route: route, size: 38) + .frame(width: 44, height: 44) + .background { glass(shape: .circle, strokeOpacity: 0.22, strokeColor: .white) } + } + .buttonStyle(.plain) } } } @@ -478,38 +554,52 @@ private extension ChatDetailView { } private var emptyStateView: some View { - VStack(spacing: 16) { - AvatarView( - initials: avatarInitials, - colorIndex: avatarColorIndex, - size: 80, - isOnline: dialog?.isOnline ?? false, - isSavedMessages: route.isSavedMessages, - image: opponentAvatar - ) + // EmptyStateKeyboardOffset applies offset(y: -keyboardPadding/2) which is + // visual-only β€” does NOT affect layout. Keeps content centered in the visible + // area above keyboard without leaking layout changes to the compositor overlay. + EmptyStateKeyboardOffset { + VStack(spacing: 0) { + Spacer() - VStack(spacing: 4) { - Text(titleText) - .font(.system(size: 17, weight: .semibold)) - .foregroundStyle(RosettaColors.Adaptive.text) + VStack(spacing: 16) { + AvatarView( + initials: avatarInitials, + colorIndex: avatarColorIndex, + size: 80, + isOnline: dialog?.isOnline ?? false, + isSavedMessages: route.isSavedMessages, + image: opponentAvatar + ) - if !route.isSavedMessages { - Text(subtitleText) - .font(.system(size: 14, weight: .regular)) - .foregroundStyle(RosettaColors.Adaptive.textSecondary) + VStack(spacing: 4) { + Text(titleText) + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.text) + + if !route.isSavedMessages { + Text(subtitleText) + .font(.system(size: 14, weight: .regular)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + } + } + + Text(route.isSavedMessages + ? "Save messages here for quick access" + : "No messages yet") + .font(.system(size: 15)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.7)) + .padding(.top, 4) } - } - Text(route.isSavedMessages - ? "Save messages here for quick access" - : "No messages yet") - .font(.system(size: 15)) - .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.7)) - .padding(.top, 4) + Spacer() + + // Reserve space for compositor so content centers above it. + Color.clear.frame(height: composerHeight) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .contentShape(Rectangle()) + .onTapGesture { isInputFocused = false } } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .contentShape(Rectangle()) - .onTapGesture { isInputFocused = false } } @ViewBuilder @@ -628,9 +718,25 @@ private extension ChatDetailView { @ViewBuilder func messageRow(_ message: ChatMessage, maxBubbleWidth: CGFloat, position: BubblePosition) -> some View { let outgoing = message.isFromMe(myPublicKey: currentPublicKey) - let messageText = message.text.isEmpty ? " " : message.text let hasTail = position == .single || position == .bottom + // Desktop parity: render image, file, and avatar attachments in the bubble. + let visibleAttachments = message.attachments.filter { $0.type == .image || $0.type == .file || $0.type == .avatar } + + if visibleAttachments.isEmpty { + // Text-only message (original path) + textOnlyBubble(message: message, outgoing: outgoing, hasTail: hasTail, maxBubbleWidth: maxBubbleWidth, position: position) + } else { + // Attachment message: images/files + optional caption + attachmentBubble(message: message, attachments: visibleAttachments, outgoing: outgoing, hasTail: hasTail, maxBubbleWidth: maxBubbleWidth, position: position) + } + } + + /// Text-only message bubble (original design). + @ViewBuilder + private func textOnlyBubble(message: ChatMessage, outgoing: Bool, hasTail: Bool, maxBubbleWidth: CGFloat, position: BubblePosition) -> some View { + let messageText = message.text.isEmpty ? " " : message.text + // Telegram-style compact bubble: inline time+status at bottom-trailing. // Right padding reserves space for the timestamp overlay (64pt outgoing, 48pt incoming). Text(parsedMarkdown(messageText)) @@ -645,25 +751,7 @@ private extension ChatDetailView { .padding(.vertical, 5) .frame(minWidth: outgoing ? 86 : 66, alignment: .leading) .overlay(alignment: .bottomTrailing) { - HStack(spacing: 3) { - Text(messageTime(message.timestamp)) - .font(.system(size: 11, weight: .regular)) - .foregroundStyle( - outgoing - ? Color.white.opacity(0.55) - : RosettaColors.Adaptive.textSecondary.opacity(0.6) - ) - - if outgoing { - if message.deliveryStatus == .error { - errorMenu(for: message) - } else { - deliveryIndicator(message.deliveryStatus) - } - } - } - .padding(.trailing, 11) - .padding(.bottom, 5) + timestampOverlay(message: message, outgoing: outgoing) } // Tail protrusion space: the unified shape draws the tail in this padding area .padding(.trailing, outgoing && hasTail ? MessageBubbleShape.tailProtrusion : 0) @@ -678,18 +766,139 @@ private extension ChatDetailView { .padding(.bottom, 0) } - // MARK: - Markdown Parsing + /// Attachment message bubble: images/files with optional text caption. + @ViewBuilder + private func attachmentBubble( + message: ChatMessage, + attachments: [MessageAttachment], + outgoing: Bool, + hasTail: Bool, + maxBubbleWidth: CGFloat, + position: BubblePosition + ) -> some View { + let hasCaption = !message.text.trimmingCharacters(in: .whitespaces).isEmpty && message.text != " " - /// Parses inline markdown (`**bold**`) from runtime strings. - /// Falls back to plain `AttributedString` if parsing fails. + VStack(alignment: .leading, spacing: 0) { + // Attachment views + ForEach(attachments, id: \.id) { attachment in + switch attachment.type { + case .image: + MessageImageView( + attachment: attachment, + message: message, + outgoing: outgoing, + maxWidth: maxBubbleWidth + ) + .padding(.horizontal, 4) + .padding(.top, 4) + case .file: + MessageFileView( + attachment: attachment, + message: message, + outgoing: outgoing + ) + .padding(.top, 4) + case .avatar: + MessageAvatarView( + attachment: attachment, + message: message, + outgoing: outgoing + ) + .padding(.horizontal, 6) + .padding(.top, 4) + default: + EmptyView() + } + } + + // Caption text (if any) + if hasCaption { + Text(parsedMarkdown(message.text)) + .font(.system(size: 17, weight: .regular)) + .tracking(-0.43) + .foregroundStyle(outgoing ? Color.white : RosettaColors.Adaptive.text) + .multilineTextAlignment(.leading) + .lineSpacing(0) + .fixedSize(horizontal: false, vertical: true) + .padding(.leading, 11) + .padding(.trailing, outgoing ? 64 : 48) + .padding(.top, 4) + .padding(.bottom, 5) + } + } + .frame(minWidth: outgoing ? 86 : 66, alignment: .leading) + .overlay(alignment: .bottomTrailing) { + timestampOverlay(message: message, outgoing: outgoing) + } + .padding(.trailing, outgoing && hasTail ? MessageBubbleShape.tailProtrusion : 0) + .padding(.leading, !outgoing && hasTail ? MessageBubbleShape.tailProtrusion : 0) + .background { bubbleBackground(outgoing: outgoing, position: position) } + .frame(maxWidth: maxBubbleWidth, alignment: outgoing ? .trailing : .leading) + .frame(maxWidth: .infinity, alignment: outgoing ? .trailing : .leading) + .padding(.trailing, outgoing ? (hasTail ? 2 : MessageBubbleShape.tailProtrusion + 2) : 0) + .padding(.leading, !outgoing ? (hasTail ? 2 : MessageBubbleShape.tailProtrusion + 2) : 0) + .padding(.top, (position == .single || position == .top) ? 6 : 2) + .padding(.bottom, 0) + } + + /// Timestamp + delivery status overlay for both text and attachment bubbles. + @ViewBuilder + private func timestampOverlay(message: ChatMessage, outgoing: Bool) -> some View { + HStack(spacing: 3) { + Text(messageTime(message.timestamp)) + .font(.system(size: 11, weight: .regular)) + .foregroundStyle( + outgoing + ? Color.white.opacity(0.55) + : RosettaColors.Adaptive.textSecondary.opacity(0.6) + ) + + if outgoing { + if message.deliveryStatus == .error { + errorMenu(for: message) + } else { + deliveryIndicator(message.deliveryStatus) + } + } + } + .padding(.trailing, 11) + .padding(.bottom, 5) + } + + // MARK: - Text Parsing (Markdown + Emoji) + + /// Static cache for parsed markdown + emoji. Message text is immutable, + /// so results never need invalidation. Bounded at 200 entries (~5 chats). + /// Without cache, regex + markdown parser runs on EVERY body evaluation + /// for EVERY visible cell β€” expensive at 120Hz scroll. + @MainActor private static var markdownCache: [String: AttributedString] = [:] + + /// Parses inline markdown (`**bold**`) and emoji shortcodes (`:emoji_CODE:`) + /// from runtime strings. Emoji shortcodes are replaced BEFORE markdown parsing + /// so that emoji characters render inline with formatted text. + /// + /// Desktop parity: `TextParser.tsx` pattern `/:emoji_([a-zA-Z0-9_-]+):/` + /// Android parity: `unifiedToEmoji()` in `AppleEmojiPicker.kt` private func parsedMarkdown(_ text: String) -> AttributedString { + if let cached = Self.markdownCache[text] { return cached } + + // Cross-platform: replace :emoji_CODE: shortcodes with native Unicode emoji. + let withEmoji = EmojiParser.replaceShortcodes(in: text) + + let result: AttributedString if let parsed = try? AttributedString( - markdown: text, + markdown: withEmoji, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) ) { - return parsed + result = parsed + } else { + result = AttributedString(withEmoji) } - return AttributedString(text) + if Self.markdownCache.count > 200 { + Self.markdownCache.removeAll(keepingCapacity: true) + } + Self.markdownCache[text] = result + return result } // MARK: - Unread Separator @@ -710,6 +919,11 @@ private extension ChatDetailView { var composer: some View { VStack(spacing: 6) { + // Attachment preview strip β€” shows selected images/files before send + if !pendingAttachments.isEmpty { + AttachmentPreviewStrip(pendingAttachments: $pendingAttachments) + } + if let sendError { Text(sendError) .font(.system(size: 12)) @@ -719,15 +933,9 @@ private extension ChatDetailView { } HStack(alignment: .bottom, spacing: 0) { - // Desktop parity: paperclip opens attachment menu with camera option. - // Camera sends current user's avatar to this chat. - Menu { - Button { - sendAvatarToChat() - } label: { - Label("Send Avatar", systemImage: "camera.fill") - } - .disabled(isSendingAvatar) + // Desktop parity: paperclip opens attachment panel (photo gallery + file picker). + Button { + showAttachmentPanel = true } label: { TelegramVectorIcon( pathData: TelegramIconPath.paperclip, @@ -740,6 +948,21 @@ private extension ChatDetailView { } .accessibilityLabel("Attach") .buttonStyle(ChatDetailGlassPressButtonStyle()) + .sheet(isPresented: $showAttachmentPanel) { + AttachmentPanelView( + onSend: { attachments, caption in + // Pre-fill caption as message text (sent alongside attachments) + let trimmedCaption = caption.trimmingCharacters(in: .whitespaces) + if !trimmedCaption.isEmpty { + messageText = trimmedCaption + } + handleAttachmentsSend(attachments) + }, + onSendAvatar: { + sendAvatarToChat() + } + ) + } HStack(alignment: .bottom, spacing: 0) { ChatTextInput( @@ -923,6 +1146,13 @@ private extension ChatDetailView { // MARK: - Actions / utils + /// Opens the opponent profile sheet. + /// For Saved Messages and system accounts β€” no profile to show. + func openProfile() { + guard !route.isSavedMessages, !route.isSystemAccount else { return } + showOpponentProfile = true + } + func trailingAction() { if canSend { sendCurrentMessage() } else { isInputFocused = true } @@ -942,34 +1172,29 @@ private extension ChatDetailView { func deliveryTint(_ status: DeliveryStatus) -> Color { switch status { case .read: return Color(hex: 0xA4E2FF) - case .delivered: return Color.white.opacity(0.94) + case .delivered: return Color.white.opacity(0.5) case .error: return RosettaColors.error default: return Color.white.opacity(0.78) } } - func deliveryIcon(_ status: DeliveryStatus) -> String { - switch status { - case .waiting: return "clock" - case .delivered: return "checkmark" - case .read: return "checkmark" - case .error: return "exclamationmark.circle.fill" - } - } - @ViewBuilder func deliveryIndicator(_ status: DeliveryStatus) -> some View { switch status { case .read: - ZStack { - Image(systemName: "checkmark").offset(x: 3) - Image(systemName: "checkmark") - } - .font(.system(size: 9.5, weight: .semibold)) - .foregroundStyle(deliveryTint(status)) - .frame(width: 12, alignment: .trailing) - default: - Image(systemName: deliveryIcon(status)) + DoubleCheckmarkShape() + .fill(deliveryTint(status)) + .frame(width: 16, height: 8.7) + case .delivered: + SingleCheckmarkShape() + .fill(deliveryTint(status)) + .frame(width: 12, height: 8.8) + case .waiting: + Image(systemName: "clock") + .font(.system(size: 10, weight: .semibold)) + .foregroundStyle(deliveryTint(status)) + case .error: + Image(systemName: "exclamationmark.circle.fill") .font(.system(size: 10, weight: .semibold)) .foregroundStyle(deliveryTint(status)) } @@ -1071,23 +1296,40 @@ private extension ChatDetailView { func sendCurrentMessage() { let message = trimmedMessage - guard !message.isEmpty else { return } + let attachments = pendingAttachments + + // Must have either text or attachments + guard !message.isEmpty || !attachments.isEmpty else { return } + // User is sending a message β€” reset idle timer. SessionManager.shared.recordUserInteraction() shouldScrollOnNextMessage = true messageText = "" + pendingAttachments = [] sendError = nil // Desktop parity: delete draft after sending. DraftManager.shared.deleteDraft(for: route.publicKey) Task { @MainActor in do { - try await SessionManager.shared.sendMessage( - text: message, - toPublicKey: route.publicKey, - opponentTitle: route.title, - opponentUsername: route.username - ) + if !attachments.isEmpty { + // Send message with attachments + try await SessionManager.shared.sendMessageWithAttachments( + text: message, + attachments: attachments, + toPublicKey: route.publicKey, + opponentTitle: route.title, + opponentUsername: route.username + ) + } else { + // Text-only message (existing path) + try await SessionManager.shared.sendMessage( + text: message, + toPublicKey: route.publicKey, + opponentTitle: route.title, + opponentUsername: route.username + ) + } } catch { sendError = "Failed to send message" if messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { @@ -1097,6 +1339,15 @@ private extension ChatDetailView { } } + /// Handles attachments selected from the attachment panel. + /// Always sends immediately β€” no preview step. + func handleAttachmentsSend(_ attachments: [PendingAttachment]) { + let remaining = PendingAttachment.maxAttachmentsPerMessage - pendingAttachments.count + let toAdd = Array(attachments.prefix(remaining)) + pendingAttachments.append(contentsOf: toAdd) + sendCurrentMessage() + } + /// Desktop parity: onClickCamera() β€” sends current user's avatar to this chat. func sendAvatarToChat() { guard !isSendingAvatar else { return } @@ -1191,7 +1442,7 @@ private struct ChatDetailGlassCirclePressStyle: ButtonStyle { // MARK: - SVG -private struct TelegramVectorIcon: View { +struct TelegramVectorIcon: View { let pathData: String let viewBox: CGSize let color: Color @@ -1202,193 +1453,8 @@ private struct TelegramVectorIcon: View { } } -private struct SVGPathShape: Shape { - let pathData: String - let viewBox: CGSize - func path(in rect: CGRect) -> Path { - guard viewBox.width > 0, viewBox.height > 0 else { return Path() } - var parser = SVGPathParser(pathData: pathData) - var output = Path(parser.parse()) - output = output.applying(.init(scaleX: rect.width / viewBox.width, y: rect.height / viewBox.height)) - return output - } -} - -private enum SVGPathToken { - case command(Character) - case number(CGFloat) -} - -private struct SVGPathTokenizer { - static func tokenize(_ source: String) -> [SVGPathToken] { - var tokens: [SVGPathToken] = [] - let chars = Array(source) - var index = 0 - - while index < chars.count { - let ch = chars[index] - - if ch.isWhitespace || ch == "," { index += 1; continue } - if ch.isLetter { tokens.append(.command(ch)); index += 1; continue } - - if ch.isNumber || ch == "-" || ch == "+" || ch == "." { - let start = index - index += 1 - while index < chars.count { - let c = chars[index] - let prev = chars[index - 1] - if c.isNumber || c == "." || c == "e" || c == "E" { index += 1; continue } - if (c == "-" || c == "+"), (prev == "e" || prev == "E") { index += 1; continue } - break - } - - let fragment = String(chars[start.. CGPath { - while index < tokens.count { - let command = readCommandOrReuse() - switch command { - case "M", "m": parseMove(command) - case "L", "l": parseLine(command) - case "H", "h": parseHorizontal(command) - case "V", "v": parseVertical(command) - case "C", "c": parseCubic(command) - case "Z", "z": - cgPath.closeSubpath() - current = subpathStart - default: - skipToNextCommand() - } - } - return cgPath.copy() ?? CGMutablePath() - } - - private var isAtCommand: Bool { - guard index < tokens.count else { return false } - if case .command = tokens[index] { return true } - return false - } - - private mutating func readCommandOrReuse() -> Character { - guard index < tokens.count else { return lastCommand } - if case let .command(command) = tokens[index] { - index += 1 - lastCommand = command - return command - } - return lastCommand - } - - private mutating func readNumber() -> CGFloat? { - guard index < tokens.count else { return nil } - if case let .number(value) = tokens[index] { - index += 1 - return value - } - return nil - } - - private func resolvedPoint(x: CGFloat, y: CGFloat, relative: Bool) -> CGPoint { - relative ? CGPoint(x: current.x + x, y: current.y + y) : CGPoint(x: x, y: y) - } - - private mutating func readPoint(relative: Bool) -> CGPoint? { - guard let x = readNumber(), let y = readNumber() else { return nil } - return resolvedPoint(x: x, y: y, relative: relative) - } - - private mutating func parseMove(_ command: Character) { - let relative = command.isLowercase - guard let first = readPoint(relative: relative) else { return } - cgPath.move(to: first) - current = first - subpathStart = first - - while !isAtCommand, let point = readPoint(relative: relative) { - cgPath.addLine(to: point) - current = point - } - - lastCommand = relative ? "l" : "L" - } - - private mutating func parseLine(_ command: Character) { - let relative = command.isLowercase - while !isAtCommand, let point = readPoint(relative: relative) { - cgPath.addLine(to: point) - current = point - } - } - - private mutating func parseHorizontal(_ command: Character) { - let relative = command.isLowercase - while !isAtCommand, let value = readNumber() { - current = CGPoint(x: relative ? current.x + value : value, y: current.y) - cgPath.addLine(to: current) - } - } - - private mutating func parseVertical(_ command: Character) { - let relative = command.isLowercase - while !isAtCommand, let value = readNumber() { - current = CGPoint(x: current.x, y: relative ? current.y + value : value) - cgPath.addLine(to: current) - } - } - - private mutating func parseCubic(_ command: Character) { - let relative = command.isLowercase - while !isAtCommand { - guard let x1 = readNumber(), - let y1 = readNumber(), - let x2 = readNumber(), - let y2 = readNumber(), - let x = readNumber(), - let y = readNumber() - else { return } - - let c1 = resolvedPoint(x: x1, y: y1, relative: relative) - let c2 = resolvedPoint(x: x2, y: y2, relative: relative) - let end = resolvedPoint(x: x, y: y, relative: relative) - - cgPath.addCurve(to: end, control1: c1, control2: c2) - current = end - } - } - - private mutating func skipToNextCommand() { - while index < tokens.count { - if case .command = tokens[index] { return } - index += 1 - } - } -} - - -private enum TelegramIconPath { +enum TelegramIconPath { static let backChevron = #"M0.317383 10.5957C0.203451 10.498 0.12207 10.376 0.0732422 10.2295C0.0244141 10.0993 0 9.96094 0 9.81445C0 9.66797 0.0244141 9.52962 0.0732422 9.39941C0.12207 9.25293 0.203451 9.13086 0.317383 9.0332L8.83789 0.317383C8.93555 0.219727 9.05762 0.138346 9.2041 0.0732422C9.33431 0.0244141 9.47266 0 9.61914 0C9.74935 0 9.87956 0.0244141 10.0098 0.0732422C10.1562 0.138346 10.2783 0.219727 10.376 0.317383C10.4899 0.431315 10.5713 0.553385 10.6201 0.683594C10.6689 0.830078 10.6934 0.976562 10.6934 1.12305C10.6934 1.25326 10.6689 1.3916 10.6201 1.53809C10.5713 1.66829 10.4899 1.79036 10.376 1.9043L2.63672 9.81445L10.376 17.7246C10.4899 17.8385 10.5713 17.9606 10.6201 18.0908C10.6689 18.2373 10.6934 18.3757 10.6934 18.5059C10.6934 18.6523 10.6689 18.7988 10.6201 18.9453C10.5713 19.0755 10.4899 19.1976 10.376 19.3115C10.2783 19.4092 10.1562 19.4906 10.0098 19.5557C9.87956 19.6045 9.74935 19.6289 9.61914 19.6289C9.47266 19.6289 9.33431 19.6045 9.2041 19.5557C9.05762 19.4906 8.93555 19.4092 8.83789 19.3115L0.317383 10.5957Z"# static let paperclip = #"M11.0156 17.9297L9.84375 16.7871L17.4316 9.11133C17.8418 8.70117 18.1543 8.22266 18.3691 7.67578C18.584 7.14844 18.6914 6.5918 18.6914 6.00586C18.6914 5.43945 18.584 4.88281 18.3691 4.33594C18.1348 3.80859 17.8125 3.33984 17.4023 2.92969C16.9922 2.51953 16.5137 2.20703 15.9668 1.99219C15.4395 1.75781 14.8828 1.65039 14.2969 1.66992C13.7109 1.66992 13.1543 1.77734 12.627 1.99219C12.0801 2.22656 11.6016 2.54883 11.1914 2.95898L3.60352 10.6055C2.97852 11.2305 2.5 11.9531 2.16797 12.7734C1.83594 13.5742 1.66992 14.4141 1.66992 15.293C1.66992 16.1719 1.8457 17.0215 2.19727 17.8418C2.5293 18.6426 3.00781 19.3555 3.63281 19.9805C4.25781 20.6055 4.98047 21.084 5.80078 21.416C6.62109 21.748 7.4707 21.9141 8.34961 21.9141C9.22852 21.8945 10.0684 21.7188 10.8691 21.3867C11.6895 21.0547 12.4121 20.5762 13.0371 19.9512L18.5449 14.3848C18.7012 14.2285 18.8965 14.1504 19.1309 14.1504C19.3652 14.1504 19.5605 14.2285 19.7168 14.3848C19.873 14.541 19.9512 14.7363 19.9512 14.9707C19.9707 15.2051 19.8926 15.4004 19.7168 15.5566L14.1211 21.1816C13.3594 21.9434 12.4805 22.5293 11.4844 22.9395C10.4688 23.3496 9.42383 23.5547 8.34961 23.5547C8.33008 23.5547 8.04688 23.5547 7.5 23.5547C6.95312 23.5547 6.17188 23.3496 5.15625 22.9395C4.14062 22.5293 3.24219 21.9336 2.46094 21.1523C1.67969 20.3906 1.07422 19.502 0.644531 18.4863C0.234375 17.4707 0.0195312 16.4062 0 15.293V15.2637C0 14.1699 0.214844 13.125 0.644531 12.1289C1.05469 11.1133 1.64062 10.2148 2.40234 9.43359L10.0195 1.78711C10.5859 1.2207 11.2402 0.78125 11.9824 0.46875C12.7246 0.15625 13.4961 0 14.2969 0H14.3262C15.1074 0 15.8691 0.146484 16.6113 0.439453C17.3535 0.751953 18.0078 1.18164 18.5742 1.72852C19.1406 2.29492 19.5801 2.94922 19.8926 3.69141C20.2051 4.43359 20.3613 5.20508 20.3613 6.00586V6.03516C20.3613 6.83594 20.2148 7.59766 19.9219 8.32031C19.6094 9.0625 19.1699 9.7168 18.6035 10.2832L11.0156 17.9297ZM10.957 6.88477C11.0352 6.80664 11.1328 6.74805 11.25 6.70898C11.3477 6.66992 11.4453 6.65039 11.543 6.65039C11.6602 6.65039 11.7676 6.66992 11.8652 6.70898C11.9629 6.74805 12.0508 6.80664 12.1289 6.88477C12.207 6.96289 12.2754 7.05078 12.334 7.14844C12.373 7.24609 12.3926 7.35352 12.3926 7.4707C12.3926 7.56836 12.373 7.67578 12.334 7.79297C12.2754 7.89063 12.207 7.97852 12.1289 8.05664L6.62109 13.623C6.40625 13.8184 6.25 14.0527 6.15234 14.3262C6.03516 14.6191 5.97656 14.9121 5.97656 15.2051C5.97656 15.498 6.03516 15.7812 6.15234 16.0547C6.26953 16.3281 6.43555 16.5723 6.65039 16.7871C6.86523 17.002 7.10938 17.168 7.38281 17.2852C7.65625 17.3828 7.93945 17.4316 8.23242 17.4316C8.54492 17.4316 8.83789 17.373 9.11133 17.2559C9.38477 17.1387 9.62891 16.9824 9.84375 16.7871L11.0156 17.9297C10.6445 18.3008 10.2246 18.584 9.75586 18.7793C9.26758 18.9941 8.75977 19.1016 8.23242 19.1016C7.70508 19.1016 7.20703 19.0039 6.73828 18.8086C6.26953 18.6133 5.84961 18.3301 5.47852 17.959C5.10742 17.5879 4.82422 17.168 4.62891 16.6992C4.41406 16.2305 4.30664 15.7324 4.30664 15.2051V15.1758C4.30664 14.6875 4.4043 14.209 4.59961 13.7402C4.77539 13.291 5.0293 12.8809 5.36133 12.5098L10.957 6.88477Z"# diff --git a/Rosetta/Features/Chats/ChatDetail/MessageAvatarView.swift b/Rosetta/Features/Chats/ChatDetail/MessageAvatarView.swift new file mode 100644 index 0000000..1cd4e04 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/MessageAvatarView.swift @@ -0,0 +1,198 @@ +import SwiftUI + +// MARK: - MessageAvatarView + +/// Displays an avatar attachment inside a message bubble. +/// +/// Desktop parity: `MessageAvatar.tsx` β€” shows a bordered card with circular avatar +/// preview, "Avatar" title with lock icon, and descriptive text. +/// +/// States: +/// 1. **Cached** β€” avatar already in AttachmentCache, display immediately +/// 2. **Downloading** β€” show placeholder + spinner +/// 3. **Downloaded** β€” display avatar, auto-saved to AvatarRepository +/// 4. **Error** β€” "Avatar expired" or download error +struct MessageAvatarView: View { + + let attachment: MessageAttachment + let message: ChatMessage + let outgoing: Bool + + @State private var avatarImage: UIImage? + @State private var isDownloading = false + @State private var downloadError = false + + /// Avatar circle diameter (desktop parity: 60px). + private let avatarSize: CGFloat = 56 + + var body: some View { + HStack(spacing: 10) { + // Avatar circle (left side) + avatarCircle + + // Metadata (right side) + VStack(alignment: .leading, spacing: 3) { + // Title row: "Avatar" + lock icon + HStack(spacing: 4) { + Text("Avatar") + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(outgoing ? .white : RosettaColors.Adaptive.text) + + Image(systemName: "lock.fill") + .font(.system(size: 10)) + .foregroundStyle(Color.green.opacity(0.8)) + } + + // Description + Text("An avatar image shared in the message.") + .font(.system(size: 12)) + .foregroundStyle(outgoing ? .white.opacity(0.5) : RosettaColors.Adaptive.textSecondary) + .lineLimit(2) + + // Download state indicator + if isDownloading { + HStack(spacing: 4) { + ProgressView() + .tint(outgoing ? .white.opacity(0.6) : Color(hex: 0x008BFF)) + .scaleEffect(0.7) + Text("Downloading...") + .font(.system(size: 11)) + .foregroundStyle(outgoing ? .white.opacity(0.4) : RosettaColors.Adaptive.textSecondary) + } + .padding(.top, 2) + } else if downloadError { + Text("Avatar expired") + .font(.system(size: 11)) + .foregroundStyle(RosettaColors.error) + .padding(.top, 2) + } + } + + Spacer(minLength: 0) + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .background( + RoundedRectangle(cornerRadius: 8) + .stroke(outgoing ? Color.white.opacity(0.15) : Color.white.opacity(0.1), lineWidth: 1) + ) + .task { + loadFromCache() + if avatarImage == nil { + downloadAvatar() + } + } + } + + // MARK: - Avatar Circle + + @ViewBuilder + private var avatarCircle: some View { + ZStack { + if let avatarImage { + Image(uiImage: avatarImage) + .resizable() + .scaledToFill() + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + } else { + Circle() + .fill(outgoing ? Color.white.opacity(0.15) : Color.white.opacity(0.08)) + .frame(width: avatarSize, height: avatarSize) + .overlay { + if isDownloading { + ProgressView() + .tint(.white.opacity(0.5)) + } else { + Image(systemName: "person.fill") + .font(.system(size: 22)) + .foregroundStyle(.white.opacity(0.3)) + } + } + } + } + } + + // MARK: - Download + + private func loadFromCache() { + if let cached = AttachmentCache.shared.loadImage(forAttachmentId: attachment.id) { + avatarImage = cached + } + } + + private func downloadAvatar() { + guard !isDownloading, avatarImage == nil else { return } + + let tag = extractTag(from: attachment.preview) + guard !tag.isEmpty else { + downloadError = true + return + } + + guard let password = message.attachmentPassword, !password.isEmpty else { + print("🎭 [AvatarView] NO password for attachment \(attachment.id)") + downloadError = true + return + } + + print("🎭 [AvatarView] Downloading avatar \(attachment.id), tag=\(tag.prefix(20))…") + isDownloading = true + downloadError = false + + Task { + do { + let encryptedData = try await TransportManager.shared.downloadFile(tag: tag) + let encryptedString = String(decoding: encryptedData, as: UTF8.self) + + let decryptedData = try CryptoManager.shared.decryptWithPassword( + encryptedString, password: password + ) + + guard let decryptedString = String(data: decryptedData, encoding: .utf8) else { + throw TransportError.invalidResponse + } + + let downloadedImage: UIImage? + if decryptedString.hasPrefix("data:") { + if let commaIndex = decryptedString.firstIndex(of: ",") { + let base64Part = String(decryptedString[decryptedString.index(after: commaIndex)...]) + if let imageData = Data(base64Encoded: base64Part) { + downloadedImage = UIImage(data: imageData) + } else { + downloadedImage = nil + } + } else { + downloadedImage = nil + } + } else if let imageData = Data(base64Encoded: decryptedString) { + downloadedImage = UIImage(data: imageData) + } else { + downloadedImage = UIImage(data: decryptedData) + } + + await MainActor.run { + if let downloadedImage { + avatarImage = downloadedImage + AttachmentCache.shared.saveImage(downloadedImage, forAttachmentId: attachment.id) + } else { + downloadError = true + } + isDownloading = false + } + } catch { + await MainActor.run { + downloadError = true + isDownloading = false + } + } + } + } + + /// Extracts the server tag from preview string. + /// Format: "tag::blurhash" β†’ returns "tag". + private func extractTag(from preview: String) -> String { + let parts = preview.components(separatedBy: "::") + return parts.first ?? preview + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/MessageFileView.swift b/Rosetta/Features/Chats/ChatDetail/MessageFileView.swift new file mode 100644 index 0000000..afb1236 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/MessageFileView.swift @@ -0,0 +1,199 @@ +import SwiftUI + +// MARK: - MessageFileView + +/// Displays a file attachment inside a message bubble. +/// +/// Desktop parity: `MessageFile.tsx` β€” shows file icon + filename + size card. +/// Tap to download (if not cached), then share via UIActivityViewController. +/// +/// Preview format: "tag::filesize::filename" (desktop parity). +struct MessageFileView: View { + + let attachment: MessageAttachment + let message: ChatMessage + let outgoing: Bool + + @State private var isDownloading = false + @State private var downloadProgress: String = "" + @State private var isDownloaded = false + @State private var downloadError = false + @State private var cachedFileURL: URL? + + var body: some View { + HStack(spacing: 10) { + // File icon circle + ZStack { + Circle() + .fill(outgoing ? Color.white.opacity(0.2) : Color(hex: 0x008BFF).opacity(0.2)) + .frame(width: 40, height: 40) + + if isDownloading { + ProgressView() + .tint(outgoing ? .white : Color(hex: 0x008BFF)) + .scaleEffect(0.8) + } else if isDownloaded { + Image(systemName: fileIcon) + .font(.system(size: 18)) + .foregroundStyle(outgoing ? .white : Color(hex: 0x008BFF)) + } else { + Image(systemName: "arrow.down.circle.fill") + .font(.system(size: 18)) + .foregroundStyle(outgoing ? .white.opacity(0.7) : Color(hex: 0x008BFF).opacity(0.7)) + } + } + + // File metadata + VStack(alignment: .leading, spacing: 2) { + Text(fileName) + .font(.system(size: 14, weight: .medium)) + .foregroundStyle(outgoing ? .white : RosettaColors.Adaptive.text) + .lineLimit(1) + + if isDownloading { + Text("Downloading...") + .font(.system(size: 12)) + .foregroundStyle(outgoing ? .white.opacity(0.5) : RosettaColors.Adaptive.textSecondary) + } else if downloadError { + Text("File expired") + .font(.system(size: 12)) + .foregroundStyle(RosettaColors.error) + } else { + Text(formattedFileSize) + .font(.system(size: 12)) + .foregroundStyle( + outgoing ? .white.opacity(0.5) : RosettaColors.Adaptive.textSecondary + ) + } + } + + Spacer(minLength: 0) + } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .frame(width: 220) + .contentShape(Rectangle()) + .onTapGesture { + if isDownloaded, let url = cachedFileURL { + shareFile(url) + } else if !isDownloading { + downloadFile() + } + } + .task { + checkCache() + } + } + + // MARK: - Metadata Parsing + + /// Parses "tag::filesize::filename" preview format. + private var fileMetadata: (tag: String, size: Int, name: String) { + let parts = attachment.preview.components(separatedBy: "::") + let tag = parts.first ?? "" + let size = parts.count > 1 ? Int(parts[1]) ?? 0 : 0 + let name = parts.count > 2 ? parts[2] : "file" + return (tag, size, name) + } + + private var fileName: String { fileMetadata.name } + private var fileSize: Int { fileMetadata.size } + private var fileTag: String { fileMetadata.tag } + + private var formattedFileSize: String { + let bytes = fileSize + if bytes < 1024 { return "\(bytes) B" } + if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) } + return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) + } + + private var fileIcon: String { + let ext = (fileName as NSString).pathExtension.lowercased() + switch ext { + case "pdf": return "doc.fill" + case "zip", "rar", "7z": return "doc.zipper" + case "jpg", "jpeg", "png", "gif": return "photo.fill" + case "mp4", "mov", "avi": return "film.fill" + case "mp3", "wav", "aac": return "waveform" + default: return "doc.fill" + } + } + + // MARK: - Download + + private func checkCache() { + if let url = AttachmentCache.shared.fileURL(forAttachmentId: attachment.id, fileName: fileName) { + cachedFileURL = url + isDownloaded = true + } + } + + private func downloadFile() { + guard !isDownloading, !fileTag.isEmpty else { return } + guard let password = message.attachmentPassword, !password.isEmpty else { + downloadError = true + return + } + + isDownloading = true + downloadError = false + + Task { + do { + let encryptedData = try await TransportManager.shared.downloadFile(tag: fileTag) + let encryptedString = String(decoding: encryptedData, as: UTF8.self) + let decryptedData = try CryptoManager.shared.decryptWithPassword( + encryptedString, password: password + ) + + // Parse data URI if present, otherwise use raw data + let fileData: Data + if let decryptedString = String(data: decryptedData, encoding: .utf8), + decryptedString.hasPrefix("data:"), + let commaIndex = decryptedString.firstIndex(of: ",") { + let base64Part = String(decryptedString[decryptedString.index(after: commaIndex)...]) + fileData = Data(base64Encoded: base64Part) ?? decryptedData + } else { + fileData = decryptedData + } + + let url = AttachmentCache.shared.saveFile( + fileData, forAttachmentId: attachment.id, fileName: fileName + ) + + await MainActor.run { + cachedFileURL = url + isDownloaded = true + isDownloading = false + } + } catch { + await MainActor.run { + downloadError = true + isDownloading = false + } + } + } + } + + // MARK: - Share + + private func shareFile(_ url: URL) { + let activityVC = UIActivityViewController( + activityItems: [url], + applicationActivities: nil + ) + if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = scene.windows.first, + let rootVC = window.rootViewController { + var topVC = rootVC + while let presented = topVC.presentedViewController { + topVC = presented + } + if let popover = activityVC.popoverPresentationController { + popover.sourceView = topVC.view + popover.sourceRect = CGRect(x: topVC.view.bounds.midX, y: topVC.view.bounds.midY, width: 0, height: 0) + } + topVC.present(activityVC, animated: true) + } + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/MessageImageView.swift b/Rosetta/Features/Chats/ChatDetail/MessageImageView.swift new file mode 100644 index 0000000..7a97021 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/MessageImageView.swift @@ -0,0 +1,179 @@ +import SwiftUI + +// MARK: - MessageImageView + +/// Displays an image attachment inside a message bubble. +/// +/// Desktop parity: `MessageImage.tsx` β€” shows blur placeholder while downloading, +/// full image after download, "Image expired" on error. +/// +/// States: +/// 1. **Cached** β€” image already in AttachmentCache, display immediately +/// 2. **Downloading** β€” show placeholder + spinner +/// 3. **Downloaded** β€” display image, tap for full-screen (future) +/// 4. **Error** β€” "Image expired" or download error +struct MessageImageView: View { + + let attachment: MessageAttachment + let message: ChatMessage + let outgoing: Bool + let maxWidth: CGFloat + + @State private var image: UIImage? + @State private var isDownloading = false + @State private var downloadError = false + + /// Desktop parity: image bubble max dimensions. + private let maxImageWidth: CGFloat = 240 + private let maxImageHeight: CGFloat = 280 + + var body: some View { + Group { + if let image { + Image(uiImage: image) + .resizable() + .scaledToFit() + .frame(maxWidth: min(maxImageWidth, maxWidth - 20)) + .frame(maxHeight: maxImageHeight) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } else if isDownloading { + placeholder + .overlay { ProgressView().tint(.white) } + } else if downloadError { + placeholder + .overlay { + VStack(spacing: 4) { + Image(systemName: "flame.fill") + .font(.system(size: 20)) + .foregroundStyle(.white.opacity(0.5)) + Text("Image expired") + .font(.system(size: 11)) + .foregroundStyle(.white.opacity(0.4)) + } + } + } else { + placeholder + .overlay { + Image(systemName: "arrow.down.circle") + .font(.system(size: 24)) + .foregroundStyle(.white.opacity(0.6)) + } + .onTapGesture { downloadImage() } + } + } + .task { + loadFromCache() + if image == nil { + downloadImage() + } + } + } + + // MARK: - Placeholder + + private var placeholder: some View { + RoundedRectangle(cornerRadius: 12) + .fill(Color.white.opacity(0.08)) + .frame(width: 200, height: 150) + } + + // MARK: - Download + + private func loadFromCache() { + if let cached = AttachmentCache.shared.loadImage(forAttachmentId: attachment.id) { + image = cached + } + } + + private func downloadImage() { + guard !isDownloading, image == nil else { return } + + // Extract tag from preview ("tag::blurhash" β†’ tag) + let tag = extractTag(from: attachment.preview) + guard !tag.isEmpty else { + print("πŸ–ΌοΈ [ImageView] tag is empty for attachment \(attachment.id)") + downloadError = true + return + } + + guard let password = message.attachmentPassword, !password.isEmpty else { + print("πŸ–ΌοΈ [ImageView] NO password for attachment \(attachment.id), preview=\(attachment.preview.prefix(40))") + downloadError = true + return + } + + print("πŸ–ΌοΈ [ImageView] Downloading attachment \(attachment.id), tag=\(tag.prefix(20))…, passwordLen=\(password.count)") + + isDownloading = true + downloadError = false + + Task { + do { + // Download encrypted blob from transport server + let encryptedData = try await TransportManager.shared.downloadFile(tag: tag) + let encryptedString = String(decoding: encryptedData, as: UTF8.self) + print("πŸ–ΌοΈ [ImageView] Downloaded \(encryptedData.count) bytes, encryptedString.prefix=\(encryptedString.prefix(80))…") + print("πŸ–ΌοΈ [ImageView] Password UTF-8 bytes: \(Array(password.utf8).prefix(20).map { String(format: "%02x", $0) }.joined(separator: " "))") + + // Decrypt with attachment password + let decryptedData = try CryptoManager.shared.decryptWithPassword( + encryptedString, password: password + ) + + print("πŸ–ΌοΈ [ImageView] Decrypted \(decryptedData.count) bytes, first20hex=\(decryptedData.prefix(20).map { String(format: "%02x", $0) }.joined(separator: " "))") + + // Parse data URI β†’ extract base64 β†’ UIImage + guard let decryptedString = String(data: decryptedData, encoding: .utf8) else { + print("πŸ–ΌοΈ [ImageView] ❌ Decrypted data is NOT valid UTF-8! first50hex=\(decryptedData.prefix(50).map { String(format: "%02x", $0) }.joined(separator: " "))") + throw TransportError.invalidResponse + } + + let downloadedImage: UIImage? + if decryptedString.hasPrefix("data:") { + // Data URI format: "data:image/jpeg;base64,..." + if let commaIndex = decryptedString.firstIndex(of: ",") { + let base64Part = String(decryptedString[decryptedString.index(after: commaIndex)...]) + if let imageData = Data(base64Encoded: base64Part) { + downloadedImage = UIImage(data: imageData) + } else { + downloadedImage = nil + } + } else { + downloadedImage = nil + } + } else if let imageData = Data(base64Encoded: decryptedString) { + // Plain base64 (fallback) + downloadedImage = UIImage(data: imageData) + } else { + // Raw image data + downloadedImage = UIImage(data: decryptedData) + } + + await MainActor.run { + if let downloadedImage { + print("πŸ–ΌοΈ [ImageView] βœ… Image decoded successfully for \(attachment.id)") + image = downloadedImage + AttachmentCache.shared.saveImage(downloadedImage, forAttachmentId: attachment.id) + } else { + print("πŸ–ΌοΈ [ImageView] ❌ Failed to decode image data for \(attachment.id)") + downloadError = true + } + isDownloading = false + } + } catch { + print("πŸ–ΌοΈ [ImageView] ❌ Error for \(attachment.id): \(error.localizedDescription)") + await MainActor.run { + downloadError = true + isDownloading = false + } + } + } + } + + /// Extracts the server tag from preview string. + /// Format: "tag::blurhash" or "tag::" β†’ returns "tag". + private func extractTag(from preview: String) -> String { + let parts = preview.components(separatedBy: "::") + return parts.first ?? preview + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/OpponentProfileView.swift b/Rosetta/Features/Chats/ChatDetail/OpponentProfileView.swift new file mode 100644 index 0000000..7f3e4ac --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/OpponentProfileView.swift @@ -0,0 +1,251 @@ +import SwiftUI + +/// Profile screen for viewing opponent (other user) information. +/// Pushed from ChatDetailView when tapping the toolbar capsule or avatar. +/// +/// Desktop parity: ProfileCard (avatar + name + subtitle) β†’ +/// Username section (copyable) β†’ Public Key section (copyable). +struct OpponentProfileView: View { + let route: ChatRoute + + @Environment(\.dismiss) private var dismiss + @State private var copiedField: String? + + // MARK: - Computed properties + + private var dialog: Dialog? { + DialogRepository.shared.dialogs[route.publicKey] + } + + private var displayName: String { + if let dialog, !dialog.opponentTitle.isEmpty { return dialog.opponentTitle } + if !route.title.isEmpty { return route.title } + if let dialog, !dialog.opponentUsername.isEmpty { return "@\(dialog.opponentUsername)" } + if !route.username.isEmpty { return "@\(route.username)" } + return String(route.publicKey.prefix(12)) + } + + private var username: String { + if let dialog, !dialog.opponentUsername.isEmpty { return dialog.opponentUsername } + return route.username + } + + private var effectiveVerified: Int { + if let dialog { return dialog.effectiveVerified } + if route.verified > 0 { return route.verified } + return 0 + } + + private var avatarInitials: String { + RosettaColors.initials(name: displayName, publicKey: route.publicKey) + } + + private var avatarColorIndex: Int { + RosettaColors.avatarColorIndex(for: displayName, publicKey: route.publicKey) + } + + private var opponentAvatar: UIImage? { + AvatarRepository.shared.loadAvatar(publicKey: route.publicKey) + } + + /// Desktop parity: @username β€’ shortKey + private var subtitleText: String { + let shortKey = route.publicKey.prefix(4) + "..." + route.publicKey.suffix(4) + if !username.isEmpty { + return "@\(username) Β· \(shortKey)" + } + return String(shortKey) + } + + // MARK: - Body + + var body: some View { + ScrollView { + VStack(spacing: 0) { + profileCard + .padding(.top, 32) + + infoSections + .padding(.top, 32) + .padding(.horizontal, 16) + } + } + .scrollIndicators(.hidden) + .background(RosettaColors.Adaptive.background.ignoresSafeArea()) + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .enableSwipeBack() + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { dismiss() } label: { backButtonLabel } + .buttonStyle(.plain) + } + } + .toolbarBackground(.hidden, for: .navigationBar) + } + + // MARK: - Back Button + + private var backButtonLabel: some View { + TelegramVectorIcon( + pathData: TelegramIconPath.backChevron, + viewBox: CGSize(width: 11, height: 20), + color: .white + ) + .frame(width: 11, height: 20) + .allowsHitTesting(false) + .frame(width: 36, height: 36) + .frame(height: 44) + .padding(.horizontal, 4) + .background { glassCapsule() } + } + + // MARK: - Profile Card (Desktop: ProfileCard component) + + private var profileCard: some View { + VStack(spacing: 0) { + AvatarView( + initials: avatarInitials, + colorIndex: avatarColorIndex, + size: 100, + isOnline: false, + image: opponentAvatar + ) + + HStack(spacing: 5) { + Text(displayName) + .font(.system(size: 22, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.text) + .lineLimit(2) + .multilineTextAlignment(.center) + + if effectiveVerified > 0 { + VerifiedBadge(verified: effectiveVerified, size: 18, badgeTint: .white) + } + } + .padding(.top, 12) + .padding(.horizontal, 32) + + Text(subtitleText) + .font(.system(size: 14)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .padding(.top, 4) + } + } + + // MARK: - Info Sections (Desktop: SettingsInput.Copy rows) + + private var infoSections: some View { + VStack(spacing: 16) { + if !username.isEmpty { + copyRow( + label: "Username", + value: "@\(username)", + rawValue: username, + fieldId: "username", + helper: "Username for search user or send message." + ) + } + + copyRow( + label: "Public Key", + value: route.publicKey, + rawValue: route.publicKey, + fieldId: "publicKey", + helper: "This is user public key. If user haven't set a @username yet, you can send message using public key." + ) + } + } + + // MARK: - Copy Row (Desktop: SettingsInput.Copy) + + private func copyRow( + label: String, + value: String, + rawValue: String, + fieldId: String, + helper: String + ) -> some View { + VStack(alignment: .leading, spacing: 6) { + Button { + UIPasteboard.general.string = rawValue + withAnimation(.easeInOut(duration: 0.2)) { copiedField = fieldId } + Task { @MainActor in + try? await Task.sleep(for: .seconds(1.5)) + withAnimation(.easeInOut(duration: 0.2)) { + if copiedField == fieldId { copiedField = nil } + } + } + } label: { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(label) + .font(.system(size: 13)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + + Text(copiedField == fieldId ? "Copied" : value) + .font(.system(size: 16)) + .foregroundStyle( + copiedField == fieldId + ? RosettaColors.online + : RosettaColors.Adaptive.text + ) + .lineLimit(1) + .truncationMode(.middle) + } + + Spacer() + + Image(systemName: copiedField == fieldId ? "checkmark" : "doc.on.doc") + .font(.system(size: 13)) + .foregroundStyle( + copiedField == fieldId + ? RosettaColors.online + : RosettaColors.Adaptive.textSecondary + ) + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + .background { glassCard() } + } + .buttonStyle(.plain) + + Text(helper) + .font(.system(size: 12)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.7)) + .padding(.horizontal, 8) + } + } + + // MARK: - Glass helpers + + @ViewBuilder + private func glassCapsule() -> some View { + if #available(iOS 26.0, *) { + Capsule().fill(.clear).glassEffect(.regular, in: .capsule) + } else { + Capsule().fill(.thinMaterial) + .overlay { Capsule().strokeBorder(Color.white.opacity(0.22), lineWidth: 0.5) } + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } + + @ViewBuilder + private func glassCard() -> some View { + if #available(iOS 26.0, *) { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(.clear) + .glassEffect( + .regular, + in: RoundedRectangle(cornerRadius: 14, style: .continuous) + ) + } else { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(.ultraThinMaterial) + .overlay { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .strokeBorder(Color.white.opacity(0.12), lineWidth: 0.5) + } + } + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/PendingAttachment.swift b/Rosetta/Features/Chats/ChatDetail/PendingAttachment.swift new file mode 100644 index 0000000..4f9c60b --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/PendingAttachment.swift @@ -0,0 +1,91 @@ +import UIKit + +// MARK: - PendingAttachment + +/// Represents an attachment selected by the user but not yet sent. +/// Used in the attachment preview strip above the compositor. +/// +/// Desktop parity: the `attachments` state array in `DialogInput.tsx` before +/// `prepareAttachmentsToSend()` processes and uploads them. +struct PendingAttachment: Identifiable, Sendable { + + /// Random 8-character alphanumeric ID (matches desktop's `generateRandomKey(8)`). + let id: String + + /// Attachment type β€” `.image` or `.file` for user-initiated sends. + let type: AttachmentType + + /// Raw image/file data (pre-compression for images). + let data: Data + + /// Thumbnail for preview (images only). `nil` for files. + let thumbnail: UIImage? + + /// Original file name (files only). `nil` for images. + let fileName: String? + + /// File size in bytes (files only). `nil` for images. + let fileSize: Int? + + // MARK: - Factory + + /// Creates a PendingAttachment from a UIImage (compressed to JPEG). + static func fromImage(_ image: UIImage) -> PendingAttachment { + let id = generateRandomId() + // Resize to max 1280px on longest side for mobile optimization + let resized = resizeImage(image, maxDimension: 1280) + let data = resized.jpegData(compressionQuality: 0.8) ?? Data() + let thumbnail = resizeImage(image, maxDimension: 200) + + return PendingAttachment( + id: id, + type: .image, + data: data, + thumbnail: thumbnail, + fileName: nil, + fileSize: nil + ) + } + + /// Creates a PendingAttachment from file data + metadata. + static func fromFile(data: Data, fileName: String) -> PendingAttachment { + return PendingAttachment( + id: generateRandomId(), + type: .file, + data: data, + thumbnail: nil, + fileName: fileName, + fileSize: data.count + ) + } + + // MARK: - Helpers + + /// Generates a random 8-character ID (desktop: `generateRandomKey(8)`). + private static func generateRandomId() -> String { + let chars = "abcdefghijklmnopqrstuvwxyz0123456789" + return String((0..<8).map { _ in chars.randomElement()! }) + } + + /// Resizes image so longest side is at most `maxDimension`. + private static func resizeImage(_ image: UIImage, maxDimension: CGFloat) -> UIImage { + let size = image.size + let maxSide = max(size.width, size.height) + guard maxSide > maxDimension else { return image } + + let scale = maxDimension / maxSide + let newSize = CGSize(width: size.width * scale, height: size.height * scale) + + let renderer = UIGraphicsImageRenderer(size: newSize) + return renderer.image { _ in + image.draw(in: CGRect(origin: .zero, size: newSize)) + } + } +} + +// MARK: - Constants + +extension PendingAttachment { + /// Desktop parity: `MAX_ATTACHMENTS_IN_MESSAGE = 5`. + static let maxAttachmentsPerMessage = 5 +} diff --git a/Rosetta/Features/Chats/ChatDetail/PhotoGridView.swift b/Rosetta/Features/Chats/ChatDetail/PhotoGridView.swift new file mode 100644 index 0000000..e34b16f --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/PhotoGridView.swift @@ -0,0 +1,356 @@ +import SwiftUI +import Photos + +// MARK: - PhotoGridView + +/// Custom photo library grid matching Figma attachment panel design (1:1). +/// +/// Figma layout (node 3994:39103): +/// ``` +/// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +/// β”‚ β”‚ Photo[0] β”‚ Photo[1] β”‚ ← row 1 +/// β”‚ Camera β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +/// β”‚ (live) β”‚ Photo[2] β”‚ Photo[3] β”‚ ← row 2 +/// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +/// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +/// β”‚ Photo[4] β”‚ Photo[5] β”‚ Photo[6] β”‚ ← row 3 (LazyVGrid) +/// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +/// ``` +/// +/// Camera tile: col 1, rows 1–2, shows live rear camera feed (AVCaptureSession). +/// Photos: square tiles with selection circle (22pt, white border 1.5pt). +/// 1px spacing between all tiles. +struct PhotoGridView: View { + + @Binding var selectedAssets: [PHAsset] + let maxSelection: Int + let onCameraTap: () -> Void + var onPhotoPreview: ((PHAsset) -> Void)? = nil + + @State private var assets: [PHAsset] = [] + @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined + + private let imageManager = PHCachingImageManager() + private let columns = 3 + private let spacing: CGFloat = 1 + + var body: some View { + Group { + switch authorizationStatus { + case .authorized, .limited: + photoGrid + case .denied, .restricted: + permissionDeniedView + default: + requestingView + } + } + .task { + await requestPhotoAccess() + } + } + + // MARK: - Photo Grid + + private var photoGrid: some View { + GeometryReader { geometry in + let tileSize = (geometry.size.width - spacing * CGFloat(columns - 1)) / CGFloat(columns) + let cameraHeight = tileSize * 2 + spacing + + ScrollView { + VStack(spacing: spacing) { + // Header: Camera (2 rows) + 4 photos (2Γ—2 grid) + headerSection(tileSize: tileSize, cameraHeight: cameraHeight) + + // Remaining photos: standard 3-column grid + remainingPhotosGrid(tileSize: tileSize, startIndex: 4) + } + .padding(.bottom, 100) // Space for tab bar + send button + } + } + } + + /// Header section: Camera tile (col 1, rows 1-2) + 4 photos (cols 2-3, rows 1-2). + @ViewBuilder + private func headerSection(tileSize: CGFloat, cameraHeight: CGFloat) -> some View { + HStack(alignment: .top, spacing: spacing) { + // Camera tile β€” spans 2 rows (double height) + CameraPreviewTile( + width: tileSize, + height: cameraHeight, + onTap: onCameraTap + ) + + // Right side: 2 rows Γ— 2 columns of photos + VStack(spacing: spacing) { + // Row 1: photos[0], photos[1] + HStack(spacing: spacing) { + headerPhotoTile(index: 0, size: tileSize) + headerPhotoTile(index: 1, size: tileSize) + } + + // Row 2: photos[2], photos[3] + HStack(spacing: spacing) { + headerPhotoTile(index: 2, size: tileSize) + headerPhotoTile(index: 3, size: tileSize) + } + } + } + } + + /// Single photo tile in the header section. + @ViewBuilder + private func headerPhotoTile(index: Int, size: CGFloat) -> some View { + if index < assets.count { + let asset = assets[index] + let isSelected = selectedAssets.contains(where: { $0.localIdentifier == asset.localIdentifier }) + let selectionIndex = selectedAssets.firstIndex(where: { $0.localIdentifier == asset.localIdentifier }) + + PhotoTile( + asset: asset, + imageManager: imageManager, + size: size, + isSelected: isSelected, + selectionNumber: selectionIndex.map { $0 + 1 }, + onTap: { handlePhotoTap(asset) }, + onCircleTap: { toggleSelection(asset) } + ) + .frame(width: size, height: size) + } else { + Color(white: 0.15) + .frame(width: size, height: size) + } + } + + /// Remaining photos after the header, displayed as a standard 3-column LazyVGrid. + @ViewBuilder + private func remainingPhotosGrid(tileSize: CGFloat, startIndex: Int) -> some View { + let remaining = assets.count > startIndex ? Array(assets[startIndex...]) : [] + + LazyVGrid( + columns: Array(repeating: GridItem(.flexible(), spacing: spacing), count: columns), + spacing: spacing + ) { + ForEach(0.. Void + + var body: some View { + ZStack(alignment: .topTrailing) { + // Live camera preview (or placeholder on simulator) + CameraPreviewView() + .frame(width: width, height: height) + .clipped() + + // Camera icon overlay (Figma: top-right) + Image(systemName: "camera.fill") + .font(.system(size: 18)) + .foregroundStyle(.white) + .shadow(color: .black.opacity(0.5), radius: 2, x: 0, y: 1) + .padding(8) + } + .frame(width: width, height: height) + .contentShape(Rectangle()) + .onTapGesture(perform: onTap) + } +} + +// MARK: - PhotoTile + +/// Single photo tile in the grid with async thumbnail loading and selection circle. +/// +/// Figma: square aspect ratio, selection circle (22pt, white border 1.5pt) in +/// top-right corner with 6pt padding. Selected: blue #008BFF filled circle +/// with white number. +/// +/// Two tap targets: +/// - Photo body (`onTap`): selects if unselected, opens preview if selected +/// - Selection circle (`onCircleTap`): always toggles selection (deselects if selected) +private struct PhotoTile: View { + let asset: PHAsset + let imageManager: PHCachingImageManager + let size: CGFloat + let isSelected: Bool + let selectionNumber: Int? + let onTap: () -> Void + let onCircleTap: () -> Void + + @State private var thumbnail: UIImage? + + var body: some View { + ZStack(alignment: .topTrailing) { + // Photo thumbnail β€” body tap + Group { + if let thumbnail { + Image(uiImage: thumbnail) + .resizable() + .scaledToFill() + .frame(width: size, height: size) + .clipped() + } else { + Color(white: 0.15) + .frame(width: size, height: size) + } + } + .contentShape(Rectangle()) + .onTapGesture(perform: onTap) + + // Selection circle (Figma: 22pt, top-right, 6pt inset) β€” circle tap + selectionCircle + .padding(6) + .contentShape(Circle()) + .onTapGesture(perform: onCircleTap) + } + .task(id: asset.localIdentifier) { + loadThumbnail() + } + } + + @ViewBuilder + private var selectionCircle: some View { + if isSelected { + ZStack { + Circle() + .fill(Color(hex: 0x008BFF)) + .frame(width: 22, height: 22) + if let number = selectionNumber { + Text("\(number)") + .font(.system(size: 12, weight: .bold)) + .foregroundStyle(.white) + } + } + } else { + Circle() + .strokeBorder(Color.white, lineWidth: 1.5) + .frame(width: 22, height: 22) + } + } + + private func loadThumbnail() { + let scale = UIScreen.main.scale + let targetSize = CGSize(width: size * scale, height: size * scale) + let options = PHImageRequestOptions() + options.deliveryMode = .opportunistic + options.isNetworkAccessAllowed = true + options.resizeMode = .fast + + imageManager.requestImage( + for: asset, + targetSize: targetSize, + contentMode: .aspectFill, + options: options + ) { image, _ in + if let image { + DispatchQueue.main.async { + self.thumbnail = image + } + } + } + } +} diff --git a/Rosetta/Features/Chats/ChatDetail/PhotoPreviewView.swift b/Rosetta/Features/Chats/ChatDetail/PhotoPreviewView.swift new file mode 100644 index 0000000..79f6081 --- /dev/null +++ b/Rosetta/Features/Chats/ChatDetail/PhotoPreviewView.swift @@ -0,0 +1,361 @@ +import Photos +import SwiftUI + +// MARK: - PhotoPreviewView + +/// Full-screen photo preview with editing toolbar and caption input. +/// +/// Presented via `.fullScreenCover` with transparent background. +/// Dismissible by swipe-down drag gesture or back button. +/// On swipe-down, the presenting view (attachment panel) is visible behind. +/// +/// Layout (Telegram-style): +/// ``` +/// +----------------------------------+ +/// | [radio button] | +/// | | +/// | [Full photo] | +/// | | +/// |-----------------------------------| +/// | Add a caption... [emoji] [done]| <- checkmark OUTSIDE bar +/// |-----------------------------------| +/// | [<] [crop] [Aa] [adj] [SD] [>] | <- toolbar row +/// +-----------------------------------+ +/// ``` +struct PhotoPreviewView: View { + + let asset: PHAsset + let isSelected: Bool + let selectionNumber: Int? + @Binding var captionText: String + let onSend: (UIImage) -> Void + let onToggleSelect: () -> Void + + @Environment(\.dismiss) private var dismiss + @State private var fullImage: UIImage? + @State private var isLoading = true + @State private var dragOffset: CGFloat = 0 + @FocusState private var isKeyboardFocused: Bool + + private var isKeyboardVisible: Bool { isKeyboardFocused } + + var body: some View { + ZStack { + // Semi-transparent background β€” fades on swipe to reveal content behind + Color.black + .opacity(max(0, 1.0 - dragOffset / 400)) + .ignoresSafeArea(.container) + + VStack(spacing: 0) { + topBar + + Spacer() + + photoContent + + Spacer() + + bottomSection + .padding(.bottom, 12) + } + } + .offset(y: dragOffset) + .gesture(dismissDragGesture) + .preferredColorScheme(.dark) + .task { + await loadFullResolutionImage() + } + } + + // MARK: - Drag to Dismiss + + private var dismissDragGesture: some Gesture { + DragGesture(minimumDistance: 20) + .onChanged { value in + if value.translation.height > 0 { + dragOffset = value.translation.height + } + } + .onEnded { value in + if value.translation.height > 120 { + dismiss() + } else { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + dragOffset = 0 + } + } + } + } + + // MARK: - Top Bar + + private var topBar: some View { + HStack { + Spacer() + + Button { + onToggleSelect() + } label: { + if isSelected { + ZStack { + Circle() + .fill(Color(hex: 0x008BFF)) + .frame(width: 28, height: 28) + if let number = selectionNumber { + Text("\(number)") + .font(.system(size: 14, weight: .bold)) + .foregroundStyle(.white) + } + } + } else { + Circle() + .strokeBorder(Color.white, lineWidth: 1.5) + .frame(width: 28, height: 28) + } + } + .frame(width: 44, height: 44) + } + .padding(.horizontal, 12) + .padding(.top, 8) + } + + // MARK: - Photo Content + + private var photoContent: some View { + Group { + if let fullImage { + Image(uiImage: fullImage) + .resizable() + .scaledToFit() + .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) + .padding(.horizontal, 8) + } else if isLoading { + ProgressView() + .tint(.white) + .scaleEffect(1.2) + } + } + } + + // MARK: - Bottom Section + + @ViewBuilder + private var bottomSection: some View { + VStack(spacing: 12) { + captionInputBar + .padding(.horizontal, 16) + + if !isKeyboardVisible { + toolbarRow + .padding(.horizontal, 12) + .transition(.opacity.combined(with: .move(edge: .bottom))) + } + } + .animation(.easeInOut(duration: 0.25), value: isKeyboardVisible) + } + + // MARK: - Caption Input Bar + + /// Inactive: placeholder centered, no icons, no checkmark. + /// Active (keyboard): placeholder slides left, emoji inside bar, checkmark OUTSIDE bar. + private var captionInputBar: some View { + HStack(spacing: 8) { + // Glass capsule input bar + HStack(spacing: 0) { + ZStack { + // Custom animated placeholder (slides center β†’ left) + if captionText.isEmpty { + Text("Add a caption...") + .font(.system(size: 16)) + .foregroundStyle(.white.opacity(0.4)) + .frame( + maxWidth: .infinity, + alignment: isKeyboardVisible ? .leading : .center + ) + .padding(.leading, isKeyboardVisible ? 16 : 0) + .allowsHitTesting(false) + } + + // TextField (no built-in placeholder, always left-aligned) + TextField("", text: $captionText) + .font(.system(size: 16)) + .foregroundStyle(.white) + .tint(Color(hex: 0x008BFF)) + .padding(.leading, 16) + .focused($isKeyboardFocused) + } + + // Emoji icon β€” only when keyboard active + if isKeyboardVisible { + TelegramVectorIcon( + pathData: TelegramIconPath.emojiMoon, + viewBox: CGSize(width: 19, height: 19), + color: RosettaColors.Adaptive.textSecondary + ) + .frame(width: 19, height: 19) + .padding(.trailing, 12) + } + } + .padding(.horizontal, 3) + .frame(height: 42) + .background { captionBarBackground } + + // Checkmark button OUTSIDE the bar (white circle + dark checkmark) + if isKeyboardVisible { + Button { + isKeyboardFocused = false + } label: { + ZStack { + Circle() + .fill(.white) + SingleCheckmarkShape() + .fill(Color.black.opacity(0.85)) + .frame(width: 14, height: 10) + } + .frame(width: 42, height: 42) + } + .buttonStyle(.plain) + .transition(.scale.combined(with: .opacity)) + } + } + .animation(.easeInOut(duration: 0.25), value: isKeyboardVisible) + } + + @ViewBuilder + private var captionBarBackground: some View { + if #available(iOS 26, *) { + RoundedRectangle(cornerRadius: 21, style: .continuous) + .fill(.clear) + .glassEffect(.regular, in: RoundedRectangle(cornerRadius: 21, style: .continuous)) + } else { + let shape = RoundedRectangle(cornerRadius: 21, style: .continuous) + shape.fill(.thinMaterial) + .overlay { shape.strokeBorder(Color.white.opacity(0.18), lineWidth: 0.5) } + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } + + // MARK: - Toolbar Row (Telegram 1:1 match) + + private var toolbarRow: some View { + HStack(spacing: 0) { + // Back button β€” white outlined circle with chevron (matches screenshot) + Button { + dismiss() + } label: { + ZStack { + Circle() + .strokeBorder(Color.white, lineWidth: 1.5) + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .medium)) + .foregroundStyle(.white) + } + .frame(width: 33, height: 33) + } + .buttonStyle(.plain) + + Spacer() + + // Center editing tools + HStack(spacing: 20) { + toolbarIconButton(systemName: "crop") + toolbarTextButton + toolbarIconButton(systemName: "slider.horizontal.3") + qualityBadge + } + + Spacer() + + // Send button β€” blue circle with arrow up (matches screenshot) + Button { + guard let image = fullImage else { return } + onSend(image) + } label: { + ZStack { + Circle() + .fill(Color(hex: 0x008BFF)) + Image(systemName: "arrow.up") + .font(.system(size: 17, weight: .bold)) + .foregroundStyle(.white) + } + .frame(width: 33, height: 33) + } + .buttonStyle(.plain) + } + } + + private func toolbarIconButton(systemName: String) -> some View { + Button { + // Phase 1: non-functional + } label: { + Image(systemName: systemName) + .font(.system(size: 20, weight: .medium)) + .foregroundStyle(.white) + .frame(width: 28, height: 28) + } + .buttonStyle(.plain) + } + + /// "Aa" text editing button. + private var toolbarTextButton: some View { + Button { + // Phase 1: non-functional + } label: { + Text("Aa") + .font(.system(size: 19, weight: .semibold)) + .foregroundStyle(.white) + .frame(width: 28, height: 28) + } + .buttonStyle(.plain) + } + + /// SD quality badge β€” dark filled background with border (matches Telegram). + private var qualityBadge: some View { + Button { + // Phase 1: non-functional + } label: { + Text("SD") + .font(.system(size: 13, weight: .bold)) + .foregroundStyle(.white) + .padding(.horizontal, 5) + .padding(.vertical, 2) + .background( + RoundedRectangle(cornerRadius: 4) + .fill(Color(white: 0.15)) + ) + .overlay( + RoundedRectangle(cornerRadius: 4) + .strokeBorder(Color.white.opacity(0.7), lineWidth: 1.5) + ) + } + .buttonStyle(.plain) + } + + // MARK: - Image Loading + + private func loadFullResolutionImage() async { + let manager = PHImageManager.default() + let image = await withCheckedContinuation { continuation in + let options = PHImageRequestOptions() + options.deliveryMode = .highQualityFormat + options.isNetworkAccessAllowed = true + options.isSynchronous = false + + manager.requestImage( + for: asset, + targetSize: PHImageManagerMaximumSize, + contentMode: .default, + options: options + ) { image, info in + let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false + if !isDegraded { + continuation.resume(returning: image) + } + } + } + await MainActor.run { + fullImage = image + isLoading = false + } + } +} diff --git a/Rosetta/Features/Chats/ChatList/ChatListView.swift b/Rosetta/Features/Chats/ChatList/ChatListView.swift index 7d41ea0..2343a00 100644 --- a/Rosetta/Features/Chats/ChatList/ChatListView.swift +++ b/Rosetta/Features/Chats/ChatList/ChatListView.swift @@ -20,7 +20,7 @@ final class ChatListNavigationState: ObservableObject { /// times per frame" β†’ app freeze. /// /// All `@Observable` access is isolated in dedicated child views: -/// - `DeviceVerificationBannersContainer` β†’ `ProtocolManager` +/// - `DeviceVerificationContentRouter` β†’ `ProtocolManager` /// - `ToolbarStoriesAvatar` β†’ `AccountManager` / `SessionManager` /// - `ChatListDialogContent` β†’ `DialogRepository` (via ViewModel) struct ChatListView: View { @@ -30,6 +30,7 @@ struct ChatListView: View { @StateObject private var navigationState = ChatListNavigationState() @State private var searchText = "" @State private var hasPinnedChats = false + @State private var showRequestChats = false @FocusState private var isSearchFocused: Bool var body: some View { @@ -84,11 +85,20 @@ struct ChatListView: View { } ) } + .navigationDestination(isPresented: $showRequestChats) { + RequestChatsView( + viewModel: viewModel, + navigationState: navigationState + ) + } .onAppear { - isDetailPresented = !navigationState.path.isEmpty + isDetailPresented = !navigationState.path.isEmpty || showRequestChats } .onChange(of: navigationState.path) { _, newPath in - isDetailPresented = !newPath.isEmpty + isDetailPresented = !newPath.isEmpty || showRequestChats + } + .onChange(of: showRequestChats) { _, showing in + isDetailPresented = !navigationState.path.isEmpty || showing } } .tint(RosettaColors.figmaBlue) @@ -223,25 +233,21 @@ private extension ChatListView { private extension ChatListView { @ViewBuilder var normalContent: some View { - VStack(spacing: 0) { - // Isolated view β€” reads ProtocolManager (@Observable) without - // polluting ChatListView's observation scope. - DeviceVerificationBannersContainer() - - // Isolated view β€” reads DialogRepository (@Observable) via viewModel - // without polluting ChatListView's observation scope. - ChatListDialogContent( - viewModel: viewModel, - navigationState: navigationState, - onPinnedStateChange: { pinned in - if hasPinnedChats != pinned { - withAnimation(.easeInOut(duration: 0.25)) { - hasPinnedChats = pinned - } + // Observation-isolated router β€” reads ProtocolManager in its own scope. + // Shows full-screen DeviceConfirmView when awaiting approval, + // or normal chat list with optional device approval banner otherwise. + DeviceVerificationContentRouter( + viewModel: viewModel, + navigationState: navigationState, + onShowRequests: { showRequestChats = true }, + onPinnedStateChange: { pinned in + if hasPinnedChats != pinned { + withAnimation(.easeInOut(duration: 0.25)) { + hasPinnedChats = pinned } } - ) - } + } + ) } } @@ -340,7 +346,7 @@ private extension ChatListView { // MARK: - Toolbar Background Modifier -private struct ChatListToolbarBackgroundModifier: ViewModifier { +struct ChatListToolbarBackgroundModifier: ViewModifier { func body(content: Content) -> some View { if #available(iOS 26, *) { content @@ -364,49 +370,38 @@ private struct ToolbarTitleView: View { let isSyncing = SessionManager.shared.syncBatchInProgress if state == .authenticated && isSyncing { - UpdatingDotsView() - } else { - let title: String = switch state { - case .authenticated: "Chats" - default: "Connecting..." - } - Text(title) + ToolbarStatusLabel(title: "Updating...") + } else if state == .authenticated { + Text("Chats") .font(.system(size: 17, weight: .semibold)) .foregroundStyle(RosettaColors.Adaptive.text) .contentTransition(.numericText()) .animation(.easeInOut(duration: 0.25), value: state) + } else { + ToolbarStatusLabel(title: "Connecting...") } } } -/// Desktop parity: "Updating..." with bouncing dots animation during sync. -private struct UpdatingDotsView: View { - @State private var activeDot = 0 - private let dotCount = 3 - private let timer = Timer.publish(every: 0.4, on: .main, in: .common).autoconnect() +/// Desktop parity: circular spinner + status text (Mantine `` equivalent). +private struct ToolbarStatusLabel: View { + let title: String + @State private var isSpinning = false var body: some View { - HStack(spacing: 1) { - Text("Updating") + HStack(spacing: 5) { + Circle() + .trim(from: 0.05, to: 0.75) + .stroke(RosettaColors.Adaptive.text, style: StrokeStyle(lineWidth: 1.5, lineCap: .round)) + .frame(width: 12, height: 12) + .rotationEffect(.degrees(isSpinning ? 360 : 0)) + .animation(.linear(duration: 1.2).repeatForever(autoreverses: false), value: isSpinning) + + Text(title) .font(.system(size: 17, weight: .semibold)) .foregroundStyle(RosettaColors.Adaptive.text) - - HStack(spacing: 2) { - ForEach(0.. Void + + var body: some View { + Button(action: onTap) { + HStack(spacing: 0) { + // Avatar: solid blue circle with white icon (62px) + ZStack { + Circle() + .fill(RosettaColors.primaryBlue) + .frame(width: 62, height: 62) + + Image(systemName: "tray.and.arrow.down") + .font(.system(size: 24, weight: .medium)) + .foregroundStyle(.white) + } + .padding(.trailing, 10) + + // Content section β€” matches ChatRowView.contentSection layout + VStack(alignment: .leading, spacing: 0) { + // Title row + Text("Request Chats") + .font(.system(size: 17, weight: .medium)) + .tracking(-0.43) + .foregroundStyle(RosettaColors.Adaptive.text) + .lineLimit(1) + + // Subtitle row (count) + Text(count == 1 ? "1 request" : "\(count) requests") + .font(.system(size: 15)) + .tracking(-0.23) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + .frame(height: 63, alignment: .top) + .padding(.top, 8) + } + .padding(.leading, 10) + .padding(.trailing, 16) + .frame(height: 78) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + } +} + +// MARK: - Device Verification Content Router (observation-isolated) /// Reads `ProtocolManager` in its own observation scope. /// During handshake, `connectionState` changes 4+ times rapidly β€” this view /// absorbs those re-renders instead of cascading them to the NavigationStack. -private struct DeviceVerificationBannersContainer: View { +/// +/// Device confirmation (THIS device waiting) is handled by full-screen overlay +/// in MainTabView (DeviceConfirmOverlay). This router only handles the +/// approval banner (ANOTHER device requesting access on primary device). +private struct DeviceVerificationContentRouter: View { + @ObservedObject var viewModel: ChatListViewModel + @ObservedObject var navigationState: ChatListNavigationState + var onShowRequests: () -> Void = {} + var onPinnedStateChange: (Bool) -> Void = { _ in } + var body: some View { let proto = ProtocolManager.shared - if proto.connectionState == .deviceVerificationRequired { - DeviceWaitingApprovalBanner() - } + VStack(spacing: 0) { + // Banner for approving ANOTHER device (primary device side) + if let pendingDevice = proto.pendingDeviceVerification { + DeviceApprovalBanner( + device: pendingDevice, + onAccept: { proto.acceptDevice(pendingDevice.deviceId) }, + onDecline: { proto.declineDevice(pendingDevice.deviceId) } + ) + } - if let pendingDevice = proto.pendingDeviceVerification { - DeviceApprovalBanner( - device: pendingDevice, - onAccept: { proto.acceptDevice(pendingDevice.deviceId) }, - onDecline: { proto.declineDevice(pendingDevice.deviceId) } + ChatListDialogContent( + viewModel: viewModel, + navigationState: navigationState, + onShowRequests: onShowRequests, + onPinnedStateChange: onPinnedStateChange ) } } @@ -483,27 +545,39 @@ private struct DeviceVerificationBannersContainer: View { private struct ChatListDialogContent: View { @ObservedObject var viewModel: ChatListViewModel @ObservedObject var navigationState: ChatListNavigationState + var onShowRequests: () -> Void = {} var onPinnedStateChange: (Bool) -> Void = { _ in } /// Desktop parity: track typing dialogs from MessageRepository (@Published). @State private var typingDialogs: Set = [] var body: some View { - let hasPinned = !viewModel.pinnedDialogs.isEmpty - if viewModel.filteredDialogs.isEmpty && !viewModel.isLoading { - SyncAwareEmptyState() - .onChange(of: hasPinned) { _, val in onPinnedStateChange(val) } - .onAppear { onPinnedStateChange(hasPinned) } - .onReceive(MessageRepository.shared.$typingDialogs) { typingDialogs = $0 } - } else { - dialogList - .onChange(of: hasPinned) { _, val in onPinnedStateChange(val) } - .onAppear { onPinnedStateChange(hasPinned) } - .onReceive(MessageRepository.shared.$typingDialogs) { typingDialogs = $0 } + // Compute once β€” avoids 3Γ— filter (allModeDialogs β†’ allModePinned β†’ allModeUnpinned). + let allDialogs = viewModel.allModeDialogs + let pinned = allDialogs.filter(\.isPinned) + let unpinned = allDialogs.filter { !$0.isPinned } + let requestsCount = viewModel.requestsCount + + Group { + if allDialogs.isEmpty && !viewModel.isLoading { + SyncAwareEmptyState() + } else { + dialogList( + pinned: pinned, + unpinned: unpinned, + requestsCount: requestsCount + ) + } + } + .onReceive(MessageRepository.shared.$typingDialogs) { typingDialogs = $0 } + .onAppear { + onPinnedStateChange(!pinned.isEmpty) } } - private var dialogList: some View { + // MARK: - Dialog List + + private func dialogList(pinned: [Dialog], unpinned: [Dialog], requestsCount: Int) -> some View { List { if viewModel.isLoading { ForEach(0..<8, id: \.self) { _ in @@ -513,15 +587,25 @@ private struct ChatListDialogContent: View { .listRowSeparator(.hidden) } } else { - if !viewModel.pinnedDialogs.isEmpty { - ForEach(Array(viewModel.pinnedDialogs.enumerated()), id: \.element.id) { index, dialog in - chatRow(dialog, isFirst: index == 0) + // Telegram-style "Request Chats" row at top (like Archived Chats) + if requestsCount > 0 { + RequestChatsRow(count: requestsCount, onTap: onShowRequests) + .listRowInsets(EdgeInsets()) + .listRowBackground(Color.clear) + .listRowSeparator(.visible, edges: .bottom) + .listRowSeparatorTint(RosettaColors.Adaptive.divider) + .alignmentGuide(.listRowSeparatorLeading) { _ in 82 } + } + + if !pinned.isEmpty { + ForEach(pinned, id: \.id) { dialog in + chatRow(dialog, isFirst: dialog.id == pinned.first?.id && requestsCount == 0) .environment(\.rowBackgroundColor, RosettaColors.Dark.pinnedSectionBackground) .listRowBackground(RosettaColors.Dark.pinnedSectionBackground) } } - ForEach(Array(viewModel.unpinnedDialogs.enumerated()), id: \.element.id) { index, dialog in - chatRow(dialog, isFirst: index == 0 && viewModel.pinnedDialogs.isEmpty) + ForEach(unpinned, id: \.id) { dialog in + chatRow(dialog, isFirst: dialog.id == unpinned.first?.id && pinned.isEmpty && requestsCount == 0) } } @@ -533,19 +617,20 @@ private struct ChatListDialogContent: View { .listStyle(.plain) .scrollContentBackground(.hidden) .scrollDismissesKeyboard(.immediately) + .scrollIndicators(.hidden) } private func chatRow(_ dialog: Dialog, isFirst: Bool = false) -> some View { /// Desktop parity: wrap in SyncAwareChatRow to isolate @Observable read /// of SessionManager.syncBatchInProgress from this view's observation scope. + /// viewModel + navigationState passed as plain `let` (not @ObservedObject) β€” + /// stable class references don't trigger row re-evaluation on parent re-render. SyncAwareChatRow( dialog: dialog, isTyping: typingDialogs.contains(dialog.opponentKey), isFirst: isFirst, - onTap: { navigationState.path.append(ChatRoute(dialog: dialog)) }, - onDelete: { withAnimation { viewModel.deleteDialog(dialog) } }, - onToggleMute: { viewModel.toggleMute(dialog) }, - onTogglePin: { viewModel.togglePin(dialog) } + viewModel: viewModel, + navigationState: navigationState ) } } @@ -555,18 +640,28 @@ private struct ChatListDialogContent: View { /// Reads `SessionManager.syncBatchInProgress` (@Observable) in its own /// observation scope. Without this wrapper, every sync state change would /// invalidate the entire `ChatListDialogContent.body` and rebuild all rows. -private struct SyncAwareChatRow: View { +/// Reads `SessionManager.syncBatchInProgress` (@Observable) in its own +/// observation scope. Without this wrapper, every sync state change would +/// invalidate the entire `ChatListDialogContent.body` and rebuild all rows. +/// +/// **Performance:** `viewModel` and `navigationState` are stored as plain `let` +/// (not @ObservedObject). Class references compare by pointer in SwiftUI's +/// memcmp-based view diffing β€” stable pointers mean unchanged rows are NOT +/// re-evaluated when the parent body rebuilds. Closures are defined inline +/// (not passed from parent) to avoid non-diffable closure props that force +/// every row dirty on every parent re-render. +struct SyncAwareChatRow: View { let dialog: Dialog let isTyping: Bool let isFirst: Bool - let onTap: () -> Void - let onDelete: () -> Void - let onToggleMute: () -> Void - let onTogglePin: () -> Void + let viewModel: ChatListViewModel + let navigationState: ChatListNavigationState var body: some View { let isSyncing = SessionManager.shared.syncBatchInProgress - Button(action: onTap) { + Button { + navigationState.path.append(ChatRoute(dialog: dialog)) + } label: { ChatRowView( dialog: dialog, isSyncing: isSyncing, @@ -575,62 +670,41 @@ private struct SyncAwareChatRow: View { } .buttonStyle(.plain) .listRowInsets(EdgeInsets()) - .listRowSeparator(isFirst ? .hidden : .visible, edges: .top) - .listRowSeparator(.visible, edges: .bottom) - .listRowSeparatorTint(RosettaColors.Adaptive.divider) - .alignmentGuide(.listRowSeparatorLeading) { _ in 82 } - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button(role: .destructive, action: onDelete) { - Label("Delete", systemImage: "trash") - } - - if !dialog.isSavedMessages { - Button(action: onToggleMute) { - Label( - dialog.isMuted ? "Unmute" : "Mute", - systemImage: dialog.isMuted ? "bell" : "bell.slash" - ) - } - .tint(dialog.isMuted ? .green : .indigo) - } - } - .swipeActions(edge: .leading, allowsFullSwipe: true) { - Button(action: onTogglePin) { - Label(dialog.isPinned ? "Unpin" : "Pin", systemImage: dialog.isPinned ? "pin.slash" : "pin") - } - .tint(.orange) - } - } -} - -// MARK: - Device Waiting Approval Banner - -/// Shown when THIS device needs approval from another Rosetta device. -private struct DeviceWaitingApprovalBanner: View { - var body: some View { - HStack(spacing: 12) { - Image(systemName: "lock.shield") - .font(.system(size: 22)) - .foregroundStyle(RosettaColors.warning) - - VStack(alignment: .leading, spacing: 2) { - Text("Waiting for device approval") - .font(.system(size: 14, weight: .semibold)) - .foregroundStyle(RosettaColors.Adaptive.text) - - Text("Open Rosetta on your other device and approve this login.") - .font(.system(size: 12, weight: .regular)) - .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .listRowSeparator(isFirst ? .hidden : .visible, edges: .top) + .listRowSeparator(.visible, edges: .bottom) + .listRowSeparatorTint(RosettaColors.Adaptive.divider) + .alignmentGuide(.listRowSeparatorLeading) { _ in 82 } + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + Button(role: .destructive) { + withAnimation { viewModel.deleteDialog(dialog) } + } label: { + Label("Delete", systemImage: "trash") } - Spacer(minLength: 0) + if !dialog.isSavedMessages { + Button { + viewModel.toggleMute(dialog) + } label: { + Label( + dialog.isMuted ? "Unmute" : "Mute", + systemImage: dialog.isMuted ? "bell" : "bell.slash" + ) + } + .tint(dialog.isMuted ? .green : .indigo) + } + } + .swipeActions(edge: .leading, allowsFullSwipe: true) { + Button { + viewModel.togglePin(dialog) + } label: { + Label(dialog.isPinned ? "Unpin" : "Pin", systemImage: dialog.isPinned ? "pin.slash" : "pin") + } + .tint(.orange) } - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(RosettaColors.warning.opacity(0.12)) } } + // MARK: - Device Approval Banner /// Shown on primary device when another device is requesting access. diff --git a/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift b/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift index 6c2afe0..e1c00d1 100644 --- a/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift +++ b/Rosetta/Features/Chats/ChatList/ChatListViewModel.swift @@ -2,6 +2,11 @@ import Combine import Foundation import os +// MARK: - Dialogs Mode (All vs Requests) + +/// Desktop parity: dialogs are split into "All" (iHaveSent) and "Requests" (only incoming). +enum DialogsMode: Hashable { case all, requests } + // MARK: - ChatListViewModel @MainActor @@ -12,6 +17,7 @@ final class ChatListViewModel: ObservableObject { // MARK: - State @Published var isLoading = false + @Published var dialogsMode: DialogsMode = .all /// NOT @Published β€” avoids 2Γ— body re-renders per keystroke in ChatListView. /// Local filtering uses `searchText` param directly in ChatListSearchContent. var searchQuery = "" @@ -35,16 +41,35 @@ final class ChatListViewModel: ObservableObject { // MARK: - Computed (dialog list for ChatListDialogContent) - /// Full dialog list β€” used by ChatListDialogContent which is only visible - /// when search is NOT active. Search filtering is done separately in - /// ChatListSearchContent using `searchText` parameter directly. + /// Filtered dialog list based on `dialogsMode`. + /// - `all`: dialogs where I have sent (+ Saved Messages + system accounts) + /// - `requests`: dialogs where only opponent has messaged me var filteredDialogs: [Dialog] { - DialogRepository.shared.sortedDialogs + let all = DialogRepository.shared.sortedDialogs + switch dialogsMode { + case .all: + return all.filter { + $0.iHaveSent || $0.isSavedMessages || SystemAccounts.isSystemAccount($0.opponentKey) + } + case .requests: + return all.filter { + !$0.iHaveSent && !$0.isSavedMessages && !SystemAccounts.isSystemAccount($0.opponentKey) + } + } } var pinnedDialogs: [Dialog] { filteredDialogs.filter(\.isPinned) } var unpinnedDialogs: [Dialog] { filteredDialogs.filter { !$0.isPinned } } + /// Number of request dialogs (incoming-only, not system, not self-chat). + var requestsCount: Int { + DialogRepository.shared.sortedDialogs.filter { + !$0.iHaveSent && !$0.isSavedMessages && !SystemAccounts.isSystemAccount($0.opponentKey) + }.count + } + + var hasRequests: Bool { requestsCount > 0 } + var totalUnreadCount: Int { DialogRepository.shared.dialogs.values .lazy.filter { !$0.isMuted } @@ -53,6 +78,27 @@ final class ChatListViewModel: ObservableObject { var hasUnread: Bool { totalUnreadCount > 0 } + // MARK: - Per-mode dialogs (for TabView pages) + + /// "All" dialogs β€” conversations where I have sent (+ Saved Messages + system accounts). + /// Used by the All page in the swipeable TabView. + var allModeDialogs: [Dialog] { + DialogRepository.shared.sortedDialogs.filter { + $0.iHaveSent || $0.isSavedMessages || SystemAccounts.isSystemAccount($0.opponentKey) + } + } + + var allModePinned: [Dialog] { allModeDialogs.filter(\.isPinned) } + var allModeUnpinned: [Dialog] { allModeDialogs.filter { !$0.isPinned } } + + /// "Requests" dialogs β€” conversations where only opponent has messaged me. + /// Used by the Requests page in the swipeable TabView. + var requestsModeDialogs: [Dialog] { + DialogRepository.shared.sortedDialogs.filter { + !$0.iHaveSent && !$0.isSavedMessages && !SystemAccounts.isSystemAccount($0.opponentKey) + } + } + // MARK: - Actions func setSearchQuery(_ query: String) { diff --git a/Rosetta/Features/Chats/ChatList/ChatRowView.swift b/Rosetta/Features/Chats/ChatList/ChatRowView.swift index fa41202..7a4a818 100644 --- a/Rosetta/Features/Chats/ChatList/ChatRowView.swift +++ b/Rosetta/Features/Chats/ChatList/ChatRowView.swift @@ -25,10 +25,6 @@ struct ChatRowView: View { /// Desktop parity: show "typing..." instead of last message. var isTyping: Bool = false - /// Desktop parity: recheck delivery timeout every 40s so clock β†’ error - /// transitions happen automatically without user scrolling. - @State private var now = Date() - private let recheckTimer = Timer.publish(every: 40, on: .main, in: .common).autoconnect() var displayTitle: String { if dialog.isSavedMessages { return "Saved Messages" } @@ -48,7 +44,6 @@ struct ChatRowView: View { .padding(.trailing, 16) .frame(height: 78) .contentShape(Rectangle()) - .onReceive(recheckTimer) { now = $0 } } } @@ -107,7 +102,7 @@ private extension ChatRowView { if !dialog.isSavedMessages && dialog.effectiveVerified > 0 { VerifiedBadge( verified: dialog.effectiveVerified, - size: 12 + size: 16 ) } @@ -145,8 +140,9 @@ private extension ChatRowView { if dialog.lastMessage.isEmpty { return "No messages yet" } - // Strip inline markdown markers for clean chat list preview - return dialog.lastMessage.replacingOccurrences(of: "**", with: "") + // Strip inline markdown markers and convert emoji shortcodes for clean preview. + let cleaned = dialog.lastMessage.replacingOccurrences(of: "**", with: "") + return EmojiParser.replaceShortcodes(in: cleaned) } } @@ -198,38 +194,20 @@ private extension ChatRowView { } } - /// Desktop parity: clock only within 80s of send, then error. - /// Delivered β†’ single check, Read β†’ double checks. - private static let maxWaitingSeconds: TimeInterval = 80 - @ViewBuilder var deliveryIcon: some View { switch dialog.lastMessageDelivered { case .waiting: - if isWithinWaitingWindow { - Image(systemName: "clock") - .font(.system(size: 13)) - .foregroundStyle(RosettaColors.Adaptive.textSecondary) - } else { - Image(systemName: "exclamationmark.circle.fill") - .font(.system(size: 14)) - .foregroundStyle(RosettaColors.error) - } + // Timer isolated to sub-view β€” only .waiting rows create a timer. + DeliveryWaitingIcon(sentTimestamp: dialog.lastMessageTimestamp) case .delivered: - Image(systemName: "checkmark") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(RosettaColors.figmaBlue) + SingleCheckmarkShape() + .fill(RosettaColors.Adaptive.textSecondary) + .frame(width: 14, height: 10.3) case .read: - Image(systemName: "checkmark") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(RosettaColors.figmaBlue) - .overlay(alignment: .leading) { - Image(systemName: "checkmark") - .font(.system(size: 12, weight: .bold)) - .foregroundStyle(RosettaColors.figmaBlue) - .offset(x: -4) - } - .padding(.trailing, 2) + DoubleCheckmarkShape() + .fill(RosettaColors.figmaBlue) + .frame(width: 17, height: 9.3) case .error: Image(systemName: "exclamationmark.circle.fill") .font(.system(size: 14)) @@ -237,12 +215,6 @@ private extension ChatRowView { } } - private var isWithinWaitingWindow: Bool { - guard dialog.lastMessageTimestamp > 0 else { return true } - let sentDate = Date(timeIntervalSince1970: Double(dialog.lastMessageTimestamp) / 1000) - return now.timeIntervalSince(sentDate) < Self.maxWaitingSeconds - } - var unreadBadge: some View { let count = dialog.unreadCount let text = count > 999 ? "\(count / 1000)K" : (count > 99 ? "99+" : "\(count)") @@ -266,6 +238,37 @@ private extension ChatRowView { } } +// MARK: - Delivery Waiting Icon (timer-isolated) + +/// Desktop parity: clock β†’ error after 80s. Timer only exists on rows with +/// `.waiting` delivery status β€” all other rows have zero timer overhead. +private struct DeliveryWaitingIcon: View { + let sentTimestamp: Int64 + @State private var now = Date() + private let recheckTimer = Timer.publish(every: 40, on: .main, in: .common).autoconnect() + + private var isWithinWindow: Bool { + guard sentTimestamp > 0 else { return true } + let sentDate = Date(timeIntervalSince1970: Double(sentTimestamp) / 1000) + return now.timeIntervalSince(sentDate) < 80 + } + + var body: some View { + Group { + if isWithinWindow { + Image(systemName: "clock") + .font(.system(size: 13)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + } else { + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 14)) + .foregroundStyle(RosettaColors.error) + } + } + .onReceive(recheckTimer) { now = $0 } + } +} + // MARK: - Time Formatting private extension ChatRowView { diff --git a/Rosetta/Features/Chats/ChatList/DeviceConfirmView.swift b/Rosetta/Features/Chats/ChatList/DeviceConfirmView.swift new file mode 100644 index 0000000..773a52c --- /dev/null +++ b/Rosetta/Features/Chats/ChatList/DeviceConfirmView.swift @@ -0,0 +1,101 @@ +import Lottie +import SwiftUI + +/// Full-screen device confirmation overlay β€” shown when THIS device +/// needs approval from another Rosetta device (desktop parity: DeviceConfirm.tsx). +/// +/// Displayed as an overlay in MainTabView, covering nav bar, search, and tab bar. +struct DeviceConfirmView: View { + private let deviceName = UIDevice.current.name + + var body: some View { + ZStack { + // Full black background covering everything + RosettaColors.Dark.background + .ignoresSafeArea() + + VStack(spacing: 0) { + Spacer() + + // Inbox animation (desktop parity: inbox.json) + LottieView( + animationName: "inbox", + loopMode: .loop, + animationSpeed: 1.0 + ) + .frame(width: 140, height: 140) + + Spacer().frame(height: 24) + + // Title (desktop: fw:500, fz:18) + Text("Confirm new device") + .font(.system(size: 18, weight: .medium)) + .foregroundStyle(RosettaColors.Adaptive.text) + + Spacer().frame(height: 10) + + // Description (desktop: fz:14, dimmed, centered, px:lg) + Text("To confirm this device, please check your first device attached to your account and approve the new device.") + .font(.system(size: 14, weight: .regular)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .multilineTextAlignment(.center) + .frame(maxWidth: 320) + .padding(.horizontal, 16) + + Spacer().frame(height: 24) + + // Exit button (desktop: animated red gradient, fullWidth, radius xl) + Button(action: exitAccount) { + Text("Exit") + .font(.system(size: 16, weight: .semibold)) + .foregroundStyle(.white) + .frame(maxWidth: .infinity) + .frame(height: 48) + } + .buttonStyle(DeviceConfirmExitButtonStyle()) + .padding(.horizontal, 32) + + Spacer() + + // Footer with device name (desktop: fz:12, dimmed, bold device name) + Text("Confirm device **\(deviceName)** on your first device to loading your chats.") + .font(.system(size: 12, weight: .regular)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary.opacity(0.6)) + .multilineTextAlignment(.center) + .frame(maxWidth: 300) + .padding(.bottom, 40) + } + } + } + + private func exitAccount() { + ProtocolManager.shared.disconnect() + } +} + +// MARK: - Exit Button Style (red glass capsule, desktop: animated gradient #e03131 β†’ #ff5656) + +private struct DeviceConfirmExitButtonStyle: ButtonStyle { + private let fillColor = RosettaColors.error + + func makeBody(configuration: Configuration) -> some View { + Group { + if #available(iOS 26, *) { + configuration.label + .background { + Capsule().fill(fillColor.opacity(configuration.isPressed ? 0.6 : 0.85)) + } + .glassEffect(.regular, in: Capsule()) + } else { + configuration.label + .background { + Capsule() + .fill(fillColor.opacity(configuration.isPressed ? 0.6 : 0.85)) + } + .clipShape(Capsule()) + } + } + .scaleEffect(configuration.isPressed ? 0.97 : 1.0) + .animation(.easeOut(duration: 0.15), value: configuration.isPressed) + } +} diff --git a/Rosetta/Features/Chats/ChatList/RequestChatsView.swift b/Rosetta/Features/Chats/ChatList/RequestChatsView.swift new file mode 100644 index 0000000..b0c23b5 --- /dev/null +++ b/Rosetta/Features/Chats/ChatList/RequestChatsView.swift @@ -0,0 +1,124 @@ +import Lottie +import SwiftUI + +/// Screen showing incoming message requests β€” opened from the "Request Chats" +/// row at the top of the main chat list (Telegram Archive style). +struct RequestChatsView: View { + @ObservedObject var viewModel: ChatListViewModel + @ObservedObject var navigationState: ChatListNavigationState + @Environment(\.dismiss) private var dismiss + + /// Desktop parity: track typing dialogs from MessageRepository (@Published). + @State private var typingDialogs: Set = [] + + var body: some View { + Group { + if viewModel.requestsModeDialogs.isEmpty { + RequestsEmptyStateView() + } else { + List { + ForEach(Array(viewModel.requestsModeDialogs.enumerated()), id: \.element.id) { index, dialog in + requestRow(dialog, isFirst: index == 0) + } + + Color.clear.frame(height: 80) + .listRowInsets(EdgeInsets()) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + } + .listStyle(.plain) + .scrollContentBackground(.hidden) + .scrollIndicators(.hidden) + } + } + .background(RosettaColors.Adaptive.background.ignoresSafeArea()) + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { dismiss() } label: { + backCapsuleLabel + } + .buttonStyle(.plain) + } + + ToolbarItem(placement: .principal) { + Text("Request Chats") + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.text) + } + } + .modifier(ChatListToolbarBackgroundModifier()) + .enableSwipeBack() + .onReceive(MessageRepository.shared.$typingDialogs) { typingDialogs = $0 } + } + + // MARK: - Capsule Back Button (matches ChatDetailView) + + private var backCapsuleLabel: some View { + TelegramVectorIcon( + pathData: TelegramIconPath.backChevron, + viewBox: CGSize(width: 11, height: 20), + color: .white + ) + .frame(width: 11, height: 20) + .allowsHitTesting(false) + .frame(width: 36, height: 36) + .frame(height: 44) + .padding(.horizontal, 4) + .background { + glassCapsule(strokeOpacity: 0.22, strokeColor: .white) + } + } + + @ViewBuilder + private func glassCapsule(strokeOpacity: Double = 0.18, strokeColor: Color = .white) -> some View { + if #available(iOS 26.0, *) { + Capsule().fill(.clear).glassEffect(.regular, in: .capsule) + } else { + Capsule().fill(.thinMaterial) + .overlay { Capsule().strokeBorder(strokeColor.opacity(strokeOpacity), lineWidth: 0.5) } + .shadow(color: .black.opacity(0.12), radius: 20, y: 8) + } + } + + private func requestRow(_ dialog: Dialog, isFirst: Bool) -> some View { + SyncAwareChatRow( + dialog: dialog, + isTyping: typingDialogs.contains(dialog.opponentKey), + isFirst: isFirst, + viewModel: viewModel, + navigationState: navigationState + ) + } +} + +// MARK: - Requests Empty State + +/// Shown when there are no incoming requests. +/// Design: folder Lottie + title + subtitle. +private struct RequestsEmptyStateView: View { + var body: some View { + VStack(spacing: 0) { + LottieView(animationName: "folder_empty", loopMode: .playOnce, animationSpeed: 1.0) + .frame(width: 150, height: 150) + + Spacer().frame(height: 24) + + Text("No Requests") + .font(.system(size: 18, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .multilineTextAlignment(.center) + + Spacer().frame(height: 8) + + Text("New message requests will appear here") + .font(.system(size: 15)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .offset(y: -40) + } +} diff --git a/Rosetta/Features/MainTabView.swift b/Rosetta/Features/MainTabView.swift index 410717b..cbf49cf 100644 --- a/Rosetta/Features/MainTabView.swift +++ b/Rosetta/Features/MainTabView.swift @@ -31,26 +31,32 @@ struct MainTabView: View { } var body: some View { - Group { - if #available(iOS 26.0, *) { - systemTabView - } else { - legacyTabView + ZStack { + Group { + if #available(iOS 26.0, *) { + systemTabView + } else { + legacyTabView + } } - } - .fullScreenCover(item: $addAccountScreen) { screen in - AuthCoordinator( - onAuthComplete: { - addAccountScreen = nil - // New account created β€” end session and go to unlock for the new account - SessionManager.shared.endSession() - onLogout?() - }, - onBackToUnlock: { - addAccountScreen = nil - }, - initialScreen: screen - ) + .fullScreenCover(item: $addAccountScreen) { screen in + AuthCoordinator( + onAuthComplete: { + addAccountScreen = nil + // New account created β€” end session and go to unlock for the new account + SessionManager.shared.endSession() + onLogout?() + }, + onBackToUnlock: { + addAccountScreen = nil + }, + initialScreen: screen + ) + } + + // Full-screen device verification overlay (observation-isolated). + // Covers nav bar, search bar, and tab bar β€” desktop parity. + DeviceConfirmOverlay() } } @@ -276,3 +282,16 @@ struct PlaceholderTabView: View { } } } + +// MARK: - Device Confirm Overlay (observation-isolated) + +/// Reads `ProtocolManager` in its own observation scope. +/// Shows full-screen `DeviceConfirmView` when this device awaits approval. +private struct DeviceConfirmOverlay: View { + var body: some View { + if ProtocolManager.shared.connectionState == .deviceVerificationRequired { + DeviceConfirmView() + .transition(.opacity) + } + } +} diff --git a/Rosetta/Features/Settings/UpdatesView.swift b/Rosetta/Features/Settings/UpdatesView.swift index f84456d..6347f3e 100644 --- a/Rosetta/Features/Settings/UpdatesView.swift +++ b/Rosetta/Features/Settings/UpdatesView.swift @@ -1,18 +1,10 @@ import SwiftUI -/// Updates screen β€” Android parity. -/// Shows app version info, up-to-date status, and a "Check for Updates" button. +/// Updates screen β€” desktop + Android parity. +/// Shows app version info, up-to-date status, release notes, and a "Check for Updates" button. struct UpdatesView: View { @Environment(\.dismiss) private var dismiss - private var appVersion: String { - Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" - } - - private var buildNumber: String { - Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "1" - } - var body: some View { ScrollView(showsIndicators: false) { VStack(spacing: 0) { @@ -20,6 +12,7 @@ struct UpdatesView: View { versionCard helpText checkButton + releaseNotesSection } .padding(.horizontal, 16) .padding(.top, 16) @@ -84,13 +77,13 @@ struct UpdatesView: View { private var versionCard: some View { SettingsCard { VStack(spacing: 0) { - versionRow(title: "Application Version", value: appVersion) + versionRow(title: "Application Version", value: ReleaseNotes.appVersion) Divider() .background(RosettaColors.Adaptive.divider) .padding(.horizontal, 16) - versionRow(title: "Build Number", value: buildNumber) + versionRow(title: "Build Number", value: ReleaseNotes.buildNumber) } } .padding(.top, 16) @@ -137,4 +130,55 @@ struct UpdatesView: View { .buttonStyle(RosettaPrimaryButtonStyle()) .padding(.top, 16) } + + // MARK: - Release Notes + + /// Desktop parity: shows release notes for each version. + /// Desktop sends these as system messages from the "updates" account; + /// iOS displays them inline on the Updates settings page. + private var releaseNotesSection: some View { + VStack(alignment: .leading, spacing: 16) { + Text("What's New") + .font(.system(size: 20, weight: .bold)) + .foregroundStyle(RosettaColors.Adaptive.text) + .padding(.horizontal, 4) + + ForEach(ReleaseNotes.entries) { entry in + releaseNoteCard(entry) + } + } + .padding(.top, 28) + } + + private func releaseNoteCard(_ entry: ReleaseNotes.Entry) -> some View { + SettingsCard { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Image(systemName: "sparkles") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(RosettaColors.primaryBlue) + + Text("Version \(entry.version)") + .font(.system(size: 15, weight: .semibold)) + .foregroundStyle(RosettaColors.Adaptive.text) + } + + VStack(alignment: .leading, spacing: 6) { + ForEach(entry.changes, id: \.self) { change in + HStack(alignment: .top, spacing: 8) { + Text("\u{2022}") + .font(.system(size: 15)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + + Text(change) + .font(.system(size: 14)) + .foregroundStyle(RosettaColors.Adaptive.textSecondary) + .fixedSize(horizontal: false, vertical: true) + } + } + } + } + .padding(16) + } + } } diff --git a/Rosetta/Resources/Lottie/folder_empty.json b/Rosetta/Resources/Lottie/folder_empty.json new file mode 100644 index 0000000..447a50a --- /dev/null +++ b/Rosetta/Resources/Lottie/folder_empty.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"13_FOLDER_empty","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[316.961,264.141,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[120,120,100]}},"ao":0,"ip":-100,"op":207,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","parent":1,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.875],"y":[1]},"o":{"x":[0.374],"y":[0]},"t":30.346,"s":[63.714]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":76,"s":[83.516]},{"t":92.306640625,"s":[87.874]}]},"p":{"a":1,"k":[{"i":{"x":0.875,"y":1},"o":{"x":0.374,"y":0},"t":30.346,"s":[37.083,76.667,0],"to":[83.536,-21.492,0],"ti":[2.144,11.401,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":76,"s":[142.279,9.884,0],"to":[-0.818,-4.352,0],"ti":[26.172,-3.566,0]},{"t":92.306640625,"s":[113.634,1.863,0]}]},"a":{"a":0,"k":[96.211,4.641,0]},"s":{"a":1,"k":[{"i":{"x":[0.875,0.875,0.875],"y":[1,1,1]},"o":{"x":[0.374,0.374,0.374],"y":[0,0,0]},"t":30.346,"s":[80,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":76,"s":[75,60,100]},{"t":92.306640625,"s":[60,75,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[-0.351,-13.492],[-3.816,0.687],[2.039,8.098]],"o":[[0.19,7.331],[2.545,-0.458],[-3.698,-14.689]],"v":[[89.215,2.765],[98.703,18.842],[104.1,3.393]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.260436118141,0.20539522956,0.144827829251,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.200053184852,0.160982271017,0.117987982302,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":12},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-73,"op":207,"st":27,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"NULL CONTROL","parent":2,"sr":1,"ks":{"o":{"a":0,"k":0},"r":{"a":0,"k":-77.774},"p":{"a":0,"k":[96.683,4.941,0]},"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[181.743,215.51,100]}},"ao":0,"ip":-54,"op":207,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 10","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[67.568,56.963,0],"to":[-0.51,0.229,0],"ti":[0.51,-0.229,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[64.505,58.337,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[64.505,58.337,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[-90.673,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-3.469,7.401],[-2.28,6.628],[-2.641,-2.205]],"o":[[4.355,-9.287],[4.757,-2.601],[4.724,-0.882]],"v":[[123.357,-12.459],[130.769,-33.153],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":34.906,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47.447,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":54.525,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.833,"y":0.847},"o":{"x":0.333,"y":0},"t":67.066,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":76,"s":[{"i":[[-3.648,7.326],[-2.023,6.488],[-2.641,-2.205]],"o":[[4.584,-9.205],[4.757,-1.917],[4.788,0.053]],"v":[[121.068,0.734],[131.522,-30.039],[143.788,-22.368]],"c":false}]},{"t":88.859375,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.201807598039,0.162009145699,0.118456859215,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-54,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 9","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[67.568,56.963,0],"to":[-0.51,0.229,0],"ti":[0.51,-0.229,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[64.505,58.337,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[64.505,58.337,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[90.673,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-2.842,7.661],[-3.179,7.119],[-2.641,-2.205]],"o":[[3.555,-9.574],[4.757,-4.994],[4.502,-4.151]],"v":[[122.042,-0.723],[129.454,-21.417],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":38.83,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":43.734,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56.275,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":63.354,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":73.826,"s":[{"i":[[-2.549,4.917],[-3.6,4.642],[-2.641,-1.393]],"o":[[3.18,-6.133],[4.757,-3.863],[4.398,-3.591]],"v":[[121.426,-7.312],[128.838,-20.385],[143.788,-24.462]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":82.973,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":97.6875,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-72,"op":189,"st":9,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 8","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[60.46,60.035,0],"to":[-0.076,0.086,0],"ti":[0.076,-0.086,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[60.004,60.553,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[60.004,60.553,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[-90.673,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-6.635,4.759],[-6.521,4.493],[-2.641,-2.205]],"o":[[1.356,-0.972],[4.757,-4.242],[4.572,-3.124]],"v":[[114.323,-22.714],[130.095,-35.368],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":41.771,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":59.217,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.833,"y":0.847},"o":{"x":0.333,"y":0},"t":66.299,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":76,"s":[{"i":[[-2.864,5.623],[-3.15,5.22],[-2.641,-1.621]],"o":[[3.58,-7.03],[4.757,-3.615],[4.509,-2.975]],"v":[[122.084,-7.882],[129.496,-23.09],[143.788,-23.518]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81.012,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":85.918,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":100.630859375,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-73,"op":192,"st":12,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 7","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[60.46,60.035,0],"to":[-0.076,0.086,0],"ti":[0.076,-0.086,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[60.004,60.553,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[60.004,60.553,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[90.673,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-3.367,7.443],[-2.426,6.708],[-2.641,-2.205]],"o":[[4.225,-9.334],[4.757,-2.989],[4.688,-1.412]],"v":[[123.143,-10.553],[130.555,-31.247],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35.887,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":68.047,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.726,-14.564],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":89.842,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":94.746,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":109.4609375,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-33,"op":201,"st":21,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 12","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[64.337,59.083,0],"to":[-0.262,0.386,0],"ti":[0.262,-0.386,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[62.768,61.396,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[62.768,61.396,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[-60.449,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-7.34,3.579],[-2.728,2.948],[-2.641,-2.205]],"o":[[5.682,-2.771],[3.079,-0.869],[4.565,-3.236]],"v":[[108.28,-12.624],[129.549,-27.338],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":38.83,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":51.371,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":58.449,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":70.99,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":78.068,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":92.783203125,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4.5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-67,"op":184,"st":4,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 11","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[64.337,59.083,0],"to":[-0.262,0.386,0],"ti":[0.262,-0.386,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[62.768,61.396,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[62.768,61.396,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[60.449,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-3.109,7.55],[-2.796,6.91],[-2.641,-2.205]],"o":[[3.896,-9.452],[4.757,-3.973],[4.597,-2.757]],"v":[[122.603,-5.727],[130.015,-26.421],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":41.666,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47.66,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":61.285,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.833,"y":0.847},"o":{"x":0.333,"y":0},"t":67.279,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":76,"s":[{"i":[[-2.971,7.607],[-2.994,7.018],[-2.641,-2.205]],"o":[[3.719,-9.515],[4.757,-4.502],[4.548,-3.48]],"v":[[122.856,-9.354],[129.724,-23.827],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81.992,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86.898,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":101.611328125,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4.5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-41,"op":193,"st":13,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 6","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[52.613,62.957,0],"to":[0.196,-0.091,0],"ti":[-0.196,0.091,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[53.79,62.41,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[53.79,62.41,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[-60.449,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":30.346,"s":[{"i":[[-7.053,4.129],[-3.218,4.759],[-2.641,-2.205]],"o":[[6.694,-3.918],[4.757,-2.656],[4.719,-0.956]],"v":[[106.483,-18.526],[125.062,-34.696],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":46.572,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":52.564,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":67.279,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.833,"y":0.847},"o":{"x":0.333,"y":0},"t":72.184,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":76,"s":[{"i":[[-3.519,7.38],[-2.208,6.589],[-2.641,-2.205]],"o":[[4.42,-9.264],[4.757,-2.408],[4.742,-0.617]],"v":[[123.137,-17.139],[130.875,-34.101],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":86.898,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":91.803,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":106.517578125,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4.5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-36,"op":198,"st":18,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 5","parent":3,"sr":1,"ks":{"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":30.346,"s":[67.601]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":70.566,"s":[79.868]},{"t":92.306640625,"s":[67.601]}]},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30.346,"s":[52.613,62.957,0],"to":[0.196,-0.091,0],"ti":[-0.196,0.091,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76,"s":[53.79,62.41,0],"to":[0,0,0],"ti":[0,0,0]},{"t":92.306640625,"s":[53.79,62.41,0]}]},"a":{"a":0,"k":[143.788,-23.152,0]},"s":{"a":0,"k":[60.449,71.61,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":30.346,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[5.699,-1.164],[4.398,-5.684]],"v":[[121.426,4.778],[123.42,-23.014],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36.867,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":41.771,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56.486,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.833,"y":0.847},"o":{"x":0.333,"y":0},"t":61.393,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.153},"t":76,"s":[{"i":[[-2.549,1.897],[-3.599,1.791],[-2.641,-0.537]],"o":[[3.18,-2.366],[4.757,-1.49],[4.399,-1.385]],"v":[[121.427,-20.052],[128.839,-25.095],[143.788,-26.666]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":76.107,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":81.012,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":95.727,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":100.631,"s":[{"i":[[-3.714,7.299],[-1.929,6.436],[-2.641,-2.205]],"o":[[4.668,-9.175],[4.757,-1.664],[4.811,0.398]],"v":[[123.871,-17.052],[131.283,-37.746],[143.788,-22.368]],"c":false}]},{"t":115.345703125,"s":[{"i":[[-2.549,7.782],[-3.6,7.349],[-2.641,-2.205]],"o":[[3.18,-9.709],[4.757,-6.116],[4.398,-5.684]],"v":[[121.426,4.778],[128.838,-15.916],[143.788,-22.368]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.2,0.160784313725,0.117647058824,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":4.5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":-27,"op":207,"st":27,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 48","parent":2,"sr":1,"ks":{"p":{"a":0,"k":[-0.691,0.492,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-24.961,37.768],[7.741,-1.702],[0,0],[4.469,-9.108],[6.375,-39.883],[14.688,-5.293],[0,0],[-0.782,7.932]],"o":[[4.37,-6.613],[0,0],[-9.908,2.181],[-10.401,21.201],[-11.337,70.931],[0,0],[7.725,-1.963],[6.775,-68.707]],"v":[[468.123,222.836],[458.903,209.357],[166.165,273.788],[143.374,291.616],[115.511,390.419],[98.758,502.748],[407.551,424.311],[421.711,407.822]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-23.907,28.05],[7.689,-1.517],[0,0],[5.636,-6.162],[6.344,-22.727],[14.533,-4.358],[0,0],[-0.564,5.721]],"o":[[4.186,-4.911],[0,0],[-9.841,1.943],[-12.184,13.321],[-14.08,50.439],[0,0],[7.665,-1.703],[4.887,-49.554]],"v":[[484.607,298.649],[477.024,284.08],[174.607,355.978],[152.322,369.639],[129.071,422.529],[98.805,502.909],[404.914,426.737],[418.611,414.366]],"c":true}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[1.26,40.797],[4.413,-1.098],[0,0],[1.086,-8.319],[-4.321,-69.16],[6.933,-2.99],[0,0],[-0.201,7.968]],"o":[[-0.245,-7.922],[0,0],[-9.908,2.181],[-1.684,12.892],[4.392,70.299],[0,0],[7.725,-1.963],[1.98,-78.314]],"v":[[413.431,180.211],[404.278,171.482],[114.665,235.288],[91.874,253.116],[99.261,385.419],[98.758,502.748],[409.301,423.561],[417.21,401.322]],"c":true}]},{"t":180,"s":[{"i":[[-24.961,37.768],[7.741,-1.702],[0,0],[4.469,-9.108],[6.375,-39.883],[14.688,-5.293],[0,0],[-0.782,7.932]],"o":[[4.37,-6.613],[0,0],[-9.908,2.181],[-10.401,21.201],[-11.337,70.931],[0,0],[7.725,-1.963],[6.775,-68.707]],"v":[[468.123,222.836],[458.903,209.357],[166.165,273.788],[143.374,291.616],[115.511,390.419],[98.758,502.748],[407.551,424.311],[421.711,407.822]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":-17,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 47","parent":13,"sr":1,"ks":{"p":{"a":0,"k":[42.742,39.17,0]},"a":{"a":0,"k":[284.188,355.943,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[414.718,284.208]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.666666666667,0.486274539723,0.294117647059,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-146.369,32.216],[0,0],[146.369,-32.216]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-5.218,2.102],[0,0],[0,0]],"o":[[5.218,-2.102],[0,0],[0,0]],"v":[[-146.973,115.491],[-126.762,110.888],[162.127,41.72]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[-2.587,8.5],[0,0],[0,0]],"o":[[3.545,-11.647],[0,0],[0,0]],"v":[[-216.369,7.216],[-197.497,-6.366],[94.869,-70.716]],"c":false}]},{"t":180,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-146.369,32.216],[0,0],[146.369,-32.216]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[313.824,247.432]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[156.65,-39.791],[-156.65,39.791]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[153.754,-35.229],[-151.877,40.53]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[151.65,-39.291],[-159.4,40.291]],"c":false}]},{"t":180,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[156.65,-39.791],[-156.65,39.791]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.929411768913,0.933333337307,0.945098042488,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[262.449,454.978]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":50},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-0.117,2.624],[-4.814,30.119],[-10.636,21.678],[-8.212,1.808],[0,0]],"o":[[0.118,-2.391],[0.781,-17.567],[5.964,-37.312],[3.669,-7.479],[0,0],[0,0]],"v":[[-172.883,127.709],[-172.537,120.187],[-165.873,48.441],[-138.548,-48.666],[-119.854,-63.277],[172.883,-127.709]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[-0.578,1.835],[-6.269,21.343],[-14.275,13.007],[-8.156,1.61],[0,0]],"o":[[0.052,-1.72],[3.774,-11.988],[6.15,-20.939],[5.221,-4.757],[0,0],[0,0]],"v":[[-166.234,136.059],[-166.094,130.649],[-153.688,80.142],[-132.055,29.322],[-113.775,18.125],[188.642,-53.773]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[0,0],[-0.117,2.624],[1.314,24.693],[-0.511,15.491],[-8.212,1.808],[0,0]],"o":[[0.118,-2.391],[0.781,-17.567],[-2.009,-37.733],[0.275,-8.325],[0,0],[0,0]],"v":[[-179.633,122.646],[-179.287,115.125],[-182.873,43.383],[-190.048,-87.166],[-171.354,-101.777],[121.383,-166.209]],"c":false}]},{"t":180,"s":[{"i":[[0,0],[-0.117,2.624],[-4.814,30.119],[-10.636,21.678],[-8.212,1.808],[0,0]],"o":[[0.118,-2.391],[0.781,-17.567],[5.964,-37.312],[3.669,-7.479],[0,0],[0,0]],"v":[[-172.883,127.709],[-172.537,120.187],[-165.873,48.441],[-138.548,-48.666],[-119.854,-63.277],[172.883,-127.709]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.900359509038,0.900359509038,0.904794730392,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[287.309,342.925]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[4.321,-1.557],[0,0],[-1.467,7.009],[0,0],[0,0]],"o":[[-1.309,6.993],[0,0],[7.055,-1.793],[0,0],[0,0],[0,0]],"v":[[-153.798,36.65],[-161.674,49.427],[147.119,-29.01],[160.954,-43.472],[161.674,-49.427],[-152.905,30.234]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[4.275,-1.282],[0,0],[-1.273,5.085],[0,0],[0,0]],"o":[[-1.116,5.067],[0,0],[7,-1.556],[0,0],[0,0],[0,0]],"v":[[-150.609,39.987],[-161.627,49.588],[144.482,-26.585],[157.911,-37.489],[158.466,-41.789],[-147.925,34.462]],"c":true}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[0,0],[4.321,-1.557],[0,0],[-1.467,7.009],[0,0],[0,0]],"o":[[-1.309,6.993],[0,0],[7.055,-1.793],[0,0],[0,0],[0,0]],"v":[[-157.548,34.15],[-161.674,49.427],[149.494,-30.51],[156.454,-49.972],[156.674,-50.677],[-157.155,29.234]],"c":true}]},{"t":180,"s":[{"i":[[0,0],[4.321,-1.557],[0,0],[-1.467,7.009],[0,0],[0,0]],"o":[[-1.309,6.993],[0,0],[7.055,-1.793],[0,0],[0,0],[0,0]],"v":[[-153.798,36.65],[-161.674,49.427],[147.119,-29.01],[160.954,-43.472],[161.674,-49.427],[-152.905,30.234]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[260.432,453.321]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":20},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-24.961,37.768],[7.741,-1.702],[0,0],[4.469,-9.108],[6.375,-39.883],[14.688,-5.293],[0,0],[-0.782,7.932]],"o":[[4.37,-6.613],[0,0],[-9.908,2.181],[-10.401,21.201],[-11.337,70.931],[0,0],[7.725,-1.963],[6.775,-68.707]],"v":[[183.935,-133.107],[174.714,-146.586],[-118.024,-82.155],[-140.814,-64.327],[-168.677,34.476],[-185.431,146.805],[123.363,68.368],[137.522,51.88]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-23.907,28.05],[7.689,-1.517],[0,0],[5.636,-6.162],[5.883,-22.235],[14.533,-4.358],[0,0],[-0.564,5.721]],"o":[[4.186,-4.911],[0,0],[-9.841,1.943],[-12.184,13.321],[-13.394,50.626],[0,0],[7.665,-1.703],[4.887,-49.554]],"v":[[197.896,-56.377],[190.313,-70.946],[-112.104,0.953],[-134.389,14.614],[-156.264,68.421],[-185.384,146.966],[120.725,70.794],[134.422,58.423]],"c":true}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[1.26,40.797],[4.413,-1.098],[0,0],[1.086,-8.319],[-4.321,-69.16],[6.933,-2.99],[0,0],[-0.201,7.968]],"o":[[-0.245,-7.922],[0,0],[-9.908,2.181],[-1.684,12.892],[4.392,70.299],[0,0],[7.725,-1.963],[1.98,-78.314]],"v":[[129.242,-175.732],[120.089,-184.461],[-169.524,-120.655],[-192.314,-102.827],[-184.927,29.476],[-185.431,146.805],[125.113,67.618],[133.022,45.38]],"c":true}]},{"t":180,"s":[{"i":[[-24.961,37.768],[7.741,-1.702],[0,0],[4.469,-9.108],[6.375,-39.883],[14.688,-5.293],[0,0],[-0.782,7.932]],"o":[[4.37,-6.613],[0,0],[-9.908,2.181],[-10.401,21.201],[-11.337,70.931],[0,0],[7.725,-1.963],[6.775,-68.707]],"v":[[183.935,-133.107],[174.714,-146.586],[-118.024,-82.155],[-140.814,-64.327],[-168.677,34.476],[-185.431,146.805],[123.363,68.368],[137.522,51.88]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.808,0.808,0.82,0.5,0.869,0.871,0.882,1,0.929,0.933,0.945]}},"s":{"a":0,"k":[-6.189,-17.943]},"e":{"a":0,"k":[23.15,93.055]},"t":1,"nm":"gr1","hd":false},{"ty":"tr","p":{"a":0,"k":[284.189,355.943]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[284.189,355.943]},"a":{"a":0,"k":[284.189,355.943]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false}],"ip":-17,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"SPIDER","parent":13,"refId":"comp_0","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[7.553,-16.773,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":1},"o":{"x":1,"y":0},"t":60,"s":[17.553,-46.773,0],"to":[0,0,0],"ti":[0,0,0]},{"t":120,"s":[7.553,-16.773,0]}]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":0,"op":76,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"pautina 2","parent":13,"sr":1,"ks":{"p":{"a":0,"k":[58.553,-83.17,0]},"a":{"a":0,"k":[41,-18.397,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.163,0.163,0.163],"y":[1,1,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":60,"s":[100,80,100]},{"i":{"x":[0.301,0.301,0.301],"y":[1,1,1]},"o":{"x":[0.951,0.951,0.951],"y":[0,0,0]},"t":120,"s":[100,80,100]},{"t":180,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[13.78,-11.274],[10,11.5],[12.5,9.5],[15,6.103],[17,6.25],[12.25,-1],[14,-0.5]],"o":[[-11,9],[-5,14],[0,21.5],[2,14.103],[4.5,13.25],[7.5,13.25],[-14,0.5]],"v":[[136,4.103],[98,-4.897],[56.5,1.603],[13,22],[-44.75,47],[-80.75,82],[-106.25,117]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[7.784,-5.753],[10,10.388],[12.5,8.581],[15,5.513],[19,7.771],[20.12,-2.505],[29.068,-5.357]],"o":[[-11,8.13],[-5,12.646],[0,19.421],[2,12.74],[2.75,11.272],[8.62,1.165],[-55.933,10.308]],"v":[[135.625,10.147],[101.274,-2.56],[59.5,12.76],[16.75,31.298],[-44.125,52.257],[-83.865,81.124],[-114.136,130.046]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[7.784,-5.753],[10,10.388],[12.5,8.581],[15,5.513],[19,7.771],[20.12,-2.505],[29.068,-5.357]],"o":[[-11,8.13],[-5,12.646],[0,19.421],[2,12.74],[2.75,11.272],[8.62,1.165],[-55.933,10.308]],"v":[[135.125,9.835],[101.399,-3.029],[59.5,12.76],[16.75,31.298],[-45,52.414],[-83.74,81.436],[-114.136,130.046]],"c":false}]},{"t":180,"s":[{"i":[[13.78,-11.274],[10,11.5],[12.5,9.5],[15,6.103],[17,6.25],[12.25,-1],[14,-0.5]],"o":[[-11,9],[-5,14],[0,21.5],[2,14.103],[4.5,13.25],[7.5,13.25],[-14,0.5]],"v":[[136,4.103],[98,-4.897],[56.5,1.603],[13,22],[-44.75,47],[-80.75,82],[-106.25,117]],"c":false}]}]},"nm":"Path 8","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[5.564,-4.162],[7.619,8.011],[9.524,6.618],[10.449,4.252],[13.236,5.993],[15.161,-6.425],[13.631,-5]],"o":[[-8.381,6.27],[-3.81,9.753],[0,14.978],[1.393,9.825],[-1.547,11.64],[-1.839,10.825],[-39.55,14.507]],"v":[[126.501,23.79],[97.796,21.419],[65.856,26.888],[31.231,40.257],[-13.703,60.11],[-40.411,88.675],[-63.131,118]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[10.499,-7.095],[7.619,7.237],[9.524,5.978],[10.449,3.841],[22.453,2.478],[23.75,-3.387],[21,-1.411]],"o":[[-8.381,5.663],[-3.81,8.81],[0,13.529],[1.393,8.875],[3.203,10.382],[4,9.88],[-25.552,1.717]],"v":[[134.001,24.682],[108.946,19.491],[75.336,29.935],[38.461,40.91],[-0.906,60.426],[-42,81.761],[-69.5,121.032]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[10.499,-7.095],[7.619,7.237],[9.524,5.978],[10.449,3.841],[22.453,2.478],[23.75,-3.387],[21,-1.411]],"o":[[-8.381,5.663],[-3.81,8.81],[0,13.529],[1.393,8.875],[3.203,10.382],[4,9.88],[-25.552,1.717]],"v":[[134.001,24.682],[108.446,18.71],[75.461,29.622],[38.461,40.91],[-0.906,60.426],[-42,81.761],[-69.5,121.032]],"c":false}]},{"t":180,"s":[{"i":[[5.564,-4.162],[7.619,8.011],[9.524,6.618],[10.449,4.252],[13.236,5.993],[15.161,-6.425],[13.631,-5]],"o":[[-8.381,6.27],[-3.81,9.753],[0,14.978],[1.393,9.825],[-1.547,11.64],[-1.839,10.825],[-39.55,14.507]],"v":[[126.501,23.79],[97.796,21.419],[65.856,26.888],[31.231,40.257],[-13.703,60.11],[-40.411,88.675],[-63.131,118]],"c":false}]}]},"nm":"Path 7","hd":false},{"ind":2,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[6.775,-5.674],[4.917,5.787],[6.146,4.781],[6.744,3.071],[8.542,4.329],[10.5,-1],[13.25,-0.75]],"o":[[-5.409,4.529],[-2.458,7.045],[0,10.82],[0.899,7.097],[1.506,7.976],[1,10.5],[-13.25,0.75]],"v":[[114.975,52.037],[98.45,50.324],[75.087,54.524],[53.492,63.933],[26.494,78.274],[8.25,98],[-7,118.25]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[4.116,-3.113],[4.917,5.228],[6.146,4.318],[6.743,2.774],[10.012,1.51],[26.858,-1.417],[22.195,-5.055]],"o":[[-5.409,4.091],[-2.458,6.364],[0,9.773],[0.899,6.411],[-0.494,7.242],[1.608,3.946],[-28.717,6.54]],"v":[[130.975,47.77],[116.674,46.899],[93.099,48.846],[70.984,54.796],[43.238,67.464],[8.034,82.284],[-13.89,109.733]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[4.116,-3.113],[4.917,5.228],[6.146,4.318],[6.743,2.774],[8.542,3.911],[26.858,-1.417],[22.195,-5.055]],"o":[[-5.409,4.091],[-2.458,6.364],[0,9.773],[0.899,6.411],[-0.494,7.242],[1.608,3.946],[-28.717,6.54]],"v":[[130.975,47.77],[115.799,46.899],[92.599,49.003],[70.984,54.796],[41.988,68.089],[7.784,83.69],[-13.89,109.733]],"c":false}]},{"t":180,"s":[{"i":[[6.775,-5.674],[4.917,5.787],[6.146,4.781],[6.744,3.071],[8.542,4.329],[10.5,-1],[13.25,-0.75]],"o":[[-5.409,4.529],[-2.458,7.045],[0,10.82],[0.899,7.097],[1.506,7.976],[1,10.5],[-13.25,0.75]],"v":[[114.975,52.037],[98.45,50.324],[75.087,54.524],[53.492,63.933],[26.494,78.274],[8.25,98],[-7,118.25]],"c":false}]}]},"nm":"Path 9","hd":false},{"ind":3,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[6,-40.397],[17,46.103]],"o":[[-11.5,-42.397],[-11.611,-31.487]],"v":[[95,119],[51.5,-11]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-0.5,-37.168],[17,20.755]],"o":[[-24,-30.599],[-19.677,-24.023]],"v":[[125,85.751],[51.5,3.292]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[-0.5,-37.168],[17,20.755]],"o":[[-24,-30.599],[-19.677,-24.023]],"v":[[125,85.751],[51.5,3.292]],"c":false}]},{"t":180,"s":[{"i":[[6,-40.397],[17,46.103]],"o":[[-11.5,-42.397],[-11.611,-31.487]],"v":[[95,119],[51.5,-11]],"c":false}]}]},"nm":"Path 3","hd":false},{"ind":4,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-45.5,-10.5],[25,24.603]],"o":[[-23,-40.397],[-23.919,-23.54]],"v":[[95,119],[3.5,13]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-3.5,-38.975],[28.5,19.063]],"o":[[-43,-33.103],[-26.971,-18.04]],"v":[[125,85.751],[3.5,23.619]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[-3.5,-38.975],[28.5,19.063]],"o":[[-43,-33.103],[-26.971,-18.04]],"v":[[125,85.751],[3.5,23.619]],"c":false}]},{"t":180,"s":[{"i":[[-45.5,-10.5],[25,24.603]],"o":[[-23,-40.397],[-23.919,-23.54]],"v":[[95,119],[3.5,13]],"c":false}]}]},"nm":"Path 5","hd":false},{"ind":5,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[3.5,-41.635],[0,30.855]],"o":[[4,-43.135],[0,-31.709]],"v":[[94.75,118.635],[98,-12.855]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[-3.5,-38.975],[8.75,19.023]],"o":[[-6.25,-36.346],[-14.127,-30.713]],"v":[[125,85.751],[98,-10.529]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[-3.5,-38.975],[8.75,19.023]],"o":[[-6.25,-36.346],[-14.127,-30.713]],"v":[[125,85.751],[98,-10.529]],"c":false}]},{"t":180,"s":[{"i":[[3.5,-41.635],[0,30.855]],"o":[[4,-43.135],[0,-31.709]],"v":[[94.75,118.635],[98,-12.855]],"c":false}]}]},"nm":"Path 12","hd":false},{"ind":6,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[-73,0.5],[50.5,4.853]],"o":[[0,0],[-62,-18],[-33.406,-3.21]],"v":[[-135,116.603],[94,119],[-123,76.75]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[-99,14.207],[50.5,4.384]],"o":[[0,0],[-58.5,-4.988],[-33.406,-2.9]],"v":[[-135,134.785],[125,85.751],[-123,79.176]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[0,0],[-99,14.207],[50.5,4.384]],"o":[[0,0],[-58.5,-4.988],[-33.406,-2.9]],"v":[[-135,134.785],[125,85.751],[-123,79.176]],"c":false}]},{"t":180,"s":[{"i":[[0,0],[-73,0.5],[50.5,4.853]],"o":[[0,0],[-62,-18],[-33.406,-3.21]],"v":[[-135,116.603],[94,119],[-123,76.75]],"c":false}]}]},"nm":"Path 11","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[6,-40.397],[32,9.603]],"o":[[0,0],[-32,-25.897],[-32.143,-9.646]],"v":[[137.5,0.103],[95,119],[-55.5,43.5]],"c":false}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[6,-36.491],[32,8.675]],"o":[[0,0],[-44,-14.811],[-32.143,-8.714]],"v":[[137.5,-3.584],[125,85.751],[-55.5,49.817]],"c":false}]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[{"i":[[0,0],[6,-36.491],[32,8.675]],"o":[[0,0],[-44,-14.811],[-32.143,-8.714]],"v":[[137.5,-3.584],[125,85.751],[-55.5,49.817]],"c":false}]},{"t":180,"s":[{"i":[[0,0],[6,-40.397],[32,9.603]],"o":[[0,0],[-32,-25.897],[-32.143,-9.646]],"v":[[137.5,0.103],[95,119],[-55.5,43.5]],"c":false}]}]},"nm":"Path 4","hd":false},{"ty":"tr","p":{"a":0,"k":[41,49.552]},"a":{"a":0,"k":[41,49.552]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"st","c":{"a":0,"k":[0.705882352941,0.721568627451,0.741176470588,1]},"o":{"a":0,"k":100},"w":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2]},{"i":{"x":[0.163],"y":[1]},"o":{"x":[1],"y":[0]},"t":60,"s":[2.5]},{"i":{"x":[0.301],"y":[1]},"o":{"x":[0.951],"y":[0]},"t":120,"s":[2.5]},{"t":180,"s":[2]}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"p_str","parent":13,"sr":1,"ks":{},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.258,6.055],[0,0],[6.31,-1.446],[0,0],[-0.073,5.112],[0,0],[6.333,-1.453],[0,0],[-0.058,-6.533],[0,0],[-7.065,-16.479],[-5.052,1.289]],"o":[[6.051,-1.378],[0,0],[0.28,-6.551],[0,0],[-4.812,1.103],[0,0],[0.093,-6.574],[0,0],[-6.323,1.45],[0,0],[0.043,4.885],[7.129,16.628],[0,0]],"v":[[144.859,112.603],[156.283,99.152],[166.7,-176.707],[155.78,-185.961],[-9.063,-148.178],[-18.293,-155.977],[-18.097,-169.807],[-29.394,-179.09],[-155.367,-149.84],[-166.711,-135.375],[-164.411,125.639],[-158.249,172.856],[-139.624,185.173]],"c":true}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"p_bl4","parent":13,"sr":1,"ks":{"p":{"a":0,"k":[-94.374,-154.336,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-3.595,0.824],[0,0]],"o":[[-0.033,-3.738],[0,0],[0,0]],"v":[[-66.337,18.909],[-59.652,10.344],[66.338,-18.909]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.929411768913,0.933333337307,0.945098042488,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"p_sh2","parent":13,"sr":1,"ks":{"o":{"a":0,"k":33},"p":{"a":0,"k":[-140.348,177.818,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-4.572,7.663]],"o":[[10.337,14.81],[0,0]],"v":[[-14.261,0.419],[14.261,-6.841]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"p_shad","parent":13,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[90]},{"i":{"x":[0.163],"y":[1]},"o":{"x":[1],"y":[0]},"t":60,"s":[90]},{"i":{"x":[0.301],"y":[1]},"o":{"x":[0.951],"y":[0]},"t":120,"s":[90]},{"t":180,"s":[90]}]},"p":{"a":1,"k":[{"i":{"x":0,"y":0},"o":{"x":0.333,"y":0.333},"t":0,"s":[3.569,12.387,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[3.569,12.387,0],"to":[-0.25,-0.583,0],"ti":[0,0,0]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":120,"s":[2.069,8.887,0],"to":[0,0,0],"ti":[-0.25,-0.583,0]},{"t":180,"s":[3.569,12.387,0]}]},"a":{"a":0,"k":[245.016,323.16,0]},"s":{"a":1,"k":[{"i":{"x":[0,0,0],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,97,100]},{"i":{"x":[0.163,0.163,0.163],"y":[1,1,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":60,"s":[100,97,100]},{"i":{"x":[0.301,0.301,0.301],"y":[1,1,1]},"o":{"x":[0.951,0.951,0.951],"y":[0,0,0]},"t":120,"s":[100,100,100]},{"t":180,"s":[100,97,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.262,-1.435],[0,0],[1.345,-0.897],[-0.044,-4.985],[0,0],[-9.197,-14.632],[-1.207,0.307]],"o":[[0,0],[0.278,-6.501],[0,0],[-1.557,0.357],[-4.148,2.766],[0,0],[0.045,5.059],[1.147,0.153],[0,0]],"v":[[135.781,91.782],[145.245,-150.133],[134.41,-159.315],[-134.447,-97.21],[-138.828,-95.291],[-145.256,-82.737],[-143.551,110.732],[-135.398,159.515],[-131.847,159.315]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.262,-1.435],[0,0],[1.345,-0.897],[-0.044,-4.985],[0,0],[-9.197,-14.632],[-1.207,0.307]],"o":[[0,0],[0.278,-6.501],[0,0],[-1.557,0.357],[-4.148,2.766],[0,0],[0.045,5.059],[1.147,0.153],[0,0]],"v":[[135.781,91.782],[145.245,-119.205],[134.41,-128.387],[-134.447,-66.282],[-138.828,-64.363],[-145.256,-51.809],[-143.551,110.732],[-135.398,159.515],[-131.847,159.315]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.262,-1.435],[0,0],[1.345,-0.897],[-0.044,-4.985],[0,0],[-9.197,-14.632],[-1.207,0.307]],"o":[[0,0],[0.278,-6.501],[0,0],[-1.557,0.357],[-4.148,2.766],[0,0],[0.045,5.059],[1.147,0.153],[0,0]],"v":[[135.781,91.782],[145.245,-150.133],[134.41,-159.315],[-134.447,-97.21],[-138.828,-95.291],[-145.256,-82.737],[-143.551,110.732],[-135.398,159.515],[-131.847,159.315]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[249.77,335.347]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":80},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.351,-1.456],[0,0],[1.364,-0.909],[-0.045,-5.056],[0,0],[-9.328,-14.84],[-1.224,0.311]],"o":[[0,0],[0.282,-6.594],[0,0],[-1.579,0.362],[-4.207,2.805],[0,0],[0.045,5.131],[1.163,0.155],[0,0]],"v":[[137.715,93.09],[147.314,-152.271],[136.324,-161.584],[-136.362,-98.595],[-140.805,-96.649],[-147.325,-83.915],[-145.596,112.309],[-137.327,161.787],[-133.725,161.584]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.351,-1.456],[0,0],[1.364,-0.909],[-0.045,-5.056],[0,0],[-9.328,-14.84],[-1.224,0.311]],"o":[[0,0],[0.282,-6.594],[0,0],[-1.579,0.362],[-4.207,2.805],[0,0],[0.045,5.131],[1.163,0.155],[0,0]],"v":[[137.715,93.09],[147.314,-121.343],[136.324,-130.656],[-136.362,-67.667],[-140.805,-65.721],[-147.325,-52.987],[-145.596,112.309],[-137.327,161.787],[-133.725,161.584]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.351,-1.456],[0,0],[1.364,-0.909],[-0.045,-5.056],[0,0],[-9.328,-14.84],[-1.224,0.311]],"o":[[0,0],[0.282,-6.594],[0,0],[-1.579,0.362],[-4.207,2.805],[0,0],[0.045,5.131],[1.163,0.155],[0,0]],"v":[[137.715,93.09],[147.314,-152.271],[136.324,-161.584],[-136.362,-98.595],[-140.805,-96.649],[-147.325,-83.915],[-145.596,112.309],[-137.327,161.787],[-133.725,161.584]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[249.175,333.823]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":70},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.44,-1.476],[0,0],[1.383,-0.922],[-0.045,-5.127],[0,0],[-9.459,-15.049],[-1.241,0.316]],"o":[[0,0],[0.286,-6.686],[0,0],[-1.601,0.367],[-4.266,2.845],[0,0],[0.046,5.203],[1.179,0.158],[0,0]],"v":[[139.648,94.397],[149.383,-154.41],[138.239,-163.853],[-138.277,-99.979],[-142.783,-98.006],[-149.394,-85.094],[-147.64,113.886],[-139.255,164.059],[-135.603,163.853]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.44,-1.476],[0,0],[1.383,-0.922],[-0.045,-5.127],[0,0],[-9.459,-15.049],[-1.241,0.316]],"o":[[0,0],[0.286,-6.686],[0,0],[-1.601,0.367],[-4.266,2.845],[0,0],[0.046,5.203],[1.179,0.157],[0,0]],"v":[[139.648,94.397],[149.383,-123.482],[138.239,-132.925],[-138.277,-69.051],[-142.783,-67.078],[-149.394,-54.166],[-147.64,113.886],[-139.255,164.059],[-135.603,163.853]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.44,-1.476],[0,0],[1.383,-0.922],[-0.045,-5.127],[0,0],[-9.459,-15.049],[-1.241,0.316]],"o":[[0,0],[0.286,-6.686],[0,0],[-1.601,0.367],[-4.266,2.845],[0,0],[0.046,5.203],[1.179,0.158],[0,0]],"v":[[139.648,94.397],[149.383,-154.41],[138.239,-163.853],[-138.277,-99.979],[-142.783,-98.006],[-149.394,-85.094],[-147.64,113.886],[-139.255,164.059],[-135.603,163.853]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[248.581,332.3]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":60},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.529,-1.497],[0,0],[1.402,-0.935],[-0.046,-5.198],[0,0],[-9.59,-15.257],[-1.259,0.32]],"o":[[0,0],[0.289,-6.779],[0,0],[-1.623,0.372],[-4.325,2.884],[0,0],[0.046,5.275],[1.196,0.16],[0,0]],"v":[[141.583,95.704],[151.452,-156.548],[140.153,-166.122],[-140.192,-101.364],[-144.76,-99.363],[-151.463,-86.272],[-149.685,115.463],[-141.184,166.331],[-137.481,166.122]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.529,-1.497],[0,0],[1.402,-0.935],[-0.046,-5.198],[0,0],[-9.59,-15.257],[-1.259,0.32]],"o":[[0,0],[0.289,-6.779],[0,0],[-1.623,0.372],[-4.325,2.884],[0,0],[0.046,5.275],[1.196,0.16],[0,0]],"v":[[141.583,95.704],[151.452,-125.62],[140.153,-135.194],[-140.192,-70.436],[-144.76,-68.435],[-151.463,-55.344],[-149.685,115.463],[-141.184,166.331],[-137.481,166.122]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.529,-1.497],[0,0],[1.402,-0.935],[-0.046,-5.198],[0,0],[-9.59,-15.257],[-1.259,0.32]],"o":[[0,0],[0.289,-6.779],[0,0],[-1.623,0.372],[-4.325,2.884],[0,0],[0.046,5.275],[1.196,0.16],[0,0]],"v":[[141.583,95.704],[151.452,-156.548],[140.153,-166.122],[-140.192,-101.364],[-144.76,-99.363],[-151.463,-86.272],[-149.685,115.463],[-141.184,166.331],[-137.481,166.122]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[247.987,330.777]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":50},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.618,-1.517],[0,0],[1.421,-0.948],[-0.046,-5.269],[0,0],[-9.721,-15.465],[-1.276,0.324]],"o":[[0,0],[0.293,-6.871],[0,0],[-1.646,0.378],[-4.384,2.924],[0,0],[0.047,5.348],[1.212,0.162],[0,0]],"v":[[143.517,97.012],[153.521,-158.686],[142.068,-168.391],[-142.107,-102.748],[-146.738,-100.72],[-153.532,-87.451],[-151.73,117.041],[-143.113,168.604],[-139.359,168.392]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.618,-1.517],[0,0],[1.421,-0.948],[-0.046,-5.269],[0,0],[-9.721,-15.466],[-1.276,0.324]],"o":[[0,0],[0.293,-6.871],[0,0],[-1.646,0.378],[-4.384,2.924],[0,0],[0.047,5.348],[1.212,0.162],[0,0]],"v":[[143.517,97.012],[153.521,-127.759],[142.068,-137.464],[-142.107,-71.821],[-146.738,-69.793],[-153.532,-56.523],[-151.73,117.041],[-143.113,168.604],[-139.359,168.392]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.618,-1.517],[0,0],[1.421,-0.948],[-0.046,-5.269],[0,0],[-9.721,-15.465],[-1.276,0.324]],"o":[[0,0],[0.293,-6.871],[0,0],[-1.646,0.378],[-4.384,2.924],[0,0],[0.047,5.348],[1.212,0.162],[0,0]],"v":[[143.517,97.012],[153.521,-158.686],[142.068,-168.391],[-142.107,-102.748],[-146.738,-100.72],[-153.532,-87.451],[-151.73,117.041],[-143.113,168.604],[-139.359,168.392]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[247.393,329.253]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":40},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.708,-1.537],[0,0],[1.44,-0.961],[-0.047,-5.34],[0,0],[-9.852,-15.674],[-1.293,0.329]],"o":[[0,0],[0.297,-6.964],[0,0],[-1.668,0.383],[-4.443,2.963],[0,0],[0.048,5.42],[1.228,0.164],[0,0]],"v":[[145.451,98.319],[155.589,-160.825],[143.982,-170.661],[-144.022,-104.133],[-148.715,-102.078],[-155.601,-88.629],[-153.774,118.618],[-145.041,170.876],[-141.237,170.661]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.708,-1.537],[0,0],[1.44,-0.961],[-0.047,-5.34],[0,0],[-9.852,-15.674],[-1.293,0.329]],"o":[[0,0],[0.297,-6.964],[0,0],[-1.668,0.383],[-4.443,2.963],[0,0],[0.048,5.42],[1.228,0.164],[0,0]],"v":[[145.451,98.319],[155.589,-129.897],[143.982,-139.733],[-144.022,-73.205],[-148.715,-71.15],[-155.601,-57.701],[-153.774,118.618],[-145.041,170.876],[-141.237,170.661]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.708,-1.537],[0,0],[1.44,-0.961],[-0.047,-5.34],[0,0],[-9.852,-15.674],[-1.293,0.329]],"o":[[0,0],[0.297,-6.964],[0,0],[-1.668,0.383],[-4.443,2.963],[0,0],[0.048,5.42],[1.228,0.164],[0,0]],"v":[[145.451,98.319],[155.589,-160.825],[143.982,-170.661],[-144.022,-104.133],[-148.715,-102.078],[-155.601,-88.629],[-153.774,118.618],[-145.041,170.876],[-141.237,170.661]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[246.799,327.73]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":30},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.797,-1.558],[0,0],[1.46,-0.973],[-0.048,-5.411],[0,0],[-9.983,-15.882],[-1.31,0.333]],"o":[[0,0],[0.301,-7.056],[0,0],[-1.69,0.388],[-4.502,3.002],[0,0],[0.048,5.492],[1.245,0.166],[0,0]],"v":[[147.385,99.626],[157.658,-162.963],[145.897,-172.93],[-145.937,-105.518],[-150.693,-103.435],[-157.67,-89.807],[-155.819,120.195],[-146.97,173.148],[-143.115,172.93]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.797,-1.558],[0,0],[1.46,-0.973],[-0.048,-5.411],[0,0],[-9.983,-15.882],[-1.31,0.333]],"o":[[0,0],[0.301,-7.056],[0,0],[-1.69,0.388],[-4.502,3.002],[0,0],[0.048,5.492],[1.245,0.166],[0,0]],"v":[[147.385,99.626],[157.658,-132.035],[145.897,-142.002],[-145.937,-74.59],[-150.693,-72.507],[-157.67,-58.88],[-155.819,120.195],[-146.97,173.148],[-143.115,172.93]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.797,-1.558],[0,0],[1.46,-0.973],[-0.048,-5.411],[0,0],[-9.983,-15.882],[-1.31,0.333]],"o":[[0,0],[0.301,-7.056],[0,0],[-1.69,0.388],[-4.502,3.002],[0,0],[0.048,5.492],[1.245,0.166],[0,0]],"v":[[147.385,99.626],[157.658,-162.963],[145.897,-172.93],[-145.937,-105.518],[-150.693,-103.435],[-157.67,-89.807],[-155.819,120.195],[-146.97,173.148],[-143.115,172.93]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[246.204,326.207]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":20},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.886,-1.578],[0,0],[1.479,-0.986],[-0.048,-5.482],[0,0],[-10.114,-16.091],[-1.327,0.337]],"o":[[0,0],[0.305,-7.149],[0,0],[-1.712,0.393],[-4.561,3.042],[0,0],[0.049,5.564],[1.261,0.168],[0,0]],"v":[[149.319,100.934],[159.727,-165.102],[147.811,-175.199],[-147.852,-106.902],[-152.67,-104.792],[-159.739,-90.986],[-157.864,121.772],[-148.898,175.42],[-144.993,175.199]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.886,-1.578],[0,0],[1.479,-0.986],[-0.048,-5.482],[0,0],[-10.114,-16.091],[-1.327,0.337]],"o":[[0,0],[0.305,-7.149],[0,0],[-1.712,0.393],[-4.561,3.042],[0,0],[0.049,5.564],[1.261,0.168],[0,0]],"v":[[149.319,100.934],[159.727,-134.174],[147.811,-144.271],[-147.852,-75.974],[-152.67,-73.865],[-159.739,-60.058],[-157.864,121.772],[-148.898,175.42],[-144.993,175.199]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.886,-1.578],[0,0],[1.479,-0.986],[-0.048,-5.482],[0,0],[-10.114,-16.091],[-1.327,0.337]],"o":[[0,0],[0.305,-7.149],[0,0],[-1.712,0.393],[-4.561,3.042],[0,0],[0.049,5.564],[1.261,0.168],[0,0]],"v":[[149.319,100.934],[159.727,-165.102],[147.811,-175.199],[-147.852,-106.902],[-152.67,-104.792],[-159.739,-90.986],[-157.864,121.772],[-148.898,175.42],[-144.993,175.199]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[245.61,324.684]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":10},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 8","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[6.975,-1.599],[0,0],[1.498,-0.999],[-0.049,-5.553],[0,0],[-10.245,-16.299],[-1.345,0.342]],"o":[[0,0],[0.309,-7.242],[0,0],[-1.734,0.398],[-4.62,3.081],[0,0],[0.05,5.636],[1.277,0.171],[0,0]],"v":[[151.253,102.241],[161.796,-167.24],[149.726,-177.468],[-149.767,-108.287],[-154.647,-106.15],[-161.808,-92.164],[-159.909,123.349],[-150.827,177.692],[-146.871,177.469]],"c":true}]},{"i":{"x":0.163,"y":1},"o":{"x":1,"y":0},"t":60,"s":[{"i":[[0,0],[0,0],[6.975,-1.599],[0,0],[1.498,-0.999],[-0.049,-5.553],[0,0],[-10.245,-16.299],[-1.345,0.342]],"o":[[0,0],[0.309,-7.242],[0,0],[-1.734,0.398],[-4.62,3.081],[0,0],[0.05,5.636],[1.277,0.171],[0,0]],"v":[[151.253,102.241],[161.796,-136.312],[149.726,-146.54],[-149.767,-77.359],[-154.647,-75.222],[-161.808,-61.237],[-159.909,123.349],[-150.827,177.692],[-146.871,177.469]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[6.975,-1.599],[0,0],[1.498,-0.999],[-0.049,-5.553],[0,0],[-10.245,-16.299],[-1.345,0.342]],"o":[[0,0],[0.309,-7.242],[0,0],[-1.734,0.398],[-4.62,3.081],[0,0],[0.05,5.636],[1.277,0.171],[0,0]],"v":[[151.253,102.241],[161.796,-167.24],[149.726,-177.468],[-149.767,-108.287],[-154.647,-106.15],[-161.808,-92.164],[-159.909,123.349],[-150.827,177.692],[-146.871,177.469]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.396078437567,0.458823531866,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[245.016,323.16]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":0},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 9","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"p_bl3","parent":13,"sr":1,"ks":{"p":{"a":0,"k":[74.699,-161.22,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-82.422,18.892],[82.422,-18.892]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.929411768913,0.933333337307,0.945098042488,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"p_bl2","parent":13,"sr":1,"ks":{"o":{"a":0,"k":40},"p":{"a":0,"k":[-158.75,9.661,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0.076,8.644],[0,0]],"o":[[-1.278,-9.446],[0,0],[0,0]],"v":[[1.961,145.088],[0.34,115.925],[-1.961,-145.088]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.929411768913,0.933333337307,0.945098042488,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":6},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"p_bl1","parent":13,"sr":1,"ks":{"o":{"a":0,"k":25},"p":{"a":0,"k":[-10.378,135.807,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[145.485,-37.034],[-145.485,37.034]],"c":false}},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.929411768913,0.933333337307,0.945098042488,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":10},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":1,"k":[{"i":{"x":0.163,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[240.327,413.343,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.301,"y":1},"o":{"x":0.951,"y":0},"t":60,"s":[240.327,402.443,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.223,"y":1},"o":{"x":0.476,"y":0},"t":120,"s":[240.327,422.063,0],"to":[0,0,0],"ti":[0,0,0]},{"t":179,"s":[240.327,413.343,0]}]},"a":{"a":0,"k":[60,60,0]},"s":{"a":0,"k":[109,109,100]}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"papk1","parent":12,"sr":1,"ks":{"p":{"a":0,"k":[60,60,0]},"a":{"a":0,"k":[0,144.248,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":0,"k":{"i":[[0,0],[-0.258,6.055],[0,0],[6.31,-1.446],[0,0],[-0.073,5.112],[0,0],[6.333,-1.453],[0,0],[-0.058,-6.533],[0,0],[-7.065,-16.479],[-5.052,1.289]],"o":[[6.051,-1.378],[0,0],[0.28,-6.551],[0,0],[-4.812,1.103],[0,0],[0.093,-6.574],[0,0],[-6.323,1.45],[0,0],[0.043,4.885],[7.129,16.628],[0,0]],"v":[[144.859,112.603],[156.283,99.152],[166.7,-176.707],[155.78,-185.961],[-9.063,-148.178],[-18.293,-155.977],[-18.097,-169.807],[-29.394,-179.09],[-155.367,-149.84],[-166.711,-135.375],[-164.411,125.639],[-158.249,172.856],[-139.624,185.173]],"c":true}},"nm":"Path 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.705882370472,0.721568644047,0.741176486015,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":0,"nm":"SPIDER","parent":3,"refId":"comp_0","sr":1,"ks":{"p":{"a":0,"k":[256,256,0]},"a":{"a":0,"k":[256,256,0]}},"ao":0,"w":512,"h":512,"ip":76,"op":108,"st":0,"bm":0}]} \ No newline at end of file diff --git a/Rosetta/Resources/Lottie/inbox.json b/Rosetta/Resources/Lottie/inbox.json new file mode 100644 index 0000000..45410cb --- /dev/null +++ b/Rosetta/Resources/Lottie/inbox.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"INBOX","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL SECOND BOX","parent":2,"sr":1,"ks":{"o":{"a":0,"k":0},"p":{"a":0,"k":[-3,42,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":74,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":81,"s":[108,108,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":88,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":94,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":100,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":106,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":112,"s":[105,105,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":118,"s":[100,100,100]},{"i":{"x":[0.186,0.186,0.1],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":124,"s":[105,105,100]},{"t":156,"s":[100,100,100]}]}},"ao":0,"ip":0,"op":180,"st":-4,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"FRONT","sr":1,"ks":{"o":{"a":1,"k":[{"t":55,"s":[100],"h":1},{"t":93,"s":[0],"h":1},{"t":120,"s":[100],"h":1}]},"r":{"a":1,"k":[{"i":{"x":[0.8],"y":[1]},"o":{"x":[0.8],"y":[0]},"t":56,"s":[0]},{"i":{"x":[0.53],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":81,"s":[-3]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":94,"s":[3]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":106,"s":[-5]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":118,"s":[2]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":130,"s":[-2]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.1],"y":[0]},"t":143,"s":[1]},{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":154,"s":[0]},{"t":175,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":21,"s":[253.567,438.832,0],"to":[0,6.083,0],"ti":[0,2.917,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[253.567,475.332,0],"to":[0,-2.917,0],"ti":[0,4.417,0]},{"i":{"x":0.4,"y":0.895},"o":{"x":0.2,"y":0},"t":42,"s":[253.567,421.332,0],"to":[0,-4.417,0],"ti":[0,0.083,0]},{"i":{"x":0.745,"y":0.891},"o":{"x":0.56,"y":0.158},"t":52,"s":[253.567,448.832,0],"to":[0,-0.083,0],"ti":[0,-1.333,0]},{"i":{"x":0.752,"y":1},"o":{"x":0.688,"y":0.177},"t":68,"s":[253.567,420.832,0],"to":[0,1.333,0],"ti":[-0.167,7.667,0]},{"i":{"x":0.225,"y":1},"o":{"x":0.332,"y":0},"t":81,"s":[253.567,456.832,0],"to":[0.167,-7.667,0],"ti":[0,0.667,0]},{"i":{"x":0.797,"y":0.617},"o":{"x":0.486,"y":0},"t":97,"s":[254.567,374.832,0],"to":[0,-0.234,0],"ti":[0.14,-12.471,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.864,"y":0.242},"t":124,"s":[254.324,396.278,0],"to":[-0.26,23.063,0],"ti":[0.108,-6.274,0]},{"i":{"x":0.3,"y":0.944},"o":{"x":0.3,"y":0},"t":137,"s":[253.567,465.832,0],"to":[-0.167,9.667,0],"ti":[0,4.5,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0.209},"t":148,"s":[253.567,429.832,0],"to":[0,-4.5,0],"ti":[0,-1.5,0]},{"t":175,"s":[253.567,438.832,0]}]},"a":{"a":0,"k":[-2.433,179.832,0]},"s":{"a":1,"k":[{"i":{"x":[0.7,0.7,0.7],"y":[1,1,1]},"o":{"x":[0.9,0.9,0.9],"y":[0,0,0]},"t":21,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":34,"s":[102,98,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":42,"s":[99,101,100]},{"i":{"x":[0.8,0.8,0.8],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55,"s":[100,100,100]},{"i":{"x":[0.213,0.213,0.1],"y":[1,1,1]},"o":{"x":[0.484,0.484,0.2],"y":[0,0,0]},"t":77,"s":[100,100,100]},{"i":{"x":[0.9,0.9,0.9],"y":[1,1,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":105,"s":[92,92,100]},{"t":138,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[30.244,0],[0,0],[-2.005,6.384]],"o":[[2.222,6.384],[0,0],[-24.696,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0]],"v":[[186.181,62.567],[182.981,70.34],[111.926,70.34],[59.935,104.732],[-2.433,135.51],[-64.886,104.296],[-126.536,70.34],[-187.948,70.34],[-191.219,62.567]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-17.415],[29.04,0],[14.909,14.793],[30.244,0],[0,0],[-2.005,5.769]],"o":[[2.222,5.769],[0,0],[-24.696,0],[-15.947,16.703],[-30.144,0],[-26.243,-26.037],[0,0],[-2.484,0],[0,0]],"v":[[186.107,66.5],[182.907,73.524],[111.852,73.524],[59.861,104.6],[-2.506,132.41],[-64.96,104.206],[-126.61,73.524],[-188.022,73.524],[-191.293,66.5]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-20.078],[29.04,0],[14.909,17.054],[30.244,0],[0,0],[-2.005,6.651]],"o":[[2.222,6.651],[0,0],[-24.696,0],[-15.947,19.257],[-30.144,0],[-26.243,-30.019],[0,0],[-2.484,0],[0,0]],"v":[[186.181,56.531],[182.981,64.629],[111.926,64.629],[59.935,100.457],[-2.433,132.52],[-64.886,100.003],[-126.536,64.629],[-187.948,64.629],[-191.219,56.531]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[30.244,0],[0,0],[-2.005,6.384]],"o":[[2.222,6.384],[0,0],[-24.696,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0]],"v":[[186.181,62.567],[182.981,70.34],[111.926,70.34],[59.935,104.732],[-2.433,135.51],[-64.886,104.296],[-126.536,70.34],[-187.948,70.34],[-191.219,62.567]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[2.27,0],[0,0],[14.045,-19.463],[24.894,-2.109],[13.044,16.197],[26.016,-0.349],[0,0],[2.354,7.183]],"o":[[3.275,4.933],[0,0],[-21.243,0.285],[-13.47,18.667],[-25.84,2.189],[-22.96,-28.51],[0,0],[-2.24,0],[0,0]],"v":[[187.475,53.567],[185.089,62.34],[128.348,65.492],[83.335,98.48],[32.819,132.594],[-24.658,106.991],[-77.365,78.489],[-145.404,82.59],[-153.104,75.067]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[2.255,0],[0,0],[14.893,-17.732],[26.012,0],[13.354,15.062],[27.09,0],[0,0],[-1.796,5.874]],"o":[[1.99,5.874],[0,0],[-22.12,0],[-14.284,17.007],[-27,0],[-23.506,-26.511],[0,0],[-2.225,0],[0,0]],"v":[[166.553,66.97],[163.687,74.121],[100.042,74.121],[53.472,105.764],[-2.391,134.08],[-58.331,105.362],[-113.552,74.121],[-168.56,74.121],[-171.49,66.97]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[1.343,0],[0,0],[4.915,-16.108],[7.245,0.402],[1.838,6.151],[14.938,5.276],[0,0],[0,0]],"o":[[-10.881,5.678],[0,0],[-18.935,-7.349],[-4.034,13.22],[-7.872,-0.437],[-7.009,-23.459],[0,0],[-1.325,0],[0,0]],"v":[[3.601,88.261],[-16.981,89.538],[-67.103,67.93],[-100.415,85.006],[-121.12,102.996],[-137.116,90.106],[-158.563,33.871],[-207.208,14.371],[-209.579,13.095]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[1.036,0],[0,0],[0,0],[0.327,0],[0,0],[0,0],[0,0],[0.254,1.303]],"o":[[-2.511,-0.697],[0,0],[-2.228,0],[0,0],[-0.34,0],[0,0],[0,0],[-1.022,0],[0,0]],"v":[[-210.672,47.47],[-212.538,44.996],[-199.303,38.871],[-198.118,55.631],[-198.575,59.246],[-191.46,46.481],[-196.224,-1.754],[-180.777,-14.379],[-181.352,-14.53]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0.473,0],[0,0],[1.225,-1.665],[4.623,-4.292],[2.098,10.718],[5.133,-4.84],[0,0],[1.652,-2.22]],"o":[[1.442,0.974],[0,0],[-0.414,0.006],[-3.349,4.553],[-2.601,2.415],[-3.432,-17.534],[0,0],[-0.467,0],[0,0]],"v":[[178.354,-7.975],[178.774,-9.196],[177.674,-6.607],[176.064,-2.354],[171.12,78.792],[162.83,45.892],[154.452,29.932],[141.401,50.031],[139.596,54.012]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[1.419,0],[0,0],[5.062,-1.76],[22.297,-5.797],[6.392,11.426],[18.397,-4.993],[0,0],[4.957,-2.22]],"o":[[3.516,1.03],[0,0],[-1.249,0.007],[-13.84,4.813],[-13.319,3.463],[-14.158,-25.308],[0,0],[-1.4,0],[0,0]],"v":[[203.484,-1.183],[199.68,3.34],[187.374,8.965],[175.313,13.857],[139.069,95.885],[109.108,77.921],[71.603,50.59],[15.138,72.09],[-0.957,78.317]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[30.244,0],[0,0],[-2.005,6.384]],"o":[[2.222,6.384],[0,0],[-24.696,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0]],"v":[[186.181,62.567],[182.981,70.34],[111.926,70.34],[59.935,104.732],[-2.433,135.51],[-64.886,104.296],[-126.536,70.34],[-187.948,70.34],[-191.219,62.567]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.019607843831,0.388235300779,0.639215707779,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[0,0],[6.073,0],[0,0],[0,6.073]],"o":[[0,6.073],[0,0],[-6.073,0],[0,0]],"v":[[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[6.073,0],[0,0],[0,5.488]],"o":[[0,5.488],[0,0],[-6.073,0],[0,0]],"v":[[208.694,190.159],[197.697,200.096],[-202.563,200.096],[-213.559,190.159]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[6.073,0],[0,0],[0,6.327]],"o":[[0,6.327],[0,0],[-6.073,0],[0,0]],"v":[[208.694,195.64],[197.697,207.096],[-202.563,207.096],[-213.559,195.64]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[6.073,0],[0,0],[0,6.073]],"o":[[0,6.073],[0,0],[-6.073,0],[0,0]],"v":[[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[5.477,0],[0,0],[2.139,7.871]],"o":[[0,6.073],[0,0],[-7.733,0.001],[0,0]],"v":[[213.027,173.849],[203.11,184.846],[-159.833,210.846],[-170.859,199.195]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[5.44,0],[0,0],[0,5.588]],"o":[[0,5.588],[0,0],[-5.44,0],[0,0]],"v":[[186.718,189.826],[176.868,199.944],[-181.65,199.944],[-191.5,189.826]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[4.206,1.706],[0,0],[0,0]],"o":[[-4.875,1.325],[0,0],[-1.821,-0.481],[0,0]],"v":[[1.776,224.426],[-9.796,223.444],[-223.122,124.194],[-227.412,121.087]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[2.5,0],[0,0],[0,5.588]],"o":[[0,5.588],[0,0],[-0.548,0],[0,0]],"v":[[-209.078,172.326],[-213.605,182.444],[-194.097,77.444],[-195.089,67.326]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[1.141,0],[0,0],[0.772,0.282]],"o":[[-0.211,1.107],[0,0],[-1.141,0],[0,0]],"v":[[201.526,81.989],[199.644,85.429],[144.883,190.429],[141.38,190.262]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[3.122,-1.513],[0,0],[0.351,0.527]],"o":[[0.004,0.643],[0,0],[-1.026,0.648],[0,0]],"v":[[232.837,101.199],[227.977,105.096],[2.399,210.096],[-3.131,209.815]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[6.073,0],[0,0],[0,6.073]],"o":[[0,6.073],[0,0],[-6.073,0],[0,0]],"v":[[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.46274510026,0.490196079016,0.592156887054,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0]],"v":[[208.694,73.443],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,70.969]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,5.488],[0,0]],"o":[[0,0],[0,5.488],[0,0],[-6.073,0],[0,0],[0,0]],"v":[[208.62,76.327],[208.694,190.159],[197.697,200.096],[-202.563,200.096],[-213.559,190.159],[-213.633,74.091]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.327],[0,0]],"o":[[0,0],[0,6.327],[0,0],[-6.073,0],[0,0],[0,0]],"v":[[208.694,67.861],[208.694,195.64],[197.697,207.096],[-202.563,207.096],[-213.559,195.64],[-213.559,65.283]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0]],"v":[[208.694,73.443],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,70.969]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[5.477,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-5.477,0],[0,0],[0,0]],"v":[[212.027,61.193],[213.027,173.849],[203.11,184.846],[-159.833,210.846],[-169.75,199.849],[-168.84,87.205]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[5.44,0],[0,0],[0,5.588],[0,0]],"o":[[0,0],[0,5.588],[0,0],[-5.44,0],[0,0],[0,0]],"v":[[186.718,76.976],[186.718,189.826],[176.868,199.944],[-181.65,199.944],[-191.5,189.826],[-191.5,74.699]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[3.241,0],[0,0],[0,5.588],[0,0]],"o":[[0,0],[0,5.588],[0,0],[-3.241,0],[0,0],[0,0]],"v":[[-3.26,92.393],[-3.928,213.326],[-9.796,223.444],[-223.122,124.194],[-228.99,114.076],[-229.027,6.896]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[2.5,0],[0,0],[0,5.588],[0,0]],"o":[[0,0],[0,5.588],[0,0],[-0.548,0],[0,0],[0,0]],"v":[[-208.83,47.226],[-209.078,172.326],[-213.605,182.444],[-194.097,77.444],[-195.089,67.326],[-187.678,-29.801]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[1.141,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-1.141,0],[0,0],[0,0]],"v":[[196.611,-27.103],[201.71,74.432],[199.644,85.429],[144.883,190.429],[142.816,179.432],[139.733,59.302]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[3.424,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-3.424,0],[0,0],[0,0]],"v":[[234.426,-8.057],[234.176,94.099],[227.977,105.096],[2.399,210.096],[-3.801,199.099],[-1.801,78.969]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.073],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0]],"v":[[208.694,73.443],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,70.969]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[-0.012,-5.032],[0,0],[0,0],[-26.166,-30.559],[-29.38,0],[-17.356,20.61],[-24.689,0],[0,0],[-2.664,4.136]],"o":[[0.013,5.266],[0,0],[21.819,0],[16,18.686],[27.7,0],[19.472,-23.123],[0,0],[0,0],[1.427,-2.215]],"v":[[-212.7,72.046],[-203.094,77.756],[-122.69,77.756],[-69.97,109.841],[-3.258,142.926],[63.183,112.528],[115.166,77.358],[200.335,77.756],[208.069,74.257]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[-0.012,-4.547],[0,0],[0,0],[-26.166,-27.613],[-29.38,0],[-17.356,18.623],[-24.689,0],[0,0],[-2.664,3.737]],"o":[[0.013,4.758],[0,0],[21.819,0],[16,16.885],[27.7,0],[19.472,-20.894],[0,0],[0,0],[1.427,-2.001]],"v":[[-212.774,75.065],[-203.168,80.225],[-122.764,80.225],[-70.044,109.217],[-3.332,139.112],[63.109,111.644],[115.092,79.865],[200.261,80.225],[207.995,77.063]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[-0.012,-5.242],[0,0],[0,0],[-26.166,-31.835],[-29.38,0],[-17.356,21.471],[-24.689,0],[0,0],[-2.664,4.308]],"o":[[0.013,5.486],[0,0],[21.819,0],[16,19.467],[27.7,0],[19.472,-24.089],[0,0],[0,0],[1.427,-2.307]],"v":[[-212.7,66.406],[-203.094,72.354],[-122.69,72.354],[-69.97,105.78],[-3.258,140.246],[63.183,108.578],[115.166,71.94],[200.335,72.354],[208.069,68.709]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[-0.012,-5.032],[0,0],[0,0],[-26.166,-30.559],[-29.38,0],[-17.356,20.61],[-24.689,0],[0,0],[-2.664,4.136]],"o":[[0.013,5.266],[0,0],[21.819,0],[16,18.686],[27.7,0],[19.472,-23.123],[0,0],[0,0],[1.427,-2.215]],"v":[[-212.7,72.046],[-203.094,77.756],[-122.69,77.756],[-69.97,109.841],[-3.258,142.926],[63.183,112.528],[115.166,77.358],[200.335,77.756],[208.069,74.257]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[-0.381,-5.017],[0,0],[0,0],[-22.917,-30.255],[-25.185,2.134],[-14.654,20.808],[-21.237,0.285],[0,0],[-2.402,4.136]],"o":[[0.201,2.643],[0,0],[18.769,-0.251],[14.014,18.5],[23.745,-2.012],[16.441,-23.346],[0,0],[0,0],[1.287,-2.215]],"v":[[-168.065,88.282],[-158.813,90.506],[-73.958,85.86],[-28.957,112.594],[32.738,140.044],[86.233,106.237],[131.728,69.222],[204.489,65.506],[211.463,62.007]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[-0.011,-4.63],[0,0],[0,0],[-23.437,-28.116],[-26.316,0],[-15.546,18.962],[-22.114,0],[0,0],[-2.386,3.805]],"o":[[0.011,4.845],[0,0],[19.544,0],[14.331,17.193],[24.811,0],[17.442,-21.275],[0,0],[0,0],[1.278,-2.038]],"v":[[-190.73,75.691],[-182.126,80.944],[-110.107,80.944],[-62.885,110.464],[-3.13,140.904],[56.382,112.936],[102.944,80.578],[179.231,80.944],[186.159,77.725]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[-0.006,-4.63],[0,0],[0,0],[-5.552,-10.433],[-11.835,-2.714],[-5.464,14.296],[-10.414,-3.056],[0,0],[-1.422,3.805]],"o":[[0.007,4.845],[0,0],[14.136,5.203],[4.094,7.692],[8.068,1.85],[5.411,-14.156],[0,0],[0,0],[0.761,-2.038]],"v":[[-228.568,7.887],[-218.372,13.218],[-158.078,39.104],[-140.062,90.794],[-121.533,107.332],[-96.142,86.878],[-74.432,69.59],[-9.289,95.418],[-3.594,93.141]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[-0.005,-4.63],[0,0],[0,0],[5.142,-15.767],[-0.331,0],[0,0],[-2.227,0],[0,0],[-1.097,3.805]],"o":[[0.005,4.845],[0,0],[-2.148,3.634],[-3.646,11.179],[0.312,0],[0,0],[0,0],[0,0],[0.587,-2.038]],"v":[[-187.324,-28.809],[-188.371,-25.056],[-198.477,6.194],[-192.267,49.595],[-198.585,63.912],[-198.206,45.536],[-199.18,43.828],[-212.27,51.194],[-209.087,47.975]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[-2.59,4.314],[0,0],[0,0],[-1.515,-8.08],[-3.843,0.181],[-0.455,31.128],[-2.671,2.715],[0,0],[-1.52,2.305]],"o":[[0.844,-1.406],[0,0],[3.515,-2.166],[2.822,15.05],[3.434,-0.162],[0.121,-8.29],[0,0],[0,0],[1.166,-1.769]],"v":[[140.931,51.989],[143.078,48.665],[156.097,28.63],[163.806,54.583],[171.867,79.028],[175.228,11.596],[178.468,-6.677],[179.173,-8.335],[179.395,-8.771]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[-0.007,-5.032],[0,0],[0,0],[-9.934,-19.08],[-16.103,7.982],[-4.644,25.819],[-11.7,4.239],[-2.786,1.034],[0,0]],"o":[[0.007,5.266],[0,0],[13.979,-4.159],[8.008,15.381],[13.106,-6.497],[2.046,-11.374],[0,0],[2.786,-1.034],[0,0]],"v":[[-1.316,80.046],[4.867,80.113],[72.003,55.06],[105.189,78.181],[142.176,97.89],[168.341,33.778],[185.36,11.966],[219.695,-1.208],[225.573,-2.913]],"c":false}]},{"t":156,"s":[{"i":[[-0.012,-5.032],[0,0],[0,0],[-26.166,-30.559],[-29.38,0],[-17.356,20.61],[-24.689,0],[0,0],[-2.664,4.136]],"o":[[0.013,5.266],[0,0],[21.819,0],[16,18.686],[27.7,0],[19.472,-23.123],[0,0],[0,0],[1.427,-2.215]],"v":[[-212.7,72.046],[-203.094,77.756],[-122.69,77.756],[-69.97,109.841],[-3.258,142.926],[63.183,112.528],[115.166,77.358],[200.335,77.756],[208.069,74.257]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.988235294819,0.988235294819,0.980392158031,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[0,0],[5.728,0],[0,0],[0,5.802],[0,0],[0,0],[-75.629,-1.934],[-42.875,-1.649],[0,0]],"o":[[0,5.802],[0,0],[-5.737,0],[0,0],[0,0],[47.878,-1.649],[63.754,1.63],[0,0],[0,0]],"v":[[196.064,192.382],[185.694,202.879],[-191.989,202.879],[-202.369,192.382],[-202.369,77.878],[-117.848,78.499],[-3.148,145.644],[115.685,78.499],[195.979,77.878]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[5.728,0],[0,0],[0,5.243],[0,0],[0,0],[-75.629,-1.747],[-42.875,-1.49],[0,0]],"o":[[0,5.243],[0,0],[-5.737,0],[0,0],[0,0],[47.878,-1.49],[63.754,1.473],[0,0],[0,0]],"v":[[196.064,186.801],[185.694,196.286],[-191.989,196.286],[-202.369,186.801],[-202.442,80.335],[-117.922,80.896],[-3.221,141.568],[115.611,80.896],[195.905,80.335]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[5.728,0],[0,0],[0,6.044],[0,0],[0,0],[-75.629,-2.014],[-42.875,-1.718],[0,0]],"o":[[0,6.044],[0,0],[-5.737,0],[0,0],[0,0],[47.878,-1.718],[63.754,1.698],[0,0],[0,0]],"v":[[196.064,191.768],[185.694,202.703],[-191.989,202.703],[-202.369,191.768],[-202.369,72.481],[-117.848,73.129],[-3.148,143.077],[115.685,73.129],[195.979,72.481]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[5.728,0],[0,0],[0,5.802],[0,0],[0,0],[-75.629,-1.934],[-42.875,-1.649],[0,0]],"o":[[0,5.802],[0,0],[-5.737,0],[0,0],[0,0],[47.878,-1.649],[63.754,1.63],[0,0],[0,0]],"v":[[196.064,192.382],[185.694,202.879],[-191.989,202.879],[-202.369,192.382],[-202.369,77.878],[-117.848,78.499],[-3.148,145.644],[115.685,78.499],[195.979,77.878]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[5.165,0],[0,0],[0,5.802],[0,0],[0,0],[-64.994,3.566],[-36.903,-1.155],[0,0]],"o":[[0,5.802],[0,0],[-5.174,0],[0,0],[0,0],[41.163,-2.201],[54.788,-3.006],[0,0],[0,0]],"v":[[201.637,170.132],[192.286,180.629],[-150.298,206.629],[-159.658,196.132],[-158.158,90.628],[-69.783,86.547],[33.062,142.744],[132.19,70.357],[200.561,65.628]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[5.13,0],[0,0],[0,5.338],[0,0],[0,0],[-67.742,-1.779],[-38.404,-1.517],[0,0]],"o":[[0,5.338],[0,0],[-5.139,0],[0,0],[0,0],[42.885,-1.517],[57.105,1.5],[0,0],[0,0]],"v":[[175.405,186.406],[166.117,196.064],[-172.179,196.064],[-181.476,186.406],[-181.476,81.057],[-105.77,81.628],[-3.031,143.404],[103.409,81.628],[175.329,81.057]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[3.056,0],[0,0],[0,5.338],[0,0],[0,0],[-35.281,-3.323],[-22.879,-1.517],[0,0]],"o":[[0,5.338],[0,0],[-3.061,0],[0,0],[0,0],[18.927,4.895],[22.83,2.151],[0,0],[0,0]],"v":[[-10.668,209.906],[-16.201,219.564],[-217.479,120.314],[-223.018,110.656],[-219.778,14.932],[-158.177,40.128],[-123.205,109.747],[-78.972,68.312],[-10.045,96.473]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[2.357,0],[0,0],[0,5.338],[0,0],[0,0],[-0.852,-1.217],[-3.867,-1.517],[0,0]],"o":[[0,5.338],[0,0],[-0.517,0],[0,0],[0,0],[0.301,7.451],[0.718,1.026],[0,0],[0,0]],"v":[[-214.277,168.906],[-218.545,178.564],[-193.143,73.564],[-194.08,63.906],[-187.697,-24.818],[-198.926,7.128],[-198.583,65.622],[-199.133,44.628],[-214.063,51.307]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[1.076,0],[0,0],[0,5.802],[0,0],[0,0],[-4.579,3.154],[-5.963,2.221],[0,0]],"o":[[0,5.802],[0,0],[-1.078,0],[0,0],[0,0],[6.011,2.104],[4.099,-2.823],[0,0],[0,0]],"v":[[199.337,70.715],[197.388,81.212],[146.869,186.212],[144.919,175.715],[140.906,57.485],[160.35,30.851],[171.776,78.295],[179.373,-1.608],[195.386,-22.907]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[3.229,0],[0,0],[0,5.802],[0,0],[0,0],[-29.905,3.094],[-19.742,2.348],[0,0]],"o":[[0,5.802],[0,0],[-3.234,0],[0,0],[0,0],[35.749,-0.902],[35.493,-3.672],[0,0],[0,0]],"v":[[227.056,90.382],[222.549,103.558],[8.36,205.879],[6.008,194.382],[7.758,84.057],[73.751,50.25],[137.007,96.519],[178.742,14.249],[227.258,-3.622]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[5.728,0],[0,0],[0,5.802],[0,0],[0,0],[-75.629,-1.934],[-42.875,-1.649],[0,0]],"o":[[0,5.802],[0,0],[-5.737,0],[0,0],[0,0],[47.878,-1.649],[63.754,1.63],[0,0],[0,0]],"v":[[196.064,192.382],[185.694,202.879],[-191.989,202.879],[-202.369,192.382],[-202.369,77.878],[-117.848,78.499],[-3.148,145.644],[115.685,78.499],[195.979,77.878]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[0,0.723,0.727,0.805,0.332,0.789,0.793,0.857,1,0.855,0.859,0.91]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[0,0.722,0.726,0.804,0.332,0.779,0.784,0.851,1,0.837,0.842,0.898]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"t":141,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-2.817,199.036],"to":[-15.951,-6.665],"ti":[47.998,15.185]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[-125.426,153.739],"to":[-52.438,-16.589],"ti":[-15.549,13.165]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-188.529,105.451],"to":[29.781,-25.216],"ti":[-59.526,-2.327]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[175.871,47.742],"to":[59.526,2.327],"ti":[29.781,-25.216]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[168.625,119.414],"to":[-29.781,25.216],"ti":[28.574,-13.27]},{"t":141,"s":[-2.817,199.036]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-2.817,85.463],"to":[-6.771,0.522],"ti":[21.354,-4.004]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[-89.594,66.161],"to":[-24.564,4.605],"ti":[-10.516,10.454]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-59.502,81.976],"to":[19.657,-19.542],"ti":[-34.505,12.641]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[115.124,-31.791],"to":[34.505,-12.641],"ti":[19.657,-19.542]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[147.529,6.127],"to":[-19.657,19.542],"ti":[25.058,-13.223]},{"t":141,"s":[-2.817,85.463]}]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.9,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.073],[0,0],[0,0],[-53.606,0],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0],[0,0],[53.606,0],[0,0]],"v":[[208.694,76.85],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,76.85],[-91.086,76.85],[-3.045,134.51],[84.052,76.85]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,5.488],[0,0],[0,0],[-53.606,0],[0,0]],"o":[[0,0],[0,5.488],[0,0],[-6.073,0],[0,0],[0,0],[0,0],[53.606,0],[0,0]],"v":[[208.62,79.406],[208.694,190.159],[197.697,200.096],[-202.563,200.096],[-213.559,190.159],[-213.633,79.406],[-91.159,79.406],[-3.119,131.507],[83.979,79.406]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.327],[0,0],[0,0],[-53.606,0],[0,0]],"o":[[0,0],[0,6.327],[0,0],[-6.073,0],[0,0],[0,0],[0,0],[53.606,0],[0,0]],"v":[[208.694,71.41],[208.694,195.64],[197.697,207.096],[-202.563,207.096],[-213.559,195.64],[-213.559,71.41],[-91.086,71.41],[-3.045,131.478],[84.052,71.41]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[6.073,0],[0,0],[0,6.073],[0,0],[0,0],[-53.606,0],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0],[0,0],[53.606,0],[0,0]],"v":[[208.694,76.85],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,76.85],[-91.086,76.85],[-3.045,134.51],[84.052,76.85]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[5.477,0],[0,0],[0,6.073],[0,0],[0,0],[-45.952,3.893],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-5.477,0],[0,0],[0,0],[0,0],[45.952,-3.893],[0,0]],"v":[[212.027,64.6],[213.027,173.849],[203.11,184.846],[-159.833,210.846],[-169.75,199.849],[-168.25,89.6],[-46.783,84.589],[32.21,131.642],[104.458,72.322]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[5.44,0],[0,0],[0,5.588],[0,0],[0,0],[-48.016,0],[0,0]],"o":[[0,0],[0,5.588],[0,0],[-5.44,0],[0,0],[0,0],[0,0],[48.016,0],[0,0]],"v":[[186.718,80.111],[186.718,189.826],[176.868,199.944],[-181.65,199.944],[-191.5,189.826],[-191.5,80.111],[-81.798,80.111],[-2.939,133.16],[75.075,80.111]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[3.241,0],[0,0],[0,5.588],[0,0],[0,0],[-19.718,-1.969],[-18.811,0.037]],"o":[[0,0],[0,5.588],[0,0],[-3.241,0],[0,0],[0,0],[0,0],[21.234,2.12],[16.939,3.162]],"v":[[-3.26,95.527],[-3.928,213.326],[-9.796,223.444],[-223.122,124.194],[-228.99,114.076],[-228.527,11.807],[-150.896,41.736],[-122.657,102.991],[-80.352,64.42]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[2.5,0],[0,0],[0,5.588],[0,0],[0,0],[-0.604,0],[0,0]],"o":[[0,0],[0,5.588],[0,0],[-0.548,0],[0,0],[0,0],[0,0],[0.604,0],[0,0]],"v":[[-208.83,50.361],[-209.078,172.326],[-213.605,182.444],[-194.097,77.444],[-195.089,67.326],[-187.678,-24.389],[-198.017,7.611],[-198.582,58.616],[-200.718,40.361]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[1.141,0],[0,0],[0,6.073],[0,0],[0,0],[-4.289,2.547],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-1.141,0],[0,0],[0,0],[0,0],[3.048,-1.811],[0,0]],"v":[[197.505,-23.88],[201.71,74.432],[199.644,85.429],[144.883,190.429],[142.816,179.432],[139.482,57.35],[158.681,28.957],[172.269,78.27],[183.422,-3.369]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[3.424,0],[0,0],[0,6.073],[0,0],[0,0],[-25.705,3.213],[0,0]],"o":[[0,0],[0,6.073],[0,0],[-3.424,0],[0,0],[0,0],[0,0],[18.27,-2.283],[0,0]],"v":[[234.426,-4.65],[234.176,94.099],[227.977,105.096],[2.399,210.096],[-3.801,199.099],[0.199,79.6],[91.338,50.1],[141.462,95.385],[172.41,17.475]],"c":true}]},{"t":156,"s":[{"i":[[-9.29,-1.058],[0,0],[6.073,0],[0,0],[0,6.073],[0,0],[0,0],[-53.606,0],[0,0]],"o":[[8.657,17.122],[0,6.073],[0,0],[-6.073,0],[0,0],[0,0],[0,0],[53.606,0],[0,0]],"v":[[202.692,69.498],[208.694,196.099],[197.697,207.096],[-202.563,207.096],[-213.559,196.099],[-213.559,76.85],[-91.086,76.85],[-3.045,134.51],[84.052,76.85]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.102,0.894,0.898,0.945,0.551,0.892,0.896,0.943,1,0.89,0.894,0.941]}},"s":{"a":0,"k":[-2.817,208.036]},"e":{"a":0,"k":[-2.817,36.67]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false}],"ip":0,"op":180,"st":-4,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Sides 2","parent":5,"sr":1,"ks":{"p":{"a":0,"k":[258.346,386.27,0]},"a":{"a":0,"k":[258.346,386.27,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-226.018,192.184],[-221.698,71.834]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-186.944,198.329],[-237.498,68.714]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-194.648,172.236],[-195.341,67.631]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[129.696,179.308],[135.892,61.328]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-22.105,210.654],[203.475,104.098]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-232.676,172.153],[194.034,172.005]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[159.198,177.903],[163.843,10.78]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-21.813,195.156],[203.749,91.959]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-232.676,172.153],[194.034,172.005]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[159.198,177.903],[163.843,10.78]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-21.296,194.939],[203.087,91.717]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-232.676,172.153],[192.534,172.005]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-232.676,172.153],[192.534,172.005]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-21.431,70.523],[-21.296,194.939]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-44.867,70.284],[-45.016,195.436]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-119.332,70.893],[-120.05,197.515]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-166.487,69.503],[-167.566,197.062]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-232.864,70],[-234.5,199]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.462745127958,0.490196108351,0.592156862745,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[449.758,332.371]},"a":{"a":0,"k":[165.889,64.877]},"s":{"a":0,"k":[105,105]},"r":{"a":0,"k":0},"o":{"a":1,"k":[{"t":43,"s":[0],"h":1},{"t":53,"s":[100],"h":1},{"t":119,"s":[0],"h":1}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"R Side","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240.043,79.058],[-228.079,181.337]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.36,182.453],[-264.251,182.638]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.675,173.186],[126.058,187.273]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-272.668,104.881],[-52.936,204.809]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240.043,79.058],[-228.079,181.337]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.36,182.453],[-264.251,182.638]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.675,173.186],[126.058,187.273]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-271.715,101.071],[-52.936,204.809]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":117,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240.043,79.058],[-228.079,181.337]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.36,182.453],[-264.251,182.638]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-264.675,173.186],[126.058,187.273]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-271.688,86.309],[-52.936,204.809]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-262.973,82.244],[-75.991,204.925]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-245.544,74.114],[-149.697,206.244]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-240.529,71.041],[-196.368,205.307]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-223.227,84],[-262,206.5]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":123,"s":[0.462745127958,0.490196108351,0.592156862745,1]},{"t":132,"s":[0.537186324596,0.562710881233,0.657516300678,1]}]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":1,"k":[{"t":51,"s":[0],"h":1},{"t":93,"s":[100],"h":1}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[253.262,405.212]},"a":{"a":0,"k":[-52.75,144.25]},"s":{"a":0,"k":[105,105]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Front 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.697,358.335]},"a":{"a":0,"k":[252.697,358.335]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":81,"op":138,"st":-4,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Sides","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[5.091,81.209,0]},"a":{"a":0,"k":[258.346,386.27,0]},"s":{"a":0,"k":[95.238,95.238,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-202.501,-61.985],[-231.63,69.947],[-232.353,189.826],[-222.498,63.999]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-239.891,-64.026],[-187.364,75.594],[-186.944,198.329],[-237.498,68.714]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-174.854,-60.896],[-195.525,55.101],[-193.086,169.063],[-194.757,56.501]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[139.699,-52.014],[130.864,65.138],[128.841,177.629],[135.892,61.328]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[204.093,-0.14],[-22.24,81.476],[-22.105,210.654],[203.475,104.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[193.928,46.791],[-233.175,45.546],[-232.676,172.153],[194.034,172.005]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[165.142,-34.385],[158.134,51.833],[159.198,177.903],[163.843,10.78]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[203.705,-6.807],[-21.431,70.523],[-21.296,194.939],[203.087,91.717]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[193.928,46.791],[-233.175,45.546],[-232.676,172.153],[194.034,172.005]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[165.142,-34.385],[158.134,51.833],[159.198,177.903],[163.843,10.78]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[203.705,-6.807],[-21.431,70.523],[-21.296,194.939],[203.087,91.717]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[192.428,46.791],[-233.175,45.546],[-232.676,172.153],[192.534,172.005]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[168.118,-34.881],[158.134,51.833],[159.198,177.903],[166.818,10.284]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[204.181,-9.664],[-21.431,70.523],[-21.296,194.939],[203.563,88.86]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[201.745,3.037],[-44.867,70.284],[-45.016,195.436],[201.226,106.531]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[196.874,28.44],[-119.332,70.893],[-120.05,197.515],[196.553,141.873]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[193.772,44.614],[-166.487,69.503],[-167.566,197.062],[193.578,164.376]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[189,69.5],[-232.864,70],[-234.5,199],[189,199]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[0,0.64,0.645,0.739,0.332,0.709,0.714,0.793,1,0.777,0.783,0.848]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[0,0.771,0.775,0.851,0.332,0.847,0.851,0.91,1,0.923,0.927,0.969]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[0,0.655,0.66,0.752,0.332,0.727,0.732,0.808,1,0.799,0.804,0.864]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[0,0.655,0.66,0.752,0.332,0.727,0.732,0.808,1,0.799,0.804,0.864]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[0,0.655,0.66,0.752,0.332,0.727,0.732,0.808,1,0.799,0.804,0.864]},{"t":130,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74,"s":[-221.061,97.608],"to":[6.033,-0.818],"ti":[-55.257,2.799]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-184.864,92.698],"to":[55.257,-2.799],"ti":[-46.462,-8.411]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[110.484,80.813],"to":[46.462,8.411],"ti":[19.499,-14.498]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[93.906,143.165],"to":[-19.499,14.498],"ti":[-10.82,11.361]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-6.507,167.8],"to":[10.82,-11.361],"ti":[-16.987,8.623]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[158.826,75.001],"to":[16.987,-8.623],"ti":[27.651,-15.087]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[95.412,116.062],"to":[-27.651,15.087],"ti":[-10.569,6.843]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-7.077,165.523],"to":[10.569,-6.843],"ti":[-16.471,6.66]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[158.826,75.001],"to":[16.471,-6.66],"ti":[27.651,-15.087]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[91.751,125.563],"to":[-27.651,15.087],"ti":[-11.179,8.427]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[-7.077,165.523],"to":[11.179,-8.427],"ti":[-15.813,5.785]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[158.826,75.001],"to":[15.813,-5.785],"ti":[27.646,-13.996]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[87.801,130.811],"to":[-27.646,13.996],"ti":[15.809,-4.695]},{"t":130,"s":[-7.05,158.979]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74,"s":[-101.492,-27.113],"to":[3.357,19.124],"ti":[-23.554,-15.106]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-81.353,87.632],"to":[23.554,15.106],"ti":[-23.752,12.905]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[39.831,63.521],"to":[23.752,-12.905],"ti":[7.424,0.811]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[61.158,10.201],"to":[-7.424,-0.811],"ti":[-0.593,-9.841]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-4.715,58.652],"to":[0.593,9.841],"ti":[-11.467,12.618]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[64.718,69.25],"to":[11.467,-12.618],"ti":[12.3,-0.394]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[64.088,-17.053],"to":[-12.3,0.394],"ti":[-0.105,-14.384]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-9.08,71.612],"to":[0.105,14.384],"ti":[-9.837,15.54]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[64.718,69.25],"to":[9.837,-15.54],"ti":[12.3,-0.394]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[49.944,-21.629],"to":[-12.3,0.394],"ti":[-2.462,-15.146]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[-9.08,71.612],"to":[2.462,15.146],"ti":[-12.726,0.816]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[64.718,69.25],"to":[12.726,-0.816],"ti":[12.435,-4.019]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[67.277,66.718],"to":[-12.435,4.019],"ti":[12.861,-4.441]},{"t":130,"s":[-9.892,93.363]}]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[449.758,332.371]},"a":{"a":0,"k":[165.889,64.877]},"s":{"a":0,"k":[105,105]},"r":{"a":0,"k":0},"o":{"a":1,"k":[{"t":43,"s":[0],"h":1},{"t":53,"s":[100],"h":1},{"t":120,"s":[0],"h":1}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"R Side","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[159,79.5],[-262.864,80],[-263.499,200.508],[159,209]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[162.711,63.832],[-218.92,85.738],[-218.59,208.535],[162.722,187.832]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[121.066,72.273],[-228.568,75.493],[-229.708,190.833],[121.08,191.731]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-53.775,90.452],[-266.773,13.071],[-268.47,120.595],[-53.745,220.523]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-229.971,62.159],[-238.402,-24.071],[-240.043,79.058],[-228.079,181.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-265.21,56.5],[-232.728,-31.5],[-264.813,55.25],[-262.945,173.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[126.099,60.31],[-265.998,46.723],[-264.675,173.186],[126.058,187.273]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-52.966,79.5],[-270.971,3.071],[-272.668,104.881],[-52.936,204.809]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-229.971,62.159],[-239.394,-24.071],[-240.043,79.058],[-228.079,181.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-265.21,56.5],[-232.728,-31.5],[-264.813,55.25],[-262.945,173.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[126.099,60.31],[-265.998,46.723],[-264.675,173.186],[126.058,187.273]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-52.966,79.5],[-270.018,-0.738],[-271.715,101.071],[-52.936,204.809]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":117,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-229.971,62.159],[-239.394,-24.071],[-240.043,79.058],[-228.079,181.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-265.21,56.5],[-230.728,-28.5],[-264.813,55.25],[-262.945,173.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[126.099,60.31],[-265.998,46.723],[-264.675,173.186],[126.058,187.273]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-52.966,79.5],[-270.943,-16.929],[-271.688,86.309],[-52.936,204.809]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-76.418,79.505],[-262.749,-25.448],[-262.973,82.244],[-75.991,204.925]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-150.916,80.602],[-246.362,-42.487],[-245.544,74.114],[-149.697,206.244]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-198.091,79.524],[-242.009,-51.232],[-240.529,71.041],[-196.368,205.307]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-264.5,80.5],[-225.728,-47],[-223.227,84],[-262,206.5]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[0,0.771,0.775,0.851,0.332,0.847,0.851,0.91,1,0.923,0.927,0.969]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[0,0.771,0.775,0.851,0.332,0.847,0.851,0.91,1,0.923,0.927,0.969]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[0,0.771,0.775,0.851,0.332,0.847,0.851,0.91,1,0.923,0.927,0.969]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[0,0.749,0.753,0.827,0.332,0.824,0.827,0.886,1,0.898,0.902,0.945]},{"t":130,"s":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[-92.523,174.21],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[-92.523,174.21],"to":[-5.378,-2.764],"ti":[15.798,13.748]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-124.789,157.627],"to":[-15.798,-13.748],"ti":[10.42,10.984]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[-187.309,91.721],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-187.309,91.721],"to":[29.88,14.18],"ti":[-10.053,-11.836]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[-8.032,176.8],"to":[10.053,11.836],"ti":[29.88,14.18]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[-126.991,162.74],"to":[-29.88,-14.18],"ti":[-19.827,-2.343]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[-187.309,91.721],"to":[19.827,2.343],"ti":[-10.053,-11.836]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[-8.032,176.8],"to":[10.053,11.836],"ti":[34.339,11.26]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[-126.991,162.74],"to":[-34.339,-11.26],"ti":[14.512,8.916]},{"t":130,"s":[-214.065,109.241]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[-89.624,58.434],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[-89.624,58.434],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-89.624,58.434],"to":[4.647,-3.23],"ti":[-4.647,3.23]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[-61.742,39.052],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-61.742,39.052],"to":[8.777,5.427],"ti":[4.647,-3.23]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[-9.08,71.612],"to":[-4.647,3.23],"ti":[8.777,5.427]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[-89.624,58.434],"to":[-8.777,-5.427],"ti":[-13.424,-2.196]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[-61.742,39.052],"to":[13.424,2.196],"ti":[5.753,-4.676]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[-9.08,71.612],"to":[-5.753,4.676],"ti":[18.846,5.778]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[-96.257,67.109],"to":[-18.846,-5.778],"ti":[4.317,5.028]},{"t":130,"s":[-122.158,36.942]}]},"t":1,"nm":"Gradient Fill 3","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":1,"k":[{"t":51,"s":[0],"h":1},{"t":93,"s":[100],"h":1}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[253.262,405.212]},"a":{"a":0,"k":[-52.75,144.25]},"s":{"a":0,"k":[105,105]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Front","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.697,358.335]},"a":{"a":0,"k":[252.697,358.335]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":81,"op":138,"st":-4,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"ARROW 2","parent":9,"sr":1,"ks":{"o":{"a":1,"k":[{"t":0,"s":[100],"h":1},{"t":29,"s":[0],"h":1},{"t":118,"s":[100],"h":1}]},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[-3.762,-80.464,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.8,"y":1},"o":{"x":0.9,"y":0},"t":10,"s":[-3.762,-118.964,0],"to":[0,0,0],"ti":[-2.678,-20.327,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.15,"y":0},"t":26,"s":[-3.762,169.181,0],"to":[1.586,12.038,0],"ti":[-4.093,48.485,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":118,"s":[5.334,20.84,0],"to":[2.819,-33.389,0],"ti":[0,0,0]},{"i":{"x":0.793,"y":1},"o":{"x":0.69,"y":0},"t":126,"s":[12.6,-48.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.1,"y":0},"t":139,"s":[-4.122,-29.912,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":152,"s":[-3.762,-91.464,0],"to":[0,0,0],"ti":[0,0,0]},{"t":175,"s":[-3.762,-80.464,0]}]},"a":{"a":0,"k":[-3.762,-80.464,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,1.2,-1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":10,"s":[112,90,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":21,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":26,"s":[100,100,100]},{"i":{"x":[0.9,0.9,0.9],"y":[1,1,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":118,"s":[100,60,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.1,0.1,0.1],"y":[0,0,0]},"t":140,"s":[92,110,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":153,"s":[105,95,100]},{"t":179,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0.406,-0.919],[-2.584,-2.472],[0,0],[-3.055,3.102],[0,0],[0,0],[1.218,2.757]],"o":[[-1.218,2.757],[0,0],[3.055,3.102],[0,0],[0,0],[2.583,-2.471],[-0.406,-0.919]],"v":[[-87.444,-169.056],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[46.076,-127.756],[78.205,-160.382],[79.92,-169.055]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[0.315,-0.919],[-2.003,-2.472],[0,0],[-2.368,3.102],[0,0],[0,0],[0.944,2.757]],"o":[[-0.944,2.757],[0,0],[2.368,3.102],[0,0],[0,0],[2.002,-2.471],[-0.315,-0.919]],"v":[[-68.624,-170.556],[-67.295,-161.882],[-8.095,-93.546],[0.52,-93.546],[33.618,-130.004],[59.771,-161.882],[61.101,-170.555]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0.341,-0.919],[-2.171,-2.472],[0,0],[-2.567,3.102],[0,0],[0,0],[1.023,2.757]],"o":[[-1.024,2.757],[0,0],[2.567,3.102],[0,0],[0,0],[2.171,-2.471],[-0.341,-0.919]],"v":[[-74.085,-170.121],[-72.644,-161.447],[-8.45,-90.425],[0.89,-90.425],[37.233,-129.352],[65.121,-161.447],[66.562,-170.12]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0.365,-0.659],[-2.319,-1.773],[0,0],[-2.743,2.225],[0,0],[0,0],[1.093,1.978]],"o":[[-1.094,1.978],[0,0],[2.743,2.225],[0,0],[0,0],[2.319,-1.773],[-0.364,-0.659]],"v":[[-79.098,-117.766],[-77.558,-111.543],[-8.977,-58.904],[1,-58.904],[40.194,-88.383],[69.605,-111.543],[71.145,-117.765]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0.37,-0.694],[-2.355,-1.866],[0,0],[-2.784,2.342],[0,0],[0,0],[1.11,2.082]],"o":[[-1.11,2.082],[0,0],[2.784,2.342],[0,0],[0,0],[2.354,-1.866],[-0.37,-0.694]],"v":[[-80.208,-124.59],[-78.646,-118.042],[-9.022,-62.082],[1.106,-62.082],[40.976,-93.622],[70.749,-118.042],[72.312,-124.589]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0.005,-1.939],[-0.03,-5.213],[0,0],[-1.351,6.543],[0,0],[0,0],[0.047,5.815]],"o":[[-0.014,5.816],[0,0],[1.351,6.543],[0,0],[0,0],[0.1,-5.212],[-0.016,-1.938]],"v":[[-25.853,-171.904],[-25.833,-153.609],[-26.035,57.32],[15.121,56.831],[16.696,-144.584],[17.255,-180.711],[17.274,-199.385]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-0.085,-1.601],[-2.643,-3.629],[0,0],[-5.507,16.247],[0,0],[0,0],[1.187,4.663]],"o":[[0.231,4.356],[0,0],[7.025,18.363],[0,0],[0,0],[0.787,-3.916],[-0.396,-1.554]],"v":[[-58.819,-188.154],[-55.882,-175.578],[-16.754,-53.604],[8.493,-53.855],[41.609,-177.263],[52.513,-219.845],[52.098,-235.683]],"c":false}]},{"t":137,"s":[{"i":[[0.406,-0.919],[-2.584,-2.472],[0,0],[-3.055,3.102],[0,0],[0,0],[1.218,2.757]],"o":[[-1.218,2.757],[0,0],[3.055,3.102],[0,0],[0,0],[2.583,-2.471],[-0.406,-0.919]],"v":[[-87.444,-169.056],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[46.076,-127.756],[78.205,-160.382],[79.92,-169.055]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.678431391716,0.168627455831,0.168627455831,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[6.934,0],[0,0],[0,0],[4.82,0],[0,0],[0,-4.82],[0,0],[0,0],[-5.011,-4.793],[0,0],[-3.055,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-4.82,0],[0,0],[0,0],[-6.934,0],[0,0],[3.055,3.102],[0,0],[0,0],[5.011,-4.793]],"v":[[72.889,-173.63],[44.689,-173.63],[44.689,-214.152],[35.962,-222.879],[-43.486,-222.879],[-52.213,-214.152],[-52.213,-173.63],[-80.413,-173.63],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[65.027,-147.001],[78.205,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[5.375,0],[0,0],[0,0],[2.766,0],[0,0],[0,-4.82],[0,0],[0,0],[-3.884,-4.793],[0,0],[-2.368,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.766,0],[0,0],[0,0],[-5.375,0],[0,0],[2.368,3.102],[0,0],[0,0],[3.884,-4.793]],"v":[[55.651,-175.13],[33.793,-175.13],[24.208,-247.116],[19.199,-255.843],[-26.401,-255.843],[-31.41,-247.116],[-41.317,-175.13],[-63.175,-175.13],[-67.295,-161.882],[-8.095,-93.546],[0.52,-93.546],[49.557,-147.001],[59.771,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[5.677,0],[0,0],[0,0],[2.426,0],[0,0],[0,-4.82],[0,0],[0,0],[-4.102,-4.793],[0,0],[-2.501,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.426,0],[0,0],[0,0],[-5.676,0],[0,0],[2.501,3.102],[0,0],[0,0],[4.102,-4.793]],"v":[[58.986,-174.84],[32.652,-175.835],[22.186,-268.995],[17.793,-277.722],[-22.204,-277.722],[-26.597,-268.995],[-38.176,-176.084],[-66.509,-174.84],[-70.861,-161.592],[-8.331,-91.466],[0.766,-91.466],[52.55,-147.001],[63.338,-161.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[5.827,0],[0,0],[0,0],[2.721,0],[0,0],[0,-4.82],[0,0],[0,0],[-4.211,-4.793],[0,0],[-2.567,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.721,0],[0,0],[0,0],[-5.827,0],[0,0],[2.567,3.102],[0,0],[0,0],[4.211,-4.793]],"v":[[60.653,-174.695],[36.955,-174.695],[23.618,-291.131],[18.691,-299.858],[-26.161,-299.858],[-31.087,-291.131],[-44.479,-174.695],[-68.177,-174.695],[-72.644,-161.447],[-8.45,-90.425],[0.89,-90.425],[54.047,-147.001],[65.121,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[5.96,0],[0,0],[0,0],[2.972,0],[0,0],[0,-4.365],[0,0],[0,0],[-4.307,-4.342],[0,0],[-2.626,2.81],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.365],[0,0],[-2.972,0],[0,0],[0,0],[-5.96,0],[0,0],[2.626,2.81],[0,0],[0,0],[4.307,-4.342]],"v":[[62.046,-156.812],[37.809,-156.812],[26.206,-289.761],[20.824,-297.665],[-28.169,-297.665],[-33.551,-289.761],[-45.476,-156.812],[-69.713,-156.812],[-74.282,-144.812],[-8.626,-79.918],[0.926,-79.918],[55.289,-131.819],[66.615,-144.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[6.092,0],[0,0],[0,0],[3.223,0],[0,0],[0,-3.911],[0,0],[0,0],[-4.403,-3.89],[0,0],[-2.684,2.518],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.911],[0,0],[-3.223,0],[0,0],[0,0],[-6.092,0],[0,0],[2.684,2.518],[0,0],[0,0],[4.403,-3.89]],"v":[[63.439,-138.929],[38.664,-138.929],[28.597,-259.688],[22.761,-266.771],[-30.374,-266.771],[-36.211,-259.688],[-46.474,-138.929],[-71.25,-138.929],[-75.92,-128.178],[-8.801,-69.411],[0.963,-69.411],[56.532,-116.637],[68.11,-128.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[6.225,0],[0,0],[0,0],[3.475,0],[0,0],[0,-3.457],[0,0],[0,0],[-4.498,-3.439],[0,0],[-2.743,2.225],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.457],[0,0],[-3.475,0],[0,0],[0,0],[-6.225,0],[0,0],[2.743,2.225],[0,0],[0,0],[4.498,-3.439]],"v":[[64.832,-121.047],[39.518,-121.047],[31.186,-212.917],[24.894,-219.177],[-32.383,-219.177],[-38.674,-212.917],[-47.472,-121.047],[-72.786,-121.047],[-77.558,-111.543],[-8.977,-58.904],[1,-58.904],[57.775,-101.455],[69.605,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[6.319,0],[0,0],[0,0],[3.654,0],[0,0],[0,-3.639],[0,0],[0,0],[-4.566,-3.619],[0,0],[-2.784,2.342],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.639],[0,0],[-3.654,0],[0,0],[0,0],[-6.319,0],[0,0],[2.784,2.342],[0,0],[0,0],[4.566,-3.619]],"v":[[65.904,-128.043],[40.206,-128.043],[33.217,-156.142],[26.602,-162.73],[-33.625,-162.73],[-40.241,-156.142],[-48.102,-128.043],[-73.801,-128.043],[-78.646,-118.042],[-9.022,-62.082],[1.106,-62.082],[58.74,-107.515],[70.749,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0.083,0],[0,0],[0,0],[2.132,0],[0,0],[0,-10.165],[0,0],[0,0],[-0.058,-10.11],[0,0],[-1.351,6.543],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-10.165],[0,0],[-2.132,0],[0,0],[0,0],[-0.08,0],[0,0],[1.351,6.543],[0,0],[0,0],[-0.097,-9.212]],"v":[[17.191,-208.932],[17.268,-208.284],[17.057,-280.468],[13.197,-298.874],[-21.9,-305.043],[-25.759,-286.637],[-25.448,-181.551],[-25.772,-181.551],[-25.833,-153.609],[-26.035,57.32],[15.121,56.83],[17.039,-146.373],[17.255,-180.711]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[3.688,-0.408],[0,0],[0,0],[3.555,0],[0,0],[0,-8.329],[0,0],[0,0],[-3.75,-8.8],[0,0],[-9.198,21.627],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.329],[0,0],[-3.555,0],[0,0],[0,0],[-3.525,1.154],[0,0],[4.154,11.477],[0,0],[0,0],[1.732,-8.063]],"v":[[47.154,-242.431],[32.383,-240.464],[31.309,-287.363],[15.407,-295.914],[-32.91,-279.633],[-39.347,-264.551],[-40.088,-208.726],[-56.339,-198.867],[-55.882,-175.578],[-16.754,-53.604],[8.493,-53.855],[46.584,-193.881],[52.513,-219.845]],"c":true}]},{"t":137,"s":[{"i":[[6.934,0],[0,0],[0,0],[4.82,0],[0,0],[0,-4.82],[0,0],[0,0],[-5.011,-4.793],[0,0],[-3.055,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-4.82,0],[0,0],[0,0],[-6.934,0],[0,0],[3.055,3.102],[0,0],[0,0],[5.011,-4.793]],"v":[[72.889,-173.63],[44.689,-173.63],[44.689,-214.152],[35.962,-222.879],[-43.486,-222.879],[-52.213,-214.152],[-52.213,-173.63],[-80.413,-173.63],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[65.027,-147.001],[78.205,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.847058832645,0.227450981736,0.227450981736,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[0,6.367],[0,0],[1.691,-6.104]],"o":[[-8.556,0],[0,0],[6.372,0],[0,0]],"v":[[51.261,-160.382],[40.339,-173.187],[71.358,-173.63],[79.792,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[0,0],[0,6.367],[0,0],[1.31,-6.104]],"o":[[-6.632,0],[0,0],[4.939,0],[0,0]],"v":[[38.887,-161.882],[30.421,-174.687],[54.465,-175.13],[61.002,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[0,0],[0,6.367],[0,0],[1.384,-6.104]],"o":[[-7.004,0],[0,0],[5.216,0],[0,0]],"v":[[41.281,-161.592],[29.09,-175.392],[57.733,-174.84],[64.637,-161.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[0,6.367],[0,0],[1.421,-6.104]],"o":[[-7.19,0],[0,0],[5.355,0],[0,0]],"v":[[42.478,-161.447],[33.299,-174.252],[59.367,-174.695],[66.454,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0,0],[0,4.567],[0,0],[1.518,-4.378]],"o":[[-7.681,0],[0,0],[5.72,0],[0,0]],"v":[[45.417,-111.543],[35.612,-120.729],[63.458,-121.047],[71.03,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,4.807],[0,0],[1.541,-4.608]],"o":[[-7.797,0],[0,0],[5.807,0],[0,0]],"v":[[46.195,-118.042],[36.241,-127.709],[64.51,-128.043],[72.196,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,13.429],[0,0],[0.066,-12.873]],"o":[[-0.332,0],[0,0],[0.076,0],[0,0]],"v":[[17.243,-181.55],[17.232,-207.6],[17.173,-208.932],[17.316,-180.711]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[5.03,9.092],[0,0],[-0.238,-10.586]],"o":[[-4.659,0.515],[0,0],[3.389,-0.375],[0,0]],"v":[[43.474,-227.204],[30.147,-239.576],[46.34,-242.341],[51.076,-229.733]],"c":true}]},{"t":137,"s":[{"i":[[0,0],[0,6.367],[0,0],[1.691,-6.104]],"o":[[-8.556,0],[0,0],[6.372,0],[0,0]],"v":[[51.261,-160.382],[40.339,-173.187],[71.358,-173.63],[79.792,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.592,0.573,0.5,1,0.592,0.573,1,1,0.592,0.573,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[60.495,-168.776]},"e":{"a":0,"k":[60.464,-161.587]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[0,0],[0,6.367],[0,0],[-1.691,-6.104]],"o":[[8.556,0],[0,0],[-6.372,0],[0,0]],"v":[[-59.169,-160.382],[-48.247,-173.187],[-79.267,-173.63],[-87.701,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[0,0],[0,6.367],[0,0],[-1.31,-6.104]],"o":[[6.632,0],[0,0],[-4.939,0],[0,0]],"v":[[-46.709,-161.882],[-38.243,-174.687],[-62.286,-175.13],[-68.824,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[0,0],[0,6.367],[0,0],[-1.384,-6.104]],"o":[[7.004,0],[0,0],[-5.216,0],[0,0]],"v":[[-49.119,-161.592],[-34.929,-175.641],[-65.571,-174.84],[-72.476,-161.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[0,0],[0,6.367],[0,0],[-1.421,-6.104]],"o":[[7.19,0],[0,0],[-5.355,0],[0,0]],"v":[[-50.325,-161.447],[-41.146,-174.252],[-67.214,-174.695],[-74.301,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0,0],[0,4.567],[0,0],[-1.518,-4.378]],"o":[[7.681,0],[0,0],[-5.72,0],[0,0]],"v":[[-53.716,-111.543],[-43.911,-120.729],[-71.757,-121.047],[-79.329,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,4.807],[0,0],[-1.541,-4.608]],"o":[[7.797,0],[0,0],[-5.807,0],[0,0]],"v":[[-54.442,-118.042],[-44.488,-127.709],[-72.757,-128.043],[-80.443,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,13.429],[0,0],[-0.019,-12.873]],"o":[[0.098,0],[0,0],[-0.073,0],[0,0]],"v":[[-25.528,-153.609],[-25.402,-180.617],[-25.759,-181.551],[-25.856,-153.609]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[9.675,3.348],[0,0],[-4.142,-9.743]],"o":[[4.35,-1.424],[0,0],[-3.239,1.061],[0,0]],"v":[[-24.853,-193.451],[-37.833,-208.659],[-55.756,-199.057],[-56.885,-175.249]],"c":true}]},{"t":137,"s":[{"i":[[0,0],[0,6.367],[0,0],[-1.691,-6.104]],"o":[[8.556,0],[0,0],[-6.372,0],[0,0]],"v":[[-59.169,-160.382],[-48.247,-173.187],[-79.267,-173.63],[-87.701,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.592,0.573,0.5,1,0.592,0.573,1,1,0.592,0.573,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[-67.942,-169.151]},"e":{"a":0,"k":[-67.942,-161.962]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[-3.04,0],[0,0],[-1.419,-2.729],[11.828,0.898],[0,0],[0,0]],"o":[[0,0],[3.04,0],[0,0],[0,0],[-12.447,0],[1.419,-2.729]],"v":[[-41.139,-208.461],[33.616,-208.461],[40.724,-203.891],[32.711,-222.879],[-40.235,-222.879],[-48.247,-203.891]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[-1.745,0],[0,0],[-0.815,-2.729],[6.789,0.898],[0,0],[0,0]],"o":[[0,0],[1.745,0],[0,0],[0,0],[-7.144,0],[0.815,-2.729]],"v":[[-24.429,-248.293],[16.977,-247.794],[21.932,-236.855],[17.333,-255.843],[-24.535,-255.843],[-29.134,-236.855]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[-1.531,0],[0,0],[-0.715,-2.729],[5.954,0.898],[0,0],[0,0]],"o":[[0,0],[1.531,0],[0,0],[0,0],[-6.266,0],[0.715,-2.729]],"v":[[-18.773,-270.271],[14.612,-270.52],[20.189,-258.734],[16.156,-277.722],[-20.567,-277.722],[-24.6,-258.734]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-1.716,0],[0,0],[-0.801,-2.729],[6.677,0.898],[0,0],[0,0]],"o":[[0,0],[1.716,0],[0,0],[0,0],[-7.027,0],[0.801,-2.729]],"v":[[-23.087,-292.386],[15.867,-292.386],[21.379,-280.87],[16.856,-299.858],[-24.325,-299.858],[-28.848,-280.87]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[-1.875,0],[0,0],[-0.875,-2.472],[7.294,0.814],[0,0],[0,0]],"o":[[0,0],[1.875,0],[0,0],[0,0],[-7.676,0],[0.875,-2.472]],"v":[[-26.222,-290.287],[17.379,-289.793],[23.76,-280.467],[18.819,-297.665],[-26.164,-297.665],[-31.105,-280.467]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[-2.033,0],[0,0],[-0.949,-2.215],[7.91,0.729],[0,0],[0,0]],"o":[[0,0],[2.033,0],[0,0],[0,0],[-8.325,0],[0.949,-2.215]],"v":[[-27.806,-259.981],[20.692,-259.49],[25.945,-251.361],[20.586,-266.771],[-28.2,-266.771],[-33.558,-251.361]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[-2.192,0],[0,0],[-1.023,-1.958],[8.527,0.644],[0,0],[0,0]],"o":[[0,0],[2.192,0],[0,0],[0,0],[-8.973,0],[1.023,-1.958]],"v":[[-30.691,-208.835],[23.202,-208.835],[28.326,-205.556],[22.55,-219.177],[-30.039,-219.177],[-35.815,-205.556]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-2.305,0],[0,0],[-1.076,-2.06],[8.966,0.678],[0,0],[0,0]],"o":[[0,0],[2.305,0],[0,0],[0,0],[-9.436,0],[1.076,-2.06]],"v":[[-31.846,-151.846],[24.823,-151.846],[30.211,-148.395],[24.137,-162.73],[-31.161,-162.73],[-37.235,-148.395]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-1.345,0],[0,0],[-0.628,-5.756],[5.231,1.895],[0,0],[0,0]],"o":[[0,0],[1.345,0],[0,0],[0,0],[-5.505,0],[0.628,-5.756]],"v":[[-19.603,-297.706],[11.882,-293.11],[15.303,-258.826],[11.759,-298.874],[-20.462,-305.043],[-24.005,-264.995]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-2.243,0],[0,0],[-1.047,-4.716],[13.53,0.422],[0,0],[0,0]],"o":[[0,0],[2.243,0],[0,0],[0,0],[-9.181,0],[7.317,7.393]],"v":[[-17.18,-258.897],[27.965,-277.045],[27.521,-287.82],[13.009,-295.914],[-30.512,-279.633],[-35.34,-270.771]],"c":true}]},{"t":137,"s":[{"i":[[-3.04,0],[0,0],[-1.419,-2.729],[11.828,0.898],[0,0],[0,0]],"o":[[0,0],[3.04,0],[0,0],[0,0],[-12.447,0],[1.419,-2.729]],"v":[[-41.139,-208.461],[33.616,-208.461],[40.724,-203.891],[32.711,-222.879],[-40.235,-222.879],[-48.247,-203.891]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,1,0.592,0.573,0.5,1,0.592,0.573,1,1,0.592,0.573,0,1,0.5,0.5,1,0]}},"s":{"a":0,"k":[-4.38,-219.151]},"e":{"a":0,"k":[-4.224,-210.149]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[-5.684,1.305],[-2.015,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-7.225,-1.482],[-4.556,-4.358]],"v":[[-72.541,-173.409],[-3.762,-109.437],[-3.762,-80.464],[-85.729,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[-4.405,1.305],[-1.562,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-5.6,-1.482],[-3.531,-4.358]],"v":[[-57.073,-174.909],[-3.787,-120.192],[-3.787,-91.22],[-67.295,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-4.776,1.305],[-1.693,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-6.072,-1.482],[-3.829,-4.358]],"v":[[-61.562,-174.474],[-3.78,-117.071],[-3.78,-88.099],[-72.644,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[-5.102,0.936],[-1.809,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-6.486,-1.063],[-4.09,-3.126]],"v":[[-65.72,-120.888],[-3.989,-78.018],[-3.989,-57.235],[-77.558,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[-5.179,0.985],[-1.836,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-6.584,-1.119],[-4.152,-3.29]],"v":[[-66.627,-127.876],[-3.958,-82.198],[-3.958,-60.326],[-78.646,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-0.065,2.753],[-0.891,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-3.195,-3.126],[-0.052,-9.192]],"v":[[-25.681,-181.084],[-19.328,56.809],[-19.935,58.542],[-25.833,-153.609]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-2.188,3.09],[-1.486,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-12.683,-0.869],[-4.66,-6.399]],"v":[[-52.218,-199.814],[-3.882,-102.519],[-3.335,-43.41],[-55.882,-175.578]],"c":true}]},{"t":137,"s":[{"i":[[-5.684,1.305],[-2.015,0],[0,0],[0,0]],"o":[[0,0],[0,0],[-7.225,-1.482],[-4.556,-4.358]],"v":[[-72.541,-173.409],[-3.762,-109.437],[-3.762,-80.464],[-85.729,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.729,0.153,0.153,0.5,0.729,0.153,0.153,1,0.729,0.153,0.153,0,1,0.5,0.5,1,0]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.999},"o":{"x":0.167,"y":0},"t":31,"s":[-47.38,-122.839],"to":[3.061,5.938],"ti":[0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.031},"t":126,"s":[-29.012,-87.208],"to":[0,0],"ti":[3.061,5.938]},{"t":137,"s":[-47.38,-122.839]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.999},"o":{"x":0.167,"y":0},"t":31,"s":[-36.413,-133.997],"to":[1.955,8.095],"ti":[-2.776,-7.368]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.068},"t":118,"s":[-24.684,-85.429],"to":[2.776,7.368],"ti":[1.955,8.095]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.029},"t":126,"s":[-19.754,-89.787],"to":[-1.955,-8.095],"ti":[2.776,7.368]},{"t":137,"s":[-36.413,-133.997]}]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[5.684,1.305],[2.015,0],[0,0],[-21.525,22.902],[0,0]],"o":[[0,0],[0,0],[6.144,0],[7.795,-8.293],[4.556,-4.358]],"v":[[65.017,-173.409],[-3.762,-109.437],[-3.762,-80.464],[65.209,-146.441],[78.205,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[4.405,1.305],[1.562,0],[0,0],[-16.685,22.902],[0,0]],"o":[[0,0],[0,0],[4.762,0],[6.042,-8.293],[3.531,-4.358]],"v":[[49.55,-174.909],[-3.787,-120.192],[-3.787,-91.22],[49.698,-146.441],[59.771,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[4.776,1.305],[1.693,0],[0,0],[-18.089,22.902],[0,0]],"o":[[0,0],[0,0],[5.163,0],[6.55,-8.293],[3.829,-4.358]],"v":[[54.038,-174.474],[-3.78,-117.071],[-3.78,-88.099],[54.199,-146.441],[65.121,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[5.102,0.936],[1.809,0],[0,0],[-19.323,16.429],[0,0]],"o":[[0,0],[0,0],[5.515,0],[6.997,-5.949],[4.09,-3.126]],"v":[[57.766,-120.888],[-3.989,-78.018],[-3.989,-57.235],[57.938,-101.053],[69.605,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[5.179,0.985],[1.836,0],[0,0],[-19.616,17.29],[0,0]],"o":[[0,0],[0,0],[5.599,0],[7.103,-6.261],[4.152,-3.29]],"v":[[58.731,-127.876],[-3.958,-82.198],[-3.958,-60.326],[58.905,-107.092],[70.749,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0.068,2.753],[0.891,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[2.422,0],[0,0],[0.039,-9.517]],"v":[[17.097,-208.465],[15.319,49.867],[13.963,56.137],[16.885,-144.366],[17.255,-180.711]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[3.271,1.908],[1.486,0],[0,0],[-4.691,18.1],[0,0]],"o":[[0,0],[0,0],[11.517,-0.654],[1.925,-7.426],[1.57,-7.919]],"v":[[43.009,-241.588],[-3.882,-102.519],[-3.335,-43.41],[46.248,-192.339],[52.513,-219.845]],"c":true}]},{"t":137,"s":[{"i":[[5.684,1.305],[2.015,0],[0,0],[-21.525,22.902],[0,0]],"o":[[0,0],[0,0],[6.144,0],[7.795,-8.293],[4.556,-4.358]],"v":[[65.017,-173.409],[-3.762,-109.437],[-3.762,-80.464],[65.209,-146.441],[78.205,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.729,0.153,0.153,0.5,0.729,0.153,0.153,1,0.729,0.153,0.153,0,1,0.5,0.5,1,0]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.999},"o":{"x":0.167,"y":0},"t":31,"s":[40.183,-119.964],"to":[-0.921,6.161],"ti":[0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.031},"t":126,"s":[34.658,-82.998],"to":[0,0],"ti":[-0.921,6.161]},{"t":137,"s":[40.183,-119.964]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.999},"o":{"x":0.167,"y":0},"t":31,"s":[27.633,-133.157],"to":[-2.018,7.913],"ti":[3.658,-7.036]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.059},"t":118,"s":[15.527,-85.677],"to":[-3.658,7.036],"ti":[-2.018,7.913]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.031},"t":126,"s":[5.684,-90.941],"to":[2.018,-7.913],"ti":[-3.658,7.036]},{"t":137,"s":[27.633,-133.157]}]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[6.934,0],[0,0],[0,0],[4.82,0],[0,0],[0,-4.82],[0,0],[0,0],[-5.011,-4.793],[0,0],[-3.055,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-4.82,0],[0,0],[0,0],[-6.934,0],[0,0],[3.055,3.102],[0,0],[0,0],[5.011,-4.793]],"v":[[72.889,-173.63],[44.689,-173.63],[44.689,-214.152],[35.962,-222.879],[-43.486,-222.879],[-52.213,-214.152],[-52.213,-173.63],[-80.413,-173.63],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[63.261,-145.208],[78.205,-160.382]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[5.375,0],[0,0],[0,0],[2.766,0],[0,0],[0,-4.82],[0,0],[0,0],[-3.884,-4.793],[0,0],[-2.368,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.766,0],[0,0],[0,0],[-5.375,0],[0,0],[2.368,3.102],[0,0],[0,0],[3.884,-4.793]],"v":[[55.651,-175.13],[33.793,-175.13],[24.208,-247.116],[19.199,-255.843],[-26.401,-255.843],[-31.41,-247.116],[-41.317,-175.13],[-63.175,-175.13],[-67.295,-161.882],[-8.095,-93.546],[0.52,-93.546],[48.188,-145.208],[59.771,-161.882]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":23,"s":[{"i":[[5.677,0],[0,0],[0,0],[2.426,0],[0,0],[0,-4.82],[0,0],[0,0],[-4.102,-4.793],[0,0],[-2.501,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.426,0],[0,0],[0,0],[-5.676,0],[0,0],[2.501,3.102],[0,0],[0,0],[4.102,-4.793]],"v":[[58.986,-174.84],[32.652,-175.835],[22.186,-268.995],[17.793,-277.722],[-22.204,-277.722],[-26.597,-268.995],[-38.176,-176.084],[-66.509,-174.84],[-70.861,-161.592],[-8.331,-91.466],[0.766,-91.466],[51.104,-145.208],[63.338,-161.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[5.827,0],[0,0],[0,0],[2.721,0],[0,0],[0,-4.82],[0,0],[0,0],[-4.211,-4.793],[0,0],[-2.567,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-2.721,0],[0,0],[0,0],[-5.827,0],[0,0],[2.567,3.102],[0,0],[0,0],[4.211,-4.793]],"v":[[60.653,-174.695],[36.955,-174.695],[23.618,-291.131],[18.691,-299.858],[-26.161,-299.858],[-31.087,-291.131],[-44.479,-174.695],[-68.177,-174.695],[-72.644,-161.447],[-8.45,-90.425],[0.89,-90.425],[52.562,-145.208],[65.121,-161.447]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":25,"s":[{"i":[[5.96,0],[0,0],[0,0],[2.972,0],[0,0],[0,-4.365],[0,0],[0,0],[-4.307,-4.342],[0,0],[-2.626,2.81],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.365],[0,0],[-2.972,0],[0,0],[0,0],[-5.96,0],[0,0],[2.626,2.81],[0,0],[0,0],[4.307,-4.342]],"v":[[62.046,-156.812],[37.809,-156.812],[26.206,-289.761],[20.824,-297.665],[-28.169,-297.665],[-33.551,-289.761],[-45.476,-156.812],[-69.713,-156.812],[-74.282,-144.812],[-8.626,-79.918],[0.926,-79.918],[53.772,-130.195],[66.615,-144.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[{"i":[[6.092,0],[0,0],[0,0],[3.223,0],[0,0],[0,-3.911],[0,0],[0,0],[-4.403,-3.89],[0,0],[-2.684,2.518],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.911],[0,0],[-3.223,0],[0,0],[0,0],[-6.092,0],[0,0],[2.684,2.518],[0,0],[0,0],[4.403,-3.89]],"v":[[63.439,-138.929],[38.664,-138.929],[28.597,-259.688],[22.761,-266.771],[-30.374,-266.771],[-36.211,-259.688],[-46.474,-138.929],[-71.25,-138.929],[-75.92,-128.178],[-8.801,-69.411],[0.963,-69.411],[54.981,-115.181],[68.11,-128.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[6.225,0],[0,0],[0,0],[3.475,0],[0,0],[0,-3.457],[0,0],[0,0],[-4.498,-3.439],[0,0],[-2.743,2.225],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.457],[0,0],[-3.475,0],[0,0],[0,0],[-6.225,0],[0,0],[2.743,2.225],[0,0],[0,0],[4.498,-3.439]],"v":[[64.832,-121.047],[39.518,-121.047],[31.186,-212.917],[24.894,-219.177],[-32.383,-219.177],[-38.674,-212.917],[-47.472,-121.047],[-72.786,-121.047],[-77.558,-111.543],[-8.977,-58.904],[1,-58.904],[56.19,-100.168],[69.605,-111.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[6.319,0],[0,0],[0,0],[3.654,0],[0,0],[0,-3.639],[0,0],[0,0],[-4.566,-3.619],[0,0],[-2.784,2.342],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-3.639],[0,0],[-3.654,0],[0,0],[0,0],[-6.319,0],[0,0],[2.784,2.342],[0,0],[0,0],[4.566,-3.619]],"v":[[65.904,-128.043],[40.206,-128.043],[33.217,-156.142],[26.602,-162.73],[-33.625,-162.73],[-40.241,-156.142],[-48.102,-128.043],[-73.801,-128.043],[-78.646,-118.042],[-9.022,-62.082],[1.106,-62.082],[57.131,-106.161],[70.749,-118.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0.083,0],[0,0],[0,0],[2.132,0],[0,0],[0,-10.165],[0,0],[0,0],[-0.058,-10.11],[0,0],[-1.351,6.543],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-10.165],[0,0],[-2.132,0],[0,0],[0,0],[-0.08,0],[0,0],[1.351,6.543],[0,0],[0,0],[0.194,-10.11]],"v":[[17.191,-208.932],[16.855,-208.932],[17.057,-280.468],[13.197,-298.874],[-21.9,-305.043],[-25.759,-286.637],[-25.448,-181.551],[-25.772,-181.551],[-25.833,-153.609],[-26.035,57.32],[15.121,56.83],[16.914,-143.793],[17.255,-180.711]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[3.688,-0.408],[0,0],[0,0],[3.555,0],[0,0],[0,-8.329],[0,0],[0,0],[-5.125,-7.039],[0,0],[-8.651,20.121],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.329],[0,0],[-3.555,0],[0,0],[0,0],[-3.525,1.154],[0,0],[5.247,14.49],[0,0],[0,0],[1.818,-8.535]],"v":[[47.154,-242.431],[32.154,-240.773],[31.309,-287.363],[15.407,-295.914],[-32.91,-279.633],[-39.347,-264.551],[-40.088,-208.726],[-56.339,-198.867],[-55.882,-175.578],[-16.754,-53.604],[8.493,-53.855],[46.211,-191.68],[52.513,-219.845]],"c":true}]},{"t":137,"s":[{"i":[[6.934,0],[0,0],[0,0],[4.82,0],[0,0],[0,-4.82],[0,0],[0,0],[-5.011,-4.793],[0,0],[-3.055,3.102],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-4.82],[0,0],[-4.82,0],[0,0],[0,0],[-6.934,0],[0,0],[3.055,3.102],[0,0],[0,0],[5.011,-4.793]],"v":[[72.889,-173.63],[44.689,-173.63],[44.689,-214.152],[35.962,-222.879],[-43.486,-222.879],[-52.213,-214.152],[-52.213,-173.63],[-80.413,-173.63],[-85.729,-160.382],[-9.319,-82.791],[1.795,-82.791],[63.261,-145.208],[78.205,-160.382]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":5,"k":{"a":0,"k":[0,0.718,0.137,0.137,0.2,0.827,0.212,0.204,0.4,0.937,0.286,0.271,0.7,0.963,0.306,0.276,1,0.988,0.325,0.282]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":31,"s":[-3.005,-74.464],"to":[-0.022,11.723],"ti":[-0.071,-5.434]},{"i":{"x":0.833,"y":1},"o":{"x":0.6,"y":0},"t":118,"s":[-3.135,-4.125],"to":[0.071,5.434],"ti":[-0.022,11.723]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0.031},"t":126,"s":[-2.577,-41.859],"to":[0.022,-11.723],"ti":[0.071,5.434]},{"t":137,"s":[-3.005,-74.464]}]},"e":{"a":0,"k":[-2.971,-166.276]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Envelope Back","parent":8,"sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[-67.81,-5.926,0],"to":[1.177,-0.034,0],"ti":[3.601,2.191,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[-60.747,-6.131,0],"to":[-2.191,-1.333,0],"ti":[7.295,3.181,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[-64.041,-21.136,0],"to":[-4.695,-2.048,0],"ti":[1.871,0.845,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[-98.052,-19.809,0],"to":[-4.779,-2.157,0],"ti":[1.201,0.031,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[-105.239,-20.695,0],"to":[-1.201,-0.031,0],"ti":[-1.195,0.041,0]},{"t":118,"s":[-98.067,-20.939,0]}]},"s":{"a":0,"k":[141.31,137.693,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-6.019,-113.297],[-55,63.979],[-4.258,66.347],[28.405,-81.552]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-8.873,-95.009],[-50.601,64.197],[-12.045,64.76],[24.278,-110.528]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-50.573,-66.306],[-68.267,117.02],[86.9,66.04],[99.002,-100.923]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-116.274,-80.232],[-114.214,94.577],[139.992,87.037],[142.514,-77.962]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-78.585,-88.684],[-83.636,76.572],[86.969,87.679],[87.548,-44.506]],"c":true}]},{"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.797,-74.63],[-10.418,76.048],[23.319,90.716],[22.464,-22.16]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[0,1,1,1,0.5,0.872,0.88,0.936,1,0.744,0.761,0.872]},{"t":118,"s":[0,0.988,0.325,0.282,0.5,0.908,0.269,0.243,1,0.827,0.212,0.204]}]}},"s":{"a":0,"k":[19.99,-55.84]},"e":{"a":0,"k":[28.149,29.851]},"t":1,"nm":"Gradient Fill 7778","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[4.4,2.731],[0,0],[0,0],[0,0],[0,0],[2.602,3.991]],"o":[[-6.437,-3.995],[0,0],[0,0],[0,0],[0,0],[-4.229,-6.488]],"v":[[-4.087,-121.792],[-11.275,-116.491],[-67.5,81.5],[-2.5,83.5],[31.358,-74.035],[30.746,-87.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[2.949,-1.848],[0,0],[0,0],[0,0],[0,0],[4.249,0.818]],"o":[[-4.741,2.971],[0,0],[0,0],[0,0],[0,0],[-5.315,-1.686]],"v":[[-12.341,-99.369],[-18.389,-91.195],[-62.502,77.635],[-8.419,80.571],[31.883,-108.971],[28.422,-116.345]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[3.782,-1.024],[0.099,-0.288],[0,0],[0,0],[0,0],[7.494,-2.442]],"o":[[-7.686,2.081],[-2.444,7.119],[0,0],[0,0],[0,0],[-5.831,1.9]],"v":[[-52.725,-70.267],[-61.61,-62.294],[-81.736,122.02],[91.848,66.54],[104.64,-99.022],[96.66,-106.278]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[8.54,0.031],[0,0],[0,0],[0,0],[0,0],[9.206,0.813]],"o":[[-10.383,-0.038],[0,0],[0,0],[0,0],[0,0],[-5.503,-0.486]],"v":[[-114.624,-84.969],[-125.433,-74.59],[-126.064,104.253],[150.547,93.404],[155.761,-73.204],[146.448,-83.426]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[5.922,1.425],[0,0],[0,0],[0,0],[0.119,9.909],[0,0]],"o":[[-6.91,-1.663],[0,0],[0,0],[0,0],[-0.134,-11.158],[0,0]],"v":[[-79.863,-93.91],[-87.078,-88.081],[-95.486,86.247],[102.1,94.675],[100.849,-36.856],[98.122,-49.327]],"c":true}]},{"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-12.251,-80.812],[-12.258,-80.012],[-12.596,85.723],[26.1,97.712],[24.87,-23.464],[24.852,-24.57]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":112,"s":[0.6,0.60784295774,0.701960963829,1]},{"t":118,"s":[0.803921568627,0.196513905245,0.196513905245,1]}]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":112,"s":[0.898049507889,0.898049507889,0.958169974533,1]},{"t":118,"s":[0.988235353956,0.325490196078,0.282352941176,1]}]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false}],"ip":94,"op":119,"st":48,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Envelope","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"t":0,"s":[0],"h":1},{"t":20,"s":[100],"h":1},{"t":94,"s":[0],"h":1},{"t":118,"s":[100],"h":1},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":133,"s":[100]},{"t":135,"s":[0]}]},"p":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":16,"s":[-49.397,-101.137,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.2,"y":0.2},"t":31,"s":[-48.902,148.7,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.31,"y":0.969},"o":{"x":0.4,"y":0},"t":51,"s":[-48.902,148.7,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.01},"t":74,"s":[1.622,137.687,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[-51.324,149.697,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[-8.449,134.754,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[6.779,137.991,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[21.542,122.972,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[26.897,117.617,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[39.032,116.968,0],"to":[0,0,0],"ti":[0,0,0]},{"t":135,"s":[29.453,136.35,0]}]},"a":{"a":0,"k":[-68.927,172.802,0]},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.8,0.8,0.8],"y":[0,0,0]},"t":119,"s":[75.24,77.22,100]},{"t":135,"s":[40,0,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,-0.574],[0,0],[-2.674,0],[0,0],[0,0.574],[0,0],[2.674,0],[0,0]],"o":[[0,0],[0,0.574],[0,0],[2.674,0],[0,0],[0,-0.574],[0,0],[-2.674,0]],"v":[[-97.67,-16.121],[-97.67,8.945],[-92.827,9.983],[88.486,9.983],[93.328,8.945],[93.328,-16.121],[88.486,-17.16],[-92.827,-17.16]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,-6.889],[0,0],[-4.342,0],[0,0],[0,6.889],[0,0],[3.305,0],[0,0]],"o":[[0,0],[0,6.889],[0,0],[4.342,0],[0,0],[0,-6.889],[0,0],[-3.305,0]],"v":[[-118.864,-179.27],[-157.218,149.147],[-149.356,161.622],[145.014,161.622],[152.876,149.147],[117.167,-179.27],[111.183,-191.745],[-112.88,-191.745]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[1.963,-5.723],[-24.413,-45.915],[-6.352,0],[0,0],[-1.043,4.439],[28.152,95.717],[6.786,0],[0,0]],"o":[[-15.069,42.918],[2.845,4.294],[0,0],[6.352,0],[3.572,-8.662],[-0.398,-3.986],[0,0],[-6.786,0]],"v":[[-246.266,-81.513],[-229.115,143.133],[-217.614,152.434],[213.039,152.434],[225.464,143.133],[240.183,-81.803],[227.357,-90.67],[-232.723,-90.67]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,-6.279],[0,0],[-5.763,0],[0,0],[0,6.279],[0,0],[5.763,0],[0,0]],"o":[[0,0],[0,6.279],[0,0],[5.763,0],[0,0],[0,-6.279],[0,0],[-5.763,0]],"v":[[-207.961,-133.355],[-207.961,141.064],[-197.526,152.434],[193.185,152.434],[203.62,141.064],[203.62,-133.355],[193.185,-144.724],[-197.526,-144.724]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,-5.782],[-15.824,-64.567],[-6.033,0],[0,0],[-2.69,15.514],[0,0],[6.033,0],[0,0]],"o":[[0,0],[2.878,11.744],[0,0],[6.033,0],[11.308,-65.214],[0,-5.782],[0,0],[-6.033,0]],"v":[[-217.599,-110.72],[-217.599,141.965],[-206.675,152.434],[202.333,152.434],[213.257,141.965],[213.257,-110.72],[202.333,-121.189],[-206.675,-121.189]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0.039,-5.404],[0,0],[-5.766,0.064],[0,0],[0.111,5.403],[0,0],[5.766,0.097],[0,0]],"o":[[0,0],[0.111,5.403],[0,0],[5.766,-0.064],[0,0],[0.039,-5.404],[0,0],[-5.766,-0.097]],"v":[[-259.469,-99.408],[-266.553,181.033],[-255.911,190.7],[150.831,166.284],[161.071,156.384],[147.002,-114.108],[136.633,-124.069],[-248.957,-109.018]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[-0.108,-5.913],[0,0],[-5.434,0.099],[0,0],[0.108,5.913],[0,0],[5.434,-0.1],[0,0]],"o":[[0,0],[0.108,5.913],[0,0],[5.434,-0.099],[0,0],[-0.108,-5.913],[0,0],[-5.434,0.1]],"v":[[-187.375,-127.145],[-202.449,145.409],[-192.414,155.935],[184.607,149.031],[194.25,138.144],[194.527,-124.641],[184.493,-135.168],[-177.732,-138.032]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[1.973,-6.216],[0,0],[-4.356,-0.607],[0,0],[-1.311,6.357],[0,0],[4.196,1.216],[0,0]],"o":[[0,0],[-1.311,6.358],[0,0],[4.355,0.607],[0,0],[1.973,-6.216],[0,0],[-4.196,-1.216]],"v":[[-195.688,-162.839],[-223.311,84.61],[-219.074,99.547],[41.055,186.638],[67.273,166.302],[91.57,-98.49],[84.953,-116.37],[-184.518,-171.892]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[2.085,-5.942],[0,0],[-1.729,-0.522],[0,0],[-1.832,6.068],[0,0],[1.539,1.105],[0,0]],"o":[[0,0],[-1.832,6.068],[0,0],[1.729,0.522],[0,0],[2.085,-5.942],[0,0],[-1.539,-1.105]],"v":[[-82.96,-165.589],[-152.761,68.899],[-153.955,83.065],[-115.583,172.938],[-72.58,135.108],[-20.887,-103.777],[-26.455,-128.92],[-76.398,-174.348]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-0.324,-6.072],[0,0],[0.714,-0.592],[0,0],[0.215,6.21],[0,0],[-0.688,1.188],[0,0]],"o":[[0,0],[0.215,6.21],[0,0],[-0.714,0.592],[0,0],[-0.324,-6.072],[0,0],[0.688,-1.188]],"v":[[-60.568,-146.342],[-56.338,86.988],[-57.033,101.579],[-99.7,186.652],[-104,166.787],[-107.985,-91.869],[-106.9,-109.335],[-62.4,-155.185]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[-1.846,-6.216],[0,0],[4.076,-0.607],[0,0],[1.227,6.357],[0,0],[-3.927,1.216],[0,0]],"o":[[0,0],[1.227,6.358],[0,0],[-4.076,0.607],[0,0],[-1.846,-6.216],[0,0],[3.927,-1.216]],"v":[[49.858,-170.098],[75.693,84.609],[71.727,99.547],[-171.712,186.638],[-196.248,166.302],[-224.93,-113.39],[-218.738,-131.27],[39.404,-179.151]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-1.301,-5.531],[0,0],[4.743,-0.808],[0,0],[0.845,5.603],[0,0],[-5.087,0.934],[0,0]],"o":[[0,0],[1.095,5.569],[0,0],[-4.78,0.576],[0,0],[-1.301,-5.531],[0,0],[5.087,-0.934]],"v":[[90.467,-152.328],[87.781,94.97],[81.719,107.574],[-232.913,176.302],[-249.885,162.689],[-265.013,-91.92],[-256.966,-105.637],[78.901,-160.651]],"c":true}]},{"t":135,"s":[{"i":[[-0.666,-4.96],[0,0],[5.298,-0.975],[0,0],[0.526,4.975],[0,0],[-5.348,0.699],[0,0]],"o":[[0,0],[0.985,4.912],[0,0],[-5.367,0.551],[0,0],[-0.666,-4.96],[0,0],[5.348,-0.699]],"v":[[101.67,-133.1],[133.545,107.496],[125.735,118.156],[-249.714,171.507],[-260.383,163.498],[-278.804,-86.446],[-270.327,-96.692],[90.782,-140.815]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0.672,-0.155],[0,0],[7.192,1.512],[0,0],[-0.993,0]],"o":[[0.987,0],[0,0],[-6.635,1.567],[0,0],[-0.683,-0.154],[0,0]],"v":[[80.458,-16.895],[81.286,-16.487],[9.106,0.175],[-11.786,0.198],[-85.529,-16.479],[-84.709,-16.888]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0.83,-1.863],[0,0],[11.677,18.161],[0,0],[-1.227,0]],"o":[[1.22,0],[0,0],[-10.772,18.82],[0,0],[-0.844,-1.855],[0,0]],"v":[[101.263,-188.562],[102.286,-183.668],[17.46,-13.452],[-16.458,-13.176],[-103.86,-183.565],[-102.847,-188.481]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[0,0],[1.705,-1.389],[51.315,-29.646],[9.496,5.358],[0,0],[-2.519,0]],"o":[[2.505,0],[0,0],[-16.9,9.763],[-46.749,-26.377],[-1.733,-1.383],[0,0]],"v":[[206.987,-88.297],[209.089,-84.648],[24.497,64.59],[-25.124,64.796],[-214.203,-84.571],[-212.122,-88.236]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,0],[1.448,-1.698],[0,0],[15.498,16.552],[0,0],[-2.139,0]],"o":[[2.127,0],[0,0],[-14.297,17.153],[0,0],[-1.472,-1.691],[0,0]],"v":[[175.886,-141.823],[177.671,-137.363],[22.13,45.058],[-22.89,45.31],[-181.799,-137.269],[-180.032,-141.749]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[1.516,-1.564],[0,0],[16.224,15.241],[0,0],[-2.239,0]],"o":[[2.227,0],[0,0],[-14.967,15.794],[0,0],[-1.541,-1.557],[0,0]],"v":[[184.224,-118.518],[186.093,-114.411],[23.268,53.562],[-23.86,53.794],[-190.211,-114.324],[-188.361,-118.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[1.459,-1.437],[0,0],[13.303,9.319],[0,0],[-2.14,-0.036]],"o":[[2.128,0.035],[0,0],[-12.844,14.55],[0,0],[-1.462,-1.48],[0,0]],"v":[[119.307,-121.862],[121.065,-117.993],[-10.228,56.163],[-53.151,62.009],[-233.268,-102.338],[-231.473,-106.165]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[1.336,-1.624],[0,0],[13.31,16.718],[0,0],[-2.017,0.037]],"o":[[2.006,-0.037],[0,0],[-14.742,15.015],[0,0],[-1.417,-1.567],[0,0]],"v":[[168.232,-132.137],[169.992,-127.968],[17.341,42.956],[-24.994,39.767],[-162.775,-131.283],[-161.186,-135.532]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[1.588,-1.375],[0,0],[4.651,13.921],[0,0],[-1.557,-0.451]],"o":[[1.549,0.449],[0,0],[-13.435,9.177],[0,0],[-0.54,-1.985],[0,0]],"v":[[71.446,-117.149],[71.716,-108.441],[-100.192,24.495],[-131.111,17.953],[-179.52,-160.766],[-172.714,-165.255]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0.951,-1.329],[0,0],[-3.812,13.382],[0,0],[-0.571,-0.41]],"o":[[0.568,0.407],[0,0],[-8.512,5.175],[0,0],[0.168,-1.882],[0,0]],"v":[[-32.039,-129.49],[-27.761,-114.057],[-114.929,-10.29],[-119.797,-3.459],[-74.672,-164.279],[-72.713,-168.18]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[-0.26,-1.344],[0,0],[-0.699,13.598],[0,0],[0.255,-0.441]],"o":[[-0.254,0.438],[0,0],[2.018,8.965],[0,0],[0.089,-1.939],[0,0]],"v":[[-104.684,-110.097],[-104.729,-101.589],[-79.203,19.033],[-74.43,4.513],[-63.22,-144.317],[-64.336,-148.701]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[-1.486,-1.375],[0,0],[-5.562,9.689],[0,0],[1.458,-0.451]],"o":[[-1.45,0.449],[0,0],[6.927,9.005],[0,0],[0.506,-1.985],[0,0]],"v":[[-206.098,-132.049],[-206.351,-123.34],[-50.95,11.531],[-27.802,5.438],[34.727,-168.025],[28.358,-172.514]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[-1.63,-1.261],[0,0],[-10.533,10.704],[0,0],[1.888,-0.346]],"o":[[-1.878,0.344],[0,0],[13.862,9.59],[0,0],[0.949,-1.728],[0,0]],"v":[[-241.096,-105.886],[-241.919,-99.887],[-67.321,55.327],[-35.1,49.904],[68.456,-151.341],[64.076,-155.195]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[-1.524,-1.166],[0,0],[-10.283,9.93],[0,0],[1.985,-0.259]],"o":[[-1.974,0.258],[0,0],[12.505,12.047],[0,0],[1.186,-1.514],[0,0]],"v":[[-253.968,-96.499],[-255.151,-92.759],[-80.834,91.013],[-56.053,90.916],[76.98,-133.018],[74.865,-136.343]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.103,0.961,0.961,1,0.512,0.882,0.884,0.951,0.734,0.804,0.808,0.902]}},"s":{"a":0,"k":[-1.728,-126.964]},"e":{"a":0,"k":[-0.228,77.542]},"t":1,"nm":"Gradient Fill 11","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0],[7.205,1.629],[0,0],[0,0],[0,0]],"o":[[0,0],[-7.127,1.645],[0,0],[0,0],[0,0],[0,0]],"v":[[92.308,-16.758],[11.966,1.788],[-14.489,1.817],[-96.594,-16.751],[-91.375,-17.16],[88.055,-17.16]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,0],[11.698,19.573],[0,0],[0,0],[0,0]],"o":[[0,0],[-11.571,19.762],[0,0],[0,0],[0,0],[0,0]],"v":[[115.907,-186.926],[22.104,5.921],[-20.848,6.271],[-117.535,-186.833],[-111.085,-191.745],[110.651,-191.745]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[0,0],[53.248,-33.089],[22.104,12.396],[0,0],[0,0],[0,0]],"o":[[0,0],[-12.356,7.678],[-51.45,-28.854],[0,0],[0,0],[0,0]],"v":[[237.057,-87.077],[31.291,79.036],[-31.546,79.296],[-242.282,-87.008],[-229.037,-90.67],[226.264,-90.67]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,0],[0,0],[15.527,17.839],[0,0],[0,0],[0,0]],"o":[[0,0],[-15.358,18.012],[0,0],[0,0],[0,0],[0,0]],"v":[[201.422,-140.332],[28.293,62.715],[-28.716,63.034],[-205.644,-140.248],[-194.396,-144.724],[192.256,-144.724]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[16.254,16.426],[0,0],[0,0],[0,0]],"o":[[0,0],[-16.077,16.585],[0,0],[0,0],[0,0],[0,0]],"v":[[210.956,-117.145],[29.72,69.821],[-29.959,70.115],[-215.173,-117.067],[-203.399,-121.189],[201.362,-121.189]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[12.525,10.619],[0,0],[0,0],[0,0]],"o":[[0,0],[-14.391,18.802],[0,0],[0,0],[0,0],[0,0]],"v":[[144.847,-120.15],[-3.809,68.511],[-59.125,75.983],[-257.107,-105.302],[-245.826,-108.966],[135.704,-124.084]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[13.716,16.772],[0,0],[0,0],[0,0]],"o":[[0,0],[-12.29,13.848],[0,0],[0,0],[0,0],[0,0]],"v":[[192.334,-131.174],[21.793,60.001],[-31.817,55.964],[-185.309,-133.676],[-174.781,-138.086],[183.617,-135.152]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[2.883,12.238],[0,0],[0,0],[0,0]],"o":[[0,0],[-12.283,7.983],[0,0],[0,0],[0,0],[0,0]],"v":[[92.162,-105.862],[-105.317,45.175],[-134.771,33.479],[-191.835,-169.174],[-182.239,-171.232],[84.277,-116.566]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[-3.744,9.151],[0,0],[0,0],[0,0]],"o":[[0,0],[-2.838,1.025],[0,0],[0,0],[0,0],[0,0]],"v":[[-19.157,-110.802],[-127.311,5.302],[-129.087,6.167],[-80.052,-171.668],[-75.562,-173.748],[-26.703,-129.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[-0.558,17.364],[0,0],[0,0],[0,0]],"o":[[0,0],[1.845,7.798],[0,0],[0,0],[0,0],[0,0]],"v":[[-108.082,-99.07],[-79.556,33.814],[-73.88,19.679],[-61.2,-152.53],[-62.774,-154.54],[-106.789,-109.527]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-5.675,12.725],[0,0],[0,0],[0,0]],"o":[[0,0],[6.408,7.928],[0,0],[0,0],[0,0],[0,0]],"v":[[-225.485,-120.761],[-55.258,22.492],[-28.326,17.647],[46.252,-176.433],[37.271,-178.491],[-218.106,-131.466]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-9.929,12.651],[0,0],[0,0],[0,0]],"o":[[0,0],[14.916,11.065],[0,0],[0,0],[0,0],[0,0]],"v":[[-264.519,-98.423],[-72.479,67.315],[-30.059,61.762],[86.994,-158.024],[76.139,-160.144],[-256.146,-105.787]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[-9.48,11.045],[0,0],[0,0],[0,0]],"o":[[0,0],[14.285,15.795],[0,0],[0,0],[0,0],[0,0]],"v":[[-277.504,-92.224],[-84.936,103.018],[-49.448,103.14],[98.789,-138.263],[87.878,-140.435],[-269.465,-96.805]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.51372551918,0.533333361149,0.631372570992,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.804,0.808,0.902,0.647,0.822,0.824,0.892,1,0.839,0.839,0.882]}},"s":{"a":0,"k":[-0.637,94.833]},"e":{"a":0,"k":[4.624,-135.86]},"t":1,"nm":"Gradient Fill 122","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0],[6.575,-1.454],[0,0],[0,0],[0,0]],"o":[[0,0],[-6.575,-1.454],[0,0],[0,0],[0,0],[0,0]],"v":[[90.176,9.917],[9.844,-7.807],[-14.186,-7.807],[-94.52,9.917],[-87.36,9.917],[82.68,9.917]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,0],[10.675,-17.468],[0,0],[0,0],[0,0]],"o":[[0,0],[-10.675,-17.468],[0,0],[0,0],[0,0],[0,0]],"v":[[147.759,160.825],[18.659,-79.403],[-20.355,-79.403],[-152.103,160.825],[-140.479,160.825],[135.589,160.825]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[0,0],[0,0],[15.617,-13.024],[0,0],[0,0],[0,0]],"o":[[0,0],[-15.617,-13.024],[0,0],[0,0],[0,0],[0,0]],"v":[[217.054,151.84],[26.25,-6.904],[-30.825,-6.904],[-221.633,151.84],[-204.627,151.84],[199.25,151.84]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,0],[0,0],[14.168,-15.92],[0,0],[0,0],[0,0]],"o":[[0,0],[-14.168,-15.92],[0,0],[0,0],[0,0],[0,0]],"v":[[196.828,151.707],[23.72,-42.334],[-28.062,-42.334],[-201.173,151.707],[-185.744,151.707],[180.675,151.707]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[14.832,-14.66],[0,0],[0,0],[0,0]],"o":[[0,0],[-14.832,-14.66],[0,0],[0,0],[0,0],[0,0]],"v":[[206.147,151.765],[24.933,-26.908],[-29.274,-26.908],[-210.493,151.765],[-194.341,151.765],[189.237,151.765]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[12.721,-13.491],[0,0],[0,0],[0,0]],"o":[[0,0],[-12.522,-13.914],[0,0],[0,0],[0,0],[0,0]],"v":[[154.463,165.618],[-4.458,-9.308],[-70.263,-16.314],[-259.572,190.116],[-244.135,189.944],[138.301,165.798]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[14.527,-13.868],[0,0],[0,0],[0,0]],"o":[[0,0],[-12.108,-16.024],[0,0],[0,0],[0,0],[0,0]],"v":[[188.029,148.284],[23.714,-31.203],[-24.958,-35.142],[-195.865,155.314],[-181.318,155.048],[172.8,148.563]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[10.685,-14.185],[0,0],[0,0],[0,0]],"o":[[0,0],[-4.549,-17.866],[0,0],[0,0],[0,0],[0,0]],"v":[[15.971,148.167],[-71.329,-19.355],[-120.93,-12.746],[-210.475,63.801],[-220.482,91.895],[31.752,184.585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[6.103,-14.242],[0,0],[0,0],[0,0]],"o":[[0,0],[3.317,-16.415],[0,0],[0,0],[0,0],[0,0]],"v":[[-126.56,110.901],[-113.779,-25.379],[-100.17,-48.121],[-140.273,35.505],[-150.069,63.899],[-119.125,171.103]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[-1.605,-13.857],[0,0],[0,0],[0,0]],"o":[[0,0],[0.683,-17.452],[0,0],[0,0],[0,0],[0,0]],"v":[[-97.864,167.966],[-83.498,-5.512],[-77.077,-17.946],[-58.443,66.661],[-56.802,94.104],[-98.174,184.647]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-4.234,-12.32],[0,0],[0,0],[0,0]],"o":[[0,0],[6.227,-12.66],[0,0],[0,0],[0,0],[0,0]],"v":[[-161.239,167.509],[-73.989,-19.315],[-34.861,-14.579],[65.166,96.207],[73.045,91.895],[-163.006,184.585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-6.939,-11.941],[0,0],[0,0],[0,0]],"o":[[0,0],[7.257,-13.144],[0,0],[0,0],[0,0],[0,0]],"v":[[-230.036,167.482],[-79.396,-22.585],[-34.934,-18.403],[80.147,102.26],[76.348,104.873],[-222.634,174.402]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[-12.285,-11.087],[0,0],[0,0],[0,0]],"o":[[0,0],[9.117,-14.072],[0,0],[0,0],[0,0],[0,0]],"v":[[-253.167,171.28],[-91.617,-24.919],[-41.916,-24.571],[128.974,116.971],[114.789,119.582],[-238.125,169.735]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.662,0.662,0.759,0.197,0.759,0.764,0.852,0.53,0.855,0.867,0.945]}},"s":{"a":0,"k":[-0.045,127.032]},"e":{"a":0,"k":[-4.766,-98.874]},"t":1,"nm":"Gradient Fill 123","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0],[7.127,-1.576],[0,0],[0,0],[0,0]],"o":[[0,0],[-7.127,-1.576],[0,0],[0,0],[0,0],[0,0]],"v":[[93.328,8.945],[10.854,-9.296],[-15.195,-9.296],[-97.67,8.945],[-94.52,9.983],[89.713,9.983]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,0],[11.572,-18.935],[0,0],[0,0],[0,0]],"o":[[0,0],[-11.572,-18.935],[0,0],[0,0],[0,0],[0,0]],"v":[[152.876,149.147],[20.298,-97.292],[-21.994,-97.292],[-157.218,149.147],[-152.103,161.622],[147.006,161.622]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[0,0],[44.479,16.391],[13.327,-6.645],[0,0],[0,0],[0,0]],"o":[[0,0],[-9.305,-3.429],[-56.103,27.972],[0,0],[0,0],[0,0]],"v":[[224.541,143.132],[28.648,-20.243],[-33.223,-20.243],[-229.115,143.132],[-221.633,152.434],[215.953,152.434]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,0],[0,0],[15.359,-17.258],[0,0],[0,0],[0,0]],"o":[[0,0],[-15.359,-17.258],[0,0],[0,0],[0,0],[0,0]],"v":[[203.62,141.064],[25.896,-58.638],[-30.237,-58.638],[-207.961,141.064],[-201.173,152.434],[195.829,152.434]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[16.078,-15.891],[0,0],[0,0],[0,0]],"o":[[0,0],[-16.078,-15.891],[0,0],[0,0],[0,0],[0,0]],"v":[[213.257,141.965],[27.21,-41.921],[-31.552,-41.921],[-217.599,141.965],[-210.493,152.434],[205.101,152.434]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[13.79,-14.625],[0,0],[0,0],[0,0]],"o":[[0,0],[-13.574,-15.083],[0,0],[0,0],[0,0],[0,0]],"v":[[161.07,156.384],[-2.418,-23.309],[-72.099,-30.379],[-266.553,181.033],[-259.56,190.741],[153.477,166.255]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[15.748,-15.034],[0,0],[0,0],[0,0]],"o":[[0,0],[-13.125,-17.37],[0,0],[0,0],[0,0],[0,0]],"v":[[194.25,138.144],[26.998,-46.344],[-25.764,-50.614],[-202.449,145.409],[-195.853,155.998],[187.1,148.985]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[12.796,-13.352],[0,0],[0,0],[0,0]],"o":[[0,0],[-3.25,-20.563],[0,0],[0,0],[0,0],[0,0]],"v":[[32.513,150.046],[-65.244,-30.947],[-119.316,-32.783],[-220.698,61.221],[-221.336,91.637],[43.053,186.916]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[6.333,-13.61],[0,0],[0,0],[0,0]],"o":[[0,0],[4.3,-19.061],[0,0],[0,0],[0,0],[0,0]],"v":[[-121.711,118.026],[-104.496,-21.574],[-96.197,-67.139],[-145.62,36.985],[-150.381,63.665],[-114.79,173.178]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[-1.922,-13.042],[0,0],[0,0],[0,0]],"o":[[0,0],[0.488,-20.087],[0,0],[0,0],[0,0],[0,0]],"v":[[-100.577,169.802],[-84.412,-16.835],[-77.32,-37.519],[-56.766,64.141],[-56.661,93.852],[-100.027,186.924]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-5.744,-12.03],[0,0],[0,0],[0,0]],"o":[[0,0],[5.892,-14.886],[0,0],[0,0],[0,0],[0,0]],"v":[[-176.719,169.388],[-75.75,-28.962],[-32.189,-29.842],[74.734,93.628],[73.845,91.637],[-173.582,186.916]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-7.997,-12.361],[0,0],[0,0],[0,0]],"o":[[0,0],[7.553,-14.791],[0,0],[0,0],[0,0],[0,0]],"v":[[-241.008,164.092],[-81.154,-33.954],[-33.304,-32.438],[86.989,95.92],[84.51,103.642],[-235.106,176.566]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[-13.317,-12.019],[0,0],[0,0],[0,0]],"o":[[0,0],[9.883,-15.254],[0,0],[0,0],[0,0],[0,0]],"v":[[-260.382,163.498],[-94.883,-37.573],[-41.895,-37.683],[133.545,107.496],[129.088,117.539],[-252.176,171.76]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.733,0.722,0.796,0.459,0.81,0.804,0.876,1,0.886,0.886,0.957]}},"s":{"a":0,"k":[-0.659,92.569]},"e":{"a":0,"k":[-2.024,18.053]},"t":1,"nm":"Gradient Fill 1234","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0],[0.67,-0.177],[0,0],[0,0],[0,0],[-0.642,0.178],[0,0]],"o":[[0,0],[0.662,0.179],[0,0],[0,0],[0,0],[-0.655,-0.176],[0,0],[0,0]],"v":[[-95.876,-15.991],[-50.33,-4.226],[-50.344,-3.616],[-95.617,8.491],[91.275,8.491],[46.89,-3.536],[46.868,-4.138],[91.599,-15.991]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,0],[0,0],[1.088,-2.131],[0,0],[0,0],[0,0],[-1.042,2.136],[0,0]],"o":[[0,0],[1.075,2.147],[0,0],[0,0],[0,0],[-1.063,-2.109],[0,0],[0,0]],"v":[[-116.647,-177.703],[-70.441,-52.001],[-70.463,-44.68],[-153.885,143.693],[149.543,143.693],[70.207,-39.164],[70.171,-46.399],[115.03,-177.703]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[0,0],[0,0],[1.591,-1.589],[0,0],[0,0],[0,0],[-1.524,1.592],[0,0]],"o":[[0,0],[1.572,1.601],[0,0],[0,0],[0,0],[-1.555,-1.573],[0,0],[0,0]],"v":[[-240.46,-80.2],[-116.675,25.172],[-118.722,30.631],[-224.24,139.066],[219.665,139.066],[117.597,31.348],[114.188,25.953],[235.256,-80.2]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,0],[0,0],[1.444,-1.942],[0,0],[0,0],[0,0],[-1.383,1.947],[0,0]],"o":[[0,0],[1.426,1.957],[0,0],[0,0],[0,0],[-1.411,-1.923],[0,0],[0,0]],"v":[[-204.096,-131.926],[-105.95,-3.124],[-105.979,3.548],[-203.538,136.094],[199.196,136.094],[103.55,4.424],[103.502,-2.17],[199.893,-131.926]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[1.511,-1.788],[0,0],[0,0],[0,0],[-1.448,1.792],[0,0]],"o":[[0,0],[1.493,1.802],[0,0],[0,0],[0,0],[-1.477,-1.77],[0,0],[0,0]],"v":[[-213.553,-109.404],[-110.81,9.196],[-110.841,15.34],[-212.968,137.388],[208.626,137.388],[108.501,16.147],[108.451,10.075],[209.356,-109.404]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[1.298,-1.65],[0,0],[0,0],[0,0],[-1.244,1.655],[0,0]],"o":[[0,0],[1.258,1.706],[0,0],[0,0],[0,0],[-1.245,-1.676],[0,0],[0,0]],"v":[[-255.611,-98.113],[-146.627,22.486],[-146.695,28.228],[-262.215,176.706],[156.556,152.156],[66.675,23.251],[66.673,17.575],[143.265,-112.941]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[1.328,-1.854],[0,0],[0,0],[0,0],[-1.448,1.722],[0,0]],"o":[[0,0],[1.379,1.818],[0,0],[0,0],[0,0],[-1.18,-1.912],[0,0],[0,0]],"v":[[-183.706,-125.866],[-99.761,0.671],[-99.673,6.955],[-198.364,140.651],[189.993,133.54],[95.238,8.89],[95.694,2.696],[191.038,-123.231]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[1.119,-1.843],[0,0],[0,0],[0,0],[-1.118,1.78],[0,0]],"o":[[0,0],[0.447,2.102],[0,0],[0,0],[0,0],[-0.388,-2.118],[0,0],[0,0]],"v":[[-193.323,-160.609],[-156.841,-14.112],[-157.991,-7.359],[-217.953,56.605],[39.014,151.464],[-21.782,12.556],[-20.537,5.913],[88.407,-97.862]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0.728,-1.828],[0,0],[0,0],[0,0],[-0.712,1.768],[0,0]],"o":[[0,0],[-0.412,1.939],[0,0],[0,0],[0,0],[0.43,-1.959],[0,0],[0,0]],"v":[[-82.402,-163.496],[-110.087,-43.295],[-112.037,-36.847],[-134.733,37.93],[-124.061,110.656],[-103.021,-3.324],[-101.075,-9.676],[-22.357,-103.139]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[-0.168,-1.801],[0,0],[0,0],[0,0],[0.168,1.739],[0,0]],"o":[[0,0],[-0.067,2.054],[0,0],[0,0],[0,0],[0.058,-2.069],[0,0],[0,0]],"v":[[-60.956,-144.163],[-71.581,-16.059],[-71.408,-9.462],[-57.216,59.632],[-101.519,171.187],[-90.095,22.039],[-90.282,15.549],[-107.467,-91.256]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-0.377,-1.558],[0,0],[0,0],[0,0],[0.389,1.511],[0,0]],"o":[[0,0],[-0.676,1.504],[0,0],[0,0],[0,0],[0.641,-1.525],[0,0],[0,0]],"v":[[47.644,-167.868],[-11.729,-7.327],[-12.24,-2.087],[72.165,89.012],[-182.091,170.806],[-98.065,2.336],[-97.635,-2.838],[-221.971,-112.762]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-0.689,-1.492],[0,0],[0,0],[0,0],[0.673,1.475],[0,0]],"o":[[0,0],[-0.744,1.58],[0,0],[0,0],[0,0],[0.726,-1.575],[0,0],[0,0]],"v":[[87.352,-150.443],[-2.81,4.648],[-2.903,9.905],[83.178,92.108],[-241.43,162.357],[-115.083,15.615],[-114.993,10.418],[-261.428,-91.266]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[-1.284,-1.382],[0,0],[0,0],[0,0],[1.238,1.392],[0,0]],"o":[[0,0],[-0.883,1.697],[0,0],[0,0],[0,0],[0.874,-1.668],[0,0],[0,0]],"v":[[98.236,-131.502],[9.213,9.203],[9.899,14.472],[128.698,104.356],[-256.679,159.136],[-156.93,29.654],[-157.55,24.439],[-275.194,-85.769]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.324,0.024,0,0.435,0.589,0.022,0,0.425,0.854,0.02,0,0.416,0.324,1,0.589,0.65,0.854,0.3]}},"s":{"a":0,"k":[0.105,-45.053]},"e":{"a":0,"k":[-1.728,23.793]},"t":1,"nm":"Gradient Fill 12345","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":15},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,-0.574],[0,0],[-2.5,0],[0,0],[0,0.574],[0,0],[2.5,0],[0,0]],"o":[[0,0],[0,0.574],[0,0],[2.5,0],[0,0],[0,-0.574],[0,0],[-2.5,0]],"v":[[-91.458,-16.121],[-91.458,8.945],[-86.931,9.983],[82.589,9.983],[87.116,8.945],[87.116,-16.121],[82.589,-17.16],[-86.931,-17.16]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,-6.889],[0,0],[-4.06,0],[0,0],[0,6.889],[0,0],[3.09,0],[0,0]],"o":[[0,0],[0,6.889],[0,0],[4.06,0],[0,0],[0,-6.889],[0,0],[-3.09,0]],"v":[[-111.188,-179.27],[-147.133,149.147],[-139.782,161.622],[135.441,161.622],[142.791,149.147],[109.491,-179.27],[103.896,-191.745],[-105.593,-191.745]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[5.551,-8.898],[-22.945,-45.307],[-5.939,0],[0,0],[-0.956,4.826],[15.364,36.403],[6.345,0],[0,0]],"o":[[-22.763,30.902],[5.209,3.571],[0,0],[5.939,0],[20.655,-55.036],[-3.64,-4.71],[0,0],[-6.345,0]],"v":[[-229.249,-81.369],[-214.362,143.133],[-203.608,152.434],[199.033,152.434],[209.787,143.133],[223.883,-81.369],[212.394,-90.67],[-217.76,-90.67]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,-6.279],[0,0],[-5.388,0],[0,0],[0,6.279],[0,0],[5.388,0],[0,0]],"o":[[0,0],[0,6.279],[0,0],[5.388,0],[0,0],[0,-6.279],[0,0],[-5.388,0]],"v":[[-194.576,-133.355],[-194.576,141.064],[-184.82,152.434],[180.478,152.434],[190.234,141.064],[190.234,-133.355],[180.478,-144.724],[-184.82,-144.724]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,-5.782],[-7.22,-72.976],[-5.64,0],[0,0],[-1.287,4.324],[0,0],[5.641,0],[0,0]],"o":[[0,0],[2.758,5.941],[0,0],[5.641,0],[11.352,-64.567],[0,-5.782],[0,0],[-5.64,0]],"v":[[-203.586,-110.72],[-203.586,141.965],[-193.373,152.434],[189.032,152.434],[199.245,141.965],[199.245,-110.72],[189.032,-121.189],[-193.373,-121.189]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0.039,-5.404],[0,0],[-5.391,0.06],[0,0],[0.112,5.403],[0,0],[5.391,0.09],[0,0]],"o":[[0,0],[0.111,5.403],[0,0],[5.391,-0.06],[0,0],[0.039,-5.404],[0,0],[-5.391,-0.09]],"v":[[-246.077,-99.183],[-253.16,180.884],[-243.197,190.559],[138.117,166.426],[147.677,156.533],[133.61,-114.332],[123.92,-124.281],[-236.244,-108.805]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[-0.108,-5.913],[0,0],[-5.08,0.093],[0,0],[0.108,5.913],[0,0],[5.08,-0.093],[0,0]],"o":[[0,0],[0.108,5.913],[0,0],[5.08,-0.093],[0,0],[-0.108,-5.913],[0,0],[-5.08,0.093]],"v":[[-174.754,-127.376],[-189.829,145.178],[-180.434,155.716],[172.627,149.25],[181.629,138.375],[181.907,-124.41],[172.512,-134.948],[-165.751,-138.251]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[1.973,-6.216],[0,0],[-4.072,-0.567],[0,0],[-1.311,6.357],[0,0],[3.923,1.137],[0,0]],"o":[[0,0],[22.279,11.61],[0,0],[4.072,0.567],[0,0],[1.973,-6.216],[0,0],[-3.923,-1.137]],"v":[[-185.942,-160.014],[-208.362,54.113],[-227.179,54.678],[28.271,165.571],[56.359,152.3],[75.388,-91.728],[75.701,-119.052],[-175.266,-169.21]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[2.085,-5.942],[0,0],[-1.617,-0.488],[0,0],[-1.832,6.068],[0,0],[1.439,1.033],[0,0]],"o":[[0,0],[7.14,10.769],[0,0],[1.617,0.488],[0,0],[2.085,-5.942],[0,0],[-1.439,-1.033]],"v":[[-79.385,-163.023],[-140.294,39.68],[-140.696,45.385],[-127.445,116.538],[-74.795,98.629],[-40.743,-79.496],[-29.849,-131.355],[-73.004,-171.913]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-0.324,-6.072],[0,0],[0.668,-0.554],[0,0],[0.215,6.21],[0,0],[-0.644,1.111],[0,0]],"o":[[0,0],[-3.654,11.341],[0,0],[-0.668,0.554],[0,0],[-0.324,-6.072],[0,0],[0.644,-1.111]],"v":[[-62.166,-143.582],[-58.789,57.198],[-55.703,57.75],[-97.603,166.073],[-102.21,153.109],[-105.331,-85.264],[-105.382,-111.955],[-63.918,-152.566]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[-1.846,-6.216],[0,0],[3.811,-0.567],[0,0],[1.227,6.357],[0,0],[-3.672,1.137],[0,0]],"o":[[0,0],[-20.85,11.61],[0,0],[-3.811,0.567],[0,0],[-1.846,-6.216],[0,0],[3.672,-1.137]],"v":[[40.737,-167.273],[63.189,86.52],[80.799,87.085],[-159.748,165.571],[-186.034,152.3],[-209.787,-106.628],[-210.08,-133.952],[30.745,-176.469]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-1.301,-5.531],[0,0],[4.434,-0.755],[0,0],[0.844,5.603],[0,0],[-4.756,0.873],[0,0]],"o":[[0,0],[-8.94,7.957],[0,0],[-4.469,0.539],[0,0],[-1.301,-5.531],[0,0],[4.756,-0.873]],"v":[[78.652,-150.158],[75.029,93.924],[79.114,99.933],[-221.02,166.063],[-238.443,155.626],[-250.237,-89.732],[-245.75,-107.697],[67.686,-158.592]],"c":true}]},{"t":135,"s":[{"i":[[-0.666,-4.96],[0,0],[4.954,-0.912],[0,0],[0.526,4.975],[0,0],[-5,0.654],[0,0]],"o":[[0,0],[0.985,4.912],[0,0],[-5.018,0.515],[0,0],[-0.666,-4.96],[0,0],[5,-0.654]],"v":[[89.25,-131.476],[121.239,109.761],[114.053,120.307],[-237.881,170.292],[-247.918,162.217],[-266.383,-88.069],[-258.536,-98.234],[78.992,-139.274]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.208,0.945,0.945,0.98,0.522,0.824,0.824,0.882,0.836,0.702,0.702,0.784]}},"s":{"a":0,"k":[-1.728,-119.964]},"e":{"a":0,"k":[0.359,102.165]},"t":1,"nm":"Gradient Fill 123456","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 8","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,-0.574],[0,0],[-2.674,0],[0,0],[0,0.574],[0,0],[2.674,0],[0,0]],"o":[[0,0],[0,0.574],[0,0],[2.674,0],[0,0],[0,-0.574],[0,0],[-2.674,0]],"v":[[-97.67,-16.121],[-97.67,8.945],[-92.827,9.983],[88.486,9.983],[93.328,8.945],[93.328,-16.121],[88.486,-17.16],[-92.827,-17.16]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.167,"y":0.167},"t":28,"s":[{"i":[[0,-6.889],[0,0],[-4.342,0],[0,0],[0,6.889],[0,0],[3.305,0],[0,0]],"o":[[0,0],[0,6.889],[0,0],[4.342,0],[0,0],[0,-6.889],[0,0],[-3.305,0]],"v":[[-118.864,-179.27],[-157.218,149.147],[-149.356,161.622],[145.014,161.622],[152.876,149.147],[117.167,-179.27],[111.183,-191.745],[-112.88,-191.745]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":34,"s":[{"i":[[2.34,-3.035],[-25.089,-48.347],[-6.352,0],[0,0],[-3.32,6.418],[10.36,28.296],[6.786,0],[0,0]],"o":[[-12.739,17.873],[3.516,3.281],[0,0],[6.352,0],[18.744,-22.809],[-1.653,-5.289],[0,0],[-6.786,0]],"v":[[-245.011,-81.369],[-229.115,143.133],[-217.614,152.434],[213.039,152.434],[224.541,143.133],[239.645,-81.369],[227.357,-90.67],[-232.723,-90.67]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":42,"s":[{"i":[[0,-6.279],[0,0],[-5.763,0],[0,0],[0,6.279],[0,0],[5.763,0],[0,0]],"o":[[0,0],[0,6.279],[0,0],[5.763,0],[0,0],[0,-6.279],[0,0],[-5.763,0]],"v":[[-207.961,-133.355],[-207.961,141.064],[-197.526,152.434],[193.185,152.434],[203.62,141.064],[203.62,-133.355],[193.185,-144.724],[-197.526,-144.724]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,-5.782],[-14.494,-74.594],[-6.033,0],[0,0],[-2.328,10.469],[0,0],[6.033,0],[0,0]],"o":[[0,0],[0.307,7.72],[0,0],[6.033,0],[12.639,-51.63],[0,-5.782],[0,0],[-6.033,0]],"v":[[-217.599,-110.72],[-217.599,141.965],[-206.675,152.434],[202.333,152.434],[213.257,141.965],[213.257,-110.72],[202.333,-121.189],[-206.675,-121.189]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0.039,-5.404],[0,0],[-5.766,0.064],[0,0],[0.111,5.403],[0,0],[5.766,0.097],[0,0]],"o":[[0,0],[0.111,5.403],[0,0],[5.766,-0.064],[0,0],[0.039,-5.404],[0,0],[-5.766,-0.097]],"v":[[-259.469,-99.408],[-266.553,181.033],[-255.911,190.7],[150.831,166.284],[161.071,156.384],[147.002,-114.108],[136.633,-124.069],[-248.957,-109.018]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[-0.108,-5.913],[0,0],[-5.434,0.099],[0,0],[0.108,5.913],[0,0],[5.434,-0.1],[0,0]],"o":[[0,0],[0.108,5.913],[0,0],[5.434,-0.099],[0,0],[-0.108,-5.913],[0,0],[-5.434,0.1]],"v":[[-187.375,-127.145],[-202.449,145.409],[-192.414,155.935],[184.607,149.031],[194.25,138.144],[194.527,-124.641],[184.493,-135.168],[-177.732,-138.032]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[1.973,-6.216],[0,0],[-4.356,-0.607],[0,0],[-1.311,6.357],[0,0],[4.196,1.216],[0,0]],"o":[[0,0],[-1.311,6.357],[0,0],[4.355,0.607],[0,0],[1.973,-6.216],[0,0],[-4.196,-1.216]],"v":[[-195.688,-162.839],[-224.586,86.938],[-219.074,99.547],[41.055,186.638],[65.895,166.01],[91.57,-98.49],[84.953,-116.37],[-184.518,-171.892]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[2.085,-5.942],[0,0],[-1.729,-0.522],[0,0],[-1.832,6.068],[0,0],[1.539,1.105],[0,0]],"o":[[0,0],[-1.832,6.068],[0,0],[1.729,0.522],[0,0],[2.085,-5.942],[0,0],[-1.539,-1.105]],"v":[[-82.96,-165.589],[-153.769,71.132],[-153.955,83.065],[-115.583,172.938],[-72.245,134.291],[-20.887,-103.777],[-26.455,-128.92],[-76.398,-174.348]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-0.324,-6.072],[0,0],[0.714,-0.592],[0,0],[0.215,6.21],[0,0],[-0.688,1.188],[0,0]],"o":[[0,0],[0.215,6.21],[0,0],[-0.714,0.592],[0,0],[-0.324,-6.072],[0,0],[0.688,-1.188]],"v":[[-60.568,-146.342],[-56.128,89.262],[-57.033,101.579],[-99.7,186.652],[-103.774,166.502],[-107.985,-91.869],[-106.9,-109.335],[-62.4,-155.185]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[-1.846,-6.216],[0,0],[4.076,-0.607],[0,0],[1.227,6.357],[0,0],[-3.927,1.216],[0,0]],"o":[[0,0],[1.227,6.357],[0,0],[-4.076,0.607],[0,0],[-1.846,-6.216],[0,0],[3.927,-1.216]],"v":[[49.858,-170.098],[76.886,86.938],[71.727,99.547],[-171.712,186.638],[-194.959,166.01],[-224.931,-113.39],[-218.738,-131.27],[39.404,-179.151]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-1.301,-5.531],[0,0],[4.743,-0.808],[0,0],[0.845,5.603],[0,0],[-5.087,0.934],[0,0]],"o":[[0,0],[1.095,5.569],[0,0],[-4.78,0.576],[0,0],[-1.301,-5.531],[0,0],[5.087,-0.934]],"v":[[90.467,-152.328],[88.324,96.028],[81.719,107.574],[-232.913,176.302],[-249.299,162.556],[-265.013,-91.92],[-256.966,-105.637],[78.901,-160.651]],"c":true}]},{"t":135,"s":[{"i":[[-0.666,-4.96],[0,0],[5.298,-0.975],[0,0],[0.526,4.975],[0,0],[-5.348,0.699],[0,0]],"o":[[0,0],[0.985,4.912],[0,0],[-5.367,0.551],[0,0],[-0.666,-4.96],[0,0],[5.348,-0.699]],"v":[[101.67,-133.1],[133.545,107.496],[125.735,118.156],[-249.714,171.507],[-260.383,163.498],[-278.804,-86.446],[-270.327,-96.692],[90.782,-140.815]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.89,0.89,0.922,0.615,0.798,0.798,0.851,1,0.706,0.706,0.78]}},"s":{"a":0,"k":[-1.728,-111.964]},"e":{"a":0,"k":[0.774,157.435]},"t":1,"nm":"Gradient Fill 1234567","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 9","bm":0,"hd":false}],"ip":20,"op":118,"st":-4,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"BACK","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"t":51,"s":[100],"h":1},{"t":93,"s":[0],"h":1},{"t":120,"s":[100],"h":1}]},"p":{"a":0,"k":[-2.9,137.51,0]},"a":{"a":0,"k":[-2.9,137.51,0]},"s":{"a":1,"k":[{"i":{"x":[0.7,0.7,0.7],"y":[1,1,1]},"o":{"x":[0.9,0.9,0.9],"y":[0,0,0]},"t":19,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.9,0.9,0.9],"y":[0,0,0]},"t":32,"s":[100,110,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.2,0.2,0.2],"y":[0,0,0]},"t":40,"s":[100,98,100]},{"i":{"x":[0.31,0.31,0.31],"y":[1.036,1.004,1.036]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":52,"s":[100,100,100]},{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":79,"s":[100,110,100]},{"i":{"x":[0.843,0.843,0.843],"y":[1,1,1]},"o":{"x":[1,1,1],"y":[0,0,0]},"t":101,"s":[100,100,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.393,0.393,0.393],"y":[0,0,0]},"t":117,"s":[100,100,100]},{"i":{"x":[0.864,0.864,0.864],"y":[1,1,1]},"o":{"x":[0.96,0.96,0.96],"y":[0,0,0]},"t":126,"s":[100,113,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.1,0.1,0.1],"y":[0,0,0]},"t":140,"s":[100,93,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":151,"s":[100,102,100]},{"t":175,"s":[100,100,100]}]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[-2.669,0],[0,0],[-0.519,-2.574],[0,0]],"o":[[0,0],[0.474,-2.627],[0,0],[2.626,0],[0,0],[0,0]],"v":[[-191.87,64.648],[-169.856,-39.684],[-164.428,-44.221],[156.912,-44.221],[162.319,-39.796],[186.887,64.567]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[-2.721,0],[0,0],[-0.53,-2.679],[0,0]],"o":[[0,0],[0.483,-2.734],[0,0],[2.677,0],[0,0],[0,0]],"v":[[-191.838,72.865],[-173.123,-42.868],[-167.588,-47.59],[160.048,-47.59],[165.561,-42.985],[186.919,72.78]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[-2.64,0],[0,0],[-0.514,-2.355],[0,0]],"o":[[0,0],[0.468,-2.403],[0,0],[2.598,0],[0,0],[0,0]],"v":[[-190.62,51.316],[-167.863,-29.227],[-162.493,-33.379],[155.375,-33.379],[160.723,-29.329],[186.387,52.134]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[-2.669,0],[0,0],[-0.519,-2.574],[0,0]],"o":[[0,0],[0.474,-2.627],[0,0],[2.626,0],[0,0],[0,0]],"v":[[-191.87,64.648],[-169.856,-39.684],[-164.428,-44.221],[156.912,-44.221],[162.319,-39.796],[186.887,64.567]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[-2.666,0.124],[0,0],[-0.638,-2.547],[0,0]],"o":[[0,0],[0.351,-2.646],[0,0],[2.623,-0.122],[0,0],[0,0]],"v":[[-153.437,78.045],[-199.187,-35.967],[-199.974,-39.252],[121.02,-54.148],[126.626,-49.978],[186.585,58.732]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[-2.409,0],[0,0],[-0.469,-2.599],[0,0]],"o":[[0,0],[0.427,-2.652],[0,0],[2.37,0],[0,0],[0,0]],"v":[[-173.063,74.284],[-153.606,-41.423],[-148.706,-46.005],[141.309,-46.005],[146.189,-41.536],[166.879,72.275]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[-2.409,0],[0,0],[-2.064,0.715],[0,0]],"o":[[0,0],[0.427,-2.652],[0,0],[2.37,0],[0,0],[0,0]],"v":[[-211.723,14.683],[13.144,-47.423],[19.794,-46.505],[219.996,9.433],[223.22,8.151],[5.882,85.029]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[0,0],[-2.409,0],[0,0],[-2.064,0.715],[0,0]],"o":[[0,0],[0.427,-2.652],[0,0],[2.37,0],[0,0],[0,0]],"v":[[-184.223,-6.067],[118.144,-28.673],[124.794,-27.755],[205.996,35.933],[209.22,34.651],[-138.638,65.601]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[-1.785,0.428],[0,0],[-1.698,0.067],[0,0]],"o":[[0,0],[-0.296,-2.63],[0,0],[1.756,-0.421],[0,0],[0,0]],"v":[[135.63,56.898],[-193.997,37.561],[-191.426,32.278],[-117.825,-25.28],[-112.302,-24.845],[182.665,-7.409]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-1.785,0.428],[0,0],[-1.698,0.067],[0,0]],"o":[[0,0],[-0.296,-2.63],[0,0],[1.756,-0.421],[0,0],[0,0]],"v":[[-19.87,86.648],[-201.997,4.561],[-199.426,-0.723],[-7.325,-43.78],[-1.802,-43.345],[207.262,15.933]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-2.158,0.248],[0,0],[-1.201,-1.047],[0,0]],"o":[[0,0],[0.028,-2.629],[0,0],[2.123,-0.244],[0,0],[0,0]],"v":[[-94.224,78.159],[-176.29,-14.752],[-172.513,-19.721],[65.358,-46.81],[70.832,-44.691],[192.901,39.495]],"c":false}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[-2.406,0.127],[0,0],[-0.869,-1.79],[0,0]],"o":[[0,0],[0.245,-2.628],[0,0],[2.367,-0.125],[0,0],[0,0]],"v":[[-134.515,71.326],[-174.152,-26.794],[-169.572,-31.553],[110.98,-46.246],[116.422,-43.006],[192.058,52.756]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0],[-2.567,0.037],[0,0],[-0.614,-2.347],[0,0]],"o":[[0,0],[0.403,-2.627],[0,0],[2.525,-0.036],[0,0],[0,0]],"v":[[-171.665,65.382],[-167.221,-35.95],[-162.091,-40.552],[144.293,-44.808],[149.655,-40.726],[188.965,54.064]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0],[-2.669,0],[0,0],[-0.519,-2.574],[0,0]],"o":[[0,0],[0.474,-2.627],[0,0],[2.626,0],[0,0],[0,0]],"v":[[-191.87,64.648],[-169.856,-39.684],[-164.428,-44.221],[156.912,-44.221],[162.319,-39.796],[186.887,64.567]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.019607843831,0.388235300779,0.639215707779,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[0,0],[-0.45,-2.25],[0,0]],"o":[[0,0],[0,0],[2.19,0.4],[0,0],[2.363,12.283]],"v":[[163.681,72.34],[157.903,46.656],[157.903,-44.133],[162.323,-39.794],[186.887,64.567]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[0,0],[-0.459,-2.342],[0,0]],"o":[[0,0],[0,0],[2.233,0.416],[0,0],[2.363,12.784]],"v":[[163.712,80.871],[157.721,29.377],[161.058,-47.499],[165.565,-42.982],[186.919,72.78]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[0,0],[-0.445,-2.059],[0,0]],"o":[[0,0],[0,0],[2.166,0.366],[0,0],[2.363,11.239]],"v":[[163.681,66.645],[157.805,55.388],[156.355,-33.298],[160.727,-29.327],[185.262,53.41]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[-0.45,-2.25],[0,0]],"o":[[0,0],[0,0],[2.19,0.4],[0,0],[2.363,12.283]],"v":[[163.681,72.34],[157.903,46.656],[157.903,-44.133],[162.323,-39.794],[186.887,64.567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[-0.554,-2.227],[0,0]],"o":[[0,0],[0,0],[2.206,0.298],[0,0],[7.41,27.558]],"v":[[144.577,71.12],[124,34.906],[122.013,-54.106],[126.63,-49.976],[186.085,59.482]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[-0.406,-2.272],[0,0]],"o":[[0,0],[0,0],[1.977,0.404],[0,0],[2.133,12.404]],"v":[[145.935,80.124],[142.203,45.765],[142.203,-45.916],[146.192,-41.534],[166.879,72.275]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[1.332,-7.678],[0,0],[0,0],[-0.406,-2.272],[0,0]],"o":[[-3.043,-23.428],[0,0],[1.977,0.404],[0,0],[-8.362,3.171]],"v":[[33.918,149.701],[83.703,86.265],[231.3,32.568],[228.226,35.138],[15.862,110.351]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[3.017,-7.185],[0,0],[0,0],[0.113,-2.305],[0,0]],"o":[[2.279,-23.515],[0,0],[1.836,0.836],[0,0],[-8.86,1.219]],"v":[[-14.216,114.598],[48.507,63.918],[211.279,43.471],[207.708,45.287],[-23.004,72.205]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[0,0],[-0.951,1.065],[0,0]],"o":[[0,0],[0,0],[1.558,0.037],[0,0],[2.363,12.283]],"v":[[178.181,86.34],[-113.097,55.406],[-117.142,-25.354],[-112.299,-24.593],[182.29,-7.409]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[-0.951,1.065],[0,0]],"o":[[0,0],[0,0],[1.558,0.037],[0,0],[2.363,12.283]],"v":[[203.181,84.34],[-3.597,32.906],[-6.642,-43.854],[-1.799,-43.093],[206.887,15.933]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[0,0],[-0.71,-0.531],[0,0]],"o":[[0,0],[0,0],[1.862,0.212],[0,0],[2.363,12.283]],"v":[[191.388,114.664],[68.387,39.775],[66.846,-46.201],[71.485,-43.717],[191.732,43.424]],"c":true}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[0,0],[-0.607,-1.208],[0,0]],"o":[[0,0],[0,0],[1.991,0.286],[0,0],[2.363,12.283]],"v":[[186.383,127.535],[109.148,42.485],[108.213,-45.387],[109.516,-44.922],[190.104,52.518]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0],[0,0],[-0.491,-1.948],[0,0]],"o":[[0,0],[0,0],[2.111,0.367],[0,0],[2.339,12.283]],"v":[[181.218,141.604],[144.465,45.448],[144.197,-44.496],[147.678,-41.279],[188.405,53.995]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[-0.45,-2.25],[0,0]],"o":[[0,0],[0,0],[2.19,0.4],[0,0],[0.309,2.436]],"v":[[179.921,133.341],[157.903,46.656],[157.903,-44.133],[162.323,-39.794],[186.887,64.567]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.043137256056,0.286274522543,0.407843142748,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.082352943718,0.329411774874,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[-2.23,0.41],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.41,-2.29]],"v":[[-165.427,-44.133],[-165.427,46.656],[-169.294,70.382],[-191.87,64.648],[-169.857,-39.684]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[-2.274,0.427],[0,0],[0,0],[-1.456,10.318],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.418,-2.383]],"v":[[-168.607,-47.499],[-165.609,29.377],[-169.263,78.832],[-191.838,72.865],[-173.124,-42.867]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[-2.206,0.375],[0,0],[0,0],[-1.456,9.071],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.406,-2.095]],"v":[[-163.482,-33.298],[-165.525,55.388],[-169.294,64.853],[-190.495,53.356],[-167.864,-29.226]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[-2.23,0.41],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.41,-2.29]],"v":[[-165.427,-44.133],[-165.427,46.656],[-169.294,70.382],[-191.87,64.648],[-169.857,-39.684]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[-2.23,0.41],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.41,-2.29]],"v":[[-200.427,-39.133],[-152.363,81.428],[-151.98,81.153],[-152.055,81.545],[-198.857,-36.184]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[-2.013,0.414],[0,0],[0,0],[-1.314,10.011],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.37,-2.312]],"v":[[-149.609,-45.916],[-149.609,45.765],[-153.099,104.723],[-173.063,74.284],[-153.607,-41.423]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[-2.013,0.414],[0,0],[0,0],[-1.314,10.011],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.37,-2.312]],"v":[[14.391,-46.666],[15.391,17.64],[-154.599,66.223],[-207.723,13.683],[13.393,-46.673]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[-2.013,0.414],[0,0],[0,0],[-1.314,10.011],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.37,-2.312]],"v":[[119.391,-27.916],[114.391,42.39],[-172.599,70.223],[-180.223,-7.067],[118.393,-27.923]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[-1.395,0.756],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[-0.26,-2.293]],"v":[[-191.324,37.773],[-191.74,38.282],[-188.794,79.632],[111.63,56.898],[-193.998,37.561]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[-1.395,0.756],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[-0.26,-2.293]],"v":[[-199.324,4.773],[-199.74,5.282],[-188.794,69.882],[-34.37,82.648],[-201.998,4.561]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[-1.747,0.61],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.023,-2.291]],"v":[[-172.876,-16.506],[-185.266,22.734],[-180.569,70.093],[-100.283,76.597],[-176.291,-14.752]],"c":true}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[-1.982,0.513],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.211,-2.291]],"v":[[-170.244,-29.859],[-175.618,34.369],[-173.937,74.378],[-134.946,71.388],[-174.153,-26.794]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[-2.136,0.44],[0,0],[0,0],[-1.441,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.349,-2.29]],"v":[[-162.987,-39.999],[-164.527,43.097],[-167.094,70.339],[-171.789,65.4],[-167.222,-35.95]],"c":true}]},{"t":156,"s":[{"i":[[-2.23,0.41],[0,0],[0,0],[-1.456,9.913],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0.41,-2.29]],"v":[[-165.427,-44.133],[-165.427,46.656],[-169.294,70.382],[-191.87,64.648],[-169.857,-39.684]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.043137256056,0.286274522543,0.407843142748,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.082352943718,0.329411774874,0.509803950787,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":1,"k":[{"t":51,"s":[100],"h":1},{"t":93,"s":[0],"h":1},{"t":141,"s":[100],"h":1}]},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 3","bm":0,"hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,46.654],[157.906,46.654]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.609,29.374],[157.724,29.374]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.525,55.386],[157.808,55.386]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,46.654],[157.906,46.654]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-158.928,50.154],[123.406,35.308]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-149.609,45.762],[142.205,45.762]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-77.963,34.065],[116.97,57.343]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.565,18.468],[99.656,43.617]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[116.565,43.218],[202.156,113.867]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-83.928,51.154],[-113.594,53.154]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-70.053,50.529],[-4.094,30.654]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.654,63.763],[65.753,37.451]],"c":false}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-136.602,57.231],[102.159,40.02]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-159.779,51.111],[142.881,46.453]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,46.654],[157.906,46.654]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.333333343267,0.580392181873,0.768627464771,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,52.477],[157.906,52.477]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.609,35.435],[157.724,35.435]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.525,60.714],[157.808,60.714]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,52.477],[157.906,52.477]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-158.928,55.977],[123.406,41.131]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-149.609,51.643],[142.205,51.643]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-78.133,38.393],[117.067,61.724]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.168,20.726],[99.883,46]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[116.168,45.476],[202.383,116.25]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-84.178,55.227],[-117.094,57.477]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-69.803,53.977],[-7.594,34.977]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.825,68.388],[63.357,42.247]],"c":false}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-136.721,62.224],[100.499,45.131]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-161.29,55.248],[141.028,50.616]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-165.428,52.477],[157.906,52.477]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.098039217293,0.368627458811,0.568627476692,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 2","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 4","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-165.428,-44.13],[-165.428,46.654],[157.906,46.654],[157.906,-44.131]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-168.607,-47.495],[-165.609,29.374],[157.724,29.374],[161.061,-47.496]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-163.482,-33.295],[-165.525,55.386],[157.808,55.386],[156.357,-33.296]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-165.428,-44.13],[-165.428,46.654],[157.906,46.654],[157.906,-44.131]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-200.428,-39.13],[-158.928,50.154],[123.406,35.308],[121.906,-52.881]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-149.609,-45.913],[-149.609,45.762],[142.205,45.762],[142.205,-45.914]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-80.18,-47.734],[-77.395,34.191],[117.205,58.262],[177.723,-22.985]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[14.391,-46.663],[18.891,18.762],[100.205,45.762],[225.08,7.586]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[121.891,-27.663],[116.391,43.262],[205.205,115.762],[205.205,39.586]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-192.073,32.527],[-81.428,53.154],[-113.594,53.154],[-117.14,-25.352]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-200.073,-0.473],[-67.303,54.279],[-4.094,30.654],[-6.64,-43.852]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-174.397,-21.987],[-108.661,62.55],[68.131,38.605],[66.848,-46.199]],"c":false}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-171.069,-30.656],[-142.417,54.999],[112.478,41.624],[109.057,-46.164]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-164.729,-40.155],[-163.009,54.121],[143.845,45.197],[143.796,-43.923]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-165.428,-44.13],[-165.428,46.654],[157.906,46.654],[157.906,-44.131]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":5,"k":{"a":0,"k":[0,0.114,0.447,0.698,0.25,0.212,0.539,0.829,0.5,0.31,0.631,0.961,0.75,0.212,0.539,0.829,1,0.114,0.447,0.698]}},"s":{"a":0,"k":[-158.817,0.036]},"e":{"a":0,"k":[151.666,1.12]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 5","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[17.112,0],[0,0],[-0.441,2.444],[0,0],[-0.519,-2.574]],"o":[[0.498,2.468],[0,0],[-19.428,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0],[0.474,-2.627],[0,0]],"v":[[185.17,58.567],[181.264,72.34],[105.532,72.34],[59.218,106.732],[-3.15,137.51],[-65.603,107.296],[-113.722,72.34],[-186.665,72.34],[-190.587,58.648],[-169.856,-39.684],[162.319,-39.796]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-20.059],[29.04,0],[14.909,17.039],[17.112,0],[0,0],[-0.441,2.544],[0,0],[-0.53,-2.679]],"o":[[0.498,2.568],[0,0],[-19.428,0],[-15.947,19.239],[-30.144,0],[-26.243,-29.991],[0,0],[-2.484,0],[0,0],[0.483,-2.734],[0,0]],"v":[[185.202,66.536],[181.296,80.871],[105.563,80.871],[59.249,116.666],[-3.15,141.556],[-65.572,117.253],[-113.69,80.871],[-186.633,80.871],[-190.555,66.62],[-173.123,-42.868],[165.562,-42.985]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-17.635],[29.04,0],[14.909,14.979],[17.112,0],[0,0],[-0.441,2.237],[0,0],[-0.514,-2.355]],"o":[[0.498,2.258],[0,0],[-19.428,0],[-15.947,16.914],[-30.144,0],[-26.243,-26.366],[0,0],[-2.484,0],[0,0],[0.468,-2.403],[0,0]],"v":[[185.17,54.042],[181.264,66.645],[105.532,66.645],[59.218,98.114],[-3.15,126.275],[-65.603,98.63],[-113.722,66.645],[-186.665,66.645],[-190.587,54.116],[-167.863,-29.227],[160.723,-29.329]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[17.112,0],[0,0],[-0.441,2.444],[0,0],[-0.519,-2.574]],"o":[[0.498,2.468],[0,0],[-19.428,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0],[0.474,-2.627],[0,0]],"v":[[185.17,58.567],[181.264,72.34],[105.532,72.34],[59.218,106.732],[-3.15,137.51],[-65.603,107.296],[-113.722,72.34],[-186.665,72.34],[-190.587,58.648],[-169.856,-39.684],[162.319,-39.796]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[2.309,0],[0,0],[15.254,-19.273],[26.641,0],[12.216,17.488],[15.698,0],[0,0],[-0.404,2.444],[0,0],[-0.638,-2.547]],"o":[[0.457,2.468],[0,0],[-17.823,0],[-14.63,18.485],[-27.653,0],[-11.209,-16.046],[0,0],[-2.279,0],[0,0],[0.351,-2.646],[0,0]],"v":[[186.759,57.732],[188.639,68.206],[122.576,71.698],[77.718,106.732],[24.253,134.51],[-30.291,102.296],[-72.31,84.299],[-146.253,86.978],[-160.2,48.899],[-199.187,-35.967],[126.626,-49.978]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[2.272,0],[0,0],[15.007,-19.462],[26.209,0],[13.456,16.531],[15.444,0],[0,0],[-0.398,2.468],[0,0],[-0.469,-2.599]],"o":[[0.449,2.492],[0,0],[-17.534,0],[-14.393,18.666],[-27.205,0],[-23.684,-29.098],[0,0],[-2.242,0],[0,0],[0.427,-2.652],[0,0]],"v":[[165.33,66.216],[161.804,80.124],[93.454,80.124],[53.138,106.43],[-2.033,135.497],[-59.515,107],[-112.691,80.186],[-168.366,82.052],[-171.905,68.225],[-153.606,-41.423],[146.189,-41.536]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[2.272,0],[0,0],[9.576,-14.653],[18.362,1.934],[11.553,12.456],[13.943,2.066],[0,0],[-3.735,3.724],[0,0],[-0.469,-2.599]],"o":[[0.337,1.63],[0,0],[-17.534,0],[-9.783,16.17],[-32.12,-2.924],[-17.87,-20.005],[0,0],[-2.242,0],[0,0],[0.427,-2.652],[0,0]],"v":[[96.135,75.008],[85.083,83.05],[20.375,73.407],[-13.398,99.68],[-56.94,120.717],[-106.154,78.635],[-146.065,59.832],[-185.792,54.687],[-186.331,44.289],[-81.82,-44.101],[182.51,-20.751]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[2.272,0],[0,0],[2.334,-8.241],[7.9,4.513],[9.015,7.023],[11.943,4.822],[0,0],[-8.185,5.398],[0,0],[-0.469,-2.599]],"o":[[0.188,0.48],[0,0],[-17.534,0],[-3.638,12.842],[-12.037,-6.877],[-10.117,-7.881],[0,0],[-2.242,0],[0,0],[0.427,-2.652],[0,0]],"v":[[6.395,87.657],[-17.213,86.951],[-77.063,64.451],[-102.112,90.68],[-130.15,101.01],[-150.765,38.5],[-182.193,25.701],[-209.026,18.201],[-205.565,12.375],[13.894,-47.673],[230.939,6.964]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[2.272,0],[0,0],[0.376,-1.143],[4.4,-0.871],[0.932,13.221],[6.693,15.937],[0,0],[-0.81,0.638],[0,0],[-0.469,-2.599]],"o":[[-18.812,-3.404],[0,0],[-2.187,-1.313],[-1.138,3.458],[-2.669,0.529],[-0.228,-3.228],[0,0],[-2.242,0],[0,0],[0.428,-2.652],[0,0]],"v":[[-112.688,65.042],[-145.463,60.951],[-156.563,47.951],[-165.362,64.93],[-172.15,74.259],[-178.932,39.667],[-181.693,15.201],[-184.026,5.701],[-184.44,-6.5],[123.894,-29.173],[209.439,39.464]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[2.517,0],[0,0],[0.072,-4.69],[2.074,0.454],[1.435,10.415],[4.722,-1.244],[0,0],[7.086,1.949],[0,0],[-0.948,-2.42]],"o":[[0.498,2.468],[0,0],[-3.032,19.507],[-0.218,14.114],[-7.601,-1.663],[-1.647,-11.949],[0,0],[-2.484,0],[0,0],[-0.296,-2.63],[0,0]],"v":[[180.573,-10.284],[173.264,-0.41],[174.032,25.09],[174.968,52.482],[170.851,74.26],[163.897,50.546],[153.855,33.128],[140.609,55.168],[119.664,58.399],[-193.997,37.561],[-114.677,-24.595]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[2.517,0],[0,0],[5.782,-13.635],[10.621,-1.026],[4.8,27.342],[17.112,0],[0,0],[7.086,1.949],[0,0],[-0.948,-2.42]],"o":[[0.498,2.468],[0,0],[-3.032,19.507],[-9.531,22.476],[-13.851,1.338],[-3.897,-22.199],[0,0],[-2.484,0],[0,0],[-0.296,-2.63],[0,0]],"v":[[206.42,15.558],[180.264,30.206],[167.532,30.34],[160.218,76.232],[140.101,99.76],[102.897,66.046],[44.778,68.629],[-8.165,84.59],[-26.337,84.148],[-201.997,4.561],[-4.177,-43.095]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[2.517,0],[0,0],[10.357,-16.013],[18.39,-0.593],[14.321,19.987],[17.112,0],[0,0],[3.911,2.158],[0,0],[-0.767,-2.485]],"o":[[0.498,2.468],[0,0],[-9.948,11.279],[-12.238,20.792],[-20.723,0.773],[-12.739,-17.176],[0,0],[-2.484,0],[0,0],[0.028,-2.629],[0,0]],"v":[[194.426,43.518],[173.775,48.317],[140.554,52.415],[105.613,84.706],[67.847,117.709],[16.246,83.824],[-29.404,70.507],[-88.336,82.614],[-97.947,75.383],[-176.29,-14.752],[69.459,-44.547]],"c":true}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[2.517,0],[0,0],[13.407,-17.599],[23.57,-0.305],[11.907,19.629],[17.112,0],[0,0],[1.795,2.297],[0,0],[-0.647,-2.528]],"o":[[0.498,2.468],[0,0],[-14.559,5.793],[-14.042,19.67],[-25.305,0.397],[-19.606,-26.85],[0,0],[-2.484,0],[0,0],[0.245,-2.628],[0,0]],"v":[[190.601,48.426],[180.087,62.459],[123.945,59.867],[89.213,97.674],[39.394,126.298],[-15.561,95.046],[-71.1,72.208],[-132.505,80.123],[-136.409,68.366],[-174.152,-26.794],[115.716,-42.932]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[2.492,0],[0,0],[15.535,-18.788],[27.177,-0.11],[13.897,17.314],[16.938,0],[0,0],[0.205,2.402],[0,0],[-0.551,-2.561]],"o":[[0.493,2.468],[0,0],[-17.835,1.678],[-15.239,18.828],[-28.451,0.115],[-24.074,-28.246],[0,0],[-2.459,0],[0,0],[0.403,-2.627],[0,0]],"v":[[187.34,48.547],[181.579,62.396],[111.882,68.728],[69.359,104.109],[11.222,134.262],[-48.448,103.748],[-97.398,71.523],[-167.429,73.394],[-171.306,60.263],[-167.221,-35.95],[149.453,-40.704]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[2.517,0],[0,0],[16.627,-19.273],[29.04,0],[14.909,16.371],[17.112,0],[0,0],[-0.441,2.444],[0,0],[-0.519,-2.574]],"o":[[0.498,2.468],[0,0],[-19.428,0],[-15.947,18.485],[-30.144,0],[-26.243,-28.815],[0,0],[-2.484,0],[0,0],[0.474,-2.627],[0,0]],"v":[[185.17,58.567],[181.264,72.34],[105.532,72.34],[59.218,106.732],[-3.15,137.51],[-65.603,107.296],[-113.722,72.34],[-186.665,72.34],[-190.587,58.648],[-169.856,-39.684],[162.319,-39.796]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[0,0.114,0.435,0.69,0.35,0.149,0.478,0.759,0.7,0.184,0.522,0.827]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[0,0.098,0.388,0.618,0.35,0.141,0.455,0.723,0.7,0.184,0.522,0.827]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[0,0.098,0.388,0.618,0.35,0.141,0.455,0.723,0.7,0.184,0.522,0.827]},{"t":131,"s":[0,0.114,0.435,0.69,0.35,0.149,0.478,0.759,0.7,0.184,0.522,0.827]}]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[-1.817,38.036],"to":[0,12.167],"ti":[0,-12.167]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[-1.817,111.036],"to":[0,0],"ti":[0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[-1.817,111.036],"to":[0,-12.167],"ti":[0,12.167]},{"t":131,"s":[-1.817,38.036]}]},"e":{"a":0,"k":[-1.817,114.348]},"t":1,"nm":"Gradient Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 6","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[0,0],[0,0],[-7.08,0],[0,0],[-1.54,-6.91],[0,0]],"o":[[0,0],[1.55,-6.91],[0,0],[7.08,0],[0,0],[0,0]],"v":[[-213.434,70.257],[-185.727,-49.014],[-170.967,-60.834],[164.673,-60.834],[179.423,-49.014],[208.403,71.546]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[0,0],[0,0],[-7.219,0],[0,0],[-1.57,-7.192],[0,0]],"o":[[0,0],[1.58,-7.192],[0,0],[7.219,0],[0,0],[0,0]],"v":[[-213.403,78.702],[-189.305,-52.578],[-174.256,-64.88],[167.961,-64.88],[183,-52.578],[208.435,80.044]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[0,0],[0,0],[-7.004,0],[0,0],[-1.523,-6.323],[0,0]],"o":[[0,0],[1.533,-6.323],[0,0],[7.004,0],[0,0],[0,0]],"v":[[-213.059,60.529],[-183.563,-37.764],[-168.962,-48.579],[163.051,-48.579],[178.508,-37.764],[209.278,65.918]],"c":false}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[-7.08,0],[0,0],[-1.54,-6.91],[0,0]],"o":[[0,0],[1.55,-6.91],[0,0],[7.08,0],[0,0],[0,0]],"v":[[-213.434,70.257],[-185.727,-49.014],[-170.967,-60.834],[164.673,-60.834],[179.423,-49.014],[208.403,71.546]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[-7.072,0.328],[0,0],[-4.034,-5.772],[0,0]],"o":[[0,0],[1.228,-6.974],[0,0],[7.072,-0.328],[0,0],[0,0]],"v":[[-170.362,88.402],[-221.473,-43.051],[-207.277,-55.543],[118.752,-71.352],[136.784,-60.478],[211.576,65.998]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[-6.39,0],[0,0],[-1.39,-6.978],[0,0]],"o":[[0,0],[1.399,-6.978],[0,0],[6.39,0],[0,0],[0,0]],"v":[[-192.803,76.032],[-167.93,-50.844],[-154.608,-62.78],[148.313,-62.78],[161.625,-50.844],[186.298,79.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[-14.142,-2.947],[0,0],[7.875,-2.133],[0,0]],"o":[[0,0],[9.93,-2.883],[0,0],[4.687,1.553],[0,0],[0,0]],"v":[[-227.767,8.98],[-4.18,-54.344],[22.392,-56.78],[223.813,1.72],[222.875,9.656],[10.675,84.576]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[0,0],[0,0],[-12.218,-7.707],[0,0],[7.875,-2.133],[0,0]],"o":[[0,0],[10.305,-1.018],[0,0],[4.687,1.553],[0,0],[0,0]],"v":[[-193.436,-20.153],[107.82,-41.344],[134.092,-32.791],[213.813,35.22],[203.75,39.656],[-126.47,60.899]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[0,0],[0,0],[-4.734,1.136],[0,0],[-3.829,-1.415],[0,0]],"o":[[0,0],[-7.215,-1.688],[0,0],[4.013,-4.474],[0,0],[0,0]],"v":[[117.691,58.507],[-213.285,36.035],[-216.425,27.673],[-130.263,-35.179],[-109.421,-39.988],[195.393,-23.609]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[-4.734,1.136],[0,0],[-5.954,-1.79],[0,0]],"o":[[0,0],[-7.215,-1.688],[0,0],[4.734,-1.136],[0,0],[0,0]],"v":[[-28.309,84.257],[-225.035,-1.715],[-228.175,-10.077],[-14.013,-58.679],[5.204,-58.113],[234.653,7.412]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[0,0],[0,0],[-5.724,0.657],[0,0],[-4.092,-3.95],[0,0]],"o":[[0,0],[-3.518,-3.891],[0,0],[5.724,-0.657],[0,0],[0,0]],"v":[[-108.05,79.817],[-199.08,-22.166],[-195.044,-31.987],[61.515,-61.931],[78.847,-56.618],[222.314,40.512]],"c":false}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[0,0],[0,0],[-6.383,0.337],[0,0],[-2.851,-5.389],[0,0]],"o":[[0,0],[-1.053,-5.359],[0,0],[6.383,-0.337],[0,0],[0,0]],"v":[[-151.932,75.684],[-194.776,-35.217],[-187.957,-45.76],[112.2,-59.6],[128.276,-51.122],[215.319,55.131]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[0,0],[0,0],[-6.808,0.098],[0,0],[-1.9,-6.47],[0,0]],"o":[[0,0],[0.788,-6.461],[0,0],[6.808,-0.098],[0,0],[0,0]],"v":[[-191.822,70.628],[-184.294,-45.017],[-171.96,-56.468],[150.1,-60.476],[165.08,-49.624],[210.763,59.709]],"c":false}]},{"t":156,"s":[{"i":[[0,0],[0,0],[-7.08,0],[0,0],[-1.54,-6.91],[0,0]],"o":[[0,0],[1.55,-6.91],[0,0],[7.08,0],[0,0],[0,0]],"v":[[-213.434,70.257],[-185.727,-49.014],[-170.967,-60.834],[164.673,-60.834],[179.423,-49.014],[208.403,71.546]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.600000023842,0.607843160629,0.701960802078,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 7","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.8,"y":0},"t":19,"s":[{"i":[[1.17,5.24],[0,0],[7.08,0],[0,0],[1.55,-6.91],[0,0],[-5.37,0],[0,0]],"o":[[0,0],[-1.54,-6.91],[0,0],[-7.08,0],[0,0],[-1.17,5.24],[0,0],[5.37,0]],"v":[[207.693,68.546],[179.423,-49.014],[164.673,-60.834],[-170.967,-60.834],[-185.727,-49.014],[-213.997,68.546],[-205.807,78.756],[199.503,78.756]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":32,"s":[{"i":[[1.17,5.454],[0,0],[7.219,0],[0,0],[1.58,-7.192],[0,0],[-5.37,0],[0,0]],"o":[[0,0],[-1.57,-7.192],[0,0],[-7.219,0],[0,0],[-1.17,5.454],[0,0],[5.37,0]],"v":[[207.724,76.922],[183,-52.578],[167.961,-64.88],[-174.256,-64.88],[-189.305,-52.578],[-213.966,76.922],[-205.776,87.549],[199.534,87.549]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.2,"y":0},"t":40,"s":[{"i":[[1.17,4.795],[0,0],[7.004,0],[0,0],[1.533,-6.323],[0,0],[-5.37,0],[0,0]],"o":[[0,0],[-1.523,-6.323],[0,0],[-7.004,0],[0,0],[-1.17,4.795],[0,0],[5.37,0]],"v":[[207.693,63.173],[177.642,-37.764],[163.051,-48.579],[-168.962,-48.579],[-183.563,-37.764],[-213.622,58.964],[-205.807,72.515],[199.503,72.515]],"c":true}]},{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[1.17,5.24],[0,0],[7.08,0],[0,0],[1.55,-6.91],[0,0],[-5.37,0],[0,0]],"o":[[0,0],[-1.54,-6.91],[0,0],[-7.08,0],[0,0],[-1.17,5.24],[0,0],[5.37,0]],"v":[[207.693,68.546],[179.423,-49.014],[164.673,-60.834],[-170.967,-60.834],[-185.727,-49.014],[-213.997,68.546],[-205.807,78.756],[199.503,78.756]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[1.073,5.24],[0,0],[7.072,-0.328],[0,0],[1.228,-6.974],[0,0],[-4.926,0],[0,0]],"o":[[0,0],[-1.859,-6.831],[0,0],[-7.072,0.328],[0,0],[4.349,10.889],[0,0],[4.926,0]],"v":[[212.174,64.498],[136.784,-60.478],[118.752,-71.352],[-207.277,-55.543],[-221.473,-43.051],[-175.925,73.185],[-167.758,92.568],[203.001,70.008]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[1.056,5.291],[0,0],[6.39,0],[0,0],[1.399,-6.978],[0,0],[-4.846,0],[0,0]],"o":[[0,0],[-1.39,-6.978],[0,0],[-6.39,0],[0,0],[-1.056,5.291],[0,0],[4.846,0]],"v":[[185.656,76.293],[161.625,-50.844],[148.313,-62.78],[-154.608,-62.78],[-167.93,-50.844],[-193.311,74.304],[-185.642,88.531],[178.265,86.603]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[-6.89,3.403],[0,0],[2.312,1.428],[0,0],[7.43,-2.383],[0,0],[-5.698,-2.907],[0,0]],"o":[[0,0],[1,-0.321],[0,0],[-8.641,-3.947],[0,0],[-7.556,1.903],[0,0],[7.752,2.593]],"v":[[13.034,85.047],[224.125,9.406],[219,3.47],[21.641,-56.53],[-4.43,-54.344],[-228.65,8.503],[-215.552,16.18],[-11.752,89.18]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92,"s":[{"i":[[-6.89,3.403],[0,0],[2.062,2.293],[0,0],[8.68,-0.518],[0,0],[-5.698,-2.907],[0,0]],"o":[[0,0],[12.375,-2.883],[0,0],[-6.715,-6.72],[0,0],[-0.056,1.268],[0,0],[7.752,2.593]],"v":[[-124.611,61.37],[205,39.406],[212.063,32.97],[133.302,-32.819],[107.57,-41.344],[-195.444,-20.13],[-188.552,-6.82],[-145.752,61.68]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[{"i":[[1.17,5.24],[0,0],[8.638,-5.099],[0,0],[-7.527,-1.688],[0,0],[-7.693,3.216],[0,0]],"o":[[0,0],[-7.579,-1.04],[0,0],[-4.734,1.136],[0,0],[7.122,1.301],[0,0],[2.497,-5.16]],"v":[[200.057,-23.734],[-98.171,-39.613],[-129.638,-35.679],[-216.425,27.673],[-213.285,36.035],[117.253,58.296],[139.193,57.506],[191.654,-5.089]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[4.682,1.926],[0,0],[4.734,-1.136],[0,0],[-7.527,-1.688],[0,0],[-7.693,3.216],[0,0]],"o":[[0,0],[-5.835,-3.85],[0,0],[-4.734,1.136],[0,0],[7.122,1.301],[0,0],[11.872,-5.534]],"v":[[234.193,6.912],[13.204,-56.113],[-14.013,-58.679],[-228.175,-10.077],[-225.035,-1.715],[-28.747,84.046],[-6.808,83.256],[228.128,21.622]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130,"s":[{"i":[[3.201,3.324],[0,0],[5.724,-0.657],[0,0],[-3.698,-3.891],[0,0],[-6.713,1.859],[0,0]],"o":[[0,0],[-4.023,-5.141],[0,0],[-5.724,0.657],[0,0],[3.624,2.962],[0,0],[9.129,-3.2]],"v":[[221.748,38.958],[83.473,-55.462],[61.515,-61.931],[-195.044,-31.987],[-199.455,-22.166],[-108.915,78.974],[-91.8,83.724],[213.787,47.27]],"c":true}]},{"i":{"x":0.9,"y":1},"o":{"x":0.167,"y":0.167},"t":134,"s":[{"i":[[2.213,4.256],[0,0],[6.383,-0.337],[0,0],[-1.146,-5.359],[0,0],[-6.06,0.955],[0,0]],"o":[[0,0],[-2.816,-6.001],[0,0],[-6.383,0.337],[0,0],[1.293,4.07],[0,0],[7.301,-1.644]],"v":[[214.683,52.874],[130.652,-50.528],[112.2,-59.6],[-187.957,-45.76],[-195.401,-35.217],[-153.083,74.419],[-139.184,82.862],[207.124,64.42]],"c":true}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":141,"s":[{"i":[[1.457,4.955],[0,0],[6.808,-0.098],[0,0],[0.761,-6.461],[0,0],[-5.513,0.277],[0,0]],"o":[[0,0],[-1.89,-6.647],[0,0],[-6.808,0.098],[0,0],[-0.452,4.901],[0,0],[5.869,-0.476]],"v":[[210.081,56.924],[165.762,-49.452],[150.1,-60.476],[-171.96,-56.468],[-184.473,-45.017],[-192.548,69.047],[-182.804,78.745],[202.155,67.522]],"c":true}]},{"t":156,"s":[{"i":[[1.17,5.24],[0,0],[7.08,0],[0,0],[1.55,-6.91],[0,0],[-5.37,0],[0,0]],"o":[[0,0],[-1.54,-6.91],[0,0],[-7.08,0],[0,0],[-1.17,5.24],[0,0],[5.37,0]],"v":[[207.693,68.546],[179.423,-49.014],[164.673,-60.834],[-170.967,-60.834],[-185.727,-49.014],[-213.997,68.546],[-205.807,78.756],[199.503,78.756]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0.103,0.945,0.941,0.969,0.684,0.851,0.849,0.904,0.999,0.757,0.757,0.839]}},"s":{"a":0,"k":[-2.817,-8.964]},"e":{"a":0,"k":[-2.817,151.004]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 8","bm":0,"hd":false}],"ip":0,"op":180,"st":-4,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Sides 4","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[5.091,81.209,0]},"a":{"a":0,"k":[258.346,386.27,0]},"s":{"a":0,"k":[95.238,95.238,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.4,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-202.501,-61.985],[-231.63,69.947],[-232.353,189.826],[-210.486,64.499]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-239.891,-64.026],[-187.364,75.594],[-186.944,198.329],[-237.498,68.714]],"c":true}]},{"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-174.854,-60.896],[-195.525,55.101],[-193.086,169.063],[-194.757,56.501]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.462745098039,0.490196078431,0.592156862745,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"gf","o":{"a":0,"k":100},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.701,0.706,0.786,0.332,0.76,0.764,0.833,1,0.818,0.823,0.88]}},"s":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74,"s":[-221.061,97.608],"to":[6.033,-0.818],"ti":[-6.033,0.818]},{"t":81,"s":[-184.864,92.698]}]},"e":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74,"s":[-101.492,-27.113],"to":[3.357,19.124],"ti":[-3.357,-19.124]},{"t":81,"s":[-81.353,87.632]}]},"t":1,"nm":"Gradient Fill 2","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[449.758,332.371]},"a":{"a":0,"k":[165.889,64.877]},"s":{"a":0,"k":[105,105]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"R Side","bm":0,"hd":false},{"ty":"tr","p":{"a":0,"k":[252.697,358.335]},"a":{"a":0,"k":[252.697,358.335]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Group 1","bm":0,"hd":false}],"ip":51,"op":81,"st":-4,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Inside","parent":1,"sr":1,"ks":{"p":{"a":0,"k":[-0.177,-28.813,0]},"a":{"a":0,"k":[-65.177,68.187,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.2,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[101.7,13.192],[-234.276,13.334],[-258.298,139.523],[128.895,142.672]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[64.849,2.192],[-256.776,18.834],[-209.798,143.523],[141.395,135.172]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[70.246,15.054],[-200.146,17.739],[-219.925,134.356],[90.682,139.491]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[116.478,67.15],[-50.482,13.104],[-233.874,69.733],[-65.945,131.179]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[131.346,108.074],[97.848,34],[-231.675,34.257],[-260,106.559]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-65.505,132.383],[137.048,62.328],[-64.978,15.095],[-260.834,65.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-254.793,108.415],[131.5,108.336],[99.269,35.271],[-230.112,34.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-258.586,57.899],[-56.159,133.223],[142.594,61.825],[-64.943,15.571]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-228.82,36.773],[-258.402,107.586],[128.571,107.568],[102.057,36]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-68.265,16.573],[-242.978,55.209],[-52.983,128.047],[133.095,57.223]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[101.7,13.192],[-234.276,13.334],[-256.798,137.023],[127.395,135.672]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.062745098039,0.235294132607,0.372549019608,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.074509806931,0.368627458811,0.588235318661,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 1","bm":0,"hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.31,"y":0.907},"o":{"x":0.2,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0],[2.207,13.642]],"o":[[0,0],[0,0],[0,0],[-1.824,-11.274]],"v":[[105.649,10],[-254.978,11.25],[-286.007,148.865],[136.442,146.072]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0.094},"t":74,"s":[{"i":[[0,0],[0,0],[0,0],[2.207,13.642]],"o":[[0,0],[0,0],[0,0],[-1.824,-11.274]],"v":[[63.298,-1],[-290.978,16.25],[-240.507,152.865],[140.442,133.572]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[0,0],[0,0],[1.755,11.809]],"o":[[0,0],[0,0],[0,0],[-1.45,-9.759]],"v":[[74.602,15.15],[-226.321,15.918],[-250.399,150.74],[99.824,150.563]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[151.851,78.341],[-60.371,15.679],[-287.305,80.952],[-75.174,159.965]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[140.325,126.298],[104.46,37.957],[-255.444,37.364],[-285.46,124.134]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-73.851,150.441],[150.022,71.348],[-74.007,18.06],[-292.495,70.466]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-283.909,124.937],[139.492,125.298],[104.899,37.675],[-254.651,37.298]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-290.351,67.248],[-74.978,150.441],[149.993,67.159],[-73.006,18.06]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-253.692,39.071],[-285.872,125.037],[138.46,125.298],[107.399,37.298]],"c":true}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-75.65,18.06],[-292.978,66.665],[-75.007,150.441],[150.005,65.086]],"c":true}]},{"t":156,"s":[{"i":[[0,0],[0,0],[0,0],[2.207,13.642]],"o":[[0,0],[0,0],[0,0],[-1.824,-11.274]],"v":[[105.649,10],[-254.978,11.25],[-286.007,148.865],[136.442,146.072]],"c":true}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[0.6,0.607843137255,0.701960784314,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":8},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"fl","c":{"a":0,"k":[0.945098099054,0.941176530427,0.968627510819,1]},"o":{"a":0,"k":100},"r":1,"bm":0,"nm":"Fill 1","hd":false},{"ty":"tr","p":{"a":0,"k":[9.601,-16.298]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Shape 2","bm":0,"hd":false}],"ip":93,"op":120,"st":-7,"bm":0}]} \ No newline at end of file diff --git a/Rosetta/RosettaApp.swift b/Rosetta/RosettaApp.swift index 2baa504..6c09f76 100644 --- a/Rosetta/RosettaApp.swift +++ b/Rosetta/RosettaApp.swift @@ -18,6 +18,26 @@ final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCent Messaging.messaging().delegate = self UNUserNotificationCenter.current().delegate = self + // Register notification category with CarPlay support. + let messageCategory = UNNotificationCategory( + identifier: "message", + actions: [], + intentIdentifiers: [], + options: [.allowInCarPlay] + ) + UNUserNotificationCenter.current().setNotificationCategories([messageCategory]) + + // Clear caches on memory pressure to prevent system from killing the app. + NotificationCenter.default.addObserver( + forName: UIApplication.didReceiveMemoryWarningNotification, + object: nil, + queue: .main + ) { _ in + Task { @MainActor in + AvatarRepository.shared.clearCache() + } + } + // Request notification permission UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .badge, .sound] @@ -53,15 +73,13 @@ final class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCent // MARK: - UNUserNotificationCenterDelegate /// Handle foreground notifications β€” suppress ALL when app is in foreground. - /// Android parity: `isAppInForeground` check suppresses everything. - /// Messages arrive in real-time via WebSocket, push is only for background. + /// Android parity: messages arrive via WebSocket in real-time, push is background-only. func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { - // App is in foreground β€” suppress all notifications (Android parity). completionHandler([]) }