](http://apps.obtainium.imranr.dev/redirect.html?r=obtainium://add/https://github.com/stormlightlabs/lazurite/releases)
## Features
-### Home Feed & Composer
-
-| Home Feed | Composer | Profile |
-| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
-|  |  |  |
-| View your personal timeline with support for threads and media. | Create new posts with rich text and media attachments. Supports replies and quoting. | View detailed actor profiles, including their feed and metadata. |
-
-### Search & Profile
-
-| Search | About | DevTools |
-| ----------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |
-|  |  |  |
-| Discover people and posts across the Bluesky network. | About (showing Rose Pine Moon theme) | Built-in logs and developer utilities for exploring the AT Protocol (Rose Pine Dawn). |
-
-### Offline Support & Drafts
-
-Local-only drafts and caching powered by Drift (SQLite).
-
-- **Drafts:** Save posts locally and publish later.
-- **Search History:** Persisted local search history.
-- **Saved Feeds:** Manage and pin your favorite feeds.
-
-## What Lazurite Offers Beyond Bluesky
-
-### Available Now
-
-- **Semantic Search:** On-device vector embeddings (all-MiniLM-L6-v2) let you search saved
- and liked posts by meaning, not just keywords.
-- **Post Scheduling:** Write posts now, publish them later.
-- **Follow Audit:** Bulk-analyze your follows to find deleted, deactivated, suspended, or
- blocking accounts. Batch unfollow in one tap like [clean follows](https://cleanfollow-bsky.pages.dev/)
-- **Constellation Integration:** See who has blocked you and which lists you appear on,
- powered by [Constellation](https://constellation.microcosm.blue) backlinks.
-- **AT Protocol Dev Tools:** Browse any user's PDS repository, inspect collections and
- individual records as JSON, like an in-app [pds.ls](https://pds.ls/).
-- **Rich Theming:** Five full palettes (Lazurite™️[^2], Rose Pine, Catppuccin, Nord, Oxocarbon),
- each with light and dark variants, built on Material 3.
-- **Offline First:** First page of feeds is cached locally; drafts, search history, and saved
- posts persist in an on-device database.
-- **Local Drafts:** Auto-saved to the database, surviving crashes and force-closes. Multiple
- drafts per account with full reply/quote/media context.
-- **Layout Options:** Toggle between Card and Compact feed views. Configure thread
- auto-collapse depth (off, 1–6 levels).
-- **In-App Logs:** Filter by level, full-text search, share or export, useful for
- debugging and AT Protocol development.
-
-### On the Roadmap
-
-- **RSS Feed Export:** View and export any public Bluesky profile as an RSS feed.
-- **Custom Fonts:** User-selectable serif, sans-serif, and monospace typefaces across the
- entire app.
-- **Markdown Posts:** Toggleable Markdown rendering in post bodies.
-- **Firehose & Jetstream Viewers:** Live AT Protocol event streams inside Dev Tools.
-- **Auto-Threading:** Automatically split long posts into threaded replies.
-- **Last Read Position:** Resume your timeline exactly where you left off.
+### Core Bluesky & BlackSky Client
+
+- Home timeline, custom feeds, feed pinning, and feed reordering.
+- Post threads, replies, quote posts, reposts, likes, saves, and sharing.
+- Rich post composition with facets, images, video uploads, replies, quotes, drafts, and scheduling.
+- Profile screens with author feeds, likes, starter packs, lists, follows, followers, and profile actions.
+- Search for posts, actors, hashtags, starter packs, and profile-scoped posts.
+- Notifications, direct messages, lists, starter packs, labelers, and moderation preferences.
+- In-app image viewer, video playback, media sharing, and media download support.
+
+### Local And Offline Features
+
+- Drift-backed local cache for the first page of feeds and profile data.
+- Local drafts with account-scoped reply, quote, and media context.
+- Local saved posts, liked-post sync, and search history.
+- On-device semantic search for saved and liked posts using MiniLM embeddings and ObjectBox vector search.
+- Offline-aware screens that render cached data and disable network-only actions when needed.
+
+### Account And Protocol Tools
+
+- OAuth login, account switching, session restore, and debug app-password login.
+- Provider-aware AppView routing for Bluesky, Blacksky, and validated custom AppViews.
+- Follow audit for deleted, deactivated, suspended, blocking, hidden, and self-follow records.
+- Profile context powered by [Constellation](https://constellation.microcosm.blue) backlinks.
+- AT Protocol Dev Tools for browsing PDS repositories, collections, and records as JSON.
+- In-app logs with level filters, search, sharing, and export for debugging.
+
+### Customization
+
+- Five theme palettes: Lazurite™️[^2], Rose Pine, Catppuccin, Nord, and Oxocarbon.
+- Light and dark variants built on Material 3.
+- Card and Compact feed layouts.
+- Configurable thread auto-collapse depth.
+
+## Planned
+
+### Reading And Media
+
+- Gallery mode for browsing media-heavy feeds and post threads.
+- Last-read position for resuming timelines.
+- RSS feed export for public Bluesky profiles.
+
+### Publishing
+
+- Markdown rendering for post bodies.
+- Auto-threading for long posts.
+
+### Customization
+
+- User-selectable serif, sans-serif, and monospace typefaces.
+- Expanded layout controls for feed density and feed architecture.
+
+### Protocol And Maintenance
+
+- Social graph visualization.
+- Firehose and Jetstream viewers inside Dev Tools.
+- Expanded notification settings, permission flows, and remote push validation.
## Architecture
diff --git a/docs/dev/animate.md b/docs/dev/animate.md
new file mode 100644
index 0000000..ef848eb
--- /dev/null
+++ b/docs/dev/animate.md
@@ -0,0 +1,65 @@
+---
+title: Motion
+updated: 2026-05-07
+---
+
+Lazurite uses small motion cues to show navigation, feedback, and loading
+state. Motion should help the user understand what changed. It should not draw
+attention to itself.
+
+`flutter_animate` is the shared animation package for widget-level effects such
+as fades, slides, scales, shimmer, and staggered entrances. Use raw controllers
+only when the interaction needs custom timing or scroll-driven behavior that
+the shared package does not fit.
+
+## Shared Tokens
+
+Animation durations, curves, and stagger offsets belong in
+`lib/core/theme/animation_tokens.dart`. Shared helpers live in
+`lib/core/theme/animation_utils.dart`. Widget files should use those tokens
+instead of local magic numbers. This keeps feed cards, snackbars, buttons, and
+empty states moving at the same pace.
+
+Common timing buckets are fast feedback, normal entrance or exit, and slower
+state transitions. Staggered lists cap the number of offset items so pagination
+does not create long delayed sequences.
+
+## Where Motion Is Used
+
+Feed cards, notification rows, search results, follow-audit rows, list members,
+and saved posts use a one-time entrance as new items appear. Track seen item
+keys so scrolling back does not replay the animation.
+
+Like, repost, and bookmark controls use short scale feedback when toggled.
+Bottom navigation uses a small active-icon transition. Floating action buttons
+scale in when they appear and scale out when removed. Snackbars enter from the
+bottom and dismiss quickly.
+
+Loading placeholders use shimmer where it communicates waiting for content with
+known shape. Empty states fade and scale in once, avoiding abrupt swaps between
+loading and empty UI.
+
+Profile banner parallax is scroll-driven and should stay separate from
+`flutter_animate`. It is implemented with scroll offset and transforms so it
+does not trigger layout work.
+
+## Reduced Motion
+
+Respect `MediaQuery.disableAnimations`. When the platform asks for reduced
+motion, skip nonessential transitions. Route changes can use a short crossfade,
+and loading indicators may continue when they communicate progress rather than
+decoration.
+
+Reduced-motion behavior needs widget coverage. Tests should mount the widget
+with disabled animations and verify that optional animation wrappers are not in
+the tree or that the final state is reached without waiting for motion.
+
+## Performance And Tests
+
+Prefer transform and opacity effects because they stay on the compositor path.
+Avoid animating dimensions in scrolling lists. Check feed and profile surfaces
+with Flutter performance tools when adding broad motion.
+
+Tests should settle animations before checking final state. Token tests guard
+against accidental timing drift, while focused widget tests cover action
+feedback, reduced motion, shimmer placeholders, and one-time list entrances.
diff --git a/docs/dev/compose-notifications-actions.md b/docs/dev/compose-notifications-actions.md
new file mode 100644
index 0000000..9542a19
--- /dev/null
+++ b/docs/dev/compose-notifications-actions.md
@@ -0,0 +1,70 @@
+---
+title: Compose, Notifications, And Actions
+updated: 2026-05-07
+---
+
+Compose, notification polling, post actions, profile actions, and saved posts
+touch local state and network writes. Use optimistic UI only where rollback
+behavior is clear and tested.
+
+## Compose
+
+`ComposeBloc` in `lib/features/compose/bloc/compose_bloc.dart` tracks text,
+facets, media, reply refs, quote refs, language tags, and submission status.
+Posts are written through `ComposeRepository` with `com.atproto.repo.createRecord`
+in the `app.bsky.feed.post` collection.
+
+Text length is counted with Dart grapheme clusters, not code units. The submit
+action is disabled for empty text and over-limit posts. Rich text facets are
+detected before submission and rendered as a live preview while the user types.
+
+Images upload through `com.atproto.repo.uploadBlob` and are embedded as
+`app.bsky.embed.images`. A post may include up to four images. Video upload
+uses `app.bsky.video.uploadVideo`, then polls job status until the processed
+blob is available. Video and image embeds are mutually exclusive; switching
+between them should ask before replacing existing attachments.
+
+Drafts are account-scoped Drift rows. Network failure and explicit save both
+persist the draft. Scheduled posts extend the draft model with a future publish
+time and rely on platform background scheduling to retry when connectivity
+returns.
+
+## Notifications
+
+`NotificationBloc` in `lib/features/notifications/bloc` owns polling state.
+Polling notifications use `app.bsky.notification.listNotifications`,
+`getUnreadCount`, and `updateSeen`. Notifications are grouped by day and render
+author, reason, reason icon, read state, and an optional post preview.
+
+Foreground unread polling runs on an interval while the app is active. Opening
+the notifications screen marks current notifications as seen. Tapping a
+notification routes to the relevant post or profile. Later push notification
+work builds on this navigation and seen-state model.
+
+## Post And Profile Actions
+
+Likes, reposts, follows, and blocks are AT Protocol records. Muting is a server
+procedure call. `PostActionRepository` and `ProfileActionRepository` should
+derive delete keys from viewer state URIs rather than guessing record keys.
+
+Post actions manage like, repost, reply, share, save, report, and copy-link
+behavior. Profile actions manage follow, mute, block, report, DID copy, and
+profile sharing. Destructive actions require confirmation where user intent
+could be ambiguous.
+
+Optimistic updates immediately adjust icon state and counts, run the network
+request, then reconcile with the server response. On failure, state rolls back
+and the user receives a snackbar. Tests should cover success, rollback, and
+viewer-state hydration.
+
+## Saved Posts
+
+Saved posts are private and local-only. `SavedPostsCubit` reads and writes
+Drift rows with
+`account_did`, `post_uri`, serialized post JSON, and `saved_at`. The table has a
+unique account/post constraint so repeated saves update one row instead of
+creating duplicates.
+
+The save action is shown from post controls and overflow menus. Saved posts are
+read back through a Cubit that exposes both the saved-post list and a quick
+lookup stream for filled bookmark state in feeds.
diff --git a/docs/dev/feeds-search-logging.md b/docs/dev/feeds-search-logging.md
new file mode 100644
index 0000000..e7d8707
--- /dev/null
+++ b/docs/dev/feeds-search-logging.md
@@ -0,0 +1,67 @@
+---
+title: Feeds, Search, And Logging
+updated: 2026-05-07
+---
+
+Feeds, search, logging, and the PDS explorer share two implementation rules:
+repositories hide network details, and UI state stays explicit enough to test
+without live services.
+
+## Logging
+
+`AppLogger` in `lib/core/logging/app_logger.dart` is the shared logging entry
+point. Feature code should log through that wrapper so filtering, redaction,
+file output, and in-app viewing stay consistent.
+
+Console logging is development-only. File logging is available in all builds
+and writes rotated daily files in the app documents directory. HTTP logging
+must redact authorization data and avoid full request or response bodies. Use
+summary fields, route names, status codes, and bounded body previews.
+
+The log viewer in `lib/features/logs` reads persisted log files from disk. It
+supports level filtering, text search, sharing the current log, and clearing
+logs with confirmation. File reads and UI filters belong in `LogViewerCubit`
+rather than a larger feature Bloc.
+
+## Home Feeds
+
+`FeedRepository` in `lib/features/feed/data/feed_repository.dart` loads the
+Following timeline and user-pinned feed generators. The timeline uses
+`app.bsky.feed.getTimeline`. Generator feeds use `app.bsky.feed.getFeed` with
+the generator AT-URI. Both return hydrated post views and paginate by cursor.
+
+Pinned feed preferences live in the user's Bluesky preferences, backed by
+local Drift caching for offline startup. The `savedFeedsPrefV2` array controls
+which feeds appear as tabs, their order, and whether each entry is a timeline,
+generator, or list. Mutations should update local state optimistically, persist
+to the server, and reconcile with the returned preference state.
+
+The feed renderer reuses the shared post-card stack. Feed-specific code should
+own only pagination, refresh state, and feed identity.
+
+## Search
+
+`SearchRepository` in `lib/features/search/data/search_repository.dart` owns
+post and actor search. Post search uses `app.bsky.feed.searchPosts`. Actor
+search uses `app.bsky.actor.searchActors`, and handle autocomplete uses the
+typeahead path documented in [typeahead.md](./typeahead.md).
+
+Search history is account-scoped in Drift. Insertions update the timestamp for
+repeat queries, and each account keeps a bounded recent history. UI actions
+include re-running a past search, deleting one entry, and clearing all entries.
+
+Search state should keep result type, query, pagination cursor, loading state,
+and error state separate. Avoid mixing actor typeahead into post-search
+pagination state; the shared typeahead repository and cubit own autocomplete.
+
+## PDS Explorer
+
+The PDS explorer in `lib/features/devtools` is a developer tool for inspecting
+AT Protocol repositories. It resolves handles to DIDs, lists repo collections,
+paginates records, and opens individual records as formatted JSON. It also
+accepts AT-URIs so a developer can jump directly to a record.
+
+Explorer requests use `com.atproto.identity.resolveHandle`,
+`com.atproto.repo.describeRepo`, `com.atproto.repo.listRecords`, and
+`com.atproto.repo.getRecord`. The tool is read-only and should stay isolated
+from production feed or profile state.
diff --git a/docs/dev/follow-hygiene.md b/docs/dev/follow-hygiene.md
new file mode 100644
index 0000000..5a365fe
--- /dev/null
+++ b/docs/dev/follow-hygiene.md
@@ -0,0 +1,60 @@
+---
+title: Follow Hygiene
+updated: 2026-05-07
+---
+
+Follow hygiene audits the active account's follow records and helps the user
+remove dead or problematic follows in batches. It works from the user's own
+repo records, then hydrates followed accounts to classify their current state.
+
+## Audit Flow
+
+`FollowAuditRepository` in `lib/features/profile/data/follow_audit_repository.dart`
+paginates `app.bsky.graph.follow` records through
+`com.atproto.repo.listRecords` for the active DID. Each record provides the
+follow URI, record key, and subject DID. The repository then batch-hydrates
+subjects with `app.bsky.actor.getProfiles`.
+
+Missing profiles are resolved individually so the app can distinguish deleted,
+deactivated, and suspended accounts where the API exposes that difference.
+Profiles that hydrate successfully are classified from viewer state and labels.
+The implemented statuses include deleted, deactivated, suspended, blocked by,
+blocking, mutual block, hidden, and self-follow.
+
+## Rate Limits And Partial Results
+
+Profile hydration uses the SDK batch limit of 25 actors. Batches run with
+bounded concurrency and backoff on transient errors. Partial hydration failure
+does not discard the whole audit. The Cubit reports failed profile count and
+continues with the results it could classify.
+
+## Batch Unfollow
+
+Batch removal uses `com.atproto.repo.applyWrites` delete operations against the
+`app.bsky.graph.follow` collection. Each delete operation uses the rkey from
+the original follow record URI. Writes are chunked to the protocol limit and
+executed sequentially.
+
+After each successful chunk, local state updates the completed count. If a chunk
+fails, the Cubit stops, reports how many accounts were already unfollowed, and
+leaves the remaining selected rows available for retry.
+
+## UI Model
+
+`FollowAuditCubit` in `lib/features/profile/cubit/follow_audit_cubit.dart`
+drives the audit screen through fetching records, classifying profiles, ready,
+unfollowing, complete, and error states. The UI shows scan progress, category
+counts, selectable rows, visibility filters, and selected totals.
+
+Rows include checkbox, handle, truncated DID, status badge, and profile
+navigation. The screen renders empty and complete states for clean audits and
+finished removals. Entry points are guarded behind the authenticated account and
+appear where account maintenance actions are expected.
+
+## Boundaries
+
+The audit does not infer inactivity from posting history. That would require
+fetching feeds for every followed account and would be expensive for large
+follow lists. There is also no automatic undo; unfollow is a protocol write.
+Any future undo flow should keep a local, time-bounded list of removed DIDs and
+make re-follow explicit.
diff --git a/docs/dev/foundation.md b/docs/dev/foundation.md
new file mode 100644
index 0000000..92815a0
--- /dev/null
+++ b/docs/dev/foundation.md
@@ -0,0 +1,78 @@
+---
+title: App Foundation
+updated: 2026-05-07
+---
+
+Lazurite's app shell is feature-first Flutter code backed by `flutter_bloc`,
+`go_router`, and Drift. Cross-feature concerns live under `lib/core`; feature
+modules own their data, state, and presentation code under `lib/features`.
+
+State is modeled with small Bloc or Cubit classes. Presentation widgets render
+state and dispatch user intent; they should not own network or persistence
+rules. State classes are immutable and use explicit `copyWith` methods. Session
+or preference values that must survive app restarts are loaded from Drift rather
+than widget state.
+
+## Persistence
+
+Drift is the primary local store. The schema lives in
+`lib/core/database/tables.dart` and `lib/core/database/app_database.dart`.
+Account, cached profile, cached post, and settings rows form the base local
+model. Any user-scoped row must include the active account DID, and repository
+queries should filter by that DID. Drift migrations remain mandatory for schema
+changes.
+
+The account table stores the DID, handle, and token material needed to restore
+the active session. Sensitive values must not be logged. Settings store theme
+choice, active account, and later feature preferences.
+
+## Authentication
+
+Production login uses AT Protocol OAuth. Lazurite is a public native client
+with a hosted client metadata document and DPoP-bound tokens. The login flow
+resolves the user's account authority, sends the user through the system
+browser, captures the callback, exchanges the authorization code, and stores
+the resulting session for later restore.
+
+DPoP proof generation belongs in the auth layer. Every authenticated request
+uses the access token plus a fresh DPoP header. Refresh behavior is owned by
+the OAuth client and recovery services, not individual screens.
+
+App-password login exists only for debug paths. It calls
+`com.atproto.server.createSession`, stores the returned JWTs, and should remain
+guarded by debug flags. App-password sessions do not have the same protocol
+coverage as OAuth sessions, so production behavior should assume OAuth.
+
+Logout revokes or discards the active token state, clears the in-memory
+authentication state, and returns the user to login. Account switching builds
+on the same account table, but the active DID setting decides which session is
+currently hydrated.
+
+## Profile Rendering
+
+Profiles are fetched through `app.bsky.actor.getProfile` or batched with
+`getProfiles`. The profile UI renders avatar, banner, display name, handle,
+description, counts, and any supported extended fields.
+
+Author feeds come from `app.bsky.feed.getAuthorFeed`, paginated with cursors.
+The feed renderer consumes hydrated `feedViewPost` values, including embeds,
+reply metadata, language tags, and viewer state. The same post-card renderer is
+used across later feed, search, saved-post, and list surfaces.
+
+Rich text facets use UTF-8 byte ranges, not Dart UTF-16 indices. Use
+`bluesky_text` or existing shared facet helpers to detect and render mentions,
+links, and tags. Mentions navigate to profile routes, hashtags navigate to
+topic or search routes, and normal links open externally unless a provider-aware
+internal route exists.
+
+## Settings And Themes
+
+Settings expose system, light, and dark modes plus named theme palettes. Theme
+selection is persisted in Drift and applied through `ThemeMode` at the app
+root. New widgets should read theme data through the shared theme extensions in
+`lib/core/theme`.
+
+The implemented palette work includes built-in families such as Oxocarbon,
+Catppuccin, Nord, and Rose Pine. Each palette maps into Flutter `ThemeData` and
+`ColorScheme` objects. Feature code should depend on semantic colors from the
+theme, not raw palette constants, unless it is implementing the theme itself.
diff --git a/docs/dev/patterns.md b/docs/dev/patterns.md
index 6fc5cb1..3b76a47 100644
--- a/docs/dev/patterns.md
+++ b/docs/dev/patterns.md
@@ -1,4 +1,7 @@
-# Lazurite Code Patterns
+---
+title: Code Patterns
+updated: 2026-05-07
+---
This document captures recurring architectural and implementation patterns in the Lazurite codebase.
@@ -110,7 +113,7 @@ State copyWith({
## Testing Patterns
-- Per-file harness builders are standard (`buildSubject(...)`, plus helpers like `openSheet(...)`) so test bodies focus on behavior, not setup.
+- Per-file setup builders are standard (`buildSubject(...)`, plus helpers like `openSheet(...)`) so test bodies focus on behavior, not setup.
- Widget tests usually mount through `MaterialApp`/`Scaffold` for component tests, and `MaterialApp.router` + `GoRouter` for navigation tests.
- Interaction flow follows a consistent shape:
`pumpWidget` -> input (`tap`, `enterText`) -> `pump`/`pumpAndSettle` -> assertions on visible UI and side effects.
@@ -120,4 +123,4 @@ State copyWith({
- `MockClient` from `http/testing.dart` for HTTP-level contracts
- Small local fake implementations when protocol surfaces are complex
- Async UI timing is made explicit where animations/debounce are relevant (`pump(const Duration(...))` in sheet/search tests), and responsive behavior is exercised with `setSurfaceSize` in router/shell tests.
-- Defensive assertions are common for robustness: boundary-value checks in utility tests, null/empty/error-path repository tests, and `expect(tester.takeException(), isNull)` guards in navigation/router tests.
+- Defensive assertions are common: boundary-value checks in utility tests, null/empty/error-path repository tests, and `expect(tester.takeException(), isNull)` guards in navigation/router tests.
diff --git a/docs/dev/routing.md b/docs/dev/routing.md
new file mode 100644
index 0000000..0074ecd
--- /dev/null
+++ b/docs/dev/routing.md
@@ -0,0 +1,76 @@
+---
+title: AppView Routing
+updated: 2026-05-07
+---
+
+Lazurite can route AppView reads through Bluesky, Blacksky, or a validated
+custom provider. The selected provider controls `app.bsky.*` content routing
+and web-link resolution. It does not decide where the user's account
+authenticates; OAuth authority still comes from the account's PDS metadata.
+
+## Provider Model
+
+Provider selection is persisted from login and settings. Built-in providers
+define a stable key, AppView service DID, public XRPC host, login entryway, and
+web base URL. Custom providers require validation before use.
+
+`AppViewRouter` in `lib/core/network/app_view_router.dart` is the runtime
+source of provider state. Repositories should ask it for headers, public
+endpoint URLs, auth entryway URLs, web-link resolution, and health results.
+Long-lived services should not cache provider fields independently.
+
+## Request Policy
+
+`AppBskyRoutingPolicy` applies the request policy. Authenticated `app.bsky.*`
+requests route through the user's PDS with an explicit `atproto-proxy` header
+for the selected AppView. Signed-out public `app.bsky.*` reads call the
+selected provider's public host directly. `com.atproto.*` requests bypass
+AppView routing and resolve to the relevant repo or PDS.
+
+Provider switching requires a soft restart. The app persists the new provider,
+stops new requests, cancels in-flight work where possible, rebuilds dependency
+injection, and drops stale responses by routing epoch. This prevents mixed
+provider state across long-lived Cubits and repositories.
+
+## Fallbacks
+
+Cross-provider fallback is opt-in and limited to read-only public endpoints. It
+can retry transient failures such as rate limits, server errors, timeouts, or
+DNS failure against another built-in provider. It must not retry writes across
+providers.
+
+Identity fallback through Slingshot and backlink enrichment through
+Constellation are separate settings and separate trust boundaries. Treat
+fallback data as recovery or enrichment, not as authority for writes.
+
+## Health And Capability
+
+Provider health probes run at startup and through a manual settings action.
+Capability checks track specific endpoints such as profile reads, post-thread
+reads, trends, and trending topics. The router uses capability state to avoid
+blind retries against a provider that does not support a path.
+
+Logs should include provider, endpoint, fallback reason, and circuit-breaker
+state without including auth tokens or full payloads.
+
+## Trending
+
+Trending is a route at `/trending`. `TrendingScreen` loads trending topics and
+trend metadata from the selected provider. `lib/features/feed/data/trending_join.dart`
+joins topic rows with trend rows by parsed link key first, then by normalized
+topic text. If multiple candidates match, the newest trend wins, with a stable
+link tie-breaker.
+
+The screen handles provider divergence. Bluesky and Blacksky can return
+different link formats and suggested topic sets. Unknown internal links fall
+back to provider-aware external URLs. If topic loading fails, the screen shows a
+blocking error. If topic loading succeeds but metadata fails, the screen renders
+usable topic rows with a non-blocking metadata warning.
+
+## OAuth Boundary
+
+Selected AppView controls content reads. OAuth host selection starts with the
+account authority: resolve handle or DID, fetch protected-resource metadata, and
+prefer the advertised authorization server. Fallbacks can use the resolved PDS,
+then default entryways, but the selected AppView must not override account
+authority.
diff --git a/docs/dev/semantic-search.md b/docs/dev/semantic-search.md
new file mode 100644
index 0000000..e951e28
--- /dev/null
+++ b/docs/dev/semantic-search.md
@@ -0,0 +1,72 @@
+---
+title: Semantic Search
+updated: 2026-05-07
+---
+
+Semantic search lets users search saved and liked posts by meaning while
+keeping all indexing and query work on device. Drift remains the source of truth
+for post content. ObjectBox stores embedding vectors and the metadata needed to
+join search results back to Drift rows.
+
+## Storage Model
+
+ObjectBox is the secondary store for vectors. `EmbeddedPost` in
+`lib/core/objectbox/embedded_post.dart` records the post URI, active account
+DID, source (`saved` or `liked`), indexed text, vector, and embedding timestamp.
+The vector uses a 384-dimensional HNSW cosine index.
+
+All queries filter by account DID. Account switching must not query another
+account's vector rows. Removing a saved or liked post deletes the matching
+embedded row.
+
+Liked posts are cached in Drift so they can participate in local search. The
+liked-post table is account-scoped, keyed by post URI, and capped to keep
+storage bounded. Sync fetches recent likes until it reaches a known URI or the
+configured cap.
+
+## Embedding Runtime
+
+`EmbeddingService` in `lib/core/embedding/embedding_service.dart` loads the
+bundled MiniLM INT8 TFLite model and WordPiece vocabulary in a long-lived
+isolate. The isolate keeps model work off the UI thread. Each request tokenizes
+text, pads or truncates to the model limit, runs inference, normalizes the
+vector, and returns it to the caller.
+
+Searchable text comes from the post text, image alt text, and link-card title
+or description. If the model or tokenizer cannot load, the service reports
+unavailable and UI entry points hide or explain semantic search rather than
+throwing.
+
+## Indexing
+
+`SemanticIndexer` in `lib/features/search/data/semantic_indexer.dart` handles
+incremental indexing when a post is saved or synced as liked. The indexer
+extracts text, embeds it, and upserts the ObjectBox row. Backfill runs when the
+feature is enabled for an account or when the user requests reindexing. It
+processes posts in batches and reports progress for the settings UI.
+
+Posts on AT Protocol are immutable for this purpose, so embeddings do not need
+content refresh. Deletion paths still matter: unsave and unlike must remove
+vectors so search results match the user's visible saved and liked sets.
+
+## Query Flow
+
+`SemanticSearchRepository` embeds the query with the same model, runs ObjectBox
+nearest-neighbor search, applies account and source filters, then hydrates full
+post views from Drift. Results are ordered by vector similarity and shown with a
+relevance indicator.
+
+The UI lives as a Search tab on the saved-posts screen. It debounces input,
+supports saved/liked/both scopes, reuses post cards, and exposes empty,
+unavailable, loading, and no-result states. Settings control feature enablement,
+default scope, indexed count, reindexing, and maximum result count.
+
+## Operational Notes
+
+ObjectBox initialization runs once at startup after Drift. The generated
+ObjectBox model files must stay committed. Entity changes require code
+generation and a review of migration impact on existing vector data.
+
+The feature has clear limits: image meaning is available only through alt text,
+search is scoped to one account, results are not BM25 re-ranked, and very old
+likes may fall outside the local like cap.
diff --git a/docs/dev/social-features-and-moderation.md b/docs/dev/social-features-and-moderation.md
new file mode 100644
index 0000000..b5543aa
--- /dev/null
+++ b/docs/dev/social-features-and-moderation.md
@@ -0,0 +1,93 @@
+---
+title: Social Features And Moderation
+updated: 2026-05-07
+---
+
+Messaging, in-app media, multi-account behavior, offline rendering, moderation, lists,
+and starter packs extend the same repository, Cubit, and presentation patterns used by
+feeds and profiles.
+
+## Direct Messages
+
+Direct messages use the `chat.bsky.*` namespace. `ConvoListBloc` in
+`lib/features/messages/bloc` manages conversation list state.
+`chat.bsky.convo.listConvos` loads conversations, and message threads paginate
+through `chat.bsky.convo.getMessages`. Sending uses `sendMessage`; starting a
+thread uses `getConvoForMembers`.
+
+The list separates primary conversations from requests. A request is a conversation
+where the active user has not sent a message. Threads render own messages on the
+trailing side and other messages on the leading side. Long press copies one message,
+while the conversation overflow can copy the full thread.
+
+## Media
+
+Media screens live under `lib/features/feed/presentation/media`. Images open
+in an in-app full-screen viewer with paging, zoom, alt text, share, and
+download controls. Videos open in an in-app player that uses the embed HLS
+playlist, respects aspect ratio, disposes controllers on pop, and handles
+GIF-style looping playback.
+
+Downloads ask for media-library permission only when the user starts a save.
+Images use their full-size URL. Videos resolve the best available playlist
+variant before saving. The UI reports progress and surfaces permission or
+download failures through snackbars.
+
+## Accounts And Offline State
+
+The active account DID is persisted in settings. Switching accounts updates the
+active DID, rebuilds account-scoped repositories and Cubits, and reloads data
+for the new identity. If refresh fails for the selected account, navigation
+returns to login for that account.
+
+Offline rendering depends on cached posts and profiles. Screens should show
+cached data first, fetch fresh data in the background, and keep rendering cache
+if the network call fails. Actions that require network access are disabled
+while offline with a clear explanation.
+
+## Moderation
+
+`ModerationService` in `lib/features/moderation/data/moderation_service.dart`
+uses Bluesky labelers, user preferences, and the SDK moderation engine. The app
+builds moderation options from the active account's preferences and subscribed
+labelers, then runs posts, profiles, and notifications through the appropriate
+moderation helper before display.
+
+Rendering uses the moderation UI decision for the current context. Filtered
+content is removed from lists. Blurred content gets a click-through overlay
+unless the decision forbids override. Inform and alert labels render as badges.
+Avatar-specific decisions use placeholder avatars when needed.
+
+Subscribed labeler definitions are cached in Drift so preference screens and
+moderation decisions can use recent data when offline. The XRPC client includes
+the accepted-labelers header on content requests and updates that header when
+preferences change.
+
+## Lists
+
+`ListRepository` in `lib/features/lists/data/list_repository.dart` manages AT
+Protocol graph list records. Curation lists provide feeds, moderation lists can
+be muted or blocked as a group, and reference lists back starter packs. List
+records, list items, and list blocks are created and deleted through
+`com.atproto.repo` operations.
+
+My Lists shows lists created by the active account. List detail screens show
+metadata, members, and for curation lists, a feed backed by
+`app.bsky.feed.getListFeed`. Member management uses actor typeahead, creates
+`app.bsky.graph.listitem` records, and deletes the corresponding item records
+when removing members.
+
+## Starter Packs
+
+`StarterPackRepository` in `lib/features/starter_packs/data` manages starter
+packs. A starter pack points at a reference list for members and can include up
+to three feed generator URIs.
+
+Creating a starter pack first creates the reference list, then member list-item
+records, then the starter pack record. Editing members changes the backing
+reference list. Editing name, description, or feeds updates the starter pack
+record.
+
+Starter pack detail screens render creator, description, member sample, feed
+recommendations, and join counts. Actor profile surfaces can show starter packs
+created by that actor, and search can route directly to a pack detail screen.
diff --git a/docs/dev/typeahead.md b/docs/dev/typeahead.md
new file mode 100644
index 0000000..9151818
--- /dev/null
+++ b/docs/dev/typeahead.md
@@ -0,0 +1,74 @@
+---
+title: Typeahead
+updated: 2026-05-07
+---
+
+Typeahead is a shared actor autocomplete system used by login, search,
+jump-to-profile, list member management, and starter pack member management. It
+normalizes results from more than one backend so UI code can render one result
+model.
+
+## Providers
+
+The official provider calls `app.bsky.actor.searchActorsTypeahead` through the
+Bluesky SDK. It requires an authenticated session and returns profile basics,
+including viewer data when available.
+
+The community provider calls the waow.tech compatible XRPC endpoint over HTTP.
+It does not require authentication, so login can offer suggestions before the
+user has a session. It returns a compatible actors array but omits viewer state.
+The app adds an `X-Client: lazurite` header and applies local moderation after
+parsing.
+
+Both providers normalize into the shared typeahead result model: DID, handle,
+optional display name, optional avatar URL, and labels. UI code should not
+branch on raw provider response shapes.
+
+## Repository Behavior
+
+`TypeaheadRepository` in `lib/features/typeahead/data/typeahead_repository.dart`
+owns provider selection, HTTP calls, SDK calls, parsing, moderation filtering,
+and fallback. When the configured provider is official, it delegates to the SDK
+and includes moderation-aware request behavior. When the configured provider is
+community, it performs the HTTP request, parses JSON, and filters locally.
+
+If the community endpoint fails and an authenticated Bluesky client is
+available, the repository can fall back to the official endpoint. Login cannot
+use this fallback because there is no session yet. Fallbacks should be logged
+with provider and failure reason.
+
+## Settings
+
+The selected provider is stored in settings as `typeahead_provider`. The
+default is the official Bluesky provider. Settings expose both official and
+community options, with copy that makes the third-party nature of the community
+provider clear.
+
+Login overrides the setting and uses the community provider because official
+typeahead requires auth. All authenticated surfaces respect the saved setting.
+
+## UI And State
+
+`TypeaheadCubit` owns query changes, debounce, loading, results, and error
+state. Consumers should use the shared Cubit or repository instead of calling
+search repositories directly for actor autocomplete.
+
+`TypeaheadTextField` in `lib/features/typeahead/presentation/typeahead_text_field.dart`
+anchors suggestions below a text field with an overlay. It debounces input,
+ignores empty or too-short queries, and updates overlay position with keyboard
+and layout changes. Selecting a result fills the field and calls the consumer's
+selection callback.
+
+The search screen should keep post search state separate from actor typeahead.
+Jump-to-profile, list member add, and starter pack member add use the same
+autocomplete path so moderation, rate limiting, and provider choice remain
+consistent.
+
+## Limits
+
+Debounce defaults to 300 ms and empty queries return without network calls.
+In-flight requests should be canceled or ignored when a newer query starts.
+
+Community responses do not include viewer state, so follow badges or other
+viewer-dependent affordances should hide rather than guess. DID entry bypasses
+typeahead because typeahead is handle and display-name search.
diff --git a/docs/images/about.png b/docs/images/about.png
deleted file mode 100644
index 387b329f94f9f1229f67a70ae6817fb151f7f580..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 117125
zcmeAS@N?(olHy`uVBq!ia0y~yV0y>Ez-q?9#=yXE>bfzAVk{1FcVbv~PUa;81A|Ly
zglC$sFM}2X0|N&GV|yk83j+fKBLf426axq_FJNR~28%EQ4d_E{FX^3uGo2JpQlH;9yT*e*MZndYEmsNK fO!-jLowJ>vycXTX=kd2mg&{(-wxUZI0c-^XXu$!3}1iY2VxB>jZArT=HCg zqb+dD>422wu_w=0b4-`Nzgzsw>Gi@ZkDgA-j(q-CP%S5Z(-ZFW+~X4tRL^{Feb?!| z?x)}HvmdAVUeSGJvRBjk{T}Z-B?o0s-kZI{P*K8Th5W}J!Gkv4x2ha%=I;pG`}y3T zCDTt?pXN7MAN@V!wcjp-R}%AF^}jR(PCscLasP(c>t#>+F9xxgO>YqS&cd>BL!#{= z(Kh=-j%=;sO8Rbp@`EQ{_RAKM?6FL6kbj;${Z!#gpUoK;57o3T^A4BP3cYnDx8l#o z*;5Ww{4~zE!rhhPsPXQ}{}+q-Z)iTr-p!xUFIPK9|7(xr`rD0N@2y#PDTr*Fsy+R; zH+L-C`JZ2}Ydc^5(YwQH;STlLJlylEcIBO(zW%{0lhT^kYqx*8xx=96AG@M&bC-4L z-xJEOci!EcTV8O%{+7J(?j?=grduY-_DHKed8wQ{r{K^@O &E@QgnU0DxOtW|Wu0HYW<+c2RHL`0e z7fSCq)lfO5{o!GD^^k(#y=;dHt3IAu@LzV@nU@P^yj!$9z+XtS_h%)4ZR%lJwMp{z ze+)NSuQ=jv#uipE(?LVoHfZIQrw1Ntu1(@OFx@V2Pf5)AfLjw&xGXaz1oo8W{%K># z+~du+H$uYtQRH*a=qsvoUwPs}TF7&@QSSW|1mWcdtO`4u5==kfDb`+M{>7- FS@+HS&V^$?CktD)iI+V+rK`MLS>}ni{@w?RHZ1w{sb6MBg6fJH z?W&)r8BIGWrStmQQ=UgpoW=cDH1m}4ey;5;_bISu-n_Tw&quY_tL}?!J2J^K|K6M( zf;~-@MKy7+SiHhl2HjRIS<9(C`Mz7T^yyjhmmh5xpRW4x#KujF&y>Erbo%Ea;dO$n zt@pamPTiDx+I3Thc$U$mgvVT6-SU?k9yPzOnepVg?Ut&qt2V4rx_xc^fAvlO-fr)F zev`xQ@Wy0urI|U;y2_guI+~`Q;@(;HrXuXT_4=9C+`LBjZ%=)(Wq!TfJ3VomWBbi- zxE ub5QYBc4S-+JTRB!4F4uK6F&$(p +X7t;*yJ2w47wKKG{9}!4ccK=sl_vD<65jDSf)t@5y$% B`dDn<3%Bb{c0n@7I>^D;9rrbNB0YfqLg`c;j@QX)Ikn=hBHeC6`vd zTeHJ=*&}Y*n)+oscerZpZmO{R{Aq4VkebR34lbre>D>qTdP)!T*V+F*lPIw8y1>JI zpN`is^4r^3>Dne7{G45TT20}o?X!Q$j4R{+OKhKduzdZ#Uqve>_1Y(Cd0ETv$zH$r zo@S$l=bi<@FQ@TF^>5Ho6WaB?ySwK1+pO@AUHS1 b5P8*_M0zpypeXq!}ta^IsSooHUKo^3MllC1&+Xzezhg zA53ytFZgtAw(_%{P0XIncH4jMWK)_@ad5xA#k%A9{jrbg`wRTHmy|Oen`ZO#$>Vc> zyVIAIzTUQR$DQ} e2JT0n8oh-kt`n7XVv(i zFJMBR^~-(Jf4{F!P&xg4-fWTZOm)GF!H+nv70R==-)oOBpT6O-R#=MGhdaz^wNYy} zGSXO z?sdQSM2J6rDqZ4xv;6+5kCR-t8LKt1u^XNK^-z7&4tKs&kEVK+#kilJH=C{1c*l>I z%a8B;FtbJ2Eiv6J;=fO-VV6y8l%wtZudhruxV8V-Tfc8Y&F#G#-<;Ne%+C7ZWwUox zFh~EXJ9=?^_IuXFUkzfp 5FKk!ambOUd@wSV%j_o;qDK@}q z$^7f_^K%cc@a>eo$+&dt!L&GuCy_Bn_@6D=(^>PhkUxp-jEhn6GwF$U&m>OpSTTF~ zoJoK7{nT&|T@|vmd9P6P(H|>< e`l }^%^rZ( Dd}H{NWyylkUmV&+y~mQP*| zUDXW|c5OUbj#4HT=b}Q*YacM5oFgx-$R*5q(Q3v;p@*!0e;nhi5t0?Y(|&)|e34Gg zo3&3G6Qr(XI+)~E6~1}q^!M#rA$FMn Dmj@c7)u&3$pDp|3Z@2Hxh-e)+m;ecazWJsSf# z&Hg=*OjyJ5m9c!ngVNTe{o8YYep)JcblS1f8^^`Fzq*!v^OX`l$)Y+l;x32roVKUa zb^34oHMvu3lUI|fzyHsw9jC0HFJ3-jqXfrZj*lr%Pl-;9J=ozpvA0pQYIaRdNmYbZ z$%{u)9E~ayL)G5wdOGd)o%};{*raz(bcw#e)jThgL-~s89g~XvOOih7^7w#b<6VjU z{DVb?zxCdwd~C_rRg@Eyn%|Sp&BZ2jC%sbqV~kwYi-!mAPAoNVUR65P@g66qV`tvM z2l@XlU9=Xn+!(fvgDLXm<8#iT>tg=A`}tsQZ*Nt@d<}k;Y2|;K-tYe(XRZ6mOZ>U` zcfPa*IoIF0N$*Tt*PZ6bGAZKV`ne|m7XP2~bt>QNSknrbyLS#YR{dBm#4Z^hW5-bY z>q~jqtU2= bE8L&wk0te agP3S9=W}eqc@%S1!&U`n1fz9cQ8j9n+ZeQqXnV7 d zECVK0iW|HTYuy&Px%=$Q^m((VwA)MeO1@&RU;XCyquA*A7W`l5_Sb*7`R%w$w9uj{ zZLUvGg`P3J9`k%k>&?ySyWIE^tt!ty%yT>Q<+T3!%#w?~|Bp$pKYrF~#g6#$*lOd% zL&4WdUrAdll+W#+#Mr$cK{xFkYt^2PeTN;=*q#=ymt1qc`o#g}?42hU{3-0==8XFG z_nU5}=U-I~Mz2Nvo8PaWzw6NMysN83&pq-x5&56*YbIB6q(G@zMBcpHK}#onclanH z>~+Q4(PsXWvolnA^0wbGx*&PUZj*6&vcRGq9=)aWe&_Q&ww@tl_-3c-8|9}@e@YuY z@%#Sw(eI6EXTPs2JZtQ!zpi}y&j-!l|15Q#Ha+gvp>JyOkKa{)es(s9D_GJpLOa@~ zaZSjb+c)w`{sm3^^_9Q7%0iOk*qU6>NN=!6fLKLGV8ovN+IMG{-!(n6kL4VnEi1o? zyp{gpwtH{GAL~h**=U7_Fe=3+a2{nkkRbVQ!<0XtPU{O!ms>VX-t?86IHziGm$l(c z$r;UCoAQn&gg?F-ZeH#c`{7U^pH+F@d!2VBs^#zNHs3ybynDv2f33&e(s-6822D70 zTJl-Nl6SDP0Oq@-EqbEdJ#~gXoABa>1s0WwZZ=N4Mb#fFU*7om>$+_5)$#ZDF~@%V zS#W*D#`-5gGtP7vEbSC27ZR45{JZ_XUazT$L(4A7J>_%6*X;;U4Z34F?N~s++}xPt znO24s9}3K@!+R6 kq9DENPNo=Z=Ek>%|j%Nt+3HxH|S^q~5*sn@4wv#?(;m(PD{ zcWIqN;XlFJt~PmI?kbiapNkH0etmRq&dbX!x{}W??JgHDe%u>7m+AcRdG;#b?rr2< zDr@Gb+VR#=u_gBZzgn4erScEYTiL!CpYQo@e|J)K)$g~=xgkCKW0cvCw%fNnKfGJ) z)9g0A&C4%!+`iO0 ;H61I_C0GvD9;pO#+2mG}L1&HfpJ zQ{_tUeBJO!d5`^uE$s6-=V{7|U6J?amuxdj`SirI%i{6DjG0EDlo7n|*MyccXWE0# zxpTNmc8kjw{WnZ6xcK9vzlHb7_xtlV3ALnby*az0bMcNvYp$#=@7dh3NaABs!tEAL zf2*a_^xiHAt={ZE=Qxi sFY`V-#I0YdsJrs~Ke>yK4eI_(4$G{+So(Z<(YeUY%c8XlxG%5x`0`oO>maAS zJX&ux8j_FOy*ie4W#!~s57uN}Ubd@Wep;*)|C8ra%jO;5S^V7MsoH)$ql2%NR`K<6 z#RPOzIX^yQej#Ip{nsnPVV$zi*F1|@X}dZ>?qKPfT$hDecYf&HZVo&f8TB&oR!pMN z^x1h?;+(P`j~O_pwM*9SEH~K|^s;qlbE&7LY>I(Vc?R=-}lQ1J0$mY?a& ztaX=`d<^rfRm=*gU1YPlHvZ(Kr964@9DA)b7WoLc9v1Mn>2<40G!(hAD)jc(Ih%R| zHn*y>hNfQ*{8wT>e+s9!UEB1d-S$Pi303C}H&s5AH7 8SG%x$@=93E^>p>$>DQaLE%~x1MKZ%B+=$ nbp3kqp(RS2aGW6QzlMRfuM^3ky@=gd{9p-xUlHc>4L1(+gt@Ll?Zqs~t z%Gp^nnx%EdJl;hO3zh^;&-n8+e9zR*-|gJ&lkW$(o4a>J9JUZ-EBh$;d~sC3H4mMH zEA}F9@7Mi4yR+}ZME$Hsr#
~Al5 z>~YS!zkRm;h5u{6-F$xAb7Peiv-jq-^rN!E%!g;^y?S(K(~f7sewJq0_g=r;)xPiE z^4~w7SSFPv{5e~h|MbdZ`_G{{Jc>I`TqsbJ3SPu!vhm*U@A8k1UH l>c T-v&0_Y#gH1ngRxjMOiAh!A=w649TE$QuoeUFOB!rs{)ELA!RWwa{ zq ^l#>{}-V4Z};16`!*e_ zQi$qm;5Y*6PU}g%1$h_b<3GX)M(dxME&IRmxZL|^HXUa ZMW1fg z{eFA6d|Bn|Yti|8H@;Cl8y1~A_1T@r>ESc3OY9e!{q4<`%jag-W%we+3n!!IyUIO* z=65cfJTu4ge2>h}1iAh*bN5tz{d 89(~tyVN!awRbLF(?=d=D^77K=0BQZsQ%h?R(J_IGQS;r(0IdZk$7PD= ztc=^+b~Cx3YgyrAW9}C^GtX|*h)sTa>i4Y6{`R*SEq}lHd^WE7?c7KUtKEp$f6~|> z^4;cuSNWxWyI%^Eo?5>@)4wMDvRB})V^g)mXPmeDedOWa4~M^>y&k__%WQ@B6ph4! z%Zsi%D8XWML#oh?w9|XuEGoQO{QR7u@9cS*pyu)CqvG>Ld!3f<6mfkib$#vc+gmb= zr>+c4w*UNc`TQAQUteGTGmBf#I;Q%Z#bcS`-*21Gv~c>1-RYdN8Rj;JiJ<&+czfE> zuBVcEjtA%O(};a6U;ighOt)(L1AVKr={IV>|6gk9o~jOWOTZnD75De F3w$@n_n2r5` y*>&jmU&3|f?T2}J=-=EL? zpTFO)Z(qipr_z)B^i(L1*bUk8J175qI(`1x@Av!5|K0lkXS3c6lc!%}%Whr`Ssiv) zK&|lWtMz9$rF!#Dds*TUw|-;F$)IIPN4usywd?!MZ@;F{rvAUJ?bDB?5AN6hH{JjD zTlPBd7R`T8{{Q_xKY6ycV*+!cqxW>Zx7+@FJTA``KBwYPt# 7p({dmm(ZH~86 z>Zg}U(|`PyuXy12&hqz{%SYeMJG$&*KDZoX6g;8E6uEGRc}~Tqm-qgrmB&k&X1$oQ z=*H}pPoIB0_K#kgX} PBMJv&Fc53o^`6vD|=~Oo|m6?c9z^!_Ydq`q0MZ^ zcNVp__8s|~{N{%H**TW^$8smId`^~K`gW3c)Qo3m*BfZWJ668k8h!?JZN|Re?`}VM zX0v{IRqv6i{Vx`sKJ#>X{M6Mt8m;~I|291RnG`c^rP!MC_tCL0&Q6c7yZ7boZSCuc z*W>Fa@1FkS+4lVVYS;fgU{hkU{czxMUhwX+wP%`GAAVh#b@kP< op3f<6eC+jWU2MLIahgwZ zC@kwndMjwhq&7|T{&qY6|ApDEHihfAOnxxInUCMMy{Z0Aq56(mjY|=6k5B6Dev?%7 zZI9}rE&Km|%US+r^1R~Ms!Z 5 zrTF{y*4>6nKVNh%{Cf5JtZQp;Te;4xNjpDJRP&st`rNGbYxDJ9=czbKg$J-$m%n=` z5_G@8A@a|Y$uh1JH}u}!_0=pqrm%F$ah2ZX`WM!`x@LRItijDy !DN3}9nyTOaaQBt6~iYx9!t# 3YMYyd$7R<}-QN#7hh_6O!8n2Kd@Uz?H+Yz37A@QP;gI)P^ZRSe9v`z6)L*^i z_QtOGx=Qcqd`r(qXZ-*3*?&8)`P|RvvR~<3({S36X!GU5i Uo6jNPr5iT;%~e+Vc(rrZ zr&p`>t<6Pa6boi%-uj}HJu^2eYD-3@m|jf6v$NIKtue;)TRv^iyL;~H@}u4MM%(XI zZ7ya!_?E?dDtMIx@7~)x50<>Ra8hee?qZAb_xCQg*6YOX>RD@lei!$y_xl8&wTc&A z`zu<-di(#m*Pm`A_nW8BEnO$QrTgjM&FAe(ZKZcG&8>W9d2W?&bC=u0^Y;I9{H)h@ zEM7fw&z~>yQTO@2 zN3VS<_niNm*nU;>#G)mp@ArPccl^gMmgnpD{Ze|Q-538W`r57@ZBeJ#L)&dMK$rJr z^E~?emX+_y)(7#MHj4baQQ }pO&vaC#33igYAH?U*_`Z?Z-~;ey`V|@pj4b zJpWlH2Te-hlE^G6)ev3m1 zrfdvsOOuY;_J;p>c6RpTKLt1Cvs=?=`?vR-pYl+0jEvim5SKA&|G!_Sf4 ;2~LN<67_dzs(dM^nC?Xqz!r zJbsSM%^<_J^c445VG=Vs3V0JYUJcah+<)%=?EG^+mm|6DW*0pzIu=~8Y{Tm$>$*Gt zZmlq$IVo_l+YYy&8$Xx()mmnKJ~6TU=-T#(gL|*6*f>AFS10qtg~`ny{#>{$?Eivu zkz%_^UQN|HHp|-&z4lIowq*}Yb5O_@DgU!@@6*ElfB8Cgm9xu5oc?>PUQG4F-SX$V z?|*$QFFdtRLoQ;*#E>+juXZ;AXR_3>Or69fx@hgn#+1Tv$seZ_B2%87F`vXIa`e-^ zH8*d*dw*1Xe)nvB>x>sFK@|n>KRy!8{%?Kv^!IzwFE*tFq yf!ntR~t*`+m?HXkniDE?+^r%Z&0cBjs@`!%0^Lo}!9#R`43 z{?YvWH23-7H9zi&Zd0FK((_T(?wQ@Se+kYSX7PW;p#2TqMu)H;JBk-yyH!y3 eJsLp8v4=_gnM3YW53+QyUDb<2+LtR!{G@|94^iu~?yhdJ|ckgCFhs^z-?A zHLrQu@dcOW_S^r{xc=|#%?TbqmEvP8J9-W)+`b{#bJjBd#>O98j1^|wn*>_QbZ%p> z=)3!eTHi;NFFkr~YI@}%H@Ap+u8GomdOs?+=ific`)kkdcfaRGb4;vX5kJ5FUu1^I zDW55SU#|~8YODLs(!^;cyUCA3hcbROH`_Y>cy`=tsn=|&4aeo{gWkN0*)KWcO>5hm zS*G`Y&j)qKK(Ti~%pDYU?_~0{HQs47D!cJ)7yD6~C-7k9a Mci8su8o^nF75QM_UF^*3lr6wJ)7SN@`RjO&7wPvhskI8nmF0` zz{fTb*8hJbuZ_KZ_Nb}<^PS0`4|&~Q9WH)5xM1bq-8UldaDF{5&y; dWcLjnxw(6ebCKSj!%2T5}!XNH9r!J>;4G) z9dtdpHhg{K%AlpI_D;XLR=%Rt`HQg54W4tlTtfOji5qh6m>&}sxHpNzrNtm6r?vc~ z(i^pU9){W<4=-x^9uhrcFZ;UAAWN%~RrGVnrQ^#uuUQ| W zefZbc|2uU4_N?gqv_3>LJs@|HNuB(ujo(w%O*ERPW?!|s;p*c!QT>Xlp5w`5YCryT zz5aANzxbHng~eYld7pP(^hV_R>%g5b2ODrcxm&;3z 5cuLaa^JObee&nb} z2vI)%>x*{$^tPrLdFs7X+B)92fq0)P26$(|B7ScPD@I4$$%*Ep{%&`kN2J4m^lV z^HoiGG{f-w&ez9(#VtLxPjll5|3#^%r|mjiuuv; s`cLd z9C3TSmbQ$XAE$Hnm&MM9c8YTJ8rKBfX?%EB!_V%giFveYm)ym)KP7MFBx3(vUgleB z&u{r8p!k9#`|eVmy+4kLvmTsZ{pm@1OyG=;wv%Z>oa-clm+yP|@6Vg{otMO0Yk8$) z($~J;e0TnPSx)Wg``5qmHq%bO@o{C|=Pv7)!mgJWCW?Fs?LRx;koWt&-wnIh)%|5_ z{Z(De(`zh$Fk|`LvaYko{{H*@e!tS@8DdYiw$HOUEB*ZZeCd-PFF2e3`~_MWlHlX- zoo)Tw;QVHI0xn^ST)sni=h5~h*_WSHmDk5tK3#gjvA0|7X^W!G>ouR>I-K|%8znz$ z!-^0drF9ht=PJe7{0Lg=b@Syj{SwcOmy*xw@*P@m#{S=r%U^2@s@1OxsvUfx#ClV? zX?K_SE0-G#-p70lx;5&4d~CVL^iI~+>ca9JgF{knQf!UAGkrl{($KWy~(eC zyu4rcTOpifO&@4AX|)hvzs3h2)A|F9roR9GIr}VjTfV!*xt*^h_;UKDJ1ghSc00Dj zQ#j_6Tkkmo+v;zoS65%}*zs$@jd>N1G|LLItf#k~Y-mzs54QDR^-@CRb#+n($HE2S zD*`SqP swzdv$%w&S^bS2Iscu)e!yamx=E&7-~D$2^{&yL mPEFH|h1DEiE+OxqKj)xqBP$fD>8v!0}v zZD4Ve(-qNmD=j#?X7V~@u3dfI>Zr(55pCmZkveDMI4)aeU9tU_-l)@AWmHj6pZ)Xx zIRi)WuO0SPg{P+JiU)4;G)_Kda(Pqg>m5%YNTj*Gy|c4jI->gX*}t(-N-`G}79M<& zdSU`2r*yCW{y$C9CACi`evMgEz2-ukpT)wsOObW8w-#+^@qT{pEw5O)l-%9j_uq7F zt@#;r+hdhT;+bO?7r!_4xB2LivGMPbtT#tq*%d$ga-rlFY>exslY+MB_oIy`Cl}Z& zczN1<+<5 qPby+<8;aHe+`VaJe%jrRX(|>r=qIpCKzKjujZ4XwBXaq z=wq^z#8|y6m;0GpJe}WpU`Kky^u1?hnclVe`z2T~Tw=d%+0J8rHdmJWmskHkU$oHg z<9hvb6WdlSm$i<2U!{1?cKgb1xh>!CO}(3X+1oJvT+Zi7xefbFtHX4 c&F_4 zq;s~Z&*zq#?O6Bc!(l0N#Sp7=_Bl5=#O3G3b?&RJRy`|ZE+Uc=69c-0)w=nGmb0_+ z{!OpfrN){}wg3O;a@=jLY|a11`y*4&7V_-)d;k9_=~t%An?4_!v1?P};r(3NGflTT ziYtM~eI|4`ghkyEjx8{I^ZV!X`IFAwikh>2@(Q~hz9OuN6IS-?&hPv5=gZ~(Hv5xm zOc7@|g f0@Z)CNxqSI7F(MxY#4# i1O{tKLN(e6#QLxn(ssW6$iWzc%}Q{lBAYe*O&q{BhFLw5R3Sn}nQa%5F1vz7?1H zNdy8&m~OL>;ZoUcEuS*h9DYTA};w$$b_o5BJto-J1RE zXJql&L+AJT%viVMt<92O`}{JO-!V6_KHXEDyDake@%cyJ8imBcf&f%_c!O#T*=^dX z>ulebT;B7sbK>lt)Y^}iM4!!;uQR%x7kubVU|x9fhR~evHy_tO`y(v1W4~g*eeSV+ zR(JpWe&2SY@Mz`J%}-aKd0+oG)hmDd+{%AvcLv?+{(Ls%CbTu{z_P7^DLPam{_Xyp zMGe&_8m>*B{g{9KTRra9(nnivKHICm@6^%>`SGikSZ=!Z@7bha=~>GBcg`=Z&w9mU z{9+^L&P!t9ZoA)w91FR(c+by_lSa>#LSoiCKZW)sLCIQou0yBn=_zj~`wHBebNk4g z+Z*=nUSd6O&Xbzm;kEypf4<*;=k4UL|NmuATleppPyO>+^M=m{YQGz9T@_}wdjJ26 zHJfr=!xDbJeqXw7`osIYH!pTi5H)udS470*PlXAgfvFNLl~-pjx8hl>?r+Gy*Wl`c zIqH+|ueom?_RV|^Q-4Wb-M*DUh5ae*nfnoiE~wm I>P9zT1=h@ mq+wzj=4!y^!iQGtqMG^l$f)%Qri(4AOhMDSw~w{eM4-RebkM z4LS9u|8CXmwR7Lw*Zp7m)}qmL|DUX|+V9QX2OeL}&_sAnX@VqE z!hF z-q-MP{A|%G*}%@P&af)QfyHIP0gja3)n^1vvt1O<#{Y>-v0bmdS*-R2pW-W@w^^Z! z_~w5P^xJn_rYK>Bl5djIRhFj ut}h?y^y(f-M%AVU49g2BZpzY z4wg;F9_@Sn?x~FBA$h%@zi!tA|EW28ePZ(I>32;xojb)IuhaSG-u_Q<&wjh^cd7d= zI_1}bD>wI7P1*GJyP<&C!MD#Aub=kw?8MaD+~*8>`_?_K`(B%{X6E;c%hOutYIHen zJJWJ&NnPynE5eKa#lJp&|LMH;HK%>=u`ftU-2d@W@!iT}`|VEKWT eeQRK;(rFIZZCa(uwLWKiPOc;o)^8o zx% cHu@xKigLyI=0@)J#lz{Gt*u!|63B@ z;@AH)ERw5vaol%q=Dn)_$ W$`KiBWJtct%K&^KFoKU zsW E4{8u{dh@K&t}o)CE+)3`#w0b@2%Igo*zr}H;I+A zY@ell-r}f<&7XwN8+iRYr^>7hH9fw+_F{Ux 3=tm%jWu7elqy}{nYLEGb-ilpVhqIAG7n;pTGX^*nCc@ z2RK2U2x@XQZV@;kb@cD<`NeEqVcVxYzjQk{eGjYM`YBCyZ|(oiiY`3<=+CJ+B;N-JKw6dX775+^SbQu z)XMFOANt!Ep6}TAa%SV&m+?=l*zBzC)uf&-n`tZ0D^zpyr*qvFo@EiPb#JDa&zWEQ z%X@(gycTkCU 5Qy2`C=@tL-hP5yS}UZ#SvAAY<2 z{QAxy_?b?@e#>{y?_ZlGes4~?Uw**#)@d$<>SrELWPkU36E8RONj|on-*$aUDt@zY zYs{|a_vd7b=|?8+`}xp)^SARd(-q1DP8Am1);@c#OlkSqiT3|~-L}a|pIKY>E4uCA zD$`tlr@PGAGnDyOJd{!vpI@{mGw3~s@sEwmc~fD-zu;UdV%NAP@Xqt-?ILU5MBILT z`=
|Z?1cI^0@1iZ+YMC-mLkRIIS`*?rzLd+lqJdb7yuW zpO+Uld%N%Txn=v_%4Vdm{TK1*_x7E49titCkwfZBbT~W|Ix*|uN{vIp%g>g5nGvw! z+A)ill?OfVl&m>(;`B9EHJ`||``_*VCP_4O70lcF{l?u2@4xNRQ>(UL{#RX}{HFYq z+r{sbtsc7fEwv8Ywk_<7asOFP*3N#Lbrv5_ZJchK@5pt%^S`yC=H4Hle&2@;t3rZ= z4-_N^O|KnO{C~_(u4T*q7c!eq_od#c()#~x?Uwer9WP|2PidUFe$QNCLA84uH{U-! zGxE!csNXYhg%&66@}Il?!kV8yudi^Hs5zw&!t!+A!^Ew3t)6pCo^(6!^_tsnqIdne zy~E4;_xbD_4X*o^E|Ps?^}O-+-TOzE%gLL|zu)ZNB ;O&&~T+XDOedcy35ldj6ah z8WnbHp62KD9Id;o`h3=^>ac>{e-B(uh`RPaYWq1}w$!;1A2$Z3+~}K=lWt=B{YRCh z{imPuahIR%e}1RC;QT>THJ@*HY}MWDe%r-uveD_>2Jbg^G;oL*fX3|-kG8n_NVLdo zi#Ih+bl`73)U#FkXvu6L)jK}RL{CjMbtylXUtRKD`p%ifE&)69PV9X)xi>H|My>Fl zpxVdPp_a|zd!x_&p0Cg8`&Rc|-J})U?ILn&ew^CCES9t7*yHfJ(^seO>M)riZ7${H z6nF23it(Ga)x7^cuh)+dndW-x_6!X!#aCzVnr}5Usn~Ph#(1AxOl|z-r9WBqco(XR z>rZ|3F5v}ipb_L|qsBFob6)LCd^qpJFP+U_Ug!J1pC&%L_S=v7!6kuZ!kiz^1pWv- z5iFP{Qk(nFsn~a^o1W#g7ZrgS1*SjmwXJp$beH%u(?jii)$KRgx}RN&KQ5Ha4Egix zx4hrMXAv<4FC;C}jn}?27O%^&x@UUKZR5@pu9GD%FW##y)jNGvby&}tDZjqXlJ|Wd z*ze}L)w6{67JQutsG}MO8b++P`7B opVyztL4gHIM#( ~gCv(MidNz*4)Jhy(gVeieWd||FX{~nJzJu~Lz z^ZRxFuTIB_=#^ccduqnsf7^FQZm-+ho$>jw{zc}5%aLwQNz=A(-YS$|*K2z|D?4*} zxg&DjJRy=Py5-Tl$Ro(hc{&<6a_kz{NYB~z(_>0AYWvrg>FB-Vd(T$-Dos`swcT?w zcF{!Pc#|I03CP(;#HKN6{f_1p=NPYY&zo`%(hPu+k}3-F-+2yQUy1Bf7llx!Plf(d zr(8l#@f{9qoF0Ye6>8>2A+;18AbZY2gS?RAQh`ZQMd7Gz^M}$bq#BG-&|_+Y$oCLW z&ABN4NSV+eg4Gd0cY;-ymLRtnlqNVb1>G^`h+mHArhs}XA)Fqg4h9AFXb6MaN~1{v zseL<|B!*p*$eDbw`1KXn9*3Dmsb{|3exGn{eSCi1ub0PHT*}>gHEdbZQLksW^Y<72 zUR;* w8L$@hMHeC?+`d))m_~wq HU|*PCE;2)H+ceBC#QWZ?^gVQ#M~NL>Q-@Dha>;?e6xh#5qyVJ)9OewR%k~KjCNn zc8$&5_dk=v+6?QrXqNdd;^PuMS(=aYx-Q cy%>?_QS*NBEKq24_)D0Qq<39y{4};eb2=&Z*Q;m(0unL FZat&S|e~Gd%bUG(5=6b5*>z_moC3kzk4L&;b9hY0nd*r z0n5(x9o_suYb&VPvc#joalUI{Q?h>D@3-3*MYv4$WZBf@a4q7_>d@8i@=lcBuNVJ* zOh9mH`uTHg)4RUDHjljBIpYy~_7BJCo;Ln{3jhCpkC&dS^zU@XF@`)dm(-s>pU*#Y zJ$}8{s~{(qi9)K|3I*l%e69cecKdeb uT5VY>(cP_^z@}QwmcH60~Rj&yZXtZH4kq0 z{LidA`dQ7WbNzM~?FHAn@7tvRIm&g> z=`-KIS@PezUvd2CZ9lu)Ih)tsmR+Csq37+^>vre5JnlZ<8MiTMX7?!}{reBfX1v+? zJZ{~Y9rrGB|9C#Xo^5i@H>>kf@9jRtZp-mJ^k*fz^_zfOb1(0Ae |p z>wmuAuaCTUapm>3zpWqs^q4#Slk4+&f5hM4{`B;F!Sf# td;eg)>r)T-NHJd-t41YPq&qSU;jWeIIh3E|IgP66aTw?%;T(ETw_^z zuIxr4d-=JFQtJqXcVEkH=e{?qtULJk `i(mEp(t{di ysUbg(fhsM*Ja-LpmO+uoZSp=@qNkb_I}fv zF3#1aAOHB6`VRKV2Pd=3RRlblQ)3`kkuXKp^j}Z#yo!T{0oQ-mOcq$mCBAU?JE>_~ z0tHW2JU+E@x!J~Kj;dXM@^`Pzf0Ot9cLQ_QtxwwxL2K=H+3(nxdwca+>-T5)^Saht zUw!>ROpzzI^YjhkB4 a=?4Y0&1uSKn7ImrIZTw%;XJ z`-FT=N7R;#Oubz%Hm$f^AZwhyFVU@EZsrEX7yMSIu07v;-tN3i`5n%>$ B zzkZ8U@uLsAwAYpWTfa|x?iOybGo_^;Kc7Ere`U$T$Q|EqNx%7O{v_SS?)Mwv=X|bn zPh>}IX6CcF;5K{L?b-wFywVwqRc%F2USBU$bRzNDCGYhoLZ|Pnzv9^UuvuEorrP}8 zjjJ-%Z$1X@-syLb-KKf= u^FOqJbu_;VLrU$Jq&r13Mu z@MX#UwukoeFXGCmf53cynXmQq_2HkAFaNk-e!q6Nr1iJJ|HfG}=YMido$!FYW~ce{ z`S*3SM0roL%kR1GGw)AN_J`uNo72x5$y#sg_Im#M{Q}4Bv(o3Eec9UUwc+FOtKspl zkGbBhd>&AKKKb&3in*1~c5cgy7cLZ3 l&`RuQUxF^es_vC+Uag%Yh)Z6hu>3iJgmlk!m zwp_pSv2OOXqw^iEX}tTfL3z&)tMYe=?{>d`HzoRUjm!P2*S_U8YyYhZU2XmPY {q|c83qqMudiAc`#WHHLdlyOXFp7Ombeg6M5SKm5EUE90B{`sr>^(%fiBo e@iyLZC#P|{_@6UnU}lo{*|!UkblQb_V~gz{4y2RtKU8T{qHyT z+xB$(;y)kNr9^%m=3k#Zb4%BkGdo%u>or8e -|Bf9yNj)V$tZS*gF9Luki>iygbZToT(hJ< )w)y=z4_{e-pUeMQcl(`ZRX5}6DkmpRUd`ZXc3?;P z`REw^Q=iZKZ#wlbX!VMBkEfg{KDYD0H H(e?8(wVil`hM2ytAc(X>wmp0 z+@_wtwDy~n)Vq!1Qr-~(({+BH%I}I23;Aim$;*A}h4feXx*z*?+iR5XxqoZh+k}i6 zzO&6tSNXjBxAF72-8-rt@G4)KSo!(cu6bLP`)zJbdwS)>=hmym#i@69)hoR>d1cF+ zAhq5`Jg(+E)3j?FljRE^@02pNQeODTZ~f7u;-=-1dp@1=wp(qZ`?B|8ZTG>W+`KXW z|9*M;>Gz*W)t-xOCN6CMbhoCs{=3hm?(ffL8^@j(>NS6*+9r7I_xJb0w<4a#*a+`y zJY4&UP5DlCW&el0J5QRuySJ%#&mJ$USBzmr_q?~?v%0ZY`p3G<+Ff-AzkhzzJ$rZh zthP_TzP>INoyf-j^ ?zJrb5c3ppTa+iuVxE07P<85?Gwu@P` z?#>SN81G+?`|Z7c2p-ylnQZUDj3qev04vy6&5+$!X0yAM=dmKmPm6ZoPCazhoHe zeOV7~Iq$^%b ~W+eBOhjoU>&wVsSIL7l(%Lz*j z17@~6LY09Z7e0IFws85IC&y3AdfSEh>bID=EOh@Ez2}E=dgh1UX6Yw?{+#j2P%-KJ zm6huwSgYrr781MDEob}7BK6O_>bCZMH6A|9EO8Nz8(V%(7b$$aF(>ik^Ivv99@sAD z_-%C9?)eYrGb;l5cYpPpyUssyW- 0amd z*Nc=?4s!cx*sIU0@Y?4b7gruzogSET>x)l${$JKThJQ_sb6(s%cgl6TeyO19%;!h6 zZ$xBSm%Ml*HOa&xvw?+m=}zZ%J-^K5>5Q*aFFbl8U2*2(hoj>2cOT38@MA;ioswVQ z spCUi-}gU^u({PWFJo{6Pc+Od&~Wm zK`*yd&KJLuH#e?ErewZ(?q1O*u3L3H>Q1!FJ;o=WA9bI<`TdOTI`Q{^zuzyu_I=M2 z|77l^bJ;#0=I`(P!g2A&GCO|xy7iBZGB35XOncI#CC~f$+}!G2Ri}T~9cZvyC-d*$ zox0x&*EcMRldJi#tj#S>V#%y&Zl+$=-#3)L)_$XK(RZ&KzfHicef+<7ww14s`}?ML zm(41^rB!_iR#J`sq_3{Oukig}#jBO-YXTMaFrFxH+0e76TE6y6V~3FJnaQOh;Uf0; zj7?ZRDS!F(vHjqqPW7TH1HIjAGMD^2a(Z*=>uu-GcF*JQ@!qkQWq#qE+>9O9w;hvs z_2lpO`_f-!)1yyMTRZpAuix*p-+uqK?L_(U`ID<#F7+9m-aX^}Jn^0wi5q!4QyU~R zcndmv6#mV)k!tmd@!f*$_pDCr`FJe*+yjY}535W&KIlEY>o|A$c0G+6>6c60Gmo!0 zTHfE*`M>Pbyv2%^DJSN#&2^n5%d+iU-{})OxptkcxvrqEZ0DlvZkMr^UwH1<-9bzD zUMkvY^P#38@Z83VyVdXK3NF1nKjG)6rPqQkoYgcbVO^wfRCvm_gU!FKpZ;IHeqX_~ zg7puM$Q?fYwa=Q(y>>&Thq_P2smLE5{hu0>{FF-^Ef+e>m4K6{~I=huG+GE z^Sk8OZ(i1woBv(%K5v<=@1-qvvcgGDBO}7`Z+H8a4F_0HL|=Ru7t(5VZOO@*>ne8W zJzOp!@{aK<>&ocwD;}&{Q7YxQr=gv1udY|7y5PQRUo;L%Wef94z4w1*ypT_!WNH=n z DQOcca@xS)2Dx(W5m;2 z#a>Y3xBb7fp7xs3%89w#bzkbtkZE3Y?(avZo0&TO(&pE4y^ifxICZ?!aMgv!9&=w@ zU%!3x5BB#DZ*9%KTU&c4N^EC4tEz;SL7&26x4Xhiw`}H3tvk|T9Z||F_kQv_Gm%U7 z`;_;{3#gTHPrkJ6I>&XcmiG}8QX_tq t4iN>`rN7zm~+zhZ<*}vy~QtjSC zep$;;dg12hEgs9<78d{O`}l9;R=JnAz8=$9IyZL?_jKoZ>0emY?w;;mvRdxJ<$o`i z&wmnhOOyM3&@1C6L6xvRt5x%|7i`??-tV`rT=mD>C%?Vd$G^W9dw09iw~D`d^H?@{ z6+RbxaK8L;?{&~dPKh;cB{vreY|FZOE$a9@bMreFF7l?`nRB~Un^(8=$;@;$mq^a0 zpO5Zaq