From 24cfddc64a9b92172247ae9fcdac3c67daa277ea Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 11:22:26 +0530 Subject: [PATCH 01/10] Modernize Cosmic Rituals accuracy and experience --- ios/README.md | 26 +- ios/native/.gitignore | 1 + ios/native/ACCURACY.md | 90 +++++ ios/native/ARCHITECTURE.md | 38 +- .../CosmicRituals/App/CosmicAppIntents.swift | 19 +- .../CosmicRituals/App/CosmicRitualsApp.swift | 3 +- .../Engine/CalculationContext.swift | 40 ++- .../CosmicRituals/Engine/CosmicEngine.swift | 328 ++++++++++++++---- .../CosmicRituals/Engine/FestivalData.swift | 4 + .../CosmicRituals/Engine/PanchangModels.swift | 59 ++++ .../Engine/PanchangPDFExporter.swift | 90 ++++- .../CosmicRituals/Theme/CosmicTheme.swift | 5 +- .../Views/GrahaPositionsView.swift | 3 + .../Views/MonthlyCalendarView.swift | 13 +- .../Views/PanchangExperienceViews.swift | 147 ++++++-- .../CosmicRituals/Views/PanchangView.swift | 238 +++++++------ .../Views/RitualLocationViews.swift | 2 + .../CosmicEngineTests.swift | 314 ++++++++++++++++- ios/native/README.md | 18 +- ios/native/ROADMAP.md | 235 ++++++------- 20 files changed, 1293 insertions(+), 380 deletions(-) create mode 100644 ios/native/ACCURACY.md diff --git a/ios/README.md b/ios/README.md index 138fffb..29ea2f6 100644 --- a/ios/README.md +++ b/ios/README.md @@ -1,25 +1,29 @@ # Cosmic Rituals — Native iOS handoff -This `ios/` section is an additive, self-contained snapshot of the completed native SwiftUI application. The GitHub repository was empty when cloned; its remote and new local history remain intact. +This `ios/` section is the canonical, self-contained native SwiftUI application. +It began as a sanitized handoff from the development checkout and now carries its +own tested accuracy and product-integrity improvements. ## Layout -- `native/` — exact sanitized snapshot of the live Xcode project -- `evidence/` — selected final simulator screenshots +- `native/` — working Xcode project, tests, and product documentation +- `evidence/` — historical handoff screenshots (not current QA evidence) ## Current product state - Native SwiftUI application targeting iOS 26 - Six selectable light/dark visual themes with Liquid Glass surfaces and motion polish -- Ritual-now guidance, Panchang/five-limb context, Vedic ledger, location support, and saved preferences +- Sunrise-anchored Panchang, four limb transitions, three experience layouts, + location-aware timing, saved preferences, and calculation-integrity disclosure - Privacy manifest, city data, tests, and app icon assets included - `CosmicWidgets/` source is preserved, but it is not currently wired as an Xcode widget target and must not be presented as shipping functionality -## Verification already completed +## Historical release state -- Simulator design and experience QA completed on iPhone 17 / iOS 26.5 -- Native project and unit-test target are included under `native/` -- TestFlight: version 1.0, build 3 uploaded successfully; archive/build products are intentionally excluded from source control +- The imported handoff had simulator design QA on iPhone 17 / iOS 26.5. +- TestFlight version 1.0 build 3 was uploaded from the earlier development checkout. +- Those facts do not prove that the current source is release-ready; rerun the + build, tests, and simulator QA below for every change. ## Build and test @@ -41,11 +45,13 @@ xcodebuild \ test ``` -## Snapshot provenance +## Initial-import provenance - Copied read-only from `/Users/psy/Documents/Gohler/Cosmic-Rituals` - Snapshot date: 2026-07-26 - Included source files: 41 -- Aggregate relative-path/content SHA-256: `80bab676abbf21a77da200b60ab534e37318af6dfe6f018d099dd7a679430495` +- Original aggregate relative-path/content SHA-256: `80bab676abbf21a77da200b60ab534e37318af6dfe6f018d099dd7a679430495` - Excluded: `.DS_Store`, user Xcode state, DerivedData, build products, archives, and signing material +`SNAPSHOT.sha256` records that import only. It is intentionally not a checksum of +the evolving canonical source tree. diff --git a/ios/native/.gitignore b/ios/native/.gitignore index 5bb84b1..2e8cc84 100644 --- a/ios/native/.gitignore +++ b/ios/native/.gitignore @@ -4,6 +4,7 @@ # Xcode build/ DerivedData/ +.derived-data/ *.xcuserstate xcuserdata/ *.moved-aside diff --git a/ios/native/ACCURACY.md b/ios/native/ACCURACY.md new file mode 100644 index 0000000..8350ba7 --- /dev/null +++ b/ios/native/ACCURACY.md @@ -0,0 +1,90 @@ +# Calculation accuracy and provenance + +Cosmic Rituals is an offline Panchang application. It favors deterministic, +location-aware calculations and explicit limits over unexplained precision. + +## Daily reference convention + +- A Panchang day is anchored to sunrise at the selected calculation location. +- Tithi, Nakshatra, Yoga, Karana, Moon sign, and the displayed Pada are evaluated + at that sunrise instant. +- If sunrise does not exist for the selected latitude and civil date, the app + does not fabricate sunrise-based schedules. The five-limb snapshot falls back + to 12:00 PM local civil time and labels that fallback in the interface. +- Dates and clock times are formatted in the calculation location's IANA time + zone, including daylight-saving transitions. They never silently inherit the + device time zone. + +## Astronomical methods + +| Quantity | Current method | Presentation contract | +|---|---|---| +| Julian date | Gregorian UTC conversion based on Meeus chapter 7 | Absolute instant | +| Sun longitude | Meeus chapter 25 apparent ecliptic longitude | Offline compact ephemeris | +| Moon longitude | Complete Meeus table 47.A longitude series | Offline compact ephemeris | +| Sidereal conversion | Lahiri (Chitra Paksha) ayanamsha polynomial | Named in Settings and exports | +| Sunrise / sunset | NOAA/Meeus solar altitude crossing at -0.8333 degrees | No result for polar day/night | +| Limb transitions | Bracketed 56-step boundary solve for each limb | Tithi, Nakshatra, Yoga, and Karana independently | +| Muhurta / Choghadiya / Hora | Proportional local day and night divisions | Requires real sunrise, sunset, and next sunrise | +| Rahu Kala / Yamaganda / Gulika | Weekday-specific eighths of local daylight | Requires real sunrise and sunset | +| Abhijit | Eighth of 15 daylight muhurtas | Scales with daylight; omitted on Wednesday | +| Brahma Muhurta | 96 to 48 minutes before sunrise | Requires real sunrise | +| Dur Muhurta | Weekday-specific day/night division table | Tuesday second period uses the night division | + +The numerical boundary solver converges to sub-second resolution. That describes +the solver, not the absolute accuracy of the compact astronomical model. The UI +uses a conservative plus-or-minus 12 minute validation envelope for externally +published civil-time comparisons. + +## Regression evidence + +The native test target includes independent fixtures whose expected values are +recorded beside their sources: + +- Sunrise and sunset: New Delhi, Tokyo, Los Angeles, and New York on a DST + transition day. +- Five-limb transitions: Paris on 21 July 2026, covering Tithi, Nakshatra, Yoga, + and Karana. +- Rahu Kala, Yamaganda, Gulika, and Dur Muhurta: published Mumbai Friday + fixtures, plus Hyderabad Tuesday Dur Muhurta including its night period. +- Abhijit Muhurta: Mumbai on Friday, plus the Wednesday omission rule. +- Boundary invariants: multiple seasons and time zones verify that every solved + transition changes from its declared current value to its declared next value. +- Civil-time invariants: selected-day preservation east and west of UTC, DST day + advancement, civil-day preservation when changing locations, and explicit + polar fallback behavior. + +Run the evidence with: + +```bash +xcodebuild \ + -project CosmicRituals.xcodeproj \ + -scheme CosmicRituals \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ + CODE_SIGNING_ALLOWED=NO \ + test +``` + +## Deliberately not claimed as reference-grade + +The repository preserves experimental festival, extended Vedic-calendar, +moonrise/moonset, and low-precision Graha-position work for future development. +Those calculations are not routed into the shipping navigation and must not be +described as available or reference-grade until they have independent fixtures, +regional rule handling where applicable, and visible precision disclosures. + +The WidgetKit source is also not a shipping widget until an extension target, +App Group, signing capability, and end-to-end tests are present. + +## Practical limits + +- Atmospheric refraction, observer elevation, terrain, and local horizon + obstruction can shift observed sunrise and sunset. +- A compact offline ephemeris is not a replacement for JPL or Swiss Ephemeris + data when arcsecond-level planetary positions are required. +- Festival and vrata observance can depend on regional school, sunrise/tithi + precedence, and event-specific rules; a simple date or tithi lookup is not + sufficient. +- Ritual interpretation is traditional and symbolic. For consequential + ceremonial timing, users should confirm the chosen convention with a qualified + practitioner. diff --git a/ios/native/ARCHITECTURE.md b/ios/native/ARCHITECTURE.md index 7d983a7..b4251dc 100644 --- a/ios/native/ARCHITECTURE.md +++ b/ios/native/ARCHITECTURE.md @@ -45,20 +45,25 @@ brought over: - **`PanchangModels.swift`** is the *Panchang slice* of the parent `AstroModel.swift` — only the value types the Panchang screen touches. `CelestialBody` is retained because a nakshatra reports its (dasha) lord. -- **`CosmicTheme.swift`** is the shared theme/component library, copied verbatim so the - app looks identical to its siblings. -- **`PanchangView.swift`** is copied verbatim from the parent. +- **`CosmicTheme.swift`** began from the shared theme/component library and now carries + app-specific semantic colors, responsive navigation, and motion safeguards. +- **`PanchangView.swift`** began from the parent surface and is now independently + maintained around the location-aware calculation contract described below. ## Data flow -`RootView` hosts `PanchangView`, which owns a `selectedDate` and a sub-tab selection -(Five Limbs / 30 Muhurtas). On appear and on any date change it asks the engine for the -day's facts — the computation is cheap and side-effect-free, so there is no view model -or persistence. +`RootView` hosts `PanchangView`, which owns a selected civil date, destination, +persisted experience/theme preferences, and a `LocationManager`. A +`CalculationContext` binds that date to explicit coordinates and an IANA time zone. +The daily Panchang snapshot is cached with its context so SwiftUI updates never show +values from a previous location while recomputation is in flight. ``` -selectedDate ──▶ CosmicEngine.getPanchang(date:) ──▶ Five Limbs section - └─▶ CosmicEngine.getMuhurtas(date:) ──▶ 30 Muhurtas section (live "now") +selectedDate + RitualLocation ──▶ CalculationContext + ├─▶ sunrise-anchored five limbs + ├─▶ four independent transition solves + ├─▶ solar / kala / choghadiya / hora schedules + └─▶ 30 day + night muhurtas ``` ## The engine @@ -67,12 +72,15 @@ selectedDate ──▶ CosmicEngine.getPanchang(date:) ──▶ Five Limbs sec ayanamsha and Meeus algorithms (Sun §25, Moon §47 47-term series). `getPanchang` derives the five limbs from the Sun/Moon sidereal longitudes: **tithi** (12° elongation steps), **nakshatra** (Moon's 13°20′ segment + pada), **yoga** (Sun+Moon), **karana** (half-tithi), -and **vara** (weekday). `getMuhurtas` divides the local day/night (from NOAA sunrise/sunset) -into 15 + 15 named windows, each tagged with its classical auspiciousness and marked -`isCurrent` against the clock. - -> Muhurta timings use a Delhi default location (28.61° N, 77.21° E). For other locations, -> pass `latDeg`/`lonDeg` to `getMuhurtas`. +and **vara** (weekday). Each changing limb has a separately solved next boundary. +`getMuhurtas` divides the local day/night (from NOAA sunrise/sunset) into 15 + 15 +named windows, each tagged with its classical auspiciousness and marked `isCurrent` +against the clock. See [ACCURACY.md](ACCURACY.md) for the calculation contract, +fixtures, tolerances, and quarantined prototypes. + +> New Delhi is the visible first-run default, not a hidden calculation fallback. +> Once the user chooses a city or current location, every daily calculation, export, +> and App Intent uses that explicit saved context. ## Theme diff --git a/ios/native/CosmicRituals/App/CosmicAppIntents.swift b/ios/native/CosmicRituals/App/CosmicAppIntents.swift index 84fd16f..95f3faf 100644 --- a/ios/native/CosmicRituals/App/CosmicAppIntents.swift +++ b/ios/native/CosmicRituals/App/CosmicAppIntents.swift @@ -16,6 +16,16 @@ private func shortTime(_ date: Date, in timeZone: TimeZone) -> String { return formatter.string(from: date) } +private func spokenTransition( + _ transition: PanchangTransition?, + relativeTo referenceDate: Date, + in timeZone: TimeZone +) -> String { + guard let transition else { return "transition unavailable" } + let ending = transition.endTime.ritualTransitionLabel(relativeTo: referenceDate, in: timeZone) + return "\(transition.currentName) until \(ending), then \(transition.nextName)" +} + // MARK: - "What's the muhurta right now?" Intent struct CurrentMuhurtaIntent: AppIntent { @@ -47,7 +57,7 @@ struct CurrentMuhurtaIntent: AppIntent { struct TodayPanchangIntent: AppIntent { static let title: LocalizedStringResource = "Daily Panchang Snapshot" - static let description = IntentDescription("Get the five limbs sampled at local noon: Vara, Tithi, Nakshatra, Yoga, and Karana.") + static let description = IntentDescription("Get the five limbs at local sunrise, with their next transition times.") @MainActor func perform() async throws -> some ReturnsValue & ProvidesDialog { @@ -56,7 +66,10 @@ struct TodayPanchangIntent: AppIntent { return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) } let p = CosmicEngine.getPanchang(context: context) - let summary = "Daily reference for \(location.name), sampled at 12:00 PM local time. \(p.weekdayName). Tithi: \(p.tithiName). Nakshatra: \(p.nakshatraName). Yoga: \(p.yogaName). Karana: \(p.karanaName)." + let reference = p.sunriseTime == nil + ? "Sunrise is unavailable; this snapshot uses \(shortTime(p.date, in: context.timeZone)) local time." + : "The Panchang day begins at local sunrise." + let summary = "Daily reference for \(location.name). \(reference) \(p.weekdayName). Tithi: \(spokenTransition(p.transitions.tithi, relativeTo: p.date, in: context.timeZone)). Nakshatra: \(spokenTransition(p.transitions.nakshatra, relativeTo: p.date, in: context.timeZone)). Yoga: \(spokenTransition(p.transitions.yoga, relativeTo: p.date, in: context.timeZone)). Karana: \(spokenTransition(p.transitions.karana, relativeTo: p.date, in: context.timeZone))." return .result(value: summary, dialog: IntentDialog(stringLiteral: summary)) } } @@ -110,7 +123,7 @@ struct CosmicShortcuts: AppShortcutsProvider { intent: TodayPanchangIntent(), phrases: [ "Daily Panchang snapshot in \(.applicationName)", - "Panchang noon reference in \(.applicationName)", + "Panchang sunrise reference in \(.applicationName)", "Vedic day snapshot in \(.applicationName)" ], shortTitle: "Daily Snapshot", diff --git a/ios/native/CosmicRituals/App/CosmicRitualsApp.swift b/ios/native/CosmicRituals/App/CosmicRitualsApp.swift index 4253d66..f5d0205 100644 --- a/ios/native/CosmicRituals/App/CosmicRitualsApp.swift +++ b/ios/native/CosmicRituals/App/CosmicRitualsApp.swift @@ -3,7 +3,8 @@ import SwiftUI /// Cosmic Rituals — a native SwiftUI app for the Vedic Panchang: the five limbs of /// the day (tithi, nakshatra, yoga, karana, vara), the Moon's nakshatra & pada, and /// the thirty day & night muhurtas with live timing — presented in the shared -/// cosmic-glass theme. Day-level limbs are explicitly disclosed as a noon snapshot. +/// cosmic-glass theme. Day-level limbs are anchored to the selected location's +/// sunrise, with an explicit local-noon fallback when sunrise is unavailable. @main struct CosmicRitualsApp: App { var body: some Scene { diff --git a/ios/native/CosmicRituals/Engine/CalculationContext.swift b/ios/native/CosmicRituals/Engine/CalculationContext.swift index edf4bdb..5885cf0 100644 --- a/ios/native/CosmicRituals/Engine/CalculationContext.swift +++ b/ios/native/CosmicRituals/Engine/CalculationContext.swift @@ -6,8 +6,7 @@ import Foundation /// the calendar and time zone beside the coordinates prevents that selected day from /// silently becoming the previous UTC day in eastern time zones. struct CalculationContext: Equatable, Sendable { - static let dailySnapshotReferenceHour = 12 - static let dailySnapshotDisclosure = "Daily Panchang reference · sampled at 12:00 PM local time" + static let polarFallbackReferenceHour = 12 let localDay: Date let latitude: Double @@ -45,12 +44,12 @@ struct CalculationContext: Equatable, Sendable { calendar.startOfDay(for: localDay) } - /// A deterministic instant for day-level lunar calculations. + /// Deterministic fallback for dates and latitudes where sunrise is unavailable. /// Noon exists on ordinary DST-transition days and does not inherit a hidden /// time component from whichever date picker last changed the selection. var localNoon: Date { calendar.date( - bySettingHour: Self.dailySnapshotReferenceHour, + bySettingHour: Self.polarFallbackReferenceHour, minute: 0, second: 0, of: localDayStart @@ -76,6 +75,24 @@ struct CalculationContext: Equatable, Sendable { /// formatted in the active calculation location's time zone, never implicitly in /// the device zone. extension Date { + /// Reinterprets the source location's year-month-day in another time zone. + /// The selected Panchang value is a civil day, not an instant; using local + /// noon avoids nonexistent-midnight edge cases on historical clock changes. + func ritualCivilDay( + preservingDateFrom sourceTimeZone: TimeZone, + into targetTimeZone: TimeZone + ) -> Date { + var sourceCalendar = Calendar(identifier: .gregorian) + sourceCalendar.timeZone = sourceTimeZone + let day = sourceCalendar.dateComponents([.year, .month, .day], from: self) + + var targetCalendar = Calendar(identifier: .gregorian) + targetCalendar.timeZone = targetTimeZone + var targetComponents = day + targetComponents.hour = CalculationContext.polarFallbackReferenceHour + return targetCalendar.date(from: targetComponents) ?? self + } + func ritualShortTime(in timeZone: TimeZone, locale: Locale = .autoupdatingCurrent) -> String { let formatter = DateFormatter() formatter.locale = locale @@ -105,4 +122,19 @@ extension Date { formatter.setLocalizedDateFormatFromTemplate(template) return formatter.string(from: self) } + + /// A clock time for same-day events and an unambiguous civil date plus time + /// when an astronomical transition falls after midnight in the active place. + func ritualTransitionLabel( + relativeTo referenceDate: Date, + in timeZone: TimeZone, + locale: Locale = .autoupdatingCurrent + ) -> String { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = timeZone + let time = ritualShortTime(in: timeZone, locale: locale) + guard !calendar.isDate(self, inSameDayAs: referenceDate) else { return time } + let civilDate = ritualDate(template: "EEE d MMM", in: timeZone, locale: locale) + return "\(civilDate) · \(time)" + } } diff --git a/ios/native/CosmicRituals/Engine/CosmicEngine.swift b/ios/native/CosmicRituals/Engine/CosmicEngine.swift index 1c2bee5..ce99c18 100644 --- a/ios/native/CosmicRituals/Engine/CosmicEngine.swift +++ b/ios/native/CosmicRituals/Engine/CosmicEngine.swift @@ -207,17 +207,44 @@ enum CosmicEngine { // MARK: - Panchang - static func getPanchang(context: CalculationContext) -> Panchang { - getPanchang(date: context.localNoon, calendar: context.calendar) + static func getPanchang( + context: CalculationContext, + includeTransitions: Bool = true + ) -> Panchang { + let solar = getSunriseSunset(context: context) + let referenceDate = solar?.sunrise ?? context.localNoon + return getPanchang( + date: referenceDate, + calendar: context.calendar, + sunriseTime: solar?.sunrise, + sunsetTime: solar?.sunset, + transitions: includeTransitions ? getPanchangTransitions(after: referenceDate) : .unavailable + ) + } + + private static func panchangReferenceDate(for context: CalculationContext) -> Date { + getSunriseSunset(context: context)?.sunrise ?? context.localNoon } static func getPanchang(date: Date, timezoneIdentifier: String = "UTC") -> Panchang { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(identifier: timezoneIdentifier) ?? .gmt - return getPanchang(date: date, calendar: calendar) + return getPanchang( + date: date, + calendar: calendar, + sunriseTime: nil, + sunsetTime: nil, + transitions: .unavailable + ) } - private static func getPanchang(date: Date, calendar: Calendar) -> Panchang { + private static func getPanchang( + date: Date, + calendar: Calendar, + sunriseTime: Date?, + sunsetTime: Date?, + transitions: PanchangTransitions + ) -> Panchang { let jd = julianDateFromDate(date) let sunLon = siderealize(sunLongitude(jd: jd), jd: jd) let moonLon = siderealize(moonLongitude(jd: jd), jd: jd) @@ -255,8 +282,9 @@ enum CosmicEngine { weekdayName: weekdayNames[(weekday - 1).clamped(to: 0...6)], moonSignIndex: moonSignIdx, moonSignName: ZodiacSign.fromIndex(moonSignIdx).name, - sunriseTime: nil, - sunsetTime: nil + sunriseTime: sunriseTime, + sunsetTime: sunsetTime, + transitions: transitions ) } @@ -267,6 +295,124 @@ enum CosmicEngine { return raw - 50 // 57 Shakuni, 58 Chatushpada, 59 Naga } + // MARK: - Panchang limb boundaries + + /// Solves the next boundary of every changing Panchang limb from an exact + /// reference instant. Each angle is monotonic over the short search window, + /// so a bracketed binary search gives sub-second numerical precision without + /// coupling the result to the UI's refresh cadence. + static func getPanchangTransitions(after date: Date) -> PanchangTransitions { + PanchangTransitions( + tithi: nextTransition(for: .tithi, after: date), + nakshatra: nextTransition(for: .nakshatra, after: date), + yoga: nextTransition(for: .yoga, after: date), + karana: nextTransition(for: .karana, after: date) + ) + } + + private struct LimbTransitionState { + let angle: Double + let segmentSize: Double + let currentName: String + let nextName: String + } + + private static func nextTransition( + for kind: PanchangLimbKind, + after date: Date + ) -> PanchangTransition? { + let initial = limbTransitionState(for: kind, at: date) + let remainder = initial.angle.truncatingRemainder(dividingBy: initial.segmentSize) + let distanceToBoundary = remainder < 1e-10 + ? initial.segmentSize + : initial.segmentSize - remainder + + func accumulatedAngle(at candidate: Date) -> Double { + let angle = limbTransitionState(for: kind, at: candidate).angle + return normalize360(angle - initial.angle) + } + + var lower = date + var upper = date.addingTimeInterval(36 * 3_600) + while accumulatedAngle(at: upper) < distanceToBoundary, + upper.timeIntervalSince(date) < 72 * 3_600 { + upper = upper.addingTimeInterval(12 * 3_600) + } + guard accumulatedAngle(at: upper) >= distanceToBoundary else { return nil } + + for _ in 0..<56 { + let midpoint = lower.addingTimeInterval(upper.timeIntervalSince(lower) / 2) + if accumulatedAngle(at: midpoint) >= distanceToBoundary { + upper = midpoint + } else { + lower = midpoint + } + } + + return PanchangTransition( + kind: kind, + currentName: initial.currentName, + nextName: initial.nextName, + endTime: lower.addingTimeInterval(upper.timeIntervalSince(lower) / 2) + ) + } + + private static func limbTransitionState( + for kind: PanchangLimbKind, + at date: Date + ) -> LimbTransitionState { + let jd = julianDateFromDate(date) + let sunSidereal = siderealize(sunLongitude(jd: jd), jd: jd) + let moonSidereal = siderealize(moonLongitude(jd: jd), jd: jd) + let elongation = normalize360(moonSidereal - sunSidereal) + + let angle: Double + let segmentSize: Double + let segmentCount: Int + switch kind { + case .tithi: + angle = elongation + segmentSize = 12 + segmentCount = 30 + case .nakshatra: + angle = moonSidereal + segmentSize = 360.0 / 27.0 + segmentCount = 27 + case .yoga: + angle = normalize360(sunSidereal + moonSidereal) + segmentSize = 360.0 / 27.0 + segmentCount = 27 + case .karana: + angle = elongation + segmentSize = 6 + segmentCount = 60 + } + + let rawIndex = Int(angle / segmentSize).clamped(to: 0...(segmentCount - 1)) + let nextRawIndex = (rawIndex + 1) % segmentCount + let names: (current: String, next: String) + switch kind { + case .tithi: + names = (Panchang.tithiNames[rawIndex], Panchang.tithiNames[nextRawIndex]) + case .nakshatra: + names = (Panchang.nakshatraNames[rawIndex], Panchang.nakshatraNames[nextRawIndex]) + case .yoga: + names = (Panchang.yogaNames[rawIndex], Panchang.yogaNames[nextRawIndex]) + case .karana: + names = ( + Panchang.karanaNames[karanaIndex(forHalfTithiIndex: rawIndex)], + Panchang.karanaNames[karanaIndex(forHalfTithiIndex: nextRawIndex)] + ) + } + + return LimbTransitionState( + angle: angle, + segmentSize: segmentSize, + currentName: names.current, + nextName: names.next + ) + } + // MARK: - Sunrise / Sunset (NOAA / Meeus ch.15) /// Returns sunrise and sunset as UTC hour-of-day (e.g. 6.5 = 06:30 UTC) @@ -394,7 +540,7 @@ enum CosmicEngine { } static func getMoonNakshatraPada(context: CalculationContext) -> NakshatraResult { - getMoonNakshatraPada(date: context.localNoon) + getMoonNakshatraPada(date: panchangReferenceDate(for: context)) } /// Returns sunrise and sunset as `Date` values for a given day and location. @@ -421,6 +567,36 @@ enum CosmicEngine { )) } + // MARK: - Sunrise-based daily observances + + /// Brahma Muhurta is the traditional 48-minute muhurta beginning two + /// muhurtas before sunrise and ending one muhurta before sunrise. + static func getBrahmaMuhurta(context: CalculationContext) -> (start: Date, end: Date)? { + guard let sunrise = getSunriseSunset(context: context)?.sunrise else { return nil } + return ( + start: sunrise.addingTimeInterval(-96 * 60), + end: sunrise.addingTimeInterval(-48 * 60) + ) + } + + /// Abhijit is the eighth of fifteen daylight muhurtas, centered on local + /// apparent noon. It scales with daylight rather than assuming a fixed + /// 48-minute clock interval. Classical daily Panchang practice does not + /// present it as an auspicious window on Wednesday. + static func getAbhijitMuhurta(context: CalculationContext) -> (start: Date, end: Date)? { + guard context.localDayComponents.weekday != 4, + let solar = getSunriseSunset(context: context) else { + return nil + } + let daylight = solar.sunset.timeIntervalSince(solar.sunrise) + let muhurtaLength = daylight / 15.0 + let solarNoon = solar.sunrise.addingTimeInterval(daylight / 2.0) + return ( + start: solarNoon.addingTimeInterval(-muhurtaLength / 2.0), + end: solarNoon.addingTimeInterval(muhurtaLength / 2.0) + ) + } + // MARK: - Choghadiya (Vedic day-segments) static func getChoghadiya(context: CalculationContext) -> [Choghadiya] { @@ -517,8 +693,11 @@ enum CosmicEngine { )) } - // MARK: - Moonrise / Moonset (approximate; Meeus §15 adapted) + // MARK: - Moonrise / Moonset prototype (not surfaced) + /// Experimental approximation retained for research only. It is not + /// topocentric and must not be wired into a shipping surface without the + /// validation work listed in ACCURACY.md. static func getMoonriseMoonset(date: Date, latDeg: Double, lonDeg: Double) -> (moonrise: Date?, moonset: Date?) { var cal = Calendar(identifier: .gregorian) cal.timeZone = TimeZone(identifier: "UTC")! @@ -552,41 +731,17 @@ enum CosmicEngine { } static func getSunNakshatra(context: CalculationContext) -> NakshatraResult { - getSunNakshatra(date: context.localNoon) + getSunNakshatra(date: panchangReferenceDate(for: context)) } - // MARK: - Tithi End Time (binary search to next 12° elongation boundary) + // MARK: - Tithi End Time static func getTithiEndTime(date: Date) -> Date? { - let jd0 = julianDateFromDate(date) - - func elongRaw(_ jd: Double) -> Double { - let sL = siderealize(sunLongitude(jd: jd), jd: jd) - let mL = siderealize(moonLongitude(jd: jd), jd: jd) - return normalize360(mL - sL) - } - - let e0 = elongRaw(jd0) - let degsToNext = 12.0 - e0.truncatingRemainder(dividingBy: 12.0) - - // Monotonically increasing accumulated elongation since jd0 - func accumulated(_ jd: Double) -> Double { - let e = elongRaw(jd) - return e < e0 - 180 ? e + 360 - e0 : e - e0 - } - - var lo = jd0, hi = jd0 + 2.0 - guard accumulated(hi) >= degsToNext else { return nil } - - for _ in 0..<52 { - let mid = (lo + hi) / 2.0 - if accumulated(mid) >= degsToNext { hi = mid } else { lo = mid } - } - return date.addingTimeInterval(((lo + hi) / 2.0 - jd0) * 86400.0) + nextTransition(for: .tithi, after: date)?.endTime } static func getTithiEndTime(context: CalculationContext) -> Date? { - getTithiEndTime(date: context.localNoon) + nextTransition(for: .tithi, after: panchangReferenceDate(for: context))?.endTime } // MARK: - Chandra Bala & Tara Bala @@ -622,47 +777,84 @@ enum CosmicEngine { ) } - // MARK: - Gulika Kala slot (1-based, within 8 equal day divisions) + // MARK: - Sunrise-based inauspicious kalas + + /// Rahu Kala, Yamaganda, and Gulika Kala are weekday-specific selections + /// from eight equal daylight divisions. Keeping the tables in the engine + /// prevents presentation code from inventing clock-time fallbacks. + static func getRahuKala(context: CalculationContext) -> (start: Date, end: Date)? { + let slots = [8, 2, 7, 5, 6, 4, 3] // Sunday through Saturday + return daylightKala(context: context, slotsByWeekday: slots) + } + + static func getYamaganda(context: CalculationContext) -> (start: Date, end: Date)? { + let slots = [5, 4, 3, 2, 1, 7, 6] // Sunday through Saturday + return daylightKala(context: context, slotsByWeekday: slots) + } + + static func getGulikaKala(context: CalculationContext) -> (start: Date, end: Date)? { + let slots = [7, 6, 5, 4, 3, 2, 1] // Sunday through Saturday + return daylightKala(context: context, slotsByWeekday: slots) + } - static func gulikaSlot(weekday: String) -> Int { - switch weekday { - case "Sunday": return 7 - case "Monday": return 6 - case "Tuesday": return 5 - case "Wednesday": return 4 - case "Thursday": return 3 - case "Friday": return 2 - case "Saturday": return 1 - default: return 1 + private static func daylightKala( + context: CalculationContext, + slotsByWeekday: [Int] + ) -> (start: Date, end: Date)? { + guard slotsByWeekday.count == 7, + let weekday = context.localDayComponents.weekday, + (1...7).contains(weekday), + let solar = getSunriseSunset(context: context) else { + return nil } + let slotLength = solar.sunset.timeIntervalSince(solar.sunrise) / 8.0 + let slot = slotsByWeekday[weekday - 1] + let start = solar.sunrise.addingTimeInterval(Double(slot - 1) * slotLength) + return (start: start, end: start.addingTimeInterval(slotLength)) } - /// Returns the 1-2 classically inauspicious Dur Muhurta windows for the given date. - /// Indices are 0-based into the 15 day muhurtas; table from Muhurta Chintamani. + /// Returns the classically inauspicious Dur Muhurta windows for the Vedic day. + /// Indices are zero-based within the 15 daylight or 15 night divisions. + /// Tuesday's second period is a night division; treating every weekday entry + /// as a daylight slot produces a plausible but materially wrong result. static func getDurMuhurta(context: CalculationContext) -> [(start: Date, end: Date, label: String)] { - guard let ss = getSunriseSunset(context: context) else { return [] } - let dayDuration = ss.sunset.timeIntervalSince(ss.sunrise) - let slotLen = dayDuration / 15.0 + guard let today = getSunriseSunset(context: context), + let tomorrow = getSunriseSunset(context: context.advancedByLocalDays(1)) else { + return [] + } + let daySlotLength = today.sunset.timeIntervalSince(today.sunrise) / 15.0 + let nightSlotLength = tomorrow.sunrise.timeIntervalSince(today.sunset) / 15.0 // Weekday from Calendar (1=Sun…7=Sat) let wd = context.localDayComponents.weekday ?? 1 - let slots: [Int] + let daySlots: [Int] + let nightSlots: [Int] switch wd { - case 1: slots = [3, 8] // Sunday: 4th (Vidha) + 9th (Kutupa) - case 2: slots = [6, 14] // Monday: 7th + 15th - case 3: slots = [0, 7] // Tuesday: 1st + 8th - case 4: slots = [3] // Wednesday: 4th (Rohita) - case 5: slots = [5, 9] // Thursday: 6th + 10th - case 6: slots = [2, 7] // Friday: 3rd + 8th - case 7: slots = [2, 9] // Saturday: 3rd + 10th - default: slots = [] + case 1: (daySlots, nightSlots) = ([13], []) + case 2: (daySlots, nightSlots) = ([8, 11], []) + case 3: (daySlots, nightSlots) = ([3], [6]) + case 4: (daySlots, nightSlots) = ([7], []) + case 5: (daySlots, nightSlots) = ([5, 11], []) + case 6: (daySlots, nightSlots) = ([3, 8], []) + case 7: (daySlots, nightSlots) = ([0, 1], []) + default: (daySlots, nightSlots) = ([], []) } - let labels = slots.enumerated().map { i, _ in i == 0 ? "1st" : "2nd" } - return zip(slots, labels).map { (idx, lbl) in - let start = ss.sunrise.addingTimeInterval(Double(idx) * slotLen) - let end = start.addingTimeInterval(slotLen) - return (start: start, end: end, label: lbl) + var periods = daySlots.map { index in + let start = today.sunrise.addingTimeInterval(Double(index) * daySlotLength) + return (start: start, end: start.addingTimeInterval(daySlotLength), label: "") + } + periods += nightSlots.map { index in + let start = today.sunset.addingTimeInterval(Double(index) * nightSlotLength) + return (start: start, end: start.addingTimeInterval(nightSlotLength), label: "") + } + periods.sort { $0.start < $1.start } + return periods.enumerated().map { index, period in + ( + start: period.start, + end: period.end, + label: periods.count == 1 ? "Dur Muhurta" : (index == 0 ? "1st" : "2nd") + ) } } @@ -675,7 +867,7 @@ enum CosmicEngine { )) } - // MARK: - Vedic Calendar Extended Info + // MARK: - Vedic Calendar Extended Info prototype (not surfaced) static func getVedicCalendarInfo(date: Date, latDeg: Double, lonDeg: Double, birthNakshatraIndex: Int = -1) -> VedicCalendarInfo { @@ -763,7 +955,7 @@ enum CosmicEngine { ) } - // MARK: - Nine Graha Positions (Schlyter low-precision algorithm) + // MARK: - Nine Graha Positions prototype (not surfaced; low precision) private enum PlanetID { case mercury, venus, mars, jupiter, saturn } diff --git a/ios/native/CosmicRituals/Engine/FestivalData.swift b/ios/native/CosmicRituals/Engine/FestivalData.swift index d1578a6..f0fe904 100644 --- a/ios/native/CosmicRituals/Engine/FestivalData.swift +++ b/ios/native/CosmicRituals/Engine/FestivalData.swift @@ -2,6 +2,10 @@ import Foundation // MARK: - Festival (lunisolar keyed) // +// PROTOTYPE — deliberately not routed into the shipping UI. The month estimator +// below does not yet implement Adhika/Kshaya months or observance precedence. +// See ACCURACY.md before extending or surfacing this file. +// // Festivals are keyed to (lunarMonth, tithiIndex) where: // lunarMonth: 1=Chaitra, 2=Vaishakha, 3=Jyeshtha, 4=Ashadha, // 5=Shravana, 6=Bhadrapada, 7=Ashvin, 8=Kartika, diff --git a/ios/native/CosmicRituals/Engine/PanchangModels.swift b/ios/native/CosmicRituals/Engine/PanchangModels.swift index d03f9ba..9b3f8c9 100644 --- a/ios/native/CosmicRituals/Engine/PanchangModels.swift +++ b/ios/native/CosmicRituals/Engine/PanchangModels.swift @@ -58,6 +58,57 @@ struct ZodiacSign: Equatable { static func fromIndex(_ i: Int) -> ZodiacSign { all[((i % 12) + 12) % 12] } } +// MARK: - Panchang Transitions + +enum PanchangLimbKind: String, CaseIterable, Codable, Sendable { + case tithi + case nakshatra + case yoga + case karana +} + +/// The next exact boundary for one changing Panchang limb. +/// +/// Daily labels alone are incomplete because a limb can change at any instant +/// between one sunrise and the next. Keeping the current and next names beside +/// the solved boundary makes the value self-describing for the app, exports, +/// widgets, and accessibility output. +struct PanchangTransition: Codable, Equatable, Sendable { + let kind: PanchangLimbKind + let currentName: String + let nextName: String + let endTime: Date +} + +struct PanchangTransitions: Codable, Equatable, Sendable { + let tithi: PanchangTransition? + let nakshatra: PanchangTransition? + let yoga: PanchangTransition? + let karana: PanchangTransition? + + static let unavailable = PanchangTransitions( + tithi: nil, + nakshatra: nil, + yoga: nil, + karana: nil + ) + + var chronological: [PanchangTransition] { + [tithi, nakshatra, yoga, karana] + .compactMap { $0 } + .sorted { $0.endTime < $1.endTime } + } + + func transition(for kind: PanchangLimbKind) -> PanchangTransition? { + switch kind { + case .tithi: return tithi + case .nakshatra: return nakshatra + case .yoga: return yoga + case .karana: return karana + } + } +} + // MARK: - Panchang (Five Limbs of the Vedic Day) struct Panchang: Codable { @@ -75,6 +126,7 @@ struct Panchang: Codable { let moonSignName: String let sunriseTime: Date? let sunsetTime: Date? + let transitions: PanchangTransitions static let tithiNames = [ "Pratipada","Dvitiya","Tritiya","Chaturthi","Panchami", @@ -104,6 +156,13 @@ struct Panchang: Codable { "Bava","Balava","Kaulava","Taitila","Garija","Vanija","Vishti", "Shakuni","Chatushpada","Naga","Kimstughna" ] + + func referenceDisclosure(in timeZone: TimeZone) -> String { + if let sunriseTime { + return "Panchang day begins at sunrise · \(sunriseTime.ritualShortTime(in: timeZone)) local time" + } + return "Sunrise unavailable · sampled at \(date.ritualShortTime(in: timeZone)) local time" + } } // MARK: - Nakshatra Result diff --git a/ios/native/CosmicRituals/Engine/PanchangPDFExporter.swift b/ios/native/CosmicRituals/Engine/PanchangPDFExporter.swift index a3c8b3e..183a8bb 100644 --- a/ios/native/CosmicRituals/Engine/PanchangPDFExporter.swift +++ b/ios/native/CosmicRituals/Engine/PanchangPDFExporter.swift @@ -47,19 +47,30 @@ enum PanchangPDFExporter { let bodyFont = UIFont.systemFont(ofSize: 13, weight: .regular) let labelFont = UIFont.systemFont(ofSize: 10, weight: .regular) - draw("✦ Cosmic Rituals", at: CGPoint(x: 36, y: 24), font: titleFont, color: gold) - - let dateStr = longDate(context.localNoon, timeZone: context.timeZone) - draw(dateStr, at: CGPoint(x: 36, y: 60), font: subtitleFont, color: .white) - draw("Vedic Panchang · noon reference · \(context.timeZoneIdentifier)", at: CGPoint(x: 36, y: 80), font: labelFont, color: subtle.withAlphaComponent(0.8)) - - // Compute panchang data + // Compute all values before drawing so the header and body share the + // same sunrise-based reference instant. let p = CosmicEngine.getPanchang(context: context) let nak = CosmicEngine.getMoonNakshatraPada(context: context) let sunNak = CosmicEngine.getSunNakshatra(context: context) let sunSignIdx = Int(sunNak.degree / 30.0) % 12 let ss = CosmicEngine.getSunriseSunset(context: context) - let tithiEnd = CosmicEngine.getTithiEndTime(context: context) + + func transitionSuffix(_ transition: PanchangTransition?) -> String { + transition.map { + let ending = $0.endTime.ritualTransitionLabel( + relativeTo: p.date, + in: context.timeZone + ) + return " · until \(ending), then \($0.nextName)" + } ?? "" + } + + draw("✦ Cosmic Rituals", at: CGPoint(x: 36, y: 24), font: titleFont, color: gold) + + let dateStr = longDate(context.localNoon, timeZone: context.timeZone) + draw(dateStr, at: CGPoint(x: 36, y: 60), font: subtitleFont, color: .white) + let referenceLabel = p.sunriseTime == nil ? "local-noon fallback" : "sunrise reference" + draw("Vedic Panchang · \(referenceLabel) · \(context.timeZoneIdentifier)", at: CGPoint(x: 36, y: 80), font: labelFont, color: subtle.withAlphaComponent(0.8)) var y: CGFloat = 140 @@ -70,15 +81,21 @@ enum PanchangPDFExporter { let limbs: [(String, String)] = [ ("Vara (Weekday)", p.weekdayName), ("Tithi (Lunar Day)", p.tithiName + (p.tithiIndex < 15 ? " · Shukla Paksha" : " · Krishna Paksha") - + (tithiEnd.map { " · ends \(shortTime($0, timeZone: context.timeZone))" } ?? "")), - ("Nakshatra", "\(p.nakshatraName) · Pada \(nak.pada) · Lord: \(nak.nakshatraLord.rawValue)"), - ("Yoga", p.yogaName), - ("Karana", p.karanaName), + + transitionSuffix(p.transitions.tithi)), + ("Nakshatra", "\(p.nakshatraName) · Pada \(nak.pada) · Lord: \(nak.nakshatraLord.rawValue)" + + transitionSuffix(p.transitions.nakshatra)), + ("Yoga", p.yogaName + transitionSuffix(p.transitions.yoga)), + ("Karana", p.karanaName + transitionSuffix(p.transitions.karana)), ] for (label, value) in limbs { draw(label, at: CGPoint(x: 48, y: y), font: labelFont, color: subtle) - draw(value, at: CGPoint(x: 200, y: y), font: bodyFont, color: .white) - y += 22 + let valueHeight = drawWrapped( + value, + in: CGRect(x: 200, y: y, width: pageRect.width - 236, height: 58), + font: bodyFont, + color: .white + ) + y += max(22, valueHeight + 6) } y += 12 @@ -110,10 +127,17 @@ enum PanchangPDFExporter { let muhurtas = CosmicEngine.getMuhurtas(context: context) let notable = muhurtas.filter { $0.quality == .excellent || $0.quality == .auspicious }.prefix(10) for m in notable { - let timeStr = "\(shortTime(m.startTime, timeZone: context.timeZone))–\(shortTime(m.endTime, timeZone: context.timeZone))" + let start = m.startTime.ritualTransitionLabel(relativeTo: p.date, in: context.timeZone) + let end = m.endTime.ritualTransitionLabel(relativeTo: p.date, in: context.timeZone) + let timeStr = "\(start)–\(end)" draw(m.quality.emoji + " " + m.name, at: CGPoint(x: 48, y: y), font: bodyFont, color: .white) - draw(timeStr, at: CGPoint(x: 300, y: y), font: labelFont, color: subtle) - y += 20 + let timeHeight = drawWrapped( + timeStr, + in: CGRect(x: 300, y: y, width: pageRect.width - 336, height: 38), + font: labelFont, + color: subtle + ) + y += max(20, timeHeight + 4) if y > pageRect.height - 80 { break } } @@ -147,6 +171,38 @@ enum PanchangPDFExporter { (text as NSString).draw(at: point, withAttributes: attrs) } + @discardableResult + private static func drawWrapped( + _ text: String, + in rect: CGRect, + font: UIFont, + color: UIColor + ) -> CGFloat { + let paragraph = NSMutableParagraphStyle() + paragraph.lineBreakMode = .byWordWrapping + paragraph.lineSpacing = 1 + let attrs: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: color, + .paragraphStyle: paragraph, + ] + let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading] + let bounds = (text as NSString).boundingRect( + with: CGSize(width: rect.width, height: rect.height), + options: options, + attributes: attrs, + context: nil + ) + let height = min(rect.height, ceil(bounds.height)) + (text as NSString).draw( + with: CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: height), + options: options, + attributes: attrs, + context: nil + ) + return height + } + private static func shortTime(_ date: Date, timeZone: TimeZone) -> String { let formatter = DateFormatter() formatter.dateStyle = .none diff --git a/ios/native/CosmicRituals/Theme/CosmicTheme.swift b/ios/native/CosmicRituals/Theme/CosmicTheme.swift index 2996756..9dd647a 100644 --- a/ios/native/CosmicRituals/Theme/CosmicTheme.swift +++ b/ios/native/CosmicRituals/Theme/CosmicTheme.swift @@ -262,7 +262,10 @@ struct CosmicStarfieldBackground: View { } private var darkBackground: some View { - TimelineView(.animation(minimumInterval: reduceMotion ? 60 : nil, paused: reduceMotion)) { ctx in + // Twinkling is deliberately capped at 20 fps. The motion is extremely + // slow, so display-refresh rendering adds battery/GPU cost without a + // visible quality benefit. Reduce Motion freezes the canvas entirely. + TimelineView(.animation(minimumInterval: 1.0 / 20.0, paused: reduceMotion)) { ctx in let t = ctx.date.timeIntervalSinceReferenceDate Canvas { gfx, size in // 1. Background gradient diff --git a/ios/native/CosmicRituals/Views/GrahaPositionsView.swift b/ios/native/CosmicRituals/Views/GrahaPositionsView.swift index 337df39..00c8d86 100644 --- a/ios/native/CosmicRituals/Views/GrahaPositionsView.swift +++ b/ios/native/CosmicRituals/Views/GrahaPositionsView.swift @@ -1,5 +1,8 @@ import SwiftUI +// Prototype view intentionally not routed into shipping navigation. The +// low-precision source model must meet ACCURACY.md's evidence gate first. + struct GrahaPositionsCard: View { let grahas: [GrahaPosition] @Environment(\.cosmicTheme) private var theme diff --git a/ios/native/CosmicRituals/Views/MonthlyCalendarView.swift b/ios/native/CosmicRituals/Views/MonthlyCalendarView.swift index 8632141..7beca0e 100644 --- a/ios/native/CosmicRituals/Views/MonthlyCalendarView.swift +++ b/ios/native/CosmicRituals/Views/MonthlyCalendarView.swift @@ -136,11 +136,10 @@ struct MonthlyCalendarView: View { private var legendCard: some View { CosmicGlassCard { VStack(alignment: .leading, spacing: 8) { - CosmicSectionHeader(title: "Quality Legend", icon: "circle.grid.2x2.fill") + CosmicSectionHeader(title: "Sunrise Snapshot Legend", icon: "circle.grid.2x2.fill") HStack(spacing: 16) { - legendItem(.green, "Auspicious yoga") - legendItem(.red, "Inauspicious yoga") - legendItem(.orange, "Mixed") + legendItem(.green, "Favourable yoga") + legendItem(.red, "Caution yoga") } HStack(spacing: 16) { legendItem(.purple, "Purnima / Ekadashi") @@ -187,7 +186,9 @@ struct MonthlyCalendarView: View { longitude: calculationContext.longitude, timeZoneIdentifier: calculationContext.timeZoneIdentifier ) - cache[key] = CosmicEngine.getPanchang(context: context) + // Month cells need the sunrise snapshot, not four sub-second boundary + // solves per day. Detailed transitions are computed only on the daily view. + cache[key] = CosmicEngine.getPanchang(context: context, includeTransitions: false) } panchangCache = cache } @@ -250,7 +251,7 @@ private struct DayCell: View { } .buttonStyle(.plain) .accessibilityLabel(date.ritualCompleteDate(in: calendar.timeZone)) - .accessibilityValue("\(panchang?.tithiName ?? "Panchang unavailable"), \(yogaDetail.isAuspicious ? "auspicious" : "caution")") + .accessibilityValue("At sunrise, \(panchang?.tithiName ?? "Panchang unavailable"), \(yogaDetail.isAuspicious ? "favourable yoga" : "caution yoga")") } private func shortTithi(_ idx: Int) -> String { diff --git a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift index e682712..36c1847 100644 --- a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift +++ b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift @@ -20,7 +20,7 @@ enum RitualExperienceMode: String, CaseIterable, Identifiable { var summary: String { switch self { case .ritualNow: - return "A glanceable local-noon reference" + return "A glanceable local-sunrise reference" case .vedicLedger: return "An editorial Vedic almanac" case .fiveLimbFocus: @@ -102,20 +102,20 @@ struct PanchangExperienceHome: View { let mode: RitualExperienceMode @Binding var selectedDate: Date let panchang: Panchang - let tithiEndTime: Date? let showTithiDetail: () -> Void let showYogaDetail: () -> Void let showKaranaDetail: () -> Void @Environment(\.cosmicTheme) private var theme @Environment(\.accessibilityReduceMotion) private var reduceMotion + @Environment(\.timeZone) private var timeZone var body: some View { VStack(spacing: 10) { HStack(spacing: 6) { Image(systemName: "clock.badge.info") .foregroundStyle(theme.primary) - Text(CalculationContext.dailySnapshotDisclosure) + Text(panchang.referenceDisclosure(in: timeZone)) .foregroundStyle(theme.semanticSecondaryText) Spacer(minLength: 0) } @@ -128,7 +128,6 @@ struct PanchangExperienceHome: View { RitualNowExperience( selectedDate: $selectedDate, panchang: panchang, - tithiEndTime: tithiEndTime, showTithiDetail: showTithiDetail, showYogaDetail: showYogaDetail, showKaranaDetail: showKaranaDetail @@ -137,7 +136,6 @@ struct PanchangExperienceHome: View { VedicLedgerExperience( selectedDate: $selectedDate, panchang: panchang, - tithiEndTime: tithiEndTime, showTithiDetail: showTithiDetail, showYogaDetail: showYogaDetail, showKaranaDetail: showKaranaDetail @@ -146,7 +144,6 @@ struct PanchangExperienceHome: View { FiveLimbFocusExperience( selectedDate: $selectedDate, panchang: panchang, - tithiEndTime: tithiEndTime, showTithiDetail: showTithiDetail, showYogaDetail: showYogaDetail, showKaranaDetail: showKaranaDetail @@ -162,12 +159,99 @@ struct PanchangExperienceHome: View { } } +// MARK: - Daily transition timeline + +struct PanchangTransitionTimeline: View { + let referenceDate: Date + let transitions: [PanchangTransition] + + @Environment(\.cosmicTheme) private var theme + @Environment(\.timeZone) private var timeZone + + private var calendar: Calendar { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = timeZone + return calendar + } + + var body: some View { + CosmicGlassCard(cornerRadius: 18) { + VStack(alignment: .leading, spacing: 10) { + CosmicSectionHeader(title: "Next Limb Changes", icon: "clock.arrow.circlepath") + + if transitions.isEmpty { + Label("Transition times are unavailable.", systemImage: "exclamationmark.circle") + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + } else { + ForEach(Array(transitions.enumerated()), id: \.element.kind) { index, transition in + HStack(spacing: 10) { + Image(systemName: symbol(for: transition.kind)) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(color(for: transition.kind)) + .frame(width: 24) + + VStack(alignment: .leading, spacing: 2) { + Text(transition.kind.rawValue.capitalized) + .font(.caption2.weight(.semibold)) + .foregroundStyle(theme.semanticSecondaryText) + Text("\(transition.currentName) → \(transition.nextName)") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + .lineLimit(1) + .minimumScaleFactor(0.78) + } + Spacer(minLength: 8) + Text(timeLabel(for: transition.endTime)) + .font(.caption.weight(.semibold)) + .monospacedDigit() + .multilineTextAlignment(.trailing) + .foregroundStyle(color(for: transition.kind)) + } + .accessibilityElement(children: .combine) + .accessibilityLabel( + "\(transition.kind.rawValue.capitalized), \(transition.currentName) changes to \(transition.nextName) at \(timeLabel(for: transition.endTime))" + ) + + if index < transitions.count - 1 { + Divider().overlay(theme.semanticDivider) + } + } + } + } + } + } + + private func timeLabel(for date: Date) -> String { + let time = date.ritualShortTime(in: timeZone) + guard !calendar.isDate(date, inSameDayAs: referenceDate) else { return time } + return "\(date.ritualDate(template: "EEE d MMM", in: timeZone))\n\(time)" + } + + private func symbol(for kind: PanchangLimbKind) -> String { + switch kind { + case .tithi: return "moon.fill" + case .nakshatra: return "sparkles" + case .yoga: return "circle.grid.cross.fill" + case .karana: return "divide.circle.fill" + } + } + + private func color(for kind: PanchangLimbKind) -> Color { + switch kind { + case .tithi: return .blue + case .nakshatra: return theme.tertiary + case .yoga: return .green + case .karana: return .orange + } + } +} + // MARK: - Daily Snapshot private struct RitualNowExperience: View { @Binding var selectedDate: Date let panchang: Panchang - let tithiEndTime: Date? let showTithiDetail: () -> Void let showYogaDetail: () -> Void let showKaranaDetail: () -> Void @@ -218,21 +302,22 @@ private struct RitualNowExperience: View { PanchangExperienceRow( symbol: "moon.fill", tint: .blue, label: "Tithi", supporting: "Lunar day", value: panchang.tithiName, - detail: tithiEndLabel, action: showTithiDetail + detail: transitionLabel(for: .tithi), action: showTithiDetail ) PanchangExperienceRow( symbol: "sparkles", tint: theme.tertiary, - label: "Nakshatra", supporting: "Lunar mansion", value: panchang.nakshatraName + label: "Nakshatra", supporting: "Lunar mansion", value: panchang.nakshatraName, + detail: transitionLabel(for: .nakshatra) ) PanchangExperienceRow( symbol: "circle.grid.cross.fill", tint: .green, label: "Yoga", supporting: "Union", value: panchang.yogaName, - action: showYogaDetail + detail: transitionLabel(for: .yoga), action: showYogaDetail ) PanchangExperienceRow( symbol: "divide.circle.fill", tint: .orange, label: "Karana", supporting: "Half-tithi", value: panchang.karanaName, - isLast: true, action: showKaranaDetail + detail: transitionLabel(for: .karana), isLast: true, action: showKaranaDetail ) } } @@ -264,15 +349,17 @@ private struct RitualNowExperience: View { } } - private var tithiEndLabel: String { - guard let tithiEndTime else { return "time unavailable" } - return "ends \(tithiEndTime.ritualShortTime(in: timeZone))" + private func transitionLabel(for kind: PanchangLimbKind) -> String { + guard let transition = panchang.transitions.transition(for: kind) else { + return "transition unavailable" + } + return "until \(transition.endTime.ritualTransitionLabel(relativeTo: panchang.date, in: timeZone))" } private var transitionDetail: String { - guard let tithiEndTime else { return "Transition time unavailable" } - let nextIndex = (panchang.tithiIndex + 1) % Panchang.tithiNames.count - return "\(Panchang.tithiNames[nextIndex]) begins at \(tithiEndTime.ritualShortTime(in: timeZone))" + guard let transition = panchang.transitions.tithi else { return "Transition time unavailable" } + let start = transition.endTime.ritualTransitionLabel(relativeTo: panchang.date, in: timeZone) + return "\(transition.nextName) begins at \(start)" } } @@ -281,7 +368,6 @@ private struct RitualNowExperience: View { private struct VedicLedgerExperience: View { @Binding var selectedDate: Date let panchang: Panchang - let tithiEndTime: Date? let showTithiDetail: () -> Void let showYogaDetail: () -> Void let showKaranaDetail: () -> Void @@ -349,8 +435,12 @@ private struct VedicLedgerExperience: View { Image(systemName: "moon.stars.fill") .foregroundStyle(theme.secondary) Divider().frame(height: 30) - Text(tithiEndTime.map { - "\(panchang.tithiName) ends \($0.ritualShortTime(in: timeZone))" + Text(panchang.transitions.tithi.map { + let ending = $0.endTime.ritualTransitionLabel( + relativeTo: panchang.date, + in: timeZone + ) + return "\(panchang.tithiName) ends \(ending)" } ?? "Open \(panchang.tithiName) details") .font(.system(.headline, design: .serif)) Spacer() @@ -390,7 +480,6 @@ private enum FocusLimb: String, CaseIterable, Identifiable { private struct FiveLimbFocusExperience: View { @Binding var selectedDate: Date let panchang: Panchang - let tithiEndTime: Date? let showTithiDetail: () -> Void let showYogaDetail: () -> Void let showKaranaDetail: () -> Void @@ -558,8 +647,17 @@ private struct FiveLimbFocusExperience: View { } private func supportingText(for limb: FocusLimb) -> String? { - guard limb == .tithi, let tithiEndTime else { return nil } - return "ends \(tithiEndTime.ritualShortTime(in: timeZone))" + let kind: PanchangLimbKind? + switch limb { + case .vara: kind = nil + case .tithi: kind = .tithi + case .nakshatra: kind = .nakshatra + case .yoga: kind = .yoga + case .karana: kind = .karana + } + guard let kind, let transition = panchang.transitions.transition(for: kind) else { return nil } + let ending = transition.endTime.ritualTransitionLabel(relativeTo: panchang.date, in: timeZone) + return "until \(ending) · then \(transition.nextName)" } private func color(for limb: FocusLimb) -> Color { @@ -613,12 +711,14 @@ struct RitualDestinationBar: View { } } } + .frame(maxWidth: .infinity) .padding(7) .glassEffect( .regular, in: RoundedRectangle(cornerRadius: 28, style: .continuous) ) } + .frame(maxWidth: .infinity) .accessibilityElement(children: .contain) .accessibilityLabel("Panchang destinations") } @@ -653,6 +753,7 @@ struct RitualDestinationBar: View { .frame(minHeight: usesIconOnlyPresentation ? 52 : 48) .contentShape(Rectangle()) } + .frame(maxWidth: .infinity) .accessibilityLabel(destination.title) .accessibilityValue(selection == index ? "Selected" : "Not selected") .accessibilityHint("Shows the \(destination.title) destination") diff --git a/ios/native/CosmicRituals/Views/PanchangView.swift b/ios/native/CosmicRituals/Views/PanchangView.swift index ca6092c..243f672 100644 --- a/ios/native/CosmicRituals/Views/PanchangView.swift +++ b/ios/native/CosmicRituals/Views/PanchangView.swift @@ -2,13 +2,13 @@ import SwiftUI struct PanchangView: View { @State private var selectedDate = Date() + @State private var panchangSnapshot: (context: CalculationContext, value: Panchang)? @State private var muhurtas: [Muhurta] = [] @State private var selectedTab = 0 @State private var detailMuhurta: Muhurta? @State private var sunriseSunset: (Date, Date)? @State private var choghadiya: [Choghadiya] = [] @State private var hora: [Hora] = [] - @State private var tithiEndTime: Date? @State private var showThemePicker = false @State private var showLocationPicker = false @State private var selectedActivity: String = "Business" @@ -111,15 +111,15 @@ struct PanchangView: View { RitualLocationPicker(manager: locationManager) } .sheet(isPresented: $showTithiDetail) { - let p = CosmicEngine.getPanchang(context: calculationContext) + let p = resolvedPanchang TithiDetailSheet(tithiIndex: p.tithiIndex) } .sheet(isPresented: $showYogaDetail) { - let p = CosmicEngine.getPanchang(context: calculationContext) + let p = resolvedPanchang YogaDetailSheet(yogaIndex: p.yogaIndex) } .sheet(isPresented: $showKaranaDetail) { - let p = CosmicEngine.getPanchang(context: calculationContext) + let p = resolvedPanchang KaranaDetailSheet(karanaIndex: p.karanaIndex) } } @@ -130,10 +130,31 @@ struct PanchangView: View { } } .onChange(of: selectedDate) { _, _ in recompute() } - .onChange(of: locationManager.activeLocation) { _, _ in recompute() } + .onChange(of: locationManager.activeLocation) { oldLocation, newLocation in + let sourceTimeZone = TimeZone(identifier: oldLocation.timeZoneIdentifier) ?? .gmt + let targetTimeZone = TimeZone(identifier: newLocation.timeZoneIdentifier) ?? .gmt + let translatedDate = selectedDate.ritualCivilDay( + preservingDateFrom: sourceTimeZone, + into: targetTimeZone + ) + if translatedDate == selectedDate { + recompute() + } else { + selectedDate = translatedDate + } + } .onReceive(Timer.publish(every: 60, on: .main, in: .common).autoconnect()) { date in now = date // tick forces a re-render so isCurrent badges stay accurate } + .task(id: calculationContext) { + let context = calculationContext + panchangPDF = nil + let data = await Task.detached(priority: .utility) { + PanchangPDFExporter.generatePDF(context: context) + }.value + guard !Task.isCancelled, calculationContext == context else { return } + panchangPDF = PanchangPDF(data: data) + } .environment(\.timeZone, calculationContext.timeZone) } @@ -147,18 +168,25 @@ struct PanchangView: View { locationManager.calculationContext(for: selectedDate) } + private var resolvedPanchang: Panchang { + let context = calculationContext + if let snapshot = panchangSnapshot, snapshot.context == context { + return snapshot.value + } + return CosmicEngine.getPanchang(context: context) + } + // MARK: - Panchang five limbs private var panchangScrollView: some View { ScrollView { VStack(spacing: 16) { - let p = CosmicEngine.getPanchang(context: calculationContext) + let p = resolvedPanchang PanchangExperienceHome( mode: activeExperience, selectedDate: $selectedDate, panchang: p, - tithiEndTime: tithiEndTime, showTithiDetail: { showTithiDetail = true }, showYogaDetail: { showYogaDetail = true }, showKaranaDetail: { showKaranaDetail = true } @@ -166,6 +194,12 @@ struct PanchangView: View { .padding(.horizontal) .padding(.top, 12) + PanchangTransitionTimeline( + referenceDate: p.date, + transitions: p.transitions.chronological + ) + .padding(.horizontal) + // Solar values are shown only when real rise/set events exist. CosmicGlassCard { VStack(alignment: .leading, spacing: 10) { @@ -177,26 +211,30 @@ struct PanchangView: View { solarTimeCell(icon: "sunset.fill", color: .red, label: "Sunset", time: set) } Divider() - let brahma = rise.addingTimeInterval(-96 * 60) - let brahmaEnd = rise.addingTimeInterval(-48 * 60) - HStack(spacing: 6) { - CosmicIcon(name: "moon.zzz.fill", size: 13, color: .purple) - Text("Brahma Muhurta") - .font(.caption).foregroundStyle(.secondary) - Spacer() - Text("\(shortTime(brahma)) – \(shortTime(brahmaEnd))") - .font(.caption.bold()).foregroundStyle(.purple) + if let brahma = CosmicEngine.getBrahmaMuhurta(context: calculationContext) { + HStack(spacing: 6) { + CosmicIcon(name: "moon.zzz.fill", size: 13, color: .purple) + Text("Brahma Muhurta") + .font(.caption).foregroundStyle(.secondary) + Spacer() + Text("\(shortTime(brahma.start)) – \(shortTime(brahma.end))") + .font(.caption.bold()).foregroundStyle(.purple) + } } - let noon = rise.addingTimeInterval(rise.distance(to: set) / 2) - let abhijit = noon.addingTimeInterval(-24 * 60) - let abhijitEnd = noon.addingTimeInterval(24 * 60) - HStack(spacing: 6) { - CosmicIcon(name: "sun.max.circle.fill", size: 13, color: .yellow) - Text("Abhijit Muhurta") - .font(.caption).foregroundStyle(.secondary) - Spacer() - Text("\(shortTime(abhijit)) – \(shortTime(abhijitEnd))") - .font(.caption.bold()).foregroundStyle(.yellow) + if let abhijit = CosmicEngine.getAbhijitMuhurta(context: calculationContext) { + HStack(spacing: 6) { + CosmicIcon(name: "sun.max.circle.fill", size: 13, color: .yellow) + Text("Abhijit Muhurta") + .font(.caption).foregroundStyle(.secondary) + Spacer() + Text("\(shortTime(abhijit.start)) – \(shortTime(abhijit.end))") + .font(.caption.bold()).foregroundStyle(.yellow) + } + } else if p.weekdayName == "Wednesday" { + Label("Abhijit is not observed as auspicious on Wednesday.", + systemImage: "sun.max.circle") + .font(.caption) + .foregroundStyle(.secondary) } } else { Label("Sunrise-based schedules are unavailable for this latitude and date.", @@ -256,7 +294,7 @@ struct PanchangView: View { } .padding(.horizontal) - auspiciousKalaCard(weekday: p.weekdayName) + inauspiciousKalaCard } .padding(.vertical) @@ -416,7 +454,7 @@ struct PanchangView: View { // MARK: - Share private var panchangSummary: String { - let p = CosmicEngine.getPanchang(context: calculationContext) + let p = resolvedPanchang let nak = CosmicEngine.getMoonNakshatraPada(context: calculationContext) let sunNak = CosmicEngine.getSunNakshatra(context: calculationContext) let sunSignIdx = Int(sunNak.degree / 30.0) % 12 @@ -424,14 +462,17 @@ struct PanchangView: View { var lines: [String] = [ "🌟 Panchang — \(df)", - "Reference: 12:00 PM local time", + "Reference: \(p.referenceDisclosure(in: calculationContext.timeZone))", "", "✦ Vara (Weekday): \(p.weekdayName)", "✦ Tithi: \(p.tithiIndex < 15 ? "Shukla" : "Krishna") \(p.tithiName)" + - (tithiEndTime.map { " (ends \(shortTime($0)))" } ?? ""), - "✦ Nakshatra: \(p.nakshatraName) (pada \(nak.pada))", - "✦ Yoga: \(p.yogaName)", - "✦ Karana: \(p.karanaName)", + (p.transitions.tithi.map { " (ends \($0.endTime.ritualTransitionLabel(relativeTo: p.date, in: calculationContext.timeZone)))" } ?? ""), + "✦ Nakshatra: \(p.nakshatraName) (pada \(nak.pada))" + + (p.transitions.nakshatra.map { " (ends \($0.endTime.ritualTransitionLabel(relativeTo: p.date, in: calculationContext.timeZone)))" } ?? ""), + "✦ Yoga: \(p.yogaName)" + + (p.transitions.yoga.map { " (ends \($0.endTime.ritualTransitionLabel(relativeTo: p.date, in: calculationContext.timeZone)))" } ?? ""), + "✦ Karana: \(p.karanaName)" + + (p.transitions.karana.map { " (ends \($0.endTime.ritualTransitionLabel(relativeTo: p.date, in: calculationContext.timeZone)))" } ?? ""), "", "☀ Surya Rashi: \(ZodiacSign.fromIndex(sunSignIdx).name)", "☀ Surya Nakshatra: \(sunNak.nakshatraName)", @@ -579,24 +620,26 @@ struct PanchangView: View { } } - @ViewBuilder - private func auspiciousKalaCard(weekday: String) -> some View { + private var inauspiciousKalaCard: some View { let durMuhurtas = CosmicEngine.getDurMuhurta(context: calculationContext) + let rahuKala = CosmicEngine.getRahuKala(context: calculationContext) + let yamaganda = CosmicEngine.getYamaganda(context: calculationContext) + let gulikaKala = CosmicEngine.getGulikaKala(context: calculationContext) - CosmicGlassCard { + return CosmicGlassCard { VStack(alignment: .leading, spacing: 10) { CosmicSectionHeader(title: "Inauspicious Periods", icon: "exclamationmark.triangle") HStack(spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text("Rahu Kala").font(.caption2).foregroundStyle(.tertiary) - Text(kalaTimeString(slot: rahuKalaSlot(weekday), fallback: staticRahuKala(weekday))) + Text(kalaTimeString(rahuKala)) .font(.subheadline.bold()).foregroundStyle(.red) Text("Avoid new beginnings").font(.caption2).foregroundStyle(.secondary) } Divider() VStack(alignment: .leading, spacing: 4) { Text("Yamaganda").font(.caption2).foregroundStyle(.tertiary) - Text(kalaTimeString(slot: yamaGandaSlot(weekday), fallback: staticYamaGanda(weekday))) + Text(kalaTimeString(yamaganda)) .font(.subheadline.bold()).foregroundStyle(.orange) Text("Inauspicious period").font(.caption2).foregroundStyle(.secondary) } @@ -604,8 +647,7 @@ struct PanchangView: View { Divider() VStack(alignment: .leading, spacing: 4) { Text("Gulika Kala").font(.caption2).foregroundStyle(.tertiary) - Text(kalaTimeString(slot: CosmicEngine.gulikaSlot(weekday: weekday), - fallback: "Check calendar")) + Text(kalaTimeString(gulikaKala)) .font(.subheadline.bold()).foregroundStyle(.purple) Text("Son of Saturn — highly inauspicious").font(.caption2).foregroundStyle(.secondary) } @@ -754,12 +796,11 @@ struct PanchangView: View { private func recompute() { let context = calculationContext + panchangSnapshot = (context, CosmicEngine.getPanchang(context: context)) muhurtas = CosmicEngine.getMuhurtas(context: context) sunriseSunset = CosmicEngine.getSunriseSunset(context: context) choghadiya = CosmicEngine.getChoghadiya(context: context) hora = CosmicEngine.getHora(context: context) - tithiEndTime = CosmicEngine.getTithiEndTime(context: context) - panchangPDF = PanchangPDF(data: PanchangPDFExporter.generatePDF(context: context)) } private func solarTimeCell(icon: String, color: Color, label: String, time: Date) -> some View { @@ -773,11 +814,7 @@ struct PanchangView: View { } private func shortTime(_ date: Date) -> String { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - formatter.timeZone = calculationContext.timeZone - return formatter.string(from: date) + date.ritualShortTime(in: calculationContext.timeZone) } private func qualityColor(_ q: MuhurtaQuality) -> Color { @@ -789,66 +826,9 @@ struct PanchangView: View { } } - // Rahu Kala: 1-based slot index within the 8 equal day divisions - private func rahuKalaSlot(_ weekday: String) -> Int { - switch weekday { - case "Sunday": return 8 - case "Monday": return 2 - case "Tuesday": return 7 - case "Wednesday": return 5 - case "Thursday": return 6 - case "Friday": return 4 - case "Saturday": return 3 - default: return 1 - } - } - - // Yamaganda: 1-based slot index within the 8 equal day divisions - private func yamaGandaSlot(_ weekday: String) -> Int { - switch weekday { - case "Sunday": return 5 - case "Monday": return 4 - case "Tuesday": return 3 - case "Wednesday": return 2 - case "Thursday": return 1 - case "Friday": return 7 - case "Saturday": return 6 - default: return 1 - } - } - - private func kalaTimeString(slot: Int, fallback: String) -> String { - guard let (rise, set) = sunriseSunset else { return "Unavailable" } - let slotDuration = set.timeIntervalSince(rise) / 8.0 - let start = rise.addingTimeInterval(Double(slot - 1) * slotDuration) - let end = start.addingTimeInterval(slotDuration) - return "\(shortTime(start)) – \(shortTime(end))" - } - - private func staticRahuKala(_ weekday: String) -> String { - switch weekday { - case "Sunday": return "4:30–6:00 PM" - case "Monday": return "7:30–9:00 AM" - case "Tuesday": return "3:00–4:30 PM" - case "Wednesday": return "12:00–1:30 PM" - case "Thursday": return "1:30–3:00 PM" - case "Friday": return "10:30 AM–12:00 PM" - case "Saturday": return "9:00–10:30 AM" - default: return "Check calendar" - } - } - - private func staticYamaGanda(_ weekday: String) -> String { - switch weekday { - case "Sunday": return "12:00–1:30 PM" - case "Monday": return "10:30 AM–12:00 PM" - case "Tuesday": return "9:00–10:30 AM" - case "Wednesday": return "7:30–9:00 AM" - case "Thursday": return "6:00–7:30 AM" - case "Friday": return "3:00–4:30 PM" - case "Saturday": return "1:30–3:00 PM" - default: return "Check calendar" - } + private func kalaTimeString(_ period: (start: Date, end: Date)?) -> String { + guard let period else { return "Unavailable" } + return "\(shortTime(period.start)) – \(shortTime(period.end))" } } @@ -890,6 +870,37 @@ private struct ThemePickerSheet: View { Divider().overlay(theme.semanticDivider).padding(.horizontal) + VStack(alignment: .leading, spacing: 12) { + Label("Calculation Integrity", systemImage: "checkmark.shield.fill") + .font(.caption.bold()) + .foregroundStyle(theme.semanticPrimaryText) + + integrityRow( + title: "Daily reference", + detail: "Selected location's sunrise; local noon only when no sunrise exists" + ) + integrityRow( + title: "Five limbs", + detail: "Meeus Sun and Moon series · Lahiri (Chitra Paksha) ayanamsha" + ) + integrityRow( + title: "Transitions", + detail: "Tithi, Nakshatra, Yoga, and Karana solved independently" + ) + integrityRow( + title: "Privacy", + detail: "All calculations and the city catalog stay on device" + ) + + Text("The numerical boundary solver has sub-second resolution. The compact offline ephemeris is checked against published civil-time fixtures with a conservative ±12 minute validation envelope. Regional observance rules can differ; confirm ceremonial timings with a qualified practitioner.") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal) + + Divider().overlay(theme.semanticDivider).padding(.horizontal) + // Appearance Text("Color Theme").font(.caption.bold()).foregroundStyle(theme.semanticSecondaryText) .padding(.horizontal) @@ -972,6 +983,19 @@ private struct ThemePickerSheet: View { } .buttonStyle(.plain) } + + private func integrityRow(title: String, detail: String) -> some View { + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.caption.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + Text(detail) + .font(.caption2) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + } + .accessibilityElement(children: .combine) + } } // MARK: - Supporting Views diff --git a/ios/native/CosmicRituals/Views/RitualLocationViews.swift b/ios/native/CosmicRituals/Views/RitualLocationViews.swift index aeb5e47..ba350d6 100644 --- a/ios/native/CosmicRituals/Views/RitualLocationViews.swift +++ b/ios/native/CosmicRituals/Views/RitualLocationViews.swift @@ -55,6 +55,7 @@ struct LocationContextBar: View { .font(.caption.weight(.bold)) .foregroundStyle(theme.semanticSecondaryText) } + .frame(maxWidth: .infinity) .padding(.horizontal, 16) .padding(.vertical, 9) .background(theme.surface.opacity(theme.isLight ? 0.96 : 0.88)) @@ -64,6 +65,7 @@ struct LocationContextBar: View { .frame(height: 0.5) } } + .frame(maxWidth: .infinity) .buttonStyle(.plain) .accessibilityLabel("Calculation location") .accessibilityValue( diff --git a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift index 0361ac6..0d5fb1d 100644 --- a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift +++ b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift @@ -96,6 +96,77 @@ final class CosmicEngineTests: XCTestCase { XCTAssertTrue(CosmicEngine.getChoghadiya(context: svalbard).isEmpty) XCTAssertTrue(CosmicEngine.getHora(context: svalbard).isEmpty) XCTAssertTrue(CosmicEngine.getDurMuhurta(context: svalbard).isEmpty) + XCTAssertNil(CosmicEngine.getRahuKala(context: svalbard)) + XCTAssertNil(CosmicEngine.getYamaganda(context: svalbard)) + XCTAssertNil(CosmicEngine.getGulikaKala(context: svalbard)) + } + + func testPublishedFridayDurMuhurtaUsesFourthAndNinthDaySegments() throws { + // Drik Panchang, Mumbai, 2026-07-24: 08:49–09:42 and 13:11–14:03. + let mumbai = context(2026, 7, 24, latitude: 19.0760, longitude: 72.8777, timeZone: "Asia/Kolkata") + let periods = CosmicEngine.getDurMuhurta(context: mumbai) + XCTAssertEqual(periods.count, 2) + try assertLocalInterval(periods[0], startHour: 8, startMinute: 49, endHour: 9, endMinute: 42, + context: mumbai, toleranceMinutes: 12) + try assertLocalInterval(periods[1], startHour: 13, startMinute: 11, endHour: 14, endMinute: 3, + context: mumbai, toleranceMinutes: 12) + } + + func testPublishedFridayRahuYamagandaAndGulikaKalas() throws { + // Drik Panchang, Mumbai, 2026-07-24: Rahu 11:07–12:45, + // Yamaganda 16:01–17:39, and Gulika 07:51–09:29. + let mumbai = context(2026, 7, 24, latitude: 19.0760, longitude: 72.8777, timeZone: "Asia/Kolkata") + let rahu = try XCTUnwrap(CosmicEngine.getRahuKala(context: mumbai)) + let yamaganda = try XCTUnwrap(CosmicEngine.getYamaganda(context: mumbai)) + let gulika = try XCTUnwrap(CosmicEngine.getGulikaKala(context: mumbai)) + + try assertLocalInterval( + (start: rahu.start, end: rahu.end, label: "Rahu Kala"), + startHour: 11, startMinute: 7, endHour: 12, endMinute: 45, + context: mumbai, toleranceMinutes: 12 + ) + try assertLocalInterval( + (start: yamaganda.start, end: yamaganda.end, label: "Yamaganda"), + startHour: 16, startMinute: 1, endHour: 17, endMinute: 39, + context: mumbai, toleranceMinutes: 12 + ) + try assertLocalInterval( + (start: gulika.start, end: gulika.end, label: "Gulika Kala"), + startHour: 7, startMinute: 51, endHour: 9, endMinute: 29, + context: mumbai, toleranceMinutes: 12 + ) + } + + func testPublishedTuesdayDurMuhurtaIncludesNightPeriod() throws { + // Drik Panchang, Hyderabad, 2026-06-23: 08:21–09:14 and 23:14–23:57. + let hyderabad = context(2026, 6, 23, latitude: 17.3850, longitude: 78.4867, timeZone: "Asia/Kolkata") + let periods = CosmicEngine.getDurMuhurta(context: hyderabad) + XCTAssertEqual(periods.count, 2) + try assertLocalInterval(periods[0], startHour: 8, startMinute: 21, endHour: 9, endMinute: 14, + context: hyderabad, toleranceMinutes: 12) + try assertLocalInterval(periods[1], startHour: 23, startMinute: 14, endHour: 23, endMinute: 57, + context: hyderabad, toleranceMinutes: 12) + } + + func testPublishedFridayAbhijitScalesWithTheLocalDay() throws { + // Drik Panchang, Mumbai, 2026-07-24: 12:19–13:11. + let mumbai = context(2026, 7, 24, latitude: 19.0760, longitude: 72.8777, timeZone: "Asia/Kolkata") + let period = try XCTUnwrap(CosmicEngine.getAbhijitMuhurta(context: mumbai)) + try assertLocalInterval( + (start: period.start, end: period.end, label: "Abhijit"), + startHour: 12, startMinute: 19, endHour: 13, endMinute: 11, + context: mumbai, toleranceMinutes: 12 + ) + } + + func testAbhijitIsNotPresentedAsAuspiciousOnWednesday() { + let bartlesville = context( + 2026, 7, 22, + latitude: 36.7473, + longitude: -95.9808, + timeZone: "America/Chicago" + ) + XCTAssertNil(CosmicEngine.getAbhijitMuhurta(context: bartlesville)) } func testKaranaSequenceHasOneOpeningFixedKaranaAndThreeClosingFixedKaranas() { @@ -151,6 +222,149 @@ final class CosmicEngineTests: XCTestCase { XCTAssertEqual(CosmicEngine.getPanchang(context: tokyo).weekdayName, "Friday") } + func testDailyPanchangUsesLocalSunriseAndCarriesSolarTimes() throws { + let delhi = context(2026, 7, 24, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata") + let solar = try XCTUnwrap(CosmicEngine.getSunriseSunset(context: delhi)) + let panchang = CosmicEngine.getPanchang(context: delhi) + + XCTAssertEqual(panchang.date.timeIntervalSince(solar.sunrise), 0, accuracy: 0.001) + XCTAssertEqual(panchang.sunriseTime, solar.sunrise) + XCTAssertEqual(panchang.sunsetTime, solar.sunset) + XCTAssertTrue(panchang.referenceDisclosure(in: delhi.timeZone).localizedCaseInsensitiveContains("sunrise")) + } + + func testPublishedParisPanchangTransitionsStayWithinCivilTimeTolerance() throws { + // Independent reference: Drik Panchang for Paris, 2026-07-21. + // https://www.drikpanchang.com/ — published values: sunrise 06:10, + // Ashtami until 01:46 Jul 22, Chitra until 17:19, Siddha until 14:55, + // and Vishti until 13:04. We allow 12 minutes because the app's compact + // offline ephemeris intentionally omits the larger JPL/Swiss data files. + let paris = context(2026, 7, 21, latitude: 48.8566, longitude: 2.3522, timeZone: "Europe/Paris") + let panchang = CosmicEngine.getPanchang(context: paris) + + XCTAssertEqual(panchang.tithiName, "Ashtami") + XCTAssertEqual(panchang.nakshatraName, "Chitra") + XCTAssertEqual(panchang.yogaName, "Siddha") + XCTAssertEqual(panchang.karanaName, "Vishti") + + try assertLocalTransition( + panchang.transitions.tithi, + current: "Ashtami", next: "Navami", + year: 2026, month: 7, day: 22, hour: 1, minute: 46, + context: paris, toleranceMinutes: 12 + ) + try assertLocalTransition( + panchang.transitions.nakshatra, + current: "Chitra", next: "Swati", + year: 2026, month: 7, day: 21, hour: 17, minute: 19, + context: paris, toleranceMinutes: 12 + ) + try assertLocalTransition( + panchang.transitions.yoga, + current: "Siddha", next: "Sadhya", + year: 2026, month: 7, day: 21, hour: 14, minute: 55, + context: paris, toleranceMinutes: 12 + ) + try assertLocalTransition( + panchang.transitions.karana, + current: "Vishti", next: "Bava", + year: 2026, month: 7, day: 21, hour: 13, minute: 4, + context: paris, toleranceMinutes: 12 + ) + } + + func testEverySolvedLimbBoundaryChangesToItsDeclaredNextValue() throws { + let contexts = [ + context(2026, 1, 15, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata"), + context(2026, 4, 9, latitude: -33.8688, longitude: 151.2093, timeZone: "Australia/Sydney"), + context(2026, 7, 21, latitude: 48.8566, longitude: 2.3522, timeZone: "Europe/Paris"), + context(2026, 11, 3, latitude: 37.4323, longitude: -121.8996, timeZone: "America/Los_Angeles"), + ] + + for calculationContext in contexts { + let daily = CosmicEngine.getPanchang(context: calculationContext) + XCTAssertEqual(daily.transitions.chronological.count, PanchangLimbKind.allCases.count) + + for transition in daily.transitions.chronological { + let before = CosmicEngine.getPanchang( + date: transition.endTime.addingTimeInterval(-60), + timezoneIdentifier: calculationContext.timeZoneIdentifier + ) + let after = CosmicEngine.getPanchang( + date: transition.endTime.addingTimeInterval(60), + timezoneIdentifier: calculationContext.timeZoneIdentifier + ) + XCTAssertEqual( + limbName(transition.kind, in: before), + transition.currentName, + "\(calculationContext.timeZoneIdentifier) \(transition.kind.rawValue) before boundary" + ) + XCTAssertEqual( + limbName(transition.kind, in: after), + transition.nextName, + "\(calculationContext.timeZoneIdentifier) \(transition.kind.rawValue) after boundary" + ) + } + } + } + + func testPolarFallbackIsExplicitAndDoesNotInventSunrise() { + let svalbard = context(2026, 6, 21, latitude: 78.2232, longitude: 15.6469, timeZone: "Arctic/Longyearbyen") + let panchang = CosmicEngine.getPanchang(context: svalbard) + + XCTAssertNil(panchang.sunriseTime) + XCTAssertNil(panchang.sunsetTime) + XCTAssertEqual( + svalbard.calendar.component(.hour, from: panchang.date), + CalculationContext.polarFallbackReferenceHour + ) + XCTAssertTrue(panchang.referenceDisclosure(in: svalbard.timeZone).localizedCaseInsensitiveContains("unavailable")) + } + + func testMonthCellSnapshotSkipsDetailedBoundaryWork() { + let delhi = context(2026, 7, 24, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata") + let compact = CosmicEngine.getPanchang(context: delhi, includeTransitions: false) + + XCTAssertTrue(compact.transitions.chronological.isEmpty) + XCTAssertNotNil(compact.sunriseTime) + } + + func testNextDayTransitionLabelIncludesCivilDateAndSameDayLabelDoesNot() throws { + let paris = context(2026, 7, 21, latitude: 48.8566, longitude: 2.3522, timeZone: "Europe/Paris") + let panchang = CosmicEngine.getPanchang(context: paris) + let tithi = try XCTUnwrap(panchang.transitions.tithi) + let nakshatra = try XCTUnwrap(panchang.transitions.nakshatra) + let locale = Locale(identifier: "en_US_POSIX") + + let nextDay = tithi.endTime.ritualTransitionLabel( + relativeTo: panchang.date, + in: paris.timeZone, + locale: locale + ) + let sameDay = nakshatra.endTime.ritualTransitionLabel( + relativeTo: panchang.date, + in: paris.timeZone, + locale: locale + ) + + XCTAssertTrue(nextDay.contains("22")) + XCTAssertTrue(nextDay.contains("Jul")) + XCTAssertFalse(sameDay.contains("Jul")) + } + + func testPanchangPDFExporterProducesAValidNonEmptyPDF() { + let delhi = context(2026, 7, 24, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata") + let pdf = PanchangPDFExporter.generatePDF(context: delhi) + + XCTAssertTrue(pdf.starts(with: Data("%PDF".utf8))) + XCTAssertGreaterThan(pdf.count, 5_000) + + let renderedArtifact = XCTAttachment(data: pdf, uniformTypeIdentifier: "com.adobe.pdf") + renderedArtifact.name = "Cosmic-Rituals-Panchang-QA" + renderedArtifact.lifetime = .keepAlways + add(renderedArtifact) + } + func testCompleteOfflineCatalogCountAndGlobalSearch() throws { let cities = try Self.loadedCatalog.get() XCTAssertEqual(cities.count, WorldCityCatalog.expectedCatalogCount) @@ -287,12 +501,14 @@ final class CosmicEngineTests: XCTestCase { )) } - func testDailySnapshotNamingMatchesNoonReferenceSemantics() { + func testDailySnapshotNamingMatchesSunriseReferenceSemantics() throws { let delhi = context(2026, 7, 24, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata") - XCTAssertEqual(delhi.calendar.component(.hour, from: delhi.localNoon), CalculationContext.dailySnapshotReferenceHour) + let panchang = CosmicEngine.getPanchang(context: delhi) + let sunrise = try XCTUnwrap(CosmicEngine.getSunriseSunset(context: delhi)?.sunrise) + XCTAssertEqual(panchang.date.timeIntervalSince(sunrise), 0, accuracy: 0.001) XCTAssertEqual(RitualExperienceMode.ritualNow.displayName, "Daily Snapshot") - XCTAssertTrue(RitualExperienceMode.ritualNow.summary.localizedCaseInsensitiveContains("noon")) - XCTAssertTrue(CalculationContext.dailySnapshotDisclosure.localizedCaseInsensitiveContains("12:00 PM")) + XCTAssertTrue(RitualExperienceMode.ritualNow.summary.localizedCaseInsensitiveContains("sunrise")) + XCTAssertTrue(panchang.referenceDisclosure(in: delhi.timeZone).localizedCaseInsensitiveContains("sunrise")) } func testAccessibilityLargeDestinationLayoutUsesIconsWithoutLosingLabels() { @@ -356,6 +572,28 @@ final class CosmicEngineTests: XCTestCase { ) } + func testLocationChangePreservesTheSelectedCivilDayAcrossDateLineOffsets() throws { + var losAngelesCalendar = Calendar(identifier: .gregorian) + losAngelesCalendar.timeZone = try XCTUnwrap(TimeZone(identifier: "America/Los_Angeles")) + let selected = try XCTUnwrap(losAngelesCalendar.date(from: DateComponents( + year: 2026, month: 7, day: 23, hour: 20, minute: 30 + ))) + let tokyo = try XCTUnwrap(TimeZone(identifier: "Asia/Tokyo")) + + let translated = selected.ritualCivilDay( + preservingDateFrom: losAngelesCalendar.timeZone, + into: tokyo + ) + var tokyoCalendar = Calendar(identifier: .gregorian) + tokyoCalendar.timeZone = tokyo + let components = tokyoCalendar.dateComponents([.year, .month, .day, .hour], from: translated) + + XCTAssertEqual(components.year, 2026) + XCTAssertEqual(components.month, 7) + XCTAssertEqual(components.day, 23) + XCTAssertEqual(components.hour, 12) + } + func testOnlyOwnedNotificationIdentifiersAreCleared() { XCTAssertTrue(NotificationManager.isRitualNotificationIdentifier("muhurta.2.123")) XCTAssertTrue(NotificationManager.isRitualNotificationIdentifier("brahma.123")) @@ -499,4 +737,72 @@ final class CosmicEngineTests: XCTestCase { timeZoneIdentifier: timeZone ) } + + private func assertLocalTransition( + _ transition: PanchangTransition?, + current: String, + next: String, + year: Int, + month: Int, + day: Int, + hour: Int, + minute: Int, + context: CalculationContext, + toleranceMinutes: Double, + file: StaticString = #filePath, + line: UInt = #line + ) throws { + let transition = try XCTUnwrap(transition, file: file, line: line) + XCTAssertEqual(transition.currentName, current, file: file, line: line) + XCTAssertEqual(transition.nextName, next, file: file, line: line) + let expected = try XCTUnwrap(context.calendar.date(from: DateComponents( + year: year, month: month, day: day, hour: hour, minute: minute + )), file: file, line: line) + XCTAssertEqual( + transition.endTime.timeIntervalSince(expected), + 0, + accuracy: toleranceMinutes * 60, + file: file, + line: line + ) + } + + private func limbName(_ kind: PanchangLimbKind, in panchang: Panchang) -> String { + switch kind { + case .tithi: return panchang.tithiName + case .nakshatra: return panchang.nakshatraName + case .yoga: return panchang.yogaName + case .karana: return panchang.karanaName + } + } + + private func assertLocalInterval( + _ period: (start: Date, end: Date, label: String), + startHour: Int, + startMinute: Int, + endHour: Int, + endMinute: Int, + context: CalculationContext, + toleranceMinutes: Double, + file: StaticString = #filePath, + line: UInt = #line + ) throws { + let components = context.localDayComponents + let start = try XCTUnwrap(context.calendar.date(from: DateComponents( + year: components.year, + month: components.month, + day: components.day, + hour: startHour, + minute: startMinute + )), file: file, line: line) + let end = try XCTUnwrap(context.calendar.date(from: DateComponents( + year: components.year, + month: components.month, + day: components.day, + hour: endHour, + minute: endMinute + )), file: file, line: line) + XCTAssertEqual(period.start.timeIntervalSince(start), 0, accuracy: toleranceMinutes * 60, file: file, line: line) + XCTAssertEqual(period.end.timeIntervalSince(end), 0, accuracy: toleranceMinutes * 60, file: file, line: line) + } } diff --git a/ios/native/README.md b/ios/native/README.md index 1a6ebdb..cfc88c8 100644 --- a/ios/native/README.md +++ b/ios/native/README.md @@ -8,6 +8,8 @@ cosmic-glass look and its sidereal (Lahiri ayanamsha) ephemeris. - **Pancha Anga (Five Limbs)** for any date: Vara (weekday), Tithi (lunar day), Nakshatra, Yoga, and Karana. +- **Sunrise-anchored day model** with independently solved Tithi, Nakshatra, Yoga, + and Karana transition times, plus a truthful polar fallback. - **Nakshatra detail** — the Moon's nakshatra with its symbol, gana, ruling (dasha) lord, pada (1–4), Moon sign, and Shukla/Krishna paksha phase. - **30 Muhurtas** — all fifteen day and fifteen night windows, each named and tagged by @@ -17,7 +19,7 @@ cosmic-glass look and its sidereal (Lahiri ayanamsha) ephemeris. ## Build & run -Open `CosmicRituals.xcodeproj` in **Xcode 16+** and run the `CosmicRituals` scheme on an +Open `CosmicRituals.xcodeproj` in **Xcode 26+** and run the `CosmicRituals` scheme on an iOS 26 simulator or device. - Deployment target: **iOS 26** (the muhurta summary pills use the `.glassEffect` API). @@ -36,8 +38,14 @@ CosmicRituals/ See [ARCHITECTURE.md](ARCHITECTURE.md) for the engine details and exactly what was imported from the parent Cosmic Astrology app. -## Accuracy +## Accuracy and integrity -Positions use Meeus algorithms with the Lahiri (Chitra Paksha) ayanamsha. Muhurta windows -are derived from local sunrise/sunset (Delhi default location). For ritually precise -timings, confirm with a qualified Jyotishi. +Positions use Meeus algorithms with the Lahiri (Chitra Paksha) ayanamsha. Muhurta +windows are derived from sunrise and sunset for the explicitly selected location +and IANA time zone. Independent civil-time fixtures cover solar events, all four +limb transitions, Dur Muhurta, Abhijit, DST, and polar behavior. + +See [ACCURACY.md](ACCURACY.md) for method provenance, validation tolerances, and +features that are intentionally not presented as reference-grade. For +consequential ceremonial timing, confirm the chosen convention with a qualified +practitioner. diff --git a/ios/native/ROADMAP.md b/ios/native/ROADMAP.md index ec0a235..3fb4782 100644 --- a/ios/native/ROADMAP.md +++ b/ios/native/ROADMAP.md @@ -1,116 +1,119 @@ -# Cosmic Rituals — Product Roadmap - -> Goal: the definitive native iOS Vedic Panchang experience — surpassing drikpanchang.com on UX -> while matching (then exceeding) it on content depth. On-device, private, no account required. - ---- - -## Baseline (shipped — v0.1) - -| Feature | Status | -|---------|--------| -| Five limbs — Vara, Tithi, Nakshatra, Yoga, Karana | ✅ | -| 30 day + night muhurtas with quality ratings | ✅ | -| Muhurta detail sheet (deity, resonance, activities) | ✅ | -| Rahu Kala + Yamaganda (computed from sunrise) | ✅ | -| Nakshatra detail — lord, gana, pada, symbol | ✅ | -| Moon sign + tithi phase (Shukla/Krishna) | ✅ | -| Animated starfield + Liquid Glass UI | ✅ | -| 6 color themes (3 dark, 3 light) | ✅ | -| Location-aware sunrise/sunset | ✅ | -| Date picker | ✅ | - ---- - -## Phase 1 — Daily-Use Parity with drikpanchang `[✅ shipped v0.2]` - -| Feature | Status | -|---------|--------| -| 1.1 Choghadiya (8 day + 8 night periods, live highlight) | ✅ | -| 1.2 Hora (24 planetary hours, Chaldean cycle) | ✅ | -| 1.3 Moonrise & Moonset (Meeus §15 + parallax) | ✅ | -| 1.4 Gulika Kala | ✅ | -| 1.4 Dur Muhurta (Muhurta Chintamani table) | ✅ | -| 1.4 Varjyam | pending | -| 1.5 Brahma Muhurta + Abhijit Muhurta callouts | ✅ | -| 1.6 Sun sign + Sun nakshatra | ✅ | -| 1.7 Tithi transition time (binary search) | ✅ | - ---- - -## Phase 2 — Content Depth & Discovery `[✅ shipped v0.3]` - -| Feature | Status | -|---------|--------| -| 2.1 Panchang limb detail sheets (Tithi/Yoga/Karana) | ✅ | -| 2.2 Monthly calendar view with tithi/yoga quality dots | ✅ | -| 2.3 Festival & Vrat calendar (40+ festivals) | ✅ | -| 2.4 "Best time today" card with activity picker | ✅ | -| 2.5 Muhurta timeline visualization with live cursor | ✅ | - ---- - -## Phase 3 — Platform & Ecosystem `[partial v0.4]` - -| Feature | Status | -|---------|--------| -| 3.1 WidgetKit (small + medium) | ✅ source files ready; **add target in Xcode** | -| 3.2 Live Activity + Dynamic Island | pending (needs ActivityKit target) | -| 3.3 Notifications (muhurta + Brahma Muhurta) | ✅ | -| 3.4 Siri Shortcuts / App Intents (3 intents) | ✅ | -| 3.5 Share sheet (text + PDF export) | ✅ | - ---- - -## Phase 4 — Advanced Jyotish `[✅ shipped v0.5]` - -| Feature | Status | -|---------|--------| -| 4.1 Planetary hora real-time display | ✅ | -| 4.2 Chandra Bala & Tara Bala | ✅ | -| 4.3 Transit alerts (Jupiter + Saturn sign change notifications) | ✅ | -| 4.3 Nine Graha Positions (Schlyter orbital elements, sidereal) | ✅ | -| 4.4 Panchang PDF export (dark-theme A4) | ✅ | -| 4.5 Apple Watch companion | pending (needs WatchOS target) | - ---- - -## Technical Debt & Ongoing - -| Item | Status | -|------|--------| -| CosmicTheme.swift (1300 lines) — split into components | pending | -| Panchang struct sunriseTime/sunsetTime always nil | pending | -| Unit tests for CosmicEngine | ✅ written; **add test target in Xcode** | -| Tithi name duplication (Pratipada appears twice) | pending | -| Varjyam (nakshatra+pada based inauspicious period) | pending | -| App Group entitlement for widget location sharing | **enable in Xcode Signing & Capabilities** | -| WidgetKit target — add in Xcode and enable App Group | **manual Xcode step** | - ---- - -## Competitive Positioning - -| Feature | DrikPanchang (web) | Cosmic Rituals | -|---------|-------------------|----------------| -| Native iOS UX | ✗ | ✅ | -| Muhurta detail (deity/activities) | Basic | ✅ Rich | -| Live animated UI | ✗ | ✅ | -| Offline / on-device | Partial | ✅ 100% | -| Choghadiya | ✅ | ✅ | -| Hora | ✅ | ✅ | -| Moonrise/Moonset | ✅ | ✅ | -| Monthly calendar | ✅ | ✅ | -| Festivals | ✅ | ✅ | -| Nine Graha Positions | ✅ | ✅ | -| Dur Muhurta | ✅ | ✅ | -| Chandra/Tara Bala | ✅ | ✅ | -| Transit alerts | ✗ | ✅ | -| Widgets | ✗ | ✅ (source ready) | -| Siri integration | ✗ | ✅ | -| PDF export | ✗ | ✅ | -| Privacy / no account | ✗ | ✅ | - ---- - -*Last updated: 2026-07-01* +# Cosmic Rituals — product status and roadmap + +> Product standard: a definitive, private native Panchang experience whose +> calculation claims never outrun its evidence. + +Status words in this document are strict: + +- **Shipping** means reachable in the app target and covered by a current build. +- **Validated** means it also has an automated invariant or independent fixture. +- **Prototype** means source exists but is not a user-facing capability. +- **Pending** means it is not implemented. + +## Current shipping surface + +| Capability | Status | Evidence / limit | +|---|---|---| +| Vara, Tithi, Nakshatra, Yoga, Karana | Shipping + validated | Anchored to selected-location sunrise | +| All four limb transition times | Shipping + validated | Independent Paris fixture; chronological boundary invariants | +| Moon sign, Nakshatra Pada, lord, and Gana | Shipping | Evaluated at the same sunrise reference | +| Sunrise and sunset | Shipping + validated | Four public civil-time fixtures; explicit polar no-result | +| Brahma Muhurta | Shipping | 96–48 minutes before real sunrise | +| Abhijit Muhurta | Shipping + validated | Daylight-scaled; omitted on Wednesday | +| Rahu Kala, Yamaganda, Gulika Kala | Shipping + validated | Published Mumbai fixture; local daylight divisions | +| Dur Muhurta | Shipping + validated | Correct weekday table, including Tuesday night period | +| Choghadiya | Shipping | Eight local day + eight local night periods | +| Hora | Shipping | Twelve local day + twelve local night planetary hours | +| 30 Ahoratra Muhurtas | Shipping | Fifteen day + fifteen night windows | +| Muhurta details and activity filter | Shipping | Traditional symbolic guidance, not prediction | +| Monthly sunrise-snapshot calendar | Shipping | Cache keyed by location and time zone | +| Offline location catalog | Shipping + validated | 33,909 GeoNames cities; CC BY 4.0 attribution | +| Current-location mode | Shipping + validated | Freshness, authorization, coordinate, and accuracy gates | +| Text and PDF sharing | Shipping | Uses the active calculation time zone and sunrise reference | +| App Intents / Shortcuts | Shipping | Requires a persisted explicit location | +| Three information layouts + six themes | Shipping + validated | Dynamic Type, contrast, Reduce Motion, VoiceOver labels | +| Calculation-integrity disclosure | Shipping | Method, privacy, tolerance, and ceremonial-use caveat | + +The detailed method and fixture record is in [ACCURACY.md](ACCURACY.md). + +## Platform truth + +| Capability | Status | What remains | +|---|---|---| +| Automatic notifications | Pending / visibly off | Location/date-safe scheduling, permission UX, tests | +| Home Screen widgets | Prototype source only | Widget extension target, App Group, signing, shared context, E2E tests | +| Live Activity / Dynamic Island | Pending | ActivityKit target and lifecycle design | +| Apple Watch | Pending | Watch target, shared calculation contract, device QA | + +The app and documentation must not describe prototype source as a shipping +capability. + +## Calculation work intentionally quarantined + +The following source is preserved for research but is not routed into shipping +navigation because it does not yet meet the evidence bar: + +| Area | Status | Required before surfacing | +|---|---|---| +| Moonrise and moonset | Prototype | Topocentric altitude solver, parallax, multiple global fixtures | +| Festival and vrata calendar | Prototype | True lunisolar month, Adhika/Kshaya handling, regional precedence rules, fixtures | +| Extended Samvat / Masa / Anandadi panel | Prototype | Rule-complete calendar derivation and regional settings | +| Nine Graha positions | Prototype | Reference-grade ephemeris and error envelope | +| Varjyam / Amrit Kalam | Pending | Nakshatra-based intervals and published fixtures | + +## Next product phases + +### 1. Reference-grade astronomy + +- Evaluate a legally compatible high-precision ephemeris strategy. +- Add global Moonrise/Moonset and Graha fixtures before enabling their views. +- Expand five-limb fixtures across ayanamsha-boundary and skipped/extended-tithi cases. +- Add observer elevation and refraction settings only if their effect can be + explained without creating false confidence. + +### 2. Rule-complete observance calendar + +- Model Amanta and Purnimanta lunar months from actual lunations. +- Detect Adhika and Kshaya months. +- Encode event-specific sunrise, midnight, Pradosha, and tithi-precedence rules. +- Make regional school an explicit user choice. +- Ship festival data only after multi-year, multi-city fixture validation. + +### 3. Native ecosystem + +- Add the WidgetKit target and shared context contract. +- Add notifications only with rescheduling on location, time-zone, and date changes. +- Design Live Activity and Watch surfaces around the next verified transition, + not around duplicated calculation code. + +### 4. International depth + +- Localize interface, Panchang terminology, numeral style, and 12/24-hour time. +- Add transliteration preferences without changing calculation identity. +- Validate right-to-left layout and the largest Dynamic Type sizes. + +## Release gates + +Every release must pass: + +```bash +xcodebuild \ + -project CosmicRituals.xcodeproj \ + -scheme CosmicRituals \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO \ + build-for-testing + +xcodebuild \ + -project CosmicRituals.xcodeproj \ + -scheme CosmicRituals \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ + CODE_SIGNING_ALLOWED=NO \ + test +``` + +It must also receive simulator QA for the three experience modes, dark and light +appearance, location changes, a next-day transition, polar no-schedule behavior, +Reduce Motion, and an accessibility Dynamic Type size. + +Last truthfulness review: 2026-07-27. From 610e73e7a3b8558e144ae890a25d6a5c974cd7c4 Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 11:25:11 +0530 Subject: [PATCH 02/10] Prepare TestFlight build 4 --- ios/native/CosmicRituals.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/native/CosmicRituals.xcodeproj/project.pbxproj b/ios/native/CosmicRituals.xcodeproj/project.pbxproj index 5045e93..86b0092 100644 --- a/ios/native/CosmicRituals.xcodeproj/project.pbxproj +++ b/ios/native/CosmicRituals.xcodeproj/project.pbxproj @@ -300,7 +300,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -331,7 +331,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; From e154b6d0a01223b08977764934e214c447cc417c Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 19:24:50 +0530 Subject: [PATCH 03/10] Add Pooja guides and Premium trial --- docs/index.html | 26 + docs/privacy/index.html | 38 + docs/styles.css | 57 ++ docs/support/index.html | 29 + docs/terms/index.html | 40 + ios/native/ARCHITECTURE.md | 22 +- .../CosmicRituals.xcodeproj/project.pbxproj | 4 +- .../xcschemes/CosmicRituals.xcscheme | 3 + .../CosmicRituals/App/CosmicAppIntents.swift | 12 + .../CosmicRituals/App/CosmicRitualsApp.swift | 7 +- .../Engine/PoojaVidhiCatalog.swift | 703 ++++++++++++++ .../Engine/SubscriptionStore.swift | 158 ++++ .../Views/PanchangExperienceViews.swift | 1 + .../CosmicRituals/Views/PanchangView.swift | 90 +- .../CosmicRituals/Views/PoojaVidhiViews.swift | 878 ++++++++++++++++++ ios/native/CosmicRituals/Views/RootView.swift | 28 +- .../Views/SubscriptionViews.swift | 190 ++++ .../CosmicEngineTests.swift | 150 ++- ios/native/POOJA_CONTENT.md | 65 ++ ios/native/README.md | 17 +- ios/native/SUBSCRIPTION_RELEASE.md | 66 ++ ios/native/StoreKit/CosmicRituals.storekit | 175 ++++ 22 files changed, 2719 insertions(+), 40 deletions(-) create mode 100644 docs/index.html create mode 100644 docs/privacy/index.html create mode 100644 docs/styles.css create mode 100644 docs/support/index.html create mode 100644 docs/terms/index.html create mode 100644 ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift create mode 100644 ios/native/CosmicRituals/Engine/SubscriptionStore.swift create mode 100644 ios/native/CosmicRituals/Views/PoojaVidhiViews.swift create mode 100644 ios/native/CosmicRituals/Views/SubscriptionViews.swift create mode 100644 ios/native/POOJA_CONTENT.md create mode 100644 ios/native/SUBSCRIPTION_RELEASE.md create mode 100644 ios/native/StoreKit/CosmicRituals.storekit diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..cea5378 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,26 @@ + + + + + + + Cosmic Rituals + + + +
+
Cosmic Rituals · Native for iPhone and iPad
+

Clarity for sacred time and practice.

+
+

Cosmic Rituals provides a location-aware Vedic Panchang, transparent muhurta timing, and an offline, tradition-aware Pooja Vidhi library.

+

Core calculations, saved location preferences, and ritual searches remain on the device. The app has no advertising SDK, tracking SDK, or user account system.

+
+ +
© 2026 Cosmic Rituals. Ceremonial traditions vary; confirm consequential timings and formal rites with a qualified practitioner.
+
+ + diff --git a/docs/privacy/index.html b/docs/privacy/index.html new file mode 100644 index 0000000..fd41e1d --- /dev/null +++ b/docs/privacy/index.html @@ -0,0 +1,38 @@ + + + + + + Privacy Policy · Cosmic Rituals + + + +
+
Effective July 28, 2026
+

Privacy Policy

+

Cosmic Rituals is designed to operate without a developer-operated account or analytics service.

+ +

Information processed on your device

+

The app processes the date, a selected city or current device location, time zone, and app preferences to calculate Panchang and muhurta information. These calculations, Pooja searches, checklists, and display preferences stay on your device. A selected calculation location and preferences may remain in local app storage until you change them or delete the app.

+ +

Location

+

If you choose “Current Location,” iOS provides coordinates while the app is in use. Cosmic Rituals uses them locally to calculate sunrise, sunset, Panchang, and muhurta times. The developer does not receive or store your location on a server. You may instead choose an offline city and can change location permission in iOS Settings.

+ +

Subscriptions and payments

+

Purchases are processed by Apple through the App Store. Cosmic Rituals receives signed entitlement status and localized product information needed to grant access. The developer does not receive your payment-card details. Apple’s handling of purchase information is governed by Apple’s privacy policy.

+ +

Data collection, advertising, and tracking

+

Cosmic Rituals does not include third-party advertising, cross-app tracking, analytics, or data-broker SDKs. The developer does not collect personal data through the app.

+ +

External links

+

The app can open source references, policy pages, App Store subscription management, and support pages at your request. Those destinations apply their own privacy practices once opened.

+ +

Children

+

The app does not knowingly collect personal information from children or anyone else.

+ +

Changes and contact

+

Material changes will be posted on this page with a revised effective date. For privacy questions, use the Cosmic Rituals support page.

+ +
+ + diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..e168336 --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,57 @@ +:root { + color-scheme: dark; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif; + background: #080811; + color: #f5f1ea; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + background: + radial-gradient(circle at 80% 5%, rgba(118, 89, 170, 0.22), transparent 36rem), + radial-gradient(circle at 5% 40%, rgba(217, 150, 91, 0.12), transparent 30rem), + #080811; +} + +main { + width: min(760px, calc(100% - 32px)); + margin: 0 auto; + padding: 64px 0 80px; +} + +.eyebrow { + color: #e5a469; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +h1 { font-size: clamp(2.3rem, 7vw, 4.4rem); line-height: 0.98; margin: 14px 0 24px; } +h2 { margin-top: 36px; font-size: 1.35rem; } +p, li { color: #c8c3cd; line-height: 1.68; } +strong { color: #f5f1ea; } +a { color: #f1b17b; } + +.card { + margin-top: 28px; + padding: 24px; + border: 1px solid rgba(229, 164, 105, 0.32); + border-radius: 24px; + background: rgba(26, 24, 35, 0.72); + backdrop-filter: blur(18px); +} + +.links { display: grid; gap: 12px; margin-top: 28px; } +.links a { + display: block; + padding: 15px 18px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.06); + text-decoration: none; + font-weight: 650; +} + +footer { margin-top: 52px; font-size: 0.82rem; color: #89838e; } diff --git a/docs/support/index.html b/docs/support/index.html new file mode 100644 index 0000000..6c7daa0 --- /dev/null +++ b/docs/support/index.html @@ -0,0 +1,29 @@ + + + + + + Support · Cosmic Rituals + + + +
+
Cosmic Rituals
+

Support

+
+

Restore a subscription

+

On the Premium screen, choose Restore Purchases. Use the same Apple Account that made the original purchase.

+ +

Manage or cancel

+

Open Apple subscription settings. Billing and refunds are handled by Apple.

+ +

Location or timing looks wrong

+

Confirm the selected city, date, and time zone. Current Location requires permission and a fresh GPS fix. Regional observance rules can differ even when astronomical boundaries agree.

+ +

Contact support

+

Open a support request with the app version, device model, iOS version, selected city, and a description of the issue. Do not include payment details, precise home coordinates, or other sensitive information.

+
+ +
+ + diff --git a/docs/terms/index.html b/docs/terms/index.html new file mode 100644 index 0000000..b5985f5 --- /dev/null +++ b/docs/terms/index.html @@ -0,0 +1,40 @@ + + + + + + Terms of Use · Cosmic Rituals + + + +
+
Effective July 28, 2026
+

Terms of Use

+ +

App license

+

Your use of Cosmic Rituals is subject to these terms and Apple’s Standard Licensed Application End User License Agreement.

+ +

Auto-renewable subscriptions

+ + +

Traditional and astronomical information

+

Panchang, muhurta, festival, and Pooja content is educational and ceremonial reference material. Traditions and regional observances vary. The app does not guarantee outcomes and is not medical, legal, financial, safety, or professional religious advice. Confirm consequential timing and formal rites with a qualified practitioner.

+ +

Availability and acceptable use

+

Do not use the app where an error or interruption could cause harm. You may not reverse engineer, redistribute, misuse, or interfere with the app except where applicable law expressly permits.

+ +

Updates

+

Features, calculations, content, and these terms may be updated to improve accuracy, security, legal compliance, or reliability. A revised effective date will appear here for material changes.

+ +

Support

+

For subscription or technical assistance, visit Cosmic Rituals Support.

+ +
+ + diff --git a/ios/native/ARCHITECTURE.md b/ios/native/ARCHITECTURE.md index b4251dc..f61d384 100644 --- a/ios/native/ARCHITECTURE.md +++ b/ios/native/ARCHITECTURE.md @@ -13,12 +13,14 @@ CosmicRituals/ ├── Engine/ ← pure Foundation, no SwiftUI │ ├── PanchangModels.swift ZodiacSign, Panchang, NakshatraResult, Muhurta(+Quality), CelestialBody │ ├── CosmicEngine.swift Panchang-focused ephemeris (sidereal, Lahiri ayanamsha) -│ └── MuhurtaLibrary.swift rich per-muhurta detail: deity, resonance, favourable/avoid +│ ├── MuhurtaLibrary.swift rich per-muhurta detail: deity, resonance, favourable/avoid +│ └── PoojaVidhiCatalog.swift offline ritual models, search, validation, content policy ├── Theme/ │ └── CosmicTheme.swift shared cosmic-glass palette + reusable components └── Views/ ├── RootView.swift hosts PanchangView in the cosmic theme ├── PanchangView.swift Five Limbs + 30 Muhurtas, with live "now" timing + ├── PoojaVidhiViews.swift library, material checklist, detail + guided ritual flow └── MuhurtaDetailView.swift tap-through sheet: deity, description, favourable/avoid ``` @@ -66,6 +68,24 @@ selectedDate + RitualLocation ──▶ CalculationContext └─▶ 30 day + night muhurtas ``` +Pooja guidance is deliberately independent of the ephemeris. `PoojaVidhiCatalog` is +pure Foundation data with deterministic search and executable validation. +`PoojaVidhiLibraryView` renders that catalog, while `PoojaVidhiDetailView` and +`GuidedPoojaView` own only ephemeral checklist/progress state. No ritual completion, +search text, or religious preference leaves the device. + +The catalog distinguishes `simpleHousehold`, `extendedHousehold`, and +`priestRecommended` practice. Formal homa, nyasa, kalasha installation, visarjana, +initiatory mantras, and supposedly universal priestly procedures are never synthesized. +See [POOJA_CONTENT.md](POOJA_CONTENT.md). + +Premium access is owned by `SubscriptionStore`. It accepts only verified current +StoreKit entitlements for the configured monthly or annual product, observes transaction +updates, and preserves access when product merchandising metadata is temporarily +unavailable. `RootView` owns this app-level service and selects the Premium gate or the +main Panchang experience. App Intents check the same StoreKit entitlement boundary. +Trial eligibility and localized prices come from App Store Connect, never device time. + ## The engine `CosmicEngine` is a sidereal Vedic ephemeris using the **Lahiri (Chitra Paksha)** diff --git a/ios/native/CosmicRituals.xcodeproj/project.pbxproj b/ios/native/CosmicRituals.xcodeproj/project.pbxproj index 86b0092..b574056 100644 --- a/ios/native/CosmicRituals.xcodeproj/project.pbxproj +++ b/ios/native/CosmicRituals.xcodeproj/project.pbxproj @@ -300,7 +300,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -331,7 +331,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRituals.xcscheme b/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRituals.xcscheme index 5ee98c0..d1d9987 100644 --- a/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRituals.xcscheme +++ b/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRituals.xcscheme @@ -75,6 +75,9 @@ ReferencedContainer = "container:CosmicRituals.xcodeproj"> + + some ReturnsValue & ProvidesDialog { + guard await SubscriptionEntitlementChecker.grantsCurrentAccess() else { + let reply = "Open Cosmic Rituals to start or restore Premium before using this shortcut." + return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) + } guard let (context, location) = IntentCalculationContext.resolve(for: Date()) else { let reply = "Open Cosmic Rituals and choose a calculation location before using this shortcut." return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) @@ -61,6 +65,10 @@ struct TodayPanchangIntent: AppIntent { @MainActor func perform() async throws -> some ReturnsValue & ProvidesDialog { + guard await SubscriptionEntitlementChecker.grantsCurrentAccess() else { + let reply = "Open Cosmic Rituals to start or restore Premium before using this shortcut." + return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) + } guard let (context, location) = IntentCalculationContext.resolve(for: Date()) else { let reply = "Open Cosmic Rituals and choose a calculation location before using this shortcut." return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) @@ -82,6 +90,10 @@ struct NextAuspiciousTimeIntent: AppIntent { @MainActor func perform() async throws -> some ReturnsValue & ProvidesDialog { + guard await SubscriptionEntitlementChecker.grantsCurrentAccess() else { + let reply = "Open Cosmic Rituals to start or restore Premium before using this shortcut." + return .result(value: reply, dialog: IntentDialog(stringLiteral: reply)) + } let now = Date() guard let (context, location) = IntentCalculationContext.resolve(for: now) else { let reply = "Open Cosmic Rituals and choose a calculation location before using this shortcut." diff --git a/ios/native/CosmicRituals/App/CosmicRitualsApp.swift b/ios/native/CosmicRituals/App/CosmicRitualsApp.swift index f5d0205..b0a5e2d 100644 --- a/ios/native/CosmicRituals/App/CosmicRitualsApp.swift +++ b/ios/native/CosmicRituals/App/CosmicRitualsApp.swift @@ -2,9 +2,10 @@ import SwiftUI /// Cosmic Rituals — a native SwiftUI app for the Vedic Panchang: the five limbs of /// the day (tithi, nakshatra, yoga, karana, vara), the Moon's nakshatra & pada, and -/// the thirty day & night muhurtas with live timing — presented in the shared -/// cosmic-glass theme. Day-level limbs are anchored to the selected location's -/// sunrise, with an explicit local-noon fallback when sunrise is unavailable. +/// the thirty day & night muhurtas with live timing, and an offline, tradition-aware +/// Pooja Vidhi library — presented in the shared cosmic-glass theme. Day-level limbs +/// are anchored to the selected location's sunrise, with an explicit local-noon +/// fallback when sunrise is unavailable. @main struct CosmicRitualsApp: App { var body: some Scene { diff --git a/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift b/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift new file mode 100644 index 0000000..cb7b0ce --- /dev/null +++ b/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift @@ -0,0 +1,703 @@ +import Foundation + +enum PoojaCategory: String, CaseIterable, Identifiable, Hashable, Sendable { + case daily + case deity + case festival + case vrata + case lifeEvent + case planetary + + var id: String { rawValue } + + var displayName: String { + switch self { + case .daily: return "Daily" + case .deity: return "Deity" + case .festival: return "Festival" + case .vrata: return "Vrata" + case .lifeEvent: return "Life event" + case .planetary: return "Planetary" + } + } + + var symbol: String { + switch self { + case .daily: return "sunrise.fill" + case .deity: return "sparkles" + case .festival: return "flame.fill" + case .vrata: return "moon.stars.fill" + case .lifeEvent: return "house.fill" + case .planetary: return "circle.hexagongrid.fill" + } + } +} + +enum PoojaPracticeLevel: String, Hashable, Sendable { + case simpleHousehold + case extendedHousehold + case priestRecommended + + var displayName: String { + switch self { + case .simpleHousehold: return "Simple household" + case .extendedHousehold: return "Extended household" + case .priestRecommended: return "Priest recommended" + } + } + + var guidance: String { + switch self { + case .simpleHousehold: + return "A respectful home sequence using public name-mantras and simple offerings." + case .extendedHousehold: + return "Suitable at home when the family already observes this practice; a priest may guide local variations." + case .priestRecommended: + return "This guide prepares the household and explains the sequence. Formal sankalpa, homa, nyasa, and lineage mantras should be led by a qualified priest." + } + } +} + +struct PoojaMaterial: Identifiable, Hashable, Sendable { + let id: String + let name: String + let purpose: String + let isRequired: Bool + let alternative: String? +} + +struct PoojaMantra: Hashable, Sendable { + let title: String + let devanagari: String + let transliteration: String + let meaning: String + let repetition: String +} + +struct PoojaStep: Identifiable, Hashable, Sendable { + let number: Int + let title: String + let instruction: String + let mantra: PoojaMantra? + let note: String? + let estimatedMinutes: Int + + var id: Int { number } +} + +struct PoojaSourceNote: Identifiable, Hashable, Sendable { + let title: String + let detail: String + let urlString: String + + var id: String { urlString } +} + +struct PoojaVidhi: Identifiable, Hashable, Sendable { + let id: String + let title: String + let sacredFocus: String + let summary: String + let purpose: String + let category: PoojaCategory + let practiceLevel: PoojaPracticeLevel + let durationMinutes: ClosedRange + let occasions: [String] + let preparation: [String] + let materials: [PoojaMaterial] + let steps: [PoojaStep] + let completion: [String] + let safetyNotes: [String] + let traditionNote: String + let sourceNotes: [PoojaSourceNote] + + var durationText: String { + if durationMinutes.lowerBound == durationMinutes.upperBound { + return "\(durationMinutes.lowerBound) min" + } + return "\(durationMinutes.lowerBound)–\(durationMinutes.upperBound) min" + } + + var searchableText: String { + ([title, sacredFocus, summary, purpose, category.displayName, practiceLevel.displayName] + + occasions + + preparation + + materials.flatMap { [$0.name, $0.purpose, $0.alternative ?? ""] } + + steps.flatMap { step in + [step.title, step.instruction, step.note ?? "", step.mantra?.devanagari ?? "", + step.mantra?.transliteration ?? "", step.mantra?.meaning ?? ""] + }) + .joined(separator: " ") + } + + var shareText: String { + """ + \(title) · \(durationText) + \(summary) + + \(steps.map { "\($0.number). \($0.title): \($0.instruction)" }.joined(separator: "\n")) + + Household reference from Cosmic Rituals. Family, temple, and sampradaya practice may differ. + """ + } +} + +enum PoojaContentPolicy { + static let sourceReviewDate = "2026-07-28" + static let reviewStatus = "Source-adapted household reference, not a sampradaya-specific certification. Confirm formal rites and received practice with a qualified practitioner." + static let householdScope = "These are inclusive household reference sequences, not claims of one universal or canonical Pooja Vidhi. Family, temple, regional, and sampradaya practice takes precedence." + static let inclusionScope = "The app does not restrict participation by caste, gender, menstruation, marital status, or birth. A person may sit, stand, bow, or adapt movements for safety, disability, age, and comfort." + static let mantraScope = "Only short public name-mantras are used. Initiatory bija-mantras, nyasa, fire rites, and lineage-restricted recitations are intentionally omitted. Meaning is shown so a person may pray in a familiar language if Sanskrit pronunciation is uncertain." + static let safetyScope = "A flame, incense, water, food, and flowers are optional when unsafe or unavailable. Never leave a flame unattended, keep smoke away from sensitive people and animals, and check food offerings for allergies before sharing prasada." +} + +enum PoojaVidhiCatalog { + private static let householdPujaSource = PoojaSourceNote( + title: "Himalayan Academy · Home Puja", + detail: "Used for the common preparation, offering, prasada, and quiet-closing pattern. Its detailed Ganesha rite is Saiva-lineage specific and is not generalized here.", + urlString: "https://www.himalayanacademy.com/media/books/home-puja_ei/web/ch24a.html" + ) + + private static let dailyPracticeSource = PoojaSourceNote( + title: "BAPS Swaminarayan Sanstha · Daily Puja", + detail: "Used as a second reference for cleanliness, a steady daily practice, meditation, prayer, and respectful closure. Sect-specific elements remain identified as such and are not copied into the general sequence.", + urlString: "https://www.baps.org/Spiritual-Living/Hindu-Practices/Daily-Puja.aspx" + ) + + private static let lakshmiSource = PoojaSourceNote( + title: "Drik Panchang · Lakshmi Shodashopachara Puja", + detail: "Used to check the traditional progression from dhyana and avahana through offerings and closure. The app presents a shorter household Panchopachara form, not the full sixteen-offering rite.", + urlString: "https://www.drikpanchang.com/festivals/lakshmipuja/info/lakshmi-puja-vidhi.html" + ) + + private static let navratriSource = PoojaSourceNote( + title: "Drik Panchang · Durga Puja Vidhi", + detail: "Used to confirm the broad Navratri worship progression. Ghatasthapana and lineage-specific Shodashopachara mantras are outside the simple household guide.", + urlString: "https://www.drikpanchang.com/navratri/durga-puja/puja-vidhi/durga-puja-vidhi.html" + ) + + private static let satyanarayanaSource = PoojaSourceNote( + title: "Drik Panchang · Satyanarayana Puja Vidhi", + detail: "Used to check the Vishnu-centered vrata sequence. A complete vrata includes the locally received katha and may include priest-led steps.", + urlString: "https://www.drikpanchang.com/hindu-gods/trimurti/lord-vishnu/puja-vidhi/satyanarayan-puja-vidhi.html" + ) + + static let all: [PoojaVidhi] = [ + householdVidhi( + id: "daily-panchopachara", + title: "Daily Panchopachara Pooja", + sacredFocus: "Ishta Devata", + summary: "A calm, compact daily practice of centering, intention, five symbolic offerings, prayer, and respectful closure.", + purpose: "Daily devotion, gratitude, and a steady home-shrine rhythm.", + category: .daily, + practiceLevel: .simpleHousehold, + duration: 8...15, + occasions: ["Every day", "Morning prayer", "Evening prayer", "Gratitude"], + specificMaterials: [], + focusPreparation: "Place the murti or image of your chosen form of the Divine on a clean, stable surface.", + invocation: "With folded hands, welcome your Ishta Devata in the way your family uses. If you have no received invocation, a sincere welcome in your own language is sufficient.", + offering: "Offer fragrance or sandalwood, one flower, incense if suitable, a safely placed lamp, and a small food or water offering. Each is optional when unavailable.", + mantra: PoojaMantra( + title: "Universal offering prayer", + devanagari: "ॐ नमः", + transliteration: "Om namaḥ", + meaning: "Reverence to the Divine.", + repetition: "Repeat slowly 3, 9, or 11 times" + ), + closingPrayer: "Offer gratitude, ask forgiveness for errors, bow, and sit quietly before returning to the day.", + traditionNote: "Panchopachara names five modes of hospitality; their exact order and substances vary. This neutral home sequence does not replace a received family paddhati.", + sources: [householdPujaSource, dailyPracticeSource] + ), + householdVidhi( + id: "ganesha-home", + title: "Shri Ganesha Pooja", + sacredFocus: "Ganesha", + summary: "A simple home Pooja honoring Ganesha before a new undertaking or as a regular devotional practice.", + purpose: "Clarity, an auspicious beginning, humility, and removal of inner obstacles.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 15...25, + occasions: ["New beginning", "Before study", "Before travel", "Ganesha Chaturthi", "Wednesday"], + specificMaterials: [ + material("durva", "Durva grass or a fresh flower", "Traditional Ganesha offering", required: false, alternative: "Any clean, respectfully offered flower"), + material("modaka", "Modaka or a simple sweet", "Naivedya", required: false, alternative: "Fruit or water") + ], + focusPreparation: "Place a Ganesha murti or image on a clean altar. Keep liquids away from paper images and electronics.", + invocation: "Contemplate Ganesha as compassionate wisdom. Invite his presence with folded hands; elaborate avahana mudras are not required for this household form.", + offering: "Offer sandalwood or fragrance, durva or a flower, incense if appropriate, lamp light, and fruit or a sweet.", + mantra: PoojaMantra( + title: "Ganesha name-mantra", + devanagari: "ॐ गणपतये नमः", + transliteration: "Om gaṇapataye namaḥ", + meaning: "Reverence to Ganapati, lord of the hosts.", + repetition: "Repeat 11 or 21 times, without rushing" + ), + closingPrayer: "Pray for wisdom to recognize and work through obstacles, perform a gentle namaskara, and share the offering as prasada.", + traditionNote: "This is a short public household form. The longer Sanskrit Ganesha Pooja published by Himalayan Academy belongs to a specific Saiva teaching context and should be learned from that source or a teacher.", + sources: [householdPujaSource] + ), + householdVidhi( + id: "lakshmi-home", + title: "Mahalakshmi Pooja", + sacredFocus: "Mahalakshmi", + summary: "A household Lakshmi Pooja centered on gratitude, ethical abundance, hospitality, and care for the home.", + purpose: "Gratitude for sustenance and a prayer for well-being, generosity, and wise stewardship.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 20...30, + occasions: ["Friday", "Diwali", "Varalakshmi Vrata", "At home", "New home", "Prosperity", "Gratitude"], + specificMaterials: [ + material("lotus", "Lotus or another fresh flower", "Floral offering", required: false, alternative: "A clean local flower"), + material("coin", "Clean coin or account book", "Symbol of responsible resources", required: false, alternative: nil), + material("sweet", "Fruit or vegetarian sweet", "Naivedya", required: false, alternative: "Water") + ], + focusPreparation: "Clean the worship area and place Lakshmi's murti or image securely. Treat cleaning as preparation, not as a guarantee of material gain.", + invocation: "Meditate on Lakshmi seated on a lotus, embodying auspiciousness, generosity, beauty, and well-being. Welcome her with folded hands.", + offering: "Offer fragrance, a flower, gentle incense if suitable, lamp light, and fruit or a sweet. Full bathing and sixteen-offering rites are omitted from this home guide.", + mantra: PoojaMantra( + title: "Lakshmi name-mantra", + devanagari: "ॐ महालक्ष्म्यै नमः", + transliteration: "Om mahālakṣmyai namaḥ", + meaning: "Reverence to Mahalakshmi.", + repetition: "Repeat 11 or 21 times with attention to meaning" + ), + closingPrayer: "Express gratitude, commit to a concrete act of generosity or responsible care, offer namaskara, and share prasada.", + traditionNote: "The full Diwali Shodashopachara sequence contains sixteen formal offerings and longer mantras. This app intentionally provides a shorter Panchopachara household form.", + sources: [lakshmiSource, householdPujaSource] + ), + householdVidhi( + id: "shiva-home", + title: "Shiva Pooja", + sacredFocus: "Shiva", + summary: "A contemplative Shiva Pooja using water, bilva or flowers, lamp light, mantra, and silence.", + purpose: "Stillness, self-reflection, compassion, and release of unhelpful habits.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 15...30, + occasions: ["Monday", "Pradosha", "Mahashivaratri", "Meditation", "Peace"], + specificMaterials: [ + material("bilva", "Bilva leaves", "Traditional Shiva offering", required: false, alternative: "A clean flower or leaf accepted in your family tradition"), + material("abhisheka-water", "Separate clean water", "Optional abhisheka for a suitable Shiva Linga", required: false, alternative: nil) + ], + focusPreparation: "Use a stable Shiva murti, image, or a Shiva Linga already worshipped in your household. Never pour liquid over a framed image or electrical surface.", + invocation: "Sit quietly and contemplate Shiva as auspicious stillness and compassionate awareness. Welcome the Divine presence without attempting lineage-specific nyasa.", + offering: "If your household custom permits, offer a small stream of clean water to a suitable Linga over a catch tray. Otherwise offer water symbolically, then fragrance, bilva or a flower, lamp light, and fruit.", + mantra: PoojaMantra( + title: "Shiva name-mantra", + devanagari: "ॐ शिवाय नमः", + transliteration: "Om śivāya namaḥ", + meaning: "Reverence to Shiva, the auspicious one.", + repetition: "Repeat 11, 21, or 108 times according to comfort" + ), + closingPrayer: "Rest in silence, ask that insight become compassionate action, bow, and respectfully clear any offered water.", + traditionNote: "Abhisheka substances and rules differ widely. This guide uses only clean water and makes abhisheka optional; follow the practice received from your family or temple.", + sources: [householdPujaSource] + ), + householdVidhi( + id: "vishnu-home", + title: "Vishnu Pooja", + sacredFocus: "Vishnu or Narayana", + summary: "A simple Vishnu Pooja of welcome, Tulsi or flowers, lamp, naivedya, mantra, and gratitude.", + purpose: "Steadiness, preservation of dharma, gratitude, and service.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 15...25, + occasions: ["Thursday", "Ekadashi", "Vaikuntha Ekadashi", "Peace", "Family prayer"], + specificMaterials: [ + material("tulsi", "Tulsi leaf", "Traditional Vishnu offering", required: false, alternative: "A clean flower accepted in your tradition"), + material("fruit-vishnu", "Fruit or simple sattvic food", "Naivedya", required: false, alternative: "Water") + ], + focusPreparation: "Place a Vishnu or Narayana murti or image on a clean, stable altar. Prepare food without ingredients your family excludes from worship.", + invocation: "Contemplate Vishnu as the sustaining presence within life and welcome him with folded hands.", + offering: "Offer water, sandalwood or fragrance, Tulsi or a flower, incense if suitable, lamp light, and naivedya.", + mantra: PoojaMantra( + title: "Vishnu name-mantra", + devanagari: "ॐ विष्णवे नमः", + transliteration: "Om viṣṇave namaḥ", + meaning: "Reverence to Vishnu.", + repetition: "Repeat 11 or 21 times" + ), + closingPrayer: "Pray for the strength to uphold what sustains life, offer namaskara, and share prasada mindfully.", + traditionNote: "Vaishnava sampradayas differ in mantra, tilaka, food rules, and offering sequence. Those received practices take precedence over this general guide.", + sources: [householdPujaSource] + ), + householdVidhi( + id: "durga-navratri-home", + title: "Durga & Navratri Pooja", + sacredFocus: "Durga", + summary: "A simple daily Navratri or household Durga Pooja focused on courage, compassionate protection, and disciplined devotion.", + purpose: "Courage, protection, dignity, and reverence for the Divine Mother.", + category: .festival, + practiceLevel: .extendedHousehold, + duration: 20...35, + occasions: ["Navratri", "Friday", "Ashtami", "Durga Puja", "Protection", "Courage"], + specificMaterials: [ + material("red-flower", "Red or seasonal flower", "Floral offering", required: false, alternative: "Any clean flower"), + material("kalasha", "Kalasha and sprouting grains", "Optional Navratri observance", required: false, alternative: "Omit unless this is an established household practice") + ], + focusPreparation: "Place Durga's murti or image securely. If maintaining a Navratri kalasha, follow the practice learned from your family or priest rather than improvising installation or visarjana.", + invocation: "Contemplate Durga as compassionate strength that protects dignity and dharma. Welcome her in your own words or with the public name-mantra below.", + offering: "Offer water, fragrance, a red or seasonal flower, incense if suitable, lamp light, and fruit or a simple sweet.", + mantra: PoojaMantra( + title: "Durga name-mantra", + devanagari: "ॐ दुर्गायै नमः", + transliteration: "Om durgāyai namaḥ", + meaning: "Reverence to Durga.", + repetition: "Repeat 11 or 21 times" + ), + closingPrayer: "Pray that strength be joined with compassion, offer namaskara, and conclude without moving an installed kalasha casually.", + traditionNote: "Navratri forms, fasting, Ghatasthapana, Kumari Puja, and visarjana vary by region and lineage. Fasting is optional and is not prescribed by this app; health needs take priority.", + sources: [navratriSource, householdPujaSource] + ), + householdVidhi( + id: "hanuman-home", + title: "Hanuman Pooja", + sacredFocus: "Hanuman", + summary: "A home Pooja honoring Hanuman through simple offerings, name-mantra, prayer, and optional familiar devotional reading.", + purpose: "Courage, disciplined service, resilience, and devotion.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 15...30, + occasions: ["Tuesday", "Saturday", "Hanuman Jayanti", "Courage", "Service"], + specificMaterials: [ + material("hanuman-flower", "Red or orange flower", "Floral offering", required: false, alternative: "Any clean flower"), + material("hanuman-reading", "Hanuman Chalisa or a familiar prayer book", "Optional devotional reading", required: false, alternative: nil) + ], + focusPreparation: "Place Hanuman's murti or image on a clean altar. Do not apply oil or sindoor to a murti unless its material and your established custom allow it.", + invocation: "Contemplate Hanuman as strength guided by humility and selfless service, then welcome him with folded hands.", + offering: "Offer water, fragrance, a flower, lamp light, and fruit. Read a familiar prayer only if you can do so respectfully and without rushing.", + mantra: PoojaMantra( + title: "Hanuman name-mantra", + devanagari: "ॐ हनुमते नमः", + transliteration: "Om hanumate namaḥ", + meaning: "Reverence to Hanuman.", + repetition: "Repeat 11 or 21 times" + ), + closingPrayer: "Ask for strength to serve wisely, offer namaskara, and carry one act of disciplined kindness into the day.", + traditionNote: "Offerings and longer recitations differ by region. This guide deliberately does not prescribe sindoor, oil, or restricted ritual applications.", + sources: [householdPujaSource] + ), + householdVidhi( + id: "saraswati-home", + title: "Saraswati Pooja", + sacredFocus: "Saraswati", + summary: "A gentle Pooja for learning, music, language, discernment, and respectful use of knowledge.", + purpose: "Clarity in learning, creativity, truthful speech, and humility before knowledge.", + category: .deity, + practiceLevel: .simpleHousehold, + duration: 15...25, + occasions: ["Vasant Panchami", "Before study", "New course", "Music", "Examination", "Learning"], + specificMaterials: [ + material("book", "Book, notebook, or instrument", "Symbol of learning", required: false, alternative: nil), + material("white-flower", "White or pale flower", "Floral offering", required: false, alternative: "Any clean flower") + ], + focusPreparation: "Place Saraswati's image and the book or instrument nearby on a clean cloth. Keep food, water, oil, and flame safely separated from books and instruments.", + invocation: "Contemplate Saraswati as wisdom, language, music, and clear discernment. Welcome her with folded hands.", + offering: "Offer fragrance, a flower, safely placed lamp light, and fruit or water. Touch the learning object only with clean, dry hands.", + mantra: PoojaMantra( + title: "Saraswati name-mantra", + devanagari: "ॐ सरस्वत्यै नमः", + transliteration: "Om sarasvatyai namaḥ", + meaning: "Reverence to Saraswati.", + repetition: "Repeat 11 or 21 times" + ), + closingPrayer: "Pray to use knowledge truthfully and for benefit, bow, then begin a short period of attentive study or practice.", + traditionNote: "Vasant Panchami customs differ across regions and schools. This is a simple devotional guide, not a substitute for a school or family ceremony.", + sources: [householdPujaSource] + ), + suryaArghya, + satyanarayana, + grihaPravesh, + navagraha + ] + + static func vidhi(id: String) -> PoojaVidhi? { + all.first { $0.id == id } + } + + static func search(_ query: String, category: PoojaCategory? = nil) -> [PoojaVidhi] { + let candidates = category.map { selected in all.filter { $0.category == selected } } ?? all + let normalizedQuery = normalized(query) + guard !normalizedQuery.isEmpty else { return candidates } + let tokens = normalizedQuery.split(separator: " ").map(String.init) + + return candidates + .filter { vidhi in + let corpus = normalized(vidhi.searchableText) + return tokens.allSatisfy(corpus.contains) + } + .sorted { lhs, rhs in + let lhsTitle = normalized(lhs.title) + let rhsTitle = normalized(rhs.title) + let lhsPrefix = lhsTitle.hasPrefix(normalizedQuery) + let rhsPrefix = rhsTitle.hasPrefix(normalizedQuery) + if lhsPrefix != rhsPrefix { return lhsPrefix } + return lhs.title.localizedCaseInsensitiveCompare(rhs.title) == .orderedAscending + } + } + + static var validationIssues: [String] { + var issues: [String] = [] + let ids = all.map(\.id) + if Set(ids).count != ids.count { issues.append("Catalog IDs must be unique") } + + for vidhi in all { + if vidhi.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + issues.append("\(vidhi.id): title is empty") + } + if vidhi.steps.count < 8 { issues.append("\(vidhi.id): fewer than eight steps") } + if vidhi.materials.count < 5 { issues.append("\(vidhi.id): incomplete materials") } + if vidhi.sourceNotes.isEmpty { issues.append("\(vidhi.id): missing source transparency") } + if vidhi.safetyNotes.isEmpty { issues.append("\(vidhi.id): missing safety notes") } + if vidhi.steps.map(\.number) != Array(1...vidhi.steps.count) { + issues.append("\(vidhi.id): steps are not sequential") + } + for step in vidhi.steps { + if let mantra = step.mantra, + [mantra.devanagari, mantra.transliteration, mantra.meaning, mantra.repetition] + .contains(where: { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }) { + issues.append("\(vidhi.id): incomplete mantra at step \(step.number)") + } + } + } + return issues + } + + private static func normalized(_ value: String) -> String { + value.folding(options: [.caseInsensitive, .diacriticInsensitive], locale: Locale(identifier: "en_US_POSIX")) + .lowercased() + .components(separatedBy: CharacterSet.alphanumerics.inverted) + .filter { !$0.isEmpty } + .joined(separator: " ") + } + + private static func householdVidhi( + id: String, + title: String, + sacredFocus: String, + summary: String, + purpose: String, + category: PoojaCategory, + practiceLevel: PoojaPracticeLevel, + duration: ClosedRange, + occasions: [String], + specificMaterials: [PoojaMaterial], + focusPreparation: String, + invocation: String, + offering: String, + mantra: PoojaMantra, + closingPrayer: String, + traditionNote: String, + sources: [PoojaSourceNote] + ) -> PoojaVidhi { + PoojaVidhi( + id: id, + title: title, + sacredFocus: sacredFocus, + summary: summary, + purpose: purpose, + category: category, + practiceLevel: practiceLevel, + durationMinutes: duration, + occasions: occasions, + preparation: [ + "Choose a time when you can remain unhurried and attentive.", + "Bathe or wash hands and face, wear clean clothing, and clean the worship surface.", + focusPreparation, + "Gather every item before lighting a lamp so the ritual is not left unattended." + ], + materials: commonMaterials + specificMaterials, + steps: [ + step(1, "Prepare the sacred space", focusPreparation, minutes: 2), + step(2, "Settle and centre", "Sit or stand comfortably. Take three natural breaths, reduce distractions, and bring attention to the sacred focus.", note: "No breath retention or forceful breathing is required.", minutes: 1), + step(3, "Purify symbolically", "Sprinkle a few drops of clean water around the offering area or simply hold the intention of cleanliness when water is unsuitable.", minutes: 1), + step(4, "State the sankalpa", "Name the place, day, your name if desired, and a clear devotional intention. Avoid promises or vows you may not be able to keep.", note: "A sincere statement in your own language is valid for this household guide.", minutes: 2), + step(5, "Remember Ganesha and the teachers", "Offer a brief namaskara to Ganesha, your family tradition, teachers, and all who preserved the practice. This is remembrance, not a separate elaborate rite.", minutes: 1), + step(6, "Dhyana and welcome", invocation, minutes: 2), + step(7, "Offer respectful hospitality", offering, note: "Offer only what is clean, safe, and permitted in your household practice.", minutes: 4), + step(8, "Recite with meaning", "Read the mantra once before repeating it. Pronounce gently; if uncertain, pray using its meaning in a language you know.", mantra: mantra, minutes: 4), + step(9, "Offer naivedya and quiet prayer", "Place the food or water offering before the sacred focus. Pause quietly; do not taste it before offering.", note: "Keep allergens separate and never give unsafe foods to children or animals.", minutes: 2), + step(10, "Show the light safely", "If a lamp is lit, move it in small clockwise circles at a safe distance, or simply hold folded hands before a stationary lamp. A battery lamp or no flame is acceptable.", note: "Keep hair, sleeves, decorations, children, and animals away from flame.", minutes: 2), + step(11, "Pray and offer namaskara", closingPrayer, minutes: 2), + step(12, "Close and share prasada", "Thank the sacred presence, sit quietly for a moment, extinguish unsafe flames without leaving smoke, and share the offering respectfully.", note: "Do not discard ritual water, flowers, oil, or food where they create litter, plumbing problems, fire risk, or harm to wildlife.", minutes: 2) + ], + completion: [ + "Sit quietly for at least a few breaths before clearing the altar.", + "Share safe food offerings as prasada; refrigerate perishables promptly.", + "Return reusable items clean and dry. Compost flowers only where locally appropriate.", + "Let the intention continue through one concrete, ethical action." + ], + safetyNotes: [PoojaContentPolicy.safetyScope, "Do not ingest ash, oils, powders, flowers, or ritual water unless you know they are food-safe."], + traditionNote: traditionNote, + sourceNotes: sources + ) + } + + private static var commonMaterials: [PoojaMaterial] { + [ + material("focus", "Murti, image, or other sacred focus", "A stable focus for devotion", required: true, alternative: "A clean, empty space used with reverence"), + material("water", "Clean water and a small spoon", "Symbolic purification and offering", required: false, alternative: "Omit where spills are unsafe"), + material("flower", "Fresh flower, petals, or a clean leaf", "Floral offering", required: false, alternative: "A mental offering"), + material("lamp", "Stable oil/ghee lamp or battery lamp", "Light offering", required: false, alternative: "No flame"), + material("incense", "Incense with a fireproof holder", "Fragrance offering", required: false, alternative: "Sandalwood paste, a flower, or no fragrance"), + material("naivedya", "Fruit, simple vegetarian food, or water", "Naivedya and prasada", required: false, alternative: "Clean water") + ] + } + + private static let suryaArghya = PoojaVidhi( + id: "surya-arghya", + title: "Surya Arghya", + sacredFocus: "Surya", + summary: "A short sunrise practice offering water with gratitude for light, time, health-supporting routine, and life.", + purpose: "Gratitude, disciplined morning attention, and reverence for the visible Sun.", + category: .daily, + practiceLevel: .simpleHousehold, + durationMinutes: 5...10, + occasions: ["Sunrise", "Sunday", "Daily practice", "Gratitude"], + preparation: ["Choose a safe, non-slip place with drainage or use a basin indoors.", "Know the local sunrise from the app, but do not look directly at the Sun.", "Use a small amount of clean water."], + materials: [ + material("surya-water", "Small vessel of clean water", "Arghya", required: true, alternative: "A symbolic folded-hands offering when water cannot be poured"), + material("surya-basin", "Basin or safe soil area", "Collects the water without creating a slip hazard", required: true, alternative: nil), + material("surya-flower", "Flower", "Optional offering", required: false, alternative: "Mental offering"), + material("surya-cloth", "Clean cloth", "Dry hands and any spills", required: false, alternative: nil), + material("surya-focus", "Open sky or a well-lit window", "Direction of attention", required: false, alternative: "Face east without viewing the Sun") + ], + steps: [ + step(1, "Choose a safe place", "Stand where poured water cannot make a walkway slippery or contaminate shared space.", minutes: 1), + step(2, "Face the morning light", "Face east around local sunrise. Keep your gaze below or away from the Sun; never stare at it.", minutes: 1), + step(3, "Set the intention", "Offer gratitude for light, time, warmth, and the conditions that support life.", minutes: 1), + step(4, "Hold the water", "Hold a small vessel steadily with both hands at chest level.", minutes: 1), + step(5, "Recite the name-mantra", "Recite gently or use its meaning in your own language.", mantra: PoojaMantra(title: "Surya name-mantra", devanagari: "ॐ सूर्याय नमः", transliteration: "Om sūryāya namaḥ", meaning: "Reverence to Surya.", repetition: "Repeat 3 or 11 times"), minutes: 2), + step(6, "Offer the water", "Pour a thin stream into the basin or safe soil while keeping eyes away from direct sunlight.", note: "Do not pour from balconies, into public walkways, or where water can damage property.", minutes: 1), + step(7, "Offer namaskara", "Bring palms together and bow gently.", minutes: 1), + step(8, "Close responsibly", "Wipe spills, reuse collected clean water where appropriate, and leave the place safe.", minutes: 1) + ], + completion: ["Carry the practice into a consistent, safe morning routine.", "Do not interpret the ritual as medical treatment or a substitute for sun-safety guidance."], + safetyNotes: ["Never look directly at the Sun. This app does not provide eye-safety or medical advice.", "Prevent slips and do not pour water where it may fall on people, property, or electrical equipment."], + traditionNote: "Surya worship has Vedic, Puranic, yogic, and regional forms. This is a minimal public name-mantra and water-offering practice, not Sandhyavandana or a Vedic rite.", + sourceNotes: [householdPujaSource] + ) + + private static let satyanarayana = PoojaVidhi( + id: "satyanarayana-vrata", + title: "Satyanarayana Vrata Pooja", + sacredFocus: "Satyanarayana, a form of Vishnu", + summary: "A preparation-aware guide to the Vishnu-centered vrata, including worship, katha, naivedya, community participation, and closure.", + purpose: "Gratitude, truthfulness, family prayer, and fulfillment of a consciously undertaken vrata.", + category: .vrata, + practiceLevel: .priestRecommended, + durationMinutes: 60...120, + occasions: ["Purnima", "Family milestone", "Thanksgiving", "New beginning", "Vrata"], + preparation: ["Confirm the date, form of sankalpa, katha text, and food practice with the priest or family tradition.", "Invite participants without pressuring anyone to fast.", "Plan safe seating, food hygiene, and enough time for the complete katha."], + materials: commonMaterials + [ + material("satya-katha", "Received Satyanarayana Katha text", "Essential narrative recitation", required: true, alternative: "Arrange a priest or trusted family reader"), + material("satya-kalasha", "Kalasha materials", "Used in many established forms", required: false, alternative: "Follow the officiant's list"), + material("satya-prasada", "Family-tradition prasada ingredients", "Naivedya and distribution", required: true, alternative: "Adapt allergens with the officiant before the vrata") + ], + steps: [ + step(1, "Confirm the received procedure", "Agree on the vrata text, sankalpa, language, food rules, and officiant before the day.", minutes: 5), + step(2, "Prepare the home and altar", "Clean the area, arrange safe seating, and gather the complete materials list.", minutes: 10), + step(3, "State the sankalpa", "The officiant or family elder states the intention without making coercive or impossible promises.", minutes: 5), + step(4, "Begin with Ganesha remembrance", "Offer a brief Ganesha prayer according to the received procedure.", minutes: 5), + step(5, "Kalasha and Vishnu worship", "Follow the officiant for kalasha installation and the chosen Vishnu or Satyanarayana offering sequence.", note: "Do not improvise Vedic or lineage mantras from the app.", minutes: 20), + step(6, "Recite the public name-mantra", "Use the name-mantra during a pause if it agrees with the officiant's procedure.", mantra: PoojaMantra(title: "Satyanarayana name-mantra", devanagari: "ॐ सत्यनारायणाय नमः", transliteration: "Om satyanārāyaṇāya namaḥ", meaning: "Reverence to Satyanarayana.", repetition: "As directed, or 11 times in a simple household pause"), minutes: 5), + step(7, "Listen to the complete katha", "Read or hear the complete locally received Satyanarayana Katha attentively; do not replace it with an app-generated summary.", minutes: 30), + step(8, "Offer naivedya and arati", "Present the prepared food and light safely according to the household procedure.", minutes: 10), + step(9, "Pray for truthful living", "Connect the vrata to honest speech, responsible action, gratitude, and service.", minutes: 5), + step(10, "Complete and share prasada", "Close through the officiant's visarjana or concluding prayer, then distribute prasada with allergy awareness.", minutes: 10) + ], + completion: ["Complete the whole katha form agreed at sankalpa.", "Share prasada without food pressure.", "Fulfill only the ethical, practical commitments consciously made."], + safetyNotes: [PoojaContentPolicy.safetyScope, "Fasting is not required by this app. Health needs, medication schedules, pregnancy, age, and eating-disorder risk take priority."], + traditionNote: "Vrata details and katha recensions differ. This app explains the structure but intentionally does not synthesize a supposedly universal katha or priestly mantra sequence.", + sourceNotes: [satyanarayanaSource, householdPujaSource] + ) + + private static let grihaPravesh = PoojaVidhi( + id: "griha-pravesh", + title: "Griha Pravesh Preparation", + sacredFocus: "The Divine, Vastu Purusha, and household deities", + summary: "A truthful household preparation and ceremony map for entering a new home, with formal rites clearly reserved for a qualified priest.", + purpose: "Gratitude, a mindful beginning, household safety, hospitality, and prayer for harmonious living.", + category: .lifeEvent, + practiceLevel: .priestRecommended, + durationMinutes: 60...180, + occasions: ["New home", "First entry", "Housewarming", "Vastu Shanti", "Life event"], + preparation: ["Confirm that the property is legally accessible, structurally safe, ventilated, and has working utilities.", "Choose the muhurta using the home's exact location and the family's tradition; the app's general Panchang is not a substitute for a complete electional chart.", "Ask the officiant for the exact materials, fire-safety plan, and local smoke rules."], + materials: commonMaterials + [ + material("griha-keys", "Keys and essential property documents", "Practical readiness", required: true, alternative: nil), + material("griha-safety", "Fireproof homa area and extinguisher if homa is planned", "Fire safety", required: true, alternative: "Omit homa where unsafe or prohibited"), + material("griha-kalasha", "Kalasha and doorway items", "Tradition-specific entry observance", required: false, alternative: "Use the officiant's list") + ], + steps: [ + step(1, "Complete practical safety checks", "Verify permission to enter, utilities, ventilation, alarms, exits, and a clear walking path before ritual decoration.", minutes: 10), + step(2, "Confirm muhurta and officiant", "Provide the exact address, time zone, household details, and tradition to the qualified person choosing the formal time.", minutes: 5), + step(3, "Clean without creating hazards", "Clean the entrance and worship area; secure rangoli, cords, lamps, and vessels so no one can trip.", minutes: 10), + step(4, "Prepare the doorway welcome", "Arrange the locally received doorway observance and enter in the order guided by the family or officiant.", minutes: 5), + step(5, "Begin with Ganesha remembrance", "Offer a simple prayer for clarity and a safe beginning.", minutes: 5), + step(6, "Perform kalasha or Vastu worship", "Follow the officiant for kalasha sthapana, Vastu Puja, Navagraha Puja, or other formal components.", note: "These components vary and are not generated by the app.", minutes: 30), + step(7, "Conduct homa only when safe", "A trained officiant manages any fire rite under property rules with ventilation and extinguishing equipment.", note: "Never improvise an indoor homa from written app steps.", minutes: 30), + step(8, "Offer household prayer", "Each resident may express gratitude and one intention for how the home will be cared for.", minutes: 10), + step(9, "Prepare food safely", "If boiling milk or cooking prasada is customary, supervise the stove and keep handles, children, garments, and decorations away from heat.", minutes: 15), + step(10, "Close, ventilate, and welcome", "Complete the officiant's closing, extinguish every flame, ventilate smoke, clear hazards, and welcome guests without crowding exits.", minutes: 10) + ], + completion: ["Confirm all flame, embers, gas, and electrical appliances are safe.", "Keep one accessible area quiet for elders, children, disabled guests, and anyone sensitive to smoke.", "Begin the household with an act of hospitality or service."], + safetyNotes: ["Property, fire, smoke, occupancy, and food-safety rules override ceremonial preference.", "A formal homa, Vastu Shanti, or priestly sankalpa must not be improvised from this app."], + traditionNote: "Griha Pravesh varies substantially by region, family, property type, and priestly school. The app provides a ceremony map and safety checklist, not a universal formal paddhati.", + sourceNotes: [householdPujaSource] + ) + + private static let navagraha = PoojaVidhi( + id: "navagraha-pooja", + title: "Navagraha Pooja Preparation", + sacredFocus: "The nine grahas", + summary: "A preparation and overview guide that keeps formal Navagraha worship, homa, donations, and remedial claims within qualified tradition.", + purpose: "Contemplation of time, consequence, responsibility, and harmony—not fear-based guarantees.", + category: .planetary, + practiceLevel: .priestRecommended, + durationMinutes: 45...120, + occasions: ["Navagraha", "Temple observance", "Life transition", "Planetary prayer"], + preparation: ["Ask a qualified practitioner why the rite is recommended and what tradition will be followed.", "Reject guarantees, fear-based sales, or demands for unaffordable gems and donations.", "Confirm whether the rite is temple-based, household worship, or homa."], + materials: commonMaterials + [ + material("navagraha-list", "Officiant-provided materials list", "Tradition-specific colors, grains, flowers, or cloth", required: true, alternative: nil), + material("navagraha-donation", "Optional ethical donation", "Service or charity", required: false, alternative: "A practical act of service within your means"), + material("navagraha-fire", "Approved homa setup if applicable", "Priest-led fire rite", required: false, alternative: "Temple rite or no fire") + ], + steps: [ + step(1, "Clarify the purpose", "State the concern in practical terms and separate spiritual reflection from medical, legal, or financial decisions.", minutes: 5), + step(2, "Choose a qualified setting", "Prefer an established temple, family priest, or transparent practitioner who explains lineage and costs.", minutes: 5), + step(3, "Prepare safely", "Use the provided materials list and arrange flame, smoke, food, and accessibility safeguards.", minutes: 10), + step(4, "State a non-fearful sankalpa", "Frame the intention around wisdom, responsibility, patience, and ethical action rather than guaranteed outcomes.", minutes: 5), + step(5, "Begin with customary preliminaries", "Follow the officiant for Ganesha, kalasha, and purification steps.", minutes: 10), + step(6, "Honor the grahas in sequence", "Follow the received order, names, offerings, and pronunciation. The app does not generate bija-mantras or substitute chants.", minutes: 25), + step(7, "Conduct homa only through the officiant", "If included, maintain local fire rules and never leave embers unattended.", minutes: 20), + step(8, "Connect prayer to conduct", "Identify one grounded action: accountability, service, budgeting, rest, reconciliation, or professional help where needed.", minutes: 5), + step(9, "Offer ethical dana if desired", "Give voluntarily and within your means; do not treat payment or gemstones as a guaranteed cure.", minutes: 5), + step(10, "Close and document the practice", "Complete the officiant's prayer, extinguish flames, and record the lineage or text used for future consistency.", minutes: 5) + ], + completion: ["Keep a note of the officiant, tradition, and text used.", "Do not replace evidence-based medical, legal, safety, or financial action with ritual remedies.", "Prefer voluntary service over fear-driven spending."], + safetyNotes: ["No Pooja, gemstone, donation, or mantra guarantees an outcome.", "Formal homa and lineage mantras require qualified guidance and safe facilities."], + traditionNote: "Navagraha procedures and mantra systems vary. The app intentionally provides an ethical preparation map rather than a fabricated universal ritual.", + sourceNotes: [householdPujaSource] + ) + + private static func material( + _ id: String, + _ name: String, + _ purpose: String, + required: Bool, + alternative: String? + ) -> PoojaMaterial { + PoojaMaterial(id: id, name: name, purpose: purpose, isRequired: required, alternative: alternative) + } + + private static func step( + _ number: Int, + _ title: String, + _ instruction: String, + mantra: PoojaMantra? = nil, + note: String? = nil, + minutes: Int + ) -> PoojaStep { + PoojaStep(number: number, title: title, instruction: instruction, mantra: mantra, note: note, estimatedMinutes: minutes) + } +} diff --git a/ios/native/CosmicRituals/Engine/SubscriptionStore.swift b/ios/native/CosmicRituals/Engine/SubscriptionStore.swift new file mode 100644 index 0000000..e3a5d10 --- /dev/null +++ b/ios/native/CosmicRituals/Engine/SubscriptionStore.swift @@ -0,0 +1,158 @@ +import Foundation +import StoreKit + +enum SubscriptionCatalog { + static let monthlyProductID = "com.cosmic.rituals.premium.monthly" + static let annualProductID = "com.cosmic.rituals.premium.annual" + static let productIDs = [annualProductID, monthlyProductID] + + static let privacyPolicyURL = URL(string: "https://gohlerdev.github.io/Cosmic-Rituals/privacy/")! + static let termsOfUseURL = URL(string: "https://gohlerdev.github.io/Cosmic-Rituals/terms/")! + static let supportURL = URL(string: "https://gohlerdev.github.io/Cosmic-Rituals/support/")! + static let manageSubscriptionsURL = URL(string: "https://apps.apple.com/account/subscriptions")! + + static let requestedTrialDays = 14 +} + +enum SubscriptionAccessState: Equatable { + case checking + case entitled + case locked + case storeUnavailable(String) + + var hasPremiumAccess: Bool { + self == .entitled + } +} + +enum SubscriptionEntitlementChecker { + static func grantsAccess(activeProductIDs: Set) -> Bool { + !activeProductIDs.isDisjoint(with: SubscriptionCatalog.productIDs) + } + + static func currentActiveProductIDs() async -> Set { + var activeProductIDs: Set = [] + for await result in Transaction.currentEntitlements { + guard case .verified(let transaction) = result else { continue } + guard SubscriptionCatalog.productIDs.contains(transaction.productID) else { continue } + activeProductIDs.insert(transaction.productID) + } + return activeProductIDs + } + + static func grantsCurrentAccess() async -> Bool { + grantsAccess(activeProductIDs: await currentActiveProductIDs()) + } +} + +@MainActor +final class SubscriptionStore: ObservableObject { + @Published private(set) var accessState: SubscriptionAccessState + @Published private(set) var products: [Product] = [] + @Published private(set) var isEligibleForRequestedTrial = false + @Published private(set) var isRefreshing = false + + private var transactionUpdatesTask: Task? + + init(accessState: SubscriptionAccessState = .checking, listensForTransactions: Bool = true) { + self.accessState = accessState + if listensForTransactions { + transactionUpdatesTask = Task { [weak self] in + for await result in Transaction.updates { + guard !Task.isCancelled else { return } + await self?.handle(transactionResult: result) + } + } + } + } + + deinit { + transactionUpdatesTask?.cancel() + } + + func refresh() async { + guard !isRefreshing else { return } + isRefreshing = true + defer { isRefreshing = false } + + if !accessState.hasPremiumAccess { + accessState = .checking + } + + let activeProductIDs = await SubscriptionEntitlementChecker.currentActiveProductIDs() + let hasAccess = SubscriptionEntitlementChecker.grantsAccess(activeProductIDs: activeProductIDs) + if hasAccess { + accessState = .entitled + } + + do { + let loadedProducts = try await Product.products(for: SubscriptionCatalog.productIDs) + products = loadedProducts.sorted(by: productSort) + isEligibleForRequestedTrial = await hasEligibleRequestedTrial(in: products) + + if !hasAccess { + accessState = products.isEmpty + ? .storeUnavailable("Subscriptions are temporarily unavailable. Check your connection and try again.") + : .locked + } + } catch is CancellationError { + return + } catch { + if !hasAccess { + accessState = .storeUnavailable("The App Store could not load subscription options. Check your connection and try again.") + } + } + } + + func restorePurchases() async { + do { + try await AppStore.sync() + await refresh() + } catch is CancellationError { + return + } catch { + accessState = .storeUnavailable("Purchases could not be restored. Confirm the App Store account and try again.") + } + } + + private func handle(transactionResult: VerificationResult) async { + guard case .verified(let transaction) = transactionResult else { return } + guard SubscriptionCatalog.productIDs.contains(transaction.productID) else { return } + await transaction.finish() + await refresh() + } + + private func hasEligibleRequestedTrial(in products: [Product]) async -> Bool { + for product in products { + guard let subscription = product.subscription, + let offer = subscription.introductoryOffer, + offer.paymentMode == .freeTrial, + offer.period.unit == .week, + offer.period.value == 2 else { continue } + if await subscription.isEligibleForIntroOffer { + return true + } + } + return false + } + + private func productSort(_ lhs: Product, _ rhs: Product) -> Bool { + guard let lhsPeriod = lhs.subscription?.subscriptionPeriod, + let rhsPeriod = rhs.subscription?.subscriptionPeriod else { + return lhs.id < rhs.id + } + return subscriptionPeriodDays(lhsPeriod) > subscriptionPeriodDays(rhsPeriod) + } + + private func subscriptionPeriodDays(_ period: Product.SubscriptionPeriod) -> Int { + let multiplier: Int + switch period.unit { + case .day: multiplier = 1 + case .week: multiplier = 7 + case .month: multiplier = 30 + case .year: multiplier = 365 + @unknown default: multiplier = 0 + } + return period.value * multiplier + } +} diff --git a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift index 36c1847..2181f72 100644 --- a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift +++ b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift @@ -681,6 +681,7 @@ struct RitualDestinationDescriptor: Equatable, Sendable { RitualDestinationDescriptor(title: "Panchang", symbol: "eye.fill"), RitualDestinationDescriptor(title: "Timing", symbol: "clock.fill"), RitualDestinationDescriptor(title: "Muhurtas", symbol: "clock.badge.checkmark.fill"), + RitualDestinationDescriptor(title: "Pooja", symbol: "hands.and.sparkles.fill"), RitualDestinationDescriptor(title: "Calendar", symbol: "calendar") ] } diff --git a/ios/native/CosmicRituals/Views/PanchangView.swift b/ios/native/CosmicRituals/Views/PanchangView.swift index 243f672..d691ae8 100644 --- a/ios/native/CosmicRituals/Views/PanchangView.swift +++ b/ios/native/CosmicRituals/Views/PanchangView.swift @@ -4,7 +4,6 @@ struct PanchangView: View { @State private var selectedDate = Date() @State private var panchangSnapshot: (context: CalculationContext, value: Panchang)? @State private var muhurtas: [Muhurta] = [] - @State private var selectedTab = 0 @State private var detailMuhurta: Muhurta? @State private var sunriseSunset: (Date, Date)? @State private var choghadiya: [Choghadiya] = [] @@ -18,6 +17,7 @@ struct PanchangView: View { @State private var panchangPDF: PanchangPDF? = nil @State private var now = Date() // ticked every minute to drive live "NOW" indicators @StateObject private var locationManager = LocationManager() + @AppStorage("ritualSelectedDestination") private var selectedTab = 0 @AppStorage("cosmicThemeVariant") private var variantRaw = CosmicThemeVariant.cosmicDark.rawValue @AppStorage("ritualExperienceMode") private var experienceRaw = RitualExperienceMode.ritualNow.rawValue @Environment(\.cosmicTheme) private var theme @@ -28,8 +28,10 @@ struct PanchangView: View { ZStack { CosmicStarfieldBackground() VStack(spacing: 0) { - LocationContextBar(manager: locationManager) { - showLocationPicker = true + if selectedTab != 3 { + LocationContextBar(manager: locationManager) { + showLocationPicker = true + } } if selectedTab == 0 { panchangScrollView @@ -41,35 +43,39 @@ struct PanchangView: View { } } else if selectedTab == 2 { muhurtaScrollView + } else if selectedTab == 3 { + PoojaVidhiLibraryView() } else { MonthlyCalendarView(selectedDate: $selectedDate, calculationContext: calculationContext) } } } - .navigationTitle(["Panchang", "Timing", "Muhurtas", "Calendar"][selectedTab.clamped(to: 0...3)]) + .navigationTitle(RitualDestinationDescriptor.all[selectedTab.clamped(to: 0...(RitualDestinationDescriptor.all.count - 1))].title) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { HStack(spacing: 14) { - ShareLink(item: panchangSummary, - subject: Text("Panchang for \(selectedDate.ritualDate(template: "yMd", in: calculationContext.timeZone))")) { - Image(systemName: "square.and.arrow.up") - } - .accessibilityLabel("Share Panchang summary") - .accessibilityHint("Opens the system share sheet") - Group { - if let pdf = panchangPDF { - ShareLink(item: pdf, preview: SharePreview("Panchang PDF", - image: Image(systemName: "doc.richtext.fill"))) { - Image(systemName: "doc.richtext.fill") - .foregroundStyle(theme.primary) + if selectedTab != 3 { + ShareLink(item: panchangSummary, + subject: Text("Panchang for \(selectedDate.ritualDate(template: "yMd", in: calculationContext.timeZone))")) { + Image(systemName: "square.and.arrow.up") + } + .accessibilityLabel("Share Panchang summary") + .accessibilityHint("Opens the system share sheet") + Group { + if let pdf = panchangPDF { + ShareLink(item: pdf, preview: SharePreview("Panchang PDF", + image: Image(systemName: "doc.richtext.fill"))) { + Image(systemName: "doc.richtext.fill") + .foregroundStyle(theme.primary) + } + .accessibilityLabel("Share Panchang PDF") + .accessibilityHint("Opens the system share sheet with the generated PDF") + } else { + ProgressView() + .controlSize(.small) + .accessibilityLabel("Preparing Panchang PDF") } - .accessibilityLabel("Share Panchang PDF") - .accessibilityHint("Opens the system share sheet with the generated PDF") - } else { - ProgressView() - .controlSize(.small) - .accessibilityLabel("Preparing Panchang PDF") } } Button { showThemePicker = true } label: { @@ -80,14 +86,16 @@ struct PanchangView: View { } } ToolbarItem(placement: .topBarLeading) { - Button { showLocationPicker = true } label: { - Image(systemName: locationManager.activeLocation.source == .current - ? "location.fill" : "mappin.and.ellipse") - .foregroundStyle(locationManager.activeLocation.source == .current ? theme.primary : .secondary) + if selectedTab != 3 { + Button { showLocationPicker = true } label: { + Image(systemName: locationManager.activeLocation.source == .current + ? "location.fill" : "mappin.and.ellipse") + .foregroundStyle(locationManager.activeLocation.source == .current ? theme.primary : .secondary) + } + .accessibilityLabel("Calculation location") + .accessibilityValue(locationManager.activeLocation.name) + .accessibilityHint("Choose current location or an offline city") } - .accessibilityLabel("Calculation location") - .accessibilityValue(locationManager.activeLocation.name) - .accessibilityHint("Choose current location or an offline city") } } .toolbarBackground(.ultraThinMaterial, for: .navigationBar) @@ -124,6 +132,9 @@ struct PanchangView: View { } } .onAppear { + if !RitualDestinationDescriptor.all.indices.contains(selectedTab) { + selectedTab = 0 + } recompute() Task { await NotificationManager.shared.clearPendingRitualNotifications() @@ -923,6 +934,27 @@ private struct ThemePickerSheet: View { .font(.caption) } .padding(.horizontal) + + Divider().overlay(theme.semanticDivider).padding(.horizontal) + VStack(alignment: .leading, spacing: 10) { + Text("Subscription & Support") + .font(.caption.bold()) + .foregroundStyle(theme.semanticSecondaryText) + Link(destination: SubscriptionCatalog.manageSubscriptionsURL) { + Label("Manage subscription", systemImage: "creditcard.fill") + } + Link(destination: SubscriptionCatalog.privacyPolicyURL) { + Label("Privacy policy", systemImage: "hand.raised.fill") + } + Link(destination: SubscriptionCatalog.termsOfUseURL) { + Label("Terms of use", systemImage: "doc.text.fill") + } + Link(destination: SubscriptionCatalog.supportURL) { + Label("Support", systemImage: "questionmark.circle.fill") + } + } + .font(.caption) + .padding(.horizontal) .padding(.bottom, 24) } .padding(.top) diff --git a/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift b/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift new file mode 100644 index 0000000..75333ff --- /dev/null +++ b/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift @@ -0,0 +1,878 @@ +import SwiftUI + +struct PoojaVidhiLibraryView: View { + @State private var query = "" + @State private var selectedCategory: PoojaCategory? + @Environment(\.cosmicTheme) private var theme + + private var results: [PoojaVidhi] { + PoojaVidhiCatalog.search(query, category: selectedCategory) + } + + var body: some View { + ScrollView { + LazyVStack(spacing: 16) { + libraryHeader + categoryPicker + + if results.isEmpty { + ContentUnavailableView( + "No matching Pooja Vidhi", + systemImage: "magnifyingglass", + description: Text("Try a deity, occasion, purpose, or clear the category filter.") + ) + .foregroundStyle(theme.semanticPrimaryText) + .padding(.vertical, 40) + } else { + HStack { + Text("\(results.count) guides") + .font(.caption.weight(.semibold)) + .foregroundStyle(theme.semanticSecondaryText) + Spacer() + Text("Available offline") + .font(.caption2) + .foregroundStyle(theme.semanticTertiaryText) + } + .padding(.horizontal, 4) + + ForEach(results) { vidhi in + NavigationLink { + PoojaVidhiDetailView(vidhi: vidhi) + } label: { + PoojaVidhiLibraryCard(vidhi: vidhi) + } + .buttonStyle(.plain) + .accessibilityIdentifier("pooja.vidhi.\(vidhi.id)") + } + } + } + .padding(.horizontal, 16) + .padding(.top, 12) + .padding(.bottom, 28) + } + .scrollDismissesKeyboard(.interactively) + .searchable(text: $query, prompt: "Deity, occasion, mantra, purpose") + .accessibilityIdentifier("pooja.library") + } + + private var libraryHeader: some View { + CosmicGlassCard(cornerRadius: 24, accentBorder: true) { + VStack(alignment: .leading, spacing: 13) { + HStack(alignment: .top, spacing: 12) { + ZStack { + Circle() + .fill(theme.primary.opacity(0.16)) + .frame(width: 48, height: 48) + Image(systemName: "hands.and.sparkles.fill") + .font(.title2.weight(.semibold)) + .foregroundStyle(theme.primary) + } + VStack(alignment: .leading, spacing: 4) { + Text("Pooja Vidhis") + .font(.title2.weight(.bold)) + .foregroundStyle(theme.semanticPrimaryText) + Text("Prepare · understand · proceed step by step") + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + } + } + + Label("Household guidance, not one universal rite", systemImage: "checkmark.seal.fill") + .font(.caption.weight(.semibold)) + .foregroundStyle(theme.primary) + + Text(PoojaContentPolicy.householdScope) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + } + } + .accessibilityElement(children: .combine) + .accessibilityLabel("Pooja Vidhis. Twelve offline household and preparation guides. Family and temple tradition takes precedence.") + } + + private var categoryPicker: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + categoryButton(nil, title: "All", symbol: "square.grid.2x2.fill") + ForEach(PoojaCategory.allCases) { category in + categoryButton(category, title: category.displayName, symbol: category.symbol) + } + } + .padding(.vertical, 2) + } + .accessibilityLabel("Filter Pooja Vidhis") + } + + @ViewBuilder + private func categoryButton(_ category: PoojaCategory?, title: String, symbol: String) -> some View { + let isSelected = selectedCategory == category + let button = Button { + withAnimation(.snappy(duration: 0.22)) { + selectedCategory = category + } + } label: { + Label(title, systemImage: symbol) + .font(.caption.weight(.semibold)) + .padding(.horizontal, 12) + .frame(minHeight: 38) + .foregroundStyle(isSelected ? theme.selectedControlForeground : theme.semanticSecondaryText) + } + if isSelected { + button + .buttonStyle(.glassProminent) + .tint(theme.primary) + .accessibilityValue("Selected") + } else { + button + .buttonStyle(.glass) + .tint(theme.surfaceElevated) + .accessibilityValue("Not selected") + } + } +} + +private struct PoojaVidhiLibraryCard: View { + let vidhi: PoojaVidhi + @Environment(\.cosmicTheme) private var theme + + var body: some View { + CosmicGlassCard(cornerRadius: 20) { + VStack(alignment: .leading, spacing: 12) { + HStack(alignment: .top, spacing: 12) { + ZStack { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(vidhi.category.accent.opacity(0.15)) + .frame(width: 46, height: 46) + Image(systemName: vidhi.category.symbol) + .font(.title3.weight(.semibold)) + .foregroundStyle(vidhi.category.accent) + } + VStack(alignment: .leading, spacing: 4) { + Text(vidhi.title) + .font(.headline) + .foregroundStyle(theme.semanticPrimaryText) + .multilineTextAlignment(.leading) + Text(vidhi.sacredFocus) + .font(.caption.weight(.medium)) + .foregroundStyle(theme.primary) + } + Spacer(minLength: 4) + Image(systemName: "chevron.right") + .font(.caption.weight(.bold)) + .foregroundStyle(theme.semanticTertiaryText) + .padding(.top, 6) + } + + Text(vidhi.summary) + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + + ViewThatFits(in: .horizontal) { + HStack(spacing: 8) { metadataPills } + VStack(alignment: .leading, spacing: 7) { metadataPills } + } + } + } + .accessibilityElement(children: .combine) + .accessibilityLabel("\(vidhi.title), \(vidhi.sacredFocus), \(vidhi.durationText), \(vidhi.practiceLevel.displayName). \(vidhi.summary)") + .accessibilityHint("Opens preparation, materials, steps, mantras, and guidance") + } + + @ViewBuilder + private var metadataPills: some View { + PoojaMetadataPill(text: vidhi.durationText, symbol: "clock") + PoojaMetadataPill(text: vidhi.practiceLevel.displayName, symbol: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "house.fill") + } +} + +struct PoojaVidhiDetailView: View { + let vidhi: PoojaVidhi + @State private var preparedMaterialIDs: Set = [] + @Environment(\.cosmicTheme) private var theme + + private var preparationProgress: Double { + guard !vidhi.materials.isEmpty else { return 0 } + return Double(preparedMaterialIDs.count) / Double(vidhi.materials.count) + } + + var body: some View { + ZStack { + CosmicStarfieldBackground() + ScrollView { + LazyVStack(spacing: 16) { + hero + practiceBoundary + preparationSection + materialsSection + stepsSection + safetySection + sourcesSection + } + .padding(.horizontal, 16) + .padding(.top, 12) + .padding(.bottom, 104) + } + } + .navigationTitle(vidhi.title) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.ultraThinMaterial, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbarColorScheme(theme.colorScheme, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + ShareLink(item: vidhi.shareText) { + Image(systemName: "square.and.arrow.up") + } + .accessibilityLabel("Share Pooja Vidhi") + } + } + .safeAreaInset(edge: .bottom) { + NavigationLink { + GuidedPoojaView(vidhi: vidhi) + } label: { + Label( + vidhi.practiceLevel == .priestRecommended ? "Open ceremony guide" : "Begin guided Pooja", + systemImage: "play.fill" + ) + .font(.headline) + .frame(maxWidth: .infinity) + .frame(minHeight: 52) + } + .buttonStyle(.glassProminent) + .tint(theme.primary) + .foregroundStyle(theme.selectedControlForeground) + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(.ultraThinMaterial) + .accessibilityIdentifier("pooja.begin.\(vidhi.id)") + } + } + + private var hero: some View { + CosmicGlassCard(cornerRadius: 24, accentBorder: true) { + VStack(alignment: .leading, spacing: 14) { + HStack(spacing: 12) { + ZStack { + Circle() + .fill(vidhi.category.accent.opacity(0.16)) + .frame(width: 56, height: 56) + Image(systemName: vidhi.category.symbol) + .font(.title2.weight(.semibold)) + .foregroundStyle(vidhi.category.accent) + } + VStack(alignment: .leading, spacing: 4) { + Text(vidhi.sacredFocus) + .font(.title3.weight(.bold)) + .foregroundStyle(theme.semanticPrimaryText) + Text(vidhi.purpose) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + } + } + + Text(vidhi.summary) + .font(.body) + .foregroundStyle(theme.semanticPrimaryText) + .fixedSize(horizontal: false, vertical: true) + + ViewThatFits(in: .horizontal) { + HStack(spacing: 8) { detailMetadataPills } + VStack(alignment: .leading, spacing: 8) { detailMetadataPills } + } + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 7) { + ForEach(vidhi.occasions, id: \.self) { occasion in + Text(occasion) + .font(.caption2.weight(.medium)) + .foregroundStyle(theme.semanticSecondaryText) + .padding(.horizontal, 9) + .padding(.vertical, 6) + .background(theme.surfaceElevated.opacity(0.72), in: Capsule()) + } + } + } + } + } + } + + @ViewBuilder + private var detailMetadataPills: some View { + PoojaMetadataPill(text: vidhi.durationText, symbol: "clock") + PoojaMetadataPill(text: "\(vidhi.steps.count) steps", symbol: "list.number") + PoojaMetadataPill(text: vidhi.practiceLevel.displayName, symbol: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "house.fill") + } + + private var practiceBoundary: some View { + CosmicGlassCard(cornerRadius: 18) { + HStack(alignment: .top, spacing: 12) { + Image(systemName: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "checkmark.seal.fill") + .font(.title3) + .foregroundStyle(vidhi.practiceLevel == .priestRecommended ? .orange : theme.primary) + VStack(alignment: .leading, spacing: 5) { + Text(vidhi.practiceLevel.displayName) + .font(.subheadline.weight(.bold)) + .foregroundStyle(theme.semanticPrimaryText) + Text(vidhi.practiceLevel.guidance) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + Text(vidhi.traditionNote) + .font(.caption) + .foregroundStyle(theme.semanticTertiaryText) + .fixedSize(horizontal: false, vertical: true) + } + } + } + } + + private var preparationSection: some View { + PoojaSectionCard(title: "Before you begin", symbol: "checklist") { + VStack(alignment: .leading, spacing: 12) { + ForEach(Array(vidhi.preparation.enumerated()), id: \.offset) { index, item in + PoojaNumberedLine(number: index + 1, text: item) + } + } + } + } + + private var materialsSection: some View { + PoojaSectionCard(title: "Prepare materials", symbol: "basket.fill") { + VStack(alignment: .leading, spacing: 12) { + ProgressView(value: preparationProgress) + .tint(theme.primary) + .accessibilityLabel("Materials prepared") + .accessibilityValue("\(preparedMaterialIDs.count) of \(vidhi.materials.count)") + + ForEach(vidhi.materials) { material in + Button { + if preparedMaterialIDs.contains(material.id) { + preparedMaterialIDs.remove(material.id) + } else { + preparedMaterialIDs.insert(material.id) + } + } label: { + HStack(alignment: .top, spacing: 12) { + Image(systemName: preparedMaterialIDs.contains(material.id) ? "checkmark.circle.fill" : "circle") + .font(.title3) + .foregroundStyle(preparedMaterialIDs.contains(material.id) ? theme.primary : theme.semanticTertiaryText) + VStack(alignment: .leading, spacing: 3) { + HStack(alignment: .firstTextBaseline) { + Text(material.name) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + if material.isRequired { + Text("REQUIRED") + .font(.system(size: 9, weight: .bold)) + .foregroundStyle(.orange) + } + } + Text(material.purpose) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + if let alternative = material.alternative { + Text("Alternative: \(alternative)") + .font(.caption2) + .foregroundStyle(theme.semanticTertiaryText) + } + } + Spacer(minLength: 0) + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .accessibilityLabel(material.name) + .accessibilityValue(preparedMaterialIDs.contains(material.id) ? "Prepared" : "Not prepared") + .accessibilityHint("Toggles the preparation checklist") + + if material.id != vidhi.materials.last?.id { + Divider().overlay(theme.semanticDivider) + } + } + } + } + } + + private var stepsSection: some View { + PoojaSectionCard(title: "Pooja sequence", symbol: "list.number") { + VStack(alignment: .leading, spacing: 14) { + ForEach(vidhi.steps) { step in + HStack(alignment: .top, spacing: 12) { + Text("\(step.number)") + .font(.caption.weight(.bold)) + .foregroundStyle(theme.selectedControlForeground) + .frame(width: 28, height: 28) + .background(theme.primary, in: Circle()) + VStack(alignment: .leading, spacing: 4) { + HStack(alignment: .firstTextBaseline) { + Text(step.title) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + Spacer() + Text("~\(step.estimatedMinutes)m") + .font(.caption2) + .foregroundStyle(theme.semanticTertiaryText) + } + Text(step.instruction) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .lineLimit(3) + if let mantra = step.mantra { + Label(mantra.transliteration, systemImage: "text.quote") + .font(.caption.weight(.medium)) + .foregroundStyle(theme.primary) + .lineLimit(2) + } + } + } + } + } + } + } + + private var safetySection: some View { + PoojaSectionCard(title: "Safety and respectful closure", symbol: "shield.lefthalf.filled") { + VStack(alignment: .leading, spacing: 12) { + ForEach(vidhi.safetyNotes, id: \.self) { note in + Label { + Text(note) + } icon: { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + } + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + } + + Divider().overlay(theme.semanticDivider) + + ForEach(vidhi.completion, id: \.self) { item in + Label(item, systemImage: "checkmark.circle.fill") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .labelStyle(PoojaTopAlignedLabelStyle(color: theme.primary)) + } + } + } + } + + private var sourcesSection: some View { + PoojaSectionCard(title: "Tradition and sources", symbol: "books.vertical.fill") { + VStack(alignment: .leading, spacing: 12) { + Text(PoojaContentPolicy.mantraScope) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + + Text(PoojaContentPolicy.inclusionScope) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + + Label(PoojaContentPolicy.reviewStatus, systemImage: "person.badge.clock.fill") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .labelStyle(PoojaTopAlignedLabelStyle(color: .orange)) + + ForEach(vidhi.sourceNotes) { source in + VStack(alignment: .leading, spacing: 4) { + if let url = URL(string: source.urlString) { + Link(destination: url) { + HStack { + Text(source.title) + .font(.subheadline.weight(.semibold)) + Spacer() + Image(systemName: "arrow.up.right") + .font(.caption) + } + .foregroundStyle(theme.primary) + } + } else { + Text(source.title) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + } + Text(source.detail) + .font(.caption2) + .foregroundStyle(theme.semanticTertiaryText) + } + } + + Text("Source review: \(PoojaContentPolicy.sourceReviewDate)") + .font(.caption2.monospacedDigit()) + .foregroundStyle(theme.semanticTertiaryText) + } + } + } +} + +struct GuidedPoojaView: View { + let vidhi: PoojaVidhi + @State private var currentStepIndex = 0 + @State private var isComplete = false + @Environment(\.cosmicTheme) private var theme + @Environment(\.accessibilityReduceMotion) private var reduceMotion + @Environment(\.dynamicTypeSize) private var dynamicTypeSize + + private var currentStep: PoojaStep { + vidhi.steps[currentStepIndex.clamped(to: 0...(vidhi.steps.count - 1))] + } + + var body: some View { + ZStack { + CosmicStarfieldBackground() + ScrollViewReader { proxy in + ScrollView { + VStack(spacing: 18) { + Color.clear.frame(height: 1).id("guidedPoojaTop") + progressHeader + if isComplete { + completionView + } else { + currentStepCard + if vidhi.practiceLevel == .priestRecommended { + priestBoundary + } + } + } + .padding(.horizontal, 16) + .padding(.top, 12) + .padding(.bottom, 108) + } + .onChange(of: currentStepIndex) { _, _ in + if reduceMotion { + proxy.scrollTo("guidedPoojaTop", anchor: .top) + } else { + withAnimation(.snappy) { proxy.scrollTo("guidedPoojaTop", anchor: .top) } + } + } + } + } + .navigationTitle(isComplete ? "Pooja complete" : "Step \(currentStep.number) of \(vidhi.steps.count)") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.ultraThinMaterial, for: .navigationBar) + .toolbarBackground(.visible, for: .navigationBar) + .toolbarColorScheme(theme.colorScheme, for: .navigationBar) + .safeAreaInset(edge: .bottom) { + if !isComplete { navigationControls } + } + .sensoryFeedback(.success, trigger: isComplete) + .accessibilityIdentifier("pooja.guided.\(vidhi.id)") + } + + private var progressHeader: some View { + CosmicGlassCard(cornerRadius: 18) { + VStack(alignment: .leading, spacing: 10) { + HStack { + VStack(alignment: .leading, spacing: 3) { + Text(vidhi.title) + .font(.headline) + .foregroundStyle(theme.semanticPrimaryText) + Text(isComplete ? "Respectful closure" : currentStep.title) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + } + Spacer() + Text(isComplete ? "Done" : "\(currentStep.number)/\(vidhi.steps.count)") + .font(.caption.monospacedDigit().weight(.bold)) + .foregroundStyle(theme.primary) + } + ProgressView(value: isComplete ? 1 : Double(currentStep.number), total: Double(vidhi.steps.count)) + .tint(theme.primary) + .accessibilityLabel("Pooja progress") + .accessibilityValue(isComplete ? "Complete" : "Step \(currentStep.number) of \(vidhi.steps.count)") + } + } + } + + private var currentStepCard: some View { + CosmicGlassCard(cornerRadius: 26, accentBorder: true) { + VStack(alignment: .leading, spacing: 18) { + HStack(alignment: .top) { + Text("\(currentStep.number)") + .font(.title2.monospacedDigit().weight(.bold)) + .foregroundStyle(theme.selectedControlForeground) + .frame(width: 48, height: 48) + .background(theme.primary, in: Circle()) + VStack(alignment: .leading, spacing: 4) { + Text(currentStep.title) + .font(.title2.weight(.bold)) + .foregroundStyle(theme.semanticPrimaryText) + Label("About \(currentStep.estimatedMinutes) min", systemImage: "clock") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + } + } + + Text(currentStep.instruction) + .font(.title3) + .foregroundStyle(theme.semanticPrimaryText) + .fixedSize(horizontal: false, vertical: true) + + if let mantra = currentStep.mantra { + mantraCard(mantra) + } + + if let note = currentStep.note { + Label { + Text(note) + .fixedSize(horizontal: false, vertical: true) + } icon: { + Image(systemName: "info.circle.fill") + .foregroundStyle(.orange) + } + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + .padding(12) + .background(.orange.opacity(0.10), in: RoundedRectangle(cornerRadius: 14, style: .continuous)) + } + } + } + .accessibilityElement(children: .contain) + } + + private func mantraCard(_ mantra: PoojaMantra) -> some View { + VStack(alignment: .leading, spacing: 12) { + Label(mantra.title, systemImage: "text.quote") + .font(.subheadline.weight(.bold)) + .foregroundStyle(theme.primary) + Text(mantra.devanagari) + .font(.title2.weight(.semibold)) + .foregroundStyle(theme.semanticPrimaryText) + .textSelection(.enabled) + Text(mantra.transliteration) + .font(.body.italic()) + .foregroundStyle(theme.semanticPrimaryText) + .textSelection(.enabled) + Divider().overlay(theme.semanticDivider) + Text(mantra.meaning) + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + Label(mantra.repetition, systemImage: "repeat") + .font(.caption.weight(.medium)) + .foregroundStyle(theme.semanticTertiaryText) + } + .padding(14) + .background(theme.surfaceElevated.opacity(0.74), in: RoundedRectangle(cornerRadius: 16, style: .continuous)) + .accessibilityElement(children: .combine) + .accessibilityLabel("\(mantra.title). \(mantra.transliteration). Meaning: \(mantra.meaning). \(mantra.repetition)") + } + + private var priestBoundary: some View { + CosmicGlassCard(cornerRadius: 18) { + Label { + Text("Pause whenever the guide reaches officiant-led mantras, homa, nyasa, kalasha installation, or formal visarjana. Follow the qualified priest or your received family procedure.") + .fixedSize(horizontal: false, vertical: true) + } icon: { + Image(systemName: "person.badge.shield.checkmark.fill") + .foregroundStyle(.orange) + } + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + } + } + + private var navigationControls: some View { + Group { + if dynamicTypeSize.isAccessibilitySize { + VStack(spacing: 10) { + nextButton + previousButton + } + } else { + HStack(spacing: 12) { + previousButton + nextButton + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(.ultraThinMaterial) + } + + private var previousButton: some View { + Button { + currentStepIndex = max(0, currentStepIndex - 1) + } label: { + Label("Previous", systemImage: "chevron.left") + .frame(maxWidth: .infinity) + .frame(minHeight: 50) + } + .buttonStyle(.glass) + .disabled(currentStepIndex == 0) + } + + private var nextButton: some View { + Button { + if currentStepIndex == vidhi.steps.count - 1 { + isComplete = true + } else { + currentStepIndex += 1 + } + } label: { + Label( + currentStepIndex == vidhi.steps.count - 1 ? "Complete" : "Next", + systemImage: currentStepIndex == vidhi.steps.count - 1 ? "checkmark" : "chevron.right" + ) + .frame(maxWidth: .infinity) + .frame(minHeight: 50) + } + .buttonStyle(.glassProminent) + .tint(theme.primary) + .foregroundStyle(theme.selectedControlForeground) + } + + private var completionView: some View { + VStack(spacing: 16) { + CosmicGlassCard(cornerRadius: 26, accentBorder: true) { + VStack(spacing: 16) { + Image(systemName: "hands.and.sparkles.fill") + .font(.system(size: 44, weight: .semibold)) + .foregroundStyle(theme.primary) + Text("Pooja complete") + .font(.title.bold()) + .foregroundStyle(theme.semanticPrimaryText) + Text("Sit quietly before clearing the space. Let the intention continue through thoughtful action.") + .font(.body) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + } + + PoojaSectionCard(title: "Close respectfully", symbol: "checkmark.seal.fill") { + VStack(alignment: .leading, spacing: 12) { + ForEach(vidhi.completion, id: \.self) { item in + Label(item, systemImage: "checkmark.circle.fill") + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + .labelStyle(PoojaTopAlignedLabelStyle(color: theme.primary)) + } + } + } + + Button { + currentStepIndex = 0 + isComplete = false + } label: { + Label("Review from the beginning", systemImage: "arrow.counterclockwise") + .frame(maxWidth: .infinity) + .frame(minHeight: 50) + } + .buttonStyle(.glass) + } + } +} + +private struct PoojaSectionCard: View { + let title: String + let symbol: String + @ViewBuilder let content: () -> Content + + init(title: String, symbol: String, @ViewBuilder content: @escaping () -> Content) { + self.title = title + self.symbol = symbol + self.content = content + } + + var body: some View { + CosmicGlassCard(cornerRadius: 20) { + VStack(alignment: .leading, spacing: 14) { + CosmicSectionHeader(title: title, icon: symbol) + content() + } + } + } +} + +private struct PoojaMetadataPill: View { + let text: String + let symbol: String + @Environment(\.cosmicTheme) private var theme + + var body: some View { + Label(text, systemImage: symbol) + .font(.caption2.weight(.semibold)) + .foregroundStyle(theme.semanticSecondaryText) + .padding(.horizontal, 9) + .padding(.vertical, 6) + .background(theme.surfaceElevated.opacity(0.74), in: Capsule()) + } +} + +private struct PoojaNumberedLine: View { + let number: Int + let text: String + @Environment(\.cosmicTheme) private var theme + + var body: some View { + HStack(alignment: .top, spacing: 10) { + Text("\(number)") + .font(.caption2.monospacedDigit().weight(.bold)) + .foregroundStyle(theme.primary) + .frame(width: 22, height: 22) + .background(theme.primary.opacity(0.12), in: Circle()) + Text(text) + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + } + } +} + +private struct PoojaTopAlignedLabelStyle: LabelStyle { + let color: Color + + func makeBody(configuration: Configuration) -> some View { + HStack(alignment: .top, spacing: 9) { + configuration.icon + .foregroundStyle(color) + .padding(.top, 1) + configuration.title + .fixedSize(horizontal: false, vertical: true) + } + } +} + +private extension PoojaCategory { + var accent: Color { + switch self { + case .daily: return .yellow + case .deity: return .orange + case .festival: return .pink + case .vrata: return .purple + case .lifeEvent: return .teal + case .planetary: return .indigo + } + } +} + +#Preview("Pooja library") { + NavigationStack { + ZStack { + CosmicStarfieldBackground() + PoojaVidhiLibraryView() + } + .navigationTitle("Pooja") + } + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .preferredColorScheme(.dark) +} + +#Preview("Lakshmi Vidhi") { + NavigationStack { + PoojaVidhiDetailView(vidhi: PoojaVidhiCatalog.all[2]) + } + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .preferredColorScheme(.dark) +} + +#Preview("Guided Pooja") { + NavigationStack { + GuidedPoojaView(vidhi: PoojaVidhiCatalog.all[1]) + } + .environment(\.cosmicTheme, CosmicColorScheme.cloudDancer) + .preferredColorScheme(.light) +} diff --git a/ios/native/CosmicRituals/Views/RootView.swift b/ios/native/CosmicRituals/Views/RootView.swift index faf146c..f09f271 100644 --- a/ios/native/CosmicRituals/Views/RootView.swift +++ b/ios/native/CosmicRituals/Views/RootView.swift @@ -2,22 +2,46 @@ import SwiftUI /// App root: owns the active theme variant and injects it into the environment. /// `PanchangView` reads/writes the same `@AppStorage` key to drive the picker. +@MainActor struct RootView: View { @AppStorage("cosmicThemeVariant") private var variantRaw = CosmicThemeVariant.cosmicDark.rawValue + @StateObject private var subscriptionStore: SubscriptionStore + @Environment(\.scenePhase) private var scenePhase + + init() { + _subscriptionStore = StateObject(wrappedValue: SubscriptionStore()) + } + + init(subscriptionStore: SubscriptionStore) { + _subscriptionStore = StateObject(wrappedValue: subscriptionStore) + } private var activeScheme: CosmicColorScheme { (CosmicThemeVariant(rawValue: variantRaw) ?? .cosmicDark).colorScheme } var body: some View { - PanchangView() + Group { + if subscriptionStore.accessState.hasPremiumAccess { + PanchangView() + } else { + SubscriptionGateView(store: subscriptionStore) + } + } .environment(\.cosmicTheme, activeScheme) .environment(\.colorScheme, activeScheme.colorScheme) .preferredColorScheme(activeScheme.colorScheme) .tint(activeScheme.primary) + .task { + await subscriptionStore.refresh() + } + .onChange(of: scenePhase) { _, phase in + guard phase == .active else { return } + Task { await subscriptionStore.refresh() } + } } } #Preview { - RootView() + RootView(subscriptionStore: SubscriptionStore(accessState: .entitled, listensForTransactions: false)) } diff --git a/ios/native/CosmicRituals/Views/SubscriptionViews.swift b/ios/native/CosmicRituals/Views/SubscriptionViews.swift new file mode 100644 index 0000000..a79b359 --- /dev/null +++ b/ios/native/CosmicRituals/Views/SubscriptionViews.swift @@ -0,0 +1,190 @@ +import StoreKit +import SwiftUI + +struct SubscriptionGateView: View { + @ObservedObject var store: SubscriptionStore + @Environment(\.cosmicTheme) private var theme + + var body: some View { + ZStack { + CosmicStarfieldBackground() + switch store.accessState { + case .storeUnavailable(let message): + unavailableView(message: message) + case .checking, .locked, .entitled: + subscriptionStoreView + } + + if store.accessState == .checking { + loadingOverlay + } + } + .accessibilityIdentifier("subscription.gate") + } + + private var subscriptionStoreView: some View { + SubscriptionStoreView(productIDs: SubscriptionCatalog.productIDs) { + marketingHeader + } + .subscriptionStoreControlStyle(.picker) + .storeButton(.visible, for: .restorePurchases) + .storeButton(.hidden, for: .cancellation) + .subscriptionStorePolicyDestination(url: SubscriptionCatalog.privacyPolicyURL, for: .privacyPolicy) + .subscriptionStorePolicyDestination(url: SubscriptionCatalog.termsOfUseURL, for: .termsOfService) + .subscriptionStorePolicyForegroundStyle(theme.semanticSecondaryText) + .tint(theme.primary) + } + + private var marketingHeader: some View { + VStack(spacing: 18) { + Image(systemName: "sparkles.rectangle.stack.fill") + .font(.system(size: 50, weight: .semibold)) + .foregroundStyle(theme.primary) + .accessibilityHidden(true) + + VStack(spacing: 8) { + Text(store.isEligibleForRequestedTrial ? "Your first 14 days are free" : "Cosmic Rituals Premium") + .font(.largeTitle.bold()) + .foregroundStyle(theme.semanticPrimaryText) + .multilineTextAlignment(.center) + Text("Precise Panchang, local muhurta intelligence, the complete offline Pooja library, calendar, sharing, and shortcuts.") + .font(.body) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + } + + VStack(alignment: .leading, spacing: 10) { + benefit("Location-aware Panchang and transition times", symbol: "location.fill") + benefit("All 30 day and night muhurtas", symbol: "clock.badge.checkmark.fill") + benefit("12 sourced Pooja Vidhis available offline", symbol: "hands.and.sparkles.fill") + benefit("Private by design — calculations stay on device", symbol: "lock.shield.fill") + } + .frame(maxWidth: 560) + + if store.isEligibleForRequestedTrial { + Text("Choose a plan to begin the 14-day free trial. After the trial, it renews automatically at the displayed price unless cancelled.") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal, 18) + .padding(.top, 24) + } + + private func benefit(_ text: String, symbol: String) -> some View { + Label { + Text(text) + .fixedSize(horizontal: false, vertical: true) + } icon: { + Image(systemName: symbol) + .foregroundStyle(theme.primary) + } + .font(.subheadline.weight(.medium)) + .foregroundStyle(theme.semanticPrimaryText) + } + + private var loadingOverlay: some View { + VStack(spacing: 12) { + ProgressView() + .controlSize(.large) + .tint(theme.primary) + Text("Checking App Store access…") + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + } + .padding(24) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 22, style: .continuous)) + .accessibilityElement(children: .combine) + .accessibilityLabel("Checking App Store access") + } + + private func unavailableView(message: String) -> some View { + ScrollView { + VStack(spacing: 24) { + marketingHeader + + VStack(spacing: 14) { + Image(systemName: "wifi.exclamationmark") + .font(.title2.weight(.semibold)) + .foregroundStyle(theme.primary) + .accessibilityHidden(true) + + Text("The App Store is unavailable") + .font(.title3.bold()) + .foregroundStyle(theme.semanticPrimaryText) + + Text(message) + .font(.subheadline) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Button { + Task { await store.refresh() } + } label: { + Label("Try again", systemImage: "arrow.clockwise") + .frame(maxWidth: .infinity) + .frame(minHeight: 44) + } + .buttonStyle(.glassProminent) + .tint(theme.primary) + .foregroundStyle(theme.selectedControlForeground) + .disabled(store.isRefreshing) + + Button("Restore purchases") { + Task { await store.restorePurchases() } + } + .font(.subheadline.weight(.semibold)) + .frame(minHeight: 44) + .disabled(store.isRefreshing) + + Text("A connection is required to start a subscription. Existing verified access is preserved when product information cannot load.") + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + } + .padding(20) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous)) + .accessibilityElement(children: .contain) + + HStack(spacing: 18) { + Link("Privacy", destination: SubscriptionCatalog.privacyPolicyURL) + Link("Terms", destination: SubscriptionCatalog.termsOfUseURL) + Link("Support", destination: SubscriptionCatalog.supportURL) + } + .font(.caption.weight(.semibold)) + .foregroundStyle(theme.semanticSecondaryText) + .frame(minHeight: 44) + } + .frame(maxWidth: 620) + .padding(.horizontal, 18) + .padding(.bottom, 24) + .frame(maxWidth: .infinity) + } + .scrollIndicators(.hidden) + .accessibilityIdentifier("subscription.unavailable") + } +} + +#Preview("Eligible trial") { + SubscriptionGateView( + store: SubscriptionStore(accessState: .locked, listensForTransactions: false) + ) + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .preferredColorScheme(.dark) +} + +#Preview("Store unavailable") { + SubscriptionGateView( + store: SubscriptionStore( + accessState: .storeUnavailable("The App Store could not load subscription options."), + listensForTransactions: false + ) + ) + .environment(\.cosmicTheme, CosmicColorScheme.cloudDancer) + .preferredColorScheme(.light) +} diff --git a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift index 0d5fb1d..66fa93e 100644 --- a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift +++ b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift @@ -516,11 +516,93 @@ final class CosmicEngineTests: XCTestCase { XCTAssertTrue(RitualResponsiveLayout.usesIconOnlyDestinations(for: .accessibility2)) let titles = RitualDestinationDescriptor.all.map(\.title) - XCTAssertEqual(titles, ["Panchang", "Timing", "Muhurtas", "Calendar"]) - XCTAssertEqual(Set(titles).count, 4) + XCTAssertEqual(titles, ["Panchang", "Timing", "Muhurtas", "Pooja", "Calendar"]) + XCTAssertEqual(Set(titles).count, 5) XCTAssertTrue(RitualDestinationDescriptor.all.allSatisfy { !$0.symbol.isEmpty }) } + func testPoojaVidhiCatalogIsStructuredCompleteAndTraceable() { + XCTAssertGreaterThanOrEqual(PoojaVidhiCatalog.all.count, 12) + XCTAssertEqual(PoojaVidhiCatalog.validationIssues, []) + + for vidhi in PoojaVidhiCatalog.all { + XCTAssertFalse(vidhi.summary.isEmpty, vidhi.id) + XCTAssertFalse(vidhi.traditionNote.isEmpty, vidhi.id) + XCTAssertFalse(vidhi.sourceNotes.isEmpty, vidhi.id) + XCTAssertTrue(vidhi.sourceNotes.allSatisfy { $0.urlString.hasPrefix("https://") }, vidhi.id) + XCTAssertGreaterThanOrEqual(vidhi.durationMinutes.lowerBound, 5, vidhi.id) + XCTAssertLessThanOrEqual(vidhi.durationMinutes.lowerBound, vidhi.durationMinutes.upperBound, vidhi.id) + } + } + + func testSimpleHouseholdPoojasIncludeUnderstandablePublicMantras() { + let householdVidhis = PoojaVidhiCatalog.all.filter { $0.practiceLevel == .simpleHousehold } + XCTAssertFalse(householdVidhis.isEmpty) + + for vidhi in householdVidhis { + let mantras = vidhi.steps.compactMap(\.mantra) + XCTAssertFalse(mantras.isEmpty, vidhi.id) + XCTAssertTrue(mantras.allSatisfy { + !$0.devanagari.isEmpty && !$0.transliteration.isEmpty && !$0.meaning.isEmpty + }, vidhi.id) + } + } + + func testPoojaSearchCoversPanditGPTPublicStarterPillars() { + XCTAssertEqual(PoojaVidhiCatalog.search("Lakshmi at home").first?.id, "lakshmi-home") + XCTAssertEqual(PoojaVidhiCatalog.search("Griha Pravesh").first?.id, "griha-pravesh") + XCTAssertTrue(PoojaVidhiCatalog.search("Navratri").contains { $0.id == "durga-navratri-home" }) + XCTAssertTrue(PoojaVidhiCatalog.search("mantra meaning").allSatisfy { + $0.steps.contains { $0.mantra != nil } + }) + XCTAssertTrue(PoojaVidhiCatalog.search("planetary", category: .planetary).allSatisfy { + $0.category == .planetary + }) + } + + func testSubscriptionPolicyRecognizesOnlyConfiguredProductsAndSecureLegalLinks() { + XCTAssertEqual(Set(SubscriptionCatalog.productIDs).count, 2) + XCTAssertTrue(SubscriptionCatalog.productIDs.allSatisfy { + $0.hasPrefix("com.cosmic.rituals.premium.") + }) + XCTAssertEqual(SubscriptionCatalog.requestedTrialDays, 14) + XCTAssertFalse(SubscriptionEntitlementChecker.grantsAccess(activeProductIDs: [])) + XCTAssertFalse(SubscriptionEntitlementChecker.grantsAccess(activeProductIDs: ["unrelated.product"])) + XCTAssertTrue(SubscriptionEntitlementChecker.grantsAccess( + activeProductIDs: [SubscriptionCatalog.annualProductID] + )) + XCTAssertTrue([ + SubscriptionCatalog.privacyPolicyURL, + SubscriptionCatalog.termsOfUseURL, + SubscriptionCatalog.supportURL, + SubscriptionCatalog.manageSubscriptionsURL + ].allSatisfy { $0.scheme == "https" }) + } + + func testStoreKitConfigurationDeclaresTwoWeekTrialsForEveryProduct() throws { + let nativeRoot = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + let url = nativeRoot.appendingPathComponent("StoreKit/CosmicRituals.storekit") + XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) + let data = try Data(contentsOf: url) + let root = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any]) + let groups = try XCTUnwrap(root["subscriptionGroups"] as? [[String: Any]]) + let subscriptions = groups.flatMap { $0["subscriptions"] as? [[String: Any]] ?? [] } + + XCTAssertEqual(Set(subscriptions.compactMap { $0["productID"] as? String }), Set(SubscriptionCatalog.productIDs)) + XCTAssertEqual(Set(subscriptions.compactMap { $0["recurringSubscriptionPeriod"] as? String }), ["P1M", "P1Y"]) + + for subscription in subscriptions { + let productID = try XCTUnwrap(subscription["productID"] as? String) + let offers = try XCTUnwrap(subscription["introductoryOffers"] as? [[String: Any]], productID) + let offer = try XCTUnwrap(offers.first, productID) + XCTAssertEqual(offer["paymentMode"] as? String, "free", productID) + XCTAssertEqual(offer["subscriptionPeriod"] as? String, "P2W", productID) + XCTAssertEqual(offer["numberOfPeriods"] as? Int, 1, productID) + } + } + func testAccessibilityLargeLocationMetadataKeepsFullTimeZone() { XCTAssertEqual( RitualResponsiveLayout.locationMetadataLines( @@ -680,6 +762,70 @@ final class CosmicEngineTests: XCTestCase { XCTAssertEqual(reasons, ["CA92.1"]) } + @MainActor + func testPoojaPrimaryScreensRenderAtPhoneAndAccessibilitySizes() async { + let library = NavigationStack { + ZStack { + CosmicStarfieldBackground() + PoojaVidhiLibraryView() + } + .navigationTitle("Pooja") + } + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .preferredColorScheme(.dark) + + let detail = NavigationStack { + PoojaVidhiDetailView(vidhi: PoojaVidhiCatalog.all[2]) + } + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .preferredColorScheme(.dark) + + let guided = NavigationStack { + GuidedPoojaView(vidhi: PoojaVidhiCatalog.all[1]) + } + .environment(\.cosmicTheme, CosmicColorScheme.cloudDancer) + .environment(\.dynamicTypeSize, .accessibility2) + .preferredColorScheme(.light) + + await attachSnapshot(library, name: "Pooja Library") + await attachSnapshot(detail, name: "Lakshmi Vidhi Detail") + await attachSnapshot(guided, name: "Guided Pooja Accessibility Text") + } + + @MainActor + private func attachSnapshot(_ content: Content, name: String) async { + let size = CGSize(width: 390, height: 844) + let controller = UIHostingController(rootView: content) + guard let windowScene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first else { + XCTFail("A window scene is required for the rendering contract") + return + } + let window = UIWindow(windowScene: windowScene) + window.frame = CGRect(origin: .zero, size: size) + window.rootViewController = controller + window.makeKeyAndVisible() + controller.beginAppearanceTransition(true, animated: false) + controller.endAppearanceTransition() + controller.view.frame = window.bounds + controller.view.setNeedsLayout() + controller.view.layoutIfNeeded() + await Task.yield() + try? await Task.sleep(nanoseconds: 150_000_000) + controller.view.setNeedsLayout() + controller.view.layoutIfNeeded() + + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + controller.view.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) + } + XCTAssertEqual(image.size, size) + let attachment = XCTAttachment(image: image) + attachment.name = name + attachment.lifetime = .keepAlways + add(attachment) + window.isHidden = true + } + private func contrast(_ foreground: Color, over background: Color) -> Double { let foregroundRGBA = rgba(foreground) let backgroundRGBA = rgba(background) diff --git a/ios/native/POOJA_CONTENT.md b/ios/native/POOJA_CONTENT.md new file mode 100644 index 0000000..419297e --- /dev/null +++ b/ios/native/POOJA_CONTENT.md @@ -0,0 +1,65 @@ +# Pooja Vidhi content contract + +Cosmic Rituals provides an offline, inclusive household reference—not a claim that one +Pooja Vidhi is canonical for every family, temple, region, or sampradaya. A received +family or temple paddhati takes precedence. + +## Product scope + +The first catalog contains twelve structured guides: + +| Practice | Level | App coverage | +| --- | --- | --- | +| Daily Panchopachara | Simple household | Complete simple sequence | +| Shri Ganesha | Simple household | Complete simple sequence | +| Mahalakshmi | Simple household | Short Panchopachara form | +| Shiva | Simple household | Water-only optional abhisheka boundary | +| Vishnu | Simple household | Complete simple sequence | +| Durga and Navratri | Extended household | Daily worship; kalasha/fasting variations disclosed | +| Hanuman | Simple household | Complete simple sequence; oil/sindoor not prescribed | +| Saraswati | Simple household | Complete simple sequence with book/instrument safety | +| Surya Arghya | Simple household | Complete safe water-offering sequence | +| Satyanarayana Vrata | Priest recommended | Preparation and ceremony map; received katha required | +| Griha Pravesh | Priest recommended | Household preparation, safety, and ceremony map | +| Navagraha Pooja | Priest recommended | Ethical preparation and ceremony map; no remedial guarantees | + +Each item includes purpose, occasions, duration, preparation, a material checklist, +ordered steps, respectful completion, safety notes, tradition scope, and source notes. +Short public name-mantras include Devanagari, IAST-style transliteration, plain meaning, +and a non-compulsory repetition suggestion. + +## Boundaries + +- The app does not generate initiatory bija-mantras, nyasa, Vedic recitation, homa, + formal kalasha installation, or lineage-specific visarjana. +- Priest-recommended guides describe household preparation and the ceremony's shape; + they stop at the officiant's boundary. +- Rituals do not guarantee material, medical, legal, relationship, or astrological + outcomes. Fear-based purchasing, unaffordable donations, and gemstone guarantees are + explicitly rejected. +- Fasting is optional and is not prescribed. Health needs, medication schedules, + pregnancy, age, and eating-disorder risk take priority. +- Fire, smoke, water, food, allergy, accessibility, property, and local-law constraints + override ceremonial preference. +- Participation is not restricted by caste, gender, menstruation, marital status, or + birth. Posture and movement may be adapted for disability, age, safety, and comfort. + +## Provenance and adaptation + +The linked **PanditGPT** listing inspired five discoverability pillars—at-home Lakshmi +Pooja, mantra meaning, Navratri practice, Griha Pravesh, and location-aware muhurta. +Only its public description and prompt starters were visible; no hidden instructions or +unseen answers were copied or treated as ritual authority. + +- [PanditGPT public listing](https://chatgpt.com/g/g-67c41c53568c8191a29edabd3ce907d0-panditgpt) +- [Himalayan Academy — Home Puja](https://www.himalayanacademy.com/media/books/home-puja_ei/web/ch24a.html): common preparation, offering, prasada, and quiet-closing pattern. Its detailed rite remains identified as a Saiva Ganesha form. +- [BAPS — Daily Puja](https://www.baps.org/Spiritual-Living/Hindu-Practices/Daily-Puja.aspx): cleanliness, regularity, meditation, prayer, and closure, with Swaminarayan-specific elements not generalized. +- [Drik Panchang — Lakshmi Puja Vidhi](https://www.drikpanchang.com/festivals/lakshmipuja/info/lakshmi-puja-vidhi.html): progression of the formal sixteen-offering Lakshmi rite, adapted to a clearly labeled short household form. +- [Drik Panchang — Durga Puja Vidhi](https://www.drikpanchang.com/navratri/durga-puja/puja-vidhi/durga-puja-vidhi.html): broad Navratri worship progression; formal Ghatasthapana and Shodashopachara remain outside the simple guide. +- [Drik Panchang — Satyanarayana Puja Vidhi](https://www.drikpanchang.com/hindu-gods/trimurti/lord-vishnu/puja-vidhi/satyanarayan-puja-vidhi.html): broad vrata structure; the app requires a locally received complete katha instead of synthesizing one. + +The catalog's executable validation checks unique identifiers, sequential steps, +minimum preparation detail, safety notes, source transparency, and complete mantra +fields. The source-adaptation review was completed on **2026-07-28**. This is not +sampradaya-specific practitioner certification; formal rites and received practice must +still be confirmed with a qualified practitioner. diff --git a/ios/native/README.md b/ios/native/README.md index cfc88c8..d8b389b 100644 --- a/ios/native/README.md +++ b/ios/native/README.md @@ -16,6 +16,15 @@ cosmic-glass look and its sidereal (Lahiri ayanamsha) ephemeris. classical auspiciousness (Excellent / Auspicious / Mixed / Avoid), with the **current** muhurta highlighted live against the clock. - **Auspicious & inauspicious kala** — Rahu Kala and Yamaganda for the weekday. +- **12 offline Pooja Vidhis** — searchable daily, deity, festival, vrata, life-event, + and planetary guides with materials, preparation, ordered steps, Devanagari, + transliteration, mantra meaning, safety, respectful closure, and guided mode. +- **Tradition-aware ritual boundaries** — household guides are separated from + priest-recommended rites; initiatory mantras, homa instructions, and universalized + claims are intentionally excluded. +- **StoreKit 2 Premium access** — verified App Store entitlements, transaction updates, + restore purchases, localized subscription UI, and eligible 14-day introductory + trials. No local trial timer or unverified receipt fallback. ## Build & run @@ -30,7 +39,7 @@ iOS 26 simulator or device. ``` CosmicRituals/ ├── App/ @main entry → RootView -├── Engine/ pure-Foundation Panchang ephemeris + models +├── Engine/ pure-Foundation Panchang ephemeris + Pooja catalog/models ├── Theme/ shared cosmic-glass theme & components └── Views/ RootView + PanchangView ``` @@ -38,6 +47,12 @@ CosmicRituals/ See [ARCHITECTURE.md](ARCHITECTURE.md) for the engine details and exactly what was imported from the parent Cosmic Astrology app. +See [POOJA_CONTENT.md](POOJA_CONTENT.md) for the ritual-content contract, source +provenance, inclusion policy, and the household/priest boundary. + +See [SUBSCRIPTION_RELEASE.md](SUBSCRIPTION_RELEASE.md) for product identifiers, +entitlement behavior, App Store Connect setup, policy URLs, and the sandbox test matrix. + ## Accuracy and integrity Positions use Meeus algorithms with the Lahiri (Chitra Paksha) ayanamsha. Muhurta diff --git a/ios/native/SUBSCRIPTION_RELEASE.md b/ios/native/SUBSCRIPTION_RELEASE.md new file mode 100644 index 0000000..c43ffa0 --- /dev/null +++ b/ios/native/SUBSCRIPTION_RELEASE.md @@ -0,0 +1,66 @@ +# Subscription and release contract + +Cosmic Rituals uses StoreKit 2 and App Store-signed transactions. It never starts a +local countdown or grants a trial from a device timestamp. + +## Products + +All products belong to the **Cosmic Rituals Premium** subscription group: + +| Reference name | Product ID | Period | Launch price basis | Introductory offer | +| --- | --- | --- | --- | --- | +| Premium Annual | `com.cosmic.rituals.premium.annual` | 1 year | USD 49.99 | 2 weeks free | +| Premium Monthly | `com.cosmic.rituals.premium.monthly` | 1 month | USD 6.99 | 2 weeks free | + +App Store Connect supplies localized prices and eligibility. Apple permits a two-week +free introductory offer for monthly and yearly subscriptions; each customer can redeem +only one introductory offer in a subscription group. + +## Access behavior + +- Access is granted only for a verified transaction returned by + `Transaction.currentEntitlements` for one of the configured product IDs. +- Unverified transactions never unlock the app. +- Revoked and refunded transactions disappear from current entitlements and therefore + lose access on refresh. +- Transaction updates are observed for purchases, renewals, revocations, and changes + made on another device. +- Existing verified subscribers remain entitled if product metadata temporarily fails + to load. A new customer sees an actionable retry state instead of an empty screen. +- Restore Purchases calls `AppStore.sync()` and the StoreKit subscription surface also + exposes the system restore action. +- App Shortcuts use the same entitlement check and do not bypass the Premium gate. + +## App Store Connect configuration + +1. Create the subscription group and both product IDs exactly as shown above. +2. Add English display names and descriptions matching the bundled StoreKit fixture. +3. Configure Family Sharing for both products if the storefront agreement permits it. +4. Set the standard prices, with App Store Connect generating territory equivalents. +5. Add a **Free · 2 Weeks** introductory offer to both products for all intended + storefronts. Set the start date to the release date with no artificial end date. +6. Add the subscriptions to version 1.0 before submission. +7. Use the published URLs below for App Privacy, Terms, and Support. + +## Public URLs + +- Privacy: +- Terms: +- Support: + +## Release test matrix + +- Eligible sandbox user sees the two-week offer and localized renewal price. +- Ineligible user sees standard pricing without a free-trial claim. +- Purchase unlocks without relaunching. +- Cancelled auto-renew retains access through the paid period. +- Expired, revoked, or refunded transaction locks access after refresh. +- Billing-grace entitlement remains accessible because StoreKit includes it in current + entitlements. +- Restore succeeds for the purchasing Apple Account and is harmless with no purchases. +- Offline launch succeeds for a locally verifiable current entitlement; first purchase + requires App Store connectivity. + +The checked-in `StoreKit/CosmicRituals.storekit` file is only a local Xcode fixture. +It lives outside the app target, so it is not bundled in release builds; its prices and +offers do not upload to App Store Connect. diff --git a/ios/native/StoreKit/CosmicRituals.storekit b/ios/native/StoreKit/CosmicRituals.storekit new file mode 100644 index 0000000..ec7b613 --- /dev/null +++ b/ios/native/StoreKit/CosmicRituals.storekit @@ -0,0 +1,175 @@ +{ + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "https://gohlerdev.github.io/Cosmic-Rituals/privacy/" + } + ] + }, + "identifier" : "C05B5A10", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + + ], + "settings" : { + "_askToBuyEnabled" : false, + "_billingGracePeriodEnabled" : false, + "_billingIssuesEnabled" : false, + "_disableDialogs" : false, + "_failTransactionsEnabled" : false, + "_locale" : "en_US", + "_renewalBillingIssuesEnabled" : false, + "_storefront" : "USA", + "_storeKitErrors" : [ + { + "enabled" : false, + "name" : "Load Products" + }, + { + "enabled" : false, + "name" : "Purchase" + }, + { + "enabled" : false, + "name" : "Verification" + }, + { + "enabled" : false, + "name" : "App Store Sync" + }, + { + "enabled" : false, + "name" : "Subscription Status" + }, + { + "enabled" : false, + "name" : "App Transaction" + }, + { + "enabled" : false, + "name" : "Manage Subscriptions Sheet" + }, + { + "enabled" : false, + "name" : "Refund Request Sheet" + }, + { + "enabled" : false, + "name" : "Offer Code Redeem Sheet" + } + ], + "_timeRate" : 0 + }, + "subscriptionGroups" : [ + { + "id" : "C05B5A14", + "localizations" : [ + + ], + "name" : "Cosmic Rituals Premium", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "billingPlans" : [ + { + "billingPlanType" : "BILLED_UPFRONT", + "commitmentDisplayPrice" : "49.99", + "displayPrice" : "49.99", + "internalID" : "C05B5A21", + "isEnabled" : true + } + ], + "codeOffers" : [ + + ], + "displayPrice" : "49.99", + "familyShareable" : true, + "groupNumber" : 1, + "internalID" : "C05B5A15", + "introductoryOffers" : [ + { + "billingPlanType" : "BILLED_UPFRONT", + "displayPrice" : "0", + "internalID" : "C05B5A17", + "numberOfPeriods" : 1, + "paymentMode" : "free", + "subscriptionPeriod" : "P2W" + } + ], + "localizations" : [ + { + "description" : "Full access, billed yearly after an eligible 14-day free trial.", + "displayName" : "Premium Annual", + "locale" : "en_US" + } + ], + "productID" : "com.cosmic.rituals.premium.annual", + "recurringSubscriptionPeriod" : "P1Y", + "referenceName" : "Premium Annual", + "subscriptionGroupID" : "C05B5A14", + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] + }, + { + "adHocOffers" : [ + + ], + "billingPlans" : [ + { + "billingPlanType" : "BILLED_UPFRONT", + "commitmentDisplayPrice" : "6.99", + "displayPrice" : "6.99", + "internalID" : "C05B5A22", + "isEnabled" : true + } + ], + "codeOffers" : [ + + ], + "displayPrice" : "6.99", + "familyShareable" : true, + "groupNumber" : 2, + "internalID" : "C05B5A16", + "introductoryOffers" : [ + { + "billingPlanType" : "BILLED_UPFRONT", + "displayPrice" : "0", + "internalID" : "C05B5A18", + "numberOfPeriods" : 1, + "paymentMode" : "free", + "subscriptionPeriod" : "P2W" + } + ], + "localizations" : [ + { + "description" : "Full access, billed monthly after an eligible 14-day free trial.", + "displayName" : "Premium Monthly", + "locale" : "en_US" + } + ], + "productID" : "com.cosmic.rituals.premium.monthly", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "Premium Monthly", + "subscriptionGroupID" : "C05B5A14", + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] + } + ] + } + ], + "version" : { + "major" : 6, + "minor" : 3 + } +} From 7e4ac17f9607ed756143d90c1ca9be7c274fab10 Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 20:08:19 +0530 Subject: [PATCH 04/10] Document testing-only App Store handoff --- ios/native/APP_STORE_SUBMISSION.md | 163 +++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 ios/native/APP_STORE_SUBMISSION.md diff --git a/ios/native/APP_STORE_SUBMISSION.md b/ios/native/APP_STORE_SUBMISSION.md new file mode 100644 index 0000000..35faf99 --- /dev/null +++ b/ios/native/APP_STORE_SUBMISSION.md @@ -0,0 +1,163 @@ +# App Store submission copy · version 1.0 (build 5) + +> **Status: TESTING ONLY.** This is a release-preparation draft, not authorization +> to select a build for the App Store version, add the version or subscriptions for +> review, submit for App Review or Beta App Review, or release publicly. Keep the +> App Store version on manual release until explicit approval is given. + +This file is the source of truth for the first Premium release. Preserve the existing +App Store Connect contact name, phone number, and email; do not invent reviewer contact +details. + +## Current testing snapshot + +Verified in App Store Connect on July 28, 2026: + +- App version 1.0 is **Prepare for Submission**, has no App Store build selected, has + no product-page screenshots or release metadata populated, and uses **manual release**. +- TestFlight build 1.0 (5) finished processing and is **Testing**. The internal group + has two testers; the existing external testing group also has two testers and no + public link. No App Review or new Beta App Review action was taken while preparing + this checklist. +- Subscription group `22270515` (`Cosmic Rituals Premium`) and both products remain + **Prepare for Submission** and have not been added for review. +- Annual Apple ID `6795562945`: USD 49.99 base price, all 175 storefronts, free for + the first two weeks from July 28, 2026 with no configured end date. +- Monthly Apple ID `6795566735`: USD 6.99 base price, all 175 storefronts, free for + the first two weeks from July 28, 2026 with no configured end date. + +This is a dated evidence snapshot. Re-check every mutable App Store Connect status +before any future submission. + +## Product page + +**Name:** Cosmic Rituals + +**Subtitle:** Panchang, Muhurta & Pooja + +**Promotional text:** Plan sacred time with a location-aware Panchang, transparent +muhurta guidance, and 12 sourced, step-by-step Pooja Vidhis that remain available +offline. + +**Keywords:** + +`panchang,muhurta,puja,pooja,vidhi,hindu,vedic,nakshatra,tithi,calendar,mantra,festival` + +**Description:** + +Cosmic Rituals brings precise sacred-time planning and careful household practice into +one private, native app. + +LOCATION-AWARE PANCHANG + +Choose an offline city or use Current Location to calculate Vara, Tithi, Nakshatra, +Yoga, Karana, sunrise, sunset, and independently solved transition times for the civil +day and time zone you selected. + +MUHURTA INTELLIGENCE + +Explore all 15 daytime and 15 nighttime muhurtas, along with Rahu Kala, Yamaganda, +Gulika, Abhijit, Dur Muhurta, Choghadiya, and Hora. Current intervals are highlighted +against the clock, while polar-day limitations and calculation fallbacks are disclosed +instead of hidden. + +12 OFFLINE POOJA VIDHIS + +Search daily, deity, festival, vrata, life-event, and planetary guides. Each guide +provides preparation, a material checklist, ordered steps, optional short public +mantras in Devanagari and transliteration, plain-language meanings, safety notes, +respectful closure, source notes, and a focused guided mode. + +TRADITION-AWARE BY DESIGN + +Household guides are clearly separated from priest-recommended rites. Family, temple, +regional, and sampradaya practice takes precedence. Cosmic Rituals does not synthesize +initiatory bija-mantras, nyasa, homa, formal kalasha installation, or supposedly +universal priestly procedures. + +PRIVATE AND ACCESSIBLE + +Calculations, saved location preferences, Pooja searches, and checklists remain on your +device. There are no advertising, tracking, analytics, or account SDKs. The interface +supports Dynamic Type, VoiceOver labels, Reduce Motion, light and dark appearances, +iPhone, and iPad. + +Cosmic Rituals Premium is available as monthly and annual auto-renewable subscriptions. +Eligible customers receive the introductory offer shown by the App Store. Payment, +renewal, eligibility, cancellation, and restoration are handled securely through Apple. + +Traditional calendars and ceremonial practice vary. For consequential timing or formal +rites, confirm the convention with a qualified practitioner. + +**What’s New in 1.0:** + +- Location-aware Panchang with independently solved transitions +- All 30 day and night muhurtas, calendar, Hora, and Choghadiya +- 12 sourced offline Pooja Vidhis with checklists and guided mode +- Private on-device calculations and accessible cosmic-glass design +- Monthly and annual Premium plans with an eligible 14-day introductory trial + +## URLs + +- Privacy Policy: +- Terms of Use: +- Support URL: +- Marketing URL: + +## App Review notes + +Cosmic Rituals does not require an account or demo credentials. + +On first launch, the app presents the native StoreKit subscription screen. Use either +the monthly or annual sandbox product. An eligible sandbox Apple Account will see the +two-week free introductory offer; an ineligible account correctly sees standard +pricing without a free-trial claim. + +Products: + +- `com.cosmic.rituals.premium.annual` +- `com.cosmic.rituals.premium.monthly` + +After purchase, the main experience unlocks immediately. Restore Purchases is available +on the subscription screen. Subscription management, Privacy, Terms, and Support are +also available from the in-app appearance/settings sheet. + +For location testing, choose the visible offline city flow; precise location permission +is optional. The Pooja destination is the fourth item in the main navigation. Its 12 +guides, sources, materials, and guided steps work offline after access is unlocked. + +The app uses StoreKit-signed `Transaction.currentEntitlements`; it has no local trial +timer or reviewer bypass. It uses only Apple-provided encryption and declares no +non-exempt encryption. + +## Subscription review metadata + +| Product | Display name | Customer description | +| --- | --- | --- | +| Annual | Premium Annual | Full access, billed yearly after trial. | +| Monthly | Premium Monthly | Full access, billed monthly after trial. | + +Use the branded subscription screen as the App Review Screenshot for both products. +The screenshot is for review only and must clearly show both plans and the service being +offered. The first auto-renewable subscriptions must be submitted with app version 1.0. + +## Privacy and compliance answers + +- **App Privacy:** No, the developer and third-party partners do not collect data from + this app. Location, date, time zone, searches, preferences, and calculations are + processed on device; Apple separately processes App Store purchases. +- **Tracking:** No. +- **Advertising identifier:** Not used. +- **Export compliance:** No non-exempt encryption. The generated Info.plist includes + `ITSAppUsesNonExemptEncryption = NO`. +- **Login:** None. +- **User-generated content, chat, social networking, ads, gambling, contests, medical + treatment, violence, sexual content, controlled substances:** None. +- **Unrestricted web access:** No. User-initiated links open bounded policy, source, + support, and Apple subscription-management destinations. +- **Release:** Manual release. Keep version 1.0 unsubmitted and do not make it publicly + available until explicit approval is given after testing. + +Content-rights or new contract attestations are account-holder decisions. Confirm them +at the point App Store Connect presents the legal statement; do not infer acceptance +from this engineering checklist. From b29a754a84f1373972f40556f24e6ffddfd41fd6 Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 21:04:19 +0530 Subject: [PATCH 05/10] Add a release-safe Rituals UI test route --- ios/native/CosmicRituals/Views/RootView.swift | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/ios/native/CosmicRituals/Views/RootView.swift b/ios/native/CosmicRituals/Views/RootView.swift index f09f271..eb5dd40 100644 --- a/ios/native/CosmicRituals/Views/RootView.swift +++ b/ios/native/CosmicRituals/Views/RootView.swift @@ -7,12 +7,27 @@ struct RootView: View { @AppStorage("cosmicThemeVariant") private var variantRaw = CosmicThemeVariant.cosmicDark.rawValue @StateObject private var subscriptionStore: SubscriptionStore @Environment(\.scenePhase) private var scenePhase + private let bypassStoreRefresh: Bool init() { - _subscriptionStore = StateObject(wrappedValue: SubscriptionStore()) + // Visual and UI automation must reach the offline product without a + // live App Store session. The launch token is compiled out of Release. + #if DEBUG + let isUITesting = ProcessInfo.processInfo.arguments.contains("-uiTestingPremium") + #else + let isUITesting = false + #endif + bypassStoreRefresh = isUITesting + _subscriptionStore = StateObject( + wrappedValue: SubscriptionStore( + accessState: isUITesting ? .entitled : .checking, + listensForTransactions: !isUITesting + ) + ) } init(subscriptionStore: SubscriptionStore) { + bypassStoreRefresh = true _subscriptionStore = StateObject(wrappedValue: subscriptionStore) } @@ -33,10 +48,11 @@ struct RootView: View { .preferredColorScheme(activeScheme.colorScheme) .tint(activeScheme.primary) .task { + guard !bypassStoreRefresh else { return } await subscriptionStore.refresh() } .onChange(of: scenePhase) { _, phase in - guard phase == .active else { return } + guard phase == .active, !bypassStoreRefresh else { return } Task { await subscriptionStore.refresh() } } } From 3a34bfdcfebbd9a8ab11eda0ec82883e53840824 Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 21:32:19 +0530 Subject: [PATCH 06/10] Give Cosmic Rituals a ceremonial identity --- ios/native/ART_ASSET_PROVENANCE.md | 26 ++ .../CosmicRituals.xcodeproj/project.pbxproj | 4 +- .../Contents.json | 21 + .../ritual-sanctuary-background.jpg | Bin 0 -> 267811 bytes .../Engine/PoojaVidhiCatalog.swift | 64 +++ .../CosmicRituals/Theme/CosmicTheme.swift | 394 ++++++------------ .../Views/MuhurtaDetailView.swift | 2 +- .../Views/PanchangExperienceViews.swift | 73 +++- .../Views/PanchangLimbDetailSheets.swift | 6 +- .../CosmicRituals/Views/PanchangView.swift | 4 +- .../CosmicRituals/Views/PoojaVidhiViews.swift | 139 +++++- .../Views/SubscriptionViews.swift | 2 +- .../CosmicEngineTests.swift | 69 ++- ios/native/PRODUCT_IDENTITY.md | 38 ++ 14 files changed, 543 insertions(+), 299 deletions(-) create mode 100644 ios/native/ART_ASSET_PROVENANCE.md create mode 100644 ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/Contents.json create mode 100644 ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/ritual-sanctuary-background.jpg create mode 100644 ios/native/PRODUCT_IDENTITY.md diff --git a/ios/native/ART_ASSET_PROVENANCE.md b/ios/native/ART_ASSET_PROVENANCE.md new file mode 100644 index 0000000..35bae0b --- /dev/null +++ b/ios/native/ART_ASSET_PROVENANCE.md @@ -0,0 +1,26 @@ +# Cosmic Rituals Art Asset Provenance + +## Ritual sanctuary background + +- **Asset:** `CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/ritual-sanctuary-background.jpg` +- **Created:** 2026-07-28 +- **Generator:** OpenAI built-in ImageGen +- **Purpose:** Full-screen ambient artwork behind Panchang, Pooja Vidhi, guided-practice, Muhurta, detail, and subscription surfaces. +- **Post-processing:** Converted from the generated PNG to JPEG at quality 88 with `sips`; dimensions remain 853 by 1844 pixels. +- **Content boundary:** Decorative atmosphere only. The image is not evidence for ritual instructions, regional practice, Panchang calculations, or any claim in the app. + +### Final generation prompt + +```text +Use case: stylized-concept +Asset type: full-screen portrait background artwork for a premium native iOS app named Cosmic Rituals +Primary request: create an elegant, immersive household Hindu ritual sanctuary background that immediately communicates pooja preparation, Panchang timing, and guided sacred practice +Scene/backdrop: a quiet Indian home altar at the threshold of dawn, with one small brass ghee lamp glowing, a restrained brass pooja thali, a few marigold and lotus petals, sandalwood, kumkum, rice grains, a small bell, and a subtle kalash silhouette; warm dawn light falls through an out-of-focus carved doorway +Style/medium: sophisticated cinematic editorial photography with a slightly painterly fine-art finish, authentic natural materials, premium but humble, contemplative and respectful +Composition/framing: vertical phone wallpaper, approximately 9:19.5; low-detail dark central field for highly legible app content; ritual objects placed mainly along the lower edge and outer corners; soft upper glow with generous negative space; no single object dominates +Lighting/mood: reverent, calm, intimate, trustworthy, warm diya amber and sandalwood brown with restrained kumkum red and ivory highlights +Color palette: deep sandalwood brown, burnt sienna, ghee-lamp gold, kumkum red, aged brass, warm ivory +Materials/textures: natural brass patina, cotton wick, stone or dark wood altar, flower petals, faint incense atmosphere kept subtle +Constraints: original artwork; culturally respectful generic household pooja setting; background must remain quiet enough for white and cream interface text after a dark overlay; avoid region-specific claims; no people; no deity faces or idols; no readable scriptures or symbols; no text; no logos; no watermark +Avoid: stars, galaxies, zodiac, planets, crescent moon, astrology charts, sci-fi, neon, purple cosmic palette, excessive smoke, flames larger than a small diya, crowded objects, kitsch festival decoration, artificial symmetry, blurry low-quality output +``` diff --git a/ios/native/CosmicRituals.xcodeproj/project.pbxproj b/ios/native/CosmicRituals.xcodeproj/project.pbxproj index b574056..8f999d5 100644 --- a/ios/native/CosmicRituals.xcodeproj/project.pbxproj +++ b/ios/native/CosmicRituals.xcodeproj/project.pbxproj @@ -300,7 +300,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -331,7 +331,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 5; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_TEAM = DJJ824N6RB; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/Contents.json b/ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/Contents.json new file mode 100644 index 0000000..6bd0101 --- /dev/null +++ b/ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ritual-sanctuary-background.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/ritual-sanctuary-background.jpg b/ios/native/CosmicRituals/Assets.xcassets/RitualSanctuaryBackground.imageset/ritual-sanctuary-background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1abe2261aedddedfee48c9d16d87ddf5517b1add GIT binary patch literal 267811 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

bs1ZF6H-}}pM^8| zNzk3go{avb;sAu>wEd`-w6*xvhKje1Ub}g2wO)%mg;Oq>U$jE5pQTJJ@Mvum8_1r| z;Ju0Q|0X+o#`Blt8SyFsx%4*ZutiH+#EB|HC6E5B4`YBsb-Db~N$d2cQxB7o7q0zFu6 zTfE$;ce_R&u<7RJo4P%w5+i=LC#Cc08y#K?DEO7=SXX~28(GcyzbkL~lbNlj;hvFo z0HZ#;=Uc9u(tOqc21NlJf4qSyGBeJ6M7`)fbwkHgXSwiK_mZdqYG4_uGG!c-YD7=j zGatx8Y+Vsi{CX!*>5{n&dMS#yYN@cPNiiV0c9cEsT^Lz+24gBJnWEg}bvxgHLp%g->+2p0&OME(4kje{%z_F%0yYuvaBs z*rgjLTGJIsOQs3sVUiO8@i(AGR=!1LPAfVSxFwPl{z<0202$@i0JNn)j!3^Y3gr-z z$2ht(4gwgdZ$8v=X+~JluPvj9KS~{o!;elQ3ID26{Pf4 z%aX;@2iT7O6N=(QB&qV}+pUM%BpYkQiFF1M0dsM6)Iw&G2;m%ATChRW1~z5>(2s1? zEgy@q!NsQri2_gh_pAxvOyc4WBX*!RjC^#b5CHsTgZN#DQ)&I5IL4ckYFKt9CHk9I z8djmY_}8185*QsiR8}LUX&)#WBABV+(EpY{1p$HI>Ni*c{aGHeToN-9v?oNGh6o=P z(e06#4JApHQUjiP8&;2i%z3&2vuvQWe1AByRIZt}+mheGu`rqST@Ftw6lt=m*Qh75 z`C-)01#0Z&iQGW)08O|!q#Q_2-Xu92&AX)3bYC*58yw>Ud?yx&^74l^Ram;=Xbz`$ zfVO0TlK>Wh6(AJJ-siL$M+7%1FD|J9=VA{>lX9)HAs(Q zq*k5R`R8mu%=47dHo0ZhI-4S&Ie%KLt@0SF%Fl_@b^czlURYM1e7%n&-(c!Is?N-%TF4UIeow5Rp$4Q~~L)YEj!g$CBsb()b z8{(mhxptD_WQg0cm3u#LyJltt2K)mwU$fVz)WSEeO{~Jra-#QhD79K|`VaylM!{Ci ze|Tu_2~+YFwb12k)>_2z_^=a%DbGnAa|BUIXq_LhsF8?*d);%Le#i6vNy5W%U2nzl znsBE3u)oTTDK8LR^QI>2B&JFpe#1z*mB2Th(u>lFOtJ%|k zcKewqMJxUuRM<7`y>S*^EWc)3%Pnz^@6mpt+l5XCm)RZb?dj1=eDD<=k*u3`)5ul} zuqar%UlyqJMh)8RFA&`;zq9ZuOY^t0V%FQoaXHL$&QlrbKdp*h&uF0T79 zBgyFH4^gm<$@kx|0(v4-Guc?+Z^p)&w@&GI*Cbpt5=G&Vgk?N@Mg62YUNe9`-B%s+ zgkw`y_pNtufsip=pI?nErJFjO>8sFAeA1yEsOxIA+10v*@L`TrN4D*4-O>~J%z{f- zh-nuq*_|R<5M-1lHr1j*?q$~=K`>?wEroHNh#$i3AyVunb=e=9WFSB@P!DxDn!Tkc z`)Z}EOTxBTW|to(=)x6V4Fljm_z$V_tdQvt2^!^_9Zmuu&!(zyPaTl>q4mX4b6Qi= z$^`uDNEOadEcQP1T%M*dt-ia{)7HVVadr?&PaiCW@eyuX0m0@8dGLmuz;)3#fhWIK z=zZ9=E5JG4vw=X98ka3DJp!nBU63SU<2$?%Lb4#_-E!S#_M%$3pYCx^=C3c|In>O*t6}a<`e%O@YoxlR#u^e}U7KE!R%&YH zPxMlq(v@@PMZ)lQH?Yc!%{`p3`|3&mz!>Y052X!R5 zM{A`e=dJPiq!bH>CjKi?aK8u58FOj{%uk%4+INPuzMpxPgNpVCy}Q%bWeqg@L@dJi z#`7y*J$0ArP5ovna0)xkI=kYPX@Q%q8riy+m+WJU-^o0AzAagO#j+HIgVMP7Eh!Sf z@TxDC)v9wAa2OH91W`sR4`uts7dNFC?)x3fd1hpMVb7aZS*R8`j@+2iLL8Xvo757* zFO9%e5|^A&t8dNDU&nbJaB^CgLRQPEW(Ey3mFw;o_@$a$*6LHl?o>V*75Kd-h+EYX=nvFq);Qp&ruU4%`< znpP~Q>Uk%_lr?qD@noTxh7HBGOYePkB?R^l*B8WHc!myw+J`bO^(a8sE9-x%NrVn} z*0;28XT)#n(q(|`ha4`y8|?V_nf6Ovx*v>saQ2>K24W?-R`Kf#$7`S89r#4~#mCDh9=69gB8C9{tZh2&Uhu&3_~JQ)y66 z!PDGAtT`b2Q7nvcB!LB(eX^Z^lrIwLIx|mThOd0zHF`~O$Qn$wMAmnT+!(To#sO`# z7HUN;9pOwKH3rv`K!yv}{*Y7It72BK`rv^5CW;4hhqxEYPy(j^e~#BgV6 zBW*jk++k$VhMSD1o|XnEyH-|R8mawBs!monJ$^n98f#qu$`etsKzDrxwI3bN*_KN{W@Mu*&zZrcncB6^vjde8oN!v_dNT062DOImQ=o35&8PXr=9`0p)E# z5T*$Ln7F12tDSljEGlcjSDxi`tFkSC-^5JhJ@4IG(OLV-BPz4DSK!|Tb`kLm{Pj&) z9(4Q@zVcII+V*zRnuxHA(_Nw5JG0S+P^^N zgUiZ=%kZTH=QDoCKK%G(z#(vx^21N(Qfdd%kMMsKo%KW0UmJ!80|^O1P>>Wt+1hVjt)WR$We|_5~F9(4GIHvw-}m$W1F-#a_MGS3_jMUaAzd-I z1yUw?^-5Q7nu6d&kXy=$n~E3*B0Etxd59YOlJ^F^CaRki(!>4>2GaMpECFN8t3VeM zaiHl^i#lm5$SIcT=efzirI5d@v4RBk-AJf8vzDfSiGdsyg8b_u9m-@8byDI7W*H5b zis?-FQhWCWw>;7DUuMx!VSkLJW{i=>LVQ1H$0TvE@4+rD`D)BYhFtkOxjropMqE}R5hO+!hV(edes4SVKu)~zpY{gnrjea)0f z#dUZi&gac%TGpaLQ*y#0!g3 zxYw_DG~k(5C&r#v9El>^1gGBYrh_zd@G*{ms$J*`c_*|Vv`X5X^ykzfj~W*{kSlq6 zTCgKHE1bAe7L<*wM4sU8N3&SZ=&rTL0QUmhiM}*KB67VGOZhq>Q2%5_tF$DX8ygmy zHhbV`vdqIo-4xKhg>I_aNj`nqU66GokaB5xx-`5C&=eYlw+Bx5n{9M=p`m$r*V*vL z%kAEOm(BfkjCBY8;IgBHBP=t%d_N!HB;h|@!gKbF-Y}5Wv5n_CHWTt;0}IMnkI7+K0{!AnVAY#x^XB-=B1M z%KS_GG2}M>|C2ZIP)LK^7g`g58ZV~&tXZcp*T?~Id!y{LPpZy`R)fkeOk8Q$PRa|* zp5QZ-gklkZUy($hxVl19*&&2f`Whz@K z{pCGBlh1YH^x_bT=LNH`(^C3NW`8bV*qHFF>_dnfcxT`Gm|Mf)#_d4w{(*RVa1r%@ z+{L)_233P%Csi5SFF`UegHX5MN%>;daS!&@7TQ78e?Q=9_ckNr`5xSB-Ui?c+(GPiC`Bae8oId@yh?zlHPt@1$T4szeZwOQD zZ&PrArp{zqNh;hKvW_Oi#_IM9#k=*4EMQ~%VWMqSz2E^n3!>HC&Ke1y08ZqNHFV8l zWgmq^0)Pb^hKH0F<~o}_jy?QSE`q8w{ek5UtJ=BR&O932(NWcgR3u(^YtZjW6)|yx z62K1yc~kY;n6ugYEE8^XPSxwaVyju*O?h3NH4IgAOW!eTjWrHPw-E>m@F!~cz?!G2 z$*P2>Sv@60_N*Ifr$50xU4I5(PNY++kGxqwM#d=lS)a+R)Oj^3a6UdunbiO!j$IMO zW_AUwqI7UZSer7lCFKjGn*)ZyWV*j zT`E57Hd@9ZxN)O1fynpvp$C|Ki?n!0$1n@LaMjw6m0q=nrhY2TUDF82aO{f!OcAd3 z(R+3J2!+2AUS13+A8-77B#CqzeQc*QPl&!Zic8Ao!nT}9JN@=Xc346zXt=!k8^SB@ zwr&)|b}rR$!dkNi5$DUb4)eA;BfO+Fo1ujFYm^_c{247LBXJ80o}-n=03#ug256f` zJ&RB{E1!BS+A!+|?Kg-VTS|T1`tmBHBF(!ma^(7Yu5jl0GZCqXI$`AUsZz?*n0S<< z+e%)KD5@XJ4s7N7vdg-rp0^kF72Ll~S)zd8E&I>4bB`}(Sjst1m9hUIo-whOeFphRdPv^ndth zyP{ORn)*h5CL!(D39ekamM_e?dkb;C(XUKVaAh515Y8%xt5Qy)-qPG2`8I;Q=+(bx zv3k@OVO{dQ_4yy%LPxIR#^PXZ`~PEp6=? z{Rc{X`5&lU1vqnUKVN+1QMa`^OZ|W9671|&W=)?}!SXB-ekYgPh#i;jF=OwAYKqn8 zry|F!C^ZA?!9Iy=5`V&tr!AHXLW>p;VcvYq;?uj(cO%jw^wT4(H!aczEwtpY%f*9K zEE_&cwoMg-Ipv;?a5I03O#K=4@>p!?N4U04zpz!s z#`dNg((lY<#pGhp*Zk%B)D3;r`k@*H<@pJQJi?(s$TCYuJglnwsNTGLl{1Q)n5Yj~ z&q1!5frF2+6(jc+X$M9_XsM;ZNLuy$zY}ptI@25TU&<5;GQ~jam`I&+nl}SmQ>Bcl z?ZS`od}%j;)8Fzh7nFPq=YyFU;m;M37_ZGK3K#~VV`?)}SL zm^(l?{8(f#l0twn%UMQp<*&9Qg_0visxxbiH(7%k;~7zd($iciauHSv9V{MDPRqST z4h9wZ-mx&_hSB*V^KRUOX!hg4pi*D-yJ0<&ctCr)gH}o0b)K>SNih!YhV6-IZc3}) zH+yxRd)rXEM~@Y4%}$bvN(XJ8Z6c?`69Frvr2JtNz`~ z&Xd2V&U^i3T~+OPmNwq|J*IYYC_*4%?rpcSmmG*L42_QD^7>2T~*f9i<(sCqZnp9RjoO z|LxZjojFew3jZJaq~T|;wE8vnL43=Ul|SA|@kAN#S8ob114WVJb4+(%fQ>PYpmKIb z4ILT*7QUK624A&3o!h?aan=*@3|UX$$u6z0z?>7GZlg@xlwe==jkxQAw_#3FKW<;+AedGX^S_ldZ4gVlW~hz@MTxdao%@*;edGig_o zsD7PDt@?KfZVDCa{WBjmXVQ6A&$(V^hN!76EXMwtCDlIfx3;=BS{g^M#Sn(Q*4_8? z!{s2&ic}Eif&QH8g-KesiE~GS02K=Zi0@sR3awfJRqfql0Tp=i4}b_!xKuhUfuUyU(!*W8VfDwnHWQyQ^av9m@4|?s1-OHy9!n zh_w^w1dk@TCEHzX*u&>Pszw(MbW85x+*vl+0lf?^s0V1SI z)?J4TR*F@#Nro96l&Gal%%ZWaUevJ#9uIQlSJr zzk)*Gdiy=#$V`2aGo^u-jib%fIV$M>4~ORE`j>NwJXszUgKV{eCL!h>Z&TKm!(EF5 z4lgF-W7;n)p4&G`fBQ`QwP~H;(O`Qwb$-jl4tH?V%Eq?=1<$)*{C&PIluJZNR}#F? z=%ZsY!Z{_(%%kMhDPJ;D_%)>UhO>tFut;(HGg`v72w0>4XRVi$?0|NFSM3tt#}2A>@(2~j;Hz%OfT=F!-sR$?uS50O$P>X%8<@wL+1DnDYI_Ok zUw5qD7B{MfSPe%{-@au{(=YPKg4n>}$(Vh{Yc*~>B8UCP5mE*8KmEMi;b*<9F5#%O%*Y-3ian-~4L80YRJAPX_OnFIU^{gRb+Ft+ zbImXPw}DDMJwov7msI$r9ty84lr?xX%gT2)(Z>cFHdNX&za$+9t2vrFBRPzvaE8x`yVLZXSYhMoFSQF&kcy8va$%{l%RdY z|3G@UW*{rWxrZzGc

(air@jko%013zJ_oIA8@b1;U@bt3+TQt(e&RGcy`I3M-@79r3&wOwSUR|=htDOQZ=e!)6z-W-+F%|)GjrP`=OxxMAWBQ8c?jh zUt~Xc99M^a)3#9fOUH01P5MuWp)J7RGY${nYk%Pvhc)YMUtRDfzax1z)~sxUblf?@ z^IoCi#PHw6kJ+6*gJ*i|*ZhF_3mDz-^sBJW>OMOGcT4J#x>H{DL`@+0SLGZNt z-lCC}K3Mz4zkQ*Kji)+~O&{QYJ7TDNH4atXekBbD!n&5daH8D~!g)3HcZd8bW8vFy z3Wk%8de>E=SXgNGLekAZAYq%isAml$Hq)B?^BI}e#m&=>kqAcee#(1Ow zft;FL9OJKgnN$ISd8Y-%8;q_=+dvfA!>$9tMecPIvuM<9XeNV!lm z+L@8NC!wxHcNSG~&rH=zt^wWBtwMKo{3^Bo0LrGNd`7K%r!g}e@m4KHWf(ZBGP4$| zc1?%p@UKtoo@&-dePQ=%EA{yfO;4xtneRrTq(*bzykE9`9lv*T&9wuDAXZdI+O;(- zZZ(q_C5C!eyAOo|!yc)~jN7LcIhS}zN zu1lKDw**MPTF})FW+JlTC(g%g*R>zvo(#W;+3%hdY!xTcy^*%b5*O?%$2=yx)mz%V zJ?{3ZqaG{ZF-r9h)UYz;qo}u5i6U-2xvwhMph#z8pIY@>yR5^2c&{PXu1J;gdeq^R zxrdB#^*iQi9>I7!!fsyH5q&TPQ$CuZok$=2-z} zRAEiHv)FFDX>BBhOXnP8 z{F=*u6jetW+Ns}o*UVyf=DA^!&NX&B=bqAZw>?i$*8$F3Ij=v|C-c~} zXX#uO=ZbvKy4`9`KU+5p6^?nYyuxMErqi+cj$xWpsG3aaZ0~iHlWbBQql_BvtTnF^ zsZk3OM;%Rbb|_^7%N=XzF9LWvOXx+*CTs2`$wn7w{HxkJ)ohvJ-YnEST|66~Di7md zJ?azR`N!>yNhhhV(~U>LG2PlUGRMX%S0+BF!1W)WQD2$GJGFE630cKE3^vy=31N{+%6?t7 z)_gD3E@IZMd_QvJ!+8^_R_VL~M}D4&DJ|uT#C#?e07~F0rIBXk{C2 z6ZoQ&*ZtGj*O@^OHhboB1dX(F=~>aJ+HbQiy)>F#(ez)#9ampEZGo~Vvs_O0Q*Py0 zGUN}br)Y-W!{Swi)!G>3m_4NBkMRS6-l+HS;&|}%jf-}<#POl(u6|S3p$597@gMvqn!kf)@Zk_% z+^>^66TxLX7U$NZ_{E@V8h^%%OMe(m@TZEV^KP`~D;pD%$~fY!_@}_OzAw6#Tj~DAC>SvvvmCJx8QzToZw-Hl}gZBO3is+f5h?2iPkKrZcpJ__7JPVxqH_!dZq7n zp~%g4T4@lpQMd6h74Fp138V7pr_6lo@w(w#U54C$06)^aKZ1R0*uFE&l6b~NU*TWt zUOuf~wdLOK29okem0S3-96AqLX*ucse_EQNz&|OkT;?3SDEFxW$rz+m+<53J<-N4? zKbHa*W6%MUS;^UIGPc|tlf_RM7|nX;g?7%el?=k28`5f1$!}~YdTSLD4 zS{WbQi~5@RdimB{hnKUdS6?LGTI-2sGU&Plsq5CudL1>&LOTfp!t#;DJeQr0xEjzWR z$#oN^slVQR#`Vsh;2kGUo-3P)@9&Bgi^;-7kLpRuVeMXTjkq0ZVEH-psNXs3*1cMB za9;GAer8gVmde93&P6*pJC>TQjr3i88U`8eYCHy>#khu$rO%zH! z1u1NfdsIP)6!mQM=76~W0J=KVs@ULms749ql)oHtQc)uv-zYU7Kn)v-!q9R!>rx_Y z_Z{iV*vY1DuG7Ijb<`NIfWQwYj6zaB8V+lD?HVZ==%pC?ek@-{gY(l@v0N&HGaqMX1zAO)69>= zclvW;Jksh8c5_hJa)TdSQGVaM+nVtg6QO4dk6P_6Px?8}Cl%pV_|f#(f8jIB^;SM%HJucI$fw@4 zwR4yE6_;uAOsnf&)PII}68PElKZ4gW4?-)Z)unjkUA4~mG=+gz+PaNW)th94fNS9@ z+uOUJrQy}^s>X$sMtgnIcdsAu95N=N@wYfX<6l7N(Oaww<{qNF@5MGY(_Wb&?^)2D zsx&-JJJDK@{40b>;&>IkYxYaPnw7PtlM0i!3I%>id^580JV^;7y?&8s7fv+wD%tk0 zmd(BTkw>wYN0x-HdtR~POBhhaB&u`UHS+%ek2T#r+5DgmX}&8LZ*#lMDi6JRzM){R z_n&b!!$O=PW{zr!&9rcj&)!%pWRQ8zYJ*PDjP(^*oFp+i zw0gKKWn3Pe4!=;>E=us-tI4&!PFWc4YSO>faHUA;_}83jnu&68+|=PRs8f~fdKk>^ zbtB4|&+1ofWGoG1oA%CB;MBKGy|PYg7Dd4)HTRIi!A90UG^vcKDf1?EaBC7Og$?ad z+-mbN;f-?T*>DGbX@+KQ^$Q11_ekmf(W%R3&W;^ISqaImpGVaU5{7feYtQYaRpt6t z-M!3QZaA%88VjMvEL}-%&#?X;y}q+5LZoMoEA+SFCxc>}OW28$25a%};SR4fTH=^k z?F3isSHRyBULq(XAd39!HiM_4XW8XXo?OiD^)Cbnq9m<+QStXcOHDROg5wqXL83G`4_8B7gCv2^lH!hN%gGj>$W@nx+rLVIO=N8|JD90^*Q{zc7q&7nfy&y(CzJIFbHCv-C`hSUmK4$Kzh@;2ZmIiB`7q_=Y2MsOWcpyOKgqRmT}5{&n)#iEs2vj}3Tk>RWe{Q4W65 zK6vuf@V`@Ec>D(N+xY(gMe%2eJS{9%FliPmDkFXAT?Tr8TKU{xEjm8d)|2b!dwwU; z;kl<<*&gLb|iPJd!YvoJ33!jQ#4)twn1z)r+x;*fXyO90xJ!|W4fS(ey_V{TIjEu4- znG|fv)ZmOaexkk~8}89nAie0jGwU#3^?6g9_hhcWQ^$^xCY@j@ zdlj|ub-EbKuEXo-LFT!zzWkjxyV?2w09qO5uwPeIZFL>jm-S|R^9(WGUo?mSUB?6I zS`&{V_T3kz;A+-|Z5XtUI)>bb2$8KLc4{hHCzXp$ncMKjX z-JO)Tw=vp4H#~$WA6n{eDPBT7PvMWk?OVt9TafuSh<|x2bRNUrzRru`$A)|lq6N_H z!ICP9 zrZX|d`zj6lH1tcasr6Lo!j(JAa-NqIc5+6}0}=BvKi0hS;*Z7+KTEedb)F=g@wI=h zE6x0K@mBOJLux#bKX$EzComV29v%ci)wa_+|@n)=MMAeDNH6^+?!jw-m0Macti zTk#I3uiYEXNA#c{B-OV(y4au+BAqdlogcXtEHsz}&4T@=Qb4?|`0Z0o1 zImtCmT!H5dF+iiXeQ8Dq$|*aPC-Be-$Ook<^gZb^cY#lECQ>%_rf0vcAOI-GdO7P- zR-2M;DmbPw$l`){e6-GPtr=CtaVxtF=r>Lm`Amnq3kn$Uh91iBC#s@Ua z^PcqPW6I}>zM2pyRVRuZWPmBF)Z(5<&U@5|22|}8Ch|@yHQml>$l2@Kq^(J?IL$wb zXvgGe8ShBF#~g!-X&buJiO*hWJ?VwUW9I8m$RiYmaxi;Pxyi>$LnOx^Q9_arG|k5# zcJ!q=Cp~BK^{8`} z#W+4b^+?C_o_bV@4tF<7PN!%!H?;so)Q0t^Hz$Ei7{@)S%H7yhTd)rYJRa2j+38US z86?!fxZ|xWJ<7hL7~`Jw^oj|Nk~-O zIAg_I&!tjgLCsFD#%UTB{L_t~^r*6T1?yG$#XT6DVy4zcibq+euK2PmCs>ba9(k=h z3y&@>r>U&%VnlVx>s)b7O!VZ}v$4cW$?I1vHxr7g>xRgy_CSXAHS0fw@mIum7C30Y z=~eY6!k=1}%`j(B${2?>_N4;;Msz$?IOq_)h{) z-a9=HsOw$L$^QUHKT6^}Keuovn(eN^HrU73yei)sKAR8xCV76Ua^wo3XUxC~y{k9x z&MK9O%;1k&_M*Rbcv647XVCr;j|jf?=o;}5T1WIX$9OU`AO%MizvC;D1PPO#4SWS} zdb&sH*mui><~gVFF}H_cY-2UfN8%XoCMzM&HNtCGj=-vQ{{TMqjicSNP4gbUweQ0W zlsS)=sf(97iS>uWdE=X0i(&PzyEH4gVYL|trG9sOD7-D=7;)1z`gP%(7-iFu2Wx>QlQotIDJ*G>+2e#PCUw&%y0jcU~in<2x90UU_>1rx~tR<>v&h<6lDx5PKd> z@ii8W9p&z}t(>R#HSywSKGoDe*jT z*@X?pEAmX~N=?}PQzvQ(Hho{Igo4qWpSmmZ+xD2%jLC2b>zez2#U49YG|2#dNWibm zZ;sv}7tzcavU#ruE2A%Ln?8dvs~J zamOX*h-20?{Bv<*xypgh?_nTI5%t`qf7&(dO`&YMp3mh~T#(Fr5t`-y0J5jjXF8sd zBg1hKC1r_<5syMSujc9)`9@ZZzFjr^zb_;8wB;A?tNv$(O(b^lO0jXbcxD#!{SKkKLEeXmfgwvn~Gsg)emW zb&gAEqjAq;)C&7s_Hftkte)q>3n!L!OBMmp{JXK!Iq%-S2J!rGcz0XYd?poypKIC} zpWt4Dt$j)G-u}uz0Aol)73I84M4%P?^R~ZvwqIHOc1llJt$t1E z_Z^48?-h8RJ|k-02D6*WxQgCnaNl*6PjURK)BYIV-TW}|^Xd2VX3txVuv?&$y8i(6 ztLFazgZ?v?FWL`7znKI_RfoGM2(`<^jG^tat;pVJmBY{ zubsz7lCq)8q|(2x*Za?J0PjXs)UM#&y*IbqdmqE=)Hc#4rRmT*t?+ z09$H$2Crjd4){za<^~-B>Oig^z@8w#yZB)DHmELFT(u-%@{>k6J!`l4H{-o$$KMbB z!>S>WMq1V7>x^-OU!K*cR*a>HUuD?+4i1(klETaFrvCu1m5JyML$RZ(eeoT~o@h zF6X+Ht|ns4$0s#Qg4tSXw>UJ4+*j*qB&?C)?mUyb0+V)5X`!>yjP{@hmhDa$Bx9vB zA1E~7He=GHWTPIHCee+wz`%8&7RLsG2q?xtrR*_HV10U0v0{^+v;%>Tw1YUM&N%5) z=qrIZrw?jxYI!_?=|zejFn(%NoYNE&-->o>EH($}N(sd@wsND2ZgRQwtGcy}iFaf1 zqX(@pFU_8`;KZG^HW@f>)F|rIi|J3~_o*L{rHDLJ!N*?p7IDvdalqtIJ@q9x`=_l0 zfHF--oE&}>F)hcX0Est`y-nOykbBg5rDMnAL0+VwWkzX9IL$Fwftrxyo;aikGn0x2 zF`7b8(v^B`Jv!#6QQuL^kTQFAsj_iV0yB_3smr^erJ+OqhLm#HH5otMH1K)$p{}AV zsZevCl;ZUOZ<_{_cXQ2VGC)pFPZWUThVM)SHx#+UVt^blYQp@WR3m_KPtR{&Xc;jL zfZnwA9gRi~2S4ZaslHzIld$>^M8ahAPh1?4*i%>Tk`GD&!1~nrmY9W!13hYJS3I9u zt~u#iHpk`*icdso)RCac-OyEkEH8hhZmeS;g;2PU4UbC0n&$2!B9WuTLv-cx4JxVU z+M80boO)M7*yT&!I;ds&1y;QojOUuQ5cx5W zdaZaqLPdGY;ys_Ssl#4y$MmX7LHSKwz8}2jwNg{g4SP}ePoI{ux!w46L1FD(z2U{N zBdu|M9UDM3+TUmF=DvQn@hp9AANVZtU1K6rLHgA@5`58$x2$}!F+Ap~SuWNFy|n)T zc6{uY?;Ad%@C1nHs!eP7`rJAvmU;tTZ{Uqz?DBw4Ij>yQV_ij59!@LaDyIxbGe1hh z$yLGoG<>zGt48dt&ozx^51;b)HS69d@RY`JBul}rV&B7!8xxEi*VEIj2Tte3)uU21 z99lhR;S_Az{3p`CPxL#bi%XB5K(Ed(g!+3!tVV0@TWwqH_JT5cSLQjd?<=H_(eMkG z4R&XIL)95$2oF^x{x$LLy{U+=7#@bcgz@gG*K?{z{mfU*J|l@lrM+vj15HLLGtI=e z%-S&SwOHgFoSNruZlh%+denC@5DR~w=UBH2{Q>J=R|q70)k>0kl1nIXLi9CBnOJd6 zU~)(~s_qU2cSR(5?Xme8#X#8oX@!qW)Fu9H)v1NcInD-p)ue2<(zBHGHE9pZ0qaN; zFNr()Rl9S(TFh5nyMJ2IvpI0p4Rtk6?#H|MA5r@&AO^nK@E42T+7P?AAXn#Zjecy% z=Dnvv@#N$L2p@LG4@qEjQA}^5=5| zI}bt(M{@o+6~xTbTtpDHsTcJfFv( zZ)(reCAqM(n@x}`_E#jCtz{dckPP6J&Q*tITrl;n%Tdg-EMhSl^i@-nlj?i5V(Ksn z8A^?%0S6z>zTOp^ii29 z;jnu7dS9ZV+sh*&#D#+np@&NMyG>tF(XLZe)8EUGZd?!F8GJx zJ3(hRj65F~m1M*%-KKXNM}CD*R)NplW6J&0*q^n);#``cU*!DzKhgEs>%Gc%PEp*s zcc;VRp8yXL=~C|1EXiBAn}g*X?RDp$dXwRPu#tGHLu-`%^_|jUQ`E*!L-ai>&u#S& zGsA~Xizy&|?BL*)>9p5T;7fTgyg3=rAQ{VppX}<(mf5Ka+c$U^r-UNz$qoG#ekJhxjEopTZ!+T}6aRm2F@~8Vz zD97te{h)P;H6I3AYq0{4cWxtm`i{B!Vw+{FY13KwcSd={Y?@q*otpL*%@BL*GLM(kJV&Zx&xjtl6Qd^(-G zEA8YAu5#7+Yp+cd`c=9|%>HQ~k^0x^Pr>W?@4PP+?uk0RrTwUiaNL-ujA4NE&3W0n zm$Ri+EmhIql(gzqqPBKsf9$$--B-dsBJjPg8eb1wG_itzfwBn3Ypea8yg{h=Vs8+5 ze)kC)Tz#Oa^mEQD&He>Gq2S-zJ5lh3wb@8D+jbAT>7UNJ{Z*mxzr^{iBW>{Na6PzT z(8!#M`Al3?>eZ!fCX|=!`kzsQl&R69Rl7JjMgE_mmHSS>XYt*Y(*V}@QG&d2{Oj{a z%WHYl)sRUstC6 z#rXdKz&-|)RX=;F7l`#ci@ij(%!WoCNjc4B*jUZ1+!&)DBJ$_ZdVOk(L2-RBSaM!N zj8l9?0eB8imV%&6!$bJz|9NB4Im}moP$6+un!c9IHqs~ASVQ4 z6y-de{VKR>m^zRRARNS;&@wVjG@f|xNE?r(1tvcrjM5R4NLv)_jx*AL+;e9br5qjK zg*SJ4UJCn)LM2c@p@u$T`G1p28$1pvj@*jRscrNV1TZw#Y>afJ1Fk7KBm-L68Il4? z$6-s5p8V4}0~D`-N3}(+GRl+9C^;Ewh&BaE2sN0+9QUUjfzNtQDM%gZ7PSI<`qNJs z$9iGT;wky>Oj|I|98#-ilRz9}y(tQ-fM^Cujx(OUspO8LqW~8AQ}`5ygPp`wioA2h zF-adaI|Q7X3PYHT40fO#=9S1icc}4(z@!9#cNCkCfQV1rd|4ii1SD*NRPP@fOhw~nsn4H%M*&GOZ| zQIEo*kO0K>%~-biap-HQy)1Jti0bXf?!c3S1ryEVya*L#vSW{d}pry1B1Nz zoYiFq0jk~TVJWKU2(M{;C(qe)9p{JF05R=dhM|K8UNKybf^xjqXM6|QPI$ntnymi- zdYSfEb^DXY^?*#QeJZW6$sW~zSO?7`HCD~DC>8F<;788QT4!UWO(nd_!+~Dup=!Tq zWd!7rUKtV<1Jb&UF8*6-Ln9H6Yv-%e_K5mSEpKGfJ@Zh487=m?=dE&6SX%_n02P6E z;(3f8G3UKuw!J0WY$uBIsnV#TdlhTaZ1z8g^T!6Tb{++NLkz}v1J}oX0eosZ>+7u! zK|QLB``685qiUBuoGkA`#|7e-BIsw*yidd|%v%GIUghHyMZLg1tH(9VQpjBQuR{y) zqsFe@sPg%nIU}`WOabDxu9T}DIjpH0o_g2Q#wX0l>QImc82l=m^~p7MQbTplYc>6xO_T7+&?9X2O-KVrDX`cB~Xl-0iWs6q35+^I3Pr z)2QIrL2DA2f%UBYS|AhNu;yI$M*P0+rwJo6$a75d4L(-^$Tc6Bzvuq|)n19oc7OlS z{%a z0Kz)-{{R}GG^wVEr@Cx2bA$Nx_x7&L*=g1hZV`oV?=i{z1#?&TpJ|c8w!~hizyAQP zy}EP0ko(RywAAD^2vSxS-401TvDUeLKI&aQ=v{y%je|!dZTBQb`^dxlq<*~iuA2V< z5@tkF2S4ZQSwCd+Y>GrV2ELY*l7*S)yWZ&XZy9I_;k`tSJeJe1kV$JH>$g8NZT{LG z8}qCf8SFK9E-jx7anGh}-8B6+9e&R1!P=1mJFnhaNPp#((*53hEQ955d{@s`@Z4K! zA7GRel?x#UsA0hs_qaO#-VT)BtA91${QS+L?P;UxZA)7|DEL5{e~6Q9?wfWaV0bSZ zbUyhV>&ivq>Yo!V^!p5?Q6P#idtiSm^^b?LSoo{pZnLD^z+}~Q`Bqq`9Ji7>t9pUf zyhBWu;_Jp%_g4IDJh1EsQ(j&c(RJzC)^hw(cRt4#Cnblgd-Evz=-RV7U1As0wRW|; z#@J@+I%d6#_FeJ(+GXyoquoee9l}LS@w<0!d-tys@nbXTQ)&f<$)zEEc<)(12Xy^n zM!(cF_S(4GTi3m7T8wJoC+#0^zUDM*&W2@A+4=p}KFIi?e|O;T9$EZOf?Ne~j8yUi zfAy=_Z#9^wwb6V-YaiNiCiOi@-QK=R_^ql*cj0N|M=uPP$iz2Ql{jzDt$Po`ysOR!L3Wohid|4$o~Lq zpU%G={5j$CCbc%btF8c%nlwFF=YoB!^e5s!$Lo{f**qDgVW*k1D;`1&V-@j3vq4dkC5UlP9GJBr|hMERjIoy?=)s()z9f!u`w;>Jy?Q5Py%?R z)GsYGt0{Ha@KPa!2l$-!{43dnTatuU=fNd?Zgbu(*OS8*LeA9x0H$0o;);K%uL5~m zS29XV7C9vMtNN~|bE@Ax_1hMC4o-iSWdcdXexHTIEIm7Qd!AiM`$wkT2@7@oD9W)E z+>M}8u1$J2FeJzX=8XK<&lJK&&QDrZZZ_jIfRG%HC>hBW7-O_3r3HuQKn`P!b3yhr z-g-B!C>d<_rUQV^J?Ypuq#Zb;H6R&kGmZ~Ar3y&TT0%(c)`5X+;-=~WCZHbF;m~8X z0din-#V9!GQLi~P;M@U7ex%F7@z$8+o=q_c{OK}5#YZlsT@ONfRGV-PYA$-z>T(II zAO}ni)Z#s90M2nv$nVW(aSEXKr5%W-WcBo?6P`y(f~?6&B#H??c9$GxoD+ft0qQjH zJ?VClN>{n*PhN$4&_1Gb`BIhUo;d&m$flFGp41c=0Ahih0Ztg^kZ>s!IYv%9)4&6U z?@iB2cwz_#lRycNoaU7O0DGaO45ZSYdQuNjpdQq@AZ5B!*8m(+0t)Rlo7@KAE-Eqi zuxaBWqUSvIrZQ#p^r;YMkx;286(C|NK$HMYDh%)`1^^Wx`F?6c9Ds%-^NN%nc8X%; zQ^4*jTDTIa=MCDPcsQo0!1t#UlUbFpg;Yb44QInPS?hvDVRwxfV}#eR$YC(*^y##m&U_&mud zU9;}+GUujno-FYF^WTOmfwsK2TG8c>7Rb$gwd36k#d18g;2`|#=N}U3(umj*p0(y- zYq=kze?xkw{<-^uSD0aCtJ3_?rV{`j#T@?zWNfnK1Ok~XC*9b zcy9HdDsz!r(<^5LVzQ)1$E|jw&SeiQwgIFuW2IQ@Ovul@T1?5YV~=Rw4h>vY3-_uN ze6~p9mE7Yr`B+^NZaB}cO1&5)fGZ@sXXUQHMY4O6ta;64CCarf9`mv?W`f`X$fVV1 zBbUum!28#?>E0iPRod&5T`2Ibm}AeDH+t8bEJP!tN2svVm%N$da7p{I#w#aMn6To# zGgQ%1*-p{Yyq8>KZ@Q|TrtX5J>8mr4k@F$NGjn_Y0H4OHG^0GzeAWK|C;tGgUbiXb zU(El}{%A~=7jPLL_j0e@antB)#&vs#x74j}Ny|edkeqi``Iq(1D&~pe8>nLt+#Se8 zbN(crEz-#kLAF8lQ-k^%{zt>%V*zRc5}JkL|C)4Y4=Mh#~BKGVy;H8 zyr~>~sXXJ1{VP6wLC~1pvOPz7v1cOWO)7;^$OFA^B`=APn%NWFX#W6boWkEOc^K+G zpGxwti<&0M?L2R2gBKWzu>F~JxpeOs-e`95iPuxGxQ}7v5>%S- ze+gX|FLH_uf8k4ou&4S1q&27F7f)kayZosrkR4-7vlb^LmDr^JsDSm}%v zbq4co!8ji|+(+SGU+noYRhwTFV*clE$)7{*dGJ(S{uxPq>g(|+_~Iv%!$IIvvK1L4 zrdz#7@Q+xQ(QT|%u#-*m`_@*s;;mCcd32`>Br<%ijm~{)e-CQ@AF|Yhw$|>EVOd6c zH+t=GpVcJ^M)%bmKGq*wjabUo(Voxa%(bollQ_l^um`VfR)2wfVXbH$4u?m!kfekW z{_3AvZ;L!dX)dUB8)pvlM4$uwJask4Y4;7h?m5pnua|Id45`U^omiY(jwQ~Fwbc)M zmov+AIV|pZC#f}7IZ@#;sOLGSOY=rw)YT`5#~+vvKj(_04_n%zJ~8u z)MC-BmvoYxaoBezywBoxujcDVK(%l5dn2=sg}a*fts$4;&x0=fTYtsv++`roPzbM% z?k*&{neHVyc^I=FQci2=GRjJ&X;xb;7q3=DYtyEwz2D?lW+aX}(t8fHjgmO+NI)bU z*W9fSBv5ls9{kj0u)uoLfdmQ|#|~)y4Fj5g40G*S>M;}$d8dp8^`;&GCV<63#XEEm z?40D%t8S)_c&7o*I|^19{@EPS^yZt?P^1nsnnESq4lztUawu=QD5fzO4wRsP)SX2y zS^#+YPDeC|JN^|WO9BsCVaUMrq#XqyU|`bqY@YP>0E0-}oX|dlM_m4uWk4VbtHvpuzGt|_Ty0Gvu z$68F|k6KN~JancrW2G^^^%_8n7Jm!-l zts@dTQ>l+qKy&j0j@2Tb!l4-hZ74f<6(CEIN?aOXJmk`J$UQ1I7-uA7)|yAnpL&z1 z&uVS}Y>xCW5kcyERe&>*P1>XDPtFbpYJeWSG5XW{^V*|Rv|v-mucZJza0WdpM8*lt zKt67??WITSK+6G-1avg#`9=jcFrj&;GhwhRGVDm5di1TUY4{kfc)kx6atRxDeQP%t zV@S4{yXIu^T2Z$d=~!_}atC_SLy#J{WgQPr4^pM^9M(?k4c$d(-JB9LS%;Ewz^&l4 zGLzMmBqJe(TeKM?J!+Jku+MI4?V90+YoY9NuZZd*$v^?Bm*WaZ=M{Pm7wK8rm;p!Y zTrv2MUHk_Ld;YUHs$hU~S1&kx`u3`*KZh0VFNFE$@g9-k%n%5!*HZrgk1#8Y@X$F? zoL6J2vwfHW-_pKbs{QId(uR~8Jcn32q#~)D^*h<(neb~WdqkLr6;$fD#U?7$ROaOqZ{eN0zOS*ptM!xMzM8RH#_#Q4nSTxR z_5RW!8 zWSaD!9C(>p{i9dM9jm~#y;plOZpk&`;ZspZW+pLmXOZhVVo7pX4zb@b*-tiH;qmgrETbX?wukgSwXGq%|gIJU(H;7YviX=r#`6qxKgH}qdXVH zdU=HiBEE3(O}TeqzPPWXd}*)RNUX51IIo@c7W+)ak384bVH;7rkCw(lE!roUzMUhT z!khh>FTdsg0N1ZVTO~B_u&??50QJ|i?NQ+UU7!Eb{yFrY5D=hBbDGrEH6c5J8{|5& zk@XehnntXpHa`tr8)^e`yMGA;*WuH`a@4(#zJ*&fcN#vWYL=ny23s9Il`Zdx9!A>` z+5q4R^KqycZe+F`VU9kBn%lLW-ap1}l zRl6&+w;4PVYAst(jn3r*o~F7X7N^RVhHc+c1l^oV-czE?_9OJTxwUILpf_?ZR8%A z_O9AXkW_8xIKZz)yK$zdo-3Mjb42D>l4B=RTb9Iuf%lDK*sk@HZ(L@sM;*#AeB_$> z`ilA;F;3jZ+)^w+t-|D2ao{fw!{QxKPRpBUj!t?KD}$Ed^)!!4f;`OqNbD=s^?hzX zhkge8G$Um5rWePa>fNsKM`2feS&=d~au9=&N!aX|K? zp`c=@H04}!X`Fyar{|DIK|m5yc{wfCo!bMNY0hy-WQ+3gNgx1xy=V->tptKjDF}&1 zIjM&oDj|+mrbC>!YGNcg=M)Z^G@#=GgHxyuA6k)eM>Q5QK9u2|q|`F4a&8Ao2_)px z0moj{2g_r%KoXZ>%`}ij8Oi!nn;mEk!vvg~V8?op9JbR;JRT?=iAK^eItqT@(wZ;_ z^QQ(YnnjN(%`^a8+MZ4q29y)R_N2Bba2No$UX<1Z3@=)C{I1`6jhhF9NCs!8y| z&T1jgT9_V`fO6n)PrU#f;2vmElS*4@>&-|GQ?Lxvf&L*-hTX}hp~uR2p`aukGCEX& zfFAVqZ8(|ltun53!6D_-88xSaaz1?Vzm1(WeY^P! zUU6~bBF;%_rSS$pn&X`R01bI{#JH0+^w@ean)V|`CyeVz^v2iYbDYyZwjzI+oZ`IHNKbyXIAJ zQZR69O5atIGqOXGT;aGvFlrAp?%}d4R)Ub)MQ zlJ>C3I<8i#nV9|R=Tud#V`)n6=V$${HsG9^vj(Kh;gr_`E|E#et1;>VJZF=Mn=)G2#8)RSGTb$eFepmvs`J3Do= zPrdv{)YX$O0A{%pe0LVlT9I`C!x`tb4wRm(<>ozKNghdQ#QJGJsh)X>4>vFS$-O{gu~(KGq$@t=fl{4K1ka`P0!d}rIzxs%6A z`iJ(Q@gIbB&m8@O!GL{^%P6FBIO4uvesBeT*MzM)_-Iv{vuOBiMJUm)I&gPMh@|bH z<0IabA4*J|;=MI4r@DB_goc4|Oyt0KV80TksV9%{O`80nAeP7DD*N<4z(fxd2O2PY<@8T-PWz~ZOS32*~b z*U(cJ7|A@)4;iW4q5JvAdN*e|?Mu)DNC4!}1C#^BJr4sk(bqnd@Os8kTWYFyxP-m_?~gpsB(SC$jNs_HT-Bmn}BYZFo;l4Rw0H;cO46u2;iryBV&t zTKR3TE9L6H-z0st75>iM&mYwrGjUdQcf{&?RZUO*e?Ij6ET1zbz2)#&`M>E6-gx4* z@$D5uYZ+~5R>g<$g4)% zfyfv&M`iq@?l(Thy<<@Eew#btQDXStI6T-5X{?B-MRL!O4Gbor7-g65*+i8K(9d4d{N<8*CIKYz1UZaTN_%I)UJED ze5RdU>ZEwphr^E^&KBz0$=LzwI(;+JoZbfU6{X>iP=v(9M%bArQ>k6QN&jblo^8IsXJ`kLxI zAFKFk-a^LOERXLB)>(yWbex?w-^d}9Qk`WNRVDL1Kj4pqzh-+dm!3R2j-_-QZnn2V z0bcku`yJrF+H>|6_&edK55rG^w;HCY2v+-6ttu>Rox6@-?StRaOX`Tg+I( zzSi>C{#hmP_lRwD^QvkVlPo0jD=Ef(&2o7qvzq>#Fa8I3AH|*M-YKJ^ab$B(5*$C0@4PaVOe&pG$0vCc8YArW&+jy-Wozz@chaHMyn zGC&5?Ox#5wCxtyJ8=fcvbs@e}LFWLvi#p%oBnsOJt4R zD>A(afdGS2oHbTPC9rBu*vX)JiUu>2$fvQ~c&2U+2sHI!jz<&;4sVrsH5NP5N&P8! z&P4^?SbCB7dSacCj=d@&)MBI|dv-M~4KeC653NYsiOJ%d(BxF4^NL#D#R$9{8gRn! zXvR)I&*woaj8FudPI;w2%A*a(%TC>b0Q%H`0y?cY9k|bWL4Y@QpmT##0-u+fZtiJD z(e*T(2En8Rpn`d)#(Bp|VA^N|VZ(}&$nCj!<1`Y*b>OgEHbv3si z`n(gxV_%ghPhng!_>WqD3Bcb1B=o4_I3W5|m#kUyOo2v6JxzOmWcly3das35hTPXz zuBaBn9+l7dRWemuwRK)2UAFkgY}d(Ef4@iF(O>MXo*$_k%yCj_fV86&-B12LGCNe- zN|QJSzKiT1F14t2p*IY6uA@LgCg!v4t$fAcbgs6-%*5e^eDz7C7LTZ*P4lT5+K-m8 z2XW3gt{zuUGn2)28oM7SVKu^;9p%}I+X#;W_b?@8nqi| zYV=oyI;_*kMMHRL-WK4u9D;6UY{!O@&V7cTI5;f zWXhIXM%$mvo$u`5t=oL29P{a1wb#KL`(7u6kHFXTh2n34))DT-xcsZ;e;Irkwun23 zM>Xiv&ZRb!G5*esS2EQ6I-B8XR@vqC>%}MhD!@kXEP8aW(T@}SIfByUlOIFrTvPZ? z5Dr%!=DvnF`Lvp6n=C9ZvGA0?3pTCygc=WD!oqIWZ#-G`I>hV9HcldESWIP0Z6;f{j{J%7K{0)CVTDR=2q^xEB%sP5? zub(_;`#D0NZQx>X&nbI&QM1$!obJEz0Negbv&t4r{&&HfDj758?f@T}># z1wwJ}RD_)-4=3VHtk=aKxs`t|x*Y4F-P zBi>@q(yjjh!n2G1c$$ZqzT^JRk7NJU{v)){hOon@o1Ii zbwc%N%w2spFd2&t12eo;H z&w$#{+}cJ&F+?+w)aHyHZqk0*S7OHH5pq(a8!bALe{L9402NDdDZE{Es5E zYulNG7SWRJ#d{rx!@m;AVwWM)3~=22+WX)3cKwpu5%F!s%jp)D=0;wP(NE)9+V_CGQQ(1Zcj7pNe`;b8ROFm@HTsqC zx4_;J)-+qIKM>4qFPZ@MEsi9VAGqI9UrRT^4+-mf)`g{Nu|+wK0@I^ll{o8)`8-b? zQ^8jDb!mI5_tSeT-95KHo{t8+T_sW#edlX-TI|;QAAyqH-ZKkpPtX(m<~xX3aM(CyIj-7#Q-{v-%9XO4rngSX{{X{1lkQ;ncD7lV!k%Mhp3Pma z_;cTW9eh0S7MtSXsM;_a!b@r-IS6}?t$wV>9-CpIY4=_riVLZ)6e26Cu*N;>^CQO4 z{5sVs6+T>$}|klgYiF3KeKUN4}kW&A$PTG+ZD&(}dRNkVFO0O040vACQ?QiJ7NZ$1{ZE*EYt7}*kBzi>VZS@K zNOrfTc^uckP{w1d=;jk{l6KdA-nx08cK)wTCx(rt(d^M`o&?rBJp>+pCfW*ZJf4;L z#rtS}&N6&K(T|Dz9}>0ounfvhGyeeV72h=1KMnkJx^?WHa= z*(drdrq=}U7yN7J^7!L&9CbX~0aEc=>$6X%%<(bQuzB7Fd2TX0>E^utNApFg*<9(@ z(pX(7j#yZusp<&pSw%2-uh5U%li^N@;BOJzYnn^~C%RE*)WhMCaKh3<~Evb~|6)+WD++X}L0;eH9{uRwk;vkO`G_8r02~%1P+JE*X>vNA)ZC17)YAdD=O?8E^TiB7 zB+*C-Ly^euOS~QhMjr#ENN`SQ0q2f8^`$$B$7*5DdFxLNv}3(3fS4XQrY}#Mtsp-r z&otskK~Z2NQ-U#1EHRN#=NyV&hZPpM{YS42k6IbAN#Aa152r)xNC&GD0H=ZlMZv{5 z91I!(Zb(S!Pw!E2_*282ZKPfGCLW@ff6b1#q|P}Mof{&8iB*Z~#R_>WX`N|*hw-Ih zk_-dEsTY&QRyq6CVH@~`LlA)Rw=|o<`A<~7d@%qHZl0nz_6GcXC&fb+05NgVd5(%uB!x&LGM77jX(A(xbf(>7%*8Sj`_Yb-?|qZ!Z|@%2;UEhk&U@EM z;$w(M(AO*BDOfUr#w(-oGnR;Z3i!$Y0K6mbseih&#kCd!;{a8?7;c;q%~RCAY0X{H zvFD6e(!b%KD!+((Mu%#$5s}4q?PMZhf!evh4dH;#YV5B`+(_fSd_7kA?0%PrjD4Rm z#OrYlnH9#tY+CClkPkSn?^TJWT(20cOA9GpQpzh>OO_16q&ey%>z~BS=--?wt#i99tH!?{7?zF6_+hGD~^r&{){Z{u!-DxAoN@von}Tk&5)ybg%UN%S=^ zrza)KdlaQOuYCB^$KDx;%-=6__}9;xo{+GUft**?{v_6Ptx`tVde@U`ntqcYJp0$$ z(B@HBIO|F+=zQqb#7GX_Gm7;u32BjCNTpZPHK4v9(&T=}f@AfOA0Kiw)J{N;WmkW?b74S9PM<#nt zt)rpknxBNCx`2g;sPwM^@u$N!xrOl`C)&SCV9@kwkPXA}u4i8GW|46NDzfIe;Oa%( zgU*S>oHxe&6`p;a-z)gq^0oEAj8bTXkTxw>Fi3KaQ;>v^!yyOB{C~bo^^3OY2FsJ54TV zyyGTIWB7B>kEK`BAIDFrCZ(jn^2KX02>$@?lV5yR>ZP4_M zL&VW}D;kp|{%4jIv$!5d>0Nctfi5*I0$ZCo!|Dz57}O z%GE9{w@eUiQp146e|UE_@}_Kg5`Ir`KZ89xs|7yrAUfy;I?akE&@_j<>PAN_kLfzl$_a z4%y2duWVf}r+=3%pU%BUM%PBGVzx8I<=i6n=y6|Hi^Wb@2vesWI{yHf@_0-f7CLpR zOJ1IxkA3)Y;-3=uQL*82VwUY0OYX+y@A%iM{4&)Ciz3$iJr~J%B4@!qopbG;wepXI zYQ@#T3Yq4DCY(o{5)+fOcRtmv;0ze#$9hO3> zt5P=R_jT)g>3`7ucL|s{I&`L;z3n`$t&guh68J(-6!?N&Cidzizta%SZyrY3nSX%Hn$Fl4GE%RfL$@{?B8%IyXSIz$bv-JM}+jiIbteTWo0u?VC&A5`vH(_ze zC)T!nXYrEz#R5BbV>2I+yY<>Q#(RqS3`SjvshCy6En{S!mbLZM)cZ^}D+?S2JWF2L8v!MeVO^7cxO@3Uc*vZ;VKg4bI>=hQ zMn!;GW^wEx7bP`huGd?3>ACOc75=rA`(-5;9b2{jUpc{9R?3MtyArC zp!>|DuF>BW`{Kc>bG9PMMXtJUZ`Ay2w( zPBu^LFvVdtZ$_7I^F5E_zrvphd`R##J|6KtBe~m%raq?_Ij_P$jUNVlL-1$Bqe9lm zc%>Vmm4AtS{ppGk zJx9Gezt)joJQ`9!>p;Vfl_%Zju4$MVqdbr*TDT6*1}VjGGwDp{6(g4DDOd#r=AX}T zQEof4=}PW??F!-^#BKMe`3p=0=sakF$6W^^k09$r(Oc^aoy!D{6MLS!A(w+`I>M_SkQP0w+s1^sc zDxJT3twEFc)AtN>)}&;`@NtS^9<E1tg4mP)-I#7zAR0*b0#g&Ux!k z2MyAj&uW!&Mksp>u!k8Urvod=sACx*8k=r8TFrpW?A<6EAaS{1(W1bWmr3_$0#SY5>Qs&@wqgVwqh z=1)XsGmzg}*Rmruh{xsyt7^oOG1I+oEskh>M^_QttLQ5iQXr!NTQCok0WVUBT% z^WpIxwMFh8eRwec09uONW?&6iyWz7@Tb#Jht$W{O`Tqd4dnbigl@+z(Vp1?V)?bA| z%7fn(wc>UC-%f(QHgEl*AE8oT?2PeS~^zP7kG0)Oeb%=_B{R3i>zvGvrtC z4|VX_b?x9~T{3vDvWBC}(G z-oKwOg8JIeJgFJR2sQLKiT);dr$Ez5v6mr1#(LM}82kgTkF!5jp^H(=Q;GYRs(#Y? zaE+n+KqW^^*MWGW_KSvQJ16V!U(TMl`%`>Dv(|1ApCdgoYa#ytYCjjxlQQQ%_4YLQ za->pvAC*fzrl!@8-us{07D(F*agWBcWc{LUq&QonpJQKxZTm^9YbMI2a`$}q2 zwq!=n*1G=yuxD?Cm7c*f_gnrKwcOb<-7yE=v)kh_X8EVj6FuKxgq#({8U&wjN^UyE_bMrHi#@xCwGQZKq(;QG~+{iChe z6XyQ4ul7Yn_Duf(wW+_fe#hE;PlcNeARyOaZSh(K04$@odi+*{_Kt^W#KWI@=rqsT zHreJ8G}$MNX1S-7RBKqBFwH6Sk^9%+PmK{<-6EN~``6r_3i!in(4q5&PCM80%ix<| zj#}@6Fa9j}cf*(W{vXl;z3ttq`N9P~8PRgVeNH&8zrr83Hj8TtthbAw_p9P@Iajv7 zu-4n#r>XSRvj|06ww|Z-t7q}1)l(jD&(@o9@s`$JvkV`4`~kN8w|p-f2<5#(f8MKe z{@Pw2l}C|whu?L0U)Y$Hyjke|dj|DVN9}iszAo6ZIP-xYTKPxDpA@ZUb_(P3ugslm z_PVmVLL#^XANH%sHE-HZ-s#W!K>X|2!{qg$t4B2R3_ZQpevnV`9sx1Lf}#Hag^>^Z z^cDGMFUOdqkg%M7bszjMSik46{OjKSKJJhzRdeb4{b{xNjRtzS-N)-^JV9BNW9 ze(__|@U349XdW|{M!ddz;}bg>B83|{CqA7GMXb+b5?a0RHw+z*kfX89VT*V0zP)LC z6w*eQr!5I2ZBlXlYw>4#l;0vrzboF|Kj5F!YnBxkGj1!-zf-&Mr;M%j%@0P>ZY)97 ztV71-j!TB=pXXWHHjUzmKjY=2w@#ockA5qQ@tk^gjpInIv|~7xu_MgqaQVGHmFPOR z{3Nm{7@us&uuBUBvqRi}@fiZ$e$Un-xgGuq0 zy(Oegs5Ex(Ao)-Xv98QrJV(@_2qmxHdDuFbb&abzH>ccmnzY^}&~;STbQ{Np<|f@6 zCkN^8?kg_m;+DN66KGn!iH;p&Mf+1l@`*Y7-(Omwn#YP})XX=LA%ugmfWfIXog-7d z(BAJ|w!w}5R$exbU&g#>(xkaoCMoT&UoP7-*mWvN%2;;R`gHUA(eHXsi!^_N4&ENp zt(#SSYTT;^=^8&m52*F7((A?_3amaJj|yqFEiSCi9j=)2r@D&ycTU#U{{X`$U4r?c zxYCqF$m2WE@$%-qKjC-6zY+NTKeaS=iR{=C^1EP0dEtj@_`F^V4NW{wE^aM9dEKsN zw!clbOLOfonA-I}+3=7}HSVh?%NM=6@4I^*zwqC~x_-Z-U+Y%)s*~8EklVKgOn-K~ z^Ts|Wn_kteH7lfdHlu2zag2W>TD}hPR-NJfW_<%vwq%+)cA3+a`A>7)SG)XI@dxbv z;yVcD)GlFy{{YC?WwiNsHb04dYvd|atA@;zsY+5(*`*ek=>c%pT z>APLu&#L>6%lW)TuiW^8?&8KG5u|Pxr&{{A;kS=8p9E;L>zdueL348ng*JWSTaTD` z_pgz4e--II6x8Lg(45_&a(ABK*SYv({t@pTcxlc0c~U~VgLbdj^GrQDm}q-yZBusJ zdwkEwGQ34d;$*2*y+wT<$@-(xJ{Ej))IJmH*9oDRZeHQH7G)UAbDHmU{{V~Hhm7@T zj<~@^!5MVpX*J}34*mt}{yg1(rzYD_+XU=zfxp^4g?i4T`#ku&N!H-d#Iaq@$_`>| zsN>eZImSmKSm!q-?JcY7ewl`;oksy0Nk3a4lYpd z!1o=hhsC=sD^0tw@g{{glLXEqxDlKI^%a5pG5Dtc0KhjorjM*^X$O#{MZBDbIOJB( z#GevqI=|VX(kwjmyJO}E&&VIvzXFtTH1oN}e)N0mW}5TU?$G^e6Q?Y)6(ghV*6H~j zmHvaF`2PUmHnZVPZb;@>;AV)qdV~+xX@2Psd*h zb<3Xy+FZB#Y+ICr_ffdTd?e2DF_uG@a#)kxn*Oige4`VV(5;B6Q<|MyIIqq8&*h#u z&G4CyB9p+Cb29$R` zw4@F*N!(QoO#Hb6uUc>eZsIA&oG7OAw={%5wic5cNaKnJ&B!!w3<8ey0QKXR>S)`O zN~1q5D8Lz@2!Q!`Kb<%L8z-$whz<{GQl#{t1`a;79-S!18S72usSJN7lhTtU4k*dS z+FWoJn1wmcJ!zfz=}rXnq)|v?kZs8{;0tm`(v91=pmj6=ypzp2wu8kXAStBCGzfSD zk?b&Q>^hZrEh|K~$Wa?!EP-dz&mvr>zzffxx{Q)Iq+Vzup{Ge!unPDw>0!A;6c`bXd$+{W@;_04@Z z0M`y<5S`*qTk=HO%slIui2@z>BrF@fYpmtW$Gg~1w+Pp?^YTk)S7{i z=L5ZHnT?qn`gW#nBy&$_&fEb_Dne$o(2iC&AfKO+P6I!UFmS+QH82^=9@GlKoP48> zD??0X7cq$WmPQJ{TFzk1Mk}@O2ABP-<4YTSW=XYk+SMaHS& zxP&?_-S{YR+_*YK~&Xi7M2Z5(rrxzdc^_`VkX9-fT-HoYZV4_^-*Tw8Jf z0JnEvHLuA0%{R9Nm4mi^rmrTcdYHto_%Wee82xt<{CDt!rtl%MiF8 zPDv+~Z&6gW_OXV}-pL?`5MdlAyP9bI)BU@T%8OZh=3 z@#4G9L&7s@mx(G3f;3#<^O7r~tB2LSg=cvUi64^#L#rcByx=Zw5vZ|3Ra{cPu!4gUbw zta-e5AA$TYta!djN?M0MWQo4_wR0a3JbB_DgZ}^zj+3Y;%ZyjN$$3^dM`J(p~0GG)H0e;!ovR5ExO)aUy?F@ zBYZ;D{w8>c>-xY7;dUq;lzP_)t=rOu01ScFnx`Ds^z|AP@bsfbjBOR7NAl%r)o~SN zUc0=Wt$`eKLx2a?nyg6XjomA#<|W(SqscTj;%QXmjL-t$DD|fH&#gQR;a8sKnS+m7 zLmWmpJ?WtO^PZHWu03fn*z;5&;O+O#0D$;C>A3IKkmMdkAS?n#0Hw$o^{B&R9`tff zO+7|Vt&zniCmX6flZL55+@zY33xSe4jq#yu$H5ZI*7FlZ4c0f7{yhgylVoR0MKf-yi2Bo4IDM(!!3XYW&h+MtfK z0HBa@PwCpDKsJp9Dt zwL(5mdT>3d8WZOu9XP1~JB2ksBaWh0I!`liSj|4Lsum(z)lhhebb#*nUPmvssu0{(`j2 zWpWKrb;E5H!jDyk;$!)EtSjzLd*ZiyW4NqcKsNlIJ*#+pMpM-oU=ZgArF2?(zyqc^ zu4#_e;PG7+o=Cy2x%?+3KZxnq8*}Si=B7(29V?<-6d0~^RB%+_bgl}25$V+UPA=mM zAp^BZZV#BW$^QV>bKb05AoCRT!LMojCxpHu*!(VV6dK*}Da;=DtRD@NfI4@rPZLPS zB0B+J8zuYnet`#UDdTtF>$iK>#)JO=9~+Nq#=HQ`Nv)j-fgwKi^iuc_m51?9Thr&j zE7($BL?@8voby?B4#b094XSysp4IabN-pQx(|qfc;x!9Li;yd0NgulkYhzQkM?-;J z<($4v#?ot4`Qc-lQTBCieS`2e#J8O%xUXjM%!Um%T>k)c*O>eVQzfLb=fCu>^WseY zUYjS-=D#toY2*1>`i%?20d#(C_^_FEyOtl{HO&Oa6}zs>=4%%Y2OG^}hWXCw{f0V0 zBk!k4)yJy4YZq`A6U1N;xZivnn!+|R~6uS zbj!V38%z8|v6Af}?~aw~niIvUUus&EHw=OmC0o=A`Fv$*$;ySax96etI7$@b2;w=e z@3*F`=aa(;*H{h0nGc$?t&#$6M~T1Kw=LkS~hxrswEp(A$(uNki+HNx{?DpRMs zj-IPjbY<9H1%t7n9aTe?;p>tMXrNycDK#*NVw@+H=Ymi z6nYz87}?yN+b61& zK#dP!-no4~IIP|6|7pKadk>TPo zk&Lg)c5|B0x@}5P8}C(OLGAcg(H;ONP_VbYcTn+-tO>6L@-j&kr>ERr*<38Pu2iW9 z+luw^lwKl&r59$83k8V!z9H3|ZkhHC#PC73Nr#w;$sV=Fc+*yoR-WB#RIE`Pk=r%L z+xXUfR(P$h@QaU_j`*rt*Nfq_l0||ns2Ed<_&OLGwCby^Pthym=v1QGmWQfMbl3WP zcW{g}9D;wmJJpM8-A?ik?JJ2H*qk{e*N*se$JXs?ntq@N4Bt8Su9sH$kEB>w&!y<+ z%y;{zu&JI05TxlSb^HYkei_qMx%z$~;|~Q!E6(xkwtlA-Z2G1NIv99b zUEb@^?*1kGQSoQQja1!h(-RnMq+{-_dAMUzF;kt@3gq{%(&$m8hNBu3V`#33{HDEX zxQf!PUQNmMWV^W>Q!skc=OgJ!yQ+%pIe?E!i=p{>rw~RlO&=l2su3apB0=50^NwkF z6o3QLmXHoy=8fdkVSu90I2`pfm^=?kF`Skh)440fAOqJF(;)`~nhx#Q9OjLf12h3R zIXriz1gNOg5zS5ak3A?DBV(_6UUBnMFguMe2D5vOjw7Zi-*{q}2OnB?;s$+cL` z1a|9E9>?>j6o5~^H9p{5ijuB8btF=4$fX0EQVipdTvU`U0Kq+}IqOW%QBNH6`BX8X z*v~$c++^hTr|r);r-j{|{&ZhaZX{!A-BYVD`A=#BjB(nRrY+ZOi}^} zY}1*D7^dg=sLpUrLK$Klz8BKBtjJM}bgUSGQ^jrD5O8Zb>~9Z?Ltk*l zJ*vEz>M5o1&qJJIqh{PQTnSk8`MMITU~$^AuUmILYfVSWLG4*rY#SrewvOgA*ug)4 zHM?Rnf-4kYFebNc4nQg~-ny!6VGoGt;^95)1bhWNaLFJp0PY7@gCLTj4}gPd`<|D1FkDS!lxiDXn3G@ z9P#}tFJt|@Ob zc(teEIm|M)4+gmFXFhg%^IrWpE=!*)TBCJret~=ex&Ht{etXw@@m5q_GEKe5YVtpT zmj*pO@z-}X>3XH$uTp#w$X9wdWtxKx0{?ng5 zH=3xjZ#fnF5?<;*3vb?K)M3`9$nV;zteDR|Xl4LoJl1@!<@0G`ca!D!%~E5#<}{vf zPIFNS4hZ$DQ8RMBjF2c5VhPgA7ZNV;hd4dzBt17(U0s-I?&6K#D6h)7yBih{Jd!lY z7(+MB$EUS=1*M0IVDQb>yQRR*WTYhW4ud_Z+TVrr{Vu}B`#`xDNhY3?$|c&UJFTNEeU_R zPZ>OBs`#!T-Xgm(3C^>1#Kmp zF#{a+HFi5i91&ko>Lhp(qjMFlQ2pVGZ`mUM06+e!=zhdo4y8qP zKmXGHJS@Cn@k$*zV}jSsh;$@zTqdXS1I9(lU)dnWdsh{8sCa=Htt@V)5unKdjs<6> z&a-oD?$f(%J^8Q8Dd4F^xwuE}X#Jk1Hl*UA8O!iB?)+h^Tp?L)!h_cWuqM^+9AHC^ z^_=r+uPKR~k_KwK&3LWzu@&#ag*dGeJULaIz9z}#zqvfDbzI)wl1|)am1DYeVpxoG zRh#WdaguY~S89xU9Nf}1thEhFDL2NP``2*>i{l+zPiwUitRDEyd3gI|ZM3n$=DN)} z^%v}a0)wPC!UqpX?g^HBe1@(v;*yy#K{pIPCY)g@s4w+UavcL zx%GUM>S3w4CCz8^>UFwDgM4E>y|vtqMxW*)P2~Ro0+Kq{>nFp14)|NZ8gy1RYxcWq zxJK|fY_J$L@&~~`3;45I_>@)7d5{{ZXP;TP>y@M_E9 zPsF>6eJJjQaZ}5y@4ECpSBS8D+_>s1O`rAnA1U?Xn%^kRPU(ze z)|y5X`q%8|@&{UWa(SZ6W6noUtpflCJeonA z=QL*|4%CDUNjV&fLG3|eb4VCp4A`&0QDk(3}6~oJabGr9MX*A z){qh_bCcUN#$XNydR_?jq&O@n5NELU=A@|bHxt)1_3546pS{E zp7abt{N|9ia5<)h;{t#(aB0{?zz(?UPXjd?kOtb5_-O#ox;dtF;M9hrBP8a5kK763 zr7Q^dsCfr8p@=+lK#2h)^G+EDX{gsDijlh-k&g!*K9r>GY;`oiF;apU=Ccqf+W!DI zPfC;ww-nre4|7gdBbuItbsWT>nvqEW8ZdF1a7@&d*5mf%b4!m(PJWaENjV)VSw!JU z9gk(#0Q`2pATB4h%%5n2o zoLSCo2I1{pEIPu=_l|h4f~g13&X3gTREmcqJd?$`Yox5h<`u))TM;H(w@UWyawzVj zYz}#@80o?y6-9gW>VEFb`C8O}dgT-8FM| zNgSAC^{+M4z8m1Z6KFI{FaUx`qg#cvYp}NxS?rcW0YA5m z10duY<}9bWklb28aSV-;E82o>bH$!arOWQLRWBc$0`R9jYQeODY}l#E%`CQP@@<^$ z0<7(_YbAJ?Y~}NOtOC7iT_;ae*P)7J1!Qbs^yZnQ zE!FP5ZEOJ|Rtt}dc z_-KT<%7*v!HT7TY&-**y{?R@GztH>*ai1Fac5gD;(2yE{w`K$43_IK6%1!Ml6jU@TQPkNUC9IYT82L~OvrmjxtZ-K|6ZZen13u+fD@c?Lg_xIC`Gc zi?HsPG^%mHrjAAjYFuFC(i%g72ZQvdjAxpSe(ncak$C2z?pa_A52YwL7!?w+2Mto7 zIjFN3qXME}e(rMb15PQ=B;AWkk?b?A{kN_CtgHq=Q1KO)E_m-t! zT2>3DP8XbZHLGut(zD}Ge)Vd?p_>DttR!y|&q11jjvA~t-2l}|KK9Ytu}4up_UJ1f z?Y6PSYG=5ti-HNsts}Ulv7M55Jv&zIl9PoUD+C{(Z))i@;mED(Y-da2I%tL=`d2Zk z9{sDOY$|ioxouG35_;F0PvSi~zX{6Av^fH-T7RuvaB7Ks%1Nu1qcX34YukGs3iyv< z@aufM`evZP^J};8z6_Z?D}|Ax? z-WNM0B>LB_RetR7=zkR+&m#eA_*XX_?6J+k&MRqU4+h+JtW$1QOt%@YlvYfwkgFepqMQ#R)?eAIN6Xh#q zC#DZd*Z5Z;O$(0ZvHm8;^4ds9>~UWM2mb&H4*k#2sxSCWR_uO%U69ujmK|9C06L6f zx+%1I{MRLVu|KU)Z7OivabK?~#QYJ)SJfH8iRo4b8mumzaJ{Jwl)<~3vgis~qC6`3Uat^ovt>_u`>_+A(%YZ#E3ixH0b z73kjryaRFK?}--P8)-bRF_;p4g=2`RP0*<3{o5MQr!Q?yLN=B99>M!rd~MLaIQ&(aEt zVd+rAQ=v!UNm$0dB6V>St5E#F|^XHw3u|sW3jg&8F9n0 zuPxRm)Ryu)JxF-+i+TV(9txcuYrEw%*duur(BcZNR{{Z`5`Ha)uG>z(d z*X0;^N)b_;U3>LEVZ_cela)L4-ov%oTVK=^S*Mlq;rmxRHH?;%m^%(Z6tY=gEXCGB z3GYzNWpytNDK+iID8)rR4;oF$xizVFYiO?&N?2}Dj8swC`OJ{U0(+j7m^9xlhGaRb z(b>N6T0>epC8HyLhOM@gWILIZ0oyg~HvSm1@lV5x`O@Y%FT6aW<}`8#of8}~k8#vj zl-X%<&DKL((r7lGLEOyb@z=F@b>B0OEbP-yZ|ilT?@+X3B;j<`xBj+zo8JtzUkwdR zT=<0>u|k8DVcxyw&&GOIo#WN<29s>}(Mu7ISmj)9$j24q-W2fOoeb#PGJA^pFT=+~ zvb=$cC>w@OE9LRCrs~Q`H>It852V4uq-dzgrLFI6PUGR~_?~M?yg6s1OL=2?FPN}6 z%Bd%@uhb9Or}hi|qxDZ3+WcDhacghk3!th5;zBY#O83a?Um$o(#5&!r>J2tcmhFNW zM^AeF-uNBygH_i46sOzPIE1J4^_iv4lczX0Ma$1uatZwVOt+CAQny4jzsWq4dA zS=zLl<=bWVbv}~t=lm0c;#Y|;B>1=SH{vzz*NASd*4B5^@d}t1{#o(VSJIyfzhpnz z>)={Pt6u5TUe4q*TwN0;_U=w{I@hOu$uFaLt3i`Z(DjKgGBW8SZYpc-KOT5*Ow#@x zO>e5I$O8BNmHfLrW^0IY_L6EVqE=dTviuSCb!CU6SyNiJ{dek9_+R3$4O&V4g<)+J zR#yWGil8qew`>~x@cp;HXB{f@QkPTGEQyZqEJ$CJHG0Q}S61-7s_NEq8SP?i!FrV$ zufF^bWz}VhlaDo=bzkIj;xCtCJ-Sy?(fF(aymBd(Jquh>|ugA}e{v5isuuCSkD!9v>btl%pr+9ZW#X(Z3Q%Cuu=5uT& z8ofEjD^JM&Zm(^pvz@JHQjUbH1Jn^&N2vzB#QmWD7E2Ap-Yn81!p47o2lM*Z$E-eY zT-Wwa6A=te6;=oH79xzW^_@8kJW>|t1HB*|2&JG9 zMJ92BKns)jRJkPkQ#k2TkOu;o_6?&LG}Rq*+MU}ppaavbNQ5Njm@slGaJXYpIN;;y zO7|GtgVVK1z#qz^Ob!J~IXsG@N2tgE_Nh8lYm>*NC_6#TARbpeDTP5hjV5_Cg^uis z0F)dY5k@o3B^|w}0I0E+$nQgf7LXDefF7iD_o-0)+|+8gImJ!)jzvRb6aWX!P5>z; zl=}V@-NPC6s2+stjARN90r{x`A9ju2qop7o0X%w|dah0lH1IJ`8~{k9KB1qQo-?$1g~+Kb4Ek|FV@iHu=}aR6hq%e3aCtRkb;zp9agmC!wUEahqvjNVbDD4; zH4W-IPy<*4!KQ)ohafe){0nQ(ooMp=Q2H#NO#qi$;o z9tl0GdeDuaj@8d2rWY&~P%hT9ETqbJR6ru5}1N+mYI_?t!hQDi>{G-Ezm; zwT|X;*rEU|eQTuC$;cfmog6l6p|AxA&wAFTsG46D&XD}k^y%KYT}W;u`d3eu5#zOU z8kZR(rEpXDk4mq?at@!o`Ks-jpEP9Rp_c*iQrVre0(q}t?07Nwk5ties1$lt4SldZ zYTl6ekDp4;*Bh25yac~{bLu4j0B~?}AKsShU2liv#G^IGumJI0E{x2$Aa$=^qP!0l zl)d|&|}l zXXuVq!0TH+B88cVUPfxRjctbq73-#z6ToS_LH0+&moKNJE6HPC(XM- zegxSy)QK1!4SuG0KfrNb>1tX$lC}8OKN0n8Pc%>2u$0w&DuX{A?!FM-#^y60JCACj z{{RIwX&Vv6lk~6FORt39W}?`OjomTtUXh{v8nzLKF=XS?ww`%9QRZir9HNy;a-@DJ zP4Gj+P^n{R*v4~%T;<2Y`^0QCU3&3fyjqvSt+AbvZu~32{Aut~Ylbu0+kD=`trRo) z)Rm>6s^pZZ#_Ln^Do=)!AD3rJ&7a}qs0dZ&zelwngPtIUGU;r8D&sD`7i(7Qy5iZt z@1FJa@XX~cRFU)Z%V{U@p90C?S;reQnx`*<8YMDFHmdST>5BWnd_9$q&j|?;*I@{bKEXo+?(!K8c_EGWW zufrDjwshAz8p-7Ss~;x+08H1Th{RwO7lg%0H79L)-=XvLsN?;z#o?o89duh;so|P0 zi)Dwz!(1h0Wtho>(2{a^uh)YQG7-D7w$CO96%( zjx*(u1`i!Ye>8qE@V=ko9~Ejk){cPN+|KeY2OlxX#~!A>V}~;wd=*?CVFr|b7Vr06 z?w3Y;80@;8RT}V;a(mj^`PppyqvAuNT#j&YP)VfRsm3eoDZU;^7jmas$d}>t-^9PA zeyXvsXUa=1n%Mbx>GJj%r2fyF{EGTt{tyWV{IfrWAMk+7{{TGAU)LaI{hv_()&45y zx;u7?$eQ9p+eaPie&@qF>`|O7ZIRSCHO;n{tBDAeMn`Ol{Mw%**`xIts)^a7GJCmK zK9vcT*nGZ~ZcBT$J7z#CGZTa6C>S2%y#;F=y7emM$@zs|jU`^Z(@~ThxfnI2Z7rx^ z#fQ?SPRJsa%NETjZGaJ)?JP9CM1^?%Wn;iKhi9l*83d}Oh~t4?m80tR!4)I6mGSM0 z`TRuZ6?nw=u<(SQvN{b1K-8s>Gy@DP)qFFd$~9%T)duu&oLx~`<6a}zAF(rsxp(Z`J?QpQ>DzJn|~&Kaq#0n zjw@kjYQAtkcRg$EUkAbCJrhUr{5!lX!w#Jf;a{7&3VdIV!%)}sXGkN`oGs*PKY17@ z1-A7dSJ{?d4}5v1>biZGio9m+r|Nc5-o`<~;Wmq)Y2GLCjrI1uXC(er+X>tS9h+!9EA_kfX7JbS=c;%^RKOe4qQ4Wdm66t&|pkFxiq0`&hfyaVEYfb~n?6>2^uxYXvJP`gZ0oI;>?Uc$TuDi~R) z(@S2jQy9*5=HVdtnfnNQF|hEhzl#14+9a2Xq2+m7drzEXmOSRa2tR920Y8GZRrYcAAZ%dY8w3TCXJ_C zBT435@ic%8{Hxa{zUK)nL~&8ZRa~&@+x&%1Nm7;-zGu?ksrV)F8^EyM={kmv6L6R_ z9zP*pi+(TAE_^fM3ymV=V`CD(LtoMN#19KRmN)jgEJP#>%JMhfY#RJx{iQq@0}XcB zet3=_1Gl|@X>itI%{N9*NBN(F@dgX`YOGeP@;){so@t=wlcg#s75jPl0L+uYqy)AU z)Q2CPFyIbo819%IjW;7W#SM@^rjyY6&>`oxC=2@1F~>By`Dg(E$i``T9VswC6xBIkX~dj#pcw9*X;*JwN?^f0^n9dX zQY4rY(9~z1w0Ff01~{ZKn8NW=1CB*Ao`Qrt9MuS&TO|9^01x9zFaV<=au4B11`Z7_ zKUjWUnrX()1tBEjkllx; zPvuhrdsI38_ZX*S=9c9v3(kI8Zv*A0P(jE&>TFhn!hw(0qy%)R+onY@Kh4EUL2`Wd zG^Mf6rB~gOGfpke1v>#N9P#T>vhk5ZlelsDQUSDPmZBvCJXDH!#X^}QIK@lyfz2my zEX3Gh=qql~fFM=`d1~3UAYhSP@;V@B%5X7N9|xMTBV=HD)mI>|(y>VD6|hsSW6S|@ zTE%jsBLcDK+*=~IiJXWR1Fu@&vL7!zeFKvAM{}AN%Ffu92c>&Ag>8yLIL~_dOhZvz$oo7WG@m0l{bx&c zavgDu)f*c`hXjiBzY-)7;I~uOyu#+|$&?j28LntbQ-+6aUP@Ec`-}E*g&y?-@G?2C z+%JW?b%dI1TfpUO@c#hzWM;XUP6_#kYW>gnPp4f?r^m4l0{U0tdD6Zj%>7d>m+mf$ z*V+c2mn`U@^IevOV__nQzysQn_d>sY&E=k_99DgvpQ&114xo3hkeofDwa-;e!`?kQ z4+mOajuaf%O=IwW#{MkJ5Fn3AyWq%fBr2`WYwAA>Xy#agxCB?vROhDbnbhjtT=@>` z;5LyWx9$Na{hITgfA)H@Xv=+^4^AufHrK+?Iu9{NHO=e(8M2NN8*T+@PQ66*IlZJC z*!*hN{{Uy32#Ya;lziGxmLPPm=%2>gZH1^|k@#1@-?Zm} ztv)7vDc1ZTpfk$@GJm|I`PbfMn98*AROct6X!3EiAzKSl5n8m*<;TDtEw^h+sdXn+ zn_{YGxUaALW&1{aNFTFJr@@%+hxQSe+<8C@m-QyUJT;9n|Q4h^@RkKZj$TmetrhMh^vtwQ29MxpinE22;js&HPVeE{lHFvS2iv z;C9V@RXUWhGE}NVRr((>Qx8`Q2U?u&o{rvUOQiVMQ;K|v2rMyQu3rLv9<}enn+;D; zUDDlU@_OL(ugCuYhdvV2d|~4aKSi`c?L4Zu9+|J{H^Y7rPYd{0Pw?z1{{SA)s+@Y` zn)%G;P^RHZp8K=k$>UN^b!W2B`O@p*%u0UX`qosx4sGP%r|Dl)$)q&Rf`pG+jCg#G zSvaqW_OpuI`;JvEhs;rYD}l!29)Hi$rT+khQE&eMJpTaCu`Y{x4?G;7lrlIOiFx>yHnq9%Z>X9_GC>T3-ipwpTYqdbDI$4<*i< zZp7QmD-ZWgeoIQUq_3~se#KUdVC|>gaT95CGc#m@I63C4$EAfieru$-)U4f(1CoE) zHJx#LZsTgKfc-1jjH+74jVQrtnNBMR_u++X+u6Yx45~9&-)r9ZRDL_+uE%pKpOz*x zr*?%KBhj>dQo_w5@}UBBkUnw20=+o+n@xvPj^z;6dnCh%X!{{RX24)SXU)S|nzxj*U$?*~1&uh1`qzqHM+lcpv2!p{oo`km^sxi)sr zBkjiQV-@(lq(42(6kFXIikZOBw)86UbXs1@Q?Pp_^32b>`Qy`Z*3+K(kRsk z{DppRn`MvLb3&cpTgu;({ZFXD;}oAPr}r=OXYNJQIEK7Uoa*NSjrbYcU%$ zhjMYpYU}*_4~yCroR(Lr;>toKVnA@f^P2o`>YmRK>RlweE8FKQpIH>88%2Hne(N43 z@iSETPw>hmyYN1b=G^YXT-@eIC)X#vc%)w#HTz9UJ!?>r{?6d3RpD?)O8URz7l1Sm z66m)&d1P-XGOh?eF#J2$$#-4?)HSUH=#VDYi#Q~DdRORJI+#gOQLU@$-)4EWA&89I zr>AQkAF6n|)=v;z+sp@*c*L)#HSizp8SvU5ZrT+6)aXtqx+pkw7g+4hS_M0Q9K%6(Zy=J!(cg1sqdH zCntkQI+IA{TVtNd+(7GBEq~Sw^r{Djt8huRg?jSX z@uKX{L$X358+z6zviM>>s%uN)tHJiHtyOvMIop$p^c@MVqKpJwR*!}4^yzJcZlfeuuK0IE z(&Rj-HSjg+q?%~`9)%}#;?EJ)bp1oSORkOYok7G@ml{uUYW_ z0D!zZCBg?`fJJz+%vCt@qkkV@rZ!M?uEyJ6 z{>nkMv5KLq>)J>|vSi8kuNCoUitV98<;gsQI5`=(qTqkj6oir@eVMioPvJ zwb5kH<6kaoUOKo4RuOCupx_we{z0{IIm zXyG5$z76pHp)IUU2y6#lmHh*L+T9GxCYs!=j2LA3f5b<)ufrdQzYbT$ z@;rZQOJfvF$9c!y9V_;%20EQgtJ-28!>9Qlkz+7bVVZJbma^CU&Syo{BeTB^H{A{2 z&{h_!7N_HH8jF1-%VgPsOevW;&C^TD!X#6jT;3)ko+N^9DSOPO$BsK)7;B#JMsXmPB zN-LrBlz$Es3}dIYN&f%};e9_!`ZVeBkGv^9!xyDBDH7~||JVLab+54`SjwD^o|We_ zO<^M#&;Uhs8rOjQNRr7cufYER>(>DUznD}g z2iCd!xg!h-#c5yY*9tth&*5C`FO9jd_AYk`ZPCh1~M`84iD*G zCF6q@m8zMFZ3Eknw89fh+a|XI%2(F!JiZKjX=$9V;Qv(u1cN>{VT}Dduheq z2Ym%bEm}wEFT)=j_=8f=lfyRGA{nP~9>zUE>x!l0pM>8Oz5!mz;{>1Wx+U=j;VsyM z9)F7hzAm%)qo?T}BhfV&?>wm^lP8|u_4Yoe`$%cNA+wiDp6~4tNQFw1pVGfKuZP89 z>ZwtJahJrc`k!M?bSlS|aDo-yyt#0YrZ1Z{7o;2<0d%6bsSfo_>;#vWw(O7mXOPHG3T1{Zx#Gn)U_QhO)?|A z$oa_hu4hxyE=PnS)~+8Vjxk@bQNepSQ-y6T^garBzG+TWdaJYIKa3MBz9?e6&u$7<>nzgP{SgV0AzPH7d>mV znsxyoQ$|m-XPhTwzsrj^LaG~&g2Q!)zToxpMIDRJ{-wJ;EIo|LB@D8F%%j#y%p zjimRa0Q9F|^q>G7XFcdArrt5dCluhNd8q-cbJL7cjym_GC-`aDA9T_XjG%nplpaU5 zC|)o{3_uv51S(G%rhuT+&p=Hs(Tr7G2Pnljj_0j4Teuw3^rQr%7&+#g4o3o*xCe2m z065QThMO3m_3ut$k_9&ejEYbXUbT>gzym&%fMgm7AmWe=ngDPlo@u~kdFxCk9XX(# z$C|xBlw%k)o3WaQ-khf#VxVN5dG)Cmo*|&X}xuiRQJ= zde%hEjy>y0idzt&1@JmoL1T<$cCK-FJFB6v0I0~Sp2DZ`8uFZ`K*8%=exbWL#db20 z<>t8!MB^u}E1H?tS7V=HgT+f2z#!CQZYLEl9ZBh4vw9vmI^2{SMKVcu^HFdP>OgmK zS=khu*qspP6}_fi3rRsIio{0EO1aJjM4EaVq}HhSO(VvZ_K<~V!St^{(>^OhYyzue z74mji9D~xIm)*D$E6JV~l=nSYV=F^M&$+BVGos-6oh!Vt_~R5(23dd{cdyPgnoux| zYG1WoFCs<*>t1Z~$`U;|=6HzpKWDrL`%PQfut&H9@vos$t_`$&uORNU}-b6?NQe;0gd8Rp|X`_`O48a!H~GO&DpwU7F|qqUBR=l9(+_f-6E z-Sag02i~QR<1)xW^S@gBL$&z*;u!gOgJa&c;{BpEfx(0FHJq{;^ha!S+Ffpb;#&M; zg_jL3Gw=G=)u+Y_ZQ)FS{x$e>5A7eQ<7>j$_BE{+?JIK`<~ObkHf{gN2`4gF4Xi>X?HtCD`qAmgVv+?dt|cs_e*P$l{aw~&!+KRMx%SAYJU)} zXM~^Z`7YgpkNM_Uk@{EPKe4aufn)ned{DWxv((|zEaCm^a?E+koPY6UXv_FF`QT8X~v@Gi`@Nf-Y=Dw#nb_T>05U9&=3ITxC!^6 z$*PA@mL?cH)%!c6HqTgDV{y0;Dch6#!~Fgg=9c=)6vltgL`psLy3%I?E91>(Ks{;P*S_c34<2UUOU)tKzHKqG;LqZYz))z5Tf%BqT7e z!f@Gq=~Gc`{(;2i5UV!SPOn$h#CdFVHN{?P;&5am2D9$ucQ1M zdiK_l>dP1I!((YiL@9OMC4r}3VtXDoV*4i!k+*f=Na zUo}Il>hR6ws$@4pDvizNxpgO&>{Usx(Eg(rEo^>p@smqL&!?mDk-xnc8jZnFpzXo0 zQ}7?eh;1&7vd+)Dp(egOv$NBojyJiv-rdD|{{Vr!P2ueV>CD#Nc{te3*w>SZ!^7D% z7~J%*_2=zVlSub(6ZoISSJTT4gifl%JC6hFUT3!Q`q74=Id>=SgI7Ff@i$f0qDwn0 zMxN-x zIZy>SD90Sqh39A##3=)cX~sUaCjgvwreRN7NCY_Od(#RYDdPZxPDSk zLCq=PVEfQL27#R8y*Wn*BAcDOAHtNMl!M-juor6drN=lF-QOwiN_gb)iUc?iPDMyN z15v2sbvT@#nu`pwq;39G@NhjU6$M5QYD|?GJ*gLR!6$$z2vhZ>EzpWk32oE@T}SjF z4k{+eJk*2|b4K5rC#52`AS4gQnsb~E{b~C^IHrB#cGEKII1CDR0OV6a$-(PN0iX#~ zXXdFF1CvbXX-VVhQduZ%!wpDDQ%Ivc3UI(VZoagd&}EpDkZVFC&picEjP%dpSI`gv z?_AP0it0u%wN=n#q~6Yg$P7&1JIu*aM-hVnn>kyym(c zJp7ofV3~hv*|c4%0PRkDSakj)UQ?9$e+<_fsGf0MZ2YbVYUA(7EY*!9Z&~PMZXt>9 zPtHc;PzL$1Y2bmK-&*wAJdynu6oY~>)|g4=oTI6wVf8I1uX?b9EmaWy0<3~ZK9z$< zLUt^IDTE$^fOE;CoP6DB4^~Qtk`HQm85>PNr<{tAw*_iViB}-t3Ykd8Dz4xLMLae} zDq28Y3{L~4Kg=9s@~Xl3z%(m@atA->+Oo4Xxooyi%sHl#R8Suyp0o|!C@akl1RV3l zO1yG?Dyp_RQ`D2+=|~ZETqw(Tsf2(IO=gE65#FT_xL{HlUh4klHx?m?iTNw*Url@o z_$}d&iFW$U#;2^ur5!HKu2f0ZRv*H(@`kYHITB&%UbFD(>g*YlaZxN}5F34SKof8xt2N)85M|!GVObGFtqm5taVq6xsW4~pHK~bz3{j8oYOu6__t5j{vHRP z%0y`vUYq#H`d7vmx>dcL%`%Q#HOWDA%X~gw^}|~ULokJ_UB=q3=Ugk+%jfP{JMY;3 zum1pIKiV6_f3$avb?+SOmdR-x>WU*lfxFig`VDz%S;&ZI(!U+Q;FU6)Zw*~e{{ZsZ zRR+Iqv25I&b6<&KD8mg}RA|Q0S|#Luu}ZA#QjJ=U?e2Nh`c&?@+g$C|lRG!vLtol}Y`dap-?4^#N%QQaaN8mfzC1Q!#2cKmXDG zN4!Ju&reBG8)cN1?mA+;<5tl8KcxK9nSdM<*P8pF{g&5F0&R|0J#qa-e9iGVS-EA3 z9jrk(Twwl{{M&%X*Q-%dsNKC!>s-St!%?K_!7sS*jatt}OK(2bLdts{waFV;l?1Ui zbJ*3*LS|4^*B;fJu8gIdgVy9>%=p>&0ylF^1kx z6;j_^yN!qhsBV2L&z%fKNc+t6;fTZ2o#PYgtrt)5<){-*rcCt99&7Etg?>8l*M~d} zf2K)b51Y;lHqr>{M@syZ(>zD1dEf>ugT;F>_>bbfBEg^o%Z6q7ml??V*X6m+LsJ_C zN^-ub?$*ca*%uJQ11VO7Eu&j^Jrm*u?S%JN7aD!KMvPAazIfDOf;98M82+@Eo;td| zgvQH(>s)pIxYI5ekEMO43jFAI84HQ&DHZ%A}>UH7urVHA5srG(M-7g-snvkYhFeE6p^oIiU@!iQb*U z_2z~IQtj>QOj``3BPThj2m^uIn}9}X0vxKnM>OR)29*@!G=*|=(=^040ebx@Iv#39 zd(4QiUBzr-Aa)w<9NtKq#aqDg^*0LD{}P13Y%5`F94Wuo02m(Swc! zKLC;qF3cV(1}e5xifGBmr|meUz)*c?4X}u3L9G0LC3kn1;UIi)p$0Le1oOP#! z!Oa5~1oYq1=KM_OQ9fl>}RG!FJ8j0|n{sRH476%ugSsld-QBe5bLK&>SChaGD* zNF)K;w4mU0tR!s`LP*AXnvr=U0+GIB^r@gs^sEiKjebrC6_+yMvsR6Y#ddzKPlp>4)rc0Otf1EcLtY! za4N86KQX3n8>w=Sn&ydcdUvT6%Af00cVzKWqkO#5<{q)~Ks-pVFrD z0sFO!k3&nAayst2WO@^v}x+tlx|+U*Ov(d zcYuGjUZHja?vJ-PuPb!_0884x-pA`)crS)X?#H*xq<=@B@J-`3pYZw%gyYPV5Pd)u z`pYkhW=-6lEAylN3H{+`{{V$er0iU@fHxks`X7Jc9Yr4=TMxp&5XIDlu`~5&>zH^- zICx*OARaW4j?mS1KNdD{8z9#O3yeYlZtWuiiId@hFjUTBiR1 zgoS-YK^KE=it!ozKmAoF{2`Zr+;8*vQSKQvE1&<;{$DjK8%y0O?{1a-w&$OwE6g;4%K|b^GhTZ#7_+;jdhf*l02}F7SHEFE8RPD~E6Gga z>|k`S)Ud9jqZw3IKODuzimxbAmB`>nmGKy^j?+Sx@s&nKdeii?YXd0*C)`(Bb)7oR zzC)5a$II!( za$3T=-H^jOzV&HO-kXAY7}fV+rqQJlOyV3I(FE{Ccp`SsJkxZ0V39kXnBuXmm88^; zM|$YxP03v2Zptplvf5t95~@SUuTRo-kF&%BjvBmP!IfP`G6i}RF2)0OH^>lVSIJ^& zCuGm8!&Pfk&CMoxyg}nK=jM(aC>8Yd7gst(s7rCRh#9Y+eiK=F7r)w)uH*-MSF?D7 zT}f?ZxKEQky{qN1tJ-2CMk_>pb^>0@4Jz__yR*Z7G$Aom zoD9=23mkW#fJYs;G}3YRM-%`eWagAd&$T!P+5o8$NF35BA)^3e6y;o=O*?5lDayQH z^sMexkg3xh>V1j_Jt{R{n-wP|aZ)|Vz`!`@X#ifQnwTI838QEjG}EwKp$T7FO}P1m zOTqb%G@*F)%{!B3L9-q`gQYm(zGG1<6O-#lZhO>- zc>sdKo=FRy^v*!%p`|31=e;Wc^V1+x2<~b7!Rbg!oT;qFFl1+p((pK^00HBrDZwNf z1TP(NN(l#_rAD~}nm2MY-jK=jfle)#z!e7n0O6x?+$oH>2qTZeoH-agR3W}&^{D~R z`RzzzuxgB<>S|y?=e0502c881zJy0A1}U3~x#pdPLYpFs%;>sn>kX4I27_*Cjz<^(%kX3tPLUaoOQ0( zQ5?LlvCH$EcBwK*AXic}c@G%rQk@P={o2pkM0}Ec4n%$KY2$I@wRN%hepWdv)~_dq z-)P9B>O~jq=K35k-JVAjijGGErFW^}iCF#Gka${Q&M+!HtV;G%?0MBT<>ucdkl zcuwJl0@J_Y5StmrKWh%*?5EiCv%4ASY1^52JuAO&2u`^>X$OR+05MW=Q|%x>Wj@CY zjKzwPRuS0b3hl>*Cm%3Vnw0obeDI&8L!3L6?C03wM1)g=5Obbuyk7_1FvvqwFM}?z zora^@MQqAc=h)zPlD|rL$mYBB@P*2@S!v7RImp|v{#2bvbY*)v_BatC>xy`N+}C=Z z4xId@cr`lsBIArMYCWVokJ;4hdDN}u!a?g@=YU6VUKC{3kHbiW zx5gwHI5p;DB^QO8N3DU3zPAM;?5wKHzs1PKdG(#IovH-&as2DJzWv^zEPcZfSe_n* zTU?LK{{WtY`PY|+e2|kpd|Kv|QT>1a0Kq!E9!JBU55XdE(5oNFSL?0U!|O?#CNOjD zUyuI)v4@YN@b7|jKpzN}>HLL$o7jAO(dQ5|N$aD0k{{X_s(!bdKD`cf(Cgy+t)BZEH3zojqf=1*r^#ZliP?fYe(&iSC z;ssPO=i0upvi+5Gqwtr-8r7BDx1J%;td6FpFZlOpU|?s6F5I(uWWMZ-V-@jkpQuSR z@yJ_p0D1c7+tR-Zr&g9GGWI&CztH}WojSN#rCUcNc*lutE(Wgeasn4Q+BwB^5ZNRU z!!Q73bLeWniI3W~b~|uMt&5fa07ixp#?TFY!<3ykBjMu+s0BwxQ_yNUSifyyZb1ZdT&}Sa!En*2uFY4H z*C(E7&~kMfVyvL4q>Wt>RS~2EnyECxU4Auj!u6|m2*p)n`E%6NP+6>ci%k7TO6bu_ zQhknlK1juPJqhKC8&f9SC?Dswd4NOz6eU zNvN%jdq(o$0lL*$VGDGUsp@-FQ(Y>q#I0Qp(XXb%4C1<t46w-BKMs)orZ!+PiDxFNu1) zKHWSXTb8dAEulP3e``Lcxtii(hQq8qK8W$F$z;2^lH~Pv%}{hX2CuLjgVKO9Lf7mI zO%KKu?(Sz5C9(zpsTD{$6>P`13rG~_0^-R)K zWfs#%EMmtUsw^<;Tk=_9j%thBVYwB$natqR)XH#kOd#~FCWAdY)DHkW8tG4|&e5Jg z4WC*A&(~}s_0&2MpC@eHT?Nf1TMZ16vOgZDFFPDsfDdropAx*Rd$jPk0jIKrnN3?^Az9SgUC}_E+S3^7kLB&o13!1PY z3C4x#q2;IQgn^2IGp3V$vuT;{uw?z|VTu7KHLCrqJgGr#*^g>bX#5f>^AO<7k1Gw6PZ2RVtzWZ~&M`vU6T#yYa~H~Gn3#{2 zk0^u4t;Mt_wMaDbKj2ikkogP&8@Ju3tV{>ZT?*;tzFLrJ{PHS!!;!O`5wPh}Y-7^8 zA*K`1P&C$FNU7#8k(~OWzyh^xt+K(fT99c>PIms4xoxHu8-T2(Gg!gg=f22DIO3xF zD-(qk*&6}}PPGr%vi|@m#bM5_Y~cGG+;%K+k!`*R0KF8!kwwQ%sIVJan#iS?+7>5_UY|9Wg_0BZ}CMO@|hwj=QT68yU>qq5V+;GOHMveP%?Bd<#2l}5UrglR_G9(3jA`YXfU71LTV=K;sHanBakiJGiDS?~;B1z4^;v-wt}9|L5Wwupa9 z{U*2Yew0*vtLK^Zdsh?2$lYP1(EP@O_I`F8#D6N%KeNm1AG-Z3^^(`& z-j-A-3(alYd^*xqesB(Jl33~YHds`CIZf~dhy1cNkvG8xg^KwnKT7?Wy!czE+&}I> z4u1dHgH&mOq3Rp^0dLsBXRx zS~}oF2Q^O{ex^!;Tchxs_Jz}Jd;9C4%pQ50*d&PR74H{u1gx2O)z}m*O>}&nrU9ZLs4GM+Ux+ z+eTSzr4-%wwyg4FN^3KYsatF4k0igmhyD_2?O>QmADYS>bR#`$>F%k+hY1j7BbIjH3i-0q+f8j?$zcF7Z^+N4c+Gv4;a}SK;^)D?veJvej)sQ{iOU=ckyTAX0>sv z%jB42dCqtpEA7erFFJhtd9Q?{O12)FrHzu6POWW!BkG*vTlbVA)OuXMY2(ikKmjuI zTPyKfUtBKipGx3m(ykYb@+u*w$Ub9=-k)tv95o4 ze>(BVbo+slJNs1MVY>eSB9CWFu`X4wx&PGspZEoFW2Ssw)3m#-ucge5Yk3n7mRPSp zbt;}Z6l0qFI`N-_FFqQ4Oz{W9eN7r0j}Pj1dV8t=0H=9b2X85qu^3VJgSK<6V4*jn0{8gu>%WoNJwi@E#V0qHTs<9! zIx6kGx|~eN$qA7&nuAPvOqeZSvl?&g2k^_`4~FeNA^dIDW7cDYZ<@*vlHbizNQ{j6 zvZ?Q#fb{8KCjQvpv!{i=6Ipme<5$CQ(0F&pt9hFZr)HgfX5-DbiP)0Ryl#@klet4} zB;z&lm>wg`a~xOHu&uW5X_h+&=SLtDl2&z<_{;#YIzrY zeo}R6W5O*Zjfta2e=)t+k$&b~z>n_NE?5aO>swbdr;@?Co|S}Ca0R+nw4-II$zFG8 zyK8Odv1aMoy-&inF-C=mu189-M^sVV*RAQck=sJWml*u($Hi7wa6L>V7Uj;)XT=w9 z1Z!;>$$l&7KZIHomRc)L!w$nF*MK#f>%Jm}C={}z57NG%g8ej&G+D)s%m5%)&SP)& zc+rOMR(&o3N9!}hRM}{bYsI>f#vHlM-nH?Ei0`D-by+84k)A%4^f$y^PD_0}uSq2Q z>*o^#Kg`{0>vFnE6uHli%`1CK+@@-;>MCIwJdaAUw(XE{(yS~+I(4t5Z67ks^^Ao% z9cT&A)`*L0?Hx@E5GOtAIf>&N@`d69Ovl zmQ_H-Sc)(>Jl0Z|Q+US2F~G+hR-L`8Zym*4hB4`!*1eP)+$gU)rA}0Obm?<3Ep*c- z-Ck?zAK7o-X=M%v3wLHhNItB%YWxt?G(!uJSEp+K z07-xNIgj8KuZ%xpeHY?|yaBGfE#e4Wm=0DO)G?DjlEAC) zUY~!amCi>s$=&JW@&2{@wHoS5=jIrUGlOqox{*wgd;WE*(T%(cuJPn#3hGa}$kmEF0jUC_t})F_AvwY8L5Q4H z-5E6OLL(h9M$D2iQ;6vbp47!+6pyj#Rt2a$;-yXwF;@4XUPdY%`V_V)3x2?Kq&DQS zAXb91lk)U5_1MRO(x&YNu4M~y4slVvvB_awF4);Q{3^4=4**flHya%BgyRDg{{SG2 z4@%ejK289smeU!dn?O-8{IYveGBIPfkz0~klg&ab*?#9re8^m^%|$#4ml!_cw3gS5 zj(by@&<=ajY^yX-8Q-*uky*E5u6>;6rAzi~{Me{%8YITTFr)#Roe_6@)iOo$?gJFy znKhHwQ$=)Gju?Ul0+3qn867iCT!Zgd1F8TGNvg9k<%@3$G6f_{xNQ-PYDP!Kq)f+gBm3SnM26(wCFRYeLa)N!WK#ZwB)hNlN$a=oe^x(z)| zF%l8S7^PTHoGnsW8>3gq#cwH}^!=Ciq~i@6HzJOP2$tuDdMOqN@h zjDt${ga8lWSzTP;R9J@xj+K2?o262Q*pLUcYsUda!voD`-$K>;qCYBNFtu*fNst1Z zR*jSbLKF(=t+dmQpGxPHlF->f%VCxfp~GXE>>|=qSoiGYj>4d^(;0D`f83`Ax_CSf9P#5|=ltflC5TIN zZ>zVl;kF+y3^E0E+I_LOzzc(#^jlwtYHof+;8&w*o&iV~e$GG7wQ5m9_9>z;`8wag z`}zL>1u*!QG+%+Qqa)%?N%mvZ8vJwpoxC+aiM0jOpo`6RR&p_(6lT8j3pazxrHPkw z<|n(;RydUvQ?C9DgAd_~l8rQcd?~>F`NHE6E-!aiT)5 zaJkK9$)m)i3}(K88ZD~b$pFW#Qc+TFGo<+Z{dj-dZ?Dz1$ zkNFi}{tJ((;-|vD4EzC$O-*6+9YJ+z z?p*%rJCuo`SWqwe(Y3Q7KQ2HV;MeiC3cI73(!{lNuYX3F{f&vjRK{UthNZi;zVq;( z;UDbpd*k+x#C|q;wA~2&%jKx^?ngiBfgdV*x%>g^U#(vWye+19XGV|1egUyvA534H z%8Q95BmP=S{{VU0-H+v0yfxuS9@o#kUBnIsKLLS`KAx4?Uda}N7nc)lcS15fKZ&}2 z72#)@wQPL7i+XfjKj6PVxca=SC!v?cPS=-L*Zut^eqaygDtK)rEW5|vuj_7A9d3U& zDY;o#*+J&Y4@`4QcN<+1w?8d(_Ezy)Mj4bIE9!5Af3nw$J}Ac?xvTHev}5xLN@i(? z{W3#=kM^67OxMp>#?ivBYgyTUTOQRsElSUl8nfbyO+IZx`7Eu`<(1AA5Ef=9uuw98 z0baG@Z-9Rjd?Vlse~JDf(>~3k++PhiGf0M1xGCl;$q+l^3wHO~%w6wuuHdTp@O@9=ngtD*94 z9;$$zdRN2Y_=gdX%_-opl&uY0Z+=v@dObC=Tkd^5e2$J6Evbye)V#DwEw1)ndR@Jb zh4gsZITvv_QVs=u-Qa(Kz9api{3mm){2p6jcc-GoJ+1B3P)j_3bbvNDu^o?E_?pEb zwzPTMmOKiJOY#1<;EiT0%LyJNb_XDll0C;m=xgZlm~2KZb73WEt*@$m(^p@a;o)&K zFsqXiuNiM`*JgY7g}evizX@yiIuDDq`Lw(EGO|T;8A%lT0y=w+4Rt!VlcL1aGJxQ6 zNy)Fb{ucax@TbTB0EZe!!v6q^NgdRdPFB}iBmFwfTL4DQjL8^1n!Ymqt^OYPm*AGT zbRA{V;@;&1wpWml(A>g$GLgU|_?M_P@)#^-OlCdc@o?m++oMaW{{Su5;(cZ-4-6gy z>o|FHL%(bH{{TIi@<)sH#@4kWbMmEfs~TWts#?fp%buk3R{1UR_UT`@C@OJO8q>zKhrBCE-8@pD#S{}74A&%BVzZm&?*8F}8@l>o~OBqXnxQur7 zu4z`Lx03tL_|lw~k!Kh!^Z3)2)s?b8&*@zheiHFK#JqNLZC<=qig;&TX!k9voulUB zvvsL2w7&C5(Ww5*`;0qez!@IZ)L7akLjlR_SrFK2ODf0qclo`m&@6OKPAn{z@&?9n zwC5j{cvz~jOLOS(bljb-_#U0$9XT#;V@!48zptP8HkaXo%llYszBFjQT_?kGfY|8Q z5`-V3*YYv&`gXOwjio}PJ;i@^AMk9>jcs{l@gv2NC@i-6b)avlXCl7~#Kv{_hXsb{ z>P7zm;4h#50Bt{PnuQ(~$5wl&TmJw8YCoy_NB;l?arki;#ZUMrkHp^%l=CEmOBc}$ zWbd_+Uq6;>{KI}Fcx7y^9F^*G`SV}vXYs?scK$T@GvefgANkx9K zXTTcmsES6%YV(`=;5Zy&1KHjzD*Rle~HTpgQHwDamK4Q|FF`*gGDTWb_m3PW;I(`)a+_?-7 zHGY8CLIs4a9!^g+Iua$fld+e z^LM5O3@|#1o*a@#^Q^r_mBoz$wsF?2qN8VuuKW@F>ixQ_j2f2un?-6vk8nfl7crIIY<&f$fTF*(Nd%;ZK-+jhV{0;8U41)OD>> zZyyz4TcJ`8IH__)C^MdAIKqw(6$p*9T?5;wz^dlhfE&50<`auD_7l>djs;j~#xOe4 zDg%L4a|>!hq>8u~aNXLY5Ho)&wy?4g4J+8$C)9}}W5x&+?3 zGB!|lxkSVYaf*yWROPEd(2h?O^i{_+g=A9^C_gt!vakj?HDWljKDDDAk>L7NHj$!@ z$`M9Xfm^U7fjH?+j?ZA;eX8n_5Dqx@u6auP9dUzNvIA`La(dOcY$PM*=qh;bqwYSH zY)$gybgpLIj=07*xRBW)7~-^H(%A9%)@Zr`g0EWa^rIBOZW;Bic}b;nvKDU0=bqWq zf!I~sSR`x=ewE!tq1)U^l>`dfgTttaxhsX%RmbOfCF_J57ENxi`D_ZN|jYKg-L-O_Zt8L-y z>s05FT<)Y^%-uz=i5XVfeY*X{=DHNH1p_Zys};Gn2Y9Q|U&9$2yHqyh^d5;D+I5`U zKtJd7uJ=rqOMjO8(!zsnpcQzqPoPG0N1>DZ^qVE)}zTe zBELGUyiYWfsojuGdzOo=-N|e`zA!r06)@`Bj-6KJq4nj*#KnN;c>L<@UlXk-Roufq zwej`TdWF_pImzax(yaA1C(UL)mBH+z6?>g-tlo#)cHa%;d$69QX5wv7} zwezi=o}iGDr14em{|n_Wjy zM8lu+?Nlx0r`M6-A7JF{{{X>1*TbF~@vft(c$OW}!KTU(WByt)@!GxtNTl&!ikrmx zlbNNFi*wC;r-XI?02z2+Pt-NL8!3LxAoAK(Q?z|eeI{>3SIhqZvgx~NXVX^CKPjp8 zd}sEhMb}L)qDO}ORx5SmyZgepN4j>$v2N9C#hNakq3T*q_LPK0JZj8ObKbEv$>i1U zwNbNW>u2>3_LaV*Q2 z;{crW`hGR-Hjt>5UN9A7B%FJHO!lwitW%8|e#VjhhEt^od?-ypE$pq>$At-&$?kdr zKcN1V;+`ec=8G3H{N_+!9+9uz?cTfZ6<)`Gs5Pn|-Nd<&j(`m3w^P!*7sWcEnXWFI z6IQNBFvl87n>IH9_(*5l>4viiL8f)0iNd74=H2bimh8t9nKLSN+ z_)o(6{{V`0xO86++R1-;F~o4frJ26~I0LynSGN2f_z~kzj+&6L@Z5e)lxH_LvIFHr z7?Y3ooQ{e=O8u<(1^YdCAK;dm_HPZqUg|&v&FsJ#?`{5|2zdv6o`SVYt{LRFH>Ik$kGf&T#KqmTRKd)M85 zB9}<;e}`w)uGM0K-EuGh>A(sx)bs+oYb`n<<%J(HW|00o0beToVDSw8GS;6((VIGc zklW^A(d8Uu;Bu&V`q$$Vc#Ojmr(R#=y${tW(WgeAEhKDw9`NkH5`0$vD)AJ1BMuQz zjw6pf2e@35Ux;5HykY+U3p3-_iu`F0Fx_fXCBLAC1LVh#Y%Xi}7scK%UxVMXzlc0( z5E091r_Caf#%?Yb%7_j=oadi0x54rp4XkJvAi%p?Pa86f(Paa;$7v?aUL(s{vU z&pGd3PnM7Ph+I!i_@S>eR}p{Kq2? zm6y=_*XWPKFN+@uzi8iu7az0l#rsW=%__Z?rydQ)opwglbtjV%{xQhKeCe;oz9`VH zG$F{@lEHUu>GNY9s&9bbv-Y9mKN)HM2>55FPpa!$iwM!nlvBthnD7-Bxjn1oaSo0% z4fU$+!lX4)T3XuP`hQ+W(JFJoVm_-v(x)Y77x^9$@muy__>1s+##)bo_0O}-CZ5Ch z@yfnju{S9q`!e&6)!uvy{fj<0e0aS}Ployyqp!-xmD23yxdT431re{}Yx-jSoc{n~ z&)DDKPl;lm_J(uuOU6+m%=T*8(yiSPXKv{9DDZNfUue@& z?S7uW@IGhqe|i4^1Zw#2;C%#mhvM~|J{g37c4~KVM=m(e?vS%&dRK&MAF{vf)8ISl zBk^yF#r(**SnOgh3vu$6di`Skw0r^a+h5l2B=MEi$}$}O_EwW8j&gIF{H7lXF3qHu zmtQo|wiq7$I&ocDc72%RsC{P@jJ5hfTk&^2ikTh@4>d}b8trv|Fr$D{B=i$ss7P8e|bBY|l&^E$ybs{7hC1}Ve~2$7grWZc zmVK+X@m7K0of&cAok~e03xT|0vFV!OJU#Hg#9k%RB-At&5iAEK%K?+l8$t4*2m06WYi6b6Ehi`5zo(!0C8zCCqW;dmu%4@BadNOtsd$R+ z;j(Z_Mv9~PSLA$Qi^XKPC}!DhH!9GTqi*tym+F097X?>5!_~?ZtfrUU8OBfWNB1e< z{{SE9UM{+mNqH6g^=R-9P|8$~*1yX$_T>0`;y(|3YVm)E>?VqIveqx5Si#!hEboDX z?df0ESL~Pn00lnqXNdfHWgK_ulR6KXv5}Eq$tV026XF%-oBMNoPPegjp3YwoT}r6j zt|egHW2au#@!S{0d_lyRm4wczIM$%1{qC`og}&yQolYKHEgVfL%Jl1US+47z{{VwO zG&Pjetfy;zN+*T=7-K(5^V^5Sjg#KLLVx0#@n?mW`7Pp_IZ2L0xIgNgbpY@)+;^{= zzAbznhr=33@rQ?IGuReXnHYl5m;0w9f~4{4EBiYx%;;38u=QihC#TH(XEVzy(}S;0 zT+v$j*!kj8uit@G<&94~(#m#p?Nyc9UK@)2DJ>7oB`fGyhCvI512w4nqcmj?O3Df_ zIHSrAcr-Md)SI*@yTj(R?d3-M;)=S;S$BI#u{?MfwceGiZ5;=2)=Cu~yMfr$1 zKGemTHwN^r*zaH|wBx6GB;Q0RsP{2sg}&(+9<=Dy!vdpNIy#5u-0u0 zjo7ZfYpo{Y*b|POtDaRQ2WuVBqfVl2Nb;FubJsM$vZ>8(-NdUQDbux8c*q$7x_R_C z+%43rw8`3|ZL8b!70}pea$WQo6klla!}AV6>q%5sv0lzrxgKvQoL$J_kT(QWtQ+`7ed&oGm(NOYoCX=DQDQkA260+ev!P?WbgYMU zWx%Ly#ImoP-&ZSUz>=F!0+ImIzAAA!^gk#Cg};bcbt_p~yS@)WTf)sFh-RUVQ4R4pb5=$&Xgsi zX3?!Q?_=oE<68*gX&Y;9^sZ9(#TIwcFt)%au&*V18@G!QoEpEQX;TS-Zljv#jT&)H z+__eq-I_XmX6tMLlkZmynuL=|RGe3-9RgeHUCY+FJC6n4+c}Zcj`t^DpHiKW6DG_ z7RC=1yKAT2%mGkFYv`>%;oXBt^Dc8;-M7R2CLpPSpH3@|Sh(ztm4%m9eCwj=w~>Ju zCc8^JOO=xZ=Do%*4Ov>L^5Osr%xwt_<)Vy?`&TQk%+8Nu(lq=7;hT+K;bDOct&mTk zuc+)iH+x|)wz-VP52*hD>sI&d!Qp*1=48E4t{W#lpOt-w@gu={NP-)iBBe^;;=eho zol*YO{?3xI_4M!)`kKW=`PWGB^@BhXP%?kcYq7HM?wbP#RR!Ia=gPx+iR_n$0$L>kfn#^w; z+``#*CYqxpf!2d}JHY!U9-4;7bnrs;AGtT20Gyt41&6!O}UoE&!kmBhcqTZ=W8 z;^n`03YR(bQJSTPsnCarrjB3so%q3^d=>D!2pLWE!{kx-hI#?-Uy0us{wsKk;%$Ud z&vMNeJB(!XH7D&G@qbhJsqr@IX{0X%4pmn?Xa4}LUM!Y34Jh*gRWIc|wKm>-f;{JDb6Uye6jXg}LVlg;%B?k3Y&}28)a^DGAfWhQf zujyVn*ECBh(?pcP<|rS#>yeBeoomf}H>7FtUaRU^We1RJ^y~I>_{aM>{589V_r?Ag zk5p*?0KNeqk*}W3F&@SV!i%Zot@@tkTSe7xo~nnNK5F0gKA^Q?60 zPK8`D!^NwnmD_8hdn4?r;1vZ5)GGUCo~rh;eV0@4x5w$M{6E+3yeq2z0I#<_{{V<~ zBD_-b$2y(7xlD@mU)!_dr-i@aYR9c<(<>~p;E+ckp8QvsE|(4T5#Gb|$0~*%;McvC z;vO#v8r2r8?8Z`bYPdz07vf&3qXmwA>3`v7YM$2C|JVHUu+bsY?aIWb%S^Kt>c^bp+ls`!yGso{r*neJWN*Y0zphPodcU0= zuah6%obCMiC#R)(4~%aEGT8QWmLKe|AFl$xit_#49-Skydh_I7w>Sr*wy85oWuk~sY9?ynK* ziKc$edj6)XOE0z+!W8}Yve-At<@cY5meiQJe zqFPy6#saF2ymHLg$!6+Fu9emx%JF}z3|s1ZS3RcdPHna$e7)-%#J)1J(lqI>r(hB# zEsyUJ&<-npNzS8k)w9f2QBjIH?}|SWKj9wJ&Xc6RFRsB*!>nj}w@$rHMqRzidy4vHV5&_o&?I&*o|RI!C2i5zwWu!?6DVdGc$)Z|>!| z)1Zb0!lA*hVuUJE!_uc0Zm-q<07)JOB6Movt5uY5(RwXE;Eb`;mc>$Nqii1duTAi# zi+$pY(Q9kKPFo(;*623T-FR*UXv4@3K*w73p8;uFe}c5z&1UogE2(UH3|GnHvo{)Y zviI+0_n%#X%9N)Yzj3Rx!@M=(D}5Knnx}*{DQ=*=WI(anfx1}xkD&IiyFX-a6Iu92 z#X7E&x?3GnQnw3bBaM#A2rhxK!7J!%@ps35HM;R9#HqYPrOIHM+4jXOX9R6Mjd$^U zchxkzXyewBaeR)hmyyiDl&&zrPXLa&7513ij=nPjsI460uDd_gcSq&8eCH2`#?Eh5 z89P4f{LkvQz}^|OePYK^4o?|>Q;;%G85#Gl*KdGVJ`c2*X*w0eYPZmd%R&JyB49HV z10y{-uj6mv$NUr*$9Gypz7*GjYg0`hmksn!_FJ^b88QqMbB_EP`j5i@0Jcwxb=8OA zhJoViZ-=@}u@QNA46SuLAPwcoaH?1qISLm8p17~$ywid6x|o*=%_y$Ay*Is=z38ld z{{V*1a_abQYHwHa*Vk{v{)E45{{R7aS6T3^nzxGlL8f?ZK*V2cnMASeyv7fXK0DPsM4DZj!9B9&Jlot{ z%PNcw#zxOxqos5gejm`@*|iB{Ot;M3ag&3`9DOU@!(ganP=*?#l)mrj9kkV4YIPve z{=H9!{9ofQ7hFhxvu*GWImKpZUMl$4rR~4aC7KCThEp2)bnS}zu5DYwZ$H=;G+7D2 zXIuh!uTc1JqpWk?T;4>Na6p^DiFcwNId%#_A6omWxy>9t74zX!@_O9m!{qhr;%`q9 z*QZ0~KOKJ2zqYT%jU!I*{{W74o9ml971;tR2*4b5+6GV7zIfNZ9QNI&{2lnREm@76@(?-OlU{3M z;7=XHd6l-GD!?2A*V4T|!r!#d#7#2wZz0r7+i)H^dC22~k>0)L$M%-dwM|MX?=-(D zrBEkmfFpMtfr|U8xQzD(oF#{hmy@^P&zpuplw)~n(!|O>wttcH?arg|Bg1xK1eZ`Q zIuO7b^9!#Wc%$v&Th@2=Bp!cC{Y~)q#IFi?-$x!F)a~JP&efflfDS(Q9Ah<);%|e0 z1aveyhN~UsuOp1CR&fX*C$0?g1OEVyiumdrNlJxRLmz}{T`aGE$uFBdOlJtjwufz-3 zG*~SJl1HaK!pnIylN_puBHXc(d1jS{&_3zSVR)PNkMW;_d^G<63qBWEE}3g1llFZz zS-0-M%+e_HC4XF>!m_*_`zC5%5_~HE01*B@YkKd8?9sf&38T)?MHwImlIC+1K%T)v zSEDRaq}DGpQKd=Aa;rL(CFqi}zS~OO&l-41MlT7L&Q)tDySu1DKC*hhy?Pse41O@m zW6?F=2qcMaxdcP{Vwsm@k%&Ixiu8O?juq&lKwa02IDG>za+4J?-wLc1o&z z%)|gPanNzg9tD1sU;fJf0I+Alj{#}^A=W-2>37mxz_O*c_PDvbbs!(!TG&S?m7hm- zAn-u19n!yQU-&1_!+WM}7sH+-@m<1jww6NMK(Ud%Cc9&KdmztUp$Q(9I4sW#jFj;i zK3!H)cS=%^BDL?lx7WRoH!-7LjVf@>GYWQAe6f7a=~?ZfJnO>W3H9weOt$dFov+*P zuO!^isx-|k&A^T*&~9=%4Y|*1^}pKZ;Max!0A{b*hRa9s?ffzPNb!}@mhjvKUQHiM zhv)OP0;Ks@R}aK?h~yNT zSi5PjFh>W=c)|3q%&!)B$5!!XtoNFel1U_PC5a^DvoYnE_a`R4mo3TjDwvpJGX|oQ z=4oo%{I~6YQ{b~a>k8<)7&Pr`e2!{o1e4s=c$0jX6&#Fr1RT`%=)w0V=U=otq<%!T zG}gveLdXs(jN79;*8F;p+2Gp!d)GO4a&BCn#-cpWa_7pHgGIq%gITk*xyl|Yb&0=* zrt&|%-nx~M%I?ZBJcF+_bWTXx2NdJwV2pE6g&U6)-I%pTrM$y#*67rd!2tQx)>MUn z`FJ%XO+Qu~R9mrW^ksr}w>2oi9QLZt`8Z+CG|Cr`!iI;Y<@^9P(}5nfoFr}7%^rWv znn^NljS>qly**;x!K#Td9R_I((wrKb7Hiw0gPhdTF5~j{tdx0i-mE3Y7;dRxo`$3W zf#BA)wZzHtf^pumAiIwo_N^FhRKLn`ST%E@S#>&y%yK9X)K=U!P|T{J*DSNBPjORP z>wjmH%OqmBWeIdWNK}IOj-76fmIwl<+-lxoJI4f7=x;5rR3RfC{i>d!VF@I}ax0l6 zpS-m?q}{Ivp^YqN^Bz$7MP{-*Z{^A{T~(gjL;=P{bDE{X`I%AHy5RNYIHs2`Gbyr` zz!g=kBqUImED2^AsTTYa5R6x4nmDv9+Sth}ZO;{GX?qVRBCDnQLg1%Lduop4W7e{K z_t4Qge`v65JhDj76-H>Xaat>S@K(MH-dBTE7Wx{Qec8Z(yv$ zb!|my3h*&lxvh1SqK2uyl}SOD($75Eg%NFC9$IRd-Z(JfhXfGP*j zQCD&d)_&F{IupIlV&3jj-dpp{be7GG7U(IF>17`n>E5-z#1;$=^_--dvNen(i`JTr zq(cvZR)(Q&^7XDww5z8lXviJwP5>rB*}fef<~0s`S2Ivf_d67()vR>!-l$)YXB}$v zI+8})lw!H-c$rga>skiNNj~QsS3IK=p*LgHv`-P*>=8K5Ju9v{)vU3VF@ino$+SHl za={qaqQ#`7Xg9bu!G}M)Pg&$RtdjyI6dpBvc9>IfSVz!M%wQ|+qswzTFlhENv2r> zU{!2)?@_z5Lgmu!ou2mcK`oB|0R4K=w!Do#VnN4R@X369CT}h!ITfdC;$Jl|;I3E3~!R)ov7RrE8adIe%5w=IJH|_hsY;`p4Iu+V}tS>k30NIPr6#ANsy)gz3b#*VsJ{9F^E74Dy)o-65(f&T!vzkqaW<%3)s2Xa53{{ULKYu3e5 zSEEmvBfb#B&3p5+H0?iP-;I+Wx8K;RMtJ}@C+c`SSI>VE{uFro!McgE)->6ZVaQ}s z%6;+$e!*LR!9xBA9Vl$QjJq#G*EsaWbKV~Kz2HBLT94Xnriw*o&&`kODdBOrijw{G zZSpxMLYmSNKNxMSuROiC2+NM3f>qv9$WtTNrxpDpd~)!Q zfpnjRn$Eu(TM1*eknS6E)2R2a;(y{dj4ixT;%#2TQ6=H~ONfuA4;AvawzrkA(jP%n ze$@W}47?5Sm&aFr8u0zdxrP(N3exxq3d^qx-7@haWJ1BS$m7<&XZYcLs_I@WyOQ?f z&tT+k&U4SLerbkyj7D(Vs?=0|A3o>k7^vasWofEu$;*Eu$nN#4NM}A!#ct_Z{Bi-m zfNRXI?5-Z*0CSr3Jp;qvY|~%n0hDIHN3N*Trllr)4jbClsNn>A{{V`c!hRL-R-NJv z4*Sl&oBeEKjkVtVH~pzRQ{hOWxPsx(FQLVJQK-jqu&tzoum>2fzr+3#@kfa5(!;~{ z7Zwgn=>Q_@2b-^En zex`g4@zeO1;a!dFpq7bt6ZGw07=Ff|@Jjyx+au$Z-Mo5#g=f<&WyWrA$CROX0h`X6+dJz34XwTvt`|fkF^-zRCLJQ#${|^AU^f^E;}^9VCzE*g0#Ksd%C6bN8RBl z*2P8C#5+4*UC)}K*Fjv9QvU#Muc)p=!`AJq=+@ebjwX#;^A)xIivE@L7hO*=-sk_& z{QA_cq>|!G$2(FGQ9s^4c>e%O@cm(yZAM#(Nj#DHa@hR^dasG)TT61o_yLn1r2XH> zSBGjdl4sg-BT%1KZd>{a{z#(@E`Oj^rOO*0d*TbGYgrtO8C$2h>G==mTOSXsqO-hx zVTU%VU332cJrBzt~kGxt^B3|8q{cGJnYkg`@2lyYuUIa-1H}bXPqW=JL-9#wg zi|#cqz`qSkeSL3d5Dn#sm53kn$wM4*{r1~mH~!duG`I0r#~bTyJ%d_k5XGg#j+uad z^pC8CtK^hDrYSej+Yv5WR$WdO9}*jTU8M8Z>t0>sZGCkqlFH&H7{zH!S$6sH4zg9==1SJcp=l=+%7!o@iINgk8oMi#LcH8 z>Ba*STHg}>C4a)C_)V;M!#ie;1-**5-Zyu7L?7@3%X?OLh7;|V(XP-$nRes&T=wJ8 zV!jXkygn&^ZTSBHPxwuu$(wBe8;iiFgMB#rryujmJTKP0DtLdbVpTqyzeWCg8e?&h z!Qq`->hxZ({I@=2`+Re^nJbwbl39;ZNy$Hn=DwBqANxK0S@`TPXZU%e$$M$0N9SD5 zNJx=KZOwpN*k-&Vz#jy@Eqq3?xYhg(qTFgwS&$`omnx+675jnVZ9`i9g*+MHi@zJs z4b8`jEhIXon*keOiN^zga^Q77wdZFT#fE~Tgp!pt?$OAFp_OHfsyv})K=uQ#5mY{C*%Z)V5kjCv9Do)u@ zWC4M|Y-1Jst)qDE`qAQ^JPS!vBsWjOtXWH^Y8H1F_6WN-?%)o;l_+?Fp3-xcCY)Z@ z>2D*&sp0NRCkWx^C2jTG{%G`D!(kzLA$T2lA%BE}f-rhu*7W`_zLNsp`NvKfm>=g~ zpg*=>?AL3d$v4KY0zsWF`N#I05socx8~$2=Iz=h!e}%ra`F%0+FGsPj>g<~{!ROdW z<`{0fuCaYn^IwXyS~K~FILonl7A58QuaoY&HT`d=L)&~W@docraV5mWxWNQCAIxBy z_H_8qZ+E9Xwav}kavW{)rbS%w_k(q>lZ?%jVsO0=f9Xw`<1Ek|Ke!xz71vuY!`G6O z;&M^PR-q19v+aE=;zxjugbO~uer^EFvD{ckaEN&Oj~wBT;-a0S{CC#$X(W$G)TD~q zLIcLA@hqTY8`mU$2Q~2IS~auC8H%7`oSftItqAl}2n`dK^eE@~X1to27GE~`^}lqj z`5vu2^D=^7&D!0Y{{S=WZEyC1l51J)uB{@PJB8ZYp;MliJdOzVuOB`w_;KVUv*`~! z{#X)uiZEo)EV#$Zj>k3Q7tmgm;z$1gzKN_*mr(r8BOj?X*N4gQR3PI;ThXAYbS{{RG!8m4QQV!w4>wCAqQO{1}qRz*m+`Hj~IIQ)BN zwRG(c!%wuAQooNU7*Xnb3hAvh8*M5PE$#-v_kYH@`}^nA=jY{#KIs0n+fBNfLFv@< zYp-Vcsx5jWlYRTlnD5%Nt_v5=E3Qbbi8-pm+SXYx#}(|UTXW|eTR9?RJdEbATgxm% zEDd!_r&?{u#aEur2I;`Z`R!HCo|Y$-txOp%i+rt8FPFE0&00+?NI`x8^s5c0#}Vtr zHrBysyD5E_B)@8Gkzq+VD8c53#2{;F(NG(o(ulW?hj|nU) zM7nJ4tu^RiLonELiq+GuZIBT<=QT!G3^yKgQ3OykQ1-JgW@t|o5=hRN1GXxmv<$}| zg+_7IoK=`+Anof*R2R7$*vOe+GAfaeYxBi*;_5A!$2CA%rOW5Htxq+#FP8rR36&1j z9=WSPDdQYstTm69CdK4>(zTqU1P&;^mlrGQOAu0pv5b1t1){!O9%?yR;ur&^XMLh| zB#Kshq1`RbWwMa4BfV;-nIfoSdRILK{l-Rk#cT~~V4hg1YFi^jeK^Qn9wB(T=oN-i2sBG4Wvo?{{>5E{o ze)F%rRgvwH*dQHx*ESi|Fr@lbs1P=CO=Zii+cxLAQApYQVP03INA~tyb5*>c1u^WT z$YJ?X_j;81_eQu4)#bqD>4*vk>(zKzmDf3nmP-jdfQ5$kMWz4!hL7&dMJU$PPM++lyZeyI9wqtWFmsU{eH=#=(;& zxt%yOu~nMf^;N!vs5Lq@#E4ooBz3MD!bv0`zH8X@8>=+5+cpLTaa4~YJ7C=1j$$oP z+YlIk^{Zk%b|yeQYFo`J*6Lr~a9X+B-w)ke$N~dgmxP(WzY}3@L31HE=C`fx$mH{0 zc_qE=yKQ>vEC~>R=bCbg+#@@)xVF~T1^GuNpLwlc%rk4AE0a!0Zk5sLvqFdF70TkH zt&O#M4u@0MRB@fZ=h~88OJ9+RUBkZ>+KocMzyMK^TDpFxrK%KlT%I` z&FwWkbVw3R;=0>=Tifykq^^4VS70F1rd+}mo39nl-Cs**FZwveM>jLk50=`tbox() zZNT2gb!<~tIuUowz!xqEJ3a-PCYWb5v)Ed#kz`rvbXZrN7Omv@Bi#os-b$jiU`#rz87We$slKzrsCg%H0(dA&5v0 zHxt*2{$zgNCDgK9%yQDPbM8saeyRT2UMv2~@H}1}X2iE_SdXd4@~_F3{o8=O1%9cP zQB?4)_h;vMg;zX9yB)TlszGfNwDIoaitGF_ABVNum4f()w;r|UNgRhx3?{4wAQ@$jbSTeNu%{v@fB37 zb6MGFwE3T5nbD&RJtqo0x?Sq^`59}W+Q){PK}75`SS|4>$IQk96s^Nue8KvpTML`m>Vl%pGPsoVsGtQ*Rrww8hj09)_xo5w_~5&p#3Y?zp~f+ zwem0QZ{s+xei6eQua<$fAJ)FI{{V!J+xx=22U#Z6kFZ#INhw7C(fqIRZlviTjgIX6 z?d^}l=qt!BBybFP#^b%c$;lnP>%Z{~PdtP#CHOx|^Q}lSMI@?zVn`oegSCGtQ)x!W z_myVb)aPtiUEWJ{!{y0Q^d0{II`n@A*!{m)H#06`jDo|}Bl+A8Jr^I1aQ4>X(%hf&b$z>aunm-*2RP=V>H*1APW#XR1U7+D)#jBuc?ir8y)o|pt&M#Yo0i;_-Emb zU*a9(1{OdIXwUq$xNnl(2Or+~kFc-Le++n{eKW-;#TxakwxxA-BTYM8AbsxVEKYvE zm0JKrKg{@-nbUQhmH9UH>VA_a{1gMlpAYog&jXB=8Cin@)!0lJLwT+1lS^0uc! zn&+2ZtcYfqyv0g2C1|dlkF5R~=%YmN#GWOanV}oM{Z;HO{9o66T{L!tvR_!P zLFh;o`POrj;wjW?2-mZEwb}mwk@{{?m{O|+EKkL<^Zx*Xdj^@RwwI)8I!B4oXN{w1 zC5l0nbv;#q9D`qv-yZ(}X4}6Gwa<>eA4K;$3cl%M8E3Q&&^L1*qjus@(1WsTwIQy+$n?$F0p^A?!PV1!4q#fL=t5n`-;qaow8x8_B-SM0PH%N zt+bvd_${$I$l6@zYK-!J&nf$(Kfv625nqvhC-9EH;k{;E55(3pX}7SPG&cxP=i59H z?gM zQ(sGbMfh*8Wri`(r$9v z$-TS1lJ)DM^jKWFwR%-6wv;2gZ7;cQ{zuFDN5frq`akU%Au-#pEi{=50QWt9mC|W5 z+v$vDv}}YtH`BlPRXumbz97}3Ha=^4Jp$(n0QRgaaO)6PHQ5??iqUTp*P`rj&b|{5 zE?g>Y^>4qC+Fa{V>I@am0sT*{L>3K!Q1e`I$ZU>+r%7c~xlbay<4!PKu{=4}l}R~F z)w{KxFl1l5T;t6uIXSK0?O9zsw#7C(NG{1$t&_IrGu7y0L#WIN1o2ZgqUV;Yml`9$ zH-V8^65HU`zUwp8(vI#{J5#+{Z#=GC=CYwyJvpo9Ne9Zpp{;Z&5VDsfllV{+zRv^jZYhHpbyQR(#1VN69Ar(~#wx2b!@QcyKDyTQqVsa&;7fED%REqjxan zWw4+o4M?S+`Io19g`##D6s26I)~PE{d&l3q99E24g0>aM<5|Krf0xpyOL3jXhs$d* zr!?+ekU=t@bDGVP6;|L?=`2}!Q=HWC*!ikPd8xWc-p%fc7WRv9aUDKQIH${sy>7P_ ze=uB+YMc8&Zuys=dY0YvEhzOl$FZ4C++wG;v3DRi~qk-#|Ne=+eF2Jla7_UE}X%}&_zz#G=eSQb6NXUd)(jH$#gMN z-)v{vv*n4mXT5I=D}dfp3c3A~(GBts&W?1R;q2u49EesOYQ!7Y>09z?t03G4GeT)h zP8d~lp?fJ`LcB4tA+kUnjbGmAmjyBHDN$md{Q zYgQ-PpEx44+f9iVau3FuAPw?jvXqn9-Y{!&nPww$f+@SCBXW%5wk_<#5gYN%X@6(3 zDZ=1ZPDs&HlR0s9AQ%IsOpd1yAp8Yuiz3A2asGX(7SoTJ)9VX1a@6 z<9y)t=D5kUqN8~@;-t5@HrNG^6^mCjO~vYt%3WgS%3+>Vb6279XP)6eKaFAsw3-d? zlgAYupOB1qE`10U%;6hqj+H5=qdIBs&C%MN6Il;E)Dn>(IIFgk`A#srR`TgXP$Ad7 zbGfK!&AA-4MmlQBvFvVeYktR5ftL@~s$6(z^R#j*oH}pWW!iFcQrsmS3QB5Po$ZW? zB$&<@{QFkvx3^V1K62 z(L~cO+`H$DbQSJC8_@1&kSs_^jOQ5O*MoR3$C`ehr?beri}fR}J-Dw+(|$f_`cQw} z0H9$&>t1z8RF;VyuyBR0&!r%k*uo*YK%UhF$%g*`_faNK`S!1#5624~YH-rV5FUp( zuFFI5)r!mKN^m^^9V?&MPAcr`_V9WVc#}ueBW=Nchy04+pG&)T2{;2h@N>m_SBO3% z>2pVooDbLDzH9ii@z=w?4Dhwx#5TIKLh3e#+9(uPZQ)B94AGJ~C;HbhC|jG=iy7K_SGxQ&(7&{` z%S$kujIt`@*CxJm5m%Cnll4A=oH?r~#QiX~(qBmZnV{bpLonP)9eCzD3u6^?#=0oeF2rSV-nijXFsh-|qe`%p z;VPXIIBh9$cDU)9`b)$1Gf$=csyNF9?~3{RNoXOvLc=1y&G>aC#;v3^osG8e2PE)5 zwaJ)G#tK$9z(GoGH$Jxgm_8*xgS<6;rRoi^8Bx%C5OZHn{{X`Fu>Sy_pRIm=Xu3t@ zaIisxz&Ne1?A=G-^8WzqSLKzdLUmlv(rHzx3ThpX|I+-Oy^Y<>t<+}&-nbn~)n&L4 zN6;GetE+qmX8sYya(0?$op#fN$UsT{ovZm46S6CE>b5p%cZKY`$WCcd`# zb!B6rY0~Rj^Lch!d?w=NL%}=eX9Qe6EhUKz2_pfReoV3v`k2;(^j6B}wGdac3{9y5+*7ZdzXrH>YgS2piz(4Ib-ec=uNr$7({jAr!9sK-{m&`F<%G6^0>22la zeE45=<4H3XW@1Yoq@GDXUbWrY+TLAh<|`Q5Mm?2Dud93~@O#47kNu-@Y@1UV-P1pf zecPb;7r`1&!^FC<)7s+sf!h_lCC?o*g|2MFiLmq~2very+eq=SJU@u4r#VzfS+nte z4+eOjPm}vb?%Cr-=Pku*YQ8bJzgTQ$UzG?4(~rWwu<^fvJ~#O1QJTX-@V24loe)ZMd2nV?e&fledEj(r#s#S5bj9S}O{%4UKHY$pA zu#l5a$^QV6*LZj1zNz3XJ_t0JRpZ(+xc2K)>7Nx(jXZH_scR4c5s`PVY ztV3@cUM}%|k*CN2%0nH^(|%zQPv>5<;4j%1z^74$1LBLTy~L9=dViAw1sN>zNh#Wj zIb(uA?O&VXCy~*N>qYzS>ghku$Lg4G?V7WN8GI_*J0D;8%T&>PE%4bj?*!Pu(ajJ) zyHUG1`d8<-fqo@;xqMA`;yaxpRuN|>ZaDX?y-UP8&7?5+OTyO@w31IGMl&ay6KrfG z(6IoR4Q!#rl1K;4J9PH1o&Nx8KZRGm zGx%W_iS+*fDekoKy}Z;{4!zWpGAp&w_4Uvok5;s{H&)C>0}S9-)0!r?;Ljg;T3rWF zvhtGZG6*~lN7BCph94CNAQ#MuQ=4$>z*A;Tfx421-Q1^3XrI9GmL&U&D=7qyf}Yx zUYDo%gFv^m8h!PQGLXRTanIA~RQ0s*?bY*3VQi2^#}Si{!!`AE>T*kw(`kH27+ri@Y}-ovO>>R~K4%jUA?e7%FZ9<#NOC1Jv}dC59GBKX`IYdp@b-3;8re(B_cr zZ=O)R0(%U5SCd>!o_67kSC@f}NYdr2{d%4Gg-UX&oiFRqgxa%68yU?@CY3%npyH?g z(}@reGI3KUh)|8LGhWT*Z4aOKdYtT*Q>fe8n22+**Htc|Z*z<&9920WwQrev)h7Cf zD*G(Lc$qSBR;@(Je8u$ZOLHnl1Y?}jVwjPI>E5Z{#(hhYYEpSdHyQLblB7C;SLBUk zTp;GI+30i300XT?i4|!{*%5;-P-K(9{j>lr7J6xu=-)T*#y{k4|9l^^E zYoxckiqP%riY_&qeB&SI@~nC0o`~%BaJRh9VKn%nA2{ZbqPCB(O0#uvE*mk9Gg-2v ziZXlFs_%AkK2@Scz-^g8QHt8JOHqR3n&zX5Om1Fk)G=I%cQ${|;ZW9_n!(xL$7r^2 zpPd(qwH(&)rV}-f4f{tIl;iRL03xVObf!$Ht}a&hJq~!Sj;-94^%?xC2_@Yj+&IsA&hk#ioRPK0!2(>LEdCVd z@bs~_nw=|BD~KUnqKx|Wt8r^cV%SIHQO!|&CW;i_#Ad@|AdD;b^{Ufbgl+~aZqrkR ze=8b6dVui-l5gpGx!A z)Ab;`kxnUw9a&XeWD(k#B6@=rEr$;>hdle zoC--anO)1V`sSzD#pxzFR(8Bm>>6*1tS=&~(n0VAldofl5J^LD~W z9YO6*`!1mg4J3_F@UVMGOA#fyJ>SG0BGTYMEKUlX`&TP3#3F-YujO17tP(z2cF3;6 z$41hwV*&Ukr`c1cto13?ok#BIY$wD?-Xk0!^Xphc#rluhGNZmnrER8#rP`dc16Hi9 zbXK?}!0XfV=Cq2YmDEXYr`l#qc$-&na4;(FpRZ~X%jNYakMqS|)UB)|l>MAxMOV`- z*xZSgMil3Y(I`0H#%;+x&#k^6>DP1VLFQbNx%rL(`ewU%G|f4-$13BFNv!XNS6|!q zQR#N!a-%0czm-p|Szp-d?Wf-E3{MBs^zUCL)SX7QK8;E$nrBmI;6bWw{>(XH$OoaT zT9<=7SEQenHe=A_Vz)dssyBzNlq+v@(z^Xe#JZNJV(;ah;!dyQJ$7^EmbY05#&|eC%D+JW0BaA5^XR@6 zUkO=BnI+Er9;|x)RrxD-aV4bX+~hY}`<%9mNz{_xW8*Uls_K+JV<(q>E7g7-L?O4h zxnIPWkR_EB)#tV5FoD$&kUsik{ym;>8Xbu~USIK5@ZwDy%N6}^T zi^Zn9pRb>`f5-0+`0L@$m*Hzf3nj8HPaTFh>0gFkHi;%?bshTG(LWdUEo*YM!8peT zzE#yVNggJSPD!tn;rzZB94uo;Yv1oak2}Uo6<%J@^Lb$gLxIIrDI{;be+s)gF?@qs zrq(yKQhJL0Qc_6#+A(hDsr(YqBJmfDY^-jWBFiFz&C{)Y8~a*(DAT+lsM@uZ={R%* zl;^j#e52so>zyXeGVb9pZPiU&Pw;qf%FAKG;B5SO}>(EGeK zN)IckLUH$-R%gySevszmZ5ba*`=j=K(USX8ODmB3BpCT?#rz}TeKS!(H1jNH+~U7Z ze`GB)z`B2nEn;}&)MhL{b!`LOIplB_Fim+m-eRfYW|D22Wmq-s<2kgE&VRxKd*SQJ z?WMJmQOIPM9o4D0*2B0Xf3{nZ}Bs~<}7J8KK5Be~agck?fASjj7%q_?GV zpBBGpuLIia?jw}niyhHhTiaokKNmU(;cV%(a%o*!FaBXhZc`>HZW$Qp>5+ zUi|Oqdoar3Zfcs0_@!;&zlhPprD~dvqBO`GdA7BVI7HG8k0g)0oOVzEA6m)qM~D6p zcv?X&pW(Rf*$)kUESCp9yK<@eS5F6rBA(rts0uo{JbefM09um$6OQ=2=D-6~3|TBHwnd{{S1FuMfsseE|)}hxA=O(r3nGhS?Q}_B(5+wEd&} zb$1G+QWPVua8&&l739)r7EszFT>OQ`K{+hP*j0FR)bsWNPz>+}82%lHt$a0HE)8qQ zu0|rAN_8}LU6Jlq-?Tr9&Hit!?Cw~Ma|qArj%%We^BGe@iF(N1f5W9L4v|s_- zr!~iT*II-@F1xP2=+y0wG1gf>xW2>qk6~UjABv=%zR`9;X6(EG2S7alud~5r6sgfw zBM*DO`5&8Nvr4kB$tl@({{S=Dw0|4w8Wy7#rEmA4V9TC*V;`0))qH*XM|g+gUX>1m zdv2i)6_|IglRvf_QOt1z&l#<)KJ;2Ql*5r*SITg+sSHJ0Y0f)7$6XBD5~)iMPNPwl z{%6+(!(EWttjX|8z5@;@%m)cuDq ztAJnb>)n)BI{kh}zx)dL%WGwD-VE_|voVRb=Wa*iU!z_fwb1-Rwz7D8U>M*G3~^tQ zUkAKj;jMedH`<1#QW+!}0N@Pwueh{ba^J(=1iSG?h1ZuP1e759bNbidyib-VishjP z89QmZyFXmW@vY%ps!d5N`q>|zpSPZYtXp{QIqz+D?A`mt91-=!esi__kWQqYhnoE? z_?z+ePl-0N!>-w>KQf6ioy2jTmGBRg0}Ze z{ygG(P_3C%#^7hnr0?0I$(qg?FXfB0ZgKR_6{D!>ZDxx$Fs!+#Zf!2K&2U@Fq!(2r zV!D45O%$4JOPmx?Fhzc!Ilg#u?9a&6G?d)8M>t}gG_}5uJ3$~C+wc~dEV_ZbkZ^#X z$W|4##I51^AqV6V43B#DZ-?4wlR~<=7|Nr6FX>#>s(UJOQtx8u(p9R$-L7ZJX?Z%m z!-Iy;HO)P+&9fZ`ttCHaE?MY$H8395nyo?K zx#z!PU^(gSPLEG4dwW->X}%5DHG4po7BfsobV76Z;-`b)_r$60m15GS3de%O6)zD= zH|CmOGst*K+FA|#k2P&FQ4u1l$7Mcp*}?w+K9%+qAG0rwp|eZ9aCr#BJPdvrKjBv` z{vG&7LQgL5_p%PS=lS=qE#`P_eVn>IH!*+N}OJ)3j{Z-C98Xe?eZY};(##H|RO7&@CJ+xEhvGsI#LakK!YpbtQ z=jAbk+^0NNj-ROBM<`vOgI>FP@SSx#cD>MBkTbU*(!8SX&i6vPYfVBuj=eHE{VT1m zWn<&B9Gfu8WS%M(PiD4%GiGgS-o0?rZ;a#l)mbO=zFhHH;?d`n#+8&| z6}moZb!PTv^`5ma+CNR74-><2W#uY%>t1OtmvW1qKMHhlU4<&Z(v>Jl+)%Aj z39jc`W3I;=?g}tHYkXd5nv;PxFl)Twc^q= z@orC<&1HS26o~nJlhAgmCg3WQDLAWYZ)aj7Drsu2SXm4gC1M1DU0sa&Yrs-)3s*I7 ztlKaVBK7ZziSK-ijz&o|r3WQrNK{bwk>5cUlB!MsBi6JuxNPK~X#k4yJMC6vmw~rE zsk(l@b!jk>TL-5#%Pc)d7iM+G6-h~6C$IQ{Qptd~UzGX}{SbsR#4NaCe-Tp4VFq#= z73p3rSsphm)~s=Ow`B!7R#zJaDnBkHI~3;~YL&}?+d=2rvr)S*n#)t6w2s;~UBjhp z%M4%w7Xa6v$1Lk0bI?}*0QSSO1aW``XF6|mbknOFG|@b=M;=u6tmT#llLTYZwQL2I z=gQ5`rD$E~R_V4xU`g#+b4D)GDaKM)iHer;NOFVa#ZL@Kjy%rQ*;+#dy}lR>ftuoO z<&t}npb!D5Qs!+NI4C6}auIC_^D|nK-K>%R?}96YkVzZou&oHLBt+l(P~p_2TH09Y z;<=o*2THs4^uQI&$9%;>Mf|!}J;lV{cg@C4N3-T*_S~yO)a;!udx2JD)7DIcI6vpw zyw=B4xwc`P@m)=pu3A9OaqF7GROKB}*9w&6tj#lTC9nscD%GX*;Nh|=S#^su2Tz-& zXWLyvE0Z$>2d00*xoq0IM@``wXwI1~{OI;5C*HDdmO~~~^!+PiM$r68t;{X_H>cd{ z5;qE#(8(!3f~l{hd(6{7cWtYKx~0h;|a@b~-@gZ8YNFZiwG(Y!MjGEL>$mILT9 z{Od*k0N|ZJ2mC7`Pm2El8FXtiA1xiCqp&F9&alBz~ zTQAL@mE!P7*^RITUu&I;3v@r{n*A_Og+K61?}yIG;*TBc9wL@A_ia;ZlkKz$%)j{I z{{RHZ@N)Tn7Vs9Otw^07?a2iGEPs_Xn|nuWHWsA3`E0-PW{Fr{U*gt+{{XkI`CR<_ zi&n6P0Ijz;q?O-Nk8YW6^0DX<8MFBVUvyml-+u!%IK01y{{RN`#mC)ifEfLHS37b4 z00krXs#j-({2QX_YujbGIQ;8q;xj303?;wCIIqg&xZw7dBL0zY^Re=#o#0Q1dZcQa zW`U?ppYGd9{Px#jYx_C;d(%Z_&zuL&FHNEYvg6EGW*%u#5 z^1W;J^8KmxMKIs^y6IEWEvfv4c1BsuwS+0JoS)>8!#QQq(o&z?k?Fc`?0Ne~Y6=;A zH7r2?0MA!M{Cd|*ar+H?eTppK+F9FzKjo#uiukWo{iuFAYOYQH0E%@d&!p}@m1U2K z{yW!KcCD@7OP;%@bNy?n)6JK?Il+2GztrZ9T)n#}$^6p&53*nOI{1@fOKO+;UAq4O zwRFGzYMno3*!1KlQ}I@lZbw{98~WGCnq}UHD|y}_yt$c2P-S9&op-uFjQ#-G-=DYX zmsgSPBWaPa8`rIHRh~UBc5V7WezH9}QNn$wrRz8PMD~3{;Lm_!i)HtZ^!V6y3PJw0 znQ!o);O)Ebx%h)_*z2?k`LD)aDfo3cbE9cSAjIw5b6#zyYx*Uu%_R1!;P$UZlrfU0 z&Fv(mIMX9u#6@T=NxkNYxPIS=D@W9*;(jedJK ziTpP1Om-@{^r~`rlS)=BkpcZG{;!IA)tCND{zUy}4!`BxfAVkhKU+K_ET$&n=ARBdz+2>)}k7X)=uU%xAs=IRi*fUk&DcQ8-;XzQx!?`LlZlH+rQ*;s`z@EN`!y%Z}UF4(?4bZ0DuuA-NEDJ5=F`e z+H?3~vF`o^cp5~KXgptOAQ{52P;1FFTfG`WziNbIx&HtP^hvxe;CuTsaSXmw0npcn zR=rwvKYLbB3x#jP$h zms0TdkEjGW!@yLZ#Z7#kA?EGglq2$$zchVK31v}_zbSm<{!IH1;HQND0AeqOu)=&* zp=v%LzQAlsGKBHa76QLkz6pQ9L4F(jE!m}f74Qa`pkoX6n}v;7bmW8?Kb?LM*nh!E zH0@^XAI9Gc^vl(ZZ5t#iamP)-*3Gx=dHWgo!tZB~J{RBV;lA{mbP0Txu0M^L{eApB{{Vu9>DtDGFFa%7=u+_Gba3pB zH1+_T{ZD%C{{Z2={0IL4LxTSR-^l*}#=nj$j}U*sF7<{NbL*Zq(X;;mED_zn{7F43 zzuLdxmY?)F`0Mj}`A_T&OV`@gU-u3F0GZYGtS?G*zwkBwNB`9P=<$EXPXhcRvwK(uZMmj{9^d2@lIiBsazXfImy{)ZIp-(#k;@Njb>SReVvWX z(wC8r?s+VKymQx|TGeelG92Mop#b;isp?O#;=i6Sd3F~H`P$~6cls`$nf{?r!$#L= z^2q!+uy2*XV83|(00=zwKhRY1Xu^M{;f{JxFUvGy3}>rc3`@*@o6ViSxn-p~6- zy>^pZc?a!RZI?MYJrsI@+xgcydh2r;NR+Hy3m?4M><4e9WU4mqa!JK#j&w#h1h>qf zax>HZ0y<;fuR#QoNF)V9r!IKM9kNC~wW=**jt}&_LY+zuryt%mg>dInjLa9xu!sHv z9RC16%AD#Xu6UJe!jyGo>2*Yw=1HKDij^Vm4*&uDDUs9H70m`Eh-N7hbYJ3O>c_D5 z?^f<7^8)Sx5jhcofVl6E>TAk;OX4Xm=P=rF5za{9zphWG^RJq4btKY1JH_Je`L0Fd z-xoz=0X8H^;1HkPQS0gSuN|?xzq^iYM&vx9IdkrMAU>z`74#Rw&)LV~FUMQ&wrG~p z$D~`fE-!XvbN$Fc;pFz{y-%fmF{%E_iJ<9$HP?$w)2&7lEx@)rvoY%J{6yEAS24@* zNl=vUF1}t~zDL524;#d@Mh)A>bt zJmWR>x5N!ibiWRd1ZmNk0}ZEdHTmQ4e^c>9I*d0Kq^jfw$6EUMR=U1~(fys%a^scjUn%%*!&<$(^K3zlNk4m~exrenS>c@P=+DgY z)hWXj8gv!W&G^SwhyD|Q^F||7MF4iIJ~{Cfo~xnQ!5y?wv;eO3BQKMlYnZ+#R`FH9 zx&iR}IqCB8U8jmZ8>XM(OD_^C!qHu`B7`lt73;WkudZs1DN$7(x_(FJb9JjK@}{n} z{Ldw{i_f=jFM*Is{{UM39{rWSXU`FQRPZLPtZ14g_0$$f4c4T}W3jc2kO^s61ujn; zK_}Y28`8WM_dXfB@eBxoEhLUM8;<% zEe?gRq*|k}4Tl&ch6fyq`ZmMh=fjT(X-gILt2V$gN(>)e=v zV|h9roFCnX713#4IJ8YgKHF;?aq-Arm4_~mX=3Im@P3`E4_35vO~7ykaMgrrGwblr z-PIk3#OW^lQ)P`I!w$ndSH^nx!@n2K8c5>gXQg$%G4Xxgg9{~`tQ>Rz*PCfy8vIpc z#Lh&K=c9U8)L*wp2(K5>Y z>ggA7bF@>N+4ly9zLYOTM#kFp1Xs=6sM<6-Yy zk}}uI2IKL{yk7-X$@XvJHP%SZp>8VTri_2|yC+BAm!?UXO0%?PLtOp64kH#hn9N~M z^Fp?eertXkX30nCvRSA%D0X7#vUdb}nX=CmRLxACJ0>a%&oX;j|EhIY7x==a zTfZsZgZzy}Bi0$D7nxQRF>f!AHb(d}|D4NojZHpQ!rppK&mD?d@O^wcmP=xV8kBH1 zjU|(WiM}tf^=V}cL{aX8oh4~~!qTe)Yr`$9P{;aR$i14+1yIe4Pz z+yu1@M2woF*(|_sQl<3NBD1CcNCF_UB}zi}tvac#6<;GaL4`i{(O!kN>YF3^E{;Sx zh*nk)g7k_F?xI{BGk|5W8sS>^obYZ+yUg@PxZTdezSz!WtI9;#a~ExC3?N^duCC$O z>#*>2!CeVoSgRoU`_eqrIEI$U-Fg3~q_;OhvchJ{_*LF3<&(v0fC*RMaEh8vh^N=P zv%Y1j*}mg%c*M)4Hg?B`Rk~=M1p`|Occi|2(_Xw0jq4SdJq^Pw%~>Q2<=_uq&=w%7 zty*$mh1tKNV%! zXan_GJj6C@6xP*llAc(9@#7L%IR>4iDZ3YoQSTWvHTW=B3TT;~aT3WW;jL{{IvIW0 zOG$t=Fp7HLIIO330sVwz*TwCnJr-^25%41*JYETU zrz4VsKLsRKKM2VL!6fB6@4#CEhAX(wAPuF7rlJAPEz){HDbYNPgZ}JiQxtJ=1CGwp z`!9e*K--Mck42qV#IVO=(=D(3l4u?fzK^ilL+~DZ46D#$g~UZbSaZ1;AGnBZ?!BZS z>JK)1EhC6&yA^)ui;JdRLv*c@fQ)`WKOHXqXR6V-Yw)g97OSdsqN`3z4jpw(Aduz` zeed`oPF1N%Z?Ax^>bs1)|C1A)uw|KaEAQHAe5Ge9Q%t@Y@+`dTyx?9V1nhQT+YKQUkyIp5%o}>{PnOVyw_2ybVq@>Y(hx=heP7&VjL=0ORYtm{;Knj z_v3sqXg@R@OF*N9@r40SN0G5FSAi;QF54qVSr5Xy@*SibDamhDWQNlpB>bOh=l=eC zf4+KgD*N58V=W6iUy%I;5rt_+0%SN^BTp9h)as>T@|p2s`*WM`3$cTYgw-1K>BCE3 z7R{GSU03#Q)ZHE|Wi0y|^d-|F$e$Q>63lGIEHWE*oZm3g-ToSrj2$*; z-dFm+9r_PvfkCh>L}ZV?#!FTD=VvEiCj5`RY@2)8ivnZ$Ot^#51?pNuC1f_6Y&E0h zc0TK1TiBmt5hWejyGqy2iMa?9Iyk z1F*;cHk)!Rsi8%M6_y$t%wribQy$h@7G*{so+o?MgTG)Zsac+rsRqq9iXlCA#mgq< zmQ=e$kSze76J~gALaheIv(n((;^RO+r^$>QftgLbvM0EcWuP0v{x@`PC5b(jtdg2- z-?l!2X;@9&#IrT^r}<36>7&i1TmgqN1TzTZoeD3&CdxKy$ zXKgM*tWdbnFTf4~nPUqqAz6v0A=wsW_1(&yqps=&^q=2krUV}lNo?P4(FK_*KzMuahxNtWq9-@jql@aXPuAMGnNHOo8vFTZqSLwZL0fYYR z))8JN94>jnRd0x~3g~DVBkFA%L?so0yi;3;Rq4f*^ov6ma-)hwlFW*>UU%sR+EtLe zPMH13cA}a>^%w8>$QYqNmawHHtTGeM-GeWDSW+L$JpXrKE-st1@NAgW=sE7|zK>G= zhVMY^6_GwBMbzJBX7>JM@i^1g4*h5{U#MT)2 z+YnMb--6Ff-NnVMhaQ#!Yy0$;*Q(rPi$V8TLbr>mTw4BMNC(DLD@-p>>)E#VP`N0&n-$Hhp-vr=`wuToB`-f=EX(%BS365r zzHJ8ml)Ecky%UJ-fn0AwiZFnQ8)!FU7?5Q0#q*)%@&S6Yxm2^DH)uo#?4c74zq`en zTznxYC~VdZJ4DoOz@%P70=Vll+>?p?NU$A&;t67ZgWd}$pIzykG*E3U8r2KTu1EcVaE6}I>xXW}sZA+Lv&tnPhf z`S)yM70wRPgf`9F{t~V*CeV9P(h&b}s&f;>M<|4O9;Y{-hW{Lt|4 zG7TyMADTYDxyqcIA5BJCcIzGMwKY28805&U)I@=T7`G7RI{&LPKl zZVU0LzT-eZg^I^|IT`Y${o6a_Op>>d*FB@AUBvPi)ECT{%yaUYk@PdS>e7s6tKSib zUCY)0&JmZU(2)(PCk*L#>Q-PGW`pfd$&Zz&dL5(8agLQdWD~B=%jtDYa!{p#Ay;2rN5ob0VqJFpEq%HeC&>w2K>g}C zfIR>@C;!+1KV}KxF#8meR39e@K&u?Pi z9?8c#XnLP&Qr}skJEH!?3oq`m=s(cI$WH+wZak_!%NgqB38{O*V!{Xd9VAi?6{GbR z!KFE>J49xTm-_hlfOG}khJ8-M5J^gnq0{rZb+^T|45n)OF&|=euMa52{1VBfC;Q-i ze3V;kP+Wb!{aN(`Z~j?j%f|`Xb>YV(#Cr)LQNAli3E;~L2EFst$ zYF{)!1MK!QI^HAP)s{M)lU+9(wm<<~`pXx8Nj(~b=DQKenoiXIvUal??`^eD7CAdq zu2j>U@|7#v04P0D8Yv}m)*V4pg*8yK_>jH#<)v^>GCtXKt`B(D`nHHW!IocgfU;Qr z!_}Ta4h6=NxTs9EUZkS-dx`lk*gDY0S7`czgksNS?`oV;;6~i6@nDQomh^=KvEpSI z+ED#rybDgxwUwWSG#4Mwv^Q&zsn}|iRIL4$hnTHiP$MFJ%wP3vaRMd(%fDhKuY zP%M3Og;AXtcc6m6E0VJbO4(95xSu_#?#l9KWi8tj2LamS$Fqub7jy_$G_^-Ia zoa*y``PAR9Khbw|Y%QL-*rK*2X+xGvh`OwnF8Hzqb1OqGTKY*07RoK1Y`;na&5$lx{CV9ltx(NY$qxj% z6BZC$s)q{=OAhl5v5#h+gLlXjh+rb#EUUZ@5La!B?Ltg8+TH9u22iSiRHp#FeG)iS zs%&y8R@_Q^YXy#bQ-$XAig+qYaBqKK-(eXUNu*wEc8%u+8G(>LvLHIPC8|Ae^UK-z;e>D$LPixfxm|`Pi^L`7A`KiD< zrQkxDuXdp;O_A7drjIJqslySwe(#_7dn~IJ?!P$m{j1MnB%98EY7(DuOZDATsPZ`W ztuRdqC5n=Q|;E_!h7V7j{h#hOdKn z2lM%<6Iz&VdrkAcQw@JgMeJLawbNlAIjIeY0SJEgi0nKKQOI&28vm^ z_U?K)7@bE8_S48Td~bFLGvAwlRXB>8J)RLN9$C@1IP6(yTDSAqP9765P}l0^wf&(D zjntz<=bZVhmh95(1kW1&hl5m=y6}&Xxp1De>0bb{8Ki&Lxl(?!4c#?bsaQY2oK$4` z_l%#)Bp=;C!q1^ppBe!C+?2nZ;_!{Ol8vT~A_1mw!CQ$K_6%1b*Wu+gqpX>cS@3C; zg3Dx09_pcR-%^${;!;}e!}K9fNIvIvaX`yLqv$l5Nm6zMhg5MzuHmT6#~s^sQ|i;% zX3l%83{nYp(4bZlqX)AeAt-C8Q=~gWjrQ1DC!zG;USY~b1hEm&IPICwjufNv%yg0fRpD7=&xFz~LTGI8PDTVWUg}n4A*2+i~9<}7O=MduA zPRIL9?$me|F)HC2@|o3PFv5cwMMH>q zfB5;BAuFSrH*5U$tH-Jrquzcg;a(PWsYXAB-^hNRRfmPtd49E1bjKFk?PP4-blW2& z9?FsR;uSQP$18s0977kC$MC+Yr6A%z5dJ;&i91=#muhYDU&wEM+Qvq>D%ZBuwpDSC zH@vYH9%Wbkd;P;rK_AOU6s*g*>3(>k-l#3j#rHtk!WI5|ws+0hsk025;y$98lXD^n z!KLS#_IQQK%;XO8Bs%W*mmj3sYYoJWFtZ&F6nYUus8|7H>{lZ^O zXOLG)*vX|{!6CKI-3%9Fx4}n!poQ`2b{E_Sys!EcgU73Kjx|$Gtsa_VSFnekXT9F6 zMZE!ww1%!O>A%uOedCihlaH`^!ljVGqcvaHZFz*kwyjmT2_!jUz79$#a z3BS)dE;$8TP-7s{Y6#}Z#|niD()C4RqNgyE?)gSIw!}7qIWLg=&P8EK{p`j zzJ~{z%QE@z8wMrZ$qw0GYw-{ZZv%fj?+&+sH4bbS;^Rb_gcMEfjLXK7;>rmuDt%4! zT=^mP&JQaNS??1AEJvkVS#L!!#+p?k!*$;Ssd}8E7@m4T!(8DQuCXRGun*RQ>P2{8 zY>+-ruK_!}iXK4<^zhRQ_W8!5I@SSQ@CCkgAuU_2^krQ^Q=^7dzaOVh#JI&ANl;bH zxbz3>^vstoXxL%X?PpL9ZwutsrMujec{)#gor><@><#C2%!YrRRR`f+&K@TQz};1kC~vT|ppsFF7*Z@{`k z^aG!-_N=d_j{}MH1xunxUGa0=hfvA_2by&lmh+P*lPB(wFuc8~^*v&F^XCFh2=6wiKVuLI~B$ zW{jH6oKzZt3?SLn+5#I$ib4mpIbC< zoTol6+0iBMMcm1v354k!t}c$oox4;~Svr4Xm>GJ_6fBk8 zoNOFd-K$ROyxzvoldM2(#k>G34k zoLwB4@6KqIuj6dtBuIKUu_9!S1k%1^VE zBP00^WjZpvt}+*#W44CJk6Fg z&dEZy3$o^>N;uwR^BOaP(xt2zbI1!^M@R_w!PeWxYEe2;YT&$mkv_u@!u?$?cphFK zyQF5?)O0>bH7@=FR&hFCMmWiGy1gkmRo>V}>1RsieRi$XG)DQ=avH6&#B@4?|7Gen z#eA%5B)=>=3`c!KAbS^LYCH$vHjjIufiD=qh#byJ8irYiPrhgQimmtA!ez1|_EH?h zd-b4VvxQ;8_jD{Vzqv?v)m=^s6olsCM)%j4AQZbYx^J41(AN}yo|{Q8arCFfHodag z3Gki&9Ah;_I4ZK60=vt>?l%T4aP*p^!!3gD0yazyd>66}-)Smd9(uW*Uh70Axhdb3 zw2hMkoX@lt_Wr}M{@PRL{i-BqiTuP6rf%iShu`KW0p)V8_vDST&(cc1{2lpBUg2aXG?+zEU=%R%ZUENA7oAO4(qp)VuhtEX&O6m0+z@)uDy&l5j5NGgg%KjkL~rK@oLFFKFWOc ze6bNGA0TiID7kx&4H}5IQuBqh`q=V*_HY`s&ky08JHwF)Jy_{TI)F)CI`$7~fKJHD z%P&EaM+c>{Rj4_-lmu5YJJ#Bq8;26$`pFRq`i07bM%GQ#ak0k}t1XqaQy)4)0G6Li z#J2&zzDs?$VO%#Of?_L)M7decqLOyUF?1xzMF9zn*m76pm zmpceH+%@f+Y*raEl^O57p&~VOT+IMwdF&XZ^cUt8*EP%|_kQMLzR%X}pXtA#m)}3_ z>Jekdl%5x|at45#qrZ4vH9ic)??;sEAK^UOGxI6;yy}*42WrpqBu;z%d{9k?^3V87 zcoIx8s~TRgQzL~*VGz?%Y~?vBU*Eg9X0#%sn?@qs7jD+tmI3nkUUH~{w{Z_y*E+8> zHkjsiU9@m19;t!_VSxwO>?1V04ias~HA5egNoMrJx|!$%>&e05LmR2?K4QC8H@62LI8FT+P9F=1>7ZTP%HAjTN&U1cAW zbbe=eDoj>gi3wTQt^brmf?;1bRRTmT8h0cJ+5dGquWa~&0k5~=n|K}`jn0%?KmPnZ z+q^-acNoMF55PPM;GPscgZ_=gXb zgdsS!Dp{DCz5Pm3osEd8i>VpaYMbih8Y9;O$i^h9qmOedWOB>@qA9jMTWsPss9qez zhCzs1S*@R}{MjoWrg$E$#E!1~=CGinI#c(Y%z(c>@fYJCXzHKptm6s^p30MRndVkU z0oAC3g3bEtL{E~=b{VwLs9nL|hOm5IdQX@yU~+|eR`?`HLT7ajG|9B{nm&u;W<$6W zgMXRpfH$|Ku)h3qaGRq`nk?sH1e16ff+<7VF_`kIiRBq*VRr&z!bttxbPFyCH;{tP zlm|Fxl(Wg7BDJt~~sXAyXOnfA`5n$QAo zSB@{krHVJP8-G&$w-Hx=s9w^&-ug=NK1Eq4~9!wpar`a zm}h5C`o6?7k;Y{|->C4AA;*~Jl{8M?X%%bG^%}hlJ>jPph(LQ_v{(_HwAA1H%5p zp{yIlTB*NdM5uXq06}cT+vFv=7&$3%K~Vjdbc)Yp+&GMo3iCVV8F)|HdQ+Q^PYLg9 zCNf$6feqng0x<*RiK^7-k;e_m>#$xwEs1|yYCg={|KU*VQ@S^sH_n(0O0~YVFIs7p zRjg4tSwPx*4a(Gwd*%Z>5Ic6*XB%h0F-+p!WvRESeTrB|sJrGm5ULYdAh+u7PaRQN zEf!YyPdc>A(aWgS#pF%@`maN-M$8~I8ryS|$iiZdbB^;BAi2Kysd~ zOxi-l4t8V8|?u4~b}blW#;uy04S;Vo&kn9xGc?HNH%-%1)=}}S=1NCx$fIe z*_X$a+(+}&v6-)rx`$soOS+NYhGG?i9tMWQ-B5X;RCmL^Zh5SPL{kp^0UXg4aOg*1 z9R&3y7Q;UK3k~k%;>m+OdnYsKivMyjhBQXzz?J+iAaoQhCE5F0#s8$Dne9A)`7-0& z#)99mXU2Qj;I6@}=((+9T0%?eo93%4hJxMNkU&k+~EA1aE?TPIU?RMFRp>`gO zc=?KYv0Ob^e4biUk<)7tCesQCqIDqlj3EaP+@(*O=i8I4180O>Aa?5dLEDis3YNOG zn+aP9cbdf~2-r)ueW>BYkmLKFO$!z$C7b#Cb(!zM@&}EIL-+-|`U{at zXMptc@&i*By{Q6$ebBZL`-$6|h~px~FIOckGO&2SYwFs<%9eAsnM2422evK#X9HLP z4#Uz$$&1WSo9vXj0Xm~Q#{FM*3dNwd(VtRz{NTj3+xLeu zFg40vJAGO?2gms+ENwN1b;3<8|2~(RcI?sPT!=ha*~6@4qfL&eJ^2;29Q6(2v$_1} z6woPHdz&m8+llGZWF$nWRy_C#owA~`a0Ss;kJ+<) zNtGiVLt9dlA*s24By15p!^&J-(JX0oB`}%9xsEMO=<$~9{uLnyd2iV%*pB$>W4dvs z&X`b{&1O<$RHb-jf-HGg;hQR4&Z6APl~skwWA=s+_39)>!I)%-ORnduw=7qca2X^e zJ(oD3NNd5#vO8GSX}gFeDj5B&)$Nuu?bkuXLZS*nxKX(#lk=%ETa((H{>q;IvU|VJ zEkN#Nph)Z?z~>_&f5E%Ii9eGv6S3bI1J&m1ZHl@aPQ3DHM1jbnTvMvPI>VBRhg9hE zSC*MFY2@=5+%7XQY<@f(i3AB}Z!iDECZ^$5 zO*MxV?fNpZxYo31s9}Ti>SRD$b2*H$!q**?H8Hfdv?{bR0fbsCcduDSf)osSR<9Xp z{QDSDij3hM^c8mg?|gqG`_it34J3evSK}nClcPOB1~+^Rg&lSdvLMZQormXFYlBkk zx3o6yH3#||gA#3bVAcss96`m#MXG!78SF9*yWkJ^#rmQpY*rqvTvquXKl=mZoyDVi zWFZ%fLx3UYc02I7=4%BD_+B@jEyxGB#dTGfDcq^W(km&r9laXtn)O%zWME#T=6n znm7CIwj^1*cxxYVz_%Ixgp1k9s>gt*$7 zP56qI&Nnw*KP{IlJzhlX?qP=j6#zM7X_*zi{SK^V^B>wD0UAl)2u zv9-?GpeIruv!q%ll2TOY-^0M%gb6^?3l+`aIogtX5$!;hDb`9GuADuIPL6;T0i=9F z!+5nb_B^_Mp4+nzvmXc?-ndacu-;ZIodE+GUdYZPB#-4xL$gpyh#1)d|FS!33^d;p z_JNT^h6SFfmey^!&+>4}hKFb9GDxsY+n^EBc7xFVah?2x<*yHApKQnj*<^Vu9(Ny!x&`by16F(V^eJbBOQ z<;7=KKIz#MnwIYx{2Xq4iY@;D&34$r=dZfXfV@peV+G=hUiBTiDxBCv;VrFfRcH!F zO{wcO`J(hJsM4+N)g|@!Uhf=;5Vzz@bLJfB0q736n-RMPfkDE@rQyHtg#Fy?6O;oj zV{JwHyB0>H(0jnYSPk-o@;>?SP0hq{U1qvI|A9cr#9~Ol5{nxhpa!&x0+7tVkPm#p z5yAJ^DGG_-AMQ^PP7_l z1MdtdWbFGH!{sF@+%`plbEFiV^g8B|^e@?Dze1Y&_d?gDIv(dzrM0N3>?FjB@3qU? z^_$2e!JqL^eS4fr?K2&eVv>iMR0GyeD7D@VMZ)Ub8N?^sSv+$nrlS5j?I5=Wu1Iho9?iKPy#B;T>+oMLSec zQ~9BYpu&(ZQnT@fOA(zK#y0VNinWOk*QNFXKHHjaL^^alGD78(k*PlYEo6!`0Y=~S zpl7WyrP1aHm-OOiQyW;uq4&PP(!YK)D{3c~$J&z#Q0*y33wzwV?RpiPXy6R8AaZS1 z=w|8ymW>{fYR&3Y`4Gm>mbEt91rEBbzX0)8=I)F-#r^caEieGSKA4hw(sww{SetdX z>giK;C|x#Qysse|c#AzmZ%m%*1r=z|?TNcxx!140KJXvRiCkC1?(F=}3l2Z$7;c3v zbwE^a9s(GF%NQCScHO#KPw(;^)o_{2W(1sp9{r?Xy|JF=cfvC4>3wzd!2X{t_xM4W z>YiQQq67J^R(EXwn~dYdP?gj1>g9@3o08 z`nX|@N&OP7vNj;0X@j4K`4>=O2C3HCIPQTM{Nl0|?pgCGsM*Q`<<+tNw02vgmg{@vcj3cN<@K2PKrX zNpXUgJey=`cLQ|*q}v6EJb(-{ZOFAw7Z3}FbNNB1|FYWS)b-d;k=T0Ocs~Vr}p|1-PI!}4|8}*QQUlFk0 z0(q;rZG*zlo&6g1K^%T8H=8rFeIU}Fz1sAeD6=$3T2E!DyAfCi(qJ}}8rAHK9+Z1~ zj+dhP=P#tW7<&V-$N?qvi_e%+W_3Z`h9s{9BhsyrimsLX81Uv!Otz7Bz8TkG{*-~G zYPs#Ce@Uij^Zj;ip-8@C{>c1Ry^_F3f~Fk9P3|H{j8 z*H{TPuP+Nr#W^AyROX|e!JF<7RH&`{V<)P%=-emX>y5~E?GTaFx+tC5of`e$*R%il6r5}MhK z>=t8@Fs78prSzfVbTm1~3;WccZHjQQy%0POEaCNmTyrsPn!KjvZ*y>y%#_Oq$2-_j z<;j7U6ibh`i}YIhzmIx28|#kYbDgu3mPTy|KhtN4^VZeMj1{C7gIZ!3QS1Ki?$jpl z*xlYX)aHC%`uwsnMwpuA*WCGbQ?*!x!DlJ7@M;U$;<-__*biY7D>f=yg40A5-!a%4 zWzVP*VAcX{UsuSp5^K|yBNCaw9p z9)8;>%{A6)n&;`GfM$&et=ymO(F}WLB*{A0iSsx?0{0v$@~<6}a-$Cgud5C6`pR0q zjZmTP=E5C+C#``m2OPXk;Umw{jd?Fj_SPZz%(-?YW49@BQKMeA?w{{d5~kb>CCv;E zOmY*{&Ue~w+uT?ApSq@Q^UP+_K;KWnQj~#3)D5EGP6$J@!h?F2v8wy;VYv;vAf5Vp z`+?XZDxPWJx4B_;GWDpZw~10Q=J5?)lY`8P+}odVx==>3jRdaII>y&P1B~;FHkmzb zHJ9j8C#I~J&=P2F%xiK0aaE&tR^@xu%l2V)Ntt#dx(jrpw_Xm@U*0U^)tebcPhSZ% zG~`?zXU?|Rf9YStf*5>+7-wUO97_!A9zn%kk=&nx&#d6ckTM3yGapxb@kG!_XbHW(U2AVqvRG(M2F4t|-!J zx*UPkw%2=<)l0DZe8~~IE9U@t@I^x(b}`n9fBRuRTf<8Ib?Sl_g@^;(Yvu0N8M;(bcxUD890>|pKLST|laQfs*O zkadRsDpOc7s>D`I%$<4!kj#%ZP5JPB6)WU93WFSJ$*Wq&1Jn!Jj(Ve6%Q%mue(4@! zze$)VUs2`lP}7&qZ%4Ondo9)R9p@q_`$19%dYsX7=j8|C$B4jSS%R5|QLt~H@ct9J z+nKWYS|qb|)0v1Hfy7LS;vTD`V;Re$JI8Kw!BJNCws<``)bJm=vdGPnC!(BZ`+{4> z_LQU^G%WbpVu=KYnDd!i|G!475aF)FC1h1us9xRH^g$1zBvgNU{M=4SbN0(~sYWDV zcNt9X8hM`TTSGyF_zK&%O~4!&CXm+;9cg^%Q*-k5ReP;KztH81KTJ!X`+z0Ov#Peb zCdcpI8o9Itc^p+Q+q4sm3mX?S%v9bL_(S8IOt*vAI-VNXa_1pAe4#C^Yocwwe^wV} zU8pM4XOd2dT=~tEnN>c%@@6V-miZja1J8EMRz+gWJLQ4h@m z*4wRadV@tPs2{et80~=%6B2es;RUnj*+PEVi6D*i;tE+1*H+*@J?H4Ry2hFc~2(WRWh6m7JM14_2 zMgIwg({r+6;OJL&GV0h|$-HA%&!7sC?39uJG`~{6fR=5Jk1i`fH_$y9`T`r?E zIv%l=Y_bvNG=243qY8Tef(fm5Y{Xp$FGI2mXnm%f{jCjW)(yAY*axgBh> z#e1cdk1A5(bn1-fLU@oZk!LeRS?0>*5(;<`TBPnWts;m-H#R4)*8I-=(Efdd_r3By z*=1$S*xrwMs}vEf^MP{D+2hg9oVQG|8W&BVlHGW6&?}n(=HM>!Wj#|vE-3TJ--MJ( z_{ctYt8r0X%_n%skxB0r|K`8J7AH7)nM)bwoAZ-0l`Lll)`LR@3n5g3|42H#o+~7a z%uUO7duEGcUKO-hJ!umMmn{46!fq!u9}hFS z26j)`2E8Dvk$5G^=%A5&9uR&9KpO*5jG?t`|B^sApad@MpJg(agW5i8Ib2%;`{mVS z(?(h?ZL4aoXU#GYzDv^XSr={Re@_3}8vq^2nKF0xaSsyU00J5IJu{}Mik&DqrW!@o z7{XsscVizIqXP_Ap9HMsF^Jrk)rw-&)}MVl-O>wL=O(?7Ov1+{k)c?j%ZXB9>Ih!N zzoW%KT+Gdp^XCJHNA}yNUz=N4^SuDn+Txp7qWd)C-7BBVujuW%wHMK^`x(g!#(lZm z#G0BX&%f1&fM^|foO;N``X3=XvKkwnq;zM(N+YvZ2Wcc<{h0oDDvbI|0jVB2{Md8(D~}G9*b9V|D7`Ey{UkAfayuwW;=eZrH_p46}Gv zzx{1l6ARIT-F~{`n(X*d#yZVC{n5jnFy-Y?yDnvXJ;{yG=94qZd@c+20^g^4IeMM? zkq%ehz7(L*< zS$4Ai%*nFJ3WfVdA_Y=Tm>8yEMt!rwwtV%De{Iz^a|OMca47^yIfi&;f6@=26em9c zY4qQNPLv?0Q){643p*p4gvezhnxcwGef{y}Aw&itsTb)ve&PjFaUs+PTr4!DNh^5Q zZYBm5oxI>t`_wA1bo{fbKj1gQ1?-lqUZeu9{G_k6#R{CtSa3BlYtEhORdl)nasi#D zy4-&_B&Za|ycMOzA`Yh+xN7TA2ay+8wrSJtfW(=}XhSgH;nCCL7(QlvF?<$+8m|ts zWZrWh^B2a1?PoAXqu~^R45wrS@W7eguu=Hk{Q2qwb-K}3PUv@(sm-f@OUmyj;;{rZ zTA0h8$kEdZ;_a{UEyP^E{n&xC$1(_9t~jSA&px+Hz`uf|42IlS!*8fI2@&eb-CwkF zI?~~)&U$pY$E)^zpt|Swf0A*zOWrPlN8ETs#E;X?GTU~ge+VwAj<9w@ zWhP33dFo@Xq()SBFW)xyj}FRB11dC085?qu(>-t-TW5-RA?L>(*&YI5;>(cd>%1K+ zFx4JE6>cOy?CGQ!%SCxv%k5%X*m5=4OKCx}_T7+Bz8(C-RGl9uo55ig*STcr=uVh0 zhe@dOeo?IIsgJ=oVp?%?v*5p`W`dk1e8(y~%yljr36bsF02JU08dwF`YOL*u_5##p+T#jJpG)85B~k_FfL;(BPV2I(+q*&^3xKXCBH< z8*U)QT|{(gR#Xu32W^l{?Ok)Ovo0Fi%6!X=dHMoE+(-Bz+7|1WV)7m|un{WdJ5{xJ zzcO?7P=i(sV4#g=?puiZXH>fl`lC3~plyAkalNs|@?AMG~i!Us5na(;9t-$(@{=)%!-BKi7 z>snaO-E#H#bNsVWh{=3xw3v4nPSx4@5D@*@4q=VlQHWmc%hxNQFc1T+>&SlcOn#D93M3KvJPEJrc-(nfib>UBLM7 zD)FXEc3z5drETeT;W5DIVvVj&<`?@?R%v{{tPqW#AEeIyjfLAu>;6!0adAYjlp6PD zZ^bA3ID0~Y*ePeKKYRrj#n@eVpXXYV12alF9af=G%^&P&Y;mHKjHRU!NU@^V1)Oa` z{l^)kYQ~S-X)tPE5{5sfIA6i-KReqdD!N}cXmkknw}yQi>os#UNHo1>tQm7ooBdOM z>9ITe&<;#rv)Ixhicb1|+;Dq&E{mm*fVnj1Jl6Pv4(k}hAK07F){$Xs>ayMa%s9+A zeNLsS1wi~aX^FdFlm}AtXaZ@fsqNzsv>*%#DrXu}{L1Izsk`=?b$Tl;F~$)Gu6hOx z^WF)^cI8Tfsh0~Qh9ybOJ^}3u+&gT?iVfF2@=q$}*OsH*4=Q=xX)99rqkpW?o!eOFLd+&yy>N0@bfPG43yeJ4wiEfuHgZz+aL&0Zk-dFoh*M}g(l5!aHV zqk^dR4BuGc6_Z?T4rb5HO|_0wzfIhyURq4MKsEM+qJgCbwJA!$wm$TZE}^FgGtF0I zWVmo&NUq%z-&vWe&=PVvX<%PZDYhO~wmMgjh>u;d)4F3O@4HCvw-=@!-9&c z^BuiF2d#hKhIdIE_5G%N;vq--I7(36$JYoAh^bS9kYc*NwzM{h$M3CWSsSPKy^T1YMU-dbLyxNv%cP@$DC%2fvDE`>=1~+N{v50v^d8+8Bsg4$GwU~ zWFTIQauU?pyFkC_Juf$ny#cxsQlx4mP{PDAB;oiu0s+jkKa27~QpPzBIFoW{Y3O2^27uq{ha#5PpRDLo%HI*z4`4p3B zhB|@=ODTaEgDWipYH=<-R6Ci2vRtB`()Zb6-UrYu+Fl|MV|&(i9L8aa6Ui(otoSQ% z3loaelKpxkV~nk_4=4ry{hK%ko5if(PMz%S;P*&+g*&a6sPZY3R80|^)2H2nP0z)i zfD`9cwOlu67ds9%pQNSclOyHivy5WLyNEOKT8tt!7#T2bDqJ{qox~@OEaxr z(g5S;oTX9cK{u6$48gM&T~lMZ##^>OxyExL^tTavLea{P6RQUCncAm;^O;D|2)FA> z8Ac4P?#xlabt~FZh+lE(cm%=pt`jcKGucAKb;*KqkMwneEC?bULLM0B{dT4f|4JNJ zDv28${eUM>U9O7Ydmoo%el;Ppm6RCb;_*bbgi>A%c1pFN9sZV{C0rRmy@v84Pe(1n81eiG4|K`&#vJvfaXkg%>@5d5&xvg_;>gyV*m_x()aF!JI1V5Dn;G+nrnrI1~ zv_a!kOv~+Po5YWlX8W50i_y?oeqMuSedm-M6@Tc>^i99!LdQB=b>0%l$SN>ykSTdZ zPB<$19qdF?u8+kMF5aybNsoG4m6XAX_HaK3+E~2k z|3WlZ>AA+==)NA>25}!KiS7ILtstyEJN_nrjd6jwOQPbpw^@nn_o$yDCdEJer2}Zr zhgXSgR+wrMuDT7cLk|026#36wr{A*qio_zJy=g+}gDFEKD`UNDFC_?Xub{hpJt>qj zL{lM=dF>GMAC&gwAL#<3eeGqi9d(-pJcj%0Wmx*`1u3Qo-LY{JF2(mpeH~=U#{(Vs zz2(??gHXE_yEB`*SX2|c(~cHjBX)iyQ+LUC#Cj{_i=9pS;fKk*1q*cHd3pVf{h|6{ z2kN~RU?uw#f_KhG2=ui<>6Z#ivwv>qSH}x=oFjfO2;_+DT;Kodpdze|o%nZ2s~SW^ zbhIOQ>allpAy|I+kz_iGUSNm$;BqZJl?4Zc66K~yi{n_i#LNXlFLK8mU-@L4twh_tlj$b`6 z>j9y;Oi&Nj7?+Aqol)0X#A70>bTy@`u;72U$fnMYz2lkoa&QecUuej*!bWa0cN#(4?GsiXcPk^-z zOX2T@B=FyaUL`V0ZYG&ge8(6VMv(Kg4uDt5o)z$HI?k^So97E?%iI{jkgD;Mz$A)-!^Yk#_*~Tm(78`&AE6OC2^j{ zx_^fLCvS&(qIKK*-juh3{=>0xU@(P_u7x3~4!_)55% zxV$vgYpE@?+~BNyL9g9LZJK#t7RSpF94U_-Mo{QSxP0DUj`SFq_L;cR+U zy!x8Yr{2nnZW1QSI|T#G8-XCVaoVr>{{RU80O#`S^I!0*mNuU=<)Hrn@Dmtdu{^5v zm;MCL|Iqv&_(`Z6dz4vuJZ@9QLFmBt^!nnzKrg-=crW6&f?LC0EwPUN&A}o$Mj0FX z$lX!F;E~VaUl05>@bIv7Y1GYR%-&q!WuRa_WO3-D?=NA^eaGN!d;22wbUU-Z%-iKY zdz|f7Jw9Rg8}K#!zli=PG!N`dJYOSIN8z9CW&0@nMfekYFNt&sr_{VHD{*iJWVOlR zC&%#r0CrA?z72dvqfnYW7h*j!abMXt{3F^gjdW@3uAz=tWk*S4bp=>s3>gm?=jLC2 zEBW;OwSQx6Q{aBHFOL2i+dRG(x?*O5a?N1uduNjaAG&{sq2m?z8FpnFtx&I|x<3B^ z@K2G(0Pefzus4-&<~sXXkFb^gj5fUf*ce5B7^#GIit>OC%L$Y#X6Z|QT#)YD@}YasA?A8ew7Q#oid#B=xbgd5wMEt>fv`y zaB;T)AWFiA#2?wRdA1iVZck8+fNL&#&AwG*qxT%CRh8G`Wy5)a`&dFu&H2t<7Tt6Zc9|e3Qf-8I5>2LKMeAB}kgxWFdUp2vfuR$zB1thmWF5%FRP-{a}_?O~)dE8uG z11-|+a)2&C`c&d5VypYK>NLEc`~xajs#HDtkk`}x3F-d;7vJH2pKf9BEu_md{Hsp; ziViwrzE7H2(o+Pv7&X>vx`dB*CXnt{P*nc_Ki0VUT_u>aa#+`~gQDott`<+d&*5HyrC8~Y8h}6&e|EJOOR~5U*;rhaXu6)&^RmRa zw-_rQNgO0)6)KMBhw2tXPrG;s+)2+Bt)xw9Y=%kxX9QtL;QHph0$&7L$i=0)4EWEz zU`^r6TV}_Y2^HF_!lZd_c(S89a&oclUk&~sSa?@lwA6H~DCP2@Wt3bE$jsvn$Dqzd zdnboHU1hE4XHUGG!)t4Goq@*X6AYtA-wNtCdm8w_BHE3p;~vJkZwTsE*7y6aqVtr- z$_8`?MM z5RVxFWl_))I3x=D`{0hB@rzZ}EqpfG&CP~jTaU9xA&f~60I~tWCm)4=QfWRb)Ry)X zc>Z)F4~~0t?_XNW<1ZIjXqs-WCAhS|(-?i0RU`u&^6~D+<6jGzVBF#C>7=>7diOq~ z1&qA;>bC9j(Eh7`WpCNH;dZ;G*y?`}^_x9QN7M{IYPBqW*k*)=PcWWBu{b1l=D%lk z?;C#3nyT7-B>0V`L!@b{5iI76D#VX~5t9I8({lAaYxocF=j~bJUkKgX-`eW09I+6? zYvTiqum1pEzjyxtWncIz2kiOd9}35({?RsPQNFN*i&h^oW&mMv*yp`}HRgFXaf5!| zF;yb>(*FR5>W|Q6h{59;PHi={oxbn*C+autZQ?6WhZ+{O@Sn%NM~HM~wexKa?iyBz zkCeqX-VU94KGpnU{?lI%^uHK*h5rDu0l2ceEFurfC>g+SrGByg(?1`6E`HYUE}!t% zPPv(39#k;I43eZ7!l?=~pRX9MG7s4MR`_x7>KQy{kEnRtS4>!F(2p`?1bouUqm)(d zTLQgIil_D+tJa;TC-C0({{V*W%`4OT%^$0^bhnn*{{V+Sm~BhLSDp%H)T|h_vQA)? z4@F_q^RAW;7HE2{ifO99W|fa6kIGGbEBjh_M@sm2p-^ z*XQEK#AZ{9{?EcXd6pVDr{6|?JvBTO-39YUylPq%rRVN7=S7chRZwIf#8(IZ00^zy zhffa|xR0-t<2+VYi{gz-L!Bi;Cx~!klaNPo?Ow0p{bx(@73s9nE)Jb6gCSVp6Wjw^ z$~c@XUsd>h+m0&@US9T(@%#2Xk{wR-OT89wO4d^j3~%=d9lfij)AdVhtJ5B|zhrVa zKfvDgzpeOA(^0aL31Dd^S2>xEI(i-yB@_H}7g)m?vGKgjAuq#9MEuchKwa$EOpmB+ZRE!4b4tAA;Ejr+L|8ByEoUDlNZ zGEQgky~5jEV5`G1z&&{8r?7)hjis{j6b9DpoUDXpTdyGW#W*!hw>5vy`t%~I_K7NQ zm-74joVWI^;F=@n8Cdos8LL-XCcUa^BJOV`1M#)bIQOodbQmupm+Y5~Jjw}Bk^G|{ z-ao@!teS(!O_$liNSri|-Ntzz;aWy%zum2WuS1$qN}66a=J&u9}{?<@Yv`mCq+*(NkPPMrg6GMkC&bY za(ET`nc`pBf8ei-?k$_ckw1nW^%Ly(wz9mii5MIUh+l#aa-0v+FZt8z5mbU8jZ98pb?{=<{`dvN; z##hGj<1DHF05o2nn9C4?+m!5y7wAdr{#lj|{GXr|KejEN<9KEGTlZ zO~&n~lAw&7eJjPjHGax{(~v7A*?S-@v))*n_t<^4U)s z;v-YzU$@>8 z)pf``J#Mf_BR*(&qPHV#F+-dXgNXV2xZ=J={i}as?MLF5#4+(7;NXHe^;Q#V_LDG* zDCPbuglu4Wpc2vJ?{YY=e=p;VCND8~T+#}Zb+oyzuDU%HuDb1YZm9X3!-28ceqHs9 zzD}#U=8dfE`mJ5;`fqPWetl`nZ>uDi8Y>CoR@%d({dS&6_Z52K?rh@NpC|*7-n!o% z{>i@>eh>Y(#K|SDsifE^oh99*n^_Px)4C?Z4W#po*Op1~7EiFO8pH{A5$MF?LH_HH z(!XZIVz9XQdwjm4sShW&lk@U+KLx~P^)PaDanN+<=CppEUY9oX;~uxF+3Cy+VEL?| zWRsjI1HU}i>Gz4Jhr+rgj5Da zw#dvLTFrt>hTVq70R-`y{WN+miH((D=YvXg*C{?9Tq3GpM zPBkRnxBQO@@b8Fq2`^sO^pdk24D|$ipVqxk!}hIlGRFN?!2{e^9=a40TE(ftFzX_# z0o)!`kL6!N_(Q^r4b6_BtP>m)xXuU7w116!4mPwS9%Z9G!v{*FrDlCa@I&DBw!7i$ z{{R_yis3AD%^Blqw;$fj@*J#D4(xlMO6~Qph1dQrxSvys4MH z*I#+G`&m4gtz0}Xb|)Wrj^`EoQTsG}4F3Ry*YN7b8w)`V@;BZisYSu)lDmD+=jZ+u)wPDOseg!EHwxWbr*Gw7te>)H!~Xz{ zx0VlQ@Z#O=;b0WVP#5j|O~Tiu{Am4`H2p8eR`+*MET+-QrNm<~9+?2+n((jNH}>B6 zr~4Xsd%@oez7)hX#_=pt&2gv&9%97_RWZa!;fZcrt#mw2CqDLAEIPcM_t{%kcnwj& zMs?)$U#`cXf5Aw9Wxv`U_x3H;^{*4^BI&#>scHTox3(-^SCZCHjG4e6G;%fvt}FSd zT^C2a@a5ILoO!yomE*X9$Cq;ifw@@`fMp}5ag0~(a(sCB+3}m+yNeUIU{Jn@KG-e+2}tLw2u^N`z-Z2^y@o=dlG>o8KI5J$^ZgYCpp|Y zSJGuzs=0kyc{N=*oSJK7(v8zypO&l7V>#jDh=ojg*2yNjr+rtS=j48V+Fe^(>DRWG zOl_cRR*q0e!!f~9#PA2W{?Jw}R;obJ3;!lh|0lwEA>gZuU5ZX z9}=j?8BtNCB(2`bzeIjZ>QQT2YceEw-PerPSBJG7YfbSTt^Dvk#4$K82m|H>V;q7x z?U7%oy05^$4{8@e<^lGETxZJLDD8}tBpTn){s;I!OTM(!HJv@$-6T^qBoud#BXX-_ zg2SQhUTk>3FNH697i^j2n7LGZv$99!SBN6ib$jQ%Kw;bm$eB<$8Nlp4tIw}Ad$(g0 zj{J1w@n5+w@I&FAyLqTzL#J9=2vp4+EpofOrez4De=;~7N8?;Q$L!nS_l9e!Y-62e zh=@#VV~#m;pcy1QV0Th_*4TbD!NSpW+WU;FWEhHd@Usk<^cye29tYBT4vc07Z`H$z#td5d|HHye*r_*c~*vnRt7 z;9rMcA^oquB*0+srk7=Trf7>H*xKf?bcPkTwCH4t*b^np%an`&xh!!^@Exb@rSRf* zy`648E^Dx1mKl;wFT-&^P-I-2%kZRdrK639-dtC6`&@YQ#Qr?^VW#-2#`l8i^5PgR z;DRXD-p24CtYlP~ae;Wi zo(SuVjyqR}_*-AJw72plI4!{ZYwFJn%B!Nlg_MoGaxv>(W@T2k5>BNntG@62&$G+1 zm9cPCt5mvPntlh9-TXGtAhe#w+fSHA{IE;I!5RMmX1Tb$C*g}bm=@#1hIIjihHKf* zah`D*U+}MYxwoH2ihWKNS)!4*5KklV6-H|--D=J&`$Ucv1d=2q6Q4|1B`nhu%kNJ| z*6nL=Q+n8V$?3ZCPp_e+;Jr)3T91X~@YjWWJE47;xo4i@(XMV)j(8Hx#DVm#zT3zD z022Hx>80qJpZ01-yI1TmMFCahaLS~PzO}_@t8t*&$u+9M3X(|+o=>e*)F;%ZkY3Mp zsk@@8D?n(9ot$WmQp2H6>ZzQ6>L*j9mI8?76tHtv@&!avL>AoMa@XXq@ z5K7mA+wAtzHwea4w2(b<&TF{Vzh}gb$X?4bCRcN1fFSp+Ul!^1eizd}uyg~z><_du zjP7;FEI8?!@LhLN_>FuSg566I?n7d}PX+BOH(nC8-(JVDjg%=U(3Edw)4Ak+FZg$$ zL8`r-wy!ClI0+vr57xQF_+zcwsc75nIR5cDuD)AsatoE7`q-mnVTk9N`kUb=!)<%V zFC@1>SXsn3J;lH(GtL!Pg2l1LeYJd4u+(|wEfMjwFpCpgk`UDMFN0qYyhZSSUpnH& z(iT56!g=r4y?P&wzi(d>+M`=BjzIut zP+0T?@G+B~yjKZ(@KfP7uVFT$;r&AIN__m0-$o{hvz%em3_5i=ua&2dz}BhlD#5?L z-{yTCY^NVipDj5rORN6?k@4i;6uu~YXqE21AYL`Kf@Jwyim%fh>p#Qav^R-96zQ6e zfcza4(&(4$8gRJ)u?R5#vJtf`le7E2)+JoZ`mwL&jD!rj^k{LlH z3iqEA{?NY_ym{ceuL_MlETp%2Cu{ zjPYEiqv6jHTPU>qNoiOND!zHIHj~5pm8ImL+D?4M>I-Dnn0zvKlc{mm#5*LZ*zQ_8 zIU|vTiX%ght#h~fUxzNE9&6!`K9xc^Ztdk$98Bxff;c~=YCM{BujXCCf88C8dr^lg z(Vk4_%&gIpY%Fv;Ic}{^@ak1a=rdkrCB>}FMdX2gy#7__-YnCmi${*~-d4A=<)l;J z>FZxV-0BvQ&Sn`ZKIlD7ar+lK(v{Du!&i+wEMsG@)I3QYzAbE(j_!nxx%3sOZFj0^ z_pvpZNbZIRB!a;9FB8_CqFl>?m9RU4n z()?|s*j!wLX@^(jp1B-Xlxa4)lzt(a=nc`v`(gxqzktVT?ZH`4bEhjiYoX`k9F=Io zYU=t1yB4i|smXh7BD_zHxIHjA;<7%&u6nQMUb3=ycR_(9*4PQ|a_2sSryZ$3;U@4m z_t)~S_+lz3rB0pKdmQq^(v+O4RI`3(|Iz&A@Ro&dq*_c&WsK;uD1!{st_!P0*`xz5 zhxou~o8}eEc;8U5@Kx=d`^+0m)DZbIASO6uW!wSoAe*Z9jk3L~$UF`4P{(yHg=;UH z3^~8CD0xi$*AXG?8wJ#V1c}F5^p6$GbK~tU`@;5qN7@;Gv}|#K9F7hrjeokc1`Y>B zFX$`zuh_~JJ^hdF$ifvVu2boM0(?q_^2KJ@P2@z0;s-4vC?u8jk}>lS?x7G0rUkyv>UW!VKi^;BKg7P`zC`gaj;=0lZU&ql?XQU64L`_;&4vc5aaJe<2morzn+f~=$eOsyj^|Z&1&hbue8b9 zCmFzD)nrd+TrjVx!A3RjQlj^k{(fiV`Nb-@8KnpAOK&IG=I*>#;wdg0P1_~AE^?ay zeEacMtzq$wrW#1nY=Ox?DEii$c%pY>Z#?l_GcM6H09^;Mqx*JSbxTLR-VZ`g1MBs# zsBTtD5J>o#$}d^XvjbAF)X`yk>5kcfA%V^a>JPnh@1@^b#BW|!FqYaFde>7wh}sDq zr)cm_eq0`XK=rKX^@uJSNuA7b%lE(lllXS7+HS04syL_YobPj*mC!cXWj!k`u5|c| zg1%wt*N%JEzNw}qz!y4kWQ3d-lYyUV=5BP^EoCAnS(DW(*QVmu=bur@X=7tb*Cx|u z4FDuC!Cdfv{Z;DvM~(H{IK-B>q$-e1Waky&3nOkIaaV2i8SJFr4>=syJ$y7PZjI<- z>Qa8|XWI5RJ{gFH9TeDZk-Zj&8zYc4XXU~FWdJuaW-_W%syl}p6l_HQp&H?uo)%g70__ZU{LK*HYApPm;N+(i%a68w|;c3qU ziuP^$kE7`PpJ&^n*!FRCV~xZ6Pqw=EUVdkoL8v75;t38^@(-mw^!8!72_vyywa>mI z$Jtb)ckU|fuB$9Rd=))12Nm_Mn_9s6PiUI7&W_&V;v%^vPp?YoZFL*QkSN5U_0I&? z9-8D*$@{>v56Jy1dc}0h-L1Lf`e4_aQZ`qbJqmJfNVD3!BdBOL#TNb+MbFLOjx&Q_ zPG8!53y#Nri+u4)k)s88@Lh=KC)n4>UK;S6+NIjVr0cL*u>^;@B@-$$jPuW>cb694 zJc8L|xYl(0;ULBXD=^0xBLIs0{{R_>jY^A_t54>?G|#xg;%82qQ>As^NBXbGtKzLo zLW1WulCLAEHRO`tdF;%xWM;LsXtf!ft@n_7ovWUoO6Hc+Pu-a%R2}x$(@>=faJBAy zm3oq?QR!lZ&D(DIn*%kYrrpYusYgEC)1Je_GA>@|lk96lM^6XAbfQV5U&;(b@(&lYJKPLFG;>v}Br(Zb^_pCYmByYcH?cZz-@{8ZI+cr+W^F>R-p z^R4ZmD<(L=&N9H_xW5QoL9f8tUx(qA=gEntDDHM`jAWd8_TXlq<(WG?Urg+WTqKuC_l_l;!Loxw&H2`YYRgdWN6y zv&Y^jw-oy%t={ffo73@h4=^Xu2S>%rug0BRytI}8pO%ee}7)Puf4xZpK$XD#d~DAQ}3tz7xFZI zEclAc$M-_wd0N&WNsDr`$mIV36MLHQ_`D~0k^Q3DMYtu13$eyOF^Xl^#Lp12GQ7jrmnEuJ2w6{yQRI$S&t{gLLm?w2Ycp&twzYcsV@a@Kj zF0*%WaPN=ZCITZL_L%eoxu)u$3T~JE8rH@cC-VH%k~@&-0MAw&1L>Oi>h&-crzpkQ z{vUI(?b3!S6w|f;06)Cf)jkvWcTu&EQ=FS0wh|$2`IMe{Deu$Xyr)#qykFtnH+;+e zrKiTkfsDAuc_8vDu<-u?k32!9$pd(z(5x{2=`Mf{0*rz(GCEh%*4np)?|eC9IkabI zm5bf3IYXRfd*j}`>Ebb1Mx$ACrvCt|KkLxa6mYndvVjLp?pD_^2$vrHr;0@ z`s-v-9ETa|K*<8MJT>6$58_6nKAGX`Tcj`z(nzkW94biwyFUY--F+*l_^IFxBT%{i z)6vp9Nrp(C8C2xt^<0Bqh43>=)bt+@YZg8|vXo6DM|BFKF6+dNSyb&mc*fXoQZrcN zv9QBnC5ysoCu=L~x>vVbf5Yf>U@#KJW)Q++)#0t(yC&^@ZGWGU*^l@q_m5!EzqE8c zUrxP|{$m@+6AQ@&Wf?{!4u=)&{{Rg91qXp3y}0;Vrps8eAKaP5SrKS8vhT~GzEFtr( z@8OuiD1>egxd7VcGDCC86_u>n_(xoQOW|BPt66F`zrE4#nU9d_6PPAZ_jf1+IOuxU z$5eEce_`yLCkJ`^w^nxdqt@FuqH51g&(Y~AMvJVbr3rjG>e}e~dVG#<9a78T{{V=k z*6rL+X>n%E%fQ^q*-?yfmKh(FaK8=yA^4up#?#+wrfBVT)shR8!C2;oLP=jj5R8vZ zabA7meG|lA4fPE!@8TR2y}Xjo4B<}UkKuUOWm$RLJ!>aQ@m8~~&ts%mF_6J7*jK*d zP7k*jHTRIfQ=S@fR+^HtmrE<{=9*uD^Az!oSAy0pJM#M2`-i|<+xR!)G&+6ahl1yI zw57i5qx{IEA`?ZR~pg0K^z{TW=4+r$K1* zxshXr-BZqKBaCH{*BNy^+r55l{?I=Qw2vOasOw$;@Wjw-I&%5bTj{a+Q8Y?^b0xQ8 z2lqL}evoTgjpg;@-fEh>wpy)>l0p(ZBE+L4?I2?eKU(tpE1OAfZahV0adgtdGONsA zY>m6VK<5Xpde^a#l#4NvNhAWtDm>m4MUD@ZKl>Sxvn-;Mbj6NHUQX{@-CbgPY?sBHyLVdSmC*GU! z7vN{ZABdC9s_WNsXxBHG65k96l2h`m*AW4O+i@J?zFznb;`89Y9BLm5JY{zs&9%Ie z+D$FH`H;&C{H-KS-FR6S+db>|_~uxCAj3RP9W^*=;`@0%y8QOKKa#Q8Mk|LgOAUx= z#(PP1eruz*{5wAF@Nb0Ri0QhJ*vWn7TNv9MIt+u4V_!pDLt&}IqOFd?&W=i{1gojT zDIK}yyhl&dCDHY3D@&#gEwsR8Ps-b|hF@n~F#6Y`_;bpz_=9t%un19${Z)o@`PbvP z&T5i!zSlm_4a-g{Hr>pN}T^S>j7+L=H{X0!&ZtkYs)DTK=X#V(*K840P|N`RBgJaVfhD-ODkNC=B0vY;1Z0NvseUGY!(Rda0BsKmS^PKnzZCY@ zIy}C1pQzb5ORGs30NZj1bYaUW;EMSp_D|HjVP)Yvi>Vqr(Q6@s32p&vh+`Pv9CCL~ zGhd;;3Gv0hhp!;i?X8(ur`(0A^!*^PR*}c?s zq?I2}o>ySA+2PYqr*nn|rZ?0#tL8ep4Gco<-- z2f_aUJl11rQ40aLPbIR5vJ~+2Fp)PHl?doG7kI3_1 z)70r=9217RpO$-UG-lOJ4KE4!i^Tdg?G~SVEx6nRE=f~@4@~jWzN*u|Y9AhWa!8<# z%>j)-2Hxg)w-5);Rf{fo=NwncI)1(3d%Mv*a|pMQm5>#|{A<_mwC@ex8%vuTxTOUL zm;(YwPkQq)^r%&uSh^Ql?v1eWnuByI{mks!_QCkkr`;VuJ+;Vb1BJbUIfx*G^0oo$ zD`xBd3Ip*ERFhDH#5yLMa=MhpdzjG43~eJ2O9<5M8OK4ze8u98seE=}kROx}(AS!3 z7J%Kf>Gw|~{{XK}o<)-4BAr|`eoEh!k1nQXn9-9|>p#!)KJV1OZLMd-_PTDEJR780 z#bGMQ4Wr2mOO3otRQ%xMocq^Qzqcogyg6}oYafR6=*#i8gPljLKP%@Hb-)K2hi6w;va|~5%q5ni|-KJ*~<57Wb>f2j0Gu;!Iix+ z`PYnmJJoGl!_ev1*hX#uZorZGeQVQvRTZLKE!CyOdwi1{eB&y`agps`9`M+FCLz$L zNhPhcPw_s=5_Ratvzu>A>UsozKJjk7qQfQDrFLUv%w`5#X!i8USXEEATKE&<)|mGm zJ80y~$+R$e36ZxS_MDUY*Vf(})->Bq9t|eiIj))o+6pEIJ$_~&e}#C@j{G|R0B?U2 z>m^`b6=UDM$n@iZ^{=4Hs!oPBT-q&C`}EWP0q5pa;adqFMXrti0K=Y1@bb>#HOJbe zvIa-v)SpW9{{Y%U$GTpv;NK2-V@p@vZN=1$jjtqNfDjMiT=nj1r-m&ob$wCAnwxt_ zVUlJ=B=0D_U5_WoM6O%G)kK7CANgCLav3nT%^mYYl$}kI(AjC4|7r zj9!w{@wvaJ+QBqgjP8Ee^{$^u@y?^6H`y78LB9Rzbf?&Kg5=LrIA~?6m!NnLoST<7auMxEl@%OVyz4m}Ngwksm1J!t69sKV!UVC`2_eZ6t2Sl}dVqz<^| zs@z;5i_AsB_C5aq3i&-gC*RvJ+KAn9QDBcJ6F!UlNSp(#{U2_ z=~jD&mDJsjrhFNqUwE%mj@U+CF!%%vjGxlKQ9Lnc;ZK3O6f2z8cmq>@-oQ-$^ln zn{zZ6!94PF$;Z8Xo@13)tx_?oD@AV=*VUfB7c`?7N>tU`>U%Apk9C{PLh5Zk%~52L zjDBFj3zLujy=H135p^}b`v$dgkuPm(pYGglisA9i6w1_GoG!$ z74@CZ>}BAMFG;!ar^KCe?2y^Q_Y%dYsC+5{#CeaLlk7Uy6>%60B-ASP+S;gf!wX51v31F8&s#CmQ3V!d{GEv12CS6#6v!!8IVmva&LV1f8o&)y5s zF7;cneQ4V*Cw;LNJP^6ZC%12U_DEt|9W`x4Kp66hd+=MgLR9rNg<5J2B+iO;T<+Q0 zcthd^-@{V+?1{eCEWEI{+>rqz%XK|>pUS&`1nb@c)b!)x*wJi^@;NY*pDl?bqMt!u zDEvp%#8wemg$S}LvnXQCg%}wJpzn_Ps6HBa`u_kx(k`^yYe?>qM3_c$l@_zYTFXxK#Sifj*6}|j+Gu%Qq1NVUA z72IoI5i~y$Sf-DAqr2O``ql^mUtx}a8sRnFJHT=2&1s}HtRqO8(>E0ZfF7%x=B(4gXBpZm&QlS3;g?bUH>3fu; zW0k@+J^He_^U?4=t*TnHXm;--L^93&->E;PdIyL64XpUG-%ZjY(QW*|$IiHK^^)WK zE7$2gSgN(N?(Vq<@~<56hsG}|C?3Aacg+i zcW72bVyvgABdvYi@t@=Gh%E0I#>zqapkoKzSHxP+i)Ha{qW8B+9CMM&uNnIL*XlSp zVjVY9q*afEty+{Ss!)-qYp7T!%Vi4}QG#>p{c1Oo_R>Lbr>gI9xtNc;T*i&6L!@0a z6R=}1AaH9#Q1KLPXDeBZN_rf0_pYZ#N>+-w-5f+*ZzpHuRPhF(sOh!>++4YqMjYp- zu=cLcQ-bGH*GhSXS?CyNKtLvj7I42*{q@P{4xz$!RiN3`c#?jS} z_lQ~%QbO*}c{sXRXM138%g(XhBg-vvE(X{R!EPtMp+2|0AwEZ`L(Fo&u+YZ)8DwMbT1WZo*KT6 z>cU)XZQDAj^#pnm*1sdGMm1w|`uh)!mL7JR(EV}vtE6e#{*nIx2_2-Hp2^zYan+16 z90J~@7^@TATkBqD;C~X@XaV~Uc+&z=`J%NTYyLKvvdCdq3$1zbhoen015BHO;bpihYe*Z zUsL5DF-O!CBQ@PhKEi5n(C~8{Jv_q=IuciQy7gNhm^WHQt%82?AcT{FyK`gI*mE#h@e*!fRf`V&Z+GENz7%WNYhqhL?@_OE$V((<|d z=_*#2x{=3?I_25p^5fhGJRFaDh7B;l0CxIg*1BV1dSpbhm`yBW11eF-^dR-1Y5I-A zJ4|*fM_MvLp zmb%zt51?I?mSxGPrtfzBs{7Sy8xCM z?f@Az_BVt)8=-1n4sZ2a6LBoJ2(~uwAlhehhn0~!g+cO~@cUm7XkHwI$Kk6v4apzz z`7y+~<0ds9Dt}I;fwDI9X8>cPSq9$wev_a!ph`kRRab&I~)#c#mxS*R-9cZ zw3AaR_>AD?}ztu!v>jau3j{Z6L#3)Mstn< zZZprVdQTAu(!0Oo`q<}KM(bKGHu-*+Jl@mczK5(vZuGm=1(**iLKV=QbvlC%;m?fp+ly$m zm9w>LrQ8F|GNX^a6cWFUc2-{rsNGswU5rP2cN1R0B!DT}4YHB}8OHC%w)DS*$#-FDbPH(phmv_Aac+#M8pgm9 zWRR%Od>(PdRczuGPBQ!~y7hNmJDXv$SnB5;yzlu~@J|TpI+up@wv$EiGTB_nHVCb3 z+F1`yj!p%6o~Phrc{!T$=To|xLR_GTMi{Z`J#*g``V#*D416svr>f{WcZhesn`W0w z1pfeKu?@h;kCWyeIs>21yo%$&npUx9x4t0pE{CM5#%0KYcft zHu3Ye)54DYSE9-A!h3Bu%tz6!zQ=GCqK-F~;yD+&2)wVaHO*;$F4BA>uS0il;!zx= z{_^imhGli&XDUtzHR@VV?FFGlXENW7O6urkQhP#>30IA!!5Ihn)MBTZN@{RTtvWyb zy8i&?bm3`WqX$n!{$Hl2p?C*G@gApXe+{3HZOQvPIkK|1K4Wbe!Yr%!5nnCIqiDLL z%G2uxDOrlT&$W5aQ<08O9@T$d{h&3w+gr<9>uIF9kSMmiOOGkw3VGa!qkcyRHO52n zKg1@~#pK$`#c;Au0=x6n=ehT)!(cJ=MyA{+f<(#nRe2ha1_aTDEN&EFx zzHjq9dDp@vcvIKpkELw(;M$hvHU+Ewt8< z>oRH(f&hkPGGt?tt})Q@&3uUtooMa3(Q-QUC+qdDk~Fa}1!gFE0y<~hRdDRW9! zevAGI6!EpWY?8Ok^*MehY1&Q8+31aVEvv69DOl5S&Oks9%UNf~uuB_5rdeCtGw{Uj zY@T@Qit*dA;2zcZl}Gi=O!1SZ;`X$j*3(NimHf9qaG;}t!$S*B zY4yBT_t#t5yB?jV>YCNxg|%I8PoM1(T0BwRfIe{)yM%HQF~D$Dk6e+O^ZVZzc!?T2 z?H|l}tz57qv;3zyQcg(*qtWa%yq ze$V-D*z;Q-i1)q)@uVIk(GQd64>6+X2Jl3RN1@57JVWBks5J;RwUF&;0aphc1}7hY zJuB&*H{!>`eQWl5PYL`>@D-nkCDSI7-W^p>+3ur9$xvlan{pF{Cm{E)j66s1{{Z3@ z?X|v>;p=z5u)2RX(oBHdk)QETT1m9m}4;f(PU2RK5{>MAf_<74?PKlE+fBmM1Jm*%702 zxMR_~j)&=9=kcS%o)Lc*NpbL#T@R!d{fY*~ce^SO&z_?ly z)it(>U&~a}UF`0CP8$%hI2w4^%JPos@7CUWHLt18d@|IgzwuLA>5wBVuuhPxsAeyM zSAscWcv|&u3w%huO)FXOzl(m;8(rHMhwU-4G*>82Htqwc$m8o?5%B{1!(R`4S2m^L zMix^*Ro22842y3srnH$SCT@mI!g82HaY(~pUDIdx0f8Kq+`F#>d0)Z;mBr!~o2 zFL?f1(TBS$YV?w8Ud=x}ZQA!cu(_;oQgrH1-dAlaG}F@CugmT|b5Yebi~j%#*y$x? zYwO8NHwB&4XJGXw9X)Gn;nCNubsNnqTuJS%q){!wnl?bbLC$_)GJ60j4;py#-&63% zhP8nbTk6dV#`9Y?!WJWpkUBB-9V?X5wNDai5M1iE=Hhs4R7pF$jUpO{P zN~KIBqbMldzrUK=d1;~cuC*A|Q|G&~`>ogYJ=?@SB+-0GhStg@f*Dk-amsVOKZv&- zK<2+Uyg@#Xed0@qyc?$r3AE|mtnP?t-t8BLMpgk&KXV9$(EvfXKu%VlA6 z4(pb-D3UCP_*PZ~ApUjo{{Y7CgK|gV=)6~<_>Wh>P7m zpAOU1yU_M8f?pQX z#y%f|!&;=j@iDUkJ7#l+WmBKCu|LG8n91$(`d7T!c#`seik261Hcjk8GDyeFyndbQ z^B+LfZ2VuOomazbww@q`aV$jaOOQDPAK{ZD9Z(h zm*o3ULNko;CIhPf03T6bWsA#`l}fOUn!hx9uRl1x{{Zk$!qy^Gy`3pa>HIS2{Jfg% zeyx0B@Z_4O!XFQOMw0a|E~8njWVhRiV@Qrsv;0f%UmP5 z5z2BoZ*kn$)Lu9JuRJT^eFE}7hbVO>d>gy*9_5S-Wi40u-oKfD1ZwuWmx%53n;F*1 z!g*3TCXs+x!x4f>-~-ydC&PcWhr`Q@+59Q5Uft=U&grdf?QP;RZYtq?&B#dkv7bX< zDPM-^&a16XEv?%AwL4*$Ldq2yO!}Ypf$+!0U)tW`iQO`Mxt4N1 z>o)~gu<2h2e%k;OLF~SNvFD5a9DD`0_>m^BX{t7na*qOSHw0S*$V~4^0SaWc~qS62;O^rE93tF+K*cB{+p<_q2mooWz+R*Ni3$ZjuxIN z<7L|FxFJyUoyVN0BD(UdRw9-zohmBOgV80cT~~dJ#^LEp3nwSYPU8J#ARtF&kl)JUVT8UB^qonyn(LJXRG0f@$MRQ_VTs?z4(0!btjz{P3D ze{Sk6+~YkB6=*3+`jJl&C`Xp(O+D42C1Nh1^c&4(w9*btvH|t1=XH!7=O@~$ODwFp z^x#vCK88}MeOcY;nv)B(R++%u20^V)7+gsPpdy0ePcHz2o^$zEms;LPk_HDDHR?Ky zZLDdLVL_5K+(`8AUUh0}ooLPMdUR@gN-~1n>HZtbYpCjJX(^H2MsXvYH?XgvZhplb zoYy*HLmNgFPs#j8+~=iyW1;G2NYobO5*bez;=Yj3yk}>5r?eOHDhAqdz!_ed^{FDzP+N1IyNCNDjCcJjf%u!NF1@H(cw){F zzDW6%Mh08A9^9I4q2ql!Pi?linepBao$R#W;n^N>%rd!yl3N?#6A?$E^imSfp)|_%#DTo zvtMU?KK+7qtN5e7@y4W0agVi{T%RJ|IjVL-6(Ppj`g$O{O?8>RGT4;a{pA2mO`)6?h)u zZgkxNFD8$ZbAIAy9eKlipnUTO#bL90SCmTTaugr1WS4mTF z!PEJ@KL!41{Rauc)g47VWlxg-05|?e#2yU&g}x~2dS$Jbj5SML77T9tJ*t^y$2r`1 z86LIwU&5c+d*NS#T*rOqYd1Fk0C?TWxk2XxV)4&x*U~mRHmqQ6Y365Dj0cc`&}4!* zA1LqZUPY+sS|l3Gx_lSKWz_SNY+88-6%R z4iTxw7k+yG0KhyyhW5a*jdQ{gkTAZ@QB7CRs1M>A8clNF~#CF8oJ;X1x$fTA2AD1H- z?0R!uzP)ogzu9f1Xa&0NDyJDd@_m>2Rz;tYs%oMu)8@)l%)^ZBPr3P?qPrs9-9w$F zWdilFYg-$8c}CABFkCNZ#&gG}e_Hf!2BdnC>&i|J+n%i`B|CIHN8<*^ z@kQ0z#36FWjAI~VE9)Qd(DhY<+RiB8jsU=>*v0@O2d`c~ z8sw{(JUu8;jBfsWBSn|}YIB^Ok@4=A;hVT_#)+uUZnqiBZk;y8j!yOlwr%`1@QzEl zVbnFlcIpX#vBp_R9AQ}X9<}t&yWsx-1bCvz-0K=ew6n-EW{o_u*zc9c9^_XwqWmxL zeT)a}7Zch_tT>7#kr%J8Ij(r-bfp{0@qgE;=hVt^5R&CeOZxOTz7zZ}_(0Z>UoC{U za$mD75!_6%3@=u4NZLNN$b3=I{ujZliyc1N#wj6fjV-LtG6y^j&Cvc8^f$xb0sJV| zt~DPF>yo2EG6q|#k{0SmAHul#5ft)AQ(q4JQq=UH3(u+Ozhed~NlHUJh+sxc<8okO ziS3hIG$mIPigHtv{=H6$iWD&Xv!<8z=yzk`&%$34+6@LX1lJ0{wC+q`H?t0-&{qDf z;Y~M3vzJ4$HmMYG{IXiyFe*nl!QhkYUn^^$5-q$l;=LB^Xj+6`9KC@fmO*a7nLp>E z@$(Mn*R_39rT7wG9bF~XjjKVYS~Nt*Jkc^Uf;t2P^RHsMsYjAkrukmI{{SP+r7AUC z<3=l(_;<&$YH&(zBh|K`2OxnYgB_a#pIZ4_#Ja`r&nBm;3z*blGH`z*`d8V16Fv>< zdR5$-g{G>>e2mD67B@b6{J11;_pgUMN$|${+WC|0l1nH*HvF6u*zsSeWz}%jp!WyTpRa3?SzG;% z#~yUNXV4z?&qt<4%1iamL9C1WB`$ott5aA&lmV}#db@1q1>e0Ws^9OPopnd(9|Qgnj*NyI^(bZ0II*Aez9JZxo4w4x@CmI zVyXK|Ej0R{AzZEIoi6s=8BYh(8R<=b5ZpEqn+MG@DLsi&r}M9&buSNT+Kiq`MytnC zd9Ne!Ux8%Pn9Qnn9)x!5UrC3?)00q^jQGq>6A_0_RNc_sJH?kRVR2^12xHs;_h49l zd9Qo;SK}nK6Y0{O(KK_McG}T4N9kW9h~=|qM*bxu^RGkEH6^w1?ara*c}!qv6ooJI z&GL_wa(K;m)U=xOF9VE~I*Z#qk>jJm7dp(C)32;UNy?+rcj27@BNNEiR|Rm4tg*ljGC9dD_|<6i-4Yv*GR`YSU`Ptm zy!bre5I;)wV_EXMJN&=k{)gm~ggv9Qe;@cCsnc6u_`|~%HhQk6pxRHn?Cm1QB0k=u zgScbg6&9uATdTy|hIuIjM( zb5Dp{%NLm|fL2K^%*z-(8)Qqglh_*a+NJF-;dkZ#01y6WrFnZlc2-_zBWvyY1WovbFdm0i37D!?s za@#>HMh8=h-qfxHx1&GOZ|n?|)4`^uMpf?r zmEdlVAaC2;bv4KbwvHs0D~J`{@gzvA7>O`GVq2e>^%bQ4DAL*)%zAWrmNisqWs%G> zahX>b1h=8$xu|uWYfj86VPufw=7k-yanPS$)alZ!+vfRS`hTI*S;2Dh{vXmkOTrqR z{nf0tI(j9{Ta{WJ?1?jAV{QRlft;v0HRzrVzVQwGlXz}F529E=s?*#3tL(uEDi%gF zw$es&0Pohma9w9!5w(<;ay$@`D2yGc@_-Afr~oqlbp^$wnu;vafp7;W<}Az8vlCp? z!1ME6rGH<)b^3mW@vqAmwYv2G049CE;q7-xjWxdvSbt*O+S+Up>i74i-r-TgkRe#I zB_NJ?&U)7=W$_bA@KLjiQ=388)Vh;!t8{N54Y%fCyF`V1Dd&f3wHqmj7Ai1j4xk&4D(%8zwIxp=z3%5x=pR^#f`%x zlZ(wkq;_Qq%ucKOwiw94>BnmM5oEQFRx&UOKm?JD;~D<|>-tuuw9r6O6mO887j8~Q z2;-srE2ddS3zbGty8gRZnt1F=cXPM(=X2ht{iA$M4yP`s;d@W8T&yh+4>Xf}lPau@ zf=BNg8Qto0Sz1T!3FGTM9C%wtT`y0!pBs!;F|?9>-@PoGo02p2Cl%v&b6b{MhWQx* z3RCZ3DPz`eZPhWb+sa~IfN`(|$Q)-J)^YKD-lJ~I_Q_Z|-6UpaP+L5l1ALc;4m4>4Sq!RQ<)-~!|N#E43 zbck+NcZ@#MxD(H(-6uTO?vt;Snc`rINEz+@7E>4?^dPo6`uDDhG~IT~Ot#c-^_?*yo%R@w z$D2Gr5DUo?DJrAun(*pVr_x8#QpNi}jwD*ujlIh=+st5W@vv@R#2#yqxP4y2L=&>j z7H|mUd;5y_O)tXw9nXlBA=EGRtv)b5(>|SSB5Vb7*a6#vU4E_mJp4=2wE=p% z4bGvb$%jQ-n_Gztc*ye&vz@K<6`IG;r)X4?Ub=pEcR6ZMRbM<4Ps{!V&z0qp>Pc|B zfa(>nQ}aD3lO?;UE&>tPBw!z}y?r~T>0b@JHF}!Py%oNq@Y_o%)9mdhdF`%Mwj$n} zk|QWOw$8P`@cY9n@e5R%_u+l)mb#^!MpWp+R8qn!N+9c$2SJQw2GqJ@6PBdn(gWZ4iW zo<4R2AE>XdEbhDm7P%jTz9-x2T1=KvGu-O7lZ(5FAD&So0x=Oh3=D(qTonE=_(gYb zs@nL*>h9w37EM1;mfansjduYZ%4adK<7|u0Yn|p*>i+-^ll!lu*{g5gy}BJS!P29C zdY`AuU-)C^%Z~u-O!6C{Z3IXTOt*0;$j<sUA_gVScY-pvAOVRn^{meid{Ov` zshv`7F?AhxPqMdZQ|-Vxw}A;6IZ&r)J4%kA)B4ssmz$Q>-ufr#_54yYl?)@Dl&|Tp z-oMJ{Eu#E0@cyB51)bI9+>+c6kVPCp7<2a>jvLn*2E9Jx;S~N0S@qjkEzX=2#Iou@ zGs@>YMvIIbcj@n3<>!s{J#xWx9}9R^+eEX~HpzCEa|kTvx{q;LVFcnoUI_FxkK&zs z#5$tEW8xWvmlkMY0xOR`AfTKCVaH1O++?xUEgpMC?wy;r$^5$i0GHHd^zeMmxLvJN z)qZ9z{nwc@M!*~%0O{A$`c%?OZK=XV`UH_U`F>JA`rg&g-7&RAnm^t-IRFFOxvY!b za^hJ;)1NJnU?1`Isf8Cbxem$Jj2q|sj;lxU&Ci7{nWVUJ26mOeVn;k}K9$+{r(V+a zNiF2l5*co$PceBhu>=w^jxuZKT|#*ES1lBRSvn1%li#4OhQ>=MbTMOm8_kTI76)+p zcNp}ps+cNDO0~W2rgX9M}iiofZtDo&bYSwXoMT`$^){6^fgTw$;v9 z9CBHS;=Q)-#@`K^)_q&UiKp9INeg+lBYKIy+49eG=sH*Be}#NyeQMV_Oaso6@jQ}G zBrbD>9=Y#dSNuKr5Ai?7_Q`u|XCxNGpFUx<51>4g`B&swu1|%+&Ehe&+!vg*ch_$B z`JcOGSA#`q*545He-_w{Fv^--NZ5)5i1{EB(`xgdV_!G?WbsC& zX!m{*f3!y!P1f!e;Eq6gvqs}B>5*SqYQFBf}$%c?w#T}0Yr@hiOy7$19geV@u9&L{Kj~D;(Se~4-fdVF+I7)B>p8m zo7nq`_n!!SM%R21p-W=~$64^r%z|01ix6Xe(hPmc0O;yD?_Y)E_;Np8Sh%i9-Co}e z{X0J|_;d90OKDWSolN0w^pfb9pu+VcdX1rK?X8G9Onn8 zUrPCwAB;XB)2xtavs>B0a56s10+k$n=6tX@99O!;v}dVab800>Rh-jU=&rv}--?{VaJ~%g%Gp zrB0IWDtqKr`MfWCEQ=PFe$ZHe6ma=HvE0@intq{e2c0r9Dz-9s3mwp@~0Gv~zviCz7n#=ifD5uDAy^k*C<|H{`9VhWfA{&lOtgJG(b9T=2m1GoR&H`#9g0`kM1Q zUdF(=3&}k8uR!qEh*I2rn&XslwY_m(I``Nd53M5VcPsnOKpp8-!uweAu%}tlmoh$% zpGdLPhHT32UW6Xr^}FHkhZ_E|ZubrHK@k~mt$f>S;(OaM`?oo*n{OKUuHqlDTU^Xz zEs}wb-2008`j{MAk1aW`R(lY|;koZpFXVoZ_#@#@!z~lUcA9^UwM%&5MG{;(x0s+{ zHoy~?fA&XTTFL#dKWgs|>v~3$@VnuCm-csu^az?eJ7TJ`ODu;n+|ReWo`9Tz#e73$ z;%^qF-9JncOK~9D5)?8&J%-Rtdgp_GWDkqpJCthL<*Cu_<@wCFhcb|Rh?MUC0KTzb zB~LEG;c*eiX0`8auJpfjyZz_YPaBHG(0Dwqjjy{)r<*=gTdR#qce%S_^0_R;`u_lh zblw~AXNde^cr1K3V?LoV;4BDVA5)E^@iqEY;UC!7!2TG8ZuOXE)#pxkT;*c=h|`U} z)N64*0p(V{{Y+4{ew()-lQ&M+2%<&?)|;Ehh<&kW7?aqY!oMX7koTgAGuj9USfmMPzGm2z>Nzv|c?>Gzv2KE9 zZ#d-SWGaB9`U77y>6(v<vQ_sJOP z&mfxln(?Znl;wZW_i1}Jn~JedQ_-(2?1%RLwRfxKi4ija8WK+;B)|*KFvp7VKOFe0 zTJYwbG#V6f+U>|bSmgEO^yAm9dSYB3vWV;exI4b-Jy)Jd9Zy=T;|~ky`VNOLh`eie zX`@&KEP~dP>GV&Z8 z0tRu9Z))!3@Rjk@EiU!NTkE^HV!n5gK|XA!cgn<=X$HRU7eZKg z!;(C$@A@Atgsluio0Ped{SH@Do^+bz-LMiF2`os!BOn5LbR1N=E}wa7vs}y?31^W( zCx=uFf0@tWilsG_Z0lhRUTj-J$OyYWev_|0dtTf42^k;iW;P&bo%7Z!Bg=td2*RGea zh3(qrlg3e^?Os;(KIQP@=)M;5gufE4rMbJEV|9kY>SHaGZo?bmjE|QcfZ$gcpA&UC zwXd=Gw_N)kpLESCq&s6S-Ig#$>~_Z<)%jiVL-xD)iQ~&lsJuE;!|`M@MPyD0K8)Ot zps$wT@jr<^-kRQ_c{JBhy2`Rh<(nu$$j{QgzYUP$XAfzKnonzP{Jg(2;<1^&4in_V zLj7CWAGGu9nr5lvCa}^iZ&~F(CLv)d2r@}_A1ED8PfE}6ZH3p3bX`YRw6uGhi#vVq zb#A70irf&UXY$6?j2wc^)Yqu^Ps865J|I{{t7)S8_ANrmRbok1R%oV0E~+?oB%E>A z>04jh{wVO5!<|Fno|S3+k8gE|Z>{WApps=$F%8K-GbTntbk8;8drnzJC93laUNUz- zGrUdVC^fw?taQoJD>xU8`+Uh5;Iqodu|IGQ)$yl}ei_GZ#p7iy*st@i((j7j7326f z;ER0@4JA&ae>nZjc55%03J_$M&wiwIRRa{wUSAIQ`U^++U*X+DTasAgk|+|=8)+R_ z0_CGzw&>3Uk^^To_ZVzOFAkMuA9cMoTAmeJI7~(8$*yF)&&OH338}<|kq!?|#MeD_ z;eB2Kx@i7iYW*tL{1R?1*xtnHE1j%oP@xY4Zsp(*cs1sJCHO-28i!vkXA`q$b|@Mhv>!Bj;Dmve#Jw|d0X zz7N15-2x56`~443O7~g& zwo%%&Y6EOy+t2ro?wO|l0K&Dl{^0#9(*D?_w6Vqgl{c?3|J42_ z>o=C;gVP;r)pVa9c&kc9w3>gnj;tjEc zIO77pI?k!#a;h=LP7-mxyuBBH>+ijh`bJxsWAW3L9m+Shm+SW*8(&#yJ|Sjp0#uN& zI8`|zj&Xt4pGxKJt=~-0-}_oz)3yF1Br(V}lZd5f3F zJTgku((<6Z=$$lIA%8PNmHMh zNk20WdbMw@_zzB=b7=+PRt$@Q90E}1CvoJ1>5rv-W#F&)C(nIr= z)WCNExQu=^_P>Dt0A;^~-w#zaFAwO;dwChayVMWbB_wrZQ~R~Yuri4JhT3uYSL=(NPTzD=NLerl>;`*)FaYh+qqb|kDrl~3+;1l& z3>=SAKb?Hc^O~(cX9Ta_@AG#24`pB=tiL6H#Qer@*l**Wrx*NB{9~rZ@_fnW zw8S$Gm~0Qj6~*|M_6z-@?6nOpD;2TU8&QTvb8U05BgS&VUo#mbat(gc>w5nHhkQ;; zYL@K_2+2UI~JRMA~rx0`PXOIiAbAn>F4cl9+ibDVs2)6e(4r^L^P7tEeI(C@U>LytB{Tgg7+Rmx!Y=D0~M5iP#byI72E z9YN1Sk=xq8qb~w$_affQN|NGhi6(Iz@lFJ`_+mcraLRXfX?@h^;+B1M!dv^KNan*)EuaK5oIbi+W#6dv}0Cmcdjz>J^zeJ;2jv_wO4=b-D z=GvTn%BswsDU~Euyi#`^#gyZg;E;dX=~b>^@{w*Ja$`~eW#Fmff;$>Rd?tM1OXC}n zfgQeLwY{ckT9wN$*{!CN%@`}?k{EHG*gsy?({n}V29OOv$|f^(dZMRCft2(D)T0AI+`)i29qgWDp- z=E7Ub;1xJ+q+^Ef`2KZlTup2iDY61_wSeFpbU)6!n5?`rH-{jG54SFN8%a zq5ZZW1Wl}Xi6y(RyP0k5rni_uZxqM_F`5`xdu4I8SmL=mojb$Jrbnu2a`~5gfR-DW zu*!Z;M5iIw(RzVgzgd%$f?i$!0L$?<_I~d(GOx5vA}uZFywssfoP`!~B$7^k?>WFh z*RD=+TYebOJR7a}pG|op)uYg($M%H#;qyTQ1)~7r6t)LE)-CsoQrAtk&_<*$w8vpP zTOXEKpXF8x%rdUao`j57EC1wTn)Hc0Kk0oz^xLkC`n39Z)g2m zUfcS!ic(Hdwx@Th$9Leb7(J8eI&HDe0%{RG(umt>Ssb}h%HWn-?^{oU!uOCzZ*-T| zQ+ZE(+DSnia*{B%a^a6Ea#eumiu5lK_~*qx7o^ktIpdqX2TrxqA@eP*r$@Gi(RQ!y z<7V9KazE9UI49D)c`dYSeL5Xd*HkvwQe7A(wX{PVi*BJvonP)bZh#(muMRS)RuNT| zrPkMJ_SV<`04LS;_LVgWIm2$9KLgtQRp8wR!utN1KZEtFeP-WXjD3ev)Z{BQu^`p6Q+gG{q9;<2p00@7E?CkC~ExJc%6q0$;E+hS>yizR@*{`R$kjpR?)l<@kink>KgQR8jiFrBl}(C@&}Xz9C@tLADE8#$*#HK z=}IoOZdG+_zr(lnv6O37jN?w7{XeUn#n+1U9~0e0r+BATv(qEAigt~bC!R-}kjNzpS2*B|WP19F_1}hC z{{X|i3ij!~AZZ`)kXz4e(MJKedtb9m$0Br*bGsc1o+{@_P^RTcH{`AQ{;b-R<5H(B zT_1Me_4peSc+cUUl@^^oouOWv%K(1V)8DaMvU9h3Co17S!!DDsVLL@JK?KK zhn3i&`zy^OZqLXW*aME(&3y;r&mUX-Ww_A1F{x?OO%;Z1VVN4*3D__qINIa|-4XyX z<_zP#d3VJ5PKdf^gLFuiZ|tOIxVp5OS)`UmCvtC5wKk4EUOVQvu=3SYlpiy`@@~sr zcVE-;3|vx9ahJNgD>t{7^<%W~4u$c%R`D*E;jm)R9lXhHrn|R>-ZWU1n1Jl9m*9m` zc|2D&uG#B)rjpXd;`nt|l1$5F8dzpROJ$lo1%7xSdWz(Kx2&}d9jrzC_RAmdO5B@= z%%eRsmE)m3>t1ga>bB5WUQHF9+sOf%HuBZqVwO@R@`y3EdK1VLI(5CLGD$w%sdsz3 z{$`4$6$KY2*QX&aO)*!Y*mINut=to-VEHwLFJ4lYs zJlKR@kDphT>ZA%-O_m!`RyC5u{R}^95xEz^dphYZciodopB|E z(P7~r91vHLxL}SjK?GKKwOUl;89&I|0_eiD&MPle9xAy4?&e1aIo+N`bb5vDgw|7~u@3Q!;ClMjc9pH&-$f>$s0@&#t9yDM z>0Xo^lvLDq@43NJH038v&wZ`2#aU~bmAn; z0M}(%K1oh7wwtc~H2#_8Vc{Nl!c)GxKHYWv@1f?}-M#*gt;wU<&6`-?bjK7TJOv`iZvf|Xz0(LN$p#=mzSOs@hm!yk8N+MM*N_iJj5XS z{s)6v`ZvVsykp}v()Eoh));OJM$tsPXOo;BxbN#jsa?huV;jXQy=>pFUq3SmQ=q3h z4sA)hM@yxC-8{UHS@JP1bL;EV+X}TAR zE`AuzsKtBXo1IQSDtY#<##jB+!6eAak^_$5*Tk1m#j5KPXj&x5BC$zaM(u@AfR+dk z2R@X~1Nh2&37by0wT9=%ht<6!sYc!wP)!D1{Uy1sj zSD4|l>hu(8rOR~M`&uifZPR~o?q3i-FZ@IB{{Y5~XT$zATWd{L!unfhCe|pz?PIhJ zss;$b2E9+ke-=Dnr}#xR4;kvM_B)T=MQ;SmuRcdUTBa}*bz{J<1=Kz=_z&WKzk7f2 zd&AAC86^t^9pA$*j+$SC^)I&gM^SAxqqmwN{j3b}{_#qHBwm0XS0k-_lj4g> zEgnfVTbS-HT$T+J2?G{6+#BATu6R#f@YRg+>rn{Rmui5)i6`ltSDa6KYX!0DPY-yvR?sE$EM;Y0>z)s`Jq>Y9BFec=2hEXLQ1J&(BKeJy%|k=arl+te;>|}d{Bj+`9N6pdrU`o zxasEfONIXccn0@1@N81FU_3{WTWPJ_NAk*dudaFg>*MO>aBrFo`FCEA^gf|uoTKlh z`ahBNZ;Sj1ty@?&y`@}@b4*^h5M;bV{+hya@%?!f;-f~Z5BQU) zXje%-n|9F1INg%1k3xD7dxOP$y`A^NPlyS>#n#{1x3B*IF$(|yW9uwGbo11VSDz_X z!bg^}y6(68zsv3)vZaW8)ZL$Fx8MGMdGpSzsU&RM$d7kLZg}(+Ps4go)vvNlfVSmzuf!{*ROSGBhc^Q)O4atNp)?9l_LWu_bu}m!1+h5 za-JsD?BKH=3el77I%Sgyr-<_`)1Tv=us6Dl41Lr&+B?@wag?VO8XCXL)o*_zk>M&S zIabjx$NKJaDEelFa}Dm6%`}h@!g)s3b{PAS^;PsO$ra_EDb%CXbvbYC?b6*OVtlBH z^3)Ya7noj(2zeG+qThRokDuxXVSUdPeWU4hebI6(9AaX^{bO;4z`Op z8OiD^xymW6PJ2CFiF8}d5(!cDI2!F$)tWUscPKor?w}roitfbrmomwJZ)CQ%^SLP$ zWbBL{hzaKd)b+1Du!&-mYm6M%s(4^qwa1ZhCzg__Q^xF^e7t>Y%fwk;OY=JDw<|^d zM_Hoyi&oK~5b0M^S*h;{*h%+c^M19go-^?|x0=o^M(8Bx_n=JpYeZ$Wc}yE=uQ$bAWIh{G%67E2|K)D55{b3F;R~<%w3i^M;pA$YC_}9bJ z4+rXkc>rDI&dJma<1x29jyw9A{9#>|=NLFW@%-yS?k+8vXSH^UL7X}gtUiQuUo(iw zaP=MOHy_1+pW=N6A2+Q{TB1+zf7i(Uo4D3(Ep5ct6M2%cVYB#|S2=b)hirH2T&3s4 z8+$o+Br-sR@gN(Aus)){JiHzILHM^ux=Vi;7uBtR3%s4PqK=qukB>uNZ1@jZ@E4El zTUF9^6lI8yItL;-*Kx-x$EJUXSIuHDH86VAS6$ZM*QxYaY;8PU)i-C^pF;RY$Fp2U zwu0fDOm?hnzcx-r6+qm>*jLil{{R7eU*fGZ{t`WANPInKVmG|kr^^N<&Q;{}*z49>lJ zXMjGKuKxhXo+-Z;bJ{h`V%L=rhA!u?Q_y>b>0bzV%T}Iiqih1io4(?IQ{KN~$@01p znyBM>cW30eu47-7!Ai@q>^}sx3#)B%@5FoaASoKN@(E&CgY0pS#=lkk3E~d{>%Ik* zd^{kx)nh7?T0E`h?q+S$#;1mlZtgpGugY(RO12&jlTl<`y}@C-qitMc@#enC{hMa+ zhOZ8)n8v-G@VuET93UU{P0>p+!*1#;k2<2N)SX>@+im6F$n|oH&W##$wD+#{KCXa8 zZFh!n0r^Nhag&kA{Hk&(M+ve(4 zP#o|`1cK}S&fMqUn$K?yMXBocntj=k;F@5w2>Eds&gM_}2(QlLQAO%~dnw#2ROSVwVYNAKNYlf90&B z5vl(GbufP#`aG)!@mRWdT6|B@f1m1n=4FhI9}7~?`>WOe08j2dIkuUhDy({U_pd?F zwIGAbJq>xsnG4E%rcw#@u8U2Pqg~C^SMOEnsl!w7$}@1DjP=-M3c#rV_UEN`IuDKf zPvMJ^aiCvIYaluO(-1!{hw&BUc30ygE5WU#y@emljq&M`UmsF}e(aUcuBC`;SUVp3 z;}42{Hu!_3+or#CWPu~vRX|wdw?0Lg8e7_Gt1d`+x zE#?u($vMZhQ@fVN#HF+(KK`uUhri=qeJIA2=~7l#JsPu>R;tx^W?jR&!z)67?<#UT zk6*^IuWlYYNg6|z*gzeK#}&1!YBm;8{{U!QNU*GFMQ827g+K3qv5N4Wi;t-WxZ9Af3z;v zCd1>IG4o}*sV2Q=;ysO>$AzbF26%=Ie^z3?b*%bff*kR~lk)Bu!iFD8`PaoVe$W>l z42$A7f^Xt$tqTJ4LDe+Suum3MSi>@moq*(n_*di*e_BbqCw{;17vr(_^1TJ6+U$t+ ze*xUvc(&i-AI4wo_+Yk8u>SMRjujXL#Sl4iJA;5tYIsNDXTu)>z}_l@O4Y8sA*kIS z?HjEVYEI4y;yaZ4%Ml|gszJvU>l$B!{Ac3pn+x9@={^v=!l^bwYna(`7l9{y4@}oZ zLS$wXDf|qt7AR%**o5tbI;EtesdsSXY&ome%=Q{)M=- zoTEbQ$Oe$yi`3|ZOE$RbhvP1Nu!tdqd=d@=AYli@##x_!5Ze#pi+?{o{J z<~y?jNrA@VPI7o40qb8T{B57$w}>_OweU6Muv)g{jPSkRq~^YAT9}ff_ji^$szcCVXr`Ssr)l*brqCmd2V1&1GK_H@!|zrpMG zl{GmwosTPD1Na8@M7DRf(t(99JU{SD!rmy= zJT0Kx+<210&QS-|$Z% zN}t+F{{YvK|JD8w4ZiiyYME_!w)}Hm%c*z+P19|mxVoNWBxqXp6^Q=;qnw?YepIWD z{kzjGG(QN&B)V{93r_(e|En|{9bs~5}qi^1$Iqk;-p$4+#)b8!n zMQ0kr3x9DC5=l7AbC5o@=2uX~(Ul63yjrt8$~bq0sH&;zyMIXi(D-HhL3p#mo*A;y zu2L;$Lm){66aDDX@_%(FRnKKS3j2@3m){INE@@k|Oxs?C%UaBFAnVW*h8~1h;P>pi z@i{eXIer`Kq0iwF%mlC% zrA9h6_^yooQk0{MrmHXbW_{e>4=uFF_LzQ9dk&S#>mD4iwTMM)AIiO}&UCNZZ^phJ zj9TkfcKXx`Htup_oDcWc9liQl)ez~w6`B^hT?Ngf#Y zv2GYB>7RPFW8ikTxPwe>Ior?VNjV@B?T(o4E9-kt107E0XVV~QVuP2N{zL=ZpI+5( zUhqGOptJ~W86fhD#VQ@h%sCnKC$=luSgn=MHPN=Gh{vbto)tr+!>3!`OB$?_OtUf| z2bIb7^!%%1RPp|&HiazLR}ri(yCaI#gE$;#b03<#yTwbbjZ5ucW128RhgL)LGW7*| z9(q@m_{U!HZHj%O=GJ>vpM`M_#XjSY(z;4m(R?-I z9cBETKEg#+>m-0j7#_Qu01Eb-jwi%k3;02=ffl*p%`ox_@*(>>@JB{W zR%8Cnp8|d)>at69tm~R2^ME5UO1C!1z$gGjcCcPK3tpqE{4CP#tPZcOYf2h6%d82Q zz~eZP$OXG#SAqD?_N4IN!nowp{1Ir8T#>X%xJ!2U$1LP+jCx>KL~3H{$CCv)b9p_! z{S5uc%Jt%`a(*ZO0D^z`ufd)k)I3Y1c!$Hc=E6B{;u0+ITuiYjY$C|JLy?>z>TzG4 zdUl~}Y=Y|6@x|76DlBdtcVj;?ocfQYdw!$)PyA}syizr*J#nobaAOJxXnS!%ndXureE z@+&MYS+A-^t8ah8*J!V2FNi!-WNj?`JQ{0t4)=)05>RdByuueBN`p)BHnVM^T%zkS zT3YUoIc-GEzFZJgh!=+ldFzVed_m!ja>G(sZ(?h;W0>Sed1I3}!9sW=Byek=fnFSj zUA+NPFh@*v!4>qep-OSlt8e{XzsXqfol23Nnt#`#M_(P)oH|7Ft?r<=LPTYb+5#=u z004Jle()R)4R1xQ>37e&Pw?{3ZyahG8Dy4Yylu>{w;0{i9XYN400HQ_Akpu9N#deb z!W(iHVB}BdHUcHAS=%F*7cR)9`a_q;J zCb3CXsVGHl*7jeA^p5w-P6|?c^tbt)=ZB;5j<2jO?tvACi0cb5pUFrq(IZevGEKN{ z?b9TlD~8l8d{M1xFx%eiF7rYc!rK-f$wD0SeB0n?7P z=T}qT-CV3%rLClrI2dJ6g!KL-IQ=U+POVyMb@%dTu~wZLQ{X%I|^XmM%9F(d@4GBrRH`iD9XnhHJ@lMyo@Lu@2uI7it z_Zo6q!oS&`WRkw&lCDcGHi3yi-Jh*+T4?b;xuD%@7SrlCD`vs&Vvgj6R6HHX3Y@n* zfOhv4<@Q?t0QSAy8jhiJJ&V~z`U4MvmKU&dNoGiM%wY~hhy}#kfsZNx&>DNuaBg5v4#eN3y18H}5uVH@_Ya`p- zqL5NW+__AV^8=q+^dAQNUGZjxX?3T@uW1d?JEgP&DO8;F-WUXYIRhwtRKPZnC; zOe5S~pq)Wh0PJKArwR{LCm61^N~~Mb>GrkP;J;JMoqAJB&dqndzcyBxZRED{?hu%_ z96T%zM?7sO=01kIb+prkyB!jF<+b1kxIq+$1CUe_Fb@EOUVC}t%V=U6#fsbi0BA`g zeWNh~2*v?Xowz*+1KyqC?-JW;%XqgkEI~*s_AoEW6{B4D6CH_ zd!2jRXI`uoneq3C9<{3^ ziE6m=hTd}_IsGbzpKvEr=Pn+0dBXZY`)>o>sNhtr;!DDyiDa-F>;N zKNF(aX_mTdV+dHb-=M(Hy?0S~et7;}In`Q2$!?X-Y8G=}-`r^y@(a1G@LEClMtXtk zj`*!CXXw>HRk66FyBeHm!68+p&r61|Dw7iE* zz0>~ywk&2(E29j6K|PB4*PwWF#Ficv&`rjrYL`(t2vR@}cXm1cV!mV4tRzeLu51o) zLF1>l73!ASoPHtDp4#YPRQ$?1hF*XA^~H$7$Jxa0UhWZpZuzboRMzix7HQ&) zLb4uqp1+CdUq^+*R)&vm{{Ysf$>VW!YHQcG^`S;tRo%>BN@^S26*l(&M(js zcAVGPw|*+qynkaJm9AafLmB`h5J(*p00H}{LEw4@uPxSo3;2%l+D{5e*`BI>Q zWB3z;_*WJa6yo33z1R4C_BX`g;?j6)wfOzlK6!~YJ^=jdthIzsr^yi@0fI6>=kup( z{t@_v;XmGMS|EvA_}UWd`KdLNW8z;C>Eq4QW`+k-o-Ngg{{X(X57NGxcdHxHY5e~H z%IAX=p({;4yZqWRr?*hpBoYr8=lNE}gpC}DjORRH9Okp`ZhTui8~dx72)QKh2&k+_ zTwy@`s%<~ZvR1pfox}eCvVLR#0M@M?+-%?7{{Re(Rg=H<`LknGv(zl(f_*_)3xzU7 z&5V`KGlA2d#;L)nJPtg;&rFW>jd|j`#aD_bRik|W0IbU#`sdcNBJmt@Vms)tf4slc zRjfSaA9)*nVhQegm8Q2Dl*arO9S>X@=zp0P-99X1uxKp!|#iKi`?r4=exB9i%MCTL>_Zbx?smRPs_iSBa5KjhJW|PP7nyAB9Lw`7DZ>wN zYbAA9u0|x}jzvr^Vvpv}-W(3({{V$?I7xg+?o*EOXWAYb_?_Zk9{5HXFFdttb@HVu zb{1q9EXS}p=Nyt~y<2CrP`2ad{{WVb2rbu$)2Scrv9CDz1FEw=rQuoGA&JqiBVwE6 z45MoW9r)yX)xBF-*8EH22tFTtKGW|t-Dgpk?Do)F#P=7M5-}rWP{)=hu>fFqHS&)S z%ZQaKYRi9mP%bH$%*~@a4KLt>TNJjD`8Ir3BMDm(A8S%kiu1kxNhkUr3Tbh)z8J8xuwg2sAnlJbHulMEgaq~cYqj`q zAIHxK>XT|73(|FVg6n9wjclbb&WcL~eaa3pI<0**sD9di9G^)04}_%B{5590CANvC zTG}FxdN#+C@hx3Oq4C?|XT+a`vFJV{)YVO<`8@cI#zJSv-o0|)3+2MdibS<4Nrt{$WWO8%Inb#S?^fma2t;wirQ3br7Y2Cl) zq_RkR{{VT7adsMdO*`A{!5IYe#eCfIh*pc7YI3gDzv_Jib9$7TgN5IF{{X;0S9SjY z1AIsECbfO8+-Va?v$>VzjK==}A}P-b=Ol&W75J6$AHrW2JVWCxPsQFM@aC%tyq)CT zC7i)k0m6)hBLP4a?6c@Pt)67EnnYaZkkgaEtz&ON=ehNHjMD?Ze)d@wswGP(xJrk>s6K>trw@7f93d`Rk9h= zryBI?Eqzz{pPZ2Rs`A?c;vD0tLtQi;F4Ja=87483IylEaUX}C}{{X>{gWC3G@c#ga zFFZW?J7d-$`%Sq#@%D>cvXAziY7H~?KKOy+X~Z_OX@^f`W*Ve-OFfns)#Le;p23t? z(7a|_IP*>N`sw-a@;o&|9|q$Uf5-ayoHTws@b$f{B1^F5IKqgb8`%E;7&+QG#eKK%i~b3(uK3SDy4U zWjNYp94K$$C#`py7lA)$9XjDW8Q{$(+rrwFl;Rn@r&t|i9oeu+?ag2WpVCKH? zn*RVu(lr~O3x3dxrg*ExT9=38v$WP=W{B9_2?2@EcYVAQj2hDL{{Vu%1!yu~czfej z_1)$5gn@j3-(^6hLkVNvHxhk18uN569?yBcgmCx`pm8yiP*e zNK^fNqB#EmR>Y0KxW;;p{8!Ze9l!A9*Tbz#!9NFeJKZYVRFXNQ^X3!UTs%a1e`$+u za>L~Tc<)}a-Y@u-<6T9T5kb(H+SoP|f#{j&s(&b`_~w z)uGL2rR<;e<^E^0I8|EHmA@2xpe_7S;u78%@&22sTHHs4#0|CJ&ImD086iOa?|#+T zS}%t_0%_K<=}V}1o5lVlSNk0PC7t83wT&^E8ZbbW^UyJ@-wJq_#6A_(zu_?WL!&;u zaVW$#w=+)33S z&mOgvNJ+&(d@pr-f5H59HiXijJhxqctq+*A&j)M%F4kh!ylt;*Hl87c!H#=%j$1|L zcKIehnE>H&)YnI$cpKqAi(r#h(tI_i_=^5nq!$xN^1(aKjuu}u5-}$5NduaX;cx91 z;hDTk70tGvduMqBfT6QQ5+lYyKf62zQV1$XTIM_h@MGiVnekJ^Q|WeEgIa5PjM}}a z*JE2bE|zx^yo$lRz2TVrqpn4CdpRhzDRQ-IExuZRnWC#6WhAbh^*)90XTZH?U1hZE zT?w?yD~m}an#wz&G*4}?ZgT^$lW7?o4m#G4!ykkez6$WPej@mHe3o-tpY&*TH5X7p zy~4KSAdI#HMUjDChvHW7cBi4~`sc)bI%_`@+YxCD@VSQdPSradkm5-3oSyl`aC)DN z^}hhvTk75`j?T-&uw~O%vuQ5lcRNOIARjlF+=Nqsk(%*i&n0F3Gt!jL5b@8z9~s_w zhHY0y)$Oliy2O&SuChpHAQ>3>SW&wp0~s9+asL4D&z}-M=jzY$YWk1ipN$^(O}OzD zzJaJ|`fi~hdqh%}&myS9OC)3x42KM-kCzqcfAE^8{R8!1{0Wc$0Ew>u0Bcqr<+k)Z z=N@Owzcc^V{!H|X2&`;g^F}~!wCNdhsLEi5WN`U~Y*ZGNOJr_yf=K4PU&S5_lGaHz zZ5SbV!F#(k7?RkMcMzE;%g;`7JJ+Dwe{E@26WzSGcMKQIxOmhUOhA$rS(KM6k~jon zxtq@z3w>1hpTpMHIz(w3d2(zdF(J1w3`urumEd&iUyb3VS`{3k@BM80@3oQrUx|hi zRHF&#{{SyF`rV#Q4x~1a=m_h;1dhJFy=y`n+euW$=oMV!w$gdvjN_ihxc>kV>rbp| zmm0>IY?jeVOBmrNXgro69#j*~J6A53vq&~%h3j8+7;{IN>G~g>yjrI&Rj;3O+%#=% zd>^k{_=8B8L33#P)OPn)1E_A5`ql8e;|_)K1HzDM+GCG3NJ---=88ei)BO)|Uxv$Y zcM^J<>--<_1H^s>)&Bss{5v^?@wP=gq>KI6UgOiauaeBNsAHg{pS-sJ03+SPX73L- z7aw*109QYHpIEcG55EdeIUNIk_3JOqjjl33cI(auLOn;fwR{!u`}UdBwZs;lAYZWD z{{XZ}iT;n<=xgX$e0ytkY_Ah}iaAzOoP8K}uga@nYhm9c;(mcg6Gpn{gSqr{QwV}j z3;gkr!1@u~dsdr#QSoku?`v;(%LJz-P8mV<8O}cn`L-{JG6;-u&CjEZVeN{{Up2r`Tq{G*iS@4-t;%f_n4Tx$RHJ5w7I7ZJ>S7 zqO^H+DLYu;m1LFA*xft!l=v~?I5v2TSh=^HoRyZ;w%mFYCm?(EuJOJ-e$JYLTiRdh zXHXllhqYxuc;$;=3jTL>KN;TIN@kY($xP(sg8u;Z>w(t3HR#%UvaQSBKXs2S&(!f< zapCz?f4h@BiRM(OuihkoM_TWVeg)}v)9Si@xo2+g&e+sDf#?lkoaw*?wZo( zYv6~Moz)>8`0w}%{A#`U>EesHTkKv{f_5*NB>V?KUVh#_)Z@5|QJ5>;qh;k~^Fsnh z4zI=thni`;%iBzo-pyqi!1*1w%|&~m0o%Ls*aW8ogOP)PR+EKBDuP^#jN^# zn-<BE6wk<%gy2&DW^uy$WVEF@t(QJQO8)ctufXng&xN|ig{s-<$ueEfa|xYdia9r`j)aCE#s=Z&D~NA0%1wZ_M+|@w z(;W}7t&a`MsNYX7_J*YJJWH2pZkRC#94luepH7w2P2o#@b3|pb)C6~Mx)CgrGept0 z11LlV!BRma_ODe{bee^EU(UxLBx*so44_`{hK_~q_ZrjV^ESbV~8+$19|a;B7l%d<^$LHgVFt7Vk^B)9W3rMijcF$KPno#luj0FnLSj-#-vZ)vJ{Lc$QYvbB}uvZ_f8jIlU{ zf-?orQI6x@xSujgi_!jnJ%1f^I%4A~IlEE zcyCYCEalbpNs{Aqf;envaBZ=}Lrcw&sAK`db-jJ7fm zsLLMz05*N=o^OX9F4yDp?3M>bbwY+^Spi}KosG^3@6xigEkjk0%S$QbEDl#=lEk+p zaopEyb>okT)|x%t)H;sKDR#v)au|e;2i*Zk-N&djq_GsI%ZKG&xBUUig<4$I`My>u z$zyTw!^9V!7V#F97}RZ)M<(gK&5S@H(UU5FUrMtt#a(0V>#W`gq`g8sx!NefOfK97 zP^wgp2q5OOtu8zbV|YH@cXO!2HK~!F1QEPRb+OC^s&EbXq&$#yl*I9s=hGGEcUpgpVU8sAZc5UBlKeL??DPwm^+tm0Qn7@h84I+TQr!HYsKI{Nt;@T80W`5ZnuhCx z&)MOK@r>jbY$!gMs`qW+*&$mEDi0-hs~gLb#^W1s%F(7{3A>>j)UxQ-qW!eS`yQx3<4; zJ%`F--x!)RCjQQu#(MHcL;ULrUS006xe5>6&JTXZyFMiDk-(jrX`Z!l zr@?I@fU%ZA)aQ)znx!qyl{Cx`nOmvJWYm82TydbJITFw8N`5meXoC zXd8bDy#OPi9+k*_vTJ)wi}+l|@!&4rw9QUXoDS10SlG`3GGN%U~ze?k!xw-oTK(a7MxdS{?G{~B0 z%*aS##|IU!u}L?5IvoE1S#pw%uKKgof?4R+qWyZyY<@y;Gwc5V*Q;8dsRr1ruUiQiJ5O9@nP~(!c9NTNFudlv z;};rlpQ*>h<<28vlh^}oX^3lY~J$n9i zgL!iI_bqWJQ`)-d;hJ}ua6>G?~j^r-l@t&s`{{T3qOKiz7L}hIBAmnpZbWbbXt_uu% z)!1cR=Ax1*6}HMYu;V?dH;vsk@G zzyq#pl2AKiG|i~(*p28nXNrlUl@B2M(;%3{fz`^K@rtG$&5#U4sU za5o^&TovP*@?B@(w~OqR(j1#zH=yP7(q<+y} z8N9VyZxZQA<^nS`1j8migM(bQ%<$VTb^Ur8zwG+^zE*svs`y`1(}vAP-be`S>Q=GS zMiZ{UF^>4newJvSBltn%MG&Wm9>tshBh(>Tkb7?&*JEe{{Wy| ztWzKRX2crhmVXRBn6%;RbiVhe;&ewRoo#UKOVeM-{HK$`a+v(Cv|>n?_U~z%KkO*^aU?8-BVAATc0)tl0HA$P)G-jeW|}<8nRQxI)-5yViWUIe(d_Eb+W( zvA7{gIaBN2wk-4WOpGPswTG3h|+YQY?qguCpu*~3t)bsr5ygVkX zq*}zz4_KdAd_KC?r13q4kA}|OM&bOJKshAIl{xn3n*G7jemCh~@J{aw-24&!n35>I zCu)}HzAW+MBEF$-=OA;YX)eJ1nkdzPnq~Rfi5&%hKA!`14RgXj5w!mR9R)WsSjr_T zc_a?F{44aw#rCb^Pl(rkHqmAKMV7k%0B*OOatM(|_4=a%q;(bf26;tC6B<*M;cLG+ zUqt(*y}JC5(Q?YZ*AFMl?_com^FD-)HNS~J4sI{}ME4q=p=|Fgw=+o6%JIlaCP`eD zJmm6g^XJ8$7KOFOx3&e`u?ng(N#}qET=uWMJQ=9p=(-lU;Ylp+H5i`M%+Xs$(z+Bs zd4gnt&IvUa!9RuH5cKaB=zkus9>iP0a>2BlmBim>ZOUIPF`rEGE8(#CiDBJJmq~Q< z@;P=8sK|$<39^Bqw$<d>+PS7ULF0IemCBq4(i?#wY1f4jw95x<~H-j=RY=7?SG>VYu7#z z{>}ay_!Z$e{72xgwWQ0KTK?++?l%Sn6dVKi3<{iHK9y<-R82|r>FaZzh8|R^w@s^e z{{Vn~0Q^90U%+>VCX07v6qBW0|?ESZsl zk8{Aye@hk~4SU@O?YA}-ml`$tv~yx8#B2yVmSj){JxzGtzxx>c3c8LNv@Z`>`Py?y zEucnXk-P3c zdROf3ncz)Zz#20D0KzAzwv8qBVV}xXYk8RN$#}ajJx4g=zc2nY{>&d3^iLYxNu=q~ z-a~U7*C}&pATc2y^pfLXYbl?O4EE# z55v!lh|q+&6DwIxZ0xUOagCQj24OGR*X8vBz3<}h!>@{dH~7525PUVF*!T}Xog%b} zt?nM~ILP@{Nn|C7X%9o+wSGK!!p7YAmV3A)-yO&f6!ixfD_^Ca5%KrLeMj~>&}5F{ z`|R(iUTN2Y*DP*fwE>}N7pn;KjP7@B*%GFEuZm>7gg;1=@w5(Ya8xA3pxPlJ9grKf>DKb>uK+cq9m<*MvtBY?n2 z2*?~^$}-PXTn-D_X?ovnCR_Hpq&&aL75Ll^dLt8*BJ;!`RHF->l*xD307N*(GuAhf?f(D(-tc$By=TClFtpY@OJ#Fm;WH`=p%l206O0YO zlDRzz>0h8<3jPv)%KE3pYm1q6nKc`a7e)3peNOA`6GE~8zF3fut&Rxa(;Qdh28r>L zTi$GUa4yWuG( zvXe`c2qI^P200*+2o2_pC>hS;Nw1&$1^Y+o`n`bFyipSAD+2~;_v9uRZ7wsFCxQz7 zYlPMQ8hjb?My(Hud|5i!-rJDPbcmBPbmmCdkt1>5i1iPJT6N9g(vHh!&)aa=vWWT0W@jr)t9%^3{t^9TH+rz8kEpNsb#_ra8 zE7Fm?qhXcgwZiy9Ndm~yNeuCjxdfAp z5ym(ozU26MuJ|JB#9Cj8yd<#bo+Z+)<+rzrUoBuZ*8q8z(u8liuD zeJfd5ybU;rHS9MLMRNDEouD)_$gB)N1+}4K)Mv?Ra<|C% zSHS-O7B%mSpAU4c3&VaKm2~YsO|t5@_fkE?w=T<(0T|&&CNZ8X?+*m{7enxct+tPG zsoZL9m5r3|*WQ3e3)mtZL)f3&9+Z~hmxr~L)= z5BvK+_*8P%kGj7jRVr;={%8N!{vjgq74#Nqr%ucwbtQJl%B&lxAx;lGoYgCt7JaE2 zg1`~U+;;QA=b-*o$l6|*BQVIJ(DpsPhq0|FC3|v$M%r7HeCP15k#u=o(fY!~N=niv znh1BYMT6_K6NBlV^x3q~(fy(*%y=I#Z{j|q{3~)f1mr^Q>D7o{0rltJn8Mi0_Lv=m zD99{wr|>l#@`=1V+pskdXMh!I0_%&@EVgfrEeB5AxH}22jSM~_n>C~Nbo2iJBc?NVQRmmo z{LacRiC+@oC}p~iU_SA9@CVg~1zNcH&*KY6%C)S_-IOuM<6Lg9;$1d1Hug*B1NX2# zP(G)vV_45_gz5k=EB8`3@&=JY6e4cKjf4H-!18^nhMi%l z+>bUXo4(>4C_hY^(llv7H^`UB%2lgMN-k~u$(v4D=6jpjWSJW~KrGCs>UcE(u(*i> zm-7HP3dj`xIqzEb3#`P!mvgRgRhuEdE^+j(t}M-RkZLel$g}V~&`3{x!;w_#Nj~8k z3Tv_F*B2%`q!U@OBcOA~=iaRpn)-jU$EB;e;4bC|{7Lq&b<_SB>hRn_{g+~O8-|uO zOGq8t&Q|39^BZHpAH-@MN5wj94J+H|@xaP)8u`xV5~L0rf=)$pEJ~BLDBtJ*00i|v zv*#Q8d0XZG008IouK`&2Mm7HcgzhEN?GAm%Il@&={z~y&#u3yAD3~VEx zNn2DyfDAVVc}{W1&5nJsUA$gCH(F@5)a_PzmA84%^D_=J<`a_rI3)2`CWGORi7c*s zN3CiHNtViiSs(y+FK5fUI)z2B%g-24g{#R?{zs&UIN{gfH=(TS>AI<*& zm5(5YPoGO=wY)}%z6$O|$mz~Tdi1Ys({$}?!J0Osb$4xYx3>!wq&p;3mLnz|m;ioa zM*tsM#Ix~#!FKT6k;|iBuD6x)Wfw?oayH<)5uTX^MRI!Ah;`2y+}kt8i{t>YrMhhy z;&9#lNy7-C$2?M zpGEL=Hum?w+HG@i#2GEp9g2hHC-;nSTdz*Frxosyl1emiYRHX3x>-VEcT}ms7W-5#2 z?kdf|;g8F}?@m>2{p(xrE0!)k?eF&-^|phfTg>9~RfqReEJZEjd0c`rSw{=BbSFJ( z@7jDZ2I!L6=WnkA2AEYJuzF> zwtCpqBo62;qrqbcP^g??hZ)^bI*I$vSL@sIGou#YFE8PL%(|M7iKe%ewB;6XG(tC( zRc+%6xcj|MLF<}5UrHCW5dG?HF#39>kg0=#96t$;YMdb!iQE#hwi-0G8G+G%&1 zJeHB!$8yfnUWv&P;YL*qVMDW?px{@P&!t|&cqG=m;?BRid4|<=9I21)@Ou;5yvjB2 z86>R#0I$6CtI}$si+4oJ;e9(#xZIIRdhWZUw|M~harc?iIAD4$Skt1vIytn_ugk<_ z%$8#jO0V2M@iL!PCa4?#054J&c{fryQt;cMb|elk$$%>!hA!+qxzoFe_xw2FD{Zf1i)`(>1lzdS|Nm+|v52tB_WZS<}-?_Cl< z{7UU&wT~DIfH6E{C676-s&pjZGhf%{a#h5}+^PQnUzg%<%XtQ%sP-_ho;1XQ7`{V} zjn|BN)&<4v%G(!l9XCqB06gU2RwTE2<1;aqKv-bmh~v=X8LXKtt{sS#fF9NDLXumv z!>LwJWOPt#HxXQU#(3i~lZGEL@6)YjE~K%_?=8V*Jc1OQ_3K&RV~?J4X&T-o8OJ|b z>rjEjPYf@?IV3KKPrWQ&H*~s7L!HvDq5ne7#BaC)Clti4Iney(V6P z?wUpy@*{;-`7@4r^zTKb)s?hu9J`p2kCY68I&y0+JHIrDSwLf)Fz@yM02)bUMk}~u zik?g8%@s%XS!wT$*peq<8vX450DBz;Q(LmCQr=k(@8MC5(&9G3Tz+({kwGDlZVWT` zc&ky0(!@$reMlKvNLTlmBcQ2bTln2%Sr4{)R50D(gjO-3I5@_6?oYK`hzaglEznLE z_?VuawQoZt;@WrAhUm?x6}OnAcsbqw0N1OwIvd@Wwua?F#~?P`kL6ZZOOEFs~%qkEHxq1zq-q>#=F-gdhARF)?cdjQ| zs+F59Vd<#dBg*8lGC62sY#_@h>M`HxQo|*@sj}&rSa7%*827HDQMQd3%^b2eN&WUV zk?eh|5A2Wt;`Ef08I6^4j!&un8thZ%iI*oNikB^O2l_4IbQwdQfb<~xRU(Y*xB^h{ zPCu<`EJCB&sd}5Cj)Wo z_4KKHgJ~#@;X=UoJQ}c;kzL5rZ`kK8vuPvg$3gX~>88EJa$FJshyq4=KT3Sdt1#uZ z$e!lf=1aAP;w(E6ft(C`8g8WxuA4vEt*2SWK43XeeFJy;iep0eN@amrV_au($RCet zN3}5s-gwht>d4(_T`tDgVvAZuG(qjp)q9Vptw3AKa$?DiPXzV+Yf|DXC;^ftkz@p6 z7-NsFYM6LuV$&Ukzle1FMQokX&f#aKCV%$0Ar7rzr8nf zupP3FfPS=cp?gU!8PcrYU<-gf`_#)MYyy$U;MX*lVlo0%k#I{Kcdl1WopG9HOrBc} ztJAe!fJYkcjOB%L>2T}w^c8j+&GDaZrnzNJ^g5J{BMgqk*k?S|e_USt1#ITWr9K=reHvrLOg`0eo5!~P-I05=P7 z_B(^`zAOy;*W})xBkd)6R>z!@kS-T+_vXGA6_Dikne)`3lkXeF{p!s6ygqf9(2pvr z+U)gTjnCIDH{%!l6Vu`pOxL<*yWq>V=xt{gFa!SU^MUoQmiORK?9Jl~WtZXS#Qhsm z^FTgEt#9^uGJ6*sSL0#R^x0E(z^Bzt4OEuJ;lo;7$cM1bYsGv&$t&S8bB~j>*Zxee z_@1x!%qJiAj5J@Gynih}1N6tmAF;T$Ej&6so}(9EJ4(A(1bW~a@EvRRb?|kBq+I>K zO^_awN4yI8hfeW#j{H9R^sPo%@%_hS{zkg!{{U!Ti#Fg#d4DwOKj))w{{YZ|uU4K1 zHl?DjrT4G%M+EViZ2th|(_eG{03+%j0em>{j*JDD_MN53c{3wOJgEX;jiWqOF9F*8 zVfaU^-dSjtX|3oJPVy`ZAabCShm3=m-yLzm?_V~{`$20r2u7)I6oa2Io4EeZcWXRr1hOF{g=b(=MOMM;Gm85b)4|$Ljp5a2)b%{xO%q(C&dMQYB<{%v z8NoQke>}2$YnxHpqfnP<=&}&w{{SA%dIpvIOL*Txv=+K%q_nQC zCuV&uJmQK^+G>8Y_?FMX9viyT^iKrD_gaaRHO{FfL~3$!u@0CAoY$cEi{b{ocdA<^ zudUwRTOx?W%mCO9aw1>40I$hCC-%$nq~&e(gofrc`>IAU^u>F2rT+j00heF!ov(uY zL!#S#p3V11EF)~AIpDDBE61;q<~+$L-{yDG%`jZ8w`1+;d{d(6nuM_0PjPc`HbTpA z+>GZKQNa4tmVPqPY>%0*!{^ASIS;pb^ZNVO$d@0ruflH_Tv=ag_Qy~A9kSah#5f^y z^D*vEdfC%|X)lCPZ)mRKl6G}ul41809!B1WBZFRCvaDm3s#AY)=qzp*liZs7&%WEl zI`@h+VW<2*-)&=jRI*C@QJayKVtRUt?|eA_02DkEcC7kR%$D(OX`;hMV;TF{Jv-#r z!O;AE@ZxLIr-AioAkk&Sky}dPj1I%+3zPSEWRF2#OZXGxHk*IpokrJF@f0gO=*b!^ zfC8j*&THXu84HBHqKjR0K8BWK8ON8IGur2yR-O~$BMdiMe6oo*sThsPB#Z;VJu3^x zo)Yk%iZy*ZR`6R%rB9#qg^`P)$vI^`$54HKg1k$^x}(YA-BwFIZr%m43jY9Ql3s2G zPu#)5BD1_p@yEhC6_giWZVP)E4pk#}MBs)}SJs>`*!b6rr+al)CUe8OZu_1)@yqsY z@m8PUE6)!69G1^f(Jy6bt}WS6NgSn#Z=0ROo`f3wv+*y(&l2d?6W;3gaKUwB!M0Jf zV08qZ{{UM3U_Z8>!e0%-#>F)&*+IdI+Qs~^GI9xgDL#g{OHbNI;h&2H_rJ?i;%9`TiK>+;>+}5) z@o((S`zriQ{i7~r@nzb7!aLyIIFGjYlTt^H$|Ig4gE(uc*zl1=2`8=vez*9O;Kz^b zJ{4(j{?Bkhd*PoBquM5yE10!LDoagwE)K?$R^)~IqYKU`@qdiGW8~^l>+?w;}`xk@sx%JQOi`LUzhmhIN^PHYOLU4Ba@N4H~ znbyrJDzHj1TSr!(&rN>sL*9;GPXkTDa=UxItaJrEa zI5~CT52&s(+vEP7qj>N8E8;$b4YMd&ZM7>o8Zz;cHp?7}7v%@84neP=2+SF0KOj#H<5_J8m+^k)wywv6rSzpudd--D8V&VLg=I$3_u8rO;L zth`I3!R4%}CAu4j^2}>3!yIpPR>Lsmhg$gaSokgRS4`G#?7kOiHmel#NK#t~x3ufH zkm4mdB!ip|mG9pVA@HYwwYx1N$J%FyMXCP)S9)LyvVd|KTR8j;aQ-d*pfqTHJYDGC z3~P%yQKC9}>8gsdt~0q$ARLaOrw#4pQOcd#OH1u!I_i{T%PkvyJD)v#EAY>Xyl0~7 zT8*^vYG2uNGF(G>z735T`Nx;lijZ@kOjpuc-@;#yJ|Xy5rhHJo(X6$<4(bzqn$BxP zTd3{lQZW>WShNns@C?@YG`e z08_q(DD14RZu1tN#Hrm3jDA%O*L8cB!QYEs6!?eX>DyRcKUlK0l>MSzRT4!;ILmz5 z%Mdg2tRV6qp$KFS(c(dVk-@~5;MBfs8X=|z*aK2`hr^zgvOKl-V+H$Pi?Z!rU zuamVO+4tc0iY(;SJRhj)IvkfHa;5Q({w88qD&%5d54gzdT@Hz5@dgbV)4=}#68t$Q zmukLrvsy|L^uS7z?3p3(Iup=z=DwBFz9jq(vhZx48TcQgL8$8&$|8vV~xf&Y~;Vp_%q^1zzMu9sM|@QLmkeWdjiV~Te)Oc00~rX`LG+N zK(9If0EJzu{{YX6{&o6s@VDSx*VK8SN{O_(p0Q!kGm;c$$M;HtKU~+&I*BiNpRL0VcMZVaBi2($FGt_3fpAYz6 z*TgZbcJnJuw6Tl>k&Ne`?vGl}@cy?yg>`7ITsgFCtI4;inS#nh<_^j#lI_Iv&NA5DeBQK-_6t!}#8y8i$z&oh4n z%>gp#^2Z#xDz6(BX6wgJKD{fExbV8_LEA@JrMQJjomq3!s{%N$sdPUTw!LXSk8gi( zrL@XS-c%c>ldf>fo;`6|_MZx_Z*&QKQ>QHK_HqFXVe>uE&pXg?7q=O&1!gY|{_^bZ zlKJ=3{u^`F{>xaG{>`ekzwkFc9*ayaN;6{>(pgWZ#EQRRkIYUt9I-yi+N!_t&CNR6XEMdmq(1d#>{t2gCOINlxLA# zkhQzJV9}#Dt`~~a`y1M?ojash%JV+Vb&ESvwHz_JlNro?b#cB+!i^&9=w{VcdPh{{sCcmsKsG# z67=W}Jp$(@scHIcmaAtjp{!orT^R5PR4&-PWi{pJ4vH~#m}GocPJQVjdI6sYac|=JP?aCULn_&5;xly*kEge zGLPa+;3z)zQ^E6mJ=5>kRKBvezY(Ksv$!ss3S$e9Tc7ij|*LC1;4cU0M;k2Q0 zzuH%GD%(X3znG59c>oW%qraf$yn2;Aij%utTe4rH`}-e3LKN!BXr7uYxB9QR^Bc*& z8rW+Vr%i)X)NNT0pA^9$V=rMFMqlOpGhEH3#jUZJ&7eVWV{ldOWsodtafM_6XWZAi z>OLg#FN7z3Hp5A=(zP24jD5D>=67hwAG!`qcm#8|uX^{39Y^CQ#UJcAbnOR4vzlNL z^(h)KZy2(VHeKh-`AaBAV%2B11UIsAG|*brxmZo9YPoY`alK0Ehn&+&J!XC#89jx$?}Ei z^JMn-ucLk=>Ax1d1>(#900wyG1-jJabV#)~nnsP1Ft`@&84)sc`gE?7;0yRJ>%>>r z9y?pM@eY(By3rmRsQ$|xYgcr;_0oW@~4Hb7T$9*kgTt}Cn|C}X1P6g;hw9c>jv2P zauE#sS`jtim6QMp%K!n*dvw|qy5Ej9+4bxAbnCz0-(2c9w*pA!mB^982=ZOsiQA9H zyx!NwT0WPhM-{%kJ?+;20GiM2O(n9fSCb?2U39THm!w_vS|uxLyq&+Uh8T(%cp7e=~5hi?2#O6jA!If z0}AzDh}x%zr_%J};JpI!D~Mc~HA$4hw;_VzRuS^94}9d;5vpq5Hqfqabk7krq;p!8 z4GhI(IXEL}8PBQay$W?_Q7Vmh(Y1cvFZk=+@+wkP?+g6W)A9WZz8di5kBM&GEN*Qh zn`j{=O@|ANZDYaf-2Sz-sQ7rBOaS(FT_kHu_}um(koLjPuN12aS40(dYB69Xw2`UMbu9 z^fFa2(T$|n_4k)x*P+#I;qdj{ycf4{oI!B}jIz3%e2$|b&%avlkoaAq)S|P|b$L9v zQI)P{iq72|jmKdu?laMI&3L2uo5m4e$*frvv3rKjAXPxQJOu-g2W(Xdq45T%sC}bb zxofq5n{hjcKEpg$M6ixp-6iVx>$Q$$iBNY*y?ZUbcRe2EhlM7wo=s-b5h89YVixLH z!vJu_!BE-%0KQFfx_5~DKDUwIYBskT_3&~ek5HM}0uQ^5rw!}0U=Eee-C5bh`I$ix zk@AE0zP#3jo~vhOg<-ca5->0T$NUSeb*ws3)wcfc%U{I8Rr#lPW%&MjegpS_pYYzQCpMcJYk!w7wR+VP)lXz zTbNPMA?If7XZ$O^KIC~)vF6l@ocWu%jx=377Iy*m^U3S@RA53BJYXOlfE*f^%eaC- zzHFNZrVrt|t;F|IZoG;iBvB9@amWY1)84mj_c`OsdsxA|Re1Nm9)tWn>kX}AW*{C< zTI?r+%4MD2VFa*%RyG9h_5@@D-bdHQV3seYead|l^DA@4~t2- zxFke`@lp!~w#m9+fOXAaT==6=xME{FLF_-3XI*L+w~7)@p2HdX*Gh#)@f)l-#$pyJ0KHm8Cu5FyN7H+1j{hC)i)#THm^0zyX=b*g7KnF zqnva;m06~iS1!jS*1*#q+1Jip#EgIrK@~6A{>}=r4tf(yo-1MdI*n?~H4?G;bH!>T zSI;Nz{sG;rnB8b5D=%0nv@MI7VT{P(Wr%S5@omL$_=v_mE^ zH)W20yj#6_W7{|0JlAKY-&-4NdsbiGB9O;{o^m_&u4=J-sl@eg)mJ-JMr*c`+iJ5~ z-pr3IM{5v4Q}o3@Nx4luC6S_zWs!DakIcU7j)uESOUoIo47V}wA-4HGS}AY$q`@Mn z>Q>R+1(#0JVzN`wqi1q|7C5dxYpzKn*qsYYo($c)(QUtV91|;k?Os$TX;wWDIpCan zS2HD?O4CGDAw&EoUB~|bu4*V=OADysjn+kf_0hTc2>kl`8uTqc!BgwHb9kN@qWe0z zRgEykReQHN`WoSr_A2w1gHh+wDDnwoI(L;V$QKZ|-BQ^6IOKa(SPR?Ems)JUG3dX< zzQ;X&wc1}rrmmnC0@4BZ+^A9y`#(y{z0+*N1-3i>Ze{zdbJ&ibjd~HNthtXaP_(@a zRl2yeyLXYEH{>?cw4C+NtyfkT(|K_Q-Sp$?YSqJio^P^ho0=ug3X#{g52aUFXR?WJ zE#+2OKY%ySk8xXRYnisMVk>!Mwew(|h=xE_w)3BH%~TUc_P=FZft=^p@U3=}Pr9^a zg3yV&`GB53;5AJe)Z3W+z4B)}`2>4op{*0^V+NM@7S2fCR7l-$ypcH@O;WvhE^eB3 zAptxH#z)~-Ud^w}Pa3K-5P?9zC)ce@Bo;Oiy_LedOs#@f7#QuE6IZ(28f{p`fS4E@ z0vKTK9gq3zO4g!H-)1}bEJ5lN44c zG5-J-Jx8r=%&)64aJ+rgV0l?!pyS`QD!h=k4>MQB^ymg+wdTvMkbWh8nlH%SqE;tE0VL-o&$t_mA3}EOMdVJURcu}Ro_FYUlwXAxOM62OaA}~ERHhDYnI5yLE@YX1ChmN?4j7xnZ4Q7L*jRh zfd!WypZ>b+Exdnu5|1i(U!xKIYsyiuxoWtItTIQvbIy%9Zi&?as2Dkbl=)f;y#%grLLmqXz18K%vZ~cZwmrXO0LSP#~cddmJbh8Nkx6fOffZ~ zw2!*9f7?&v28|z=71U0LsCZ*<>0P#m{{RIB_@|{^dGBjsDfMe){(x8J(cg?K75r(Y zYmh!!SOM=|Ra}cKt$%AqFXVcZ^Gt0!a#VkL`r1F>r5-QTM1xw;w9Atq+anloS$zr5 zBDS>e+xx*%S{p56!4@+k{ot(14t|Ee9+vL^0RDP@f|YNz5XA5AUU&MnmMHSVoxK}J zqO+`Z?qVK z%<%{9h5I9T%fj})5Z81~U|3nOXwXWmK^XonwFTG5uh>^wHa8aAN{K_-?wkqNgAZ1x+5is1eBgjdTA1t2lToQ>i8Y0B=5}Eb6SC=Lo-%``$ljpV%|S z8bkO~$2UGHp2^CRyTLyx7{LRKSHCCiG5-JrqrdQ7r-3z#9d)hj;_~B{?8?N8U?74p z0PB&O{$u|D;Uv`GqV%O6B-FttZaej^Z}mzJ_r%oy0Kk{|8~*@rtrDXjx&Hu}{Yz+{ zv_Je3Cr7v%gx9_q!0U*YR76~liMZ%J*e zia30an|XZaj<~Po6?{*t@J4Ft_{&{~lwv>6*16@v5qn~4f8a~}jX%~1J?O{of98K) z2mT5J{{RHdgHXA@z0&-1c|F?)l2=6~m<(rx~0?OPK z3_A?t2iBKP)-`PpP#0Q!%^Lx=incPRs-I(98SuP5X);YV)&1WAK|cP~RO%4Sv8|1Q ziI=Nnwog5)wazk5E;@OhY@;a2N;66Mf51J1PSVEf=#m|p7kT5a_4X-OT-(=H47c?-L#K}$8Yor1fyk&Or4 zd0QtxK~0`5_Cr**xjtt@8ZO*>X1z*OsnhsZm)*A4JUaM_wIj<3 zZ`p1gqlr2eI0HXQ&5F_q3L&{HGs#uJABnBqJ@o6aZSRo8$E&H&KH$|ITSvRMD>#*9 zgq#8x06wC-6qhrG$CEVW6;pB7mMf=E8#GHAG00{+@-x@d@T=0^*0 z9jg7L>v?Pc00{Q;a!JZZ5|&`zp^ZOJ(XNnO+rw=fS5ST9I~~A#FR!h0Ia}l3`qbVq zl)s0!^`T}5zYO-!%)UYSwg}Xbj@yXsRNlgUN(Yf&_c5R1kRpTh&o!Af_Ls3@)-@++ zhYXhL6z9;Ig4bT~R-39tccW?2UhN<1=OvY!vBg@aJ>s4HKUZR0;+j*Ex30f(xxHy; z49(>5#`B#?mzl^lZ%1HT}!<8t8QxH}IK zc$zkoP|)ob3Bv{qss_xT;iCHM#y%jCzqT(Gi}KkdpTwHtl?l|mCu?=r`uxvI6`=&B z3*V-iekSzO$*O&dJ=S(s(6-f*Y1&2^{wEpf*MKY7yeIKiKZkmBo)u3K-@co1aczyADjsXuN(^by!dlP{?FZaa^Gv;k1a>bx7R-PZp%#Y{e*=tt(>C|-L~#` zd;0zr6>ya5!;)`K)?cZ$EKOQd=ZdrMf6-X_YsCKm8vY-6m&5lKI<41}izXN)h+18gl+WVL4cT;%Tpu3Ax z(L6J!THC2s`!U}tox6D~$2cT*>s&48jXZlj&aLEJM`;GPkF~=LXo;{8La4zgqtFpq zN#Yd|7spr+jN^L05?$gVm z=vUgGi*;LTc=hc{N#ltGaYjxQC;$Kez{mhsoNBrpjd&VT*%;4pjxa&b>sK!C=Qi=e zsq!G>aXjGv07}26>o=<*p5=oQxIMufd-krnag`|EDSk&ST9B(H8DHnAfR-9$d*Ym! zpIWxo+H-7sY&HfM1NV;|Ijs##PJ;Zzu`oF~892`!39MB&F$P7q^1@&cKMLsP z=2yAQ;FGgj7cMNJA%4dZkdd6X7_8f9uNG`b8Z3JC{43eK3-G7M9}$@%(DdSCezQ0r zcH~#q{vG{|Vpt+?8EQadtZI4sWM;gIxa>Y3O0xY~*&HWSCoP{vMi5fXWfs6zsALW& z@TZ1C`OxUroS*JjHR4|d_$N}2$<}l`cShjs7&!bZm;GRalKtP}dQr$KIN#cS2jQfe zu9E`@ZtjtCIl`9xJ$-8ie;UE3-Twf@ta+*d8;C2B>5oeNgYhTqdGJ5Q)*JNr+fJX7 z2+jdM{cGe8h@Y^(i@XyQn%0Pms@NyZlx}hEE9vmL&RbpbO|<(n<*}JQZ-$ROIHtR; zS@6y8h;MZ}MmA%~F}8kQ>VCeJ%b2gOWHQK0GagCrn)RJSN{YZeoy08#&i%ewU7#;> zpIXVC)m>GPe4G#~?jcbnXe0AFv8g#;q>dHztBm~TlT#*?RAyfM*JB};@<6}C+daP; zL8o3%Ka&cXAF_`1ueE(nbxk*-In}YX5oc-L?@}9VsA$*a9cykVWwp6BT7nShe(&__ zT2tteLauKm@?~yS524RIbgp+&&r_~6?wyWK802X-!~wu3{{Yojlt(NDrpEO++mGvA zEslYs+*v)unugibkdUNo%uWf;M*vi|F<8SB$8iL6vB@$#g*J|Q0sQNpRaUlJe7`fM zG@n**-a|sokMDAq_}k95&S$slN>n_wBs20V^_wH2P3eXT&&cF*TvmoYM; zHqo4r$J^4gb=tl94N+FQXBBhd_+zt&P-){~Hb6kY$Ts!dx#RlO>*4z()0TU!>hls| za8#zz!N~jBt=q_Sm?62luw@Fb<)l;K%Y&S-$EU4kTE3TYa*J~75M%Smi}!lt03L>t zoZN4ynu2!Fj{CuPP}?Q#uIrd&A0&#nUi@-%n&Gc4;-wX-IEE)h>Ux5{3~}yzqhgS(r|Yx7G?)ITyOyO z_pZv3ym_O!rXI6TlB>|`=kS%s**yI=+DnNEAjh=)pm*S&wU2G0_|{vIXKw_^@6lA1 zC-4TYOXDpM02^sZMt+coYD9lpHndwX{h%n*E}sKE5#{AdOU_a3#?>V6!PPk5kXx)vu1xN-D6;<+2^OU*vu+o>w8(Fq45^RGq|g!PL! zCmF?BNR}w9^*KDVbjTHQ8OKxU#aC+?1+WVka8KUPT3H~nxri(oisPX}(Ed52PDHl5 zXf;I34uQ!FpL|yO>eDdM?o#_vfy%0RTws-BoEo$(t|P?OR|C`%c9B~K^T+}{3`nxw-nQ@5z=_*4GP zaSVgaK3;j`;MU?LrM1jc!*X2z01;)*KgO-TUbhlSnBHBzz%obssWLF5?w`lCMUxw} zH)90#BxL5bH8`}@bM`5kM`M$bfll)7EaL}Lw-PI_$XmBgK@`-ku1k)lWVSH8NizJw z{RkhWWzB2lxhj3JoSNtw(`gdgpEP4TdXe~42+2IbKvq%L^g5Mz zy$<#iEk}H+r(XX{TX)l~FC#OtQU)m(7`KyvQ zXCrAKdKz(*o`$d3pyxVvy(H9ab)2>_T@iE()u^gL$6uC>i*=4yLROk*f*EJkSN!U*( z*0dssDGQ!!88o&X*pNWYfsEAWY-En0RFYhn6vrCi{{WwQtrV>xDo0x7)#}Y}(6GfZ zgy?FV!Ia?E%1I5X10v?LE^lC#LPFqF!uQzQOKQptNI4xTptPRYseG`0W$#Vfv~k;- z>rUol(*OY68XF@&(wZC*xSo`O)aTxvhDc<6neR~(^81Q%9D)l`?kL#1_ZuGOBOU4P z!Zq5xyVPryAc{sgJf5_S?l7FWJ2r9swiluvWCYvYHrZ?s#+Qhb^CthuPNQR zy4Jk6Wce04^yZ8oPt*SZuT++DnFgKqk?KFs<5kMV%zyvV{x-F*hZnjP>=s&_$d7Y^ zFx!HI+??@R$>5zfYlPEbh=w5k_C^~HGn2<^*3h**P8cPB1mabb8-PB9^Ia5o zeg%6et(FU>mT#IbEFY7vuLM`)v#*F+H-AI=uAUnV?$WjU{{T$l^ zx)zW-^l14auAN!R@s_@49cmDZjG?EOLk@i!+Izy*Kw)`eMnU%PR4*l4Sp-rPjeSr7 zRW3DMHXlAIuHD%sPJsp|*&_3WP1widVLX==M>y^b?4bNwo0f+#Np&_*H)*+n`nJIKB zko>vF*A=Ui>dsz8Z6b9s2q%l}5jjTp<28Y)t;F|+a-tv<3=a73Pt#_a`DL;|Ndr6& zO09Dx$c!Yze$$R?2H2i>w7af>ICMN<4|>kmz9tLJOU=;i*48ip3OW2udTNzdv`-#* zj5JoV`5hv_&@qM4m+Cv#m6nroKYtW>+&cEIZ&>(~;!SHqf(5rSc@ARtLP4raJ$8er1AGvYfa+Ivl_kO1d^bL*2|e`@~#0R9?$IYDn1hU3lw3{^lr zjzdzVkxxZ`k#dM);i{`dHtQ?eKz9iIWSeEMqP*c*WCXA z13zbkjbhclB`Um}GFvY`pLm`>O8P@rmeLE+9-Cziq+tI5%QffoHK}2B_HpJbXEo>I zvl^9Y@={#CnbU`&<34(h{#_4Z)2y`3D@qpnED3EA3~Y>J_3K=<{nP=nMjPu~i}<>0 zU{rHhSAHw=TkeegYvO&COZPsmGlY(==JG{5l=IfP>)#YejhV^gjMp1`t-j+Sd=t%Z zH@+ub$INZj(Hah0p44Z`v^{_R79aLNGX^DnD=Gdi>9-C`S{5g|n(_PHcG>Oxw^4&! z1ozTg-2JA+kVi`FjusDeb*x0=qCT{=@x`Umf{Im1ab$oXmS9{^FjCufNg70)d`ks}AOa)$B#wV{w8BNO4XR&-?{gXT^@n=eX zYekJL^$<4(h+$m)J0H@&AO0eIHP-$c>#}H?{3ab`h6?e$}=2B9})FR?(zJO3pe4~8HOHMoEo)7 z_@9Y3wi=XoUVKTEm3o!h2=&43T@H)(drOibk)B6ZKow66*#igGqVWxfh;)ngvDKtL zY#D$8c~H6I9)_ssI_36*cDjwG46*W?hI!`xf6mDxl zWd;4fyS0feEWq2DWIF)>{{R+qxcgUeboM%@hwX$uB#rKuF|~^+jK-sOHoh?V9{H?q z5O_~Z@P)LRcZeAc+dz{-md+&F(hTBKP5}ye0=UcQE-iI58jwjJ%2R|R8(3rvuY6V< zv8`!FS}nZ(`~HVVGo^USJMZQ99;>c+LA*I*95SG^mg+MjJhG3TK6n5VfO>#xhk#q* z<@3#N;^XH?yg;n`Qylg8^shX;yP7Q8PIoJ>-D9|c>Ca47ZMK(fa}3uG!)#qaJoD4N zFJmcEah%=XR1&QfQ8+_R%=&b};D|1N&~*Du4ur1n=4$=Mz2RFJ$&${&f8K^a*1l^0 z0EBQ^TqwKqA`YR)&DBpJ)XS&obC*QjBd0)4ay_fdcqHw5jib3^B(|oP_2_!usp9X4 znw7oHjmD90a`Hs$8(S}w@yLGT?p_E})qB^5U(IW60znQ;m?B*CZ%lJt1b4O;7Ba&c zvd+T;Xdkboans3he#-zLYy|Q^&OQ5@_AvBSC1l;a+2vQR>RRc1kyRzbVM!a0BLf)v z)^+ulh@EM%M@Dkc_wIUVNxZI>l&xB|KCo7a6@u*U{WuCgyaE zeE5yT7A}%x_*-kBJB?8XOch*W6%-+6^!~epDn1lX>v)yUgO@DeJ<9*6qrQ+ z0K6GYkGwr<<;ys7+@%RA^R#ydMf+=IgmMHQFpuX0r+o2PQt1ij2%0G2+IV7e-<4eO zpNzF{4XpOMMW|zPyIjaP{V`is=Tg_C>lA8DwsLQNZce ztlip`n8&Lw(gzHqsrRgyW4W@KZxalIk>B&LO8n26ZgE?ca=nT-S7PtWj^Tdi8@uAH zOAeT^#<4p&KX~H+eJgG5EHzp7T_VI-ec{tS^+oMrfy7eqqq_19K5dg$Gv(IfZlKXV z)eG7OFsL43?oYK(c|C>vYik=cQOEZ~9G}LbFhcTqR*|%zj;D&#)AV_-E*40ZH1fwF z@;@J1mnfxn_9-UxNW*IjyAd7Lp)3y})~GAnNs%}rLU?cSu8r<*EGKwje4cZTpOs{# zgclb9Czv6VoyVq6t!Wu)c@W~ZLl)voQH!PAUj<~(BOac$d_*sQe{(u2k&W5@f})?pkxO@s_53K&tDv(^#YV8UnaV>bCpZV9pYW{5gJB@` z99LOuE}m7SokGYD-8~Qg0A8tp(y>Jw(46CMBk`?N-%=b?IfsCCJXBw2;FYd|Z9Y#o zWNJ_0%~N=G{Efydp-$%{si(RznFu{;rDRThI@Y0xH$5pD+yRqX^BnGr=w=xlj(gLD zgaR9=tB(rc4_aoy(S}EQtD`4QeaO;(UY}a9B#f@?`x-^IjrfnBY6hP%8x(xQlT)K9 zbHrW6PG|?OHCdKu+XI?H@0_(3>{mZ@Us)UYdsWinF^v4xiYF_aigR59P~zFDs*$kQ z5{CJjoG)E)3Fflmxrh&*gK-}7xA3g_JdIUF+}(TJsN2;40H0cFUrQTsdi1Q6xQS4V za%sEpcdbt%S{ie9I!P{NmjSB0((N0_;<60V5Ez^R(tyt*V4n3ngXcD)yOBl!>)xQ2 zV#fusRD;QG*#|WXOjs6fb4^F8A31fVz`~v?Av*!ry;c({E@`dv{M-t(h0JKJ*C2XT zk#rqMJ!=Jv3}&2?9-aMZINUkSuQTSzJkxyA4>ghI5yo&uNGJXr^rg$#9QD+_=Qa*% zKRL6(s@`Z|2J3VUw?p=b(q@dF4k@``?|+}pv%@j#*WQ;c?N5}iXuBW( z)BY@MyhY+WnAvTugmJfHTg|KZ-e^Q-;6SP{Hh?fbwdNzm8l+rA1yZ^3W~O8fa7}zO z!*e|%kKC3$Mmo+%ZGEF^4zDfM>%W+C!AZe1mKt7}9DvTxDaScA#2*@6F#vEq>tf%; zFv}iE+QzhWYN*+qal}xR`J?!rfNXS31E+=G-!%imT3w5R0IotQZsrX%C`evO6!`8H z;}7PQYUlQCbvm78FS2OOeLM`_WG#ir?G@Y^{bY2Y-6mx3JUTwhJtOH?3(Z6fI_QEW;F@mNsoZ zDR_>>=4%iUrCqy%jN-ZLy??~onBkabsp(w~j&zu=w_ZiQFggx7txZe&A>17`3NNAU z^r!o%%iV#;D%gsVw5lF`YZkpW^=a8l3@`-pD^AlxxQga7(~|AR6#CZvoE~GW7{amS zj<~D0@Yp5di&ny7w{;!tjwGJ#lq2xYjbVBvxgKYha@Zh@{{Wt~VXXAF66!8Oxj(yz z57g8}z~U}PYPSZM*D!hBa`}V)S@*0Pd&!$7wu&mH@0AF*Tmbz~p!TWeO*qOTgqMh8 zC%N^-QcW*WxS8$j;CTv+w$YpqUMomj-1#d0R?ve4(Ar&0(rxT|*TA0_KZ1OH2Crqk zZPnLtC!o)I{-C@Y;~xincKBszl5IEE@hY4VyQfiK$tAtr)_r%Y6WG1|WBgW|=8xuys;gaT4IBalz6csGpn{Wno}dj?(2)3ttgLlGwV zY9DVrI*RvCoaNP3K3QWJu6pi!M}5GQ&r0ua{3m?Ff~Vi~h_B&Y@RHpgs=mzOF0TZ6 zVmK9nd#&yVVE+IL>96e8L$n?%3T*}Ci1~eM(vK#W8*{y)dG+?SvO;74{cA2wdrO`& zw+VylD(0!-EpkUK3}!)|)xv6;7PDxi&jYSKtLUl5n@Y^`Ria9xbt0^+x^b3>=<+oqko%d39$Xk#{2lT~CL;B;IM)DlVIHlgafJ zyz=-?N~~6=&}G@m!_7jji28TqpNDk2+fm}(A{1+!6m}qE2ZDO__pgt%5#cYjt**Y` zXNodUnGi0=uM8{dc)V9_;!h9FUboPDmbcm{5noEUyu9~zSs4;*0X(IN`^Jl;c*%^#Zpf=IQn&}Fnm?Cw<6QS+H6qB z@obt&e|XXpoROXsXQnI9?=N76(PJ_fApP9%4Pa^ZS2~sPHkb{9a!vpOx|SN7g{HRt zweEgNJXK{h>An8|tvepMYvQQvmI-w0TXblN-}3G<9(jE5F`DfBSAU^+mq{A#m2S2x zZ?sO2bW2MiCvc8FfPX6Ru$qOAe5@5O_+ze3YL9BsKI4Q?<8dQBvs~4k zmaC}VHH-&yAU#hQ9{K5A4UUhk+fR!tfbQ8JBV~`*(zcYULL7=~?lFXE)So+p`^?W0 zcyCj(m`$joB+M{D>PX`wo|VVTrr$uD<&A(n3Gdds>+NRqMb+bq{{a0Vt@`izuupEi zyHZcB=$e!=W;x`!W-Mi6!0X06D{6DBqOPuGQWRw;t-VZWZLZchq|{_+)Gu)G_Snyt8!=zJ3y-JPjD)g<;8;9#<$kq zUQ_`ha9it1N?g`x9AcxRISFl5cN;0poyj1NtzES6e~7hvHowy>XSjwh^^&3%P=2+c zWYgM1CH=IC7yz%#Gq@hSSJM9g0KO{QX_{+zTu38rSD3hZv7=pR`jyv*uOEDt60}T%rsX&|K9!yaF{6%6LrE>srZWRu2Gf?Q z?9N;4)>FKjCOK2`f;x|{YS?W*Mi45QgBI#N4Nnh-H5>aW@8K#IAHIO)uzS{H*o$Kt zs|hzAho|FS>uI|tchD$mAUz=&*j)uK1 z<_RN_qM1M^raDzY;m5hP^CMrGGrOPZTT`myz4k=Xle|%jrE4Het0>53$p@jwYU=eb z8rP6X^*P#nt z9xXMf2_%-vN_w86sx|30YYn=Oe_HP3)AwCG7ILe|eK-cV+l?kGRASoBfcwA7g#L>KsvcOEmWguq++^&DCjsNsXp~QclQ?HCEz6wL+x5g zH=;6Lq!!l~cGo6&l(9T?&jzYX4aC#=;w_<0NIAi-_RC(lVQ2R37NxF1s@Hh%G1y;SXv$iVsQkOk;k%Lj#>X$G`N1RX* zimK9EnOa9;Na>2`N=cD*VrbqQg#vCL``_bNzSn4&N>Rqz>Kf8B`@&1-xTi}IwPiAek~aKkQ)q`xGn`F688}HZRa(kLa;J~2 zb(49}sFDbiZ(as!hWSL^b=+l;^~kDIY|YY+#xq&S2HbtCizaqLXwA)KI-KAk2 zsaR>|V9}44J?gpI-5EM@=+1UVm|>4JzI@>;t-gxFKbj$sGrmu%X1>X9AdK43oDF;{n_oFl}b^1*oO(Pv}Co$?f}IL zCPA%vG`L93vEJRls3Ne9+(!QZAwBz5b3|N~JCx1O%12!Ol;J#KMk?*jm|$ZVVB33& zgjwxC?x2IvgGJ&yRZOmKS0zs%(k#e*s!1V;9E|Z$EM3i3g#Pg|QzUK0MrT}c)|6YZ z=QQQAk%U*YNDB({SnRb4$RwUCUrzAd%-FSY& zZA2Ly%v03%6$~C@Hgn0O{{TpeLh9V}`qpjDyWCnOW{o(s>jjadh9Qs-;ZUiF?T-Lr zszp7_V-dFptyzxP%Em@;zO|mcSg(DNXq#MynlRuV=Za;{l=kv@$^cdyu%?~m+F%Bo zr(9Jze4{7HAuCB=p-mzLRsfj63*6Mmmh!;OBLKso^``m77)QGZdg6(a*CfVC#a_Kg zWFg_m2a3>>?29N!$sh{JcM-1DTx91pEw$VbWXv}=YHeL;N|l+Z^8gtX?LAFRbPdEm zZ}LJ%9v9`P+g-ex)htvGDGo;*{&jlKOTD?YW|C1HC*~lU$5pJ2x6v6beH^N)7ijeq z;96;MYNVdI6-c+6C)t(3RX7H^e+&4JQ}D#sCrc5xpBahFiW>m>XQ8DvB^SuG9XA&w zrtF2X)x1NY*-ExjHM-o5u18iLnW%K#QW)-FI>J1VD+0<63lFUqdX1mh)#fHif$7)^ z#GAw-Tr8?Kfxr}$T$9kz%|~W$>xRy4Vp(+5@*;D(H3vIK*0%gVbFXRF(dzb&?JQ(4 zZck6fxLa*n-|Y)1`G_Fk*I5sX?yb^9Obx(v?O9GT<>W}HNpo{t{W17G`$9vk!xo9+ zcJeNi05>0}y?tMAsaah{uecrDf)85$erNF|>RqMu6EaGK5Ww@reMj)e_NUeKNaND{ zP^MT%8~wkfesPY-oBi$?e2=}s=MlfN#6Ow($t0G<6%kKKT7yn;OfQNS4{mbhAxv=R%fJY8a|sE@3sN4 z*Cx5y?KGx{#WJq%L0L9lFS11a`*rpI0QJ@;tK!`?a`1t>^sYrlwfR-griEHAJlYxF zC(#6U#kBYVZ(&{;s_I&nngrn~t zF*^8~Pu)I(lUVq5;ubMoBF4?fVO)l@@Y}+gf*CYxupg)d*Mr=o(fs)!^{m-7ZC23k zidS5E8v1H@cMpa~1x&((pw=paerWaGF56o0wcpt%RxxK89E#*TV`Vth%b_7<`Kvob z_@k=n@~k?M^Wx7txi#q;-Oi8V-5hFKQ>sf4C6xMpb==c~yFBb#!{%9Bso|xpHA(Mo zr=3IK-BL)Rl34c(fLR84U^~@Vbm;8v!Mk0m4ho+C0IgWH)#r_pIA>t#l?Oj`Ri)9a zH7UwnUE#1;xjp?Wo0>}e?EH6<4%~}T>2;)it>d`JjYe?2{l1lEEo)A?#hP2JaRZ&8 z;J4J&w*Cvey0meofjb_Lp4E+e;T=y+21|xj$5Eg2%|+?UOP1AGtMce+{jW#6YloN= zl&M&fA%R?`lRmGfi7s^JF}h?H9kW(pYppKv1dFw#$0yRSUd=Ep)<{+dI3)i75v^>N zoeq6l=Dw(Hyjr!TvtC|AlO(-lZh1cbwPhohPMtp09>XU%#xe~$9XjUj+xN2wg0@ya zdf(P=wUI0rI;>%i0x9;aoUZI_qh)f%u7~#hP1nNew+kG8W0&Dqj(VQem8$7*{ekZ# zyc5nz-4-#ArBb}RhfltBNeYHsouv2bI5iA+5!?W{+D}93DJV|-_t0{j8s$q;oNy(^ zp5A!cu@Sf)`Sj+mf5JZ!BzM|V?U8u|930kn*{|lk+Z-=9U=$J$s9ow;j$Ucp zmy@w$$>%lnUZ?Q?0OFRhJW*-ZMJ16$NgAGc&H}o#yZLsKWz2kLO7vMQ3gHn7L-Jy6(I=@J8Xh<b%~QCj*=p#*aWok0L_E2X>9lH`(ulg2BYz0fDKPce=&UqQ_o zD>KZClpWFE+iLn^D$d9Y5ypB|YySWk>Q<8G*s6oj04vTWy=dTDb>NQmX2uCGnPW~# zti6<0vOSpMXD@OGd8YXP09CcR`$-}5!3Vu;U3^g8gf@$g;2tZVweYNlFEKei#dSJY z!);SiiDyV~hxm_5^XCjD`KEg?r;L<&W2qnZt(E$%-GnOJc+`bjUONX&j&Q}?mW zeG%Y~0_plGNcA$3<%ZyU4l~}pPgnSJ;XAVWqE8Z|Fba#*4m#J)RK`)GQ)JnHUksVgcit^G^`` z9(%zgniR2@fpX|rXOG6aX=CWi-IF}(m6Z}5XR`UD>yg^JjUz#_)U1n1 zI9D8yYMd8$IxXW{k&&IQr<&2d)8ckpy%Eb6T(XYdxvySwlhXF{Jebo;?XQt3@Rx^V zUotza>>n^4YmJe#Xk~P0--CdCYoxQ&Bh}VHMooYoAZ?r5V~> zvXf?VifZvzMs=JPmk4E0Hjbd4YAs7rv6jeMSpuXUa%y{BB z{!-)I=Cw|rHRU50&2Cg?h|ED{w>*)Y)0$YOlzpg>-nH4-*xG2&NN@grI2Ful_ZF8x zGzqlU(Vsl5m&}(QRjFIeduarR4!r@Xp6w=KBgjrV067((48B(mH!19B5p|N?xz9@1 zl|97o(75I~_itUuzMpyl=hRx`)CG$FkOYsMtM$$%HfVOG{LZ5)Xh2O}RtS#xr- zTyAqp+bsN~j2!SurpKZZR7p5Md953JXx&Je@ZN^1T&21lvA6mB>t1Jl3|j7oKZjxm zCR~9~O<`*kAPg228(hYmD-u7?y)~`^zo@HR{mIj!Em_%#jv#L>haGAGVH!>4$&lIW zP<^IM^pHtW#KUcAk6EG#rc$oodNq zze-kY9FBxlUD#Gd7e!XU;C7~9dezoY&5=pv{Xfs+Pni|UkN?&F7uq(89D)LxzPj5j zu(N~ZQPlS}YWB^{3X!S$;;2icPi~_lsIQ$)4|D9TC9uAG?cds{ytt5@b*ZE;w_xqs zp$cR<3!2?nPUGWMY@GX6rJQq?F{dHBRSk^4F50~WQW;e4+B?<~YT6BMO?78+8*LnM zS&SuAC96_U)7%`l%TZ3VBAAs9)7;iEea#x)#uu9?J#$-DP?t|K=OG7rC(}~i-DYO_ zo3$*H${-fqhc#{AVl8dV_t?`Aye(ddU~Q@Vs+&lxp*G_k>U(|OZutKIhnlCcCB;iq zC@wIo@7Al8Nv9?;wMpPpTI@*N4aTQPw?b#!7aafvLHfR{D>OT;hR0(y)XQrKGBG#+ za!LAkt9s9cwY?dleP-5MWq-P4Qn?=0Yr+06yzr-q;MVP76G?2_lrYH1uHWLP#y=B$ zQPmex@gCLK07%dPsy>zG%2>sUjW|JaTUUK=*(0_v!#q5vDL#AXt*733)KIL4WLQN5 zxvJL>Bp)lBsq{bndVy|1!*;6EPLAC3UXo3-iMiiE3jMGs|}fH14w(;oJkUlG3mu?`B5tELCC6Q zRT!^I^SQZ=t!;uSuB}g#owiD0tsRz=q|VFJfzvG7>oLOeZHJnuiEa zNyg_yYE-AKIav1CKWOh0LmkM3E^cwjeONI;N{LqsxGOK&0+N% zE7d~Bt4}n>!%lPRxA@cH`5|V`*7P~9c3+BmJCecr^Itf;ah2)LdRBap7HkeZD&v8n z9gj{q?m9YMA3)!HMAGL{6}dIW&*JSd*_n=hmE_M8ZTgyt<2-|2j4<@ztc@j|;^n$W zq1||^PjQ7A{{TN)$(L4w=wU}%;hQ{zQ^b=u%AM=4ktBH&%&|2zlQpka*r}I(52bWI z6bQ*Hd8JMPxFrcmXw0KH$8>sSt=_R^IiE)@3IWLW ztoz7*+6~+@7kuS|9`(#$c)L=)T<2~E4&ExM)f4PtUn<1sAoZ-HMq9IaSBuc>zTaVU zAh^|mmE#x+I%1;L_4x0gNaKh_6eo;VCuw;jF#rRR)YRtsc`VMJR2AG#>ety>+`-mu zG<5d5g|@Rg=MgIPZ>3YbyZdIKu+MVO0vr#ea}ixf1Z@JIym3#rLh}F)Ggs`AcQbWc zTACWokCgsoWU`)w_aB{DvuClA3H5m0A>1*+8C>&&T6S`!-Tket!5bC8IrPnbkoa@L zUIX!$hL^+o^}K&Fe&$>cl;feVl+5#LS#Kn3MYS&752C-)4YH1b-Xuf zx-53~?vXCyRBp~W&%I;a{9o{0oK5FnmI@Ebryt6^8^&J&H4g^rGD$Y()5{%>ynwaD zUifRncA;X2_l(0iHSx4^ye1>}u&XI2srB`b{;#3Vh{+iz_1k800dPRByICI}UjWM>u6T-^PtR-O`a=OlI& z`VJQbLk%u!FtfF<)c8zBF18_Btgf$n62{izSi>kAx}QT>8r`m>_kMg5HuXO#9+kOm zJ+x^cxx+U>>MFn6%Sy4smH_ASuW?@YJciR&x$WNsJQc3^^6B*bTKVl^QOjc|pIX=W zqwsUbJ_end3&*+B+x}W7I3D<}FX4~HNjxXwm6BjYGJft%`WpLzJW&nxppdfN>GO_M zob3j`HOE)QVDa4UDZ%u&^{M(E4-bdMV7XMEIzKYk!yQbXz{1oyAHp;=;R#GUjP7%g52bcmhlw={t0=7Jkoj>R zlzLSwtKT)_xx17|F^{EnN1bZUX>#;DCs5L7Nyq@4@l)8_YC3#^}84PF8Im%?x#DHx(-#w4NmKW$n?CWO1Itx{Eun9kc_IhSC6mw8Kh+P`zLW6JkL+~&GaZezObEtblZx|e%aT6jG|#NTQH^x@>oj#A3H}q? zc%Mds_r!PeTTgP^S83>Y;~i^LSN)m%SEKm7Qqt|(&Bjc-yLujL%_jJ};!8W#eJ^79 zXDXw$dq;;pI{51Kqk~njdG}+G4?Qd5t4A@W-AG!`ria$To&lv!ce?AT=KeqU2C-@K zU)doOLU7qUF~xXgmY4mQ#>)COvzQES1LY&$zWkd{_=V#;U$a_TEEaMdB9FTigiFk{`ml3*~1`f`0p?Zq? zxK*cKDRX?$^R+O}n{ws(7Cd#}T}#5Q8%SU+=HXl)TIKaGhJG^8bcNQhr$H2-FberK z(pZ0LYA~jcuiUtXK8jCDud3;qln5cXmFBe{aOWc*oo!Fr!qAkL-MPU$G$j3^i?^=F zoWpd;qC2G$4|>$I@b$*J8MOE?2fqTV-f8y|Geu_`Ke(@6_7*nW+{doWeo&HR=8(@JxCp zqaC%(%P?>lvi8kn-gs`!F4EskV*9cA*6Oq*lR15bOYCt=E1)YX^XpFAy|*#qy)k?x zsN1-X;@rOMU}Kt+KM&}yF@o(3Og{?C+d=GXu+eB4(j);DqJZ}my0l)9b|FNU=Ul(R;0 z>q!rWZlpgTud<@>K9>c_jL2m9NgkEu+TNX}ubn-Mgt(2d<$Bh1@!ao~9+S(xoOz~r z>^=^-knQej+jud@%H#^tlf-gBww{&I8gi6BOXxBPOX0~y; zffdPK+h4=GC*O+R8q+ZMtlNv`n2<(u#cv47JF||eqSc^sgf{FFYP7)a&TDO=Ngs-e zOHWR7U1yTxb09{nIXJ~H(zL~?&fbH)H|(ybE7FUR$jr~1Zbxbvm~-2>tv|9z3D0_A zw3m>2cB13tWdYA_DTO*#pV;IhW;)f8qhGW;)+Pu)iyVG*xsu(7E84~)VNchOrAT=> z>MNv!!kUzz9iRiwYWVPt&UI)-JU>?mkD@QCg^?r8_` z;;mlzOHzVUAK8e1rxla0Pn(spl{s6PBhHLcw<@%$Ycre$z^_BJ(kF2xt@3$hF`q;H z=@UrPR@J08Aalahy2)rI30UIb(hTtz^Auq8?@yCRkN#Lb>~^jH03y{0Wa=u#%y-wU zf*WSsdJr+1J*1gDhaDnA8*Gs_kx}N``@iS%uD*W`Yf=!?Dk;ZuGf;oRF{*k;`HHnP z*Tfu^dL#eV{w1|v6h|~ZMXKRexk+^ir@flyv zxas1s)t#wLKSOGiE7I1aS~<}-mNzIIRk&{_of+f+g`|?r9fGOjy;hnRw_hy}2D=(e z!8s!IdX0p!Gslmbb4V+gH!ASGNUAQV=Wy#<_D*H9DZ2ua zYh;cv>E5wTCuUPktD^}~7l*2UXFh3Ut@Ec`O5uW$nsZxilVfrIezknvnV!8)cv!rW z3=EnMlHg^lq#+?WL(L>=(h_sWTAp?XJ8Py%H26`RdRM3DdLz#Okzsc2z%|Zkk8vE# zAzXqw*Q)qW;s&STc#WFEj*N5j4wdF&XIq!|YVXwbaPg%n{`OWzJ?DmRpiv=+rbk29 z`PT`l{{W+Wpc3AKy#vHvHPp3ho$kuJy6hFoJSK7;PD$@kOWJa$RTWp+b2Ukv-I%#` zF5rPmjzea>DhqpfEC?zIBbvdJPl9jtV+Yc@RZG25lcd$PIE$7Y2U?(a0ys6_Md8b< z$u?U?T&T&#W!-5qTVzVOBdv7Cq&6^cmhBv-8M+FA29$pE;+?!N;28aYMtJK zv5IYF`@@O_nJ?D2*qP*96IKk_mrDC6P zPi+jg)Mc6@Z6b_z^`^q89QmUHq+xC)0P$CDX0&p#M!3gXdu)$A+);09DztI+BdumN zgqyZ*-HN)u>pZwHbKa_}O%&`G=f-pOt4WBuf*9f`zk9t~No|d=hAuJIvl8CmMAt4k z&lKn`Bz(uWZfe@lMu4}C^Koq$+P!g1l!afkq4pU60QJ=A4gICNEL@{?6&{?h%`(R? z0Mzuj7!%*$G{3vboac)A^WewEAFsP zJXg+UI64^GKFYLDa|4R2gQ@K)KU4Ov!}`yMyhWrPGgCykwwQCA9A~|JL;FMi&-$N- z3;3VF&eB>ETVnHoYv->6e0K3Cf#h3z?b5=&y@h>6;P2Ys!*OeN^6N2+i#9lxCgfoI z8vL6Nm*%-XCDp?&Xm;rT0N|ge;xV~qX@~Z8-z>ZJN5a}^6|3F{d#L1LyRHpv@2!lktdl!i zeKyYj08H|Xstf}IvB>RQ&BDPYqeAC01A$dwGRrpGb~$g>wlB2nO*-65c*Un|W1cH0 z#iy$+wAInd-S}rpk{$m5eo{Fe_2-&~ik6LnHjdToi1twhbbed4dLP3rJHc9yiu{ih zm2_diIO|nbx7F!Nt5>&T=~MQ#6*TR0*TQ!hudhBL z{>(lZ@qBNpcn=ZG225iB{utqfd~4!Q0Qkqi(wKEib_l=5xyR$_Tr@G*JZw2>O3$<0 z_G3d8fvfE*tFDis^iGd?;mtZl)h*x?9ymPLd3o`JQnz$4TE`Fo1t%5pzL(>d)9jYl zxIgE;dN!M`X}W|dcdDGXrg~O&FmZ31s=4-bGb&Xh%T3vzUU)m>W}x@+N370cwPT4k z_7%};emvKu*5iYuG7lEZpQ;XPHXHqd<)@{qfv5^+oBIllaH-=6UM9zlvOKS-gsZE zM*R0bG2!r)!`nQ%)J3EtCD*F{6@qm8y9mgQgx2S(n*Ad2o`d0;bSD}tAkR1|NbC5F z*UGT`7S(TLWQt9V)CN5MRo6o@r%^do(C4XzbnK$8dGCca{{RJPI#unir)RQxBO*52 zo@>b;Qq>Z|eJ;xAq!S#pVDM|yb-ga@!y4q8X0-~+BF62wJbLk0WcYKT_^(bnmxb;G zcPrK+YuAlR5Ps5IO4{y^E_7vCa>I4gW5BOGEvwtcCSX}lBOKKmxveb`BbA78?_Txc zopSrZ*W*j^WwG*y8DWq0=HscZ~{@H^K{XW-lGW;4esiIb2IU#Hf+YvE3nqWF5^WY>X)XwER*K_1nu z;|~+~P7A?zr$@F>dB@#9g>X+5QdRwpD7L$qtTcIjq?wuU>rx-^lSVv8BxuXqRH$Bk`dzp|BJorwXu20ZkykGyHAUTXKvccn&h#G5Nv3$l5LhnI?bkT#URmR>hZ@&}XKiOgB*3R2uVK%C?2>7FjP{o5asQZf&{dNFW=lw%)zs_*D|a-}J9xyteA`+A%w zh&&9wD9)vzL`0-z<9|V=EH%IO$#QhQ1Yxm-F=|KV~EE3g)Scr3kj!v@lbX_fhkU*~TRp9OIg` zYopy=Foq-~lYw7c_&dNlE$@hA(oLCo@qsILu4l!+53jr@uRVpe!U&v_jh=(h*PZ>L zPE+hM%Hq9!|ZB zls!o1wJ#)+^Ho@U8*0dsa-fWVKGieCR=uC!&}$7!*Z%;AGxW~_87vv>UJ zjrN6e3{7RLO0X#8C#`x`hv7dK>o=mqTDn_A9ORmCr>v-x5&g2aa|s>s(d`r zZ5H0f!KRpUOP(t~M)-{-z0NdiIHG6yMgZ^WTQxe7bBr7Hb_mmiWT7c9k|K{v@ruYb zeH%(KzA{cQJ?fL_el*r0I*skbHqn;aaz;V##eGTPuZNyK@uX`V-lmpuO1qBS3=eAZ z?-PE^{wOfr-)I`dcaYl>SQf@e>&1DLv2=Z>C0Qi>8~jeX*h%vHk!>Ckci_zjbcv|y z##xBqL^28Ey;ZUB{)qxCaoNdnH*MYh>gR%WOJ4{0j8Csy3$G!tQ1UVjeTm_36zIPa zJW~w1MvWD;D4BNtX1zRBYSb3Z#@pQ^hYy6KSKfLv;w?@K*yh?Se4V|v^!nE2uY|l` zu16jGb3~Et;|u}E(!TNdh49P68m5Ilqv4ysBH;lNES#x7TKNLs;l{7wvloapSN{M+ z-}7v4qdluEJ~kEU^DQ4{X|`RL8LG<|fHmF{Fk zsX=3Ul8$zq*W6wW`1htmqQPT05wu_ojw|7R?7YYwy=$_!wzKk8M+d!mSnM4fWE5(x zk3$QJsfCl3GxS4C`0=KZ^4uIA#OL|fb7|sVhMqbfWAPx0KQT!26a$lkjIX_ZVJ@2^ z5#&lh`t+-D%X4ONCCeyZLth6xf{T|m3An!K`uOKlapkK7(boJ)()4Wu#dF!{4~uj2 z08VzFO7l}H2{F4Ptu4&v>fp#pYj;%h;-RxgxoJdU2R1pp6!-a=_5X44r@N*c6oQN8L3(}BL}5K zs2RXLy3>kE;=9ziwlkd63Og_wm2b#1zqCR8C`S+~bK<;NX&A*luuQdi> zlxJ>z>deqZBmAc$(y1&saw9wnX)6J0I2ocQGk^_TK|5_Ea4FDf8l=`n_6XR-y-#oF zP(*H>Mo$3tsdnyOt8-3k*~}}2CmxlLBQcjVoD*FY#-@VJ79|Ad81$}U;#Zl&IKlL+ z;=GG(BJ>(?op;7h6}w??ro{^-##%<`DwVy(y{)=7{&ULqr`~H;(iC`?44qA6)SiZm zl@=}s_Dn(gGY%^a^(kdsx#>%LsT=tePTAug)h(u-6y!vQB-JF6yo}p#L>AH{zcJ>z z-98wkWmf#_mYUGnOXY*jbk;7BqRAnwr>fB29%n&!rU+vglQ|tLGRn=SoG2Ls(w}>( z*iR4%kU;gtL89tuXL1-4bJ+7-X-z@g?`|CFbUM2&cl!o*i6X|~-ndiJ)AOwDXGVfOtg?3LnpEu>Cf23S@=ht}dHm(xA^^Z0>lj-N!H+(* z++E6$+Y53&Ui=JJ3cMmrDCWHww`0zd*Kw?(c!X-a3ZT|(4)x}=$!Ng<{{WLt#@O)R zbk>R3ZdzPjHvZb(S{c)J?uMA`Q!~fF&q~gp9%fgyT4<+uhJIBwO)bgiWH)!OJa|FC ztU-Az0~UDn_pNy?-Zt3}UiDeDmW@oMxjfdf7Vjy_LFD}my;j?T2a{AT}2>_;m1t>0Q%~080Z#nQoXIbQA958M;^6$ zU39$aVf%n%6_quG-<7s@%B^Hh@i7s@oDQa+DwgD3t&d8u(=2>O8*bh@ii*p_8YPA2 z+9bmmj&Yw#^Qf;b>}1_&<#Ib8{=Ia1eeB**c>!X3*At~A?w*HIs+w0?orU(O^IbIB zao8AOWA&_8x4VKtcG);NAk_M7im?&_Eu8hhslL^3JC=}-m(=I^)`?p~Hu<$H+<2o{ z)fl4U1?k0Gn)*nrL7IKd&8&&0i!|wii6MxubbC z%+bYnb0J_jP)$-3-dxOWoqBW6N`ax7Q+Yos^c8m5WOo-bFkj)tXsn34Dcr*}_>D%^ zUbPBZ+$r3>YpjaWK`F*Ip605_6p+5;JH~rb=IARkA)e;TY?9@C&OWuXDt(R>jzNOs zKY^-x#mZhq8j*%y&ZE+x)jUIg4w-MdS3fF&*YK>}t<1_#TO!*;BWUHcIcCQKq|}Aw z{PF#;QR+r~3 z*#7|f>s#Ge=h^Ce7sH>A7g|NMcADV9PB-_jp=`W;@YBaxwef1K(MW`Oset2;PWAC6 z)ux%Mf{LLS`c?*=;OV8lc;Mm>P^&-y8Mr@rJ7fS6*jq%$b4MTG+i^r z*D?55?zG*NuQ!iSxQGaxoPVEs_N_|B2;Jb5=Oc`o;;uX=J?x;`uLr$-)fdZ3Fq^%) zpD9!LlAJWTrJ}Z(6e)9i^A_l}@173TyfO%q{D|Wpi8b?vso~l57*(#MEHYfx5%%_OWhj8H)T#U`u?@`T6nKUYXrTzfF#6cXaFC?obg&#e-$+Atu^%BMNuN> zBPEaHj@9GS#p7U?EPYL-hr_vQ&xdU_`z>9+vAk;sUzKGe9My^Z3F7|%0Wk4Dgsi+o@u@o@5$`l;iedhEU}e$BGp5BN&FA_v$Qaf8S6s$MA2 zd{{VL@BahL%b9i{hLqSZasR| zP8fO1o^iC7Msz7uUhHLbYuo%R@hbRvyj5ntSx=jeKdolL<8;@o{{Z0`@v01=!peGs z>zeCi_>ZpXIy>kxuK0+-0Gi?aKW(J=v&1G1ZZjG`0FK6m?Ijsc61Lf`&MN$|nvEX2 z+}HmAgmV7?!*iwOs2A2po;e^_t=M?W!`jZCx=n;j9E4$k;E&~Af2qT$_y*{~YR2N! z^j7rewQ0*|;NKBxKic+l3p>^7GlQD*>POo}PNu7)U#c~XWl!CY+J^JxjI>Xs`KILw%e7WM2?<#d4uS)Z+J5kVd&_(UrNaOpbKDB>M@g9`75;mo6 zB+$PKSaVVAB^K{?jon1mwr8;TcgG$IOG}6&hk9Ho1wiBWMJ&t=f4Dk*Yc++0n~HY7c5JGsq~yNj zYc1!C^qbqOVLDnbCq41#Du?_cdLF7jv~|r@r5N1Er<(CSV_Vj4ubAqt`-$W?75BgF zso>uV`0B=3wS-94KCalw`ewT^YwB2kthei9&!pjqoT(-EpD*}lOVPDY6aAONmJM`J z@EB+D=Dlmi9}IpipD$U{G~Xd3Tmu;BE7yJ^M{n>u#gglu7qCgg0;)jaPpwPv^Y+EM zu@c#M(iQUMJu%bp{*~fS5sa#%iosJ-jk>*j^gHNKpp&Csc4xynuAAdsCPOx|WJb?9 zl(#YmK$=ABKEo1L_vgyi*+VK?gO&=w1f#Og=AN48;EcXDl$s zrZe@gVxpy56O3ib)~|2C%a(DpTNi|RSx>A1HC%F*-x0ET`eS$L}5G)-Glr(As<$wFW^w$Xqr6&;I~ku7ARq)4%8Bet-RXn%m*#rK&UB>RushGeyDP9U=j&fNILb|O=zV+~Qum9Rj45X!hk{3OSrND{TBd>K zznll@(zkU@D%Q@-Os5QgT71ejiz3ciqaF=S;mV~$J4q^+kn(w~wwZTqUZB>rOdWQ& zN_L9Y!FOvBKs>a}xy4B4IsV}1rA>C}utuGFn#^emK2&i@E4z|iTX4TH=QVKONgP7j zK0RwbJFTnp^%U2VM{^(@YPntY7RIXxjlV&f$YjQL8fcx%ZDYXgQ8d9DlUAc)@1bQa zCUwX({f!PIZoO%dUb@H#`?Yk;vJop0$7+YoeMNI5p;U6 zdNSjJQ-f6|$l6a8ehZl5U?dp=opU@>k;VmHw;;xhubH=TBCW|9N3@QnrjJfZN0}L` zQds3zV{=24ZWbdRO2f+YSM=vtGb$V!%(#M7Kz%AGUPbvuV%64#rtWG_JomR@o0_&% z$lC$O7^_-sw4PU-xZ|3sb32wK^&XXzv-eS@+SQXSv;q>{NbgrPTc|C`ErN5$`OQ+Z z)Dk;qSpmu8j%s}_JAbpYN%!-|`ORZ3oz3Fa(?-R^&CS$jAEjp9U9nF#S0%IFw9NKt zZLr`l^gRAm&fLl#GauzyK|ZMJovpbe+=-$a`e&YKi^-Bt-3Y(B$SiXVWB#6Ex~arhm^`=I$;Tm&=S}oL0@wt9f-8Mo=;DSTcFuc;2}7rv&qpH8jou2jX*0_?Cw6tGC+%woE&$h)0!8#X(wzaTxYFO zOZ0T+^G$$j=qc0N$dXAYRNhE-Ba=AaDIjRY94A&+}F@`u8r+IZUagRfumrFMpYgJ+tWb;?NFRkBr zPgaLnwTPJ^Z1OlY(CP#GDlpb~e>bCJg# zLxvn={&nc_>iS`@WRT?J0OqRc7Is&$F^>jLyq=$iYT-8sT3rP?YNYPEoX7Spz_b3) zW3(RG{{ZXOY`3?TkuqB378obmyLZ$h)2BcelUZ*0mwae^vJH2ySR&ifjTqsk)$3w+iZ5HO*F)2{tpnA-?T5(hzEYjdSd6M(?xtHmQ(${JokJu9McjG46z zMM^s}=$#W*TgXh8>;jKk?X)k5cA8ZFR+AZb9B##YwQ=G%ctkKAt6q=cEn?o<*;{Vs z`ewY!v?Ivek@dKI!MP=Ux}QHAlv7Sy&03XcPYhym5_X5mGAC}z!Za*6E zO*-#d)g(z4LmKw}5I+j~Q$Vor?vvret7{j^X9FWG{WD)1iR7oGSKNK|3ck@fNuJl> zS#4}kL2({W`v)iao+|#W;DfE|2IAco5Z#FkPv_peb3ypa;N2GA5)mYasb=Hy>T9dF z_}^ixM9^rFsgbx+I{yGM$F+QYN?5qmbs-jekgGyTMac1wA9ybB!MexxjjnN=k=Xrt zs=D`!d=cX<9xWpF-|Y8`g$<4~^{(f{x1KlgBI5Gj5e}@wwRxqF!z;fNUpUj-WRdbo z;{sHZ)umm@ULu1y@XG0M+^pkI_oq~iQf)2%PB3j zNJI_KQQMwB`qjlPzN=?y`YyP>Y)W?T9D(((Jn=t*tgRg{^tarnmQhvgXx5$TJzrFI zOPN#WhTV?Z$Hcx5&^$*i)`balJcEy|Wb3ycBJqB!CB}srL_fpp*0p>Usd!2ogg48v z)N_z9eU56bz2nd9jdp+dM^_QI0QRj^WUGGe4))#baXwW9d5~y5-KO z_ZNs4jH=agX-R5n_-9wpygzy+)M4`o^#K0>kzZA5K0o+%tk@;7wTdU> zJBMFxwe!}Cqv<{(xk%)>Hi_MPnpO$s^EyHRIye7Od@7PBJ&c z%A_yKu8&3WtkZaA)jUVx7?*1D$T=AH=D7a=4SZ|4*7YQVQL<9ZaB zkiHt%AuTcaIpmZ1)$KpwmxS9Bk z?-1JCs`z#i$P^6p#dB7{;wV*B@ETvE*z_pWqf#Ev5vxapc>CcGiM$UI&n#>h^Z@?= z_3Q1w**`&-LGXpnuj02=j&(R!KBKoa^A4-y9~}77;G4+8D|OF6PkMoGyj`WD>AHpI zlM3>`HV5UJ+YuZ_Ckn9jdl9D!wBZVi zn%xdY+&QHUE_>F4`#?)3lq~e^F+|6!5r8XZ+gtJ0za(?0NRLp+IUf1{0PEMw3*tMi zJ|l6XLZ^2i5!2~cJU8(-R9`Y3Qg=cQc7uWZ>zbwkE6Rjd=E9ya-BYU1pgdvWEpu1J z=C^p)MLLB!z!m4vSe_e9v}qC}&hB$pJRR|V*F=^>dl9&fPQU)V*AwF}iI?{`E_^u2 z1Z~dI#y?u-qem8{?4-2svOB8Qq@nF1dk&l6j}Z8UPOo56(`e3eGsmrB!{JSmz5cVP zw5WuJSeu`E^ACp~5_PRMS*|%c!$&Jrm-jy+sV;+^8ih7S#p`)eg5^6UhC^7RMrn;?D!S`BR zYWste)DP)geTJhwjN)s42(6a!R-<(|TejY#r=@bqRp$3u(777-ikB8o3T9)2-`27W zYM*wuFD``Zv~a$aPCH{O3|7%?FL=e+%NPd@R~6M)U_907EHZEgO-S|v)i=q(t8z^c zVU>`%BZ_#oQn{0wva@e}ql%VAQo(vruEkiT<$$Gt=}M)UK2w2OWf2pDoK*{!J3vnL zS~1u}`Ii+vv%GFFYPeF!Gsmc^<=F@z)LTLgD~VJ;Jl1muGH>i^@F@5h!%Vl7`w>#Y`1~D-N zfn0syk#3uJ%mM9P4UV(+SiFOkJu4zs7gr>T=2uO&b~KvMuWu78Zezi&s@@x9htJ$N ztSg%-k^v~_IjVw5S|D?PDYeYB6n)-?tkERyTphlcss=LI!K=D;mCg7u#@O#wbqjW} zpKf{dsZC#&=8ZdZSjm{qvIPdL!`sY1odkz$_Z4hf$#DjHV!9jB<`yM}Rz@$u9`wni z^W@zj>C&-oCzjoeWYtr0(!Sz(t5IY&jhWS>1=oR8rkHKO>r;8sOBjp{@z$ujL>Hd5 ztXFd)(%h!Pz0EQyT%EbVs-clrCY;Vd3=id3WHQ{z{CUkiS~+7YAs~TJTO*U1Gl5s+ zYgciP#-a8k&xp$&SwX<>R}dL-oR&4W_RAKvHtN4H9lBN}jLC0vxw#cPrqU}x*EmSo zS#ifo=i|A!wqo!xQ|b+LTBYE)e~@5$S2|P|D0l|6gSym}p>`X)iSqKXBi^q;Zxq0@ zcdBvR#)?KdRjA{dLGou5-r4~<(pq4C&(0}r?50KCWbGW(|rqn7B zxE+mC)9$CYPc{c2j^N!m=30k9dvo; z?FW-wLfy!X!N~WhCYLS0p{sCRR87VYswQNSNhtD!TZ!q_Im*f);_`sSkv21_qps5?Ys*kxNbYwU!>Y;O=%_E>I9F0s?R=)BHK( z8%Sdi-YLgJish?HNa2mKRS4oyxu?WborZrC!cwbS{MTMj) zvK}*l59eHE){ht10fXvorD-;W7Dj zuO{&agEhSmb;&=w>U@ZcZk(wm}b`UF~xws&P00P^4cV_v1Jd?vK;(mmo`BSd{h*8HbftPd%B)1d?Vo7pEh`;2{L4^#`^Z-kJhv8z9M*| z!}hY+S%UD7mvJ9odiUA>6X>GicY45sqgiz z=+>i%mn6OKQ=Y9iO>->|JkhVUOU-F@Ng)iuw`IC7ztX)*E4^1(*B0$3@)&%-E?E9u z>yglP8RXL5NXkOTIaN?tesz&!Z{qcw>V?~6ZIu|uAAuF=)PkFPD-j0NQnk-^@rd!= zo{Cc9dD_r(jO?!(p3cTiJua@DlyzX)agV64sk{&IC&NA?(HBG4TXdvsWOI^!oYv=! zJQ3i#Uk)F#ZHgxwvZtPZ9z}f2aWccp+0=!d`W~#Hs(aMsq50q92((+NbEcq|XSONf>R$>y{r{SA1zdmBw$*x-N*Rf7XuQSHPMlq6_dK9%g+pQ(lqWO^4ZT+^PcWgC%ERuVQ?Dg*pPS$4X%1!a( zIHq_vSF+RbXQ~g8xHZ(Od%V#+S;kVR>B-r2I9&tB4`FroeN8djHP-5yZkyseb9EbT z&*@9?JHpy2w;HyVzy=xM*Oh2KCX-Et?Tc~RwT~@G`&xQ4kK0QRCx^u9yPb`lpNaHX z{Eaa0iMbVT?Y=ku>HRAT!{T4q;xM{~$4u4l_*$v|06f;4gxkL%F~ng~vZ&Gj(*78o ziDIXqp?+)=+M53W>l&P$2l@4{pFX5n6bH!nsM<9E`&80>@6MS08nlSf9kPZ(Ph(RR z0Gw1-zj3~&`PA>#sC$&U40ddRnzIeWtI+nUuhyR*^sLEd_Ma=Xgtuc_8jG~B;r(kX zP5bRySHJ4NI>G!%Iv9dAQN4QADT!RN@M}Q-0IM~fanSuMu-JX1(nyRP=M?Omih{@P zwN2c81um?}l63&_+*A>$JesjTU&5h}pr{v7itHo+bg9?^{{V$mAL~V0KY#F~NlBD$ zCZSaVUOUx?>-bay{b;Sa0c;K9E$K{XkS=OO>G)9p0CuwI21{`!+S}5U2{J1Y{{Yuk z?w9xKXYh?0H|V>3&V*5=6S|*&{;d54N2UGx<$nRtz9XW$V)j@)2(CtH8Bl>;Wykz^ z{uRhvebfGZD1o&1H?%9c;6@P~b5Y)pGcrFc+>=o0A9Vd{d$09l=~({&%f?%8ks}eg z7_A$*Uf$3cxX7&I)~@Pb@#eou=#kEjZdYkkmpsx#8k$4*4M`7g!nLa@)kVEqvw|v$3R35cL-?!shF!VG_a!F)j90A67r!pywwmGIr z_s7-ljzCRjEa4X3Q1_X5# zx2G`+x3R35de)|&)KZ?sbRQ(I7-d6&&0<3=k-x}s&1=uqzaQmTiTD2i8nv;CHYWMh zvvu{Pw78Z4S~kXMWc!AohwdM(9SVCCS!S zR(_5D03UDl{{RZ=dk131vV5RpkAaHdM-87tUGBU0kK-g2fVLi+7e*R6e@&W!; zp)?T%01BaL{LU%~P%!huYzrboCa( z#@<=ic<)~S0O3CmX?IGCDsXXL8K!@)U#)!|;J&f`72x9=XVt3M;Xuf*P#48@s7WwEVj0Dkd4^$>s}|}ulV(90qp@2{`5}>{{XI% zynI9GpOBR@o-j!C~{(Vx?NH4J<_v_AUF6+HdqyGS_ll=PE z91=-1j;XfVx%1bEq?X41NbMbvXE+AEli~igeRrcZy}=GPk(1WEXT_iL5|X_`;lKQY zTj*=*E3460IA}NR+BnTH6!EciVhX-^KK1T?J@E#pG*yU6^6eaRj(w}cbpHVP9NYcx z>t3(p-}wZO_g1|8QckrynNe*X-`@%=fs{Nwu;vL`5!I~YUp~M-P{jvaCayiXa4}#RJ>FF z03icR@Yh*BmHH$25%Wp4RitO@Gs$BxYg4r6wRJun@g}WxWhJ!oZcVwxavmlB09Zb? zqv2omGXDU(Sk+0xQz~+js?ugmTK=h~U&xk{5Tx|&TqlV2OUbn_F`Eak713S)0LVrD zwZ?1T@&5o@!7pgEK5De3?ApImxYE2+tczn0o0Y~fSek~ZcOtPQeia6n{;d8LR`>22 zw33r4%1)ZQD_(1Ql-35;NeMyPxd_>T>T70y^$q@gD-}BaHQfY{pRC)GyB{0~?AfV) V%@@Dt{{YsdhpSZe)Ok;H|Jgg4CPDxJ literal 0 HcmV?d00001 diff --git a/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift b/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift index cb7b0ce..f734099 100644 --- a/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift +++ b/ios/native/CosmicRituals/Engine/PoojaVidhiCatalog.swift @@ -93,6 +93,57 @@ struct PoojaSourceNote: Identifiable, Hashable, Sendable { var id: String { urlString } } +/// A factual, derived preparation state. It deliberately avoids a single +/// decorative "confidence" score: readiness depends on concrete materials, +/// the practice boundary, and visible source context. +struct PoojaReadiness: Equatable, Sendable { + let requiredMaterialCount: Int + let preparedRequiredMaterialCount: Int + let optionalMaterialCount: Int + let preparedOptionalMaterialCount: Int + let sourceCount: Int + let practiceLevel: PoojaPracticeLevel + + var remainingRequiredMaterialCount: Int { + max(0, requiredMaterialCount - preparedRequiredMaterialCount) + } + + var requiredPreparationProgress: Double { + guard requiredMaterialCount > 0 else { return 1 } + return Double(preparedRequiredMaterialCount) / Double(requiredMaterialCount) + } + + var hasPreparedRequiredMaterials: Bool { + remainingRequiredMaterialCount == 0 + } + + var materialStatus: String { + if hasPreparedRequiredMaterials { + switch requiredMaterialCount { + case 0: + return "No required materials" + case 1: + return "Required material marked ready" + default: + return "All \(requiredMaterialCount) required materials marked ready" + } + } + let noun = remainingRequiredMaterialCount == 1 ? "item" : "items" + return "\(remainingRequiredMaterialCount) required \(noun) left to review" + } + + var practiceStatus: String { + practiceLevel == .priestRecommended + ? "Qualified practitioner recommended" + : practiceLevel.displayName + } + + var sourceStatus: String { + let noun = sourceCount == 1 ? "reference" : "references" + return "\(sourceCount) cited \(noun) · reviewed \(PoojaContentPolicy.sourceReviewDate)" + } +} + struct PoojaVidhi: Identifiable, Hashable, Sendable { let id: String let title: String @@ -130,6 +181,19 @@ struct PoojaVidhi: Identifiable, Hashable, Sendable { .joined(separator: " ") } + func readiness(preparedMaterialIDs: Set) -> PoojaReadiness { + let required = materials.filter(\.isRequired) + let optional = materials.filter { !$0.isRequired } + return PoojaReadiness( + requiredMaterialCount: required.count, + preparedRequiredMaterialCount: required.filter { preparedMaterialIDs.contains($0.id) }.count, + optionalMaterialCount: optional.count, + preparedOptionalMaterialCount: optional.filter { preparedMaterialIDs.contains($0.id) }.count, + sourceCount: sourceNotes.count, + practiceLevel: practiceLevel + ) + } + var shareText: String { """ \(title) · \(durationText) diff --git a/ios/native/CosmicRituals/Theme/CosmicTheme.swift b/ios/native/CosmicRituals/Theme/CosmicTheme.swift index 9dd647a..fda09ed 100644 --- a/ios/native/CosmicRituals/Theme/CosmicTheme.swift +++ b/ios/native/CosmicRituals/Theme/CosmicTheme.swift @@ -53,9 +53,9 @@ struct CosmicColorScheme: Sendable { /// Explicit semantic roles prevent an already-presented sheet from retaining /// dark system label colors while the user switches to a light app theme. /// Dark variants continue using the existing system semantics unchanged. - var semanticPrimaryText: Color { isLight ? onSurface : .primary } - var semanticSecondaryText: Color { isLight ? onSurface.opacity(0.72) : .secondary } - var semanticTertiaryText: Color { isLight ? onSurface.opacity(0.60) : Color.secondary.opacity(0.74) } + var semanticPrimaryText: Color { onSurface } + var semanticSecondaryText: Color { onSurface.opacity(isLight ? 0.72 : 0.76) } + var semanticTertiaryText: Color { onSurface.opacity(isLight ? 0.60 : 0.62) } var semanticDivider: Color { isLight ? onSurface.opacity(0.16) : Color.white.opacity(0.10) } var semanticBorder: Color { isLight ? onSurface.opacity(0.22) : Color.white.opacity(0.10) } var semanticHighlight: Color { isLight ? onSurface.opacity(0.08) : Color.white.opacity(0.20) } @@ -65,86 +65,86 @@ struct CosmicColorScheme: Sendable { var selectedControlForeground: Color { background } static let obsidianGold = CosmicColorScheme( - id: "obsidian_gold", displayName: "Obsidian Gold", - primary: Color(hex: "E5A97C"), - secondary: Color(hex: "7E8EAA"), - tertiary: Color(hex: "73A5AA"), - background: Color(hex: "08090F"), - surface: Color(hex: "111421"), - surfaceElevated: Color(hex: "1A2030"), - onSurface: Color(hex: "E8EAED"), - gradientTop: Color(hex: "08090F"), - gradientBottom: Color(hex: "130E20"), + id: "obsidian_gold", displayName: "Deep Sandhya", + primary: Color(hex: "F0B36E"), + secondary: Color(hex: "D66A47"), + tertiary: Color(hex: "9CB77D"), + background: Color(hex: "100703"), + surface: Color(hex: "211108"), + surfaceElevated: Color(hex: "321A0D"), + onSurface: Color(hex: "FFF3E4"), + gradientTop: Color(hex: "090301"), + gradientBottom: Color(hex: "260D04"), isLight: false ) static let midnightAurora = CosmicColorScheme( - id: "midnight_aurora", displayName: "Midnight Aurora", - primary: Color(hex: "8FD5C4"), - secondary: Color(hex: "A58FD6"), - tertiary: Color(hex: "E7B97A"), - background: Color(hex: "050E14"), - surface: Color(hex: "0D1A22"), - surfaceElevated: Color(hex: "162430"), - onSurface: Color(hex: "EAF3F1"), - gradientTop: Color(hex: "050E14"), - gradientBottom: Color(hex: "091620"), + id: "midnight_aurora", displayName: "Temple Dawn", + primary: Color(hex: "F4C574"), + secondary: Color(hex: "C85F46"), + tertiary: Color(hex: "D99458"), + background: Color(hex: "150806"), + surface: Color(hex: "28110D"), + surfaceElevated: Color(hex: "3B1B13"), + onSurface: Color(hex: "FFF2DF"), + gradientTop: Color(hex: "110503"), + gradientBottom: Color(hex: "351008"), isLight: false ) static let celestialPlum = CosmicColorScheme( - id: "celestial_plum", displayName: "Celestial Plum", - primary: Color(hex: "D7A6B8"), - secondary: Color(hex: "9DA7D9"), - tertiary: Color(hex: "E2C278"), - background: Color(hex: "0E0A14"), - surface: Color(hex: "1A1224"), - surfaceElevated: Color(hex: "261A34"), - onSurface: Color(hex: "F1EAF2"), - gradientTop: Color(hex: "0E0A14"), - gradientBottom: Color(hex: "160E22"), + id: "celestial_plum", displayName: "Kumkum Night", + primary: Color(hex: "E8A764"), + secondary: Color(hex: "C84F62"), + tertiary: Color(hex: "D7B56B"), + background: Color(hex: "140508"), + surface: Color(hex: "260D13"), + surfaceElevated: Color(hex: "39151D"), + onSurface: Color(hex: "FFF0E8"), + gradientTop: Color(hex: "100305"), + gradientBottom: Color(hex: "310811"), isLight: false ) static let cloudDancer = CosmicColorScheme( - id: "cloud_dancer", displayName: "Cloud Dancer", - primary: Color(hex: "755F4C"), - secondary: Color(hex: "4F7F83"), - tertiary: Color(hex: "956C88"), - background: Color(hex: "FFFEFB"), - surface: Color(hex: "FFFCF8"), - surfaceElevated: Color(hex: "F6F0E8"), - onSurface: Color(hex: "24211D"), - gradientTop: Color(hex: "FFF8F2"), - gradientBottom: Color(hex: "EDE6DC"), + id: "cloud_dancer", displayName: "Ghee Lamp Ivory", + primary: Color(hex: "98541F"), + secondary: Color(hex: "A43F32"), + tertiary: Color(hex: "5E7341"), + background: Color(hex: "FFF8E9"), + surface: Color(hex: "FFFCF4"), + surfaceElevated: Color(hex: "F6E7C9"), + onSurface: Color(hex: "2B1C0F"), + gradientTop: Color(hex: "FFFDF5"), + gradientBottom: Color(hex: "F3D9AD"), isLight: true ) static let mochaAura = CosmicColorScheme( - id: "mocha_aura", displayName: "Mocha Aura", - primary: Color(hex: "8F604F"), - secondary: Color(hex: "4F7D63"), - tertiary: Color(hex: "556F95"), - background: Color(hex: "FFFDF9"), - surface: Color(hex: "FFF8F1"), - surfaceElevated: Color(hex: "F6ECE2"), - onSurface: Color(hex: "241C17"), - gradientTop: Color(hex: "FFF8F1"), - gradientBottom: Color(hex: "EDE0D4"), + id: "mocha_aura", displayName: "Sandalwood", + primary: Color(hex: "8A4C24"), + secondary: Color(hex: "9D4A38"), + tertiary: Color(hex: "667647"), + background: Color(hex: "FAF0DF"), + surface: Color(hex: "FFF9ED"), + surfaceElevated: Color(hex: "EDDBBC"), + onSurface: Color(hex: "2C2116"), + gradientTop: Color(hex: "FFF9ED"), + gradientBottom: Color(hex: "E8CEAA"), isLight: true ) static let celestialMist = CosmicColorScheme( - id: "celestial_mist", displayName: "Celestial Mist", - primary: Color(hex: "336F86"), - secondary: Color(hex: "725EA1"), - tertiary: Color(hex: "A56F4E"), - background: Color(hex: "FDFFFF"), - surface: Color(hex: "F8FCFE"), - surfaceElevated: Color(hex: "EEF6FA"), - onSurface: Color(hex: "122027"), - gradientTop: Color(hex: "F0F8FF"), - gradientBottom: Color(hex: "E0EFF7"), + id: "celestial_mist", displayName: "Lotus Ash", + primary: Color(hex: "7D4A32"), + secondary: Color(hex: "8A4F58"), + tertiary: Color(hex: "5C7055"), + background: Color(hex: "F4F0EA"), + surface: Color(hex: "FCFAF6"), + surfaceElevated: Color(hex: "E8DED2"), + onSurface: Color(hex: "28201B"), + gradientTop: Color(hex: "FBF8F2"), + gradientBottom: Color(hex: "E4D6C8"), isLight: true ) } @@ -176,12 +176,12 @@ enum CosmicThemeVariant: String, CaseIterable, Identifiable { var displayName: String { switch self { - case .cosmicDark: return "Obsidian Gold" - case .nebulaBlue: return "Midnight Aurora" - case .vedicSaffron: return "Celestial Plum" - case .lunarSilver: return "Cloud Dancer" - case .solarGold: return "Mocha Aura" - case .auroraGreen: return "Celestial Mist" + case .cosmicDark: return "Deep Sandhya" + case .nebulaBlue: return "Temple Dawn" + case .vedicSaffron: return "Kumkum Night" + case .lunarSilver: return "Ghee Lamp Ivory" + case .solarGold: return "Sandalwood" + case .auroraGreen: return "Lotus Ash" } } } @@ -199,205 +199,61 @@ extension CosmicThemeVariant { } } -// MARK: - Animated Starfield Background +// MARK: - Ceremonial Background -private struct StarData { - let x, y: CGFloat - let r: CGFloat - let baseOpacity: Double - let twinklePhase: Double - let tier: Int // 0=tiny, 1=medium, 2=large with glow -} - -struct CosmicStarfieldBackground: View { +struct RitualSanctuaryBackground: View { @Environment(\.cosmicTheme) private var theme - @Environment(\.accessibilityReduceMotion) private var reduceMotion - - private let stars: [StarData] = { - var out = [StarData]() - var rng = SystemRandomNumberGenerator() - // Tier 0 — tiny, very dim - for i in 0..<90 { - out.append(StarData( - x: CGFloat.random(in: 0...1, using: &rng), - y: CGFloat.random(in: 0...1, using: &rng), - r: CGFloat.random(in: 0.28...0.58, using: &rng), - baseOpacity: Double.random(in: 0.12...0.38, using: &rng), - twinklePhase: Double(i) * 0.41, - tier: 0 - )) - } - // Tier 1 — medium - for i in 0..<65 { - out.append(StarData( - x: CGFloat.random(in: 0...1, using: &rng), - y: CGFloat.random(in: 0...1, using: &rng), - r: CGFloat.random(in: 0.58...1.05, using: &rng), - baseOpacity: Double.random(in: 0.30...0.60, using: &rng), - twinklePhase: Double(i) * 0.73, - tier: 1 - )) - } - // Tier 2 — large, bright, with glow halo - for i in 0..<22 { - out.append(StarData( - x: CGFloat.random(in: 0.04...0.96, using: &rng), - y: CGFloat.random(in: 0.04...0.96, using: &rng), - r: CGFloat.random(in: 1.05...1.75, using: &rng), - baseOpacity: Double.random(in: 0.55...0.90, using: &rng), - twinklePhase: Double(i) * 1.09, - tier: 2 - )) - } - return out - }() + @Environment(\.accessibilityReduceTransparency) private var reduceTransparency var body: some View { - if theme.isLight { - LinearGradient(colors: [theme.gradientTop, theme.gradientBottom], startPoint: .top, endPoint: .bottom) - .ignoresSafeArea() - } else { - darkBackground - } - } + GeometryReader { proxy in + ZStack { + theme.background - private var darkBackground: some View { - // Twinkling is deliberately capped at 20 fps. The motion is extremely - // slow, so display-refresh rendering adds battery/GPU cost without a - // visible quality benefit. Reduce Motion freezes the canvas entirely. - TimelineView(.animation(minimumInterval: 1.0 / 20.0, paused: reduceMotion)) { ctx in - let t = ctx.date.timeIntervalSinceReferenceDate - Canvas { gfx, size in - // 1. Background gradient - gfx.fill( - Path(CGRect(origin: .zero, size: size)), - with: .linearGradient( - Gradient(colors: [theme.gradientTop, theme.gradientBottom]), - startPoint: .zero, - endPoint: CGPoint(x: 0, y: size.height) - ) - ) + Image("RitualSanctuaryBackground") + .resizable() + .scaledToFill() + .frame(width: proxy.size.width, height: proxy.size.height) + .clipped() + .saturation(theme.isLight ? 0.72 : 0.92) + .contrast(theme.isLight ? 0.88 : 1.02) + .opacity(imageOpacity) - // 2. Nebula clouds (3 layers, slow breathing) - let breathe = 0.5 + 0.5 * sin(t * 0.12) - // Blue-violet nebula — top right - gfx.fill( - Path(ellipseIn: CGRect(x: size.width*0.42, y: -size.height*0.05, - width: size.width*0.65, height: size.height*0.40)), - with: .color(theme.secondary.opacity(0.045 + breathe * 0.018)) - ) - // Warm gold nebula — left center - gfx.fill( - Path(ellipseIn: CGRect(x: -size.width*0.10, y: size.height*0.30, - width: size.width*0.50, height: size.height*0.22)), - with: .color(theme.primary.opacity(0.038 + breathe * 0.012)) - ) - // Teal nebula — bottom right, very faint - gfx.fill( - Path(ellipseIn: CGRect(x: size.width*0.60, y: size.height*0.68, - width: size.width*0.45, height: size.height*0.18)), - with: .color(theme.tertiary.opacity(0.025 + breathe * 0.010)) + LinearGradient( + colors: readabilityVeil, + startPoint: .top, + endPoint: .bottom ) - - // 3. Stars - let twinkleRate = 1.209 - for star in stars { - let twinkle = 0.55 + 0.45 * sin(t * twinkleRate + star.twinklePhase) - let opacity = star.baseOpacity * twinkle - let pt = CGPoint(x: star.x * size.width, y: star.y * size.height) - let r = star.r - - if star.tier == 2 { - // Outer soft glow - let g1r = r * 4.5 - gfx.fill( - Path(ellipseIn: CGRect(x: pt.x-g1r, y: pt.y-g1r, width: g1r*2, height: g1r*2)), - with: .color(Color.white.opacity(opacity * 0.055)) - ) - // Inner glow - let g2r = r * 2.2 - gfx.fill( - Path(ellipseIn: CGRect(x: pt.x-g2r, y: pt.y-g2r, width: g2r*2, height: g2r*2)), - with: .color(Color.white.opacity(opacity * 0.14)) - ) - } - // Star core - let finalOpacity: Double - switch star.tier { - case 0: finalOpacity = opacity * 0.22 - case 1: finalOpacity = opacity * 0.42 - default: finalOpacity = opacity * 0.88 - } - gfx.fill( - Path(ellipseIn: CGRect(x: pt.x-r, y: pt.y-r, width: r*2, height: r*2)), - with: .color(Color.white.opacity(finalOpacity)) - ) - } - - // 4. Shooting star — appears every 24 seconds, lasts 0.72s - let shootPeriod = 24.0 - let shootDuration = 0.72 - let shootPhase = t.truncatingRemainder(dividingBy: shootPeriod) - if shootPhase < shootDuration { - let progress = shootPhase / shootDuration - // Ease in, peak, ease out - let eased: Double - if progress < 0.15 { eased = progress / 0.15 } - else if progress > 0.72 { eased = (1.0 - progress) / 0.28 } - else { eased = 1.0 } - - let sx = size.width * 0.74, sy = size.height * 0.07 - let ex = size.width * 0.32, ey = size.height * 0.44 - let headX = sx + progress * (ex - sx) - let headY = sy + progress * (ey - sy) - let dx = (ex - sx), dy = (ey - sy) - - // Trail segments - for j in 0..<16 { - let frac = Double(j) / 16.0 - let tx = headX - frac * dx * 0.20 - let ty = headY - frac * dy * 0.20 - let tOpacity = (1.0 - frac) * (1.0 - frac) * eased * 0.85 - let tr = max(0.18, (1.0 - frac * 0.72) * 1.7) - gfx.fill( - Path(ellipseIn: CGRect(x: tx-tr, y: ty-tr, width: tr*2, height: tr*2)), - with: .color(Color.white.opacity(tOpacity)) - ) - } - // Head glow - let hgr = 4.5 - gfx.fill( - Path(ellipseIn: CGRect(x: headX-hgr, y: headY-hgr, width: hgr*2, height: hgr*2)), - with: .color(Color.white.opacity(eased * 0.22)) - ) - let hr = 1.6 - gfx.fill( - Path(ellipseIn: CGRect(x: headX-hr, y: headY-hr, width: hr*2, height: hr*2)), - with: .color(Color.white.opacity(eased * 0.92)) - ) - } - - // 5. Vignette — dark edges - let ve = min(size.width, size.height) * 0.38 - gfx.fill(Path(CGRect(x: 0, y: 0, width: size.width, height: ve)), - with: .linearGradient(Gradient(colors: [Color.black.opacity(0.42), .clear]), - startPoint: .init(x: 0, y: 0), endPoint: .init(x: 0, y: ve))) - gfx.fill(Path(CGRect(x: 0, y: size.height-ve, width: size.width, height: ve)), - with: .linearGradient(Gradient(colors: [.clear, Color.black.opacity(0.48)]), - startPoint: .init(x: 0, y: size.height-ve), endPoint: .init(x: 0, y: size.height))) - gfx.fill(Path(CGRect(x: 0, y: 0, width: ve*0.65, height: size.height)), - with: .linearGradient(Gradient(colors: [Color.black.opacity(0.28), .clear]), - startPoint: .init(x: 0, y: 0), endPoint: .init(x: ve*0.65, y: 0))) - gfx.fill(Path(CGRect(x: size.width-ve*0.65, y: 0, width: ve*0.65, height: size.height)), - with: .linearGradient(Gradient(colors: [.clear, Color.black.opacity(0.28)]), - startPoint: .init(x: size.width-ve*0.65, y: 0), endPoint: .init(x: size.width, y: 0))) } } .ignoresSafeArea() + .accessibilityHidden(true) + } + + private var imageOpacity: Double { + if reduceTransparency { + return theme.isLight ? 0.22 : 0.44 + } + return theme.isLight ? 0.42 : 0.78 + } + + private var readabilityVeil: [Color] { + if theme.isLight { + return [ + theme.gradientTop.opacity(0.84), + theme.background.opacity(0.72), + theme.gradientBottom.opacity(0.86) + ] + } + return [ + Color.black.opacity(0.34), + theme.background.opacity(0.30), + theme.gradientBottom.opacity(0.62) + ] } } -// MARK: - Glass Card +// MARK: - Ceremonial Surface struct CosmicGlassCard: View { let cornerRadius: CGFloat @@ -406,7 +262,7 @@ struct CosmicGlassCard: View { @Environment(\.cosmicTheme) private var theme @Environment(\.accessibilityReduceTransparency) private var reduceTransparency - init(cornerRadius: CGFloat = 20, accentBorder: Bool = false, @ViewBuilder content: @escaping () -> Content) { + init(cornerRadius: CGFloat = 26, accentBorder: Bool = false, @ViewBuilder content: @escaping () -> Content) { self.cornerRadius = cornerRadius self.accentBorder = accentBorder self.content = content @@ -416,27 +272,28 @@ struct CosmicGlassCard: View { content() .padding(16) .background( - theme.surface.opacity(reduceTransparency ? 1.0 : (theme.isLight ? 0.88 : 0.24)), + theme.surface.opacity(reduceTransparency ? 1.0 : (theme.isLight ? 0.96 : 0.82)), in: RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) ) - .glassEffect(.regular, in: RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) .stroke(borderGradient, lineWidth: accentBorder ? 1.0 : 0.6) ) - // Specular top highlight .overlay(alignment: .top) { - RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) - .fill( - LinearGradient( - colors: [theme.semanticHighlight, theme.semanticHighlight.opacity(0)], - startPoint: .leading, endPoint: .trailing - ) - ) + LinearGradient( + colors: [.clear, theme.primary.opacity(accentBorder ? 0.62 : 0.24), .clear], + startPoint: .leading, + endPoint: .trailing + ) .frame(height: 1) - .padding(.horizontal, cornerRadius * 0.5) + .padding(.horizontal, cornerRadius) .padding(.top, 1) } + .shadow( + color: theme.isLight ? theme.onSurface.opacity(0.08) : Color.black.opacity(0.24), + radius: accentBorder ? 18 : 10, + y: accentBorder ? 8 : 4 + ) } private var borderGradient: LinearGradient { @@ -526,6 +383,7 @@ struct CosmicSectionHeader: View { CosmicIcon(name: icon, size: 15) Text(title) .font(.subheadline.weight(.semibold)) + .fontDesign(.serif) .foregroundStyle(theme.semanticPrimaryText) Spacer() } @@ -920,7 +778,7 @@ struct CosmicScreenHeader: View { VStack(alignment: .leading, spacing: 4) { Text(title) - .font(.title2.weight(.bold)) + .font(.system(.title2, design: .serif, weight: .bold)) .foregroundStyle(theme.onSurface) .lineLimit(1) .minimumScaleFactor(0.82) diff --git a/ios/native/CosmicRituals/Views/MuhurtaDetailView.swift b/ios/native/CosmicRituals/Views/MuhurtaDetailView.swift index b985caf..ea82e3a 100644 --- a/ios/native/CosmicRituals/Views/MuhurtaDetailView.swift +++ b/ios/native/CosmicRituals/Views/MuhurtaDetailView.swift @@ -21,7 +21,7 @@ struct MuhurtaDetailView: View { var body: some View { NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { VStack(spacing: 16) { header diff --git a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift index 2181f72..47214fd 100644 --- a/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift +++ b/ios/native/CosmicRituals/Views/PanchangExperienceViews.swift @@ -106,21 +106,11 @@ struct PanchangExperienceHome: View { let showYogaDetail: () -> Void let showKaranaDetail: () -> Void - @Environment(\.cosmicTheme) private var theme @Environment(\.accessibilityReduceMotion) private var reduceMotion - @Environment(\.timeZone) private var timeZone var body: some View { VStack(spacing: 10) { - HStack(spacing: 6) { - Image(systemName: "clock.badge.info") - .foregroundStyle(theme.primary) - Text(panchang.referenceDisclosure(in: timeZone)) - .foregroundStyle(theme.semanticSecondaryText) - Spacer(minLength: 0) - } - .font(.caption2.weight(.medium)) - .accessibilityElement(children: .combine) + RitualCalculationSeal(panchang: panchang) Group { switch mode { @@ -159,6 +149,67 @@ struct PanchangExperienceHome: View { } } +/// Keeps the calculation contract beside the result instead of burying it in +/// Settings. This is disclosure, not an accuracy percentage or certification. +private struct RitualCalculationSeal: View { + let panchang: Panchang + + @Environment(\.cosmicTheme) private var theme + @Environment(\.timeZone) private var timeZone + + var body: some View { + VStack(alignment: .leading, spacing: 9) { + HStack(spacing: 8) { + Label("Ritual-day calculation", systemImage: "checkmark.seal.fill") + .font(.caption.weight(.bold)) + .fontDesign(.serif) + .foregroundStyle(theme.semanticPrimaryText) + Spacer(minLength: 8) + Text("ON DEVICE") + .font(.system(size: 9, weight: .bold, design: .rounded)) + .tracking(0.8) + .foregroundStyle(theme.primary) + } + + Text(panchang.referenceDisclosure(in: timeZone)) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + + ViewThatFits(in: .horizontal) { + HStack(spacing: 12) { calculationDetails } + VStack(alignment: .leading, spacing: 5) { calculationDetails } + } + } + .padding(.horizontal, 14) + .padding(.vertical, 12) + .background( + theme.surface.opacity(theme.isLight ? 0.92 : 0.76), + in: RoundedRectangle(cornerRadius: 18, style: .continuous) + ) + .overlay(alignment: .leading) { + Capsule() + .fill(theme.primary) + .frame(width: 3) + .padding(.vertical, 12) + } + .overlay( + RoundedRectangle(cornerRadius: 18, style: .continuous) + .stroke(theme.semanticBorder, lineWidth: 0.6) + ) + .accessibilityElement(children: .combine) + .accessibilityLabel( + "Ritual-day calculation, on device. \(panchang.referenceDisclosure(in: timeZone)). Lahiri sidereal system. Time zone \(timeZone.identifier)." + ) + } + + @ViewBuilder + private var calculationDetails: some View { + Label("Lahiri sidereal", systemImage: "circle.grid.cross.fill") + Label(timeZone.identifier, systemImage: "globe.asia.australia.fill") + } +} + // MARK: - Daily transition timeline struct PanchangTransitionTimeline: View { diff --git a/ios/native/CosmicRituals/Views/PanchangLimbDetailSheets.swift b/ios/native/CosmicRituals/Views/PanchangLimbDetailSheets.swift index cacab96..8dea1e9 100644 --- a/ios/native/CosmicRituals/Views/PanchangLimbDetailSheets.swift +++ b/ios/native/CosmicRituals/Views/PanchangLimbDetailSheets.swift @@ -11,7 +11,7 @@ struct TithiDetailSheet: View { let detail = TithiDetail.from(tithiIndex: tithiIndex) NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { VStack(alignment: .leading, spacing: 20) { // Header @@ -123,7 +123,7 @@ struct YogaDetailSheet: View { let detail = YogaDetail.from(yogaIndex: yogaIndex) NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { VStack(alignment: .leading, spacing: 20) { CosmicGlassCard(cornerRadius: 18) { @@ -187,7 +187,7 @@ struct KaranaDetailSheet: View { let detail = KaranaDetail.from(karanaIndex: karanaIndex) NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { VStack(alignment: .leading, spacing: 20) { CosmicGlassCard(cornerRadius: 18) { diff --git a/ios/native/CosmicRituals/Views/PanchangView.swift b/ios/native/CosmicRituals/Views/PanchangView.swift index d691ae8..c381e9b 100644 --- a/ios/native/CosmicRituals/Views/PanchangView.swift +++ b/ios/native/CosmicRituals/Views/PanchangView.swift @@ -26,7 +26,7 @@ struct PanchangView: View { var body: some View { NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() VStack(spacing: 0) { if selectedTab != 3 { LocationContextBar(manager: locationManager) { @@ -854,7 +854,7 @@ private struct ThemePickerSheet: View { var body: some View { NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { VStack(alignment: .leading, spacing: 16) { // Experience layout (independent from color appearance) diff --git a/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift b/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift index 75333ff..11f8d9f 100644 --- a/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift +++ b/ios/native/CosmicRituals/Views/PoojaVidhiViews.swift @@ -81,6 +81,11 @@ struct PoojaVidhiLibraryView: View { .font(.caption.weight(.semibold)) .foregroundStyle(theme.primary) + ViewThatFits(in: .horizontal) { + HStack(spacing: 8) { libraryTrustPills } + VStack(alignment: .leading, spacing: 8) { libraryTrustPills } + } + Text(PoojaContentPolicy.householdScope) .font(.caption) .foregroundStyle(theme.semanticSecondaryText) @@ -88,7 +93,14 @@ struct PoojaVidhiLibraryView: View { } } .accessibilityElement(children: .combine) - .accessibilityLabel("Pooja Vidhis. Twelve offline household and preparation guides. Family and temple tradition takes precedence.") + .accessibilityLabel("Pooja Vidhis. \(PoojaVidhiCatalog.all.count) offline household and preparation guides. Family and temple tradition takes precedence.") + } + + @ViewBuilder + private var libraryTrustPills: some View { + PoojaMetadataPill(text: "\(PoojaVidhiCatalog.all.count) guided rites", symbol: "hands.and.sparkles.fill") + PoojaMetadataPill(text: "Source adapted", symbol: "books.vertical.fill") + PoojaMetadataPill(text: "Offline", symbol: "lock.shield.fill") } private var categoryPicker: some View { @@ -194,16 +206,20 @@ struct PoojaVidhiDetailView: View { @Environment(\.cosmicTheme) private var theme private var preparationProgress: Double { - guard !vidhi.materials.isEmpty else { return 0 } - return Double(preparedMaterialIDs.count) / Double(vidhi.materials.count) + readiness.requiredPreparationProgress + } + + private var readiness: PoojaReadiness { + vidhi.readiness(preparedMaterialIDs: preparedMaterialIDs) } var body: some View { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollView { LazyVStack(spacing: 16) { hero + readinessSection practiceBoundary preparationSection materialsSection @@ -234,8 +250,8 @@ struct PoojaVidhiDetailView: View { GuidedPoojaView(vidhi: vidhi) } label: { Label( - vidhi.practiceLevel == .priestRecommended ? "Open ceremony guide" : "Begin guided Pooja", - systemImage: "play.fill" + beginActionTitle, + systemImage: beginActionSymbol ) .font(.headline) .frame(maxWidth: .infinity) @@ -251,6 +267,20 @@ struct PoojaVidhiDetailView: View { } } + private var beginActionTitle: String { + if vidhi.practiceLevel == .priestRecommended { + return "Open ceremony guide" + } + if readiness.hasPreparedRequiredMaterials { + return "Begin guided Pooja" + } + return "Review guide · \(readiness.remainingRequiredMaterialCount) required left" + } + + private var beginActionSymbol: String { + readiness.hasPreparedRequiredMaterials ? "play.fill" : "book.pages.fill" + } + private var hero: some View { CosmicGlassCard(cornerRadius: 24, accentBorder: true) { VStack(alignment: .leading, spacing: 14) { @@ -306,6 +336,56 @@ struct PoojaVidhiDetailView: View { PoojaMetadataPill(text: vidhi.practiceLevel.displayName, symbol: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "house.fill") } + private var readinessSection: some View { + PoojaSectionCard(title: "Ritual readiness", symbol: "checkmark.seal.fill") { + VStack(alignment: .leading, spacing: 12) { + ProgressView(value: readiness.requiredPreparationProgress) + .tint(readiness.hasPreparedRequiredMaterials ? .green : theme.primary) + .accessibilityLabel("Required material readiness") + .accessibilityValue(readiness.materialStatus) + + readinessRow( + title: "Materials", + detail: readiness.materialStatus, + symbol: readiness.hasPreparedRequiredMaterials ? "checkmark.circle.fill" : "circle.dotted", + color: readiness.hasPreparedRequiredMaterials ? .green : theme.primary + ) + readinessRow( + title: "Practice boundary", + detail: readiness.practiceStatus, + symbol: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "house.fill", + color: vidhi.practiceLevel == .priestRecommended ? .orange : theme.tertiary + ) + readinessRow( + title: "Provenance", + detail: readiness.sourceStatus, + symbol: "books.vertical.fill", + color: theme.secondary + ) + } + } + } + + private func readinessRow(title: String, detail: String, symbol: String, color: Color) -> some View { + HStack(alignment: .top, spacing: 11) { + Image(systemName: symbol) + .font(.subheadline.weight(.semibold)) + .foregroundStyle(color) + .frame(width: 22) + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.caption.weight(.bold)) + .foregroundStyle(theme.semanticPrimaryText) + Text(detail) + .font(.caption) + .foregroundStyle(theme.semanticSecondaryText) + .fixedSize(horizontal: false, vertical: true) + } + Spacer(minLength: 0) + } + .accessibilityElement(children: .combine) + } + private var practiceBoundary: some View { CosmicGlassCard(cornerRadius: 18) { HStack(alignment: .top, spacing: 12) { @@ -344,8 +424,16 @@ struct PoojaVidhiDetailView: View { VStack(alignment: .leading, spacing: 12) { ProgressView(value: preparationProgress) .tint(theme.primary) - .accessibilityLabel("Materials prepared") - .accessibilityValue("\(preparedMaterialIDs.count) of \(vidhi.materials.count)") + .accessibilityLabel("Required materials prepared") + .accessibilityValue(readiness.materialStatus) + + HStack { + Text("Required \(readiness.preparedRequiredMaterialCount)/\(readiness.requiredMaterialCount)") + Spacer() + Text("Optional \(readiness.preparedOptionalMaterialCount)/\(readiness.optionalMaterialCount)") + } + .font(.caption2.monospacedDigit().weight(.semibold)) + .foregroundStyle(theme.semanticTertiaryText) ForEach(vidhi.materials) { material in Button { @@ -521,7 +609,7 @@ struct GuidedPoojaView: View { var body: some View { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() ScrollViewReader { proxy in ScrollView { VStack(spacing: 18) { @@ -582,10 +670,41 @@ struct GuidedPoojaView: View { .tint(theme.primary) .accessibilityLabel("Pooja progress") .accessibilityValue(isComplete ? "Complete" : "Step \(currentStep.number) of \(vidhi.steps.count)") + + ViewThatFits(in: .horizontal) { + HStack(spacing: 7) { + guidedBoundaryLabel + Spacer(minLength: 6) + guidedSourceLabelView + } + VStack(alignment: .leading, spacing: 6) { + guidedBoundaryLabel + guidedSourceLabelView + } + } + .font(.caption2.weight(.semibold)) + .foregroundStyle(theme.semanticSecondaryText) + .accessibilityElement(children: .combine) } } } + private var guidedSourceLabel: String { + let noun = vidhi.sourceNotes.count == 1 ? "source" : "sources" + return "\(vidhi.sourceNotes.count) \(noun)" + } + + private var guidedBoundaryLabel: some View { + Label( + vidhi.practiceLevel == .priestRecommended ? "Practitioner-led boundary" : "Household adaptation", + systemImage: vidhi.practiceLevel == .priestRecommended ? "person.badge.shield.checkmark.fill" : "house.fill" + ) + } + + private var guidedSourceLabelView: some View { + Label(guidedSourceLabel, systemImage: "books.vertical.fill") + } + private var currentStepCard: some View { CosmicGlassCard(cornerRadius: 26, accentBorder: true) { VStack(alignment: .leading, spacing: 18) { @@ -852,7 +971,7 @@ private extension PoojaCategory { #Preview("Pooja library") { NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() PoojaVidhiLibraryView() } .navigationTitle("Pooja") diff --git a/ios/native/CosmicRituals/Views/SubscriptionViews.swift b/ios/native/CosmicRituals/Views/SubscriptionViews.swift index a79b359..e1a9b31 100644 --- a/ios/native/CosmicRituals/Views/SubscriptionViews.swift +++ b/ios/native/CosmicRituals/Views/SubscriptionViews.swift @@ -7,7 +7,7 @@ struct SubscriptionGateView: View { var body: some View { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() switch store.accessState { case .storeUnavailable(let message): unavailableView(message: message) diff --git a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift index 66fa93e..8ed6318 100644 --- a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift +++ b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift @@ -548,6 +548,33 @@ final class CosmicEngineTests: XCTestCase { } } + func testPoojaReadinessCountsRequiredAndOptionalMaterialsSeparately() throws { + let vidhi = try XCTUnwrap(PoojaVidhiCatalog.all.first { $0.id == "griha-pravesh" }) + let requiredIDs = Set(vidhi.materials.filter(\.isRequired).map(\.id)) + let optionalIDs = Set(vidhi.materials.filter { !$0.isRequired }.map(\.id)) + + let initial = vidhi.readiness(preparedMaterialIDs: []) + XCTAssertEqual(initial.requiredMaterialCount, requiredIDs.count) + XCTAssertEqual(initial.optionalMaterialCount, optionalIDs.count) + XCTAssertEqual(initial.requiredPreparationProgress, 0) + XCTAssertFalse(initial.hasPreparedRequiredMaterials) + XCTAssertEqual(initial.sourceCount, vidhi.sourceNotes.count) + XCTAssertEqual(initial.practiceStatus, "Qualified practitioner recommended") + XCTAssertTrue(initial.materialStatus.contains("\(requiredIDs.count) required")) + XCTAssertTrue(initial.sourceStatus.contains("\(vidhi.sourceNotes.count) cited")) + + let prepared = vidhi.readiness(preparedMaterialIDs: requiredIDs.union(optionalIDs.prefix(1))) + XCTAssertEqual(prepared.preparedRequiredMaterialCount, requiredIDs.count) + XCTAssertEqual(prepared.preparedOptionalMaterialCount, min(1, optionalIDs.count)) + XCTAssertEqual(prepared.requiredPreparationProgress, 1) + XCTAssertTrue(prepared.hasPreparedRequiredMaterials) + XCTAssertEqual(prepared.remainingRequiredMaterialCount, 0) + let expectedMaterialStatus = requiredIDs.count == 1 + ? "Required material marked ready" + : "All \(requiredIDs.count) required materials marked ready" + XCTAssertEqual(prepared.materialStatus, expectedMaterialStatus) + } + func testPoojaSearchCoversPanditGPTPublicStarterPillars() { XCTAssertEqual(PoojaVidhiCatalog.search("Lakshmi at home").first?.id, "lakshmi-home") XCTAssertEqual(PoojaVidhiCatalog.search("Griha Pravesh").first?.id, "griha-pravesh") @@ -721,6 +748,22 @@ final class CosmicEngineTests: XCTestCase { } } + func testThemeNamesExpressAUniqueCeremonialIdentity() { + XCTAssertEqual( + CosmicThemeVariant.allCases.map(\.displayName), + ["Deep Sandhya", "Temple Dawn", "Kumkum Night", "Ghee Lamp Ivory", "Sandalwood", "Lotus Ash"] + ) + XCTAssertTrue(CosmicThemeVariant.allCases.allSatisfy { + !$0.displayName.localizedCaseInsensitiveContains("cosmic") + && !$0.displayName.localizedCaseInsensitiveContains("celestial") + && !$0.displayName.localizedCaseInsensitiveContains("aurora") + }) + } + + func testRitualSanctuaryBackgroundAssetIsBundled() { + XCTAssertNotNil(UIImage(named: "RitualSanctuaryBackground")) + } + func testSelectedControlForegroundRemainsLegibleAcrossEveryTheme() { for theme in CosmicThemeVariant.allCases.map(\.colorScheme) { XCTAssertGreaterThanOrEqual( @@ -764,9 +807,32 @@ final class CosmicEngineTests: XCTestCase { @MainActor func testPoojaPrimaryScreensRenderAtPhoneAndAccessibilitySizes() async { + let delhi = context(2026, 7, 24, latitude: 28.6139, longitude: 77.2090, timeZone: "Asia/Kolkata") + let panchangValue = CosmicEngine.getPanchang(context: delhi) + let panchang = NavigationStack { + ZStack { + RitualSanctuaryBackground() + ScrollView { + PanchangExperienceHome( + mode: .ritualNow, + selectedDate: .constant(delhi.localDay), + panchang: panchangValue, + showTithiDetail: {}, + showYogaDetail: {}, + showKaranaDetail: {} + ) + .padding() + } + } + .navigationTitle("Panchang") + } + .environment(\.cosmicTheme, CosmicColorScheme.obsidianGold) + .environment(\.timeZone, delhi.timeZone) + .preferredColorScheme(.dark) + let library = NavigationStack { ZStack { - CosmicStarfieldBackground() + RitualSanctuaryBackground() PoojaVidhiLibraryView() } .navigationTitle("Pooja") @@ -787,6 +853,7 @@ final class CosmicEngineTests: XCTestCase { .environment(\.dynamicTypeSize, .accessibility2) .preferredColorScheme(.light) + await attachSnapshot(panchang, name: "Panchang Calculation Disclosure") await attachSnapshot(library, name: "Pooja Library") await attachSnapshot(detail, name: "Lakshmi Vidhi Detail") await attachSnapshot(guided, name: "Guided Pooja Accessibility Text") diff --git a/ios/native/PRODUCT_IDENTITY.md b/ios/native/PRODUCT_IDENTITY.md new file mode 100644 index 0000000..6413877 --- /dev/null +++ b/ios/native/PRODUCT_IDENTITY.md @@ -0,0 +1,38 @@ +# Cosmic Rituals Product Identity + +Cosmic Rituals is the ceremony and practice app in the Cosmic suite. It must feel related to the suite without looking or behaving like a recolored astrology, numerology, or Vastu product. + +## Product promise + +Help a household practitioner choose the correct civil day and location, understand the limits of a general reference, prepare safely, follow a clear ritual sequence, and retain the provenance needed to confirm regional or lineage-specific details. + +Accuracy, scope disclosure, and safety take precedence over visual drama. + +## Ownable qualities + +- **Ceremonial, not galactic:** temple light, sandalwood, kumkum, ghee-lamp ivory, quiet flame imagery, serif ritual headings, and warm opaque surfaces. Starfields, orbital motifs, chart-paper grids, and blueprint geometry belong to other products. +- **A lived ritual setting:** full-screen sanctuary photography may show a restrained household altar, diya, flowers, brass vessels, and preparation materials. It stays subordinate to content through theme-aware veils and never uses deity imagery as decoration. +- **Readiness before performance:** required materials, optional alternatives, practitioner boundaries, source count, and source-review date appear before a user begins. +- **Guidance with boundaries:** household adaptations are distinguished from priest-led, initiatory, fire, installation, and lineage-specific rites. +- **Provenance in the flow:** source and tradition context remains visible in the guide and guided-practice states, not only in legal or settings screens. +- **Calculated results with context:** Panchang results disclose sunrise anchoring, IANA time zone, Lahiri sidereal convention, and on-device calculation at the point of use. +- **Quiet modernity:** contemporary native controls support the work; they do not turn sacred practice into a game, prediction score, or spectacle. + +## Suite differentiation contract + +| App | Primary object | Visual grammar | Functional proof | +| --- | --- | --- | --- | +| Cosmic Rituals | A ceremony being prepared and performed | Altar light, warm ritual pigments, calm sequential surfaces | Readiness, timing context, steps, safety, sources, tradition limits | +| Cosmic Astrology | A chart interpreted from an ephemeris | Observatory, orbital geometry, chart contrast, celestial bodies | Birth data integrity, ephemeris method, chart geometry, interpretive provenance | +| Cosmic Numerology | A calculation traced from names and dates | Ledgers, graph paper, numeric notation, typographic hierarchy | Method-specific arithmetic, normalization trace, reproducible worksheets | +| CosmicVastu | A property measured and analyzed | Plans, compass/grid systems, spatial layers, evidence markers | Approved geometry, measurement provenance, rule-pack version, confidence per fact | + +Shared foundations may include accessibility, privacy, reliability, and native platform behavior. Primary metaphors, layouts, content surfaces, proof of accuracy, and domain workflows must remain app-specific. + +## Prohibited shortcuts + +- Do not use one generic starfield and glass-card system across all four apps. +- Do not present a confidence percentage for a ritual or imply that one sequence is universally canonical. +- Do not invent unsupported mantras, festival dates, remedial guarantees, or priestly instructions. +- Do not hide time zone, location, source, approximation, regional-variation, or safety limits when they materially affect a result. +- Do not treat a polished result as accurate until calculation fixtures, content validation, and runtime verification pass. From 603f5d1cd1f47fff0de8f90e24c69e1ed29b88bd Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 21:42:49 +0530 Subject: [PATCH 07/10] Record Rituals TestFlight build 6 --- ios/native/RELEASE_1.0_6.md | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 ios/native/RELEASE_1.0_6.md diff --git a/ios/native/RELEASE_1.0_6.md b/ios/native/RELEASE_1.0_6.md new file mode 100644 index 0000000..db35abd --- /dev/null +++ b/ios/native/RELEASE_1.0_6.md @@ -0,0 +1,50 @@ +# TestFlight evidence — 1.0 (6) + +Date: 2026-07-28 +Bundle ID: `com.cosmic.rituals` +Platform: iOS/iPadOS 26 +Disposition: uploaded, processing complete, available to the internal group + +## Delivery result + +- Source snapshot: commit `3a34bfd`. +- App Store Connect delivery UUID: + `93383fea-b2cb-4166-8c6f-ea993bcf8156`. +- Apple transport completed with no errors or warnings. +- The logged-in Connect web app showed upload status `Complete` at + 21:40 IST. +- TestFlight showed build `1.0 (6)` as `Ready to Submit`, attached to + `Cosmic Rituals Internal` with two invites. +- External beta review and external-group availability are not claimed. + +## Product increment + +- Replaced the interchangeable cosmic shell with a warm, restrained household + ritual sanctuary and six ceremonial themes. +- Added factual material readiness derived from required and optional checklist + state instead of a decorative confidence score. +- Kept safety, practitioner boundaries, tradition context, source count, and + source-review date visible before and during practice. +- Preserved Panchang calculation context, including the civil date, location, + IANA time zone, sunrise anchor, and Lahiri convention. + +## Quality gate + +- Native test suite: 50 passed, 0 failed. +- Release archive: `/tmp/CosmicRituals-6.xcarchive`. +- Archive bundle/version/build: `com.cosmic.rituals`, `1.0`, `6`. +- Release architecture: arm64. +- Strict signature and privacy-manifest validation: passed. +- App and dSYM UUID matched: + `F31DA204-04A9-3CB0-9986-14C82C4A2081`. +- No UI-test entitlement token, XCTest, runtime-injection, Guard Malloc, + Main Thread Checker, or debug launch marker was found in the release binary. + +## Known limits + +- Ritual instructions remain source-bounded household guidance, not a claim + that one sequence is universally canonical. +- Regional, lineage-specific, initiatory, installation, and fire rites still + require qualified local guidance where the app says so. +- External TestFlight review, physical-device VoiceOver, and extended energy + runs are not claimed by this record. From 957d21b4080b3f1a97a81014121990d83405932e Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 23:48:03 +0530 Subject: [PATCH 08/10] Keep TestFlight usable without StoreKit --- .../CosmicRituals.xcodeproj/project.pbxproj | 101 +++++++++++++++++ .../CosmicRitualsTestFlight.xcscheme | 103 ++++++++++++++++++ .../Engine/SubscriptionStore.swift | 22 +++- ios/native/CosmicRituals/Views/RootView.swift | 44 +++++++- .../Views/SubscriptionViews.swift | 2 +- .../CosmicEngineTests.swift | 28 +++++ ios/native/SUBSCRIPTION_RELEASE.md | 6 + 7 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRitualsTestFlight.xcscheme diff --git a/ios/native/CosmicRituals.xcodeproj/project.pbxproj b/ios/native/CosmicRituals.xcodeproj/project.pbxproj index 8f999d5..e594f27 100644 --- a/ios/native/CosmicRituals.xcodeproj/project.pbxproj +++ b/ios/native/CosmicRituals.xcodeproj/project.pbxproj @@ -294,6 +294,56 @@ }; name = Release; }; + C05B000000000000000000C3 /* TestFlight */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = TestFlight; + }; C05B000000000000000000C9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -356,6 +406,38 @@ }; name = Release; }; + C05B000000000000000000CB /* TestFlight */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 7; + DEVELOPMENT_TEAM = DJJ824N6RB; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Cosmic Rituals uses your location to calculate precise local muhurta times based on your sunrise and sunset."; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cosmic.rituals; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "TESTFLIGHT_BETA_ACCESS $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = TestFlight; + }; C05B000000000000000001C9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -388,6 +470,22 @@ }; name = Release; }; + C05B000000000000000001CB /* TestFlight */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = DJJ824N6RB; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + PRODUCT_BUNDLE_IDENTIFIER = com.cosmic.rituals.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CosmicRituals.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CosmicRituals"; + }; + name = TestFlight; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -396,6 +494,7 @@ buildConfigurations = ( C05B000000000000000000C1 /* Debug */, C05B000000000000000000C2 /* Release */, + C05B000000000000000000C3 /* TestFlight */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -405,6 +504,7 @@ buildConfigurations = ( C05B000000000000000000C9 /* Debug */, C05B000000000000000000CA /* Release */, + C05B000000000000000000CB /* TestFlight */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -414,6 +514,7 @@ buildConfigurations = ( C05B000000000000000001C9 /* Debug */, C05B000000000000000001CA /* Release */, + C05B000000000000000001CB /* TestFlight */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRitualsTestFlight.xcscheme b/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRitualsTestFlight.xcscheme new file mode 100644 index 0000000..eb07cba --- /dev/null +++ b/ios/native/CosmicRituals.xcodeproj/xcshareddata/xcschemes/CosmicRitualsTestFlight.xcscheme @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/native/CosmicRituals/Engine/SubscriptionStore.swift b/ios/native/CosmicRituals/Engine/SubscriptionStore.swift index e3a5d10..52c1173 100644 --- a/ios/native/CosmicRituals/Engine/SubscriptionStore.swift +++ b/ios/native/CosmicRituals/Engine/SubscriptionStore.swift @@ -17,11 +17,31 @@ enum SubscriptionCatalog { enum SubscriptionAccessState: Equatable { case checking case entitled + case testingAccess case locked case storeUnavailable(String) var hasPremiumAccess: Bool { - self == .entitled + self == .entitled || self == .testingAccess + } + + var isTestingAccess: Bool { + self == .testingAccess + } +} + +enum SubscriptionLaunchPolicy { + static func initialState( + isUITestingPremium: Bool, + isTestingDistribution: Bool + ) -> SubscriptionAccessState { + if isUITestingPremium { + return .entitled + } + if isTestingDistribution { + return .testingAccess + } + return .checking } } diff --git a/ios/native/CosmicRituals/Views/RootView.swift b/ios/native/CosmicRituals/Views/RootView.swift index eb5dd40..4de9018 100644 --- a/ios/native/CosmicRituals/Views/RootView.swift +++ b/ios/native/CosmicRituals/Views/RootView.swift @@ -17,11 +17,24 @@ struct RootView: View { #else let isUITesting = false #endif - bypassStoreRefresh = isUITesting + + // This condition exists only in the dedicated testing distribution + // configuration. The production Release configuration never defines it. + #if TESTFLIGHT_BETA_ACCESS + let isTestingDistribution = true + #else + let isTestingDistribution = false + #endif + + let initialState = SubscriptionLaunchPolicy.initialState( + isUITestingPremium: isUITesting, + isTestingDistribution: isTestingDistribution + ) + bypassStoreRefresh = initialState.hasPremiumAccess _subscriptionStore = StateObject( wrappedValue: SubscriptionStore( - accessState: isUITesting ? .entitled : .checking, - listensForTransactions: !isUITesting + accessState: initialState, + listensForTransactions: !initialState.hasPremiumAccess ) ) } @@ -38,7 +51,14 @@ struct RootView: View { var body: some View { Group { if subscriptionStore.accessState.hasPremiumAccess { - PanchangView() + if subscriptionStore.accessState.isTestingAccess { + PanchangView() + .safeAreaInset(edge: .top, spacing: 0) { + testingAccessBanner + } + } else { + PanchangView() + } } else { SubscriptionGateView(store: subscriptionStore) } @@ -56,6 +76,22 @@ struct RootView: View { Task { await subscriptionStore.refresh() } } } + + private var testingAccessBanner: some View { + Label("TestFlight testing access", systemImage: "testtube.2") + .font(.caption.weight(.semibold)) + .foregroundStyle(activeScheme.semanticPrimaryText) + .frame(maxWidth: .infinity) + .padding(.horizontal, 12) + .padding(.vertical, 7) + .background(.ultraThinMaterial) + .overlay(alignment: .bottom) { + Rectangle() + .fill(activeScheme.semanticDivider) + .frame(height: 0.5) + } + .accessibilityLabel("TestFlight testing access. Purchases are not required in this testing build.") + } } #Preview { diff --git a/ios/native/CosmicRituals/Views/SubscriptionViews.swift b/ios/native/CosmicRituals/Views/SubscriptionViews.swift index e1a9b31..1ac5ebd 100644 --- a/ios/native/CosmicRituals/Views/SubscriptionViews.swift +++ b/ios/native/CosmicRituals/Views/SubscriptionViews.swift @@ -11,7 +11,7 @@ struct SubscriptionGateView: View { switch store.accessState { case .storeUnavailable(let message): unavailableView(message: message) - case .checking, .locked, .entitled: + case .checking, .locked, .entitled, .testingAccess: subscriptionStoreView } diff --git a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift index 8ed6318..174782b 100644 --- a/ios/native/CosmicRitualsTests/CosmicEngineTests.swift +++ b/ios/native/CosmicRitualsTests/CosmicEngineTests.swift @@ -606,6 +606,34 @@ final class CosmicEngineTests: XCTestCase { ].allSatisfy { $0.scheme == "https" }) } + func testLaunchPolicyKeepsTestingAccessSeparateFromProductionEntitlements() { + XCTAssertEqual( + SubscriptionLaunchPolicy.initialState( + isUITestingPremium: false, + isTestingDistribution: false + ), + .checking + ) + XCTAssertEqual( + SubscriptionLaunchPolicy.initialState( + isUITestingPremium: true, + isTestingDistribution: false + ), + .entitled + ) + XCTAssertEqual( + SubscriptionLaunchPolicy.initialState( + isUITestingPremium: false, + isTestingDistribution: true + ), + .testingAccess + ) + XCTAssertTrue(SubscriptionAccessState.testingAccess.hasPremiumAccess) + XCTAssertTrue(SubscriptionAccessState.testingAccess.isTestingAccess) + XCTAssertFalse(SubscriptionAccessState.entitled.isTestingAccess) + XCTAssertFalse(SubscriptionAccessState.storeUnavailable("offline").hasPremiumAccess) + } + func testStoreKitConfigurationDeclaresTwoWeekTrialsForEveryProduct() throws { let nativeRoot = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() diff --git a/ios/native/SUBSCRIPTION_RELEASE.md b/ios/native/SUBSCRIPTION_RELEASE.md index c43ffa0..8ccbf7b 100644 --- a/ios/native/SUBSCRIPTION_RELEASE.md +++ b/ios/native/SUBSCRIPTION_RELEASE.md @@ -30,6 +30,12 @@ only one introductory offer in a subscription group. - Restore Purchases calls `AppStore.sync()` and the StoreKit subscription surface also exposes the system restore action. - App Shortcuts use the same entitlement check and do not bypass the Premium gate. +- The dedicated `CosmicRitualsTestFlight` scheme uses the `TestFlight` build + configuration to grant clearly labeled testing access when App Store products are + unavailable. Production `Release` builds never define that access condition. +- Never select a testing-access build for the App Store version. Archive the standard + `CosmicRituals` scheme for App Review so purchases and verified entitlements remain + the only production unlock path. ## App Store Connect configuration From 14c1feddc4e20c81df1ab5e2bf12d21ca56008cc Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Tue, 28 Jul 2026 23:53:13 +0530 Subject: [PATCH 09/10] Record Rituals TestFlight build 7 upload --- ios/native/RELEASE_1.0_7.md | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ios/native/RELEASE_1.0_7.md diff --git a/ios/native/RELEASE_1.0_7.md b/ios/native/RELEASE_1.0_7.md new file mode 100644 index 0000000..729b14d --- /dev/null +++ b/ios/native/RELEASE_1.0_7.md @@ -0,0 +1,55 @@ +# TestFlight evidence — 1.0 (7) + +Date: 2026-07-28 +Bundle ID: `com.cosmic.rituals` +Platform: iOS/iPadOS 26 +Disposition: uploaded for internal TestFlight testing only; Apple processing pending + +## Delivery result + +- Source snapshot: commit `957d21b`. +- App Store Connect delivery UUID: + `100fee66-edcd-410e-a13d-5d18f2b07b20`. +- Apple transport completed with no errors or warnings. +- The upload began App Store Connect processing at 23:50 IST. +- Export used `testFlightInternalTestingOnly = true`; Apple documents that such a + build cannot be distributed through external TestFlight or the App Store. +- App Store processing completion and tester-group availability are not yet claimed. + +## Reliability fix + +- The dedicated `CosmicRitualsTestFlight` scheme now grants clearly labeled testing + access without depending on App Store product metadata. +- A StoreKit outage or incomplete subscription propagation can no longer prevent + internal testers from opening Panchang, Timing, Muhurtas, Pooja, or Calendar. +- The standard production `CosmicRituals` Release configuration remains unchanged: + verified StoreKit entitlement is still required and no testing-access condition is + compiled into its launch path. + +## Quality gate + +- Native test suite: 52 passed, 0 failed. +- Focused launch-policy regression: passed. +- Exact TestFlight configuration built, installed, and opened directly into the app + with the visible `TestFlight testing access` banner. +- Standard production Release configuration separately built and remained on the + entitlement gate while StoreKit was unavailable. +- Release archive: `/tmp/CosmicRituals-7-TestFlight.xcarchive`. +- Archive bundle/version/build: `com.cosmic.rituals`, `1.0`, `7`. +- Release architecture: arm64. +- Strict signature and privacy-manifest validation: passed. +- App and dSYM UUID matched: + `AD763FF6-5F1E-30FB-9FE6-3F03393E716E`. +- App Store export re-signed with Apple Distribution, set + `beta-reports-active = 1`, and set `get-task-allow = 0`. +- No UI-test entitlement token, XCTest, runtime-injection, Guard Malloc, Main Thread + Checker, or debug launch marker was found in the archived release binary. + +## Release boundary + +- Build 7 is intentionally unsuitable for App Store submission because it grants + internal testing access without a purchase. +- Archive the standard `CosmicRituals` scheme for a future App Store candidate after + subscription products have been verified end to end. +- No App Store version, subscription, external beta group, Beta App Review, App Review, + or public release action was taken. From caeb325707affc34f20751712abbe8dc10c2f922 Mon Sep 17 00:00:00 2001 From: gohlerdev <> Date: Wed, 29 Jul 2026 00:07:32 +0530 Subject: [PATCH 10/10] Confirm Rituals build 7 internal testing --- ios/native/RELEASE_1.0_7.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ios/native/RELEASE_1.0_7.md b/ios/native/RELEASE_1.0_7.md index 729b14d..24facb9 100644 --- a/ios/native/RELEASE_1.0_7.md +++ b/ios/native/RELEASE_1.0_7.md @@ -3,7 +3,7 @@ Date: 2026-07-28 Bundle ID: `com.cosmic.rituals` Platform: iOS/iPadOS 26 -Disposition: uploaded for internal TestFlight testing only; Apple processing pending +Disposition: processing complete; available to the internal TestFlight group ## Delivery result @@ -11,10 +11,12 @@ Disposition: uploaded for internal TestFlight testing only; Apple processing pen - App Store Connect delivery UUID: `100fee66-edcd-410e-a13d-5d18f2b07b20`. - Apple transport completed with no errors or warnings. -- The upload began App Store Connect processing at 23:50 IST. +- The upload completed App Store Connect processing with status `Complete`. - Export used `testFlightInternalTestingOnly = true`; Apple documents that such a build cannot be distributed through external TestFlight or the App Store. -- App Store processing completion and tester-group availability are not yet claimed. +- The signed-in Connect app showed build `7 Internal` with status `Testing`, attached + to `Cosmic Rituals Internal` with two tester invites and a 90-day expiry window. +- External beta review and external-group availability are not claimed. ## Reliability fix

nSu0U5%qr+LjFHDc5Q1 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