Privacy-preserving location sharing with end-to-end encryption coord.is

Compare changes

Choose any two refs to compare.

+3833 -338
+252
.claude/skills/liquid-glass/SKILL.md
··· 1 + --- 2 + name: liquid-glass 3 + description: "Build native macOS/iOS apps with Apple's Liquid Glass design language and Human Interface Guidelines. Use when creating SwiftUI interfaces, implementing translucent materials, designing cards/rows/badges, applying SF Symbols, following Apple HIG spacing/typography/color systems, or building any app that should feel native to Apple platforms. Triggers on macOS apps, iOS apps, SwiftUI UI, Apple-style design, glass effects, material backgrounds, native app design." 4 + --- 5 + 6 + # Liquid Glass Design 7 + 8 + Build native macOS/iOS applications with Apple's Liquid Glass design and HIG. 9 + 10 + ## Core Rules 11 + 12 + ### Three-Layer Model 13 + 14 + ``` 15 + ┌─────────────────────────────────────────┐ 16 + │ GLASS LAYER (Navigation/Controls) │ ← Glass here ONLY 17 + ├─────────────────────────────────────────┤ 18 + │ CONTENT LAYER (Your App) │ ← Never glass here 19 + └─────────────────────────────────────────┘ 20 + ``` 21 + 22 + Glass is ONLY for navigation floating above content. Never on content itself. 23 + 24 + ### Five Principles 25 + 26 + 1. **Content First** - Glass floats above, content shines through 27 + 2. **Depth Through Light** - Lensing creates hierarchy, not opacity 28 + 3. **Adaptive Tinting** - Colors respond to background dynamically 29 + 4. **Semantic Emphasis** - Tint primary actions only 30 + 5. **Accessibility Built-In** - System handles adaptations automatically 31 + 32 + ## Materials (macOS 14+ / Pre-iOS 26) 33 + 34 + ```swift 35 + // Card with material 36 + .padding(24) 37 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) 38 + 39 + // Material options (lightest → heaviest): 40 + // .ultraThinMaterial, .thinMaterial, .regularMaterial (default), .thickMaterial, .ultraThickMaterial 41 + ``` 42 + 43 + ## Liquid Glass API (iOS 26+ / macOS Tahoe) 44 + 45 + ```swift 46 + // Basic 47 + Button("Action") { }.glassEffect() 48 + 49 + // Variants 50 + .glassEffect(.regular) // Standard UI 51 + .glassEffect(.clear) // Media backgrounds only 52 + .glassEffect(.identity) // Disabled 53 + 54 + // Interactive (adds bounce/shimmer) 55 + Button("Tap") { }.glassEffect(.regular.interactive()) 56 + 57 + // Multiple glass elements - MUST wrap in container 58 + GlassEffectContainer(spacing: 30) { 59 + HStack { 60 + Button("A") { }.glassEffect() 61 + Button("B") { }.glassEffect() 62 + } 63 + } 64 + 65 + // Button styles 66 + Button("Cancel") { }.buttonStyle(.glass) // Secondary 67 + Button("Save") { }.buttonStyle(.glassProminent).tint(.blue) // Primary 68 + ``` 69 + 70 + ## Essential Patterns 71 + 72 + ### Card 73 + 74 + ```swift 75 + VStack(alignment: .leading, spacing: 12) { 76 + HStack { 77 + ZStack { 78 + Circle().fill(color.opacity(0.15)).frame(width: 36, height: 36) 79 + Image(systemName: icon).foregroundStyle(color) 80 + } 81 + Spacer() 82 + } 83 + Text(value).font(.system(size: 28, weight: .bold, design: .rounded)) 84 + Text(label).font(.caption).foregroundStyle(.secondary) 85 + } 86 + .padding(20) 87 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) 88 + ``` 89 + 90 + ### Row with Hover 91 + 92 + ```swift 93 + HStack { content } 94 + .padding(16) 95 + .background(isHovering ? Color.primary.opacity(0.04) : .clear) 96 + .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 97 + .onHover { withAnimation(.easeInOut(duration: 0.15)) { isHovering = $0 } } 98 + ``` 99 + 100 + ### Badge 101 + 102 + ```swift 103 + HStack(spacing: 8) { 104 + Circle().fill(isActive ? .green : .orange).frame(width: 8, height: 8) 105 + Text(status).font(.caption).fontWeight(.medium) 106 + } 107 + .padding(.horizontal, 12).padding(.vertical, 8) 108 + .background(.regularMaterial, in: Capsule()) 109 + ``` 110 + 111 + ### Icon with Tinted Background 112 + 113 + ```swift 114 + ZStack { 115 + Circle().fill(color.opacity(0.15)).frame(width: 32, height: 32) 116 + Image(systemName: icon).font(.system(size: 14, weight: .semibold)).foregroundStyle(color) 117 + } 118 + ``` 119 + 120 + ## Shapes 121 + 122 + ```swift 123 + // Always use .continuous 124 + RoundedRectangle(cornerRadius: 16, style: .continuous) // Cards 125 + RoundedRectangle(cornerRadius: 12, style: .continuous) // Rows 126 + RoundedRectangle(cornerRadius: 8, style: .continuous) // Small elements 127 + Capsule() // Pills, badges 128 + Circle() // Icons 129 + ``` 130 + 131 + ## Colors 132 + 133 + ```swift 134 + // Semantic foreground 135 + .foregroundStyle(.primary) // Main content 136 + .foregroundStyle(.secondary) // Subtitles 137 + .foregroundStyle(.tertiary) // Timestamps 138 + 139 + // Backgrounds 140 + .background(.quaternary) 141 + .background(.quaternary.opacity(0.5)) 142 + 143 + // Accent meanings 144 + Color.blue // Primary actions, selection 145 + Color.green // Success, active 146 + Color.orange // Warning, loading 147 + Color.red // Destructive, error 148 + Color.purple // Premium, AI 149 + Color.cyan // Security 150 + 151 + // Tinted backgrounds: always 15% opacity 152 + .fill(color.opacity(0.15)) 153 + ``` 154 + 155 + ## Typography 156 + 157 + ```swift 158 + .font(.largeTitle).fontWeight(.bold) // Page titles 159 + .font(.headline).fontWeight(.semibold) // Section headers 160 + .font(.subheadline).fontWeight(.medium) // Row titles 161 + .font(.body) // Content (17pt default) 162 + .font(.caption) // Metadata 163 + .font(.caption2) // Timestamps 164 + .font(.system(size: 28, weight: .bold, design: .rounded)) // Stats 165 + ``` 166 + 167 + **Rules:** Min 11pt. Avoid Ultralight/Thin/Light. Use system fonts for Dynamic Type. 168 + 169 + ## Spacing (8pt Grid) 170 + 171 + ```swift 172 + // Standard values: 4, 8, 12, 16, 20, 24, 32, 40, 48 173 + 174 + .padding(24) // Cards 175 + .padding(16) // Rows 176 + .padding(.horizontal, 12).padding(.vertical, 8) // Badges 177 + 178 + // Rule: external spacing ≥ internal spacing 179 + VStack(spacing: 24) { CardView().padding(20) } // Correct 180 + ``` 181 + 182 + ## SF Symbols 183 + 184 + ```swift 185 + // Preferred: hierarchical for depth 186 + Image(systemName: icon).symbolRenderingMode(.hierarchical).foregroundStyle(color) 187 + 188 + // Match weight to nearby text 189 + Image(systemName: "gear").font(.system(size: 14, weight: .semibold)) 190 + ``` 191 + 192 + ## Hit Targets 193 + 194 + ```swift 195 + // MINIMUM: 44pt × 44pt 196 + Button { } label: { Image(systemName: "gear") } 197 + .frame(minWidth: 44, minHeight: 44) 198 + ``` 199 + 200 + ## Animations 201 + 202 + ```swift 203 + // Hover 204 + .onHover { withAnimation(.easeInOut(duration: 0.15)) { isHovering = $0 } } 205 + 206 + // Spring 207 + withAnimation(.spring(response: 0.3)) { } 208 + withAnimation(.bouncy) { } 209 + 210 + // Entry 211 + .opacity(appeared ? 1 : 0).offset(y: appeared ? 0 : 10) 212 + .onAppear { withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { appeared = true } } 213 + ``` 214 + 215 + ## Do's and Don'ts 216 + 217 + **DO:** 218 + - Use `.regularMaterial` for cards/toolbars 219 + - Use `.continuous` on ALL rounded rectangles 220 + - Use 15% opacity for icon backgrounds 221 + - Wrap multiple glass in `GlassEffectContainer` 222 + - Use 44pt minimum hit targets 223 + - Use 8pt grid spacing 224 + - Use SF Symbols with `.hierarchical` 225 + 226 + **DON'T:** 227 + - Apply glass to content (lists, tables) 228 + - Stack glass on glass without container 229 + - Use sharp corners 230 + - Go below 11pt text 231 + - Create touch targets < 44pt 232 + - Use full-color images on glass 233 + 234 + ## Accessibility 235 + 236 + System handles automatically: 237 + - Reduce Transparency → opaquer glass 238 + - Increase Contrast → visible borders 239 + - Reduce Motion → no bouncy effects 240 + - Dynamic Type → text scales 241 + 242 + Manual override if needed: 243 + ```swift 244 + @Environment(\.accessibilityReduceTransparency) var reduceTransparency 245 + .glassEffect(reduceTransparency ? .identity : .regular) 246 + ``` 247 + 248 + ## References 249 + 250 + For detailed patterns and examples, see: 251 + - [references/components.md](references/components.md) - Full component implementations 252 + - [references/apple-hig.md](references/apple-hig.md) - Complete Apple HIG guidelines
+312
.claude/skills/liquid-glass/references/apple-hig.md
··· 1 + # Apple Human Interface Guidelines Reference 2 + 3 + Complete HIG guidelines for Liquid Glass design. 4 + 5 + ## Table of Contents 6 + - [Core Principles](#core-principles) 7 + - [Typography](#typography) 8 + - [Spacing System](#spacing-system) 9 + - [SF Symbols](#sf-symbols) 10 + - [Hit Targets](#hit-targets) 11 + - [Accessibility](#accessibility) 12 + - [Resources](#resources) 13 + 14 + --- 15 + 16 + ## Core Principles 17 + 18 + Apple HIG establishes four pillars: 19 + 20 + ### Clarity 21 + - Interfaces must be legible, precise, easy to understand 22 + - Every element has a purpose 23 + - Users know what to do without instructions 24 + 25 + ### Deference 26 + - UI helps users focus on content 27 + - Minimize visual clutter 28 + - Controls don't distract from content 29 + 30 + ### Depth 31 + - Visual layers convey hierarchy 32 + - Shadows, blur, translucency show relationships 33 + - Spatial metaphors aid understanding 34 + 35 + ### Consistency 36 + - Standard UI elements feel familiar 37 + - System components adapt to Dark Mode, Dynamic Type 38 + - Leverage proven interaction patterns 39 + 40 + --- 41 + 42 + ## Typography 43 + 44 + San Francisco is the system font optimized for Apple platforms. 45 + 46 + ### Font Hierarchy 47 + 48 + | Style | Usage | SwiftUI | 49 + |-------|-------|---------| 50 + | Large Title | Page titles | `.font(.largeTitle).fontWeight(.bold)` | 51 + | Headline | Section headers | `.font(.headline).fontWeight(.semibold)` | 52 + | Subheadline | Row/card titles | `.font(.subheadline).fontWeight(.medium)` | 53 + | Body | Content (17pt) | `.font(.body)` | 54 + | Callout | Secondary content | `.font(.callout)` | 55 + | Caption | Metadata | `.font(.caption)` | 56 + | Caption 2 | Timestamps | `.font(.caption2)` | 57 + 58 + ### Stats/Numbers 59 + 60 + ```swift 61 + .font(.system(size: 28, weight: .bold, design: .rounded)) 62 + .font(.system(size: 48, weight: .bold, design: .rounded)) 63 + ``` 64 + 65 + ### Rules 66 + 67 + - **Minimum size:** 11pt (except legal disclaimers) 68 + - **Default body:** 17pt 69 + - **Avoid:** Ultralight, Thin, Light weights 70 + - **Always:** Use system fonts for Dynamic Type support 71 + - **Contrast:** Use `.primary`, `.secondary`, `.tertiary` 72 + 73 + --- 74 + 75 + ## Spacing System 76 + 77 + Apple uses an 8-point grid for consistent layouts. 78 + 79 + ### Standard Values 80 + 81 + | Points | Usage | 82 + |--------|-------| 83 + | 4 | Fine adjustments (icons, small text) | 84 + | 8 | Minimum standard spacing | 85 + | 12 | Tight spacing | 86 + | 16 | Standard spacing (most common) | 87 + | 20 | Comfortable spacing | 88 + | 24 | Section spacing, card padding | 89 + | 32 | Large section gaps | 90 + | 40 | Major section separation | 91 + | 48 | Page-level separation | 92 + 93 + ### Padding Patterns 94 + 95 + ```swift 96 + // Cards 97 + .padding(24) 98 + .padding(20) 99 + 100 + // Rows 101 + .padding(16) 102 + .padding(.horizontal, 16) 103 + .padding(.vertical, 12) 104 + 105 + // Badges/buttons 106 + .padding(.horizontal, 12) 107 + .padding(.vertical, 8) 108 + 109 + // Tight elements 110 + .padding(8) 111 + ``` 112 + 113 + ### Internal ≤ External Rule 114 + 115 + External spacing (margins) should equal or exceed internal spacing (padding): 116 + 117 + ```swift 118 + // CORRECT 119 + VStack(spacing: 24) { // External: 24pt 120 + CardView().padding(20) // Internal: 20pt 121 + } 122 + 123 + // WRONG 124 + VStack(spacing: 12) { // External: 12pt 125 + CardView().padding(24) // Internal: 24pt - cramped 126 + } 127 + ``` 128 + 129 + ### Line Height 130 + 131 + Line heights should be multiples of 8 for grid alignment: 132 + 133 + ```swift 134 + Text("Content") 135 + .font(.system(size: 15)) 136 + .lineSpacing(9) // 15 + 9 = 24pt (multiple of 8) 137 + ``` 138 + 139 + --- 140 + 141 + ## SF Symbols 142 + 143 + 6,900+ icons that integrate with San Francisco font. 144 + 145 + ### Rendering Modes 146 + 147 + | Mode | Description | Use Case | 148 + |------|-------------|----------| 149 + | Monochrome | Single color, all layers | Default | 150 + | Hierarchical | Single color with opacity depth | **Recommended** | 151 + | Palette | Up to 3 explicit colors | Branded icons | 152 + | Multicolor | Apple-defined colors | Realistic icons | 153 + 154 + ```swift 155 + // Hierarchical (preferred) 156 + Image(systemName: icon) 157 + .symbolRenderingMode(.hierarchical) 158 + .foregroundStyle(color) 159 + 160 + // Palette 161 + Image(systemName: "person.3.sequence.fill") 162 + .symbolRenderingMode(.palette) 163 + .foregroundStyle(.red, .green, .blue) 164 + 165 + // Multicolor 166 + Image(systemName: "externaldrive.badge.plus") 167 + .symbolRenderingMode(.multicolor) 168 + ``` 169 + 170 + ### Variants 171 + 172 + ```swift 173 + Image(systemName: "heart.fill") // Fill - selection states 174 + Image(systemName: "mic.slash") // Slash - unavailable 175 + Image(systemName: "plus.circle.fill") // Circle - buttons 176 + 177 + // Programmatic 178 + Image(systemName: "bell") 179 + .symbolVariant(.fill) 180 + .symbolVariant(.slash) 181 + ``` 182 + 183 + ### Best Practices 184 + 185 + - Match symbol weight to nearby text weight 186 + - Use symbols (not images) on glass backgrounds 187 + - Don't mix rendering modes in same view 188 + - Check SF Symbols app for mode support 189 + 190 + --- 191 + 192 + ## Hit Targets 193 + 194 + ### Minimum Size 195 + 196 + **44pt × 44pt** is the absolute minimum for all interactive elements. 197 + 198 + ```swift 199 + // Icon button with proper target 200 + Button { } label: { 201 + Image(systemName: "gear") 202 + .font(.system(size: 16)) 203 + } 204 + .frame(minWidth: 44, minHeight: 44) 205 + 206 + // Small visual, large target 207 + Image(systemName: "info.circle") 208 + .font(.caption) 209 + .frame(width: 44, height: 44) 210 + .contentShape(Rectangle()) // Expand hit area 211 + ``` 212 + 213 + ### Interactive Feedback 214 + 215 + ```swift 216 + // Hover (macOS) 217 + .onHover { isHovering in 218 + withAnimation(.easeInOut(duration: 0.15)) { 219 + self.isHovering = isHovering 220 + } 221 + } 222 + 223 + // Press scale 224 + .scaleEffect(isPressed ? 0.95 : 1.0) 225 + 226 + // Glass interactive (iOS 26+) 227 + .glassEffect(.regular.interactive()) 228 + ``` 229 + 230 + --- 231 + 232 + ## Accessibility 233 + 234 + ### Automatic Adaptations 235 + 236 + | Setting | Effect | 237 + |---------|--------| 238 + | Reduce Transparency | Opaquer glass | 239 + | Increase Contrast | Visible borders | 240 + | Reduce Motion | No elastic effects | 241 + | Dynamic Type | Text scales | 242 + | VoiceOver | System announces | 243 + 244 + ### Required Practices 245 + 246 + 1. **44pt minimum** hit targets 247 + 2. **11pt minimum** text size 248 + 3. **Sufficient contrast** - use semantic colors 249 + 4. **Don't rely on color alone** - combine with icons/text 250 + 5. **Support Dynamic Type** - use system font styles 251 + 252 + ### Manual Control 253 + 254 + ```swift 255 + @Environment(\.accessibilityReduceTransparency) var reduceTransparency 256 + @Environment(\.accessibilityReduceMotion) var reduceMotion 257 + 258 + var body: some View { 259 + Content() 260 + .glassEffect(reduceTransparency ? .identity : .regular) 261 + .animation(reduceMotion ? nil : .spring(), value: state) 262 + } 263 + ``` 264 + 265 + ### Testing Checklist 266 + 267 + - [ ] VoiceOver enabled 268 + - [ ] Dynamic Type at largest size 269 + - [ ] Reduce Transparency on 270 + - [ ] Increase Contrast on 271 + - [ ] Reduce Motion on 272 + - [ ] Light and Dark modes 273 + - [ ] 44pt touch targets verified 274 + - [ ] Text never below 11pt 275 + 276 + --- 277 + 278 + ## Resources 279 + 280 + ### Apple Official 281 + 282 + - [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines) 283 + - [Design Tips](https://developer.apple.com/design/tips/) 284 + - [Designing for macOS](https://developer.apple.com/design/human-interface-guidelines/designing-for-macos) 285 + - [Materials](https://developer.apple.com/design/human-interface-guidelines/materials) 286 + - [Typography](https://developer.apple.com/design/human-interface-guidelines/typography) 287 + - [SF Symbols](https://developer.apple.com/design/human-interface-guidelines/sf-symbols) 288 + - [Design Resources](https://developer.apple.com/design/resources/) 289 + 290 + ### WWDC Sessions 291 + 292 + - [WWDC 2025: Meet Liquid Glass](https://developer.apple.com/videos/play/wwdc2025/219/) 293 + - [WWDC 2025: Build a SwiftUI app with the new design](https://developer.apple.com/videos/play/wwdc2025/323/) 294 + - [WWDC 2025: Get to know the new design system](https://developer.apple.com/videos/play/wwdc2025/356/) 295 + - [WWDC 2021: SF Symbols in SwiftUI](https://developer.apple.com/videos/play/wwdc2021/10349/) 296 + 297 + ### Tools 298 + 299 + - [SF Symbols App](https://developer.apple.com/sf-symbols/) 300 + - [Liquid Glass GitHub Reference](https://github.com/conorluddy/LiquidGlassReference) 301 + 302 + ### Semantic Colors 303 + 304 + ```swift 305 + Color.blue // Primary actions, links, selection 306 + Color.green // Success, active, confirmed 307 + Color.orange // Warning, loading, attention 308 + Color.red // Destructive, error 309 + Color.purple // Premium, AI features 310 + Color.cyan // Security, privacy 311 + Color.yellow // Highlights, caution 312 + ```
+310
.claude/skills/liquid-glass/references/components.md
··· 1 + # Component Patterns 2 + 3 + Full SwiftUI implementations for Liquid Glass design. 4 + 5 + ## Table of Contents 6 + - [StatCard](#statcard) 7 + - [SettingsSection](#settingssection) 8 + - [InteractiveRow](#interactiverow) 9 + - [EmptyStateView](#emptystateview) 10 + - [OptionRow](#optionrow) 11 + - [ExpandableToolbar](#expandabletoolbar) 12 + 13 + --- 14 + 15 + ## StatCard 16 + 17 + Dashboard stat card with icon, value, and label. 18 + 19 + ```swift 20 + struct StatCard: View { 21 + let value: String 22 + let label: String 23 + let icon: String 24 + let color: Color 25 + 26 + var body: some View { 27 + VStack(alignment: .leading, spacing: 12) { 28 + HStack { 29 + ZStack { 30 + Circle() 31 + .fill(color.opacity(0.15)) 32 + .frame(width: 36, height: 36) 33 + Image(systemName: icon) 34 + .font(.system(size: 16, weight: .semibold)) 35 + .foregroundStyle(color) 36 + } 37 + Spacer() 38 + } 39 + VStack(alignment: .leading, spacing: 4) { 40 + Text(value) 41 + .font(.system(size: 28, weight: .bold, design: .rounded)) 42 + Text(label) 43 + .font(.caption) 44 + .foregroundStyle(.secondary) 45 + } 46 + } 47 + .padding(20) 48 + .frame(maxWidth: .infinity, alignment: .leading) 49 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) 50 + } 51 + } 52 + 53 + // Usage 54 + StatCard(value: "1,234", label: "Total Items", icon: "doc.fill", color: .blue) 55 + ``` 56 + 57 + --- 58 + 59 + ## SettingsSection 60 + 61 + Grouped settings section with icon header. 62 + 63 + ```swift 64 + struct SettingsSection<Content: View>: View { 65 + let title: String 66 + let icon: String 67 + let iconColor: Color 68 + @ViewBuilder let content: Content 69 + 70 + var body: some View { 71 + VStack(alignment: .leading, spacing: 16) { 72 + HStack(spacing: 12) { 73 + ZStack { 74 + RoundedRectangle(cornerRadius: 8, style: .continuous) 75 + .fill(iconColor.opacity(0.15)) 76 + .frame(width: 32, height: 32) 77 + Image(systemName: icon) 78 + .font(.system(size: 14, weight: .semibold)) 79 + .foregroundStyle(iconColor) 80 + } 81 + Text(title) 82 + .font(.headline) 83 + } 84 + content 85 + } 86 + .padding(24) 87 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) 88 + } 89 + } 90 + 91 + // Usage 92 + SettingsSection(title: "Privacy", icon: "lock.shield", iconColor: .cyan) { 93 + Toggle("Enable Feature", isOn: $isEnabled) 94 + Text("Description").font(.caption).foregroundStyle(.secondary) 95 + } 96 + ``` 97 + 98 + --- 99 + 100 + ## InteractiveRow 101 + 102 + Row with hover-revealed actions. 103 + 104 + ```swift 105 + struct InteractiveRow: View { 106 + let title: String 107 + let subtitle: String 108 + let onCopy: () -> Void 109 + let onDelete: () -> Void 110 + @State private var isHovering = false 111 + 112 + var body: some View { 113 + HStack(spacing: 16) { 114 + VStack(alignment: .leading, spacing: 6) { 115 + Text(title) 116 + .font(.body) 117 + .lineLimit(2) 118 + Text(subtitle) 119 + .font(.caption2) 120 + .foregroundStyle(.tertiary) 121 + } 122 + Spacer() 123 + if isHovering { 124 + HStack(spacing: 8) { 125 + Button(action: onCopy) { 126 + Image(systemName: "doc.on.doc").font(.caption) 127 + } 128 + .buttonStyle(.bordered) 129 + Button(action: onDelete) { 130 + Image(systemName: "trash").font(.caption) 131 + } 132 + .buttonStyle(.bordered) 133 + .tint(.red) 134 + } 135 + .transition(.opacity.combined(with: .move(edge: .trailing))) 136 + } 137 + } 138 + .padding(16) 139 + .background( 140 + RoundedRectangle(cornerRadius: 12, style: .continuous) 141 + .fill(isHovering ? Color.primary.opacity(0.04) : Color.clear) 142 + ) 143 + .background( 144 + RoundedRectangle(cornerRadius: 12, style: .continuous) 145 + .fill(.quaternary.opacity(0.5)) 146 + ) 147 + .onHover { hovering in 148 + withAnimation(.easeInOut(duration: 0.15)) { 149 + isHovering = hovering 150 + } 151 + } 152 + } 153 + } 154 + ``` 155 + 156 + --- 157 + 158 + ## EmptyStateView 159 + 160 + Placeholder for empty content states. 161 + 162 + ```swift 163 + struct EmptyStateView: View { 164 + let icon: String 165 + let title: String 166 + let message: String 167 + 168 + var body: some View { 169 + VStack(spacing: 16) { 170 + ZStack { 171 + Circle() 172 + .fill(.quaternary) 173 + .frame(width: 64, height: 64) 174 + Image(systemName: icon) 175 + .font(.title) 176 + .foregroundStyle(.secondary) 177 + } 178 + VStack(spacing: 4) { 179 + Text(title) 180 + .font(.headline) 181 + .foregroundStyle(.secondary) 182 + Text(message) 183 + .font(.subheadline) 184 + .foregroundStyle(.tertiary) 185 + .multilineTextAlignment(.center) 186 + } 187 + } 188 + .frame(maxWidth: .infinity, maxHeight: .infinity) 189 + } 190 + } 191 + 192 + // Usage 193 + EmptyStateView( 194 + icon: "doc.text", 195 + title: "No Documents", 196 + message: "Create your first document to get started" 197 + ) 198 + ``` 199 + 200 + --- 201 + 202 + ## OptionRow 203 + 204 + Selectable option with checkmark indicator. 205 + 206 + ```swift 207 + struct OptionRow: View { 208 + let title: String 209 + let subtitle: String 210 + let isSelected: Bool 211 + let action: () -> Void 212 + 213 + var body: some View { 214 + Button(action: action) { 215 + HStack(spacing: 12) { 216 + VStack(alignment: .leading, spacing: 4) { 217 + Text(title) 218 + .font(.subheadline) 219 + .fontWeight(.medium) 220 + Text(subtitle) 221 + .font(.caption) 222 + .foregroundStyle(.secondary) 223 + } 224 + Spacer() 225 + if isSelected { 226 + Image(systemName: "checkmark.circle.fill") 227 + .font(.title3) 228 + .foregroundStyle(.blue) 229 + } 230 + } 231 + .padding(14) 232 + .background(isSelected ? Color.blue.opacity(0.08) : Color.clear) 233 + .background(.quaternary.opacity(0.5)) 234 + .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) 235 + .overlay( 236 + RoundedRectangle(cornerRadius: 10, style: .continuous) 237 + .strokeBorder(isSelected ? Color.blue.opacity(0.3) : Color.clear, lineWidth: 1) 238 + ) 239 + } 240 + .buttonStyle(.plain) 241 + } 242 + } 243 + ``` 244 + 245 + --- 246 + 247 + ## ExpandableToolbar 248 + 249 + Morphing toolbar with animated expansion (iOS 26+). 250 + 251 + ```swift 252 + struct ExpandableToolbar: View { 253 + @State private var isExpanded = false 254 + @Namespace private var namespace 255 + 256 + var body: some View { 257 + GlassEffectContainer(spacing: 20) { 258 + HStack(spacing: 16) { 259 + if isExpanded { 260 + Button("Camera", systemImage: "camera") { } 261 + .glassEffect(.regular.interactive()) 262 + .glassEffectID("camera", in: namespace) 263 + 264 + Button("Photos", systemImage: "photo") { } 265 + .glassEffect(.regular.interactive()) 266 + .glassEffectID("photos", in: namespace) 267 + } 268 + 269 + Button { 270 + withAnimation(.bouncy) { isExpanded.toggle() } 271 + } label: { 272 + Image(systemName: isExpanded ? "xmark" : "plus") 273 + .frame(width: 44, height: 44) 274 + } 275 + .buttonStyle(.glassProminent) 276 + .buttonBorderShape(.circle) 277 + .glassEffectID("toggle", in: namespace) 278 + } 279 + } 280 + } 281 + } 282 + ``` 283 + 284 + --- 285 + 286 + ## Badge Variants 287 + 288 + ```swift 289 + // Status badge 290 + HStack(spacing: 8) { 291 + Circle() 292 + .fill(isActive ? Color.green : Color.orange) 293 + .frame(width: 8, height: 8) 294 + Text(isActive ? "Active" : "Loading...") 295 + .font(.caption) 296 + .fontWeight(.medium) 297 + } 298 + .padding(.horizontal, 12) 299 + .padding(.vertical, 8) 300 + .background(.regularMaterial, in: Capsule()) 301 + 302 + // Count badge 303 + Text("\(count)") 304 + .font(.caption2) 305 + .fontWeight(.medium) 306 + .foregroundStyle(.secondary) 307 + .padding(.horizontal, 6) 308 + .padding(.vertical, 2) 309 + .background(.quaternary, in: Capsule()) 310 + ```
Coords.icon/Assets/ic_launcher_foreground 3.png

This is a binary file and will not be displayed.

-62
Coords.icon/icon.json
··· 1 - { 2 - "fill" : { 3 - "linear-gradient" : [ 4 - "extended-srgb:0.33333,0.74510,0.94118,1.00000", 5 - "display-p3:0.84259,0.44586,0.82200,1.00000" 6 - ], 7 - "orientation" : { 8 - "start" : { 9 - "x" : 0.5, 10 - "y" : 0 11 - }, 12 - "stop" : { 13 - "x" : 0.5, 14 - "y" : 0.7 15 - } 16 - } 17 - }, 18 - "groups" : [ 19 - { 20 - "layers" : [ 21 - 22 - ], 23 - "shadow" : { 24 - "kind" : "neutral", 25 - "opacity" : 0.5 26 - }, 27 - "translucency" : { 28 - "enabled" : true, 29 - "value" : 0.5 30 - } 31 - }, 32 - { 33 - "layers" : [ 34 - { 35 - "image-name" : "ic_launcher_foreground 3.png", 36 - "name" : "ic_launcher_foreground 3", 37 - "position" : { 38 - "scale" : 0.75, 39 - "translation-in-points" : [ 40 - 0, 41 - 0 42 - ] 43 - } 44 - } 45 - ], 46 - "shadow" : { 47 - "kind" : "neutral", 48 - "opacity" : 0.5 49 - }, 50 - "translucency" : { 51 - "enabled" : true, 52 - "value" : 0.5 53 - } 54 - } 55 - ], 56 - "supported-platforms" : { 57 - "circles" : [ 58 - "watchOS" 59 - ], 60 - "squares" : "shared" 61 - } 62 - }
apple/Transponder/Coords.icon/Assets/ic_launcher_foreground 3.png

This is a binary file and will not be displayed.

+62
apple/Transponder/Coords.icon/icon.json
··· 1 + { 2 + "fill" : { 3 + "linear-gradient" : [ 4 + "extended-srgb:0.33333,0.74510,0.94118,1.00000", 5 + "display-p3:0.84259,0.44586,0.82200,1.00000" 6 + ], 7 + "orientation" : { 8 + "start" : { 9 + "x" : 0.5, 10 + "y" : 0 11 + }, 12 + "stop" : { 13 + "x" : 0.5, 14 + "y" : 0.7 15 + } 16 + } 17 + }, 18 + "groups" : [ 19 + { 20 + "layers" : [ 21 + 22 + ], 23 + "shadow" : { 24 + "kind" : "neutral", 25 + "opacity" : 0.5 26 + }, 27 + "translucency" : { 28 + "enabled" : true, 29 + "value" : 0.5 30 + } 31 + }, 32 + { 33 + "layers" : [ 34 + { 35 + "image-name" : "ic_launcher_foreground 3.png", 36 + "name" : "ic_launcher_foreground 3", 37 + "position" : { 38 + "scale" : 0.75, 39 + "translation-in-points" : [ 40 + 0, 41 + 0 42 + ] 43 + } 44 + } 45 + ], 46 + "shadow" : { 47 + "kind" : "neutral", 48 + "opacity" : 0.5 49 + }, 50 + "translucency" : { 51 + "enabled" : true, 52 + "value" : 0.5 53 + } 54 + } 55 + ], 56 + "supported-platforms" : { 57 + "circles" : [ 58 + "watchOS" 59 + ], 60 + "squares" : "shared" 61 + } 62 + }
-64
apple/Transponder/Transponder/Assets.xcassets/AppIcon.appiconset/Contents.json
··· 1 - { 2 - "images" : [ 3 - { 4 - "filename" : "icon-1024.png", 5 - "idiom" : "universal", 6 - "platform" : "ios", 7 - "size" : "1024x1024" 8 - }, 9 - { 10 - "idiom" : "mac", 11 - "scale" : "1x", 12 - "size" : "16x16" 13 - }, 14 - { 15 - "idiom" : "mac", 16 - "scale" : "2x", 17 - "size" : "16x16" 18 - }, 19 - { 20 - "idiom" : "mac", 21 - "scale" : "1x", 22 - "size" : "32x32" 23 - }, 24 - { 25 - "idiom" : "mac", 26 - "scale" : "2x", 27 - "size" : "32x32" 28 - }, 29 - { 30 - "idiom" : "mac", 31 - "scale" : "1x", 32 - "size" : "128x128" 33 - }, 34 - { 35 - "idiom" : "mac", 36 - "scale" : "2x", 37 - "size" : "128x128" 38 - }, 39 - { 40 - "idiom" : "mac", 41 - "scale" : "1x", 42 - "size" : "256x256" 43 - }, 44 - { 45 - "idiom" : "mac", 46 - "scale" : "2x", 47 - "size" : "256x256" 48 - }, 49 - { 50 - "idiom" : "mac", 51 - "scale" : "1x", 52 - "size" : "512x512" 53 - }, 54 - { 55 - "idiom" : "mac", 56 - "scale" : "2x", 57 - "size" : "512x512" 58 - } 59 - ], 60 - "info" : { 61 - "author" : "xcode", 62 - "version" : 1 63 - } 64 - }
apple/Transponder/Transponder/Assets.xcassets/AppIcon.appiconset/icon-1024.png

This is a binary file and will not be displayed.

+32
apple/Transponder/Transponder/BackgroundSyncManager.swift
··· 42 42 43 43 do { 44 44 try BGTaskScheduler.shared.submit(request) 45 + #if DEBUG 45 46 print("BackgroundSync: Scheduled background refresh") 47 + #endif 46 48 } catch { 49 + #if DEBUG 47 50 print("BackgroundSync: Failed to schedule background refresh: \(error)") 51 + #endif 48 52 } 49 53 } 50 54 51 55 /// Handle the background refresh task 52 56 private func handleBackgroundRefresh(task: BGAppRefreshTask) { 57 + #if DEBUG 53 58 print("BackgroundSync: Background refresh task started") 59 + #endif 54 60 55 61 // Schedule the next refresh 56 62 scheduleBackgroundRefresh() ··· 69 75 Task { 70 76 _ = await workTask.value 71 77 task.setTaskCompleted(success: true) 78 + #if DEBUG 72 79 print("BackgroundSync: Background refresh task completed") 80 + #endif 73 81 } 74 82 } 75 83 76 84 /// Perform the background sync (upload + fetch) 77 85 private func performBackgroundSync() async { 78 86 guard identityStore.hasIdentity else { 87 + #if DEBUG 79 88 print("BackgroundSync: No identity, skipping sync") 89 + #endif 80 90 return 81 91 } 82 92 ··· 87 97 88 98 // Always fetch friend locations 89 99 let result = await syncService.fetchTrackedFriends() 100 + #if DEBUG 90 101 switch result { 91 102 case .success(let locations): 92 103 print("BackgroundSync: Fetched \(locations.count) friend locations") 93 104 case .error(let message): 94 105 print("BackgroundSync: Fetch failed: \(message)") 95 106 } 107 + #endif 96 108 } 97 109 98 110 // MARK: - Significant Location Change ··· 100 112 /// Start monitoring for significant location changes 101 113 func startMonitoringSignificantLocationChanges() { 102 114 guard CLLocationManager.significantLocationChangeMonitoringAvailable() else { 115 + #if DEBUG 103 116 print("BackgroundSync: Significant location change monitoring not available") 117 + #endif 104 118 return 105 119 } 106 120 107 121 locationManager.startMonitoringSignificantLocationChanges() 108 122 isMonitoringSignificantChanges = true 123 + #if DEBUG 109 124 print("BackgroundSync: Started monitoring significant location changes") 125 + #endif 110 126 } 111 127 112 128 /// Stop monitoring for significant location changes 113 129 func stopMonitoringSignificantLocationChanges() { 114 130 locationManager.stopMonitoringSignificantLocationChanges() 115 131 isMonitoringSignificantChanges = false 132 + #if DEBUG 116 133 print("BackgroundSync: Stopped monitoring significant location changes") 134 + #endif 117 135 } 118 136 119 137 /// Request "Always" authorization for background location ··· 130 148 131 149 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 132 150 guard let location = locations.last else { return } 151 + #if DEBUG 133 152 print("BackgroundSync: Significant location change detected: \(location.coordinate)") 153 + #endif 134 154 135 155 // Only upload if auto-share is enabled 136 156 guard identityStore.autoShareEnabled else { 157 + #if DEBUG 137 158 print("BackgroundSync: Auto-share disabled, skipping upload") 159 + #endif 138 160 return 139 161 } 140 162 ··· 145 167 } 146 168 147 169 func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 170 + #if DEBUG 148 171 print("BackgroundSync: Authorization changed to \(manager.authorizationStatus.rawValue)") 172 + #endif 149 173 150 174 if manager.authorizationStatus == .authorizedAlways { 151 175 // User granted "Always" permission - enable auto-share since that's the only ··· 170 194 } 171 195 172 196 func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 197 + #if DEBUG 173 198 print("BackgroundSync: Location error: \(error.localizedDescription)") 199 + #endif 174 200 } 175 201 176 202 // MARK: - Location Upload ··· 178 204 private func uploadCurrentLocation() async { 179 205 // Use shared LocationManager with cached-okay freshness to preserve BGTask execution time 180 206 guard let location = await LocationManager.shared.requestLocation(.cachedOkay) else { 207 + #if DEBUG 181 208 print("BackgroundSync: Could not get current location") 209 + #endif 182 210 return 183 211 } 184 212 ··· 188 216 private func uploadLocation(_ location: CLLocation) async { 189 217 let recipients = getShareRecipients() 190 218 guard !recipients.isEmpty else { 219 + #if DEBUG 191 220 print("BackgroundSync: No share recipients, skipping upload") 221 + #endif 192 222 return 193 223 } 194 224 ··· 200 230 timestamp: UInt64(location.timestamp.timeIntervalSince1970 * 1000) 201 231 ) 202 232 233 + #if DEBUG 203 234 switch result { 204 235 case .success: 205 236 print("BackgroundSync: Location uploaded successfully") 206 237 case .error(let message): 207 238 print("BackgroundSync: Upload failed: \(message)") 208 239 } 240 + #endif 209 241 } 210 242 }
+2
apple/Transponder/Transponder/IdentityStore.swift
··· 123 123 if serverUrl.contains(oldDomain) { 124 124 let newUrl = serverUrl.replacingOccurrences(of: oldDomain, with: newDomain) 125 125 setServerUrl(newUrl) 126 + #if DEBUG 126 127 print("Migrated user server URL to \(newUrl)") 128 + #endif 127 129 } 128 130 } 129 131
+2
apple/Transponder/Transponder/Info.plist
··· 2 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 3 <plist version="1.0"> 4 4 <dict> 5 + <key>CFBundleName</key> 6 + <string>Coords</string> 5 7 <key>CFBundleDisplayName</key> 6 8 <string>Coords</string> 7 9 <key>CFBundleURLTypes</key>
+16
apple/Transponder/Transponder/LicensesView.swift
··· 35 35 .font(.caption) 36 36 .foregroundStyle(.secondary) 37 37 } 38 + 39 + HStack(spacing: 12) { 40 + Link(destination: URL(string: "https://coord.is")!) { 41 + Label("Website", systemImage: "globe") 42 + } 43 + .buttonStyle(.glass) 44 + 45 + Link(destination: URL(string: "https://tangled.org/bentley.sh/coords")!) { 46 + Label("Source", systemImage: "chevron.left.forwardslash.chevron.right") 47 + } 48 + .buttonStyle(.glass) 49 + } 38 50 } 39 51 .frame(maxWidth: .infinity) 40 52 .padding(.vertical, 16) ··· 111 123 .navigationBarTitleDisplayMode(.inline) 112 124 } 113 125 } 126 + 127 + #Preview { 128 + LicensesSheet() 129 + }
+15 -10
apple/Transponder/Transponder/LocationManager.swift
··· 5 5 // MARK: - Location Freshness 6 6 7 7 enum LocationFreshness { 8 - case cachedOkay // Use cached if <5min old 8 + case cachedOkay // Use cached if <3min old 9 9 case alwaysFresh // Always request new GPS fix 10 10 } 11 11 ··· 20 20 @Published var currentLocation: CLLocation? 21 21 @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined 22 22 23 - /// Maximum age for cached location to be considered fresh (5 minutes) 24 - private let maxLocationAge: TimeInterval = 5 * 60 23 + /// Called when location updates (including background). Set by MainView for uploads. 24 + var onBackgroundLocationUpdate: ((CLLocation) -> Void)? 25 + 26 + /// Maximum age for cached location to be considered fresh (3 minutes) 27 + private let maxLocationAge: TimeInterval = 3 * 60 25 28 26 29 /// Distance filter for continuous updates (reduces background GPS wake) 27 30 private let backgroundDistanceFilter: CLLocationDistance = 40 ··· 82 85 } 83 86 84 87 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 85 - currentLocation = locations.last 88 + guard let location = locations.last else { return } 89 + currentLocation = location 86 90 87 91 #if DEBUG 88 - if let location = locations.last { 89 - let age = Date().timeIntervalSince(location.timestamp) 90 - print("LocationManager: didUpdateLocations age=\(String(format: "%.1f", age))s") 91 - } 92 + let age = Date().timeIntervalSince(location.timestamp) 93 + print("LocationManager: didUpdateLocations age=\(String(format: "%.1f", age))s") 92 94 #endif 93 95 94 - // Resume continuation if waiting for fresh location 96 + onBackgroundLocationUpdate?(location) 97 + 95 98 if let continuation = locationContinuation { 96 99 locationContinuation = nil 97 - continuation.resume(returning: locations.last) 100 + continuation.resume(returning: location) 98 101 } 99 102 } 100 103 101 104 func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 105 + #if DEBUG 102 106 print("Location error: \(error.localizedDescription)") 107 + #endif 103 108 104 109 // Resume continuation with nil on error 105 110 if let continuation = locationContinuation {
+2
apple/Transponder/Transponder/LocationSyncService.swift
··· 148 148 } 149 149 } catch CoreError.StaleLocation { 150 150 // Location is older than last upload, skip silently 151 + #if DEBUG 151 152 print("LocationSync: Skipping stale location upload") 153 + #endif 152 154 return .success 153 155 } catch { 154 156 return .error("Upload failed: \(error.localizedDescription)")
+73 -24
apple/Transponder/Transponder/MainView.swift
··· 47 47 private let profileDetents: Set<PresentationDetent> = [.height(60), .fraction(0.4), .large] 48 48 private let detailDetents: Set<PresentationDetent> = [.height(60), .fraction(0.4)] 49 49 50 + /// Animated binding for detent changes 51 + private var animatedDetentBinding: Binding<PresentationDetent> { 52 + Binding( 53 + get: { selectedDetent }, 54 + set: { newValue in 55 + withAnimation(.easeInOut(duration: 0.2)) { 56 + selectedDetent = newValue 57 + } 58 + } 59 + ) 60 + } 61 + 50 62 /// Computed location for "my location" display - either server or GPS based on toggle 51 63 private var myDisplayLocation: (coordinate: CLLocationCoordinate2D, accuracy: Double)? { 52 64 if showServerLocation, let serverLoc = serverLocation { ··· 159 171 } 160 172 .sheet(isPresented: $showSheet) { 161 173 sheetContentView 162 - .presentationDetents(currentDetents, selection: $selectedDetent) 174 + .presentationDetents(currentDetents, selection: animatedDetentBinding) 163 175 .presentationDragIndicator(.visible) 164 176 .presentationBackgroundInteraction(.enabled(upThrough: .fraction(0.4))) 165 177 .interactiveDismissDisabled() ··· 258 270 triggerInitialCameraFit() 259 271 } 260 272 } 273 + .onAppear { 274 + setupBackgroundLocationCallback() 275 + } 261 276 .onChange(of: pendingCameraAction) { _, action in 262 277 guard let action = action else { return } 263 278 executeCameraAction(action) ··· 266 281 .onChange(of: pendingFriendLink) { _, newLink in 267 282 if let link = newLink { 268 283 if let parsed = try? parseFriendLink(url: link) { 269 - sheetContent = .confirmAddFriend(parsed) 270 - selectedDetent = .medium 284 + withAnimation(.easeInOut(duration: 0.2)) { 285 + sheetContent = .confirmAddFriend(parsed) 286 + selectedDetent = .medium 287 + } 271 288 } 272 289 pendingFriendLink = nil 273 290 } ··· 300 317 isFetchingFriends: isFetchingFriends, 301 318 onRefresh: { fetchFriendsIfNeeded(force: true) }, 302 319 onStartAddFriend: { 303 - sheetContent = .addFriend(.showQR(role: .showFirst, isSecondStep: false, addedName: nil)) 304 - selectedDetent = .large 320 + withAnimation(.easeInOut(duration: 0.2)) { 321 + sheetContent = .addFriend(.showQR(role: .showFirst, isSecondStep: false, addedName: nil)) 322 + selectedDetent = .large 323 + } 305 324 }, 306 325 onShowProfile: { 307 - sheetContent = .profile 326 + withAnimation(.easeInOut(duration: 0.2)) { 327 + sheetContent = .profile 328 + // Only expand if fully collapsed 329 + if selectedDetent == .height(60) { 330 + selectedDetent = .fraction(0.4) 331 + } 332 + } 308 333 // Center on my location 309 334 if let loc = locationManager.currentLocation { 310 335 pendingCameraAction = .centerOn(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude) 311 336 } 312 - // Only expand if fully collapsed 313 - if selectedDetent == .height(60) { 314 - selectedDetent = .fraction(0.4) 315 - } 316 337 }, 317 338 onSelectFriend: { friend in 318 339 selectAndCenterOnFriend(friend) ··· 333 354 serverLocation: $serverLocation, 334 355 serverVersion: serverVersion, 335 356 onBack: { 336 - sheetContent = .friends 357 + withAnimation(.easeInOut(duration: 0.2)) { 358 + sheetContent = .friends 359 + } 337 360 pendingCameraAction = .fitAllFriends 338 361 } 339 362 ) ··· 343 366 FriendDetailSheetContent( 344 367 friend: friend, 345 368 onBack: { 346 - sheetContent = .friends 369 + withAnimation(.easeInOut(duration: 0.2)) { 370 + sheetContent = .friends 371 + } 347 372 pendingCameraAction = .fitAllFriends 348 373 }, 349 374 onToggleShare: { ··· 357 382 onRemove: { 358 383 try? removeFriend(pubkey: friend.pubkey) 359 384 refreshFriends() 360 - sheetContent = .friends 385 + withAnimation(.easeInOut(duration: 0.2)) { 386 + sheetContent = .friends 387 + } 361 388 pendingCameraAction = .fitAllFriends 362 389 }, 363 390 onEditName: { ··· 374 401 ConfirmAddFriendContent( 375 402 friend: friend, 376 403 onConfirm: { 377 - sheetContent = .friends 404 + withAnimation(.easeInOut(duration: 0.2)) { 405 + sheetContent = .friends 406 + } 378 407 handleDeepLink(friend) 379 408 }, 380 409 onCancel: { 381 - sheetContent = .friends 410 + withAnimation(.easeInOut(duration: 0.2)) { 411 + sheetContent = .friends 412 + } 382 413 } 383 414 ) 384 415 ··· 387 418 identityStore: identityStore, 388 419 step: step, 389 420 onNavigate: { newStep in 390 - sheetContent = .addFriend(newStep) 421 + withAnimation(.easeInOut(duration: 0.2)) { 422 + sheetContent = .addFriend(newStep) 423 + } 391 424 }, 392 425 onAddFriend: { link, completion in 393 426 addFriendFromLink(link) { result in ··· 401 434 let friend = friends.first(where: { $0.pubkey == pubkey }) { 402 435 selectAndCenterOnFriend(friend) 403 436 } else { 404 - sheetContent = .friends 405 - selectedDetent = .fraction(0.4) 437 + withAnimation(.easeInOut(duration: 0.2)) { 438 + sheetContent = .friends 439 + selectedDetent = .fraction(0.4) 440 + } 406 441 } 407 442 }, 408 443 onCancel: { 409 - sheetContent = .friends 410 - selectedDetent = .fraction(0.4) 444 + withAnimation(.easeInOut(duration: 0.2)) { 445 + sheetContent = .friends 446 + selectedDetent = .fraction(0.4) 447 + } 411 448 } 412 449 ) 413 450 } ··· 416 453 private func selectAndCenterOnFriend(_ friend: Friend) { 417 454 guard let loc = friend.location else { return } 418 455 pendingCameraAction = .centerOn(latitude: loc.latitude, longitude: loc.longitude) 419 - sheetContent = .friendDetail(friend.pubkey) 420 - selectedDetent = .fraction(0.4) 456 + withAnimation(.easeInOut(duration: 0.2)) { 457 + sheetContent = .friendDetail(friend.pubkey) 458 + selectedDetent = .fraction(0.4) 459 + } 421 460 } 422 461 423 462 private func executeCameraAction(_ action: CameraAction) { ··· 605 644 case .alreadyFriends(let friend): 606 645 selectAndCenterOnFriend(friend) 607 646 case .needsReciprocal(let name): 608 - sheetContent = .addFriend(.showQR(role: .scanFirst, isSecondStep: true, addedName: name)) 609 - selectedDetent = .large 647 + withAnimation(.easeInOut(duration: 0.2)) { 648 + sheetContent = .addFriend(.showQR(role: .scanFirst, isSecondStep: true, addedName: name)) 649 + selectedDetent = .large 650 + } 610 651 case .failed(let message): 611 652 uploadMessage = message 612 653 } ··· 668 709 if case .error(let message) = result { 669 710 uploadMessage = message 670 711 } 712 + } 713 + } 714 + } 715 + 716 + private func setupBackgroundLocationCallback() { 717 + locationManager.onBackgroundLocationUpdate = { _ in 718 + if self.identityStore.autoShareEnabled && !getShareRecipients().isEmpty { 719 + self.uploadCurrentLocationBackground(.cachedOkay) 671 720 } 672 721 } 673 722 }
+42 -12
apple/Transponder/Transponder/OnboardingView.swift
··· 55 55 56 56 var body: some View { 57 57 VStack(spacing: 0) { 58 - // Header area 59 - VStack(spacing: 8) { 60 - Spacer() 61 - .frame(height: 60) 58 + Spacer() 62 59 63 - Image(systemName: "location.circle.fill") 64 - .font(.system(size: 64)) 65 - .foregroundStyle(.blue) 60 + // Header area 61 + VStack(spacing: 12) { 62 + AppIconView() 63 + .frame(width: 80, height: 80) 66 64 67 65 Text("Coords") 68 66 .font(.largeTitle) ··· 79 77 .frame(height: 48) 80 78 81 79 // Feature highlights - Apple style 82 - VStack(spacing: 28) { 80 + VStack(spacing: 24) { 83 81 FeatureItem( 84 82 icon: "lock.fill", 85 83 iconColor: .blue, ··· 127 125 128 126 var body: some View { 129 127 HStack(alignment: .center, spacing: 16) { 130 - // Apple-style icon with colored background 128 + // Liquid Glass style - colored icon on tinted background 131 129 Image(systemName: icon) 132 130 .font(.system(size: 20, weight: .semibold)) 133 - .foregroundStyle(.white) 131 + .foregroundStyle(iconColor) 134 132 .frame(width: 44, height: 44) 135 - .background(iconColor, in: RoundedRectangle(cornerRadius: 10)) 133 + .background(iconColor.opacity(0.15), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) 136 134 137 - VStack(alignment: .leading, spacing: 2) { 135 + VStack(alignment: .leading, spacing: 4) { 138 136 Text(title) 139 137 .font(.body) 140 138 .fontWeight(.semibold) ··· 584 582 } 585 583 } 586 584 } 585 + } 586 + } 587 + 588 + // MARK: - App Icon View 589 + 590 + /// Loads the app icon from the bundle - automatically uses the correct icon 591 + private struct AppIconView: View { 592 + var body: some View { 593 + #if os(iOS) 594 + if let iconName = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any], 595 + let primaryIcon = iconName["CFBundlePrimaryIcon"] as? [String: Any], 596 + let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String], 597 + let lastIcon = iconFiles.last, 598 + let uiImage = UIImage(named: lastIcon) { 599 + Image(uiImage: uiImage) 600 + .resizable() 601 + .scaledToFit() 602 + .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) 603 + } else { 604 + // Fallback to asset catalog 605 + Image("CoordsLogo") 606 + .resizable() 607 + .scaledToFit() 608 + .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) 609 + } 610 + #else 611 + // macOS fallback 612 + Image("CoordsLogo") 613 + .resizable() 614 + .scaledToFit() 615 + .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) 616 + #endif 587 617 } 588 618 } 589 619
+25
apple/Transponder/Transponder/PrivacyInfo.xcprivacy
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 + <plist version="1.0"> 4 + <dict> 5 + <key>NSPrivacyTracking</key> 6 + <false/> 7 + <key>NSPrivacyCollectedDataTypes</key> 8 + <array> 9 + <dict> 10 + <key>NSPrivacyCollectedDataType</key> 11 + <string>NSPrivacyCollectedDataTypePreciseLocation</string> 12 + <key>NSPrivacyCollectedDataTypeLinked</key> 13 + <true/> 14 + <key>NSPrivacyCollectedDataTypeTracking</key> 15 + <false/> 16 + <key>NSPrivacyCollectedDataTypePurposes</key> 17 + <array> 18 + <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string> 19 + </array> 20 + </dict> 21 + </array> 22 + <key>NSPrivacyAccessedAPITypes</key> 23 + <array/> 24 + </dict> 25 + </plist>
+58 -6
apple/Transponder/Transponder/Sheets/AddFriendSheet.swift
··· 214 214 if isProcessing { 215 215 ProgressView("Checking...") 216 216 .padding() 217 - .background(.regularMaterial) 218 - .cornerRadius(8) 217 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 219 218 } else if isFirstStep { 220 219 Text("Point camera at your friend's QR code") 221 220 .font(.subheadline) 222 221 .padding(.vertical, 8) 223 222 .padding(.horizontal, 16) 224 - .background(.regularMaterial) 225 - .cornerRadius(8) 223 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 226 224 } 227 225 228 226 Button { ··· 231 229 Label("Add with Link", systemImage: "link") 232 230 .frame(maxWidth: .infinity) 233 231 .padding(.vertical, 12) 234 - .background(.regularMaterial) 235 - .cornerRadius(10) 232 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous)) 236 233 } 237 234 .padding(.horizontal) 238 235 .padding(.bottom, 8) ··· 369 366 .padding() 370 367 } 371 368 } 369 + 370 + #Preview("Show QR - Step 1") { 371 + AddFriendContent( 372 + identityStore: IdentityStore(), 373 + step: .showQR(role: .showFirst, isSecondStep: false, addedName: nil), 374 + onNavigate: { _ in }, 375 + onAddFriend: { _, _ in }, 376 + onComplete: { _ in }, 377 + onCancel: {} 378 + ) 379 + } 380 + 381 + #Preview("Show QR - Step 2 (Friend Added)") { 382 + AddFriendContent( 383 + identityStore: IdentityStore(), 384 + step: .showQR(role: .showFirst, isSecondStep: true, addedName: "Alice"), 385 + onNavigate: { _ in }, 386 + onAddFriend: { _, _ in }, 387 + onComplete: { _ in }, 388 + onCancel: {} 389 + ) 390 + } 391 + 392 + #Preview("Scan QR - First Step") { 393 + AddFriendContent( 394 + identityStore: IdentityStore(), 395 + step: .scanQR(role: .scanFirst, isFirstStep: true), 396 + onNavigate: { _ in }, 397 + onAddFriend: { _, _ in }, 398 + onComplete: { _ in }, 399 + onCancel: {} 400 + ) 401 + } 402 + 403 + #Preview("Scan QR - Second Step") { 404 + AddFriendContent( 405 + identityStore: IdentityStore(), 406 + step: .scanQR(role: .showFirst, isFirstStep: false), 407 + onNavigate: { _ in }, 408 + onAddFriend: { _, _ in }, 409 + onComplete: { _ in }, 410 + onCancel: {} 411 + ) 412 + } 413 + 414 + #Preview("Link Entry") { 415 + AddFriendContent( 416 + identityStore: IdentityStore(), 417 + step: .linkEntry(role: .showFirst), 418 + onNavigate: { _ in }, 419 + onAddFriend: { _, _ in }, 420 + onComplete: { _ in }, 421 + onCancel: {} 422 + ) 423 + }
+99 -30
apple/Transponder/Transponder/Sheets/FriendDetailSheet.swift
··· 3 3 import UIKit 4 4 #endif 5 5 6 + /// Button style for settings rows with press highlight 7 + private struct SettingsRowButtonStyle: ButtonStyle { 8 + func makeBody(configuration: Configuration) -> some View { 9 + configuration.label 10 + .background(configuration.isPressed ? Color.primary.opacity(0.1) : Color.clear) 11 + .contentShape(Rectangle()) 12 + } 13 + } 14 + 6 15 struct FriendDetailSheetContent: View { 7 16 let friend: Friend 8 17 let onBack: () -> Void ··· 59 68 } trailing: { 60 69 Button(action: onEditName) { 61 70 Image(systemName: "pencil") 62 - .font(.system(size: 16, weight: .semibold)) 63 - .frame(width: 32, height: 32) 64 - .background(Color(.systemGray5)) 65 - .clipShape(Circle()) 71 + .font(.system(size: 18, weight: .semibold)) 72 + .frame(width: 40, height: 40) 73 + .background(.ultraThinMaterial, in: Circle()) 66 74 } 67 75 68 76 Button(action: onBack) { 69 77 Image(systemName: "xmark.circle.fill") 70 - .font(.system(size: 32)) 78 + .font(.system(size: 40)) 79 + .frame(width: 40, height: 40) 71 80 .foregroundStyle(.secondary) 72 81 } 73 82 } ··· 89 98 } 90 99 .frame(maxWidth: .infinity) 91 100 .padding(.vertical, 12) 92 - .background(Color.secondary.opacity(0.1)) 93 - .cornerRadius(10) 101 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 102 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 94 103 } 95 104 } 96 105 #endif 97 106 98 - Divider() 99 - 100 - // Share toggle 101 - Toggle(isOn: Binding( 102 - get: { friend.shareWith }, 103 - set: { _ in onToggleShare() } 104 - )) { 105 - HStack { 106 - Image(systemName: "arrow.up") 107 - .foregroundStyle(.blue) 108 - Text("Share with \(friend.name)") 107 + // Sharing settings group 108 + VStack(spacing: 0) { 109 + Toggle(isOn: Binding( 110 + get: { friend.shareWith }, 111 + set: { _ in onToggleShare() } 112 + )) { 113 + HStack { 114 + Image(systemName: "arrow.up") 115 + .foregroundStyle(.blue) 116 + Text("Share with \(friend.name)") 117 + } 109 118 } 110 - } 119 + .padding(.horizontal, 16) 120 + .padding(.vertical, 12) 111 121 112 - // Fetch toggle 113 - Toggle(isOn: Binding( 114 - get: { friend.fetchFrom }, 115 - set: { _ in onToggleFetch() } 116 - )) { 117 - HStack { 118 - Image(systemName: "arrow.down") 119 - .foregroundStyle(.green) 120 - Text("See \(friend.name)") 122 + Divider() 123 + .padding(.leading, 48) 124 + 125 + Toggle(isOn: Binding( 126 + get: { friend.fetchFrom }, 127 + set: { _ in onToggleFetch() } 128 + )) { 129 + HStack { 130 + Image(systemName: "arrow.down") 131 + .foregroundStyle(.green) 132 + Text("See \(friend.name)") 133 + } 121 134 } 135 + .padding(.horizontal, 16) 136 + .padding(.vertical, 12) 122 137 } 138 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 139 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 123 140 124 141 // Remove button - only when both toggles are off 125 142 if !friend.shareWith && !friend.fetchFrom { 126 - Divider() 127 - 128 143 Button(role: .destructive) { 129 144 showRemoveConfirmation = true 130 145 } label: { 131 146 HStack { 132 147 Image(systemName: "person.badge.minus") 148 + .foregroundStyle(.red) 133 149 Text("Remove Friend") 150 + .foregroundStyle(.red) 151 + Spacer() 134 152 } 153 + .padding(.horizontal, 16) 154 + .padding(.vertical, 12) 135 155 } 156 + .buttonStyle(SettingsRowButtonStyle()) 157 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 158 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 136 159 } 137 160 } 138 161 .padding() ··· 144 167 } 145 168 } 146 169 } 170 + 171 + #Preview("Friend Detail") { 172 + FriendDetailSheetContent( 173 + friend: Friend( 174 + pubkey: "abc123", 175 + server: "https://coord.is", 176 + name: "Alice", 177 + shareWith: true, 178 + fetchFrom: true, 179 + location: Location( 180 + latitude: 37.7749, 181 + longitude: -122.4194, 182 + altitude: 0, 183 + accuracy: 10, 184 + timestamp: UInt64(Date().timeIntervalSince1970 * 1000) 185 + ), 186 + fetchedAt: nil, 187 + color: "#4A90D9" 188 + ), 189 + onBack: {}, 190 + onToggleShare: {}, 191 + onToggleFetch: {}, 192 + onRemove: {}, 193 + onEditName: {} 194 + ) 195 + } 196 + 197 + #Preview("Friend Detail - No Location") { 198 + FriendDetailSheetContent( 199 + friend: Friend( 200 + pubkey: "def456", 201 + server: "https://coord.is", 202 + name: "Bob", 203 + shareWith: false, 204 + fetchFrom: false, 205 + location: nil, 206 + fetchedAt: nil, 207 + color: "#50C878" 208 + ), 209 + onBack: {}, 210 + onToggleShare: {}, 211 + onToggleFetch: {}, 212 + onRemove: {}, 213 + onEditName: {} 214 + ) 215 + }
+73 -18
apple/Transponder/Transponder/Sheets/FriendsSheet.swift
··· 21 21 SheetHeader { 22 22 Text("Friends") 23 23 .font(.title2.bold()) 24 - .frame(height: 32) 25 24 } trailing: { 26 25 if isEditMode { 27 26 // Refresh button in edit mode ··· 33 32 Image(systemName: "arrow.clockwise") 34 33 } 35 34 } 36 - .font(.system(size: 16, weight: .semibold)) 37 - .frame(width: 32, height: 32) 38 - .background(Color(.systemGray5)) 39 - .clipShape(Circle()) 35 + .font(.system(size: 18, weight: .semibold)) 36 + .frame(width: 40, height: 40) 37 + .background(.ultraThinMaterial, in: Circle()) 40 38 } 41 39 .disabled(isFetchingFriends) 42 40 43 41 // Done editing 44 42 Button(action: { isEditMode = false }) { 45 43 Image(systemName: "checkmark") 46 - .font(.system(size: 16, weight: .semibold)) 47 - .frame(width: 32, height: 32) 48 - .background(Color(.systemGray5)) 49 - .clipShape(Circle()) 44 + .font(.system(size: 18, weight: .semibold)) 45 + .frame(width: 40, height: 40) 46 + .background(.ultraThinMaterial, in: Circle()) 50 47 } 51 48 } else { 52 49 // Add friend 53 50 Button(action: onStartAddFriend) { 54 51 Image(systemName: "plus") 55 - .font(.system(size: 16, weight: .semibold)) 56 - .frame(width: 32, height: 32) 57 - .background(Color(.systemGray5)) 58 - .clipShape(Circle()) 52 + .font(.system(size: 18, weight: .semibold)) 53 + .frame(width: 40, height: 40) 54 + .background(.ultraThinMaterial, in: Circle()) 59 55 } 60 56 61 57 // Edit mode toggle 62 58 Button(action: { isEditMode = true }) { 63 59 Image(systemName: "pencil") 64 - .font(.system(size: 16, weight: .semibold)) 65 - .frame(width: 32, height: 32) 66 - .background(Color(.systemGray5)) 67 - .clipShape(Circle()) 60 + .font(.system(size: 18, weight: .semibold)) 61 + .frame(width: 40, height: 40) 62 + .background(.ultraThinMaterial, in: Circle()) 68 63 } 69 64 70 65 // Profile 71 66 Button(action: onShowProfile) { 72 67 Image(systemName: "person.circle.fill") 73 - .font(.system(size: 32)) 68 + .font(.system(size: 40)) 69 + .frame(width: 40, height: 40) 74 70 } 75 71 } 76 72 } ··· 120 116 } 121 117 } 122 118 } 119 + 120 + #Preview("Friends Sheet - Empty") { 121 + FriendsSheetContent( 122 + identityStore: IdentityStore(), 123 + friends: [], 124 + currentLocation: nil, 125 + isFetchingFriends: false, 126 + onRefresh: {}, 127 + onStartAddFriend: {}, 128 + onShowProfile: {}, 129 + onSelectFriend: { _ in }, 130 + onToggleShare: { _ in }, 131 + onToggleFetch: { _ in }, 132 + onDeleteFriend: { _ in } 133 + ) 134 + } 135 + 136 + #Preview("Friends Sheet - With Friends") { 137 + FriendsSheetContent( 138 + identityStore: IdentityStore(), 139 + friends: [ 140 + Friend( 141 + pubkey: "abc123", 142 + server: "https://coord.is", 143 + name: "Alice", 144 + shareWith: true, 145 + fetchFrom: true, 146 + location: Location( 147 + latitude: 37.7749, 148 + longitude: -122.4194, 149 + altitude: 0, 150 + accuracy: 10, 151 + timestamp: UInt64(Date().timeIntervalSince1970 * 1000) 152 + ), 153 + fetchedAt: nil, 154 + color: "#4A90D9" 155 + ), 156 + Friend( 157 + pubkey: "def456", 158 + server: "https://coord.is", 159 + name: "Bob", 160 + shareWith: true, 161 + fetchFrom: false, 162 + location: nil, 163 + fetchedAt: nil, 164 + color: "#50C878" 165 + ) 166 + ], 167 + currentLocation: nil, 168 + isFetchingFriends: false, 169 + onRefresh: {}, 170 + onStartAddFriend: {}, 171 + onShowProfile: {}, 172 + onSelectFriend: { _ in }, 173 + onToggleShare: { _ in }, 174 + onToggleFetch: { _ in }, 175 + onDeleteFriend: { _ in } 176 + ) 177 + }
+137 -93
apple/Transponder/Transponder/Sheets/ProfileSheet.swift
··· 1 1 import SwiftUI 2 2 import CoreLocation 3 3 4 + /// Button style for settings rows with press highlight 5 + private struct SettingsRowButtonStyle: ButtonStyle { 6 + func makeBody(configuration: Configuration) -> some View { 7 + configuration.label 8 + .background(configuration.isPressed ? Color.primary.opacity(0.1) : Color.clear) 9 + .contentShape(Rectangle()) 10 + } 11 + } 12 + 4 13 struct ProfileSheetContent: View { 5 14 @ObservedObject var identityStore: IdentityStore 6 15 @ObservedObject var locationManager: LocationManager ··· 85 94 Image(systemName: "icloud.and.arrow.up") 86 95 } 87 96 } 88 - .font(.system(size: 16, weight: .semibold)) 89 - .frame(width: 32, height: 32) 90 - .background(Color(.systemGray5)) 91 - .clipShape(Circle()) 97 + .font(.system(size: 18, weight: .semibold)) 98 + .frame(width: 40, height: 40) 99 + .background(.ultraThinMaterial, in: Circle()) 92 100 } 93 101 .disabled(isUploading) 94 102 95 103 Button(action: { showingEditName = true }) { 96 104 Image(systemName: "pencil") 97 - .font(.system(size: 16, weight: .semibold)) 98 - .frame(width: 32, height: 32) 99 - .background(Color(.systemGray5)) 100 - .clipShape(Circle()) 105 + .font(.system(size: 18, weight: .semibold)) 106 + .frame(width: 40, height: 40) 107 + .background(.ultraThinMaterial, in: Circle()) 101 108 } 102 109 103 110 Button(action: onBack) { 104 111 Image(systemName: "xmark.circle.fill") 105 - .font(.system(size: 32)) 112 + .font(.system(size: 40)) 113 + .frame(width: 40, height: 40) 106 114 .foregroundStyle(.secondary) 107 115 } 108 116 } 109 117 110 118 ScrollView { 111 - VStack(spacing: 20) { 112 - 113 - if let message = uploadMessage { 114 - Text(message) 115 - .font(.caption) 116 - .foregroundStyle(.secondary) 117 - } 118 - 119 - // Server location toggle 120 - Toggle(isOn: $showServerLocation) { 121 - HStack { 122 - Image(systemName: "cloud.fill") 123 - .foregroundStyle(.orange) 124 - Text("Show server location") 125 - } 126 - } 127 - .padding(.horizontal) 128 - .onChange(of: showServerLocation) { _, newValue in 129 - if newValue { fetchServerLocation() } 130 - } 131 - 132 - // Auto-share toggle 133 - Toggle(isOn: Binding( 134 - get: { identityStore.autoShareEnabled }, 135 - set: { newValue in 136 - if newValue { 137 - // Check if we have Always permission 138 - if BackgroundSyncManager.shared.hasAlwaysAuthorization { 139 - identityStore.setAutoShareEnabled(true) 140 - BackgroundSyncManager.shared.startMonitoringSignificantLocationChanges() 141 - BackgroundSyncManager.shared.scheduleBackgroundRefresh() 142 - } else { 143 - // Request permission 144 - BackgroundSyncManager.shared.requestAlwaysAuthorization() 145 - } 146 - } else { 147 - identityStore.setAutoShareEnabled(false) 148 - BackgroundSyncManager.shared.stopMonitoringSignificantLocationChanges() 149 - } 150 - } 151 - )) { 152 - HStack { 153 - Image(systemName: "location.fill") 154 - .foregroundStyle(.blue) 155 - VStack(alignment: .leading) { 156 - Text("Share automatically") 157 - Text(identityStore.autoShareEnabled ? "Sharing in background" : "Manual sharing only") 158 - .font(.caption) 159 - .foregroundStyle(.secondary) 160 - } 161 - } 162 - } 163 - .padding(.horizontal) 164 - 119 + VStack(spacing: 16) { 165 120 // Share my link button 166 121 Button { showingMyLink = true } label: { 167 122 HStack { ··· 170 125 } 171 126 .frame(maxWidth: .infinity) 172 127 .padding(.vertical, 12) 173 - .background(Color.secondary.opacity(0.1)) 174 - .cornerRadius(10) 128 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 129 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 175 130 } 176 - .padding(.horizontal) 177 131 178 - Divider() 179 - .padding(.vertical, 8) 132 + // Location settings group 133 + VStack(spacing: 0) { 134 + Toggle(isOn: $showServerLocation) { 135 + HStack { 136 + Image(systemName: "cloud.fill") 137 + .foregroundStyle(.orange) 138 + Text("Show server location") 139 + } 140 + } 141 + .padding(.horizontal, 16) 142 + .padding(.vertical, 12) 143 + .onChange(of: showServerLocation) { _, newValue in 144 + if newValue { fetchServerLocation() } 145 + } 180 146 181 - // Server URL 182 - Button { showingEditServer = true } label: { 183 - HStack { 184 - VStack(alignment: .leading, spacing: 2) { 185 - Text(serverVersion != nil ? "Server v\(serverVersion!)" : "Server") 186 - .font(.subheadline) 187 - .foregroundStyle(.primary) 188 - Text(identityStore.serverUrl) 189 - .font(.caption) 190 - .foregroundStyle(.secondary) 191 - .lineLimit(1) 147 + Divider() 148 + .padding(.leading, 48) 149 + 150 + Toggle(isOn: Binding( 151 + get: { identityStore.autoShareEnabled }, 152 + set: { newValue in 153 + if newValue { 154 + if BackgroundSyncManager.shared.hasAlwaysAuthorization { 155 + identityStore.setAutoShareEnabled(true) 156 + BackgroundSyncManager.shared.startMonitoringSignificantLocationChanges() 157 + BackgroundSyncManager.shared.scheduleBackgroundRefresh() 158 + } else { 159 + BackgroundSyncManager.shared.requestAlwaysAuthorization() 160 + } 161 + } else { 162 + identityStore.setAutoShareEnabled(false) 163 + BackgroundSyncManager.shared.stopMonitoringSignificantLocationChanges() 164 + } 192 165 } 193 - Spacer() 194 - Image(systemName: "chevron.right") 195 - .font(.caption) 196 - .foregroundStyle(.tertiary) 166 + )) { 167 + HStack { 168 + Image(systemName: "location.fill") 169 + .foregroundStyle(.blue) 170 + VStack(alignment: .leading) { 171 + Text("Share automatically") 172 + Text(identityStore.autoShareEnabled ? "Sharing in background" : "Manual sharing only") 173 + .font(.caption) 174 + .foregroundStyle(.secondary) 175 + } 176 + } 197 177 } 198 - .padding(.horizontal) 178 + .padding(.horizontal, 16) 179 + .padding(.vertical, 12) 199 180 } 181 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 182 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 200 183 201 - // App and core version 202 - Button { showingLicenses = true } label: { 203 - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?" 204 - Text("Coords iOS v\(appVersion) · Core \(getVersion())") 184 + if let message = uploadMessage { 185 + Text(message) 205 186 .font(.caption) 206 187 .foregroundStyle(.secondary) 207 188 } 208 - .padding(.horizontal) 209 - .padding(.top, 8) 189 + 190 + // App info group 191 + VStack(spacing: 0) { 192 + Button { showingEditServer = true } label: { 193 + HStack { 194 + Image(systemName: "server.rack") 195 + .foregroundStyle(.purple) 196 + VStack(alignment: .leading, spacing: 2) { 197 + Text(serverVersion != nil ? "Server v\(serverVersion!)" : "Server") 198 + .foregroundStyle(.primary) 199 + Text(identityStore.serverUrl) 200 + .font(.caption) 201 + .foregroundStyle(.secondary) 202 + .lineLimit(1) 203 + } 204 + Spacer() 205 + Image(systemName: "chevron.right") 206 + .font(.caption) 207 + .foregroundStyle(.tertiary) 208 + } 209 + .padding(.horizontal, 16) 210 + .padding(.vertical, 12) 211 + } 212 + .buttonStyle(SettingsRowButtonStyle()) 213 + 214 + Divider() 215 + .padding(.leading, 48) 216 + 217 + Button { showingLicenses = true } label: { 218 + HStack { 219 + Image(systemName: "info.circle") 220 + .foregroundStyle(.gray) 221 + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "?" 222 + VStack(alignment: .leading, spacing: 2) { 223 + Text("About") 224 + .foregroundStyle(.primary) 225 + Text("v\(appVersion) · Core \(getVersion())") 226 + .font(.caption) 227 + .foregroundStyle(.secondary) 228 + } 229 + Spacer() 230 + Image(systemName: "chevron.right") 231 + .font(.caption) 232 + .foregroundStyle(.tertiary) 233 + } 234 + .padding(.horizontal, 16) 235 + .padding(.vertical, 12) 236 + } 237 + .buttonStyle(SettingsRowButtonStyle()) 238 + } 239 + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 240 + .shadow(color: .black.opacity(0.08), radius: 4, y: 2) 210 241 } 211 - .padding(.bottom, 20) 242 + .padding() 212 243 } 213 244 } 214 245 .sheet(isPresented: $showingLicenses) { ··· 265 296 } 266 297 .frame(maxWidth: .infinity) 267 298 .padding(.vertical, 12) 268 - .background(.blue) 299 + .background(.blue, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) 269 300 .foregroundStyle(.white) 270 - .cornerRadius(10) 271 301 } 272 302 .disabled(isValidatingServer || editedServerUrl.isEmpty) 273 303 ··· 371 401 } 372 402 } 373 403 } 404 + 405 + #Preview("Profile Sheet") { 406 + ProfileSheetContent( 407 + identityStore: IdentityStore(), 408 + locationManager: LocationManager(), 409 + syncService: LocationSyncService(), 410 + isUploading: .constant(false), 411 + uploadMessage: .constant(nil), 412 + showServerLocation: .constant(false), 413 + serverLocation: .constant(nil), 414 + serverVersion: "1.0.0", 415 + onBack: {} 416 + ) 417 + }
+4
apple/Transponder/Transponder/TransponderApp.swift
··· 49 49 fromDomain: "transponder.bentley.sh", 50 50 toDomain: "coord.is" 51 51 ) 52 + #if DEBUG 52 53 if migrated > 0 { 53 54 print("Migrated \(migrated) friend(s) to coord.is") 54 55 } 56 + #endif 55 57 } catch { 58 + #if DEBUG 56 59 print("Failed to initialize storage: \(error)") 60 + #endif 57 61 } 58 62 59 63 // Migrate user's own server URL
+26 -16
apple/Transponder/Transponder.xcodeproj/project.pbxproj
··· 8 8 9 9 /* Begin PBXBuildFile section */ 10 10 374905C82F0EC50800A225C4 /* transponder_core.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 374905C72F0EC50800A225C4 /* transponder_core.xcframework */; }; 11 + 377F279F2F296B1A0010B5E7 /* Coords.icon in Resources */ = {isa = PBXBuildFile; fileRef = 377F279E2F296B1A0010B5E7 /* Coords.icon */; }; 11 12 /* End PBXBuildFile section */ 12 13 13 14 /* Begin PBXFileReference section */ 14 15 374905962F0D530600A225C4 /* Transponder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transponder.app; sourceTree = BUILT_PRODUCTS_DIR; }; 15 16 374905C72F0EC50800A225C4 /* transponder_core.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = transponder_core.xcframework; sourceTree = "<group>"; }; 17 + 377F279E2F296B1A0010B5E7 /* Coords.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = Coords.icon; sourceTree = "<group>"; }; 16 18 /* End PBXFileReference section */ 17 19 18 20 /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ ··· 52 54 3749058D2F0D530600A225C4 = { 53 55 isa = PBXGroup; 54 56 children = ( 57 + 377F279E2F296B1A0010B5E7 /* Coords.icon */, 55 58 374905982F0D530600A225C4 /* Transponder */, 56 59 374905C62F0EC50700A225C4 /* Frameworks */, 57 60 374905972F0D530600A225C4 /* Products */, ··· 139 142 isa = PBXResourcesBuildPhase; 140 143 buildActionMask = 2147483647; 141 144 files = ( 145 + 377F279F2F296B1A0010B5E7 /* Coords.icon in Resources */, 142 146 ); 143 147 runOnlyForDeploymentPostprocessing = 0; 144 148 }; ··· 296 300 374905A22F0D530800A225C4 /* Debug */ = { 297 301 isa = XCBuildConfiguration; 298 302 buildSettings = { 299 - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 + ASSETCATALOG_COMPILER_APPICON_NAME = Coords; 300 304 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 301 305 CODE_SIGN_STYLE = Automatic; 302 - CURRENT_PROJECT_VERSION = 8; 306 + CURRENT_PROJECT_VERSION = 1; 303 307 DEVELOPMENT_TEAM = BG4AR9DHD6; 304 308 ENABLE_APP_SANDBOX = YES; 305 309 ENABLE_HARDENED_RUNTIME = YES; ··· 318 322 ENABLE_USER_SELECTED_FILES = readonly; 319 323 GENERATE_INFOPLIST_FILE = YES; 320 324 INFOPLIST_FILE = Transponder/Info.plist; 321 - INFOPLIST_KEY_NSCameraUsageDescription = "Transponder uses the camera to scan friend QR codes."; 322 - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Transponder needs your location to share with friends."; 323 - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Transponder needs your location to share with friends."; 325 + INFOPLIST_KEY_CFBundleDisplayName = Coords; 326 + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; 327 + INFOPLIST_KEY_NSCameraUsageDescription = "Coords uses the camera to scan friend QR codes."; 328 + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Coords needs your location to share with friends."; 329 + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Coords needs your location to share with friends."; 324 330 "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 325 331 "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 326 332 "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; ··· 335 341 LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 336 342 "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 337 343 MACOSX_DEPLOYMENT_TARGET = 15.7; 338 - MARKETING_VERSION = 0.8; 344 + MARKETING_VERSION = 1.0; 339 345 PRODUCT_BUNDLE_IDENTIFIER = sh.bentley.Transponder; 340 346 PRODUCT_NAME = "$(TARGET_NAME)"; 341 347 REGISTER_APP_GROUPS = YES; 342 348 SDKROOT = auto; 343 349 STRING_CATALOG_GENERATE_SYMBOLS = YES; 344 - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 350 + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 351 + SUPPORTS_MACCATALYST = NO; 345 352 SWIFT_APPROACHABLE_CONCURRENCY = YES; 346 353 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; 347 354 SWIFT_EMIT_LOC_STRINGS = YES; 348 355 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; 349 356 SWIFT_VERSION = 5.0; 350 - TARGETED_DEVICE_FAMILY = "1,2,7"; 357 + TARGETED_DEVICE_FAMILY = 1; 351 358 XROS_DEPLOYMENT_TARGET = 26.2; 352 359 }; 353 360 name = Debug; ··· 355 362 374905A32F0D530800A225C4 /* Release */ = { 356 363 isa = XCBuildConfiguration; 357 364 buildSettings = { 358 - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 + ASSETCATALOG_COMPILER_APPICON_NAME = Coords; 359 366 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 360 367 CODE_SIGN_STYLE = Automatic; 361 - CURRENT_PROJECT_VERSION = 8; 368 + CURRENT_PROJECT_VERSION = 1; 362 369 DEVELOPMENT_TEAM = BG4AR9DHD6; 363 370 ENABLE_APP_SANDBOX = YES; 364 371 ENABLE_HARDENED_RUNTIME = YES; ··· 377 384 ENABLE_USER_SELECTED_FILES = readonly; 378 385 GENERATE_INFOPLIST_FILE = YES; 379 386 INFOPLIST_FILE = Transponder/Info.plist; 380 - INFOPLIST_KEY_NSCameraUsageDescription = "Transponder uses the camera to scan friend QR codes."; 381 - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Transponder needs your location to share with friends."; 382 - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Transponder needs your location to share with friends."; 387 + INFOPLIST_KEY_CFBundleDisplayName = Coords; 388 + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; 389 + INFOPLIST_KEY_NSCameraUsageDescription = "Coords uses the camera to scan friend QR codes."; 390 + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "Coords needs your location to share with friends."; 391 + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Coords needs your location to share with friends."; 383 392 "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 384 393 "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 385 394 "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; ··· 394 403 LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 395 404 "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 396 405 MACOSX_DEPLOYMENT_TARGET = 15.7; 397 - MARKETING_VERSION = 0.8; 406 + MARKETING_VERSION = 1.0; 398 407 PRODUCT_BUNDLE_IDENTIFIER = sh.bentley.Transponder; 399 408 PRODUCT_NAME = "$(TARGET_NAME)"; 400 409 REGISTER_APP_GROUPS = YES; 401 410 SDKROOT = auto; 402 411 STRING_CATALOG_GENERATE_SYMBOLS = YES; 403 - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; 412 + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 413 + SUPPORTS_MACCATALYST = NO; 404 414 SWIFT_APPROACHABLE_CONCURRENCY = YES; 405 415 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; 406 416 SWIFT_EMIT_LOC_STRINGS = YES; 407 417 SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; 408 418 SWIFT_VERSION = 5.0; 409 - TARGETED_DEVICE_FAMILY = "1,2,7"; 419 + TARGETED_DEVICE_FAMILY = 1; 410 420 XROS_DEPLOYMENT_TARGET = 26.2; 411 421 }; 412 422 name = Release;
+31
apple/app-store-description.txt
··· 1 + Share your location with friends—and only your friends. 2 + 3 + Coords lets you share your real-time location with the people you trust, using end-to-end encryption that keeps your whereabouts private from everyone else - including the server. 4 + 5 + How it works 6 + 7 + Add friends by scanning QR codes or sharing links. Your devices exchange cryptographic keys directly, so there's no server in the middle. When you share your location, it's encrypted on your device before it ever leaves. Only your friends can decrypt it. 8 + 9 + Privacy by design 10 + 11 + • End-to-end encrypted—the server only sees data it can't read 12 + • No accounts—your identity is just a cryptographic key 13 + • No tracking—we don't log who views whose location 14 + • No history—only your latest location is stored 15 + • Open source—audit the code yourself 16 + 17 + Features 18 + 19 + • See friends on a map in real-time 20 + • Share continuously or on-demand 21 + • Add friends via QR code or link 22 + • Works in the background 23 + • Clean, simple interface 24 + 25 + Why Coords? 26 + 27 + Other location-sharing apps require accounts, collect your data, and could expose your movements if breached. Coords is different. Even if the server were compromised, attackers would only find encrypted data they can't decrypt. Your location stays between you and your friends. 28 + 29 + Open source & self-hostable 30 + 31 + Coords is fully open source and supports federation, so you can run your own server no matter what server your friends use. The iOS app, Android app, server, and cryptographic core are all available for review at coord.is.
+2033
apple/encryption-documentation.pdf
··· 1 + %PDF-1.7 2 + %���� 3 + 4 + 1 0 obj 5 + << 6 + /Type /Pages 7 + /Count 2 8 + /Kids [132 0 R 136 0 R] 9 + >> 10 + endobj 11 + 12 + 2 0 obj 13 + << 14 + /Type /Outlines 15 + /First 3 0 R 16 + /Last 9 0 R 17 + /Count 7 18 + >> 19 + endobj 20 + 21 + 3 0 obj 22 + << 23 + /Parent 2 0 R 24 + /Next 4 0 R 25 + /Title (1. Overview) 26 + /Dest 125 0 R 27 + >> 28 + endobj 29 + 30 + 4 0 obj 31 + << 32 + /Parent 2 0 R 33 + /Next 5 0 R 34 + /Prev 3 0 R 35 + /Title (2. Encryption Algorithms Used) 36 + /Dest 126 0 R 37 + >> 38 + endobj 39 + 40 + 5 0 obj 41 + << 42 + /Parent 2 0 R 43 + /Next 6 0 R 44 + /Prev 4 0 R 45 + /Title (3. Implementation) 46 + /Dest 127 0 R 47 + >> 48 + endobj 49 + 50 + 6 0 obj 51 + << 52 + /Parent 2 0 R 53 + /Next 7 0 R 54 + /Prev 5 0 R 55 + /Title (4. Purpose of Encryption) 56 + /Dest 128 0 R 57 + >> 58 + endobj 59 + 60 + 7 0 obj 61 + << 62 + /Parent 2 0 R 63 + /Next 8 0 R 64 + /Prev 6 0 R 65 + /Title (5. Exemption Qualification) 66 + /Dest 129 0 R 67 + >> 68 + endobj 69 + 70 + 8 0 obj 71 + << 72 + /Parent 2 0 R 73 + /Next 9 0 R 74 + /Prev 7 0 R 75 + /Title (6. Data Flow) 76 + /Dest 130 0 R 77 + >> 78 + endobj 79 + 80 + 9 0 obj 81 + << 82 + /Parent 2 0 R 83 + /Prev 8 0 R 84 + /Title (7. Contact Information) 85 + /Dest 131 0 R 86 + >> 87 + endobj 88 + 89 + 10 0 obj 90 + << 91 + /Type /StructTreeRoot 92 + /RoleMap << 93 + /Datetime /Span 94 + /Terms /Part 95 + /Title /P 96 + /Strong /Span 97 + /Em /Span 98 + >> 99 + /K [13 0 R] 100 + /ParentTree << 101 + /Nums [0 11 0 R 1 19 0 R 2 16 0 R 3 12 0 R] 102 + >> 103 + /ParentTreeNextKey 4 104 + >> 105 + endobj 106 + 107 + 11 0 obj 108 + [104 0 R 103 0 R 101 0 R 102 0 R 100 0 R 99 0 R 99 0 R 98 0 R 98 0 R 97 0 R 97 0 R 96 0 R 95 0 R 93 0 R 91 0 R 88 0 R 87 0 R 86 0 R 84 0 R 83 0 R 82 0 R 80 0 R 79 0 R 78 0 R 75 0 R 75 0 R 73 0 R 74 0 R 73 0 R 73 0 R 73 0 R 72 0 R 72 0 R 71 0 R 70 0 R 69 0 R 68 0 R 68 0 R 65 0 R 64 0 R 63 0 R 63 0 R 63 0 R 59 0 R 59 0 R 58 0 R 58 0 R 57 0 R 56 0 R 54 0 R 53 0 R 51 0 R 50 0 R 48 0 R 47 0 R 45 0 R 44 0 R] 109 + endobj 110 + 111 + 12 0 obj 112 + [41 0 R 41 0 R 40 0 R 39 0 R 38 0 R 37 0 R 36 0 R 35 0 R 34 0 R 33 0 R 32 0 R 30 0 R 29 0 R 28 0 R 27 0 R 26 0 R 25 0 R 24 0 R 23 0 R 22 0 R 20 0 R 20 0 R 18 0 R 18 0 R 19 0 R 15 0 R 15 0 R 17 0 R 15 0 R 16 0 R 14 0 R 14 0 R 14 0 R] 113 + endobj 114 + 115 + 13 0 obj 116 + << 117 + /Type /StructElem 118 + /S /Document 119 + /P 10 0 R 120 + /K [104 0 R 103 0 R 101 0 R 100 0 R 99 0 R 98 0 R 97 0 R 96 0 R 76 0 R 75 0 R 73 0 R 72 0 R 71 0 R 60 0 R 59 0 R 58 0 R 42 0 R 41 0 R 39 0 R 31 0 R 29 0 R 21 0 R 20 0 R 18 0 R 15 0 R 14 0 R] 121 + >> 122 + endobj 123 + 124 + 14 0 obj 125 + << 126 + /Type /StructElem 127 + /S /P 128 + /P 13 0 R 129 + /K [30 31 32] 130 + /Pg 136 0 R 131 + >> 132 + endobj 133 + 134 + 15 0 obj 135 + << 136 + /Type /StructElem 137 + /S /P 138 + /P 13 0 R 139 + /K [25 26 17 0 R 28 16 0 R] 140 + /Pg 136 0 R 141 + >> 142 + endobj 143 + 144 + 16 0 obj 145 + << 146 + /Type /StructElem 147 + /S /Link 148 + /P 15 0 R 149 + /K [29 << 150 + /Type /OBJR 151 + /Pg 136 0 R 152 + /Obj 135 0 R 153 + >>] 154 + /Pg 136 0 R 155 + >> 156 + endobj 157 + 158 + 17 0 obj 159 + << 160 + /Type /StructElem 161 + /S /Code 162 + /P 15 0 R 163 + /K [27] 164 + /Pg 136 0 R 165 + >> 166 + endobj 167 + 168 + 18 0 obj 169 + << 170 + /Type /StructElem 171 + /S /P 172 + /P 13 0 R 173 + /K [22 23 19 0 R] 174 + /Pg 136 0 R 175 + >> 176 + endobj 177 + 178 + 19 0 obj 179 + << 180 + /Type /StructElem 181 + /S /Link 182 + /P 18 0 R 183 + /K [24 << 184 + /Type /OBJR 185 + /Pg 136 0 R 186 + /Obj 134 0 R 187 + >>] 188 + /Pg 136 0 R 189 + >> 190 + endobj 191 + 192 + 20 0 obj 193 + << 194 + /Type /StructElem 195 + /S /H1 196 + /P 13 0 R 197 + /T (Contact Information) 198 + /K [20 21] 199 + /Pg 136 0 R 200 + >> 201 + endobj 202 + 203 + 21 0 obj 204 + << 205 + /Type /StructElem 206 + /S /Code 207 + /P 13 0 R 208 + /A [<< 209 + /O /Layout 210 + /Placement /Block 211 + >>] 212 + /K [28 0 R 27 0 R 26 0 R 25 0 R 24 0 R 23 0 R 22 0 R] 213 + >> 214 + endobj 215 + 216 + 22 0 obj 217 + << 218 + /Type /StructElem 219 + /S /P 220 + /P 21 0 R 221 + /K [19] 222 + /Pg 136 0 R 223 + >> 224 + endobj 225 + 226 + 23 0 obj 227 + << 228 + /Type /StructElem 229 + /S /P 230 + /P 21 0 R 231 + /K [18] 232 + /Pg 136 0 R 233 + >> 234 + endobj 235 + 236 + 24 0 obj 237 + << 238 + /Type /StructElem 239 + /S /P 240 + /P 21 0 R 241 + /K [17] 242 + /Pg 136 0 R 243 + >> 244 + endobj 245 + 246 + 25 0 obj 247 + << 248 + /Type /StructElem 249 + /S /P 250 + /P 21 0 R 251 + /K [16] 252 + /Pg 136 0 R 253 + >> 254 + endobj 255 + 256 + 26 0 obj 257 + << 258 + /Type /StructElem 259 + /S /P 260 + /P 21 0 R 261 + /K [15] 262 + /Pg 136 0 R 263 + >> 264 + endobj 265 + 266 + 27 0 obj 267 + << 268 + /Type /StructElem 269 + /S /P 270 + /P 21 0 R 271 + /K [14] 272 + /Pg 136 0 R 273 + >> 274 + endobj 275 + 276 + 28 0 obj 277 + << 278 + /Type /StructElem 279 + /S /P 280 + /P 21 0 R 281 + /K [13] 282 + /Pg 136 0 R 283 + >> 284 + endobj 285 + 286 + 29 0 obj 287 + << 288 + /Type /StructElem 289 + /S /P 290 + /P 13 0 R 291 + /K [30 0 R 12] 292 + /Pg 136 0 R 293 + >> 294 + endobj 295 + 296 + 30 0 obj 297 + << 298 + /Type /StructElem 299 + /S /Strong 300 + /P 29 0 R 301 + /K [11] 302 + /Pg 136 0 R 303 + >> 304 + endobj 305 + 306 + 31 0 obj 307 + << 308 + /Type /StructElem 309 + /S /Code 310 + /P 13 0 R 311 + /A [<< 312 + /O /Layout 313 + /Placement /Block 314 + >>] 315 + /K [38 0 R 37 0 R 36 0 R 35 0 R 34 0 R 33 0 R 32 0 R] 316 + >> 317 + endobj 318 + 319 + 32 0 obj 320 + << 321 + /Type /StructElem 322 + /S /P 323 + /P 31 0 R 324 + /K [10] 325 + /Pg 136 0 R 326 + >> 327 + endobj 328 + 329 + 33 0 obj 330 + << 331 + /Type /StructElem 332 + /S /P 333 + /P 31 0 R 334 + /K [9] 335 + /Pg 136 0 R 336 + >> 337 + endobj 338 + 339 + 34 0 obj 340 + << 341 + /Type /StructElem 342 + /S /P 343 + /P 31 0 R 344 + /K [8] 345 + /Pg 136 0 R 346 + >> 347 + endobj 348 + 349 + 35 0 obj 350 + << 351 + /Type /StructElem 352 + /S /P 353 + /P 31 0 R 354 + /K [7] 355 + /Pg 136 0 R 356 + >> 357 + endobj 358 + 359 + 36 0 obj 360 + << 361 + /Type /StructElem 362 + /S /P 363 + /P 31 0 R 364 + /K [6] 365 + /Pg 136 0 R 366 + >> 367 + endobj 368 + 369 + 37 0 obj 370 + << 371 + /Type /StructElem 372 + /S /P 373 + /P 31 0 R 374 + /K [5] 375 + /Pg 136 0 R 376 + >> 377 + endobj 378 + 379 + 38 0 obj 380 + << 381 + /Type /StructElem 382 + /S /P 383 + /P 31 0 R 384 + /K [4] 385 + /Pg 136 0 R 386 + >> 387 + endobj 388 + 389 + 39 0 obj 390 + << 391 + /Type /StructElem 392 + /S /P 393 + /P 13 0 R 394 + /K [40 0 R 3] 395 + /Pg 136 0 R 396 + >> 397 + endobj 398 + 399 + 40 0 obj 400 + << 401 + /Type /StructElem 402 + /S /Strong 403 + /P 39 0 R 404 + /K [2] 405 + /Pg 136 0 R 406 + >> 407 + endobj 408 + 409 + 41 0 obj 410 + << 411 + /Type /StructElem 412 + /S /H1 413 + /P 13 0 R 414 + /T (Data Flow) 415 + /K [0 1] 416 + /Pg 136 0 R 417 + >> 418 + endobj 419 + 420 + 42 0 obj 421 + << 422 + /Type /StructElem 423 + /S /L 424 + /P 13 0 R 425 + /A [<< 426 + /O /List 427 + /ListNumbering /Circle 428 + >>] 429 + /K [55 0 R 52 0 R 49 0 R 46 0 R 43 0 R] 430 + >> 431 + endobj 432 + 433 + 43 0 obj 434 + << 435 + /Type /StructElem 436 + /S /LI 437 + /P 42 0 R 438 + /K [45 0 R 44 0 R] 439 + >> 440 + endobj 441 + 442 + 44 0 obj 443 + << 444 + /Type /StructElem 445 + /S /LBody 446 + /P 43 0 R 447 + /K [56] 448 + /Pg 132 0 R 449 + >> 450 + endobj 451 + 452 + 45 0 obj 453 + << 454 + /Type /StructElem 455 + /S /Lbl 456 + /P 43 0 R 457 + /K [55] 458 + /Pg 132 0 R 459 + >> 460 + endobj 461 + 462 + 46 0 obj 463 + << 464 + /Type /StructElem 465 + /S /LI 466 + /P 42 0 R 467 + /K [48 0 R 47 0 R] 468 + >> 469 + endobj 470 + 471 + 47 0 obj 472 + << 473 + /Type /StructElem 474 + /S /LBody 475 + /P 46 0 R 476 + /K [54] 477 + /Pg 132 0 R 478 + >> 479 + endobj 480 + 481 + 48 0 obj 482 + << 483 + /Type /StructElem 484 + /S /Lbl 485 + /P 46 0 R 486 + /K [53] 487 + /Pg 132 0 R 488 + >> 489 + endobj 490 + 491 + 49 0 obj 492 + << 493 + /Type /StructElem 494 + /S /LI 495 + /P 42 0 R 496 + /K [51 0 R 50 0 R] 497 + >> 498 + endobj 499 + 500 + 50 0 obj 501 + << 502 + /Type /StructElem 503 + /S /LBody 504 + /P 49 0 R 505 + /K [52] 506 + /Pg 132 0 R 507 + >> 508 + endobj 509 + 510 + 51 0 obj 511 + << 512 + /Type /StructElem 513 + /S /Lbl 514 + /P 49 0 R 515 + /K [51] 516 + /Pg 132 0 R 517 + >> 518 + endobj 519 + 520 + 52 0 obj 521 + << 522 + /Type /StructElem 523 + /S /LI 524 + /P 42 0 R 525 + /K [54 0 R 53 0 R] 526 + >> 527 + endobj 528 + 529 + 53 0 obj 530 + << 531 + /Type /StructElem 532 + /S /LBody 533 + /P 52 0 R 534 + /K [50] 535 + /Pg 132 0 R 536 + >> 537 + endobj 538 + 539 + 54 0 obj 540 + << 541 + /Type /StructElem 542 + /S /Lbl 543 + /P 52 0 R 544 + /K [49] 545 + /Pg 132 0 R 546 + >> 547 + endobj 548 + 549 + 55 0 obj 550 + << 551 + /Type /StructElem 552 + /S /LI 553 + /P 42 0 R 554 + /K [57 0 R 56 0 R] 555 + >> 556 + endobj 557 + 558 + 56 0 obj 559 + << 560 + /Type /StructElem 561 + /S /LBody 562 + /P 55 0 R 563 + /K [48] 564 + /Pg 132 0 R 565 + >> 566 + endobj 567 + 568 + 57 0 obj 569 + << 570 + /Type /StructElem 571 + /S /Lbl 572 + /P 55 0 R 573 + /K [47] 574 + /Pg 132 0 R 575 + >> 576 + endobj 577 + 578 + 58 0 obj 579 + << 580 + /Type /StructElem 581 + /S /P 582 + /P 13 0 R 583 + /K [45 46] 584 + /Pg 132 0 R 585 + >> 586 + endobj 587 + 588 + 59 0 obj 589 + << 590 + /Type /StructElem 591 + /S /H1 592 + /P 13 0 R 593 + /T (Exemption Qualification) 594 + /K [43 44] 595 + /Pg 132 0 R 596 + >> 597 + endobj 598 + 599 + 60 0 obj 600 + << 601 + /Type /StructElem 602 + /S /L 603 + /P 13 0 R 604 + /A [<< 605 + /O /List 606 + /ListNumbering /Decimal 607 + >>] 608 + /K [66 0 R 61 0 R] 609 + >> 610 + endobj 611 + 612 + 61 0 obj 613 + << 614 + /Type /StructElem 615 + /S /LI 616 + /P 60 0 R 617 + /K [65 0 R 62 0 R] 618 + >> 619 + endobj 620 + 621 + 62 0 obj 622 + << 623 + /Type /StructElem 624 + /S /LBody 625 + /P 61 0 R 626 + /K [63 0 R] 627 + >> 628 + endobj 629 + 630 + 63 0 obj 631 + << 632 + /Type /StructElem 633 + /S /P 634 + /P 62 0 R 635 + /K [64 0 R 40 41 42] 636 + /Pg 132 0 R 637 + >> 638 + endobj 639 + 640 + 64 0 obj 641 + << 642 + /Type /StructElem 643 + /S /Strong 644 + /P 63 0 R 645 + /K [39] 646 + /Pg 132 0 R 647 + >> 648 + endobj 649 + 650 + 65 0 obj 651 + << 652 + /Type /StructElem 653 + /S /Lbl 654 + /P 61 0 R 655 + /K [38] 656 + /Pg 132 0 R 657 + >> 658 + endobj 659 + 660 + 66 0 obj 661 + << 662 + /Type /StructElem 663 + /S /LI 664 + /P 60 0 R 665 + /K [70 0 R 67 0 R] 666 + >> 667 + endobj 668 + 669 + 67 0 obj 670 + << 671 + /Type /StructElem 672 + /S /LBody 673 + /P 66 0 R 674 + /K [68 0 R] 675 + >> 676 + endobj 677 + 678 + 68 0 obj 679 + << 680 + /Type /StructElem 681 + /S /P 682 + /P 67 0 R 683 + /K [69 0 R 36 37] 684 + /Pg 132 0 R 685 + >> 686 + endobj 687 + 688 + 69 0 obj 689 + << 690 + /Type /StructElem 691 + /S /Strong 692 + /P 68 0 R 693 + /K [35] 694 + /Pg 132 0 R 695 + >> 696 + endobj 697 + 698 + 70 0 obj 699 + << 700 + /Type /StructElem 701 + /S /Lbl 702 + /P 66 0 R 703 + /K [34] 704 + /Pg 132 0 R 705 + >> 706 + endobj 707 + 708 + 71 0 obj 709 + << 710 + /Type /StructElem 711 + /S /P 712 + /P 13 0 R 713 + /K [33] 714 + /Pg 132 0 R 715 + >> 716 + endobj 717 + 718 + 72 0 obj 719 + << 720 + /Type /StructElem 721 + /S /H1 722 + /P 13 0 R 723 + /T (Purpose of Encryption) 724 + /K [31 32] 725 + /Pg 132 0 R 726 + >> 727 + endobj 728 + 729 + 73 0 obj 730 + << 731 + /Type /StructElem 732 + /S /P 733 + /P 13 0 R 734 + /K [26 74 0 R 28 29 30] 735 + /Pg 132 0 R 736 + >> 737 + endobj 738 + 739 + 74 0 obj 740 + << 741 + /Type /StructElem 742 + /S /Code 743 + /P 73 0 R 744 + /K [27] 745 + /Pg 132 0 R 746 + >> 747 + endobj 748 + 749 + 75 0 obj 750 + << 751 + /Type /StructElem 752 + /S /H1 753 + /P 13 0 R 754 + /T (Implementation) 755 + /K [24 25] 756 + /Pg 132 0 R 757 + >> 758 + endobj 759 + 760 + 76 0 obj 761 + << 762 + /Type /StructElem 763 + /S /Table 764 + /P 13 0 R 765 + /A [<< 766 + /O /Layout 767 + /BorderColor [0 0 0] 768 + /BorderThickness 1 769 + >>] 770 + /K [89 0 R 85 0 R 81 0 R 77 0 R] 771 + >> 772 + endobj 773 + 774 + 77 0 obj 775 + << 776 + /Type /StructElem 777 + /S /TR 778 + /P 76 0 R 779 + /K [80 0 R 79 0 R 78 0 R] 780 + >> 781 + endobj 782 + 783 + 78 0 obj 784 + << 785 + /Type /StructElem 786 + /S /TD 787 + /P 77 0 R 788 + /A [<< 789 + /O /Table 790 + /Headers [] 791 + >> << 792 + /O /Layout 793 + /BorderStyle /Solid 794 + >>] 795 + /K [23] 796 + /Pg 132 0 R 797 + >> 798 + endobj 799 + 800 + 79 0 obj 801 + << 802 + /Type /StructElem 803 + /S /TD 804 + /P 77 0 R 805 + /A [<< 806 + /O /Table 807 + /Headers [] 808 + >> << 809 + /O /Layout 810 + /BorderStyle /Solid 811 + >>] 812 + /K [22] 813 + /Pg 132 0 R 814 + >> 815 + endobj 816 + 817 + 80 0 obj 818 + << 819 + /Type /StructElem 820 + /S /TD 821 + /P 77 0 R 822 + /A [<< 823 + /O /Table 824 + /Headers [] 825 + >> << 826 + /O /Layout 827 + /BorderStyle /Solid 828 + >>] 829 + /K [21] 830 + /Pg 132 0 R 831 + >> 832 + endobj 833 + 834 + 81 0 obj 835 + << 836 + /Type /StructElem 837 + /S /TR 838 + /P 76 0 R 839 + /K [84 0 R 83 0 R 82 0 R] 840 + >> 841 + endobj 842 + 843 + 82 0 obj 844 + << 845 + /Type /StructElem 846 + /S /TD 847 + /P 81 0 R 848 + /A [<< 849 + /O /Table 850 + /Headers [] 851 + >> << 852 + /O /Layout 853 + /BorderStyle /Solid 854 + >>] 855 + /K [20] 856 + /Pg 132 0 R 857 + >> 858 + endobj 859 + 860 + 83 0 obj 861 + << 862 + /Type /StructElem 863 + /S /TD 864 + /P 81 0 R 865 + /A [<< 866 + /O /Table 867 + /Headers [] 868 + >> << 869 + /O /Layout 870 + /BorderStyle /Solid 871 + >>] 872 + /K [19] 873 + /Pg 132 0 R 874 + >> 875 + endobj 876 + 877 + 84 0 obj 878 + << 879 + /Type /StructElem 880 + /S /TD 881 + /P 81 0 R 882 + /A [<< 883 + /O /Table 884 + /Headers [] 885 + >> << 886 + /O /Layout 887 + /BorderStyle /Solid 888 + >>] 889 + /K [18] 890 + /Pg 132 0 R 891 + >> 892 + endobj 893 + 894 + 85 0 obj 895 + << 896 + /Type /StructElem 897 + /S /TR 898 + /P 76 0 R 899 + /K [88 0 R 87 0 R 86 0 R] 900 + >> 901 + endobj 902 + 903 + 86 0 obj 904 + << 905 + /Type /StructElem 906 + /S /TD 907 + /P 85 0 R 908 + /A [<< 909 + /O /Table 910 + /Headers [] 911 + >> << 912 + /O /Layout 913 + /BorderStyle /Solid 914 + >>] 915 + /K [17] 916 + /Pg 132 0 R 917 + >> 918 + endobj 919 + 920 + 87 0 obj 921 + << 922 + /Type /StructElem 923 + /S /TD 924 + /P 85 0 R 925 + /A [<< 926 + /O /Table 927 + /Headers [] 928 + >> << 929 + /O /Layout 930 + /BorderStyle /Solid 931 + >>] 932 + /K [16] 933 + /Pg 132 0 R 934 + >> 935 + endobj 936 + 937 + 88 0 obj 938 + << 939 + /Type /StructElem 940 + /S /TD 941 + /P 85 0 R 942 + /A [<< 943 + /O /Table 944 + /Headers [] 945 + >> << 946 + /O /Layout 947 + /BorderStyle /Solid 948 + >>] 949 + /K [15] 950 + /Pg 132 0 R 951 + >> 952 + endobj 953 + 954 + 89 0 obj 955 + << 956 + /Type /StructElem 957 + /S /TR 958 + /P 76 0 R 959 + /K [94 0 R 92 0 R 90 0 R] 960 + >> 961 + endobj 962 + 963 + 90 0 obj 964 + << 965 + /Type /StructElem 966 + /S /TD 967 + /P 89 0 R 968 + /A [<< 969 + /O /Table 970 + /Headers [] 971 + >> << 972 + /O /Layout 973 + /BorderStyle /Solid 974 + >>] 975 + /K [91 0 R] 976 + >> 977 + endobj 978 + 979 + 91 0 obj 980 + << 981 + /Type /StructElem 982 + /S /Strong 983 + /P 90 0 R 984 + /K [14] 985 + /Pg 132 0 R 986 + >> 987 + endobj 988 + 989 + 92 0 obj 990 + << 991 + /Type /StructElem 992 + /S /TD 993 + /P 89 0 R 994 + /A [<< 995 + /O /Table 996 + /Headers [] 997 + >> << 998 + /O /Layout 999 + /BorderStyle /Solid 1000 + >>] 1001 + /K [93 0 R] 1002 + >> 1003 + endobj 1004 + 1005 + 93 0 obj 1006 + << 1007 + /Type /StructElem 1008 + /S /Strong 1009 + /P 92 0 R 1010 + /K [13] 1011 + /Pg 132 0 R 1012 + >> 1013 + endobj 1014 + 1015 + 94 0 obj 1016 + << 1017 + /Type /StructElem 1018 + /S /TD 1019 + /P 89 0 R 1020 + /A [<< 1021 + /O /Table 1022 + /Headers [] 1023 + >> << 1024 + /O /Layout 1025 + /BorderStyle /Solid 1026 + >>] 1027 + /K [95 0 R] 1028 + >> 1029 + endobj 1030 + 1031 + 95 0 obj 1032 + << 1033 + /Type /StructElem 1034 + /S /Strong 1035 + /P 94 0 R 1036 + /K [12] 1037 + /Pg 132 0 R 1038 + >> 1039 + endobj 1040 + 1041 + 96 0 obj 1042 + << 1043 + /Type /StructElem 1044 + /S /P 1045 + /P 13 0 R 1046 + /K [11] 1047 + /Pg 132 0 R 1048 + >> 1049 + endobj 1050 + 1051 + 97 0 obj 1052 + << 1053 + /Type /StructElem 1054 + /S /H1 1055 + /P 13 0 R 1056 + /T (Encryption Algorithms Used) 1057 + /K [9 10] 1058 + /Pg 132 0 R 1059 + >> 1060 + endobj 1061 + 1062 + 98 0 obj 1063 + << 1064 + /Type /StructElem 1065 + /S /P 1066 + /P 13 0 R 1067 + /K [7 8] 1068 + /Pg 132 0 R 1069 + >> 1070 + endobj 1071 + 1072 + 99 0 obj 1073 + << 1074 + /Type /StructElem 1075 + /S /H1 1076 + /P 13 0 R 1077 + /T (Overview) 1078 + /K [5 6] 1079 + /Pg 132 0 R 1080 + >> 1081 + endobj 1082 + 1083 + 100 0 obj 1084 + << 1085 + /Type /StructElem 1086 + /S /P 1087 + /P 13 0 R 1088 + /K [4] 1089 + /Pg 132 0 R 1090 + >> 1091 + endobj 1092 + 1093 + 101 0 obj 1094 + << 1095 + /Type /StructElem 1096 + /S /P 1097 + /P 13 0 R 1098 + /K [2 102 0 R] 1099 + /Pg 132 0 R 1100 + >> 1101 + endobj 1102 + 1103 + 102 0 obj 1104 + << 1105 + /Type /StructElem 1106 + /S /Code 1107 + /P 101 0 R 1108 + /K [3] 1109 + /Pg 132 0 R 1110 + >> 1111 + endobj 1112 + 1113 + 103 0 obj 1114 + << 1115 + /Type /StructElem 1116 + /S /P 1117 + /P 13 0 R 1118 + /K [1] 1119 + /Pg 132 0 R 1120 + >> 1121 + endobj 1122 + 1123 + 104 0 obj 1124 + << 1125 + /Type /StructElem 1126 + /S /P 1127 + /P 13 0 R 1128 + /K [0] 1129 + /Pg 132 0 R 1130 + >> 1131 + endobj 1132 + 1133 + 105 0 obj 1134 + << 1135 + /Type /Font 1136 + /Subtype /Type0 1137 + /BaseFont /AQOZTK+SourceSans3-Bold-Identity-H 1138 + /Encoding /Identity-H 1139 + /DescendantFonts [106 0 R] 1140 + /ToUnicode 109 0 R 1141 + >> 1142 + endobj 1143 + 1144 + 106 0 obj 1145 + << 1146 + /Type /Font 1147 + /Subtype /CIDFontType0 1148 + /BaseFont /AQOZTK+SourceSans3-Bold 1149 + /CIDSystemInfo << 1150 + /Registry (Adobe) 1151 + /Ordering (Identity) 1152 + /Supplement 0 1153 + >> 1154 + /FontDescriptor 108 0 R 1155 + /DW 0 1156 + /W [0 0 690 1 1 548 2 2 572 3 3 467 4 4 398 5 5 521 6 6 573 7 7 383 8 8 276 9 9 555 10 10 200 11 11 635 12 12 568 13 13 857 14 14 518 15 15 527 16 16 528 17 17 300 18 18 684 19 19 523 20 20 776 21 21 528 22 22 573 23 23 286 24 24 534 25 25 571 26 26 443 27 27 665 28 28 573 29 29 556 30 30 596 31 31 528 32 32 301 33 33 528 34 34 341 35 35 528 36 36 514 37 37 684 38 38 528 39 39 524 40 40 614 41 41 518 42 42 528 43 43 582] 1157 + >> 1158 + endobj 1159 + 1160 + 107 0 obj 1161 + << 1162 + /Length 14 1163 + /Filter /FlateDecode 1164 + >> 1165 + stream 1166 + x���������� 1167 + endstream 1168 + endobj 1169 + 1170 + 108 0 obj 1171 + << 1172 + /Type /FontDescriptor 1173 + /FontName /AQOZTK+SourceSans3-Bold 1174 + /Flags 131076 1175 + /FontBBox [-6 -211 797 723] 1176 + /ItalicAngle 0 1177 + /Ascent 1000 1178 + /Descent -326 1179 + /CapHeight 660 1180 + /StemV 168.6 1181 + /CIDSet 107 0 R 1182 + /FontFile3 110 0 R 1183 + >> 1184 + endobj 1185 + 1186 + 109 0 obj 1187 + << 1188 + /Length 1208 1189 + /Type /CMap 1190 + /WMode 0 1191 + >> 1192 + stream 1193 + %!PS-Adobe-3.0 Resource-CMap 1194 + %%DocumentNeededResources: procset CIDInit 1195 + %%IncludeResource: procset CIDInit 1196 + %%BeginResource: CMap Custom 1197 + %%Title: (Custom Adobe Identity 0) 1198 + %%Version: 1 1199 + %%EndComments 1200 + /CIDInit /ProcSet findresource begin 1201 + 12 dict begin 1202 + begincmap 1203 + /CIDSystemInfo 3 dict dup begin 1204 + /Registry (Adobe) def 1205 + /Ordering (Identity) def 1206 + /Supplement 0 def 1207 + end def 1208 + /CMapName /Custom def 1209 + /CMapVersion 1 def 1210 + /CMapType 0 def 1211 + /WMode 0 def 1212 + 1 begincodespacerange 1213 + <0000> <FFFF> 1214 + endcodespacerange 1215 + 43 beginbfchar 1216 + <0001> <0045> 1217 + <0002> <006E> 1218 + <0003> <0063> 1219 + <0004> <0072> 1220 + <0005> <0079> 1221 + <0006> <0070> 1222 + <0007> <0074> 1223 + <0008> <0069> 1224 + <0009> <006F> 1225 + <000A> <0020> 1226 + <000B> <0044> 1227 + <000C> <0075> 1228 + <000D> <006D> 1229 + <000E> <0065> 1230 + <000F> <0061> 1231 + <0010> <0031> 1232 + <0011> <002E> 1233 + <0012> <004F> 1234 + <0013> <0076> 1235 + <0014> <0077> 1236 + <0015> <0032> 1237 + <0016> <0041> 1238 + <0017> <006C> 1239 + <0018> <0067> 1240 + <0019> <0068> 1241 + <001A> <0073> 1242 + <001B> <0055> 1243 + <001C> <0064> 1244 + <001D> <0053> 1245 + <001E> <0050> 1246 + <001F> <0033> 1247 + <0020> <0049> 1248 + <0021> <0034> 1249 + <0022> <0066> 1250 + <0023> <0035> 1251 + <0024> <0078> 1252 + <0025> <0051> 1253 + <0026> <0036> 1254 + <0027> <0046> 1255 + <0028> <004B> 1256 + <0029> <004C> 1257 + <002A> <0037> 1258 + <002B> <0043> 1259 + endbfchar 1260 + endcmap 1261 + CMapName currentdict /CMap defineresource pop 1262 + end 1263 + end 1264 + %%EndResource 1265 + %%EOF 1266 + endstream 1267 + endobj 1268 + 1269 + 110 0 obj 1270 + << 1271 + /Length 3240 1272 + /Filter /FlateDecode 1273 + /Subtype /CIDFontType0C 1274 + >> 1275 + stream 1276 + x��VkTך���*m�( ��]�F�Qc���T_�"�hZl�(�>{��\ԎQA^ 1277 + 1278 + ���cb|%f4�fbԘ8�$jnv��g�zg%3?�O�u�Yuj���>�c4^^�ac�kl+ͱI��#�L���<�3TY}�@�W��4����j����O��!t�5��߬l_����{�9�}5���u�=����!N7P��0���$�5�<�d�̱���:d�5��bq�aRz��fII��6���f��l�4�ZrR �_M Ӭ�9��� ����q���`�6$rlI&sF�m�����S �LCN�ٰ Ӓc6bs�r�ن�L�0��`�I5� +�k2sls�P����ф;��w�������nts���;�j���}�}�]�>��� ��ޗ�\�S��C�/�~��`��T/HZ ��h4� 1279 + {�WP𞇮��f���� M��K��|�_������ﱰ�?�����V�&h��%{m�j����8��㸹����󏽇xy��M�*�>�}����������/]�n�._� � �S�07�Ώڛ���ga�9���(����-�Zώ�ui���s,"�U����8rK*�?��h�4s~@ +�-@�����`n�����B��G8l�'zc�#9aG�]�� � F �Q��Y0�����U�6ɭ���P+�8�#`�u��X��2I��Ķ������~h�ӈ-���s��-ʼ�X�-Ñ&G'tݹ��u��|��2��wl]j|E�L}�g$��1�F��"и 1280 + ��ɑ�σ%�a?z����Tվ�I�pow�*��j���� y�Vy��U3R'�l��x�,���V?�e��V-t�P�ϙ �P0Q��R?n.Y �gB������6� 1281 + �۝�%�5�۲�卛 1282 + �+Y�a7����B��c"t8 �+�& B��g4[��m�Ʀ5]{L���#�ǐI?�nђ�T�୍��`��l���Y 1283 + y���5U{��.����Xh�<�g��Q��N��y��*%� �uC��we�����x��'?D���Q䄭,1�)�'��x��:^aW��~��A]x���(~�!S�BF��5I����PG��6>ޒ�,�>�S�+$��E�CB �b Ɛ�C 1284 + � �~Q4xG"H��WLFם;���\7rdz�\E�� 1285 + � ��F��#>X���8��� r�C�~�� 1286 + ��`�>$�Q�<�_.��4|��*�{���F��(�!�!]n�=٪�ωkwOc��� 1287 + ���6S��wߑW���\A+�<O��� 8�y��`o��x]mM���I�._WW]r��~�u��e����3��Co�щ��ɞ�vn��B�������ҢJU�s��E 1288 + M�.к�;B:w��ᤲ�>kOMq�#������N����WU�B������a4�ƀ�D���~�] �oP+�A�X���l[U�1����Z�B�FC����qS�L���#��Ζ����}�W�����7c����h�w�N���fY�������=�h>˖�>ML��i1�� F��`5 ~��~@n���u����ï[Kz^�r�0�1M�p�t����3棋�W.\Լ��"��Q} "ӎ�� �B�^� �t���4�t$��,�Q/LU zE�~��>�ս{g�Q0��}3J ���=nvi1=�9�R�xlM������<6>�:�s�=6`.��!���,]��`L�Z����X��} 1289 + ����+v.o������s\_.��6%�6��qq��6u8;��B��"�s.ۼ��Ɩ���:<F�C[� ��E�M�j,�?���r�)`�}�|(z��I�QsS���oL`��j���˜(�&�ޠ��PTa�0����U��E���>+�^rŻ�+�^6�,z��R8ZM�¹��b)w����ъg����w�7��,�����HpJ���s�"6���ݾ�U�M�߰�NQ;��}�T��^H��?���= ������Ӏ�o �8 ��Dl� ���'�f4�����r�Jw��`��e_�?���'�;g�u`KE~�R���4�:�׬.�ʆ�s�o^]BC������Uk�SSB�˳���.^�Ź,˖�:������2I|�4eVw��9x�H���q�V���f$���3˛�v�-+Q\%����S\���閥�g*B�K�cA.�*Դ�_�ŧ�����=�� ���.0� ���) }>�z�v�� �������ʲ�# �����f��b����M�=l�'�?�R���O �Z�nb�2w�r��7٥'�̺( �<���e�! k�_�o�t�{X�F��}x��&O^� 1290 + A��P�Ɠ��P�� E�ް�!�aAn�D`�b'�9��U �Ka��x�g��H㸢 l�%�<N�?h0��Q7�_i�N;���C����5���J�Ӳm�U�����c0Fz�[��0���<�?3�N]��V��s4��nߩ�mkӏ��zȞ��ph�L�E�$M���KV��T������PSFv��Y�-���"����fܕ�� 1291 + ;���%�y�qxݓFd�⠇�e Ay�� 1292 + �p0�% K.�7�ۦO7���&OZ����� \@�����!�4��{{���J!/�|����b(�#>���f�/����؆��m������)����=��Sc�&��� �`�Y�>�]��E�Y�+�� ���)�^ؿŽHxa;�txt�J���������O!�}�+&9�u�J�����5Y�iG뻃�A���W�����S���������ɼ����>�w�����t��}�/l���]�2�Ə쯈7V󓷱b1����,y�����MN�s�Z���H8$���~��~BL����VvÏ�P7�������o���I>�b��d�^�������.9y��T�h*�=j�vO0"�#��5{j��} 1293 + ��ތ��1!��h��u��Y�:����}\܆��5�Չ7C(����� 1294 + ��h�~cW���HQ����. ��1Q 1295 + V�1�d�.�~%�o�ֵ6�U��W֞� ��|1%�������J�~`������z`�S����բ�҉ی))鉉Iu?�+mUMe��"� ��BB�~B�)Ζ�� l�ٓW妣��}ʍ�l�ʘ��2����6R6|��_VT�Q��D�O#�`G���Rtㄎ?� 1296 + S��?aU����5,�,��h!��.�{d�I�I��ڭx%H� ��Ex0¸�x� 1297 + >�� ���1�f��O+(�A)���߅�sH�.�~zV��-�����D��B!}q�nGC^�a��{lG����2��D�d��x�U����ߠ 1298 + endstream 1299 + endobj 1300 + 1301 + 111 0 obj 1302 + << 1303 + /Type /Font 1304 + /Subtype /Type0 1305 + /BaseFont /MRNZKI+SourceSans3-Regular-Identity-H 1306 + /Encoding /Identity-H 1307 + /DescendantFonts [112 0 R] 1308 + /ToUnicode 115 0 R 1309 + >> 1310 + endobj 1311 + 1312 + 112 0 obj 1313 + << 1314 + /Type /Font 1315 + /Subtype /CIDFontType0 1316 + /BaseFont /MRNZKI+SourceSans3-Regular 1317 + /CIDSystemInfo << 1318 + /Registry (Adobe) 1319 + /Ordering (Identity) 1320 + /Supplement 0 1321 + >> 1322 + /FontDescriptor 114 0 R 1323 + /DW 0 1324 + /W [0 0 653 1 1 571 2 2 542 3 3 347 4 4 555 5 5 419 6 6 200 7 7 246 8 8 664 9 9 534 10 10 544 11 11 555 12 12 255 13 13 456 14 14 504 15 15 338 16 16 547 17 17 588 18 18 544 19 19 496 20 20 263 21 21 615 22 22 249 23 23 480 24 24 467 25 26 497 27 27 249 28 29 497 30 30 536 31 31 544 32 32 829 33 33 553 34 34 504 35 35 292 36 36 645 37 37 249 38 38 527 39 39 446 40 40 569 41 42 303 43 43 719 44 46 497 47 47 494 48 49 497 50 50 467 51 51 513 52 52 497 53 53 577 54 54 311 55 55 652 56 56 495 57 57 617 58 58 727 59 59 647 60 60 566 61 61 249 62 62 579 63 63 800 64 64 555 65 65 497 66 66 304 67 67 786 68 68 350] 1325 + >> 1326 + endobj 1327 + 1328 + 113 0 obj 1329 + << 1330 + /Length 13 1331 + /Filter /FlateDecode 1332 + >> 1333 + stream 1334 + x���~��,�� 1335 + endstream 1336 + endobj 1337 + 1338 + 114 0 obj 1339 + << 1340 + /Type /FontDescriptor 1341 + /FontName /MRNZKI+SourceSans3-Regular 1342 + /Flags 131076 1343 + /FontBBox [3 -224 763 732] 1344 + /ItalicAngle 0 1345 + /Ascent 1000 1346 + /Descent -326 1347 + /CapHeight 660 1348 + /StemV 95.4 1349 + /CIDSet 113 0 R 1350 + /FontFile3 116 0 R 1351 + >> 1352 + endobj 1353 + 1354 + 115 0 obj 1355 + << 1356 + /Length 1562 1357 + /Type /CMap 1358 + /WMode 0 1359 + >> 1360 + stream 1361 + %!PS-Adobe-3.0 Resource-CMap 1362 + %%DocumentNeededResources: procset CIDInit 1363 + %%IncludeResource: procset CIDInit 1364 + %%BeginResource: CMap Custom 1365 + %%Title: (Custom Adobe Identity 0) 1366 + %%Version: 1 1367 + %%EndComments 1368 + /CIDInit /ProcSet findresource begin 1369 + 12 dict begin 1370 + begincmap 1371 + /CIDSystemInfo 3 dict dup begin 1372 + /Registry (Adobe) def 1373 + /Ordering (Identity) def 1374 + /Supplement 0 def 1375 + end def 1376 + /CMapName /Custom def 1377 + /CMapVersion 1 def 1378 + /CMapType 0 def 1379 + /WMode 0 def 1380 + 1 begincodespacerange 1381 + <0000> <FFFF> 1382 + endcodespacerange 1383 + 68 beginbfchar 1384 + <0001> <0043> 1385 + <0002> <006F> 1386 + <0003> <0072> 1387 + <0004> <0064> 1388 + <0005> <0073> 1389 + <0006> <0020> 1390 + <0007> <0069> 1391 + <0008> <004F> 1392 + <0009> <0053> 1393 + <000A> <0041> 1394 + <000B> <0070> 1395 + <000C> <006C> 1396 + <000D> <0063> 1397 + <000E> <0061> 1398 + <000F> <0074> 1399 + <0010> <006E> 1400 + <0011> <0042> 1401 + <0012> <0075> 1402 + <0013> <0065> 1403 + <0014> <0049> 1404 + <0015> <0044> 1405 + <0016> <003A> 1406 + <0017> <004A> 1407 + <0018> <0079> 1408 + <0019> <0032> 1409 + <001A> <0037> 1410 + <001B> <002C> 1411 + <001C> <0030> 1412 + <001D> <0036> 1413 + <001E> <0054> 1414 + <001F> <0068> 1415 + <0020> <006D> 1416 + <0021> <0062> 1417 + <0022> <0067> 1418 + <0023> <0066> 1419 + <0024> <0055> 1420 + <0025> <002E> 1421 + <0026> <0045> 1422 + <0027> <0078> 1423 + <0028> <0052> 1424 + <0029> <0028> 1425 + <002A> <0029> 1426 + <002B> <0077> 1427 + <002C> <0035> 1428 + <002D> <0031> 1429 + <002E> <0039> 1430 + <002F> <0046> 1431 + <0030> <0038> 1432 + <0031> <0033> 1433 + <0032> <0076> 1434 + <0033> <0058> 1435 + <0034> <0034> 1436 + <0035> <00660066> 1437 + <0036> <002D> 1438 + <0037> <0048> 1439 + <0038> <006B> 1440 + <0039> <0047> 1441 + <003A> <004D> 1442 + <003B> <004E> 1443 + <003C> <0050> 1444 + <003D> <2019> 1445 + <003E> <004B> 1446 + <003F> <2014> 1447 + <0040> <0071> 1448 + <0041> <00A7> 1449 + <0042> <2022> 1450 + <0043> <0057> 1451 + <0044> <002F> 1452 + endbfchar 1453 + endcmap 1454 + CMapName currentdict /CMap defineresource pop 1455 + end 1456 + end 1457 + %%EndResource 1458 + %%EOF 1459 + endstream 1460 + endobj 1461 + 1462 + 116 0 obj 1463 + << 1464 + /Length 4758 1465 + /Filter /FlateDecode 1466 + /Subtype /CIDFontType0C 1467 + >> 1468 + stream 1469 + x�}�yT���g�7(�U:�8����a0�(*�x]����68�*�,�"d��-(��7dP�%"((� ( ���`����uIRM�<�H�I�}��ӿ�uNW�N��>ߪ�P��' 1470 + �ﻄ�DnR��â��vV�l�G�쎼�� �P�O2�2�l��KJy��~����u�& ±cuϨ���w� ��{�hh�[\ ���BB��Z��S�P(â���]�5��k1�t�8�t��ͦ�A��Q���(e�J��eh��������h�5�P�鴑 O�3��E��M�#� 1471 + e�<2�4�īiP�it��t}XP�Ra�-�VF����ᑦ�с�H�M�1aёAʨ9�(�P`�N��R�SKS�S��Z�J04U�o6} M-C��3�dh�1�נ�а��=�8+��a8������B5I ��b����A����,���������9*nT�H&JU��ҋѫ��� ���)DN���b�����>я�Oү=wt�Ř�1�c�|h�Š����G�_>|�����R���V:���Z��{e��q̸�q5�ǎ_9~����UF�����0���&��n ��E��ߘ��>A@0��� &��P)�ڬM(dA˺?[�� 1472 + 4��M�沼��� �X4�80�x������u�������+�u��-����Qk��,x 6`���^+��/�ʪ�2���Jq�C0ƢXs�u ?�Ex���DpF͂��O��&?E4�<����9`���Y;w��j���ްE=�,��h�ը����c3b�����ޕ% 1473 + �L��X �D�_�["M�vߞ�s8��b�n�OVI]�kB������1�d.'�yuk��_� 1474 + ��FW��Hϰ�a��دfk���qɄ�;�L1�-����DR��B_��E�>B ��m4���2�=�@D�����'��pxP��I�-���o�F��׻ai��j� Mݏ��۞��4�i�����u�� Q��+T�r�i'���N �vl���^W�K�!A+�|N�eʍ�OY��v)��4fjT|\,���F��2O��Z>Ά���e^�����Eg��DDŽ�V�q4L��@y��EЧf���@���>ڠ ��A �1�� ס��� �`�� 1475 + F�让h�֋&�h�>1��n/|�'�L5 \o���e ��9Ŕk����͊���Q%��k�e��� �hh�dڬ6φ��U~E�(Y��k���[�)�\O����&��4*�l��-�d�P>��=ӡ��~O?�hl5vg���f��b:�G���/I=zR�#ͨل>��T����oU�5o�H�����ɾ>*��LU&��`3_�x���y�f]�tu�b:��Z†�;���!1�å���%�'�ٙ���Kp4���289V�Պ`�N6�D�7G��zjߗ{�vK�H<����e3ċ2�2z�u!�l5[-�B1PIE�m��#�x�p!4������љ\I%��}f�ǣp�b^�c�NP���٨�!�k���%�A��U��P�K���k)fk Txϭ�,\�-�s��Ueݐvk>_��CE�� 1476 + %����N��L��Rs���y�>����s��SJ���f����yϒ�L�w��y��fqq��έ[��I]���@�q�3����k��Z-f���)2���oet�F�۶����h�F]U�D** 1477 + �*���s9��B��y5M�1��^^��)���b���-� �nCP��@��h���AI`���0��k��q.E�����[$ 6/É2t%=��B�+���0[G/?��f�m�����@|���y+����,u�RWoF�=��0G����«e#a�Յ�������+0�]�2��TZc��]^�FFǨ���F��,,��CwEp��c;/���������[ңCNlS�&�z���K���8Ԁ�K����O���l���G=�@ �|3&�h<�Q �� 1478 + zD�=�fք~��Ÿl�@�#р1|F�@�(���͡5��c���b��e�6���8��1C F�#a#�`<�f��f}W��EpvK>������V�;�'�B���&5 &מ����VC�⩁~v�3��e.���w7���*[ű�T�I4�J���� 8Xɟ��O0� ����{H|Y\e����a2��������)6�g�4Q} E�m[I�:qjO��V%�󇛅����ؘ�ێ�H�b��@�a���I@oi'��qV8m�]��.��\pU���u� 1479 + q ��G�4*�v�l�D`k�G���0�9�A�j�������pf"~�Oc�� ¤:Q3.bT6?���r�78<&��ll^rJ��d�� A>Α��J�ğm�>�Dp@�� ��@�����1� �}�Io��p��{�w+m� ��,�h������ը����r�y��!�}���j�˪O=�a��fquf�� >e�<�&�5�(����y��&�S:[����n��.WQ<r�I�A��x�?N����Q�=mBP��@e����G�P�x�y�v��b�����=|�d���`������+���e�G�T��d~'�����O�ʲ �EҺ�?9��4�sw�:7Ym���%R�s�[��\ljK:N.'t���_�N���ٕ��T��a �f8�@����>܁�ûdT�y���&r��J&�KU�e҄V���s켯N.]��W9H���עP���ܰ��Qٝ�ZaS��ӥ��1���?�x�\�7����8w�؉�ǹČ���H���_v7�)ݖ��R^5�B1sw��mQ�DH�[9N^{�3o �G�t]�� ��(Om9/�#f���F(�$VZO0��z���.�e���/��\���{z�p�C�,se�� ����s 1480 + 39���.��}�����0��m��9���j�f��ZXͼzn�����M3a^�u�����i'��J��ݢ�B�Jgx���-���V��������{ �{j$��A�� I�h���K� 1481 + 0��R롉bT���l�>��G����'���v�k�oҎ��������!��v�}��?_t��i�D��SHZxZ�ɰv�L� Z�>��(K��i�������{H�����Y��Ă�4���*� 1482 + r[`o��Ì�\0 `���3� �5�Q[�Z�,G� ��G'){a��u�f����)�!h���P�"�`�g��P��Q�L�%�YE0� ���%h����@�ԅd�K��"�G{�$`����3k��ܢ�.d�p&Β�,%�\�����f� 1483 + � ��<�פ���~{�p��r���<ջ���B`G- �X��g�|���=dG����ҥP`�1�ÂD�#�/�w\�Qj&��V�[�B=X� �Q="�BWJo�� d�(T���ʝ��$����[��Tf��c�R�5�y��.�XX��6��|J|_��������a�eZ��a�M��U�� Tɷ�ݡ�A̴��P0N���z�nw��.��ײ�7�V��qGdN~n�r�g����� 迄� ����XZ#��W���5窊9��k���t�"���˕RV��H�(� 1484 + ��A����W � �!��\S����uF%w}�Ap�K7��j�i;|���9_׃t]ȭ�i��\֎"��+>r��y%XW��N!N�֒��K�PVC,ӵ(�!���f[��*�(__���Y���U��EW� 1485 + �'�Cm$+ܼ�Ȇc��"��`|�׻��� �,�~� 1486 + ��57� �A�YW*�$o�D��σ�<d��TQQ�٫Җ��Վޮ��J��ìj7[�u^��W����)�K���qKj��F����X��Q�s�Iظ�#�K�ӗ�W$��C��|B�iJ�����Əɏ�p"�d����)D�)-�d�st0yB��r�K�#��g�C��ئ����%��˵��L� ,�6J��`�u��Ps_�v�����K�>qnb 1487 + ��w]�W�q��}�����Ԝ`��,j=���#D�lv�1 |����?µu\{��N��2M��@� ���O��yq;���L�����z�HT���T�US��#����V�#�q�7K NT�,�5`�A�QA��ֵjP�;�N�P����fV����mM���)����B'���o�5@p\���F���C�>>Ɔ�/��� 1488 + �t����+5��N^�Er0dph+����)EӅ�C|�k0i���]��J�N�s���? �K8�U�[~�3��=~�}р�=������U+K!Uhc� UG`,<� *��^��������هb�o�{�� �ԨH�_������ l�/�*��/I�F�P���<`� �I���]M��٘��� C�L~4[��sr�?��Y.�Cbʣ+"K1��e2���m�c�tN)ѣ `��F���� 1489 + �ơ���,{&q��Ys�ɑ�:]~�g!Ȯ��vlkqQ�ImT�bs�*0$s[v�]���,����d���� �QO ��g�����PK��5���j�%�~��b�j~����t�2¡�y�KP�� .'L� 1490 + ����0/�|��{�Q�[t��3��q���}�Zy{Ppۨ�ζ{0�^��)$�d�5�v$fm�������� � 1491 + s��@Rp:�Fia��<Y�U��rC�*�4�5�S���8��{x��Z\ 9�H$ʐ�6����ܽ)����v)�sْәE�߳ �~,�QE+�ɬ+�0����`�v �!Z��C�Vu��;�NN�vv�oq4h�zy�������������!�T}�|�����!�N�& �Q��2��!�!q�.�:��oo��A+b����t��Nu���0hbAh� ?�U�-<}������J9���|� 1492 + endstream 1493 + endobj 1494 + 1495 + 117 0 obj 1496 + << 1497 + /Type /Font 1498 + /Subtype /Type0 1499 + /BaseFont /PKNLIE+DejaVuSansMono 1500 + /Encoding /Identity-H 1501 + /DescendantFonts [118 0 R] 1502 + /ToUnicode 121 0 R 1503 + >> 1504 + endobj 1505 + 1506 + 118 0 obj 1507 + << 1508 + /Type /Font 1509 + /Subtype /CIDFontType2 1510 + /BaseFont /PKNLIE+DejaVuSansMono 1511 + /CIDSystemInfo << 1512 + /Registry (Adobe) 1513 + /Ordering (Identity) 1514 + /Supplement 0 1515 + >> 1516 + /FontDescriptor 120 0 R 1517 + /DW 0 1518 + /CIDToGIDMap /Identity 1519 + /W [0 50 602.0508] 1520 + >> 1521 + endobj 1522 + 1523 + 119 0 obj 1524 + << 1525 + /Length 13 1526 + /Filter /FlateDecode 1527 + >> 1528 + stream 1529 + x������� 1530 + endstream 1531 + endobj 1532 + 1533 + 120 0 obj 1534 + << 1535 + /Type /FontDescriptor 1536 + /FontName /PKNLIE+DejaVuSansMono 1537 + /Flags 131077 1538 + /FontBBox [0 -235.83984 602.0508 765.1367] 1539 + /ItalicAngle 0 1540 + /Ascent 759.7656 1541 + /Descent -240.23438 1542 + /CapHeight 759.7656 1543 + /StemV 95.4 1544 + /CIDSet 119 0 R 1545 + /FontFile2 122 0 R 1546 + >> 1547 + endobj 1548 + 1549 + 121 0 obj 1550 + << 1551 + /Length 1306 1552 + /Type /CMap 1553 + /WMode 0 1554 + >> 1555 + stream 1556 + %!PS-Adobe-3.0 Resource-CMap 1557 + %%DocumentNeededResources: procset CIDInit 1558 + %%IncludeResource: procset CIDInit 1559 + %%BeginResource: CMap Custom 1560 + %%Title: (Custom Adobe Identity 0) 1561 + %%Version: 1 1562 + %%EndComments 1563 + /CIDInit /ProcSet findresource begin 1564 + 12 dict begin 1565 + begincmap 1566 + /CIDSystemInfo 3 dict dup begin 1567 + /Registry (Adobe) def 1568 + /Ordering (Identity) def 1569 + /Supplement 0 def 1570 + end def 1571 + /CMapName /Custom def 1572 + /CMapVersion 1 def 1573 + /CMapType 0 def 1574 + /WMode 0 def 1575 + 1 begincodespacerange 1576 + <0000> <FFFF> 1577 + endcodespacerange 1578 + 50 beginbfchar 1579 + <0001> <0073> 1580 + <0002> <0068> 1581 + <0003> <002E> 1582 + <0004> <0062> 1583 + <0005> <0065> 1584 + <0006> <006E> 1585 + <0007> <0074> 1586 + <0008> <006C> 1587 + <0009> <0079> 1588 + <000A> <0054> 1589 + <000B> <0072> 1590 + <000C> <0061> 1591 + <000D> <0070> 1592 + <000E> <006F> 1593 + <000F> <0064> 1594 + <0010> <005F> 1595 + <0011> <0063> 1596 + <0012> <0055> 1597 + <0013> <0020> 1598 + <0014> <0041> 1599 + <0015> <0042> 1600 + <0016> <007C> 1601 + <0017> <002D> 1602 + <0018> <0050> 1603 + <0019> <0075> 1604 + <001A> <0069> 1605 + <001B> <006B> 1606 + <001C> <0076> 1607 + <001D> <0051> 1608 + <001E> <0052> 1609 + <001F> <003A> 1610 + <0020> <002F> 1611 + <0021> <003E> 1612 + <0022> <003C> 1613 + <0023> <0044> 1614 + <0024> <0028> 1615 + <0025> <0058> 1616 + <0026> <0032> 1617 + <0027> <0035> 1618 + <0028> <0031> 1619 + <0029> <0039> 1620 + <002A> <0029> 1621 + <002B> <0053> 1622 + <002C> <0067> 1623 + <002D> <0045> 1624 + <002E> <0047> 1625 + <002F> <0043> 1626 + <0030> <004D> 1627 + <0031> <0056> 1628 + <0032> <0066> 1629 + endbfchar 1630 + endcmap 1631 + CMapName currentdict /CMap defineresource pop 1632 + end 1633 + end 1634 + %%EndResource 1635 + %%EOF 1636 + endstream 1637 + endobj 1638 + 1639 + 122 0 obj 1640 + << 1641 + /Length 9204 1642 + /Filter /FlateDecode 1643 + >> 1644 + stream 1645 + x��{ tTս�o���g���3�<!!���7@�� o(��A ABDL�X.� 1646 + F @��\��Z/E�(4"��E��Uk)��ؑzmD ��]���<�ڮ�~ߺk}ff�s��?~��޳���jr�ݷ<�b�� ���,X���.垳�����+�� }��@����sJm�pH�k���͟coPT e���ʗ���p)e��W̛�W���#���ܿ������ ���dN���=) 1647 + �6 �}diE�r�s~��)��K��_Z���6�`)`���4���14`��S���׀�8 Ŝ��N� �e��>�pk�%sүX�A#�ijN��̕�� �����U� +��q���4v ��΅�ǩ��H�X7V���9��h`xP�F�2 �x[= �m��:l�^T�mS�5�1|��؄lW.�N��8��A�c�`͸��ze����p�؉�l �Q��9!p�7+��� �\���|�ă7�f��H�4�-��gⶏa��x�ǜ���A�҇l�������U@�q7N�f͇Z[j�l�:�����)+�٬5�m.}�j% '���8�L���x�4`v���֧��m��5xLl�:�nE-�ձ؎=�z�@�nT��o��P�zd�ʹM���`��`�R�M�U.�mT�0��$�KX�j>�� 1648 + ����E��UR����I���m�R_�������Tݖ��G�Lm�vm�4� ���w�O~�~՟q�=<��g��i� ,~��0�᳇e�3y�~�/��MOK��3|��Lr������d���ye���E��E�PP&��e|/6t;���h�w���EEvә`.�3�3��#���ie*B��%�g������eZ/�`h�v^��j�@U �V<��n)v�bsƧtK)NNIp�R����Ď��M�Gj ���h����tu�bÄ.��Ħ�҇��[�����[XXX� �BKP_]ҿ���/4��9c&O���ύ�B��9=��f��t-�׍��X���޽GA 1649 + ���W�7[�b}������vO^U=���6ߝ|���G�<��W��?��o޹O-<��u��1�3<I�V�;���XP0o��\��m˃?��4S����E�!} ��Vc4��p2E��(����[�y�`.�[�F|aa32��ݥ�F��o������./�}�C�r����3ʡ��ʡ�G^�Y�:�׺w��s�c��,��(,�.-��a�F� 1650 + �\q 1651 + ��W��W�i>��9����j�5�;��<��wd�[ӒF$K�,��K������׏I����H c3�|ڶ~��7>�v}��>O,x�Ӌ'Kwd54(ٿ����3��@��4������8q���24TKy�jĢ+��#��s�c�w�i5Q��Ě�ƨɤt�qJ�z�QR�P�I��2���~Io��-4 1652 + sXl�%a�X�H��#?�k3�oS��ο�ܴ�勎�%��X����A}l��gue֝ڋ�(|�woV�b���N�yf���M��o��؍mz��6��'6Ug?m� k��ΟD r n��C0�1�!���PTh����� j 1653 + jʓ8d[5�L3i�i�/n ��H�^`����G̚Uu�ܩz��z�'ű���ߟ�e�Iev-� �թuC�b �!� :�IS���:<��`�-�W(h2��Cy��鿽�����,����:s���(��T�!u6����RHq��;�H1������:䗡Gy���=��b=& 1654 + G����9c��QNl^���3<Fo 6����Z�(�����t{�-��������x�HR��t�nؽ��Ĩ�������;=�93�fSl֛Hb��Owf�����q����f����l�qVV����h������񡭴��sـ��7Æ����a�ʊ(E�.�����ˉ��0Ҳ�J�yR����W�yo��Pר�00!��\��<��c6������4�r�}�%x�-C�|��Cg���T�j� ���9F�6T1�M�'ޙ�J������@ɳ�^z)��5���G+�n��[�侀?\�`�MRk��W3�j�� K7;mHJu'D�p(�V�M۔�Jr�M�� Q,AM�85��TU���fi�P0O:e.��.��d&���(���:���-'5'-'}7v���n�nמ����v'�N�� �li���FF��Io��o +���R#�)���,��{饁ϯ��[W��5{xˬ�w-8<mӉ��S��{��_��˽ƶ>���{��9|,�����6��1iR�K�F1U��VCGW��r��j<�{�g;Nǒ �kd� 1655 + M��!� �R�� �pn��3W��R֎ғ�|�����uV�\��� �Y;����L�U�q�*�2-�6\;��2��GF F����-�vx���7��3!� ���Wз{����{���G���裛/~}����/��~����Ϳ�`�xW�I���L& dV$7{5��EN � ��F��(�4��p>���\+����$���8)#]�;$?{s�N�~��M�֮]O���n�|�b����!\����g�?�@�D�zZ���� 1656 + %�-���9�@=}5[�O�h���k�U�Z�Dd�ܛ���ؔx4���G#�H�񺡍H�[��"���A��#U#-/.��k���G��$2?So�9�d�̟�|���ۇ���0��7�yuH��9un�-R�-�Ϋ�y3��+M^��Q��Cg�S�S�\%9)z(l=��� sb�|�'}Z2���K�f�~�,���;:u��;�O�v�Y8����E�C�)�i{�]>3q��Ag,�ˡ�?�z�������������f(pb{`� *W?Wm~�j~�U���v?9��IN�9�A�:���;��n������.��A���]# 8�x�Ff������F-j�s)*�U�M�?�6(v��)��� J� ���Ջ_|��8����ŧyF�J�]��֊j��P��Ͱ�@*2Q�t�y׳:�/���n1v�Ó����:���I�C�D�1��Yɂ�aMw���"Z^\�O��nQ��ؽݿ�ɧ����yh��^��7k�lyJ\#�7Z?���� �q��U���{�� O��N�>y 1657 + L��d�QK��"� *T�j���[f��4>�%�4+eg�\Y�q���~�)��i�i�Tq�~��� '���� � �NG�c�Ww�</j��G��R�,��-�_o�ן��������`�������b,;x�)������٭���]�e��?�S�����E�u]/��Ɏ�e4Z�`�B!�JY^qkɶ-5t?�p��n|���g�}�i��l�d+������9�����:L���(d���nr>�c�rH��.�����y�3�.C�̞5�B�L�i�*H3�X~l�-+-a����Р�e��R�2���+����D����s���5�;�I������x�+���s)�s���pjv���`�6 ��zK0�gA=(x!�sgb�ܦ�{3$�6�4�n�Sx��@����G( �*�>�v�ݞH�jOԒl ��ԃ�� ��j^h�oM%���t�=�H]�-�����Jm���Ľ�7d��lFF�������91wC�� U�����J�C�V0�j�:�zQ�� �L��� ��b�p�k��� ��-V��w��kz�� 1658 + �̀���]U��W��lf�l�XF�+l8��X��:;4���� 1659 + �ė� � $cp )�ݥ�b��h��b$��h$x�����+}����ȅ�d��B�� y��?� ��m1�b���UjmI�+_j�zæ���u;w�[�c��. 1660 + [�f��j�Z���K�kV’���E� >f���PO�fĠ+� p�m������*k*bI��s���ֻTn�94r���r&��F�ϙ��I6c�IƈQh~HǑA*U���F��),>&����Y� _�-��o�/�t������z���̴�����c��=�����'��ʟ�l�����������K?|�� P�=�����w D){���G�,Y�]?3Ȍ�`���4›�PH�!N�Bsk �׊������K��o 1661 + �����v-M9fj_��� 1662 + o�{9�a��R] i��,p�9�s���T�æ��Q�����XV���iW��$z����O�z� ��WN�i?�1=UI'��FǡH2���s4��Y5�^G��ٞ}l��o��p]�����Q�!�a�<w�-E��F�.�� 1663 + ��S�z�� #�lz1�ĵ7߸�٥l*&��zqz�U6���zu�[�j�C��<b�^|IU�Q���v� O� �Ր����<�K�`ӔX�7>��R��۟�֮Y�vw�֭u������E�Ǘ��?�eMA0x��7Å�c~��i���.�e�7��q��}��~R\p1?�*vٹ�:4���u��������Iry��p�u]:�o��Q\u%��'���]�\�++� 1664 + �K�3Fۣ�>%ɞ��yo��U�ܮL�Os�W*=�8,C浩��Ql��b;����������ַ�����EeM�*��~�P3��������5���D�]��ź�TT�o��:�SaΔ�t�nJ�CM��ѣ�״p^K�孈�枒�7=�/�K?�6��l� � I���*��P�a�PP:b���^_��Ws>d�������O��[�������o��]����ˇ|,孽v^�4{���tՖ�Tg36������~2j��>�R�œ�л�HA���%s#"s��K�h�Z�L)b}�x�e 1665 + zJiim�}G�O�.�����/�y���Oݶso������gA�Ej��y����>����2����p�{�Gu�R���P��gh)� ����=_�3��� ��� 3+ϱ�o�~���ժ� ��4�[5��k��� �p�c4���L���~D�K���nK���шuKw���'v��� �P�m�sY����'�_�/�L}��� )���O�ʶ>��2��-[zx��s�>2�����%���o`���#񗒭ʶ���}�?n놗�=XK,��9[��9�FQ!։�n2fg�/�G$fm2fm�eG��uy�3!�0&��q�X�b���-諰f��^�f�� �AgωA�O�k�/�פ�7];���_��-0�<+���K�ݺ��'�,�y�S u{O�u�mƎ�)�Ai�.��\��0=�����tPj�%�I#���]���<ia6�l 1666 + ��uG�j'W�f���&yv�2��Y�����ݣ��Т3�T�]�������aQg�6�=o���ye ��������ߖ �P�������ݯ�W���To��aT�G�;�Ky7��9jR�j����Ɣ��=���#�{�������:�;P�…&��Ao�$����2Ƨ��?X��A�/ȧ;�=������k����}������>�˛>��i������o{�����B"Եˡ�>{��W͚�����Y��;�j���Ǜy�W N�q���:�N��6JA�۪�Mm�+^��t_����e�+�2GԷ�������D��1(����8J��WGQϻ�f���%1ō~1���)��f}qF�b�`�k�O���o;y�� �X�%�zv�m��;q��׆���>�Y=f�cl �Q��ԸqJ�Z�!������!�ù� $"+���<t�/)&ύ\-'Ib����!��;n�]��8�ne�w�7�~ꉦ�Ӟ))�ՋmcK�8�z�ů�;���,Su%o�k}� 1667 + )6"���PV��);᠋;첳uj���Ԑe�H��'�� 1668 + ��MW�o�����NY�n�v�#�%P�=�ѝ����^�~����8<Ѷh[��d Ks0y��2��1�y��T}X\�!�z�7�ڕo���=B�S̕��P��y34� �b~��5��2w��ν�$M%�d������Qo����W���N�1_�1��y�+��~�<��a���7��u���X ��hǜJ� �EE�:�@_ȓ{�<���،t-YnVe�Z�&]Q{��7�=$l���<��vܶp^m(�N׎o�?W�3(��D�<���A_���%~�X�o"�W7�2�h/����:[�H���Ս�|�Ѥ�����(C3���x�8�5؍2d�����S���q�c@?�)^V�8��U�e���0 X=�؎Z� ��M�� ��D��P���؅3��屇Y#�F�I٩!��&�(u��G�T��O�{����ֆi�k�m�l��.��y��������(uq:����:���\O��r��K�MnUdj�����@�;$"�f�C5U�S����5fHEcx���?������ȃ���c ]!�c;�YRx�BG�tg�����va(*�+� ��eX�T��<�B*����#s��(�",G%�c�c���(��CR1�����F�Ҽ��J��2܇�(E����s0UH�<�a�`�93sL��X�%H�RTa.c�!��@9��Ϯ�3٤")�C��Ũ@��T�%���fY�G�N�#�#k;�^`޵4Z�^j�KQ�ld�4<�>T! ��@�a��k���ea �c9Ft��6���Q��$�r�\��bT`z����AR��yC�rs��I�o{����?���S��΋�(J��;���e�����*��l�I�ܤ����2����^ M.e)����|:���ea˛����0%\b�_�s�C�by����0�˼0���M):���3��ci�z���m�ny�|3j,N��%�����R�[�^�yX�9a�,��*��T�����Y�EX���m2�s��.�_�a?��1�w�b*�!�l����@Zl��ʔ���AR�8��bT�T,LV�>Pf���02�ޝ5��o���6i�L �t��K\�m���(3��H�>mzf�y'դlŃE{Q����n�#�Y�Z~fi����5Za�Q�/q�D�3g. kh��EMz�Ԥ”��D�n��<��5��/gɈ���R�Em��D��S«�`.*���n�����ogY/$]�e���Fbe� s@�uR+ۖ�d���kV&������|!m_n~����˱Ҕw��$��NH}�Z���6�%w����HF��K?]�vǒTb�Rڳ��E���b�U���U�Fr�Dva� -}b��MY�޲9TjgIR���z|*��Ns\i'���X�3��q����KLNR���-+�O)_y'��;�m�������p���sZajUj�O�A]Lo���r~���w�6+v�^Wg$.Vm��*�aqK܇ 1669 + ,�b�q�����s�K�ULF��6��o���#�(���ʰ���w���ݍr�|Ze���p�w�+��l��2�.n���Q�(�AX�'��U_��(���=�cΑ���(������������:- EY�!5 1670 + �M>0SL>0Sp�`��������� �� �p 3�2�|"����xJL�p�Iˢ1 CL�ӑjҖ��x�j J0�̵�1��1�M�0ɤ=1%&O������1��4�Q��xL0���q�,��S0���RIʓ�$�ᘄ�~:�(1�I�%��x|��#’11��%͡�c�+y�vL�DL�dOIyXX���#0)��pS��DC11ݜ1�0ŔBr��)����Hˌ3��1�Z�M[Y�۩�}��i�!����y���X�5���N69 ��k����&)�����~R�SCIC>�(J<Ƕ͜��*ҦCL�I��z��Dd� 5�P�l�yG���%�&�kΞ����&%����%��C��Z4-��|šk�$�3޴�m�=<âw�V�H�۵�,0$�9�f�֗��y�7K/�6*2��$r��yAA����%�/��o�g$��_�o$�"����aъ��lA���ɒ��$��%�V���ͼ�4\�*�U�;v��]i���O�\۱���Hsn�u���Z�٪Y�{��=܍*Wd�����7�}X���u�~K�>��+ۺ�~T�u&���:�ȮE�e}��߫4�Z�Y��M��/�<�[� ��� 1671 + u�Q�"1��W�cy�du s�9r����u�bK��]���n�YEe/��DX��Ya����g��H�ӭ�����}�Zѷ�P��������cȾB�t��ײM���"d�2ʵ�#��—"���R��i����w�O�;��L��Q� w`f���['cF�#0�������#�0�̶}Q�dx��n�" ��+����艛�9(D�K�j{`�L>�+��6��j��v�1?����a�u�3;A�c���e$�gQXbn�A̅<s���,;< f3ǚ9��cռO�ż�� A��($誠+y�����*��r �Z��G��_M��k��j��������U@m�N�2��� 1672 + }�M�%� AͣK>�|�2���\S�2�>�X�?�FK�SA�|܅"��.�gA�����t���1���BL�����>��c��}Kl�������.��X��*�M5ov��":#��w��=A�:鴠w��h�S]��q�������M~~RЛ��XE� ������śtLЫ��nv��>z�M��<����L���������~~d& ��������QC����C�C��+���(~P���t��~���^�OAω@+�\г�~��zA�<������zj����I� ���L��U�7��C�A?��lj|w)��I��8����ߝ�K�;���vFю�Y|���YTW;��m�m���=�u&�0=�Z����o�I[�A?�أ~��az�O����v=�����f�l���Rڴ�����Ѡ �� Z'���X�Z��M�A?4��'�C�V�O�`P�VѪ���j= h���U-w��h�j`|�.w���j��*�2A� 1673 + Z*�b�d^������K&SyOZ,��<�[Т<*�B �A�� 1674 + �77��4:��Bs��=A��t�Y�YJ3^���t��|t��� ���;Mt{�D~{M4Y�$A�����&�h��q,��4�0��I�K���T2��Kh��>J�ȡ^>��F O�#��6�ˇ����ꥡ J �P��D�b��ꐀ���! �h��nu��� lu����v�[�tk J�[ f�|�$h`O*Th��R��������e�x?Ac�oN�;��s�x��<��� �����$�I��L�N�,G�:L�}bx��2ɶ�n�>1�G��M�}���t�#����^J�%�����G�?�����Mє!(=:�� JK��i�(5�������JԵK"�*� t�%��% 1675 + JW��GP\l&�+�X��c3ɧS t�#�Q̽� �ɍbң��n�na�q��h����D9��M �(��G9)���n��o�W]���8���#�N6A��� �#R�8]!�er��t�2 :�V�v3���� ���_��oz�L� 1676 + endstream 1677 + endobj 1678 + 1679 + 123 0 obj 1680 + [/ICCBased 124 0 R] 1681 + endobj 1682 + 1683 + 124 0 obj 1684 + << 1685 + /Length 258 1686 + /N 1 1687 + /Range [0 1] 1688 + /Filter /FlateDecode 1689 + >> 1690 + stream 1691 + x�u��J�PFO��U�v�D�D@�`]\� 1692 + F��Mk�I���R�B|��&إ�n�ऋ��(�H�r�Tų��9�`�� ըa�&�ʺ���:�l 1693 + �3Ŭ�*ުn���h)�&C|>b���������纝�黓��A��v��Cƫ+ ��y')� 1694 + ̵�8+�/<����a�Rkp��ش�A�mW�Q�Ih��� �%��l��E5VW~��d���X��)�ϴ�:׺���b���k#�&�_¤ 3�0�� �I� 1695 + endstream 1696 + endobj 1697 + 1698 + 125 0 obj 1699 + [132 0 R /XYZ 72 645.3498 0] 1700 + endobj 1701 + 1702 + 126 0 obj 1703 + [132 0 R /XYZ 72 585.46576 0] 1704 + endobj 1705 + 1706 + 127 0 obj 1707 + [132 0 R /XYZ 72 433.75177 0] 1708 + endobj 1709 + 1710 + 128 0 obj 1711 + [132 0 R /XYZ 72 359.45776 0] 1712 + endobj 1713 + 1714 + 129 0 obj 1715 + [132 0 R /XYZ 72 229.83374 0] 1716 + endobj 1717 + 1718 + 130 0 obj 1719 + [136 0 R /XYZ 72 779.8898 0] 1720 + endobj 1721 + 1722 + 131 0 obj 1723 + [136 0 R /XYZ 72 514.3126 0] 1724 + endobj 1725 + 1726 + 132 0 obj 1727 + << 1728 + /Type /Page 1729 + /Resources << 1730 + /ProcSet [/PDF /Text /ImageC /ImageB] 1731 + /ColorSpace << 1732 + /c0 123 0 R 1733 + >> 1734 + /Font << 1735 + /f0 105 0 R 1736 + /f1 111 0 R 1737 + /f2 117 0 R 1738 + >> 1739 + >> 1740 + /MediaBox [0 0 595.2756 841.8898] 1741 + /StructParents 0 1742 + /Parent 1 0 R 1743 + /Contents 133 0 R 1744 + >> 1745 + endobj 1746 + 1747 + 133 0 obj 1748 + << 1749 + /Length 2785 1750 + /Filter /FlateDecode 1751 + >> 1752 + stream 1753 + x��\[oܸ~ׯ`�u6J�4��4�ƻ-�E���!1��Ec�^�,4#R<��2�ї�%������ u���_�лwB����"�����OW��:�"��"j֒H�b�F7ߺ��n~������p�t}�]�Dt};?=~]����3!�3!t�f�7��E�.4�w��[��)�_}��z�|���Q��>��;T0��#v�����|����������v?��.C� 'XQ3 �4�� RD�":N����r��&��������sPՏ�:ށ;]��8�R�K��F�h��Ѣ�3Z��x�M�K���b��Cfy�j+��)����iA x�5컽 1754 + ��.�� 1755 + �j� 1756 + �~����D�Cr��8RZc*jZR�q )�;n%������I+�� 1757 + �t�Ol��c�g��෬1@&�RLb:��� �M�[,�Q�T sM�"�j��3�����O��u6.��3Q[�ΰ����"I�nuh�B ���|M9�Ac�ǩM0�3D�ʩ����[#��T���(�tt%8Eg���;��g��s�$�윇�^& 2t/Y���j^x��v&�(��4���hc���r���3�@f�U�oH��43%�ܠ�5�[���|��<��/�~�#:��yK��[^����-�g��I-��%e����N�Z�|�?��w{��������)�l�nyK? ������v<T�Ы�Ǐ�{^X�X%1'T�m^O�:`J 1758 + xbNrYPl'^������N�)9��p��/�뼊(���0X���x 2G�b� mH��[@0��D�۔�: �i`�|� ��u�>O��g�X �8�gZ�zk�]4J������r��>|��kL u8��SGЧ��t}��۾u��@��>���`J�Σ2A����wT�$6F�<��X"���!���I���c�wT���z�Q���s>o�Q�RzN�u�Ȉ���?��'z���>�' ��v�=d9U�كU�����ᨑۗ�G ��0�Ҧ3D��[�?+j c��� ��t=�1^�ҟ��  3@}[�^�\�Q^~p��� 1759 + ���#��R��ė�ߢ1Ʒ �W�v]�o�K���a��5�,t� ��?S̙����Ql�`Z%�� �0�u�\I;S&3�K̤�����:dT ���ߤ�����i�����>|6�쪒��Tr�_%" ���P��"A�5��k5 h3�qL�\l�1�L����G;��7�߃_R<���6�W�����F���DX���hF�$��m������У K���`1��b|��M�|VR��k���4�*f�d�~�7��wY7i�W�w�U C��o�m��*��#�x�����=�,�`�e�}ΗT��sJc�� 1760 + &9�x����b�fHP���k������j.��B.rbP*�Tj�O��`�f���ޢb���].+�}f /������l��n[���*����mX`A3��D��Ei���}U�Sq�� \6 v��ā�=�D �m�N(ɤ�3Sa�8��p�vPiW��R��+��c�6Ű-e����ʼn��4d-������������a��{�6A��G�|�,�B��Y x��Q��^�iF���8��U�z9i�� d}7�xP����(@|��ЋTLhg��d�vs�'U3�7i( �%�DS?=8X���3ɭy�0Ʌ5�,��N���O���G�2�&��T�3ug� fftE����V�Y��AɪT��W��@G��} 1761 + UN��v1���O�&�!pc+[����:r�7����@�pE�O�����|$>����9��=�E������Ě�z���R?δ�!(p��`����.b�(�'�gÊ�7����� J���]�j�5�ޞ�p���� ��B�I�9��H� 1762 + ��+6lhٙW�)���o:1a����f�~h�` �j �$+��@�GJ�u���_�u��[�A�k����II ��_^�6�[Mk?a���g6��q�W�4��Ԁ�xzr��m�F<�@R�'iD�`���wt2�x�D|g��)��}�!�?V��t���8Q�p��~'0E�[���͕�K�2,�9U�D+���f�gC�9�����ے� �R6��Z�C���҂�c���Єv����U�S?i��!?��,�����Ӻ�2�9k���t?H���ѣ��Zq�ǴdB�Jy%��� 1763 + �� V�ks��pa�' ^�=�������,��+P�����{�.�֮�ս�d�x��އZ��ek�W�B�wBF��#1v�31�;` �_t���;�`���,_$2����J�Iq[My�ܖnm:�-�$.u�6oju����ӵ9T ������>�L�!:�j/�u��n�s��]�Ի�ϛ��Et�����u��c��+� ��lKg��>N��rٻ����MPi0V���@��]��4o�Bl��o� ����b����2��T�}�"d'a ���; �oI᫠��]9S���ӝÕK�]�ژ����@���_�e#c��Z����B�A��SMj.IA�7L����9@�WɎX"-X~�R����N��M��){s�&��B�L� � �9�i +hˆ���J�Ѧ��;���鱲)�%�C|�3%m�,��R�,��sY�k��Q�s�)�z�%������IlP�0�fU�T�v��vo�����%x}\>/��b1��5�XY�B�Y-)h���������+a�\!?LQDy�����Q�b%̪f�6�S��0���:�E˲5�T�U�}7�ֳ��bU�Ўu������i��颷,����ǂ��+��*� 1764 + endstream 1765 + endobj 1766 + 1767 + 134 0 obj 1768 + << 1769 + /Type /Annot 1770 + /Subtype /Link 1771 + /Rect [162.629 460.65366 244.656 475.06366] 1772 + /Border [0 0 0] 1773 + /A << 1774 + /Type /Action 1775 + /S /URI 1776 + /URI (https://bentley.sh) 1777 + >> 1778 + /F 4 1779 + /StructParent 1 1780 + /Contents (https://bentley.sh) 1781 + >> 1782 + endobj 1783 + 1784 + 135 0 obj 1785 + << 1786 + /Type /Annot 1787 + /Subtype /Link 1788 + /Rect [113.459 411.37366 184.046 425.78363] 1789 + /Border [0 0 0] 1790 + /A << 1791 + /Type /Action 1792 + /S /URI 1793 + /URI (https://coord.is) 1794 + >> 1795 + /F 4 1796 + /StructParent 2 1797 + /Contents (https://coord.is) 1798 + >> 1799 + endobj 1800 + 1801 + 136 0 obj 1802 + << 1803 + /Type /Page 1804 + /Resources << 1805 + /ProcSet [/PDF /Text /ImageC /ImageB] 1806 + /ColorSpace << 1807 + /c0 123 0 R 1808 + >> 1809 + /Font << 1810 + /f0 105 0 R 1811 + /f1 111 0 R 1812 + /f2 117 0 R 1813 + >> 1814 + >> 1815 + /MediaBox [0 0 595.2756 841.8898] 1816 + /StructParents 3 1817 + /Tabs /S 1818 + /Parent 1 0 R 1819 + /Contents 137 0 R 1820 + /Annots [134 0 R 135 0 R] 1821 + >> 1822 + endobj 1823 + 1824 + 137 0 obj 1825 + << 1826 + /Length 1683 1827 + /Filter /FlateDecode 1828 + >> 1829 + stream 1830 + x��[[O7~�_aH2I1�_�(RyhU*U�V�@Q�*�U����jvl�}�cϲ@6d#e��<>�|���ޓ����������i>|@��N���"��"͐�k&�F7w�� A7_���,�O� A����� *�@��Û]sy�������;\F����O��������is�@3c0sMf W�r��Q��U�[Ĉ?�z�.��k7��ҪE�&jҰ��7.� � 1831 + ��Z����޴��0vբpB� �!�EB`k����-b��N3�&Z ���$�U5�yF`�5��ϒ���&D��6%c�}�l*~�e���� ��T������p�;�x�}-|�W8a�yޢc&��Y�‡�@:�@����m�'�v�������X5�ș� 1832 + l��i&0g����2ؔ���Ch�1��A�����ε���T�AJia�V�cR;��}�u�j�TEL*k��ڊG�ޱvB�����K���>}S�O�� 1833 + ���9ʍ�]b8t__�(P��������9��!3����s?��Y�0���A�1����"o����g�n�4ej�5QD�S��}��lˠT sB!.NeƗi��Q�G�F3IG������( ����{aڹ�~��q�:�����k�gaGIw�bc����������s0�3HGE ��� 1834 + ��F��j��hġ��jT ��/@��Ǥn��ȚX��$���9�o�>n������0�E��� �`��_f5tN;���n�� 1835 + 01��{�q�4�Rgs�4�s̭�Ik�{�qh�M<Q���T�c���� ����oU�z\�e�h�5WOC��~�&?�q5��}Lݪ^�����b@�1�(月'�1�{Z?���=�!}�:�������Iw ����@�a �L�Ң�>a<�'�Z��������95edp�%5rO]n��]�-�Veˈb�[�)�e�j�䚘��e/WmR�U7�M��V`*��[l�{;wo+m�{���˰C��y�kr�������_DO��ı��RU��&�� V��5��§E��t�m�38�ď�'��Q��� ةQ^�o;h��ϡ��P����Ph�����A����j�%�:�r�a���(�_�v��� ���,�����Q�;�Uu� ���*���&`u?D/�2a� 1836 + \�p>��0�i� �^U3jB3�b.�t��0��~�$�D�Nr����ª��������YJ��a# ��9�d��Ž��b/E��`�Opw �U � �P���1O��� i@�mc��_ Y� #s\�G�`M�����߷�7+���k��0*�� A��4�7�sw��3-"���br �D������b���G��E���P0nB��Җq$*n~קּ]�vʂP��Y�8�2�Ą�#��6���&���U��`��S gk����o��a,���,P�3�D����q�<m3U-⯣�/0���0q�R� ���N跳�?�;+ӝ �}"�{;��}��A�^�����`C��[nj�/g�Ya�t)r@<Z&.�qZV �>]��̳F�!SEĽ�W�GM�^� 1837 + ��B1���P��St���p�/)�)�G#�k#���Y �xoŜV�ٱh�2�rt��(M! �W0X� 1838 + A� �F� A�����҇�ͱ~n��:讽������G���J�?:�u� 1839 + endstream 1840 + endobj 1841 + 1842 + 138 0 obj 1843 + << 1844 + /Title (Encryption Documentation - Coords) 1845 + /Author (Allison Bentley) 1846 + /Creator (Typst 0.14.2) 1847 + /ModDate (D:20260127221602Z) 1848 + /CreationDate (D:20260127221602Z) 1849 + >> 1850 + endobj 1851 + 1852 + 139 0 obj 1853 + << 1854 + /Length 1183 1855 + /Type /Metadata 1856 + /Subtype /XML 1857 + >> 1858 + stream 1859 + <?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="xmp-writer"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" xmlns:pdf="http://ns.adobe.com/pdf/1.3/" ><dc:title><rdf:Alt><rdf:li xml:lang="x-default">Encryption Documentation - Coords</rdf:li></rdf:Alt></dc:title><dc:creator><rdf:Seq><rdf:li>Allison Bentley</rdf:li></rdf:Seq></dc:creator><xmp:CreatorTool>Typst 0.14.2</xmp:CreatorTool><dc:language><rdf:Bag><rdf:li>en</rdf:li></rdf:Bag></dc:language><xmp:ModifyDate>2026-01-27T22:16:02+00:00</xmp:ModifyDate><xmp:CreateDate>2026-01-27T22:16:02+00:00</xmp:CreateDate><xmpTPg:NPages>2</xmpTPg:NPages><dc:format>application/pdf</dc:format><xmpMM:InstanceID>/edj7ocCzVTQWn1Wn/xHAA==</xmpMM:InstanceID><xmpMM:DocumentID>R2RXNK7eVI6P8V9DGMN8bw==</xmpMM:DocumentID><xmpMM:RenditionClass>proof</xmpMM:RenditionClass><pdf:PDFVersion>1.7</pdf:PDFVersion></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="r"?> 1860 + endstream 1861 + endobj 1862 + 1863 + 140 0 obj 1864 + << 1865 + /Type /Catalog 1866 + /Pages 1 0 R 1867 + /Metadata 139 0 R 1868 + /Lang (en) 1869 + /StructTreeRoot 10 0 R 1870 + /MarkInfo << 1871 + /Marked true 1872 + /Suspects false 1873 + >> 1874 + /ViewerPreferences << 1875 + /Direction /L2R 1876 + >> 1877 + /Outlines 2 0 R 1878 + >> 1879 + endobj 1880 + 1881 + xref 1882 + 0 141 1883 + 0000000000 65535 f 1884 + 0000000016 00000 n 1885 + 0000000090 00000 n 1886 + 0000000170 00000 n 1887 + 0000000261 00000 n 1888 + 0000000384 00000 n 1889 + 0000000495 00000 n 1890 + 0000000613 00000 n 1891 + 0000000733 00000 n 1892 + 0000000839 00000 n 1893 + 0000000941 00000 n 1894 + 0000001197 00000 n 1895 + 0000001620 00000 n 1896 + 0000001870 00000 n 1897 + 0000002133 00000 n 1898 + 0000002226 00000 n 1899 + 0000002333 00000 n 1900 + 0000002480 00000 n 1901 + 0000002570 00000 n 1902 + 0000002667 00000 n 1903 + 0000002814 00000 n 1904 + 0000002932 00000 n 1905 + 0000003106 00000 n 1906 + 0000003193 00000 n 1907 + 0000003280 00000 n 1908 + 0000003367 00000 n 1909 + 0000003454 00000 n 1910 + 0000003541 00000 n 1911 + 0000003628 00000 n 1912 + 0000003715 00000 n 1913 + 0000003809 00000 n 1914 + 0000003901 00000 n 1915 + 0000004075 00000 n 1916 + 0000004162 00000 n 1917 + 0000004248 00000 n 1918 + 0000004334 00000 n 1919 + 0000004420 00000 n 1920 + 0000004506 00000 n 1921 + 0000004592 00000 n 1922 + 0000004678 00000 n 1923 + 0000004771 00000 n 1924 + 0000004862 00000 n 1925 + 0000004968 00000 n 1926 + 0000005128 00000 n 1927 + 0000005213 00000 n 1928 + 0000005304 00000 n 1929 + 0000005393 00000 n 1930 + 0000005478 00000 n 1931 + 0000005569 00000 n 1932 + 0000005658 00000 n 1933 + 0000005743 00000 n 1934 + 0000005834 00000 n 1935 + 0000005923 00000 n 1936 + 0000006008 00000 n 1937 + 0000006099 00000 n 1938 + 0000006188 00000 n 1939 + 0000006273 00000 n 1940 + 0000006364 00000 n 1941 + 0000006453 00000 n 1942 + 0000006543 00000 n 1943 + 0000006665 00000 n 1944 + 0000006805 00000 n 1945 + 0000006890 00000 n 1946 + 0000006971 00000 n 1947 + 0000007071 00000 n 1948 + 0000007163 00000 n 1949 + 0000007252 00000 n 1950 + 0000007337 00000 n 1951 + 0000007418 00000 n 1952 + 0000007515 00000 n 1953 + 0000007607 00000 n 1954 + 0000007696 00000 n 1955 + 0000007783 00000 n 1956 + 0000007903 00000 n 1957 + 0000008006 00000 n 1958 + 0000008096 00000 n 1959 + 0000008209 00000 n 1960 + 0000008389 00000 n 1961 + 0000008481 00000 n 1962 + 0000008661 00000 n 1963 + 0000008841 00000 n 1964 + 0000009021 00000 n 1965 + 0000009113 00000 n 1966 + 0000009293 00000 n 1967 + 0000009473 00000 n 1968 + 0000009653 00000 n 1969 + 0000009745 00000 n 1970 + 0000009925 00000 n 1971 + 0000010105 00000 n 1972 + 0000010285 00000 n 1973 + 0000010377 00000 n 1974 + 0000010547 00000 n 1975 + 0000010639 00000 n 1976 + 0000010809 00000 n 1977 + 0000010901 00000 n 1978 + 0000011071 00000 n 1979 + 0000011163 00000 n 1980 + 0000011250 00000 n 1981 + 0000011374 00000 n 1982 + 0000011462 00000 n 1983 + 0000011567 00000 n 1984 + 0000011654 00000 n 1985 + 0000011749 00000 n 1986 + 0000011840 00000 n 1987 + 0000011927 00000 n 1988 + 0000012014 00000 n 1989 + 0000012192 00000 n 1990 + 0000012843 00000 n 1991 + 0000012935 00000 n 1992 + 0000013185 00000 n 1993 + 0000014475 00000 n 1994 + 0000017821 00000 n 1995 + 0000018002 00000 n 1996 + 0000018846 00000 n 1997 + 0000018937 00000 n 1998 + 0000019188 00000 n 1999 + 0000020832 00000 n 2000 + 0000025696 00000 n 2001 + 0000025861 00000 n 2002 + 0000026129 00000 n 2003 + 0000026220 00000 n 2004 + 0000026497 00000 n 2005 + 0000027885 00000 n 2006 + 0000037169 00000 n 2007 + 0000037207 00000 n 2008 + 0000037566 00000 n 2009 + 0000037613 00000 n 2010 + 0000037661 00000 n 2011 + 0000037709 00000 n 2012 + 0000037757 00000 n 2013 + 0000037805 00000 n 2014 + 0000037852 00000 n 2015 + 0000037899 00000 n 2016 + 0000038209 00000 n 2017 + 0000041074 00000 n 2018 + 0000041325 00000 n 2019 + 0000041572 00000 n 2020 + 0000041921 00000 n 2021 + 0000043684 00000 n 2022 + 0000043874 00000 n 2023 + 0000045148 00000 n 2024 + trailer 2025 + << 2026 + /Size 141 2027 + /Root 140 0 R 2028 + /Info 138 0 R 2029 + /ID [(R2RXNK7eVI6P8V9DGMN8bw==) (/edj7ocCzVTQWn1Wn/xHAA==)] 2030 + >> 2031 + startxref 2032 + 45386 2033 + %%EOF
+110
apple/encryption-documentation.typ
··· 1 + #set document( 2 + title: "Encryption Documentation - Coords", 3 + author: "Allison Bentley", 4 + ) 5 + #set page(margin: 1in) 6 + #set text(font: "Source Sans 3", size: 11pt) 7 + #set heading(numbering: "1.") 8 + 9 + #align(center)[ 10 + #text(size: 18pt, weight: "bold")[Encryption Documentation] 11 + 12 + #text(size: 14pt)[Coords iOS Application] 13 + 14 + #v(0.5em) 15 + 16 + Bundle ID: `sh.bentley.Transponder` 17 + 18 + #datetime.today().display("[month repr:long] [day], [year]") 19 + ] 20 + 21 + #v(2em) 22 + 23 + = Overview 24 + 25 + This document describes the cryptographic functionality used in the Coords iOS application for the purpose of U.S. Export Administration Regulations (EAR) compliance. 26 + 27 + = Encryption Algorithms Used 28 + 29 + The application uses the following standard cryptographic algorithms: 30 + 31 + #table( 32 + columns: (auto, auto, auto), 33 + inset: 8pt, 34 + align: left, 35 + [*Algorithm*], [*Standard*], [*Purpose*], 36 + [Ed25519], [RFC 8032 (IETF)], [Digital signatures for user identity verification], 37 + [X25519], [RFC 7748 (IETF)], [Elliptic curve Diffie-Hellman key exchange], 38 + [AES-256-GCM], [NIST SP 800-38D], [Authenticated encryption of location data], 39 + ) 40 + 41 + = Implementation 42 + 43 + The cryptographic algorithms are implemented via a bundled Rust library (`transponder_core`) rather than Apple's CryptoKit or Security framework. The implementations use well-established, audited cryptographic libraries from the Rust ecosystem. 44 + 45 + = Purpose of Encryption 46 + 47 + The encryption in this application is used solely for: 48 + 49 + + *User Authentication* — Ed25519 digital signatures verify user identity when sharing location data with friends. Each user generates a keypair that serves as their cryptographic identity. 50 + 51 + + *Personal Data Protection* — X25519 key exchange derives a shared secret, which is then used with AES-256-GCM to encrypt location data. This provides end-to-end encryption where only the intended recipient can decrypt the data. 52 + 53 + = Exemption Qualification 54 + 55 + This use of encryption qualifies for export exemption under EAR §740.17(b)(1) for the following reasons: 56 + 57 + - All algorithms used are publicly available, international standards published by IETF and NIST 58 + - No proprietary or non-standard cryptographic algorithms are used 59 + - Encryption is used exclusively for authentication and protection of personal user data 60 + - The application does not provide encryption as a service to third parties 61 + - The application is not designed for government or military use 62 + 63 + = Data Flow 64 + 65 + *Key Exchange* (out-of-band, no server involvement): 66 + 67 + #figure( 68 + ``` 69 + User A User B 70 + | | 71 + |------------------ Public key via QR code or coord:// link ->| 72 + |<- Public key via QR code or coord:// link --------------------| 73 + | | 74 + |-- Derive shared secret (X25519) | 75 + | Derive shared secret (X25519) --| 76 + ```, 77 + ) 78 + 79 + #v(1em) 80 + 81 + *Location Sharing* (server only sees encrypted data): 82 + 83 + #figure( 84 + ``` 85 + User A Server User B 86 + | | | 87 + |-- Sign (Ed25519) ------->| | 88 + |-- Encrypt (AES-GCM) ---->| | 89 + | |-- Encrypted blob --->| 90 + | | Decrypt (AES-GCM) 91 + | | Verify (Ed25519) 92 + ```, 93 + ) 94 + 95 + = Contact Information 96 + 97 + Developer: Allison Bentley \ 98 + Developer Website: https://bentley.sh \ 99 + 100 + Application: Coords \ 101 + Bundle Identifier: `sh.bentley.Transponder` \ 102 + Website: https://coord.is 103 + 104 + #v(2em) 105 + 106 + #line(length: 100%) 107 + 108 + #text(size: 9pt, fill: gray)[ 109 + This document is provided for U.S. export compliance purposes under the Export Administration Regulations (EAR). The cryptographic functionality described herein is limited to authentication and personal data protection as defined in EAR §740.17(b)(1). 110 + ]
apple/screenshots/Simulator Screenshot - iPhone 17 - 2026-01-27 at 22.48.47.png

This is a binary file and will not be displayed.

apple/screenshots/Simulator Screenshot - iPhone 17 - 2026-01-27 at 22.49.07.png

This is a binary file and will not be displayed.

apple/screenshots/Simulator Screenshot - iPhone 17 - 2026-01-27 at 22.49.52.png

This is a binary file and will not be displayed.

+3
deploy/coord.is/configuration.nix
··· 58 58 dates = "03:00"; 59 59 }; 60 60 61 + # Timeout upgrade by 04:00 so it doesn't block fdroid-update (04:30) 62 + systemd.services.nixos-upgrade.serviceConfig.TimeoutStartSec = "60m"; 63 + 61 64 # Tailscale (connects to headscale) 62 65 services.tailscale = { 63 66 useRoutingFeatures = "server";
+3 -3
flake.lock
··· 89 89 }, 90 90 "nixpkgs": { 91 91 "locked": { 92 - "lastModified": 1769089682, 93 - "narHash": "sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms=", 92 + "lastModified": 1769318308, 93 + "narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=", 94 94 "owner": "NixOS", 95 95 "repo": "nixpkgs", 96 - "rev": "078d69f03934859a181e81ba987c2bb033eebfc5", 96 + "rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c", 97 97 "type": "github" 98 98 }, 99 99 "original": {
+4
flake.nix
··· 203 203 pkgs.unzip 204 204 pkgs.gdal 205 205 (pkgs.python3.withPackages (ps: [ ps.shapely ])) 206 + # Documentation 207 + pkgs.typst 208 + pkgs.source-sans 206 209 ]; 207 210 208 211 shellHook = '' 209 212 export ANDROID_HOME="${androidSdk}/libexec/android-sdk" 210 213 export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/26.3.11579264" 211 214 export JAVA_HOME="${pkgs.jdk17}" 215 + export TYPST_FONT_PATHS="${pkgs.source-sans}/share/fonts/opentype" 212 216 ''; 213 217 }; 214 218 }
+107
website/src/content/docs/docs/privacy.md
··· 1 + --- 2 + title: Privacy Policy 3 + description: How Coords handles your data 4 + --- 5 + 6 + *Last updated: January 27, 2026* 7 + 8 + ## Overview 9 + 10 + Coords is designed with privacy as a core principle. Your location data is end-to-end encrypted and can only be read by friends you explicitly add. The Coords server acts as a relay for encrypted data it cannot decrypt. 11 + 12 + ## Data We Collect 13 + 14 + ### Location Data 15 + 16 + When you enable location sharing, your device encrypts your location using AES-256-GCM before uploading it to the Coords server. The server stores only the most recent encrypted blob for each user. **The server cannot decrypt your location**—only friends who have exchanged keys with you can decrypt it. 17 + 18 + - Location data is encrypted on your device before transmission 19 + - Only your most recent location is stored (no history) 20 + - Encrypted blobs are automatically deleted when replaced by newer uploads 21 + 22 + ### Cryptographic Identity 23 + 24 + Your device generates an Ed25519 keypair when you first use the app. Your public key serves as your identity on the network. Your private key never leaves your device. 25 + 26 + - No account registration required 27 + - No email, phone number, or personal information collected 28 + - Your identity is just a cryptographic public key 29 + 30 + ### Server Logs 31 + 32 + The Coords server may temporarily log: 33 + 34 + - IP addresses (for rate limiting and abuse prevention) 35 + - Timestamps of requests 36 + - Public keys involved in uploads/fetches 37 + 38 + These logs are not correlated with location data (which the server cannot read) and are retained only as long as necessary for operational purposes. 39 + 40 + ## Data We Do Not Collect 41 + 42 + - Your actual location (only encrypted blobs) 43 + - Your name, email, or phone number 44 + - Your contacts or address book 45 + - Device identifiers or advertising IDs 46 + - Analytics or usage tracking data 47 + - Location history (only the latest upload is stored) 48 + 49 + ## How Your Data Is Used 50 + 51 + - **Encrypted location blobs**: Relayed to friends who request them 52 + - **Public keys**: Used to verify signatures on uploaded data 53 + - **Server logs**: Used only for rate limiting and abuse prevention 54 + 55 + We do not sell, share, or monetize your data in any way. 56 + 57 + ## Third-Party Services 58 + 59 + ### Map Tiles 60 + 61 + The app displays maps using third-party providers: 62 + 63 + - **iOS**: Apple MapKit 64 + - **Android**: MapTiler 65 + 66 + These providers may receive your device's approximate viewport location when loading map tiles. This is standard for any map-based application. Refer to [Apple's Privacy Policy](https://www.apple.com/legal/privacy/) and [MapTiler's Privacy Policy](https://www.maptiler.com/privacy-policy/) for details. 67 + 68 + ## Data Retention 69 + 70 + - **Encrypted location blobs**: Replaced with each new upload; only the latest is stored 71 + - **Server logs**: Retained for a limited period for operational purposes, then deleted 72 + 73 + ## Your Rights 74 + 75 + You have the right to: 76 + 77 + - **Delete your data**: Stop sharing and your encrypted blob will be replaced or expire 78 + - **Export your data**: Your cryptographic identity is stored locally on your device 79 + - **Opt out**: Simply uninstall the app; no account deletion needed since there are no accounts 80 + 81 + ## Security 82 + 83 + - All data in transit uses HTTPS/TLS 84 + - Location data is encrypted with AES-256-GCM before leaving your device 85 + - Key exchange uses X25519 elliptic curve Diffie-Hellman 86 + - Digital signatures use Ed25519 87 + - The server is open source and can be self-hosted 88 + 89 + ## Children's Privacy 90 + 91 + Coords is not intended for children under 13. We do not knowingly collect personal information from children under 13. If you believe a child under 13 has provided us with personal information, please contact us. 92 + 93 + ## Changes to This Policy 94 + 95 + We may update this privacy policy from time to time. We will notify users of any material changes by updating the "Last updated" date at the top of this page. 96 + 97 + ## Open Source 98 + 99 + Coords is fully open source. You can audit the code yourself through the links available on this site. 100 + 101 + ## Contact 102 + 103 + If you have questions about this privacy policy or your data, contact: 104 + 105 + Allison Bentley 106 + Email: allison@bentley.sh 107 + Website: https://coord.is