")
+ b.WriteString(strings.ReplaceAll(escapeHTMLText(para), "\n", "
"))
+ b.WriteString("
First paragraph.
") { + t.Errorf("rss content missing paragraph markup: %q", techItem.Content) + } + if !strings.Contains(techItem.Content, "<html>") { + t.Errorf("rss content did not escape embedded markup: %q", techItem.Content) + } + if !strings.Contains(techItem.PubDate, "2026") { + t.Errorf("rss pubDate = %q, want the published date", techItem.PubDate) + } + if !strings.Contains(rw.Body.String(), `href="https://news.example/feed.xml"`) { + t.Errorf("rss missing atom self link with base_url") + } + + // --- Site-wide JSON Feed --- + rw = httptest.NewRecorder() + s.handleFeedJSON(rw, httptest.NewRequest("GET", "/feed.json", nil), "") + if rw.Code != 200 { + t.Fatalf("json status = %d", rw.Code) + } + if ct := rw.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/feed+json") { + t.Errorf("json content-type = %q", ct) + } + var jf struct { + Version string `json:"version"` + FeedURL string `json:"feed_url"` + HomePageURL string `json:"home_page_url"` + Items []struct { + ID string `json:"id"` + URL string `json:"url"` + Title string `json:"title"` + ContentText string `json:"content_text"` + Tags []string `json:"tags"` + Authors []struct { + Name string `json:"name"` + } `json:"authors"` + } `json:"items"` + } + if err := json.Unmarshal(rw.Body.Bytes(), &jf); err != nil { + t.Fatalf("json feed did not parse: %v", err) + } + if !strings.Contains(jf.Version, "jsonfeed.org") { + t.Errorf("json version = %q", jf.Version) + } + if jf.FeedURL != "https://news.example/feed.json" { + t.Errorf("json feed_url = %q", jf.FeedURL) + } + if len(jf.Items) != 2 { + t.Fatalf("json items = %d, want 2", len(jf.Items)) + } + var jTech *struct { + ID string `json:"id"` + URL string `json:"url"` + Title string `json:"title"` + ContentText string `json:"content_text"` + Tags []string `json:"tags"` + Authors []struct { + Name string `json:"name"` + } `json:"authors"` + } + for i := range jf.Items { + if jf.Items[i].ID == "feed-tech-1" { + jTech = &jf.Items[i] + } + } + if jTech == nil { + t.Fatal("tech item missing from json feed") + } + if jTech.URL != "https://example.com/tech/story" { + t.Errorf("json url = %q, want canonical", jTech.URL) + } + if !strings.Contains(jTech.ContentText, "Second paragraph") { + t.Errorf("json content_text = %q", jTech.ContentText) + } + if len(jTech.Tags) != 1 || jTech.Tags[0] != "tech" { + t.Errorf("json tags = %v", jTech.Tags) + } + if len(jTech.Authors) != 1 || jTech.Authors[0].Name != "Example Wire" { + t.Errorf("json authors = %v", jTech.Authors) + } + + // --- Channel-scoped RSS excludes other channels --- + rw = httptest.NewRecorder() + s.handleFeedXML(rw, httptest.NewRequest("GET", "/tech/feed.xml", nil), "tech") + rss.Channel.Items = nil // xml.Unmarshal appends; clear the site-wide items + if err := xml.Unmarshal(rw.Body.Bytes(), &rss); err != nil { + t.Fatalf("channel rss parse: %v", err) + } + if len(rss.Channel.Items) != 1 { + t.Fatalf("channel rss items = %d, want 1", len(rss.Channel.Items)) + } + if rss.Channel.Items[0].GUID != "feed-tech-1" { + t.Errorf("channel rss wrong item: %q", rss.Channel.Items[0].GUID) + } + if !strings.Contains(rss.Channel.Title, "Tech") { + t.Errorf("channel rss title = %q, want channel name", rss.Channel.Title) + } +} diff --git a/internal/web/handlers.go b/internal/web/handlers.go index e4b3bf7..51e9acc 100644 --- a/internal/web/handlers.go +++ b/internal/web/handlers.go @@ -45,16 +45,20 @@ func toView(s storage.Story) StoryView { } type pageData struct { - SiteTitle string - Channels []Channel - Active string // slug of active channel, "" for landing - Weather Weather - Phase string // when set, forces day/dawn/dusk/night and disables the clock-driven phase JS - AllSources template.JS // JSON array of {name, channel} for the settings panel - AuthEnabled bool // sign-in is available - User *SessionUser // nil for anonymous visitors - UserPrefs template.JS // signed-in user's stored prefs blob (JSON), or "null" - Path string // current request path, for post-login return + SiteTitle string + Channels []Channel + Active string // slug of active channel, "" for landing + Weather Weather + Phase string // when set, forces day/dawn/dusk/night and disables the clock-driven phase JS + AllSources template.JS // JSON array of {name, channel} for the settings panel + AuthEnabled bool // sign-in is available + User *SessionUser // nil for anonymous visitors + UserPrefs template.JS // signed-in user's stored prefs blob (JSON), or "null" + Path string // current request path, for post-login return + PostingEnabled bool // false = web-only mode; hide Matrix-posting UI + IsAdmin bool // signed-in user is on the admin allowlist (shows /status link) + PushEnabled bool // Web Push is configured (shows the notifications toggle to signed-in users) + PushPublicKey string // VAPID public key handed to the client to subscribe } type channelPage struct { @@ -74,6 +78,7 @@ type indexPage struct { JustPosted []StoryView Stats []channelStat Latest []StoryView + ForYou []StoryView // personalized rail; empty for anon / no-history users } type channelStat struct { @@ -105,17 +110,21 @@ func (s *Server) base(r *http.Request) pageData { srcJSON = []byte("[]") } d := pageData{ - SiteTitle: s.cfg.SiteTitle, - Channels: channels, - Weather: currentWeather(time.Now()), - AllSources: jsForScript(srcJSON), - AuthEnabled: s.auth != nil, - UserPrefs: template.JS("null"), - Path: r.URL.Path, + SiteTitle: s.cfg.SiteTitle, + Channels: channels, + Weather: currentWeather(time.Now()), + AllSources: jsForScript(srcJSON), + AuthEnabled: s.auth != nil, + UserPrefs: template.JS("null"), + Path: r.URL.Path, + PostingEnabled: s.postingEnabled, + PushEnabled: s.auth != nil && s.cfg.Push.Enabled, + PushPublicKey: s.cfg.Push.VAPIDPublicKey, } if s.auth != nil { if u := s.auth.userFromRequest(r); u != nil { d.User = u + d.IsAdmin = s.adminSubs[u.Sub] if blob, err := storage.GetUserPrefs(u.Sub); err == nil && blob != "" { d.UserPrefs = jsForScript([]byte(blob)) } @@ -176,9 +185,54 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) { Stats: stats, Latest: latest, } + // For signed-in users with some read/bookmark history, lead with a small + // personalized rail. ForYou returns nothing for anon / no-history users, so + // the section simply doesn't render for them. + if s.auth != nil { + if u := s.auth.userFromRequest(r); u != nil { + const forYouLimit = 8 + if fy, err := storage.ForYou(u.Sub, forYouLimit); err != nil { + slog.Error("web: for-you rail failed", "sub", u.Sub, "err", err) + } else { + for _, row := range fy { + data.ForYou = append(data.ForYou, toView(row)) + } + } + } + } s.render(w, "index", data) } +type forYouPage struct { + pageData + Stories []StoryView +} + +// handleForYou renders the dedicated personalized feed. Anonymous visitors are +// sent to sign-in (the route is only registered when auth is on). +func (s *Server) handleForYou(w http.ResponseWriter, r *http.Request) { + u := s.auth.userFromRequest(r) + if u == nil { + http.Redirect(w, r, "/auth/login?next=/for-you", http.StatusSeeOther) + return + } + s.track(r, "for-you") + const limit = 30 + rows, err := storage.ForYou(u.Sub, limit) + if err != nil { + slog.Error("web: for-you page failed", "sub", u.Sub, "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + views := make([]StoryView, 0, len(rows)) + for _, row := range rows { + views = append(views, toView(row)) + } + base := s.base(r) + base.Active = "for-you" + s.render(w, "for-you", forYouPage{pageData: base, Stories: views}) +} + func (s *Server) handleChannel(w http.ResponseWriter, r *http.Request, ch Channel) { s.track(r, ch.Slug) page := 1 @@ -220,6 +274,64 @@ func (s *Server) handleChannel(w http.ResponseWriter, r *http.Request, ch Channe s.render(w, "channel", data) } +type bookmarksPage struct { + pageData + Stories []StoryView + Page int + HasPrev bool + HasNext bool + PrevURL string + NextURL string + Total int +} + +// handleBookmarks lists the signed-in user's bookmarked stories. Anonymous +// visitors are sent to sign-in (the route is only registered when auth is on). +func (s *Server) handleBookmarks(w http.ResponseWriter, r *http.Request) { + u := s.auth.userFromRequest(r) + if u == nil { + http.Redirect(w, r, "/auth/login?next=/bookmarks", http.StatusSeeOther) + return + } + s.track(r, "bookmarks") + page := 1 + if p := r.URL.Query().Get("page"); p != "" { + if n, err := strconv.Atoi(p); err == nil && n > 0 { + page = n + } + } + offset := (page - 1) * pageSize + rows, err := storage.ListBookmarks(u.Sub, pageSize+1, offset) + if err != nil { + slog.Error("web: list bookmarks failed", "sub", u.Sub, "err", err) + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + hasNext := len(rows) > pageSize + if hasNext { + rows = rows[:pageSize] + } + views := make([]StoryView, 0, len(rows)) + for _, row := range rows { + views = append(views, toView(row)) + } + total, _ := storage.CountBookmarks(u.Sub) + + base := s.base(r) + base.Active = "bookmarks" + data := bookmarksPage{ + pageData: base, + Stories: views, + Page: page, + HasPrev: page > 1, + HasNext: hasNext, + PrevURL: fmt.Sprintf("/bookmarks?page=%d", page-1), + NextURL: fmt.Sprintf("/bookmarks?page=%d", page+1), + Total: total, + } + s.render(w, "bookmarks", data) +} + var ( demoVariants = []string{"rain", "petals", "jacaranda", "motes", "leaves", "clear", "clouds", "snow", "fog", "storm"} demoIntensities = []string{"light", "medium", "heavy"} @@ -309,6 +421,12 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { http.Error(w, "internal error", http.StatusInternalServerError) return } + _ = json.NewEncoder(w).Encode(map[string]any{"results": toSearchResults(rows)}) +} + +// toSearchResults maps stories to the JSON shape shared by /api/search and +// /api/related, resolving each story's channel to its display metadata. +func toSearchResults(rows []storage.Story) []searchResult { channelByName := make(map[string]Channel, len(channels)) for _, ch := range channels { channelByName[ch.Slug] = ch @@ -333,7 +451,50 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { Posted: row.Posted, }) } - _ = json.NewEncoder(w).Encode(map[string]any{"results": results}) + return results +} + +// handleRelated returns stories textually similar to a given story, for the +// "You might also like" rail in reader mode. It is public (reader mode works +// for anonymous visitors too); for signed-in users it drops already-read +// stories so recommendations stay fresh. +func (s *Server) handleRelated(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("Cache-Control", "no-store") + id, err := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("id")), 10, 64) + if err != nil || id <= 0 { + http.Error(w, `{"error":"bad id"}`, http.StatusBadRequest) + return + } + const relatedLimit = 6 + // Over-fetch so dropping already-read stories doesn't starve the rail. + rows, err := storage.RelatedStories(id, relatedLimit*2) + if err != nil { + slog.Error("web: related failed", "id", id, "err", err) + http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError) + return + } + if s.auth != nil && len(rows) > 0 { + if u := s.auth.userFromRequest(r); u != nil { + ids := make([]int64, 0, len(rows)) + for _, row := range rows { + ids = append(ids, row.ID) + } + if read, _, err := storage.UserStoryState(u.Sub, ids); err == nil { + kept := rows[:0] + for _, row := range rows { + if !read[row.ID] { + kept = append(kept, row) + } + } + rows = kept + } + } + } + if len(rows) > relatedLimit { + rows = rows[:relatedLimit] + } + _ = json.NewEncoder(w).Encode(map[string]any{"results": toSearchResults(rows)}) } // handleArticle serves the stored full text of a single story for reader mode. diff --git a/internal/web/push_sender.go b/internal/web/push_sender.go new file mode 100644 index 0000000..35d6657 --- /dev/null +++ b/internal/web/push_sender.go @@ -0,0 +1,199 @@ +package web + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "time" + + "pete/internal/storage" + + webpush "github.com/SherClockHolmes/webpush-go" +) + +// digestScan caps how many new stories the sender inspects per subscriber in one +// pass. Well past MinStories; the digest only needs a count and one headline. +const digestScan = 60 + +// runPushSender periodically builds and delivers a "N new stories" digest to +// each subscriber, respecting their disabled-sources preference. It's started +// only when push is configured. Best-effort throughout: a failed send never +// stops the loop, and a permanently-gone endpoint is pruned. +func (s *Server) runPushSender(ctx context.Context) { + interval := time.Duration(s.cfg.Push.IntervalMinutes) * time.Minute + if interval <= 0 { + interval = 6 * time.Hour + } + slog.Info("web: push digest sender started", "interval", interval, "min_stories", s.cfg.Push.MinStories) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + s.sendDigests() + } + } +} + +// sendDigests walks every subscription once, notifying those with enough new +// stories since their last digest. +func (s *Server) sendDigests() { + subs, err := storage.ListPushSubscriptions() + if err != nil { + slog.Error("push: list subscriptions failed", "err", err) + return + } + if len(subs) == 0 { + return + } + now := time.Now().Unix() + // Disabled-source sets are per user; cache within a pass so a user with + // several devices only parses prefs once. + disabledByUser := make(map[string]map[string]bool) + sent, pruned := 0, 0 + + for _, sub := range subs { + stories, err := storage.NewClassifiedSince(sub.LastNotifiedAt, digestScan) + if err != nil { + slog.Error("push: scan new stories failed", "sub", sub.UserSub, "err", err) + continue + } + if len(stories) == 0 { + continue + } + disabled, ok := disabledByUser[sub.UserSub] + if !ok { + disabled = disabledSourcesFor(sub.UserSub) + disabledByUser[sub.UserSub] = disabled + } + count, top := 0, "" + for _, st := range stories { + if disabled[st.Source] { + continue + } + if count == 0 { + top = st.Headline + } + count++ + } + if count < s.cfg.Push.MinStories { + continue + } + + payload := buildDigestPayload(count, top) + gone, err := s.sendPush(sub, payload) + if gone { + if derr := storage.RemovePushSubscription(sub.Endpoint); derr != nil { + slog.Error("push: prune gone subscription failed", "err", derr) + } else { + pruned++ + } + continue + } + if err != nil { + slog.Warn("push: send failed", "sub", sub.UserSub, "err", err) + continue + } + if derr := storage.TouchPushSubscription(sub.Endpoint, now); derr != nil { + slog.Error("push: advance watermark failed", "err", derr) + } + sent++ + } + if sent > 0 || pruned > 0 { + slog.Info("push: digest pass complete", "sent", sent, "pruned", pruned, "subscriptions", len(subs)) + } +} + +// buildDigestPayload renders the notification JSON the service worker expects. +func buildDigestPayload(count int, top string) []byte { + body := fmt.Sprintf("%d new stories", count) + if count == 1 { + body = "1 new story" + } + if top != "" { + body += " — " + top + } + b, _ := json.Marshal(map[string]string{ + "title": "Pete has fresh news", + "body": body, + "url": "/", + "tag": "pete-digest", + }) + return b +} + +// sendPush encrypts and delivers one notification. It reports gone=true when the +// push service says the endpoint no longer exists (404/410) so the caller can +// prune it; err is set for other, likely-transient failures. +func (s *Server) sendPush(sub storage.PushSubscription, payload []byte) (gone bool, err error) { + resp, err := webpush.SendNotification(payload, &webpush.Subscription{ + Endpoint: sub.Endpoint, + Keys: webpush.Keys{P256dh: sub.P256dh, Auth: sub.Auth}, + }, &webpush.Options{ + Subscriber: s.cfg.Push.Subject, + VAPIDPublicKey: s.cfg.Push.VAPIDPublicKey, + VAPIDPrivateKey: s.cfg.Push.VAPIDPrivateKey, + TTL: 24 * 60 * 60, + Urgency: webpush.UrgencyNormal, + }) + if err != nil { + return false, err + } + defer resp.Body.Close() + if resp.StatusCode == 404 || resp.StatusCode == 410 { + return true, nil + } + if resp.StatusCode >= 400 { + return false, fmt.Errorf("push service returned %d", resp.StatusCode) + } + return false, nil +} + +// disabledSourcesFor returns the set of source names a user has hidden, read +// from their stored prefs blob. The blob mirrors localStorage: a JSON object +// whose "pete.disabledSources.v1" value is itself a JSON string encoding a +// {sourceName: true} map. Any parse failure yields an empty (deny-nothing) set. +func disabledSourcesFor(sub string) map[string]bool { + out := map[string]bool{} + blob, err := storage.GetUserPrefs(sub) + if err != nil || blob == "" { + return out + } + var prefs map[string]json.RawMessage + if err := json.Unmarshal([]byte(blob), &prefs); err != nil { + return out + } + raw, ok := prefs["pete.disabledSources.v1"] + if !ok { + return out + } + // The value is normally a JSON *string* containing JSON; unwrap that first, + // but tolerate a bare object too. + inner := []byte(raw) + var asStr string + if err := json.Unmarshal(raw, &asStr); err == nil { + inner = []byte(asStr) + } + var set map[string]bool + if err := json.Unmarshal(inner, &set); err != nil { + return out + } + for name, on := range set { + if on { + out[name] = true + } + } + return out +} + +// StartPushSender launches the digest loop if push is enabled. Safe to call +// unconditionally; it's a no-op when push is off. +func (s *Server) StartPushSender(ctx context.Context) { + if !s.cfg.Push.Enabled || s.auth == nil { + return + } + go s.runPushSender(ctx) +} diff --git a/internal/web/push_sender_test.go b/internal/web/push_sender_test.go new file mode 100644 index 0000000..a6083d2 --- /dev/null +++ b/internal/web/push_sender_test.go @@ -0,0 +1,58 @@ +package web + +import ( + "path/filepath" + "strings" + "testing" + + "pete/internal/storage" +) + +func TestDisabledSourcesFor(t *testing.T) { + storage.Close() + if err := storage.Init(filepath.Join(t.TempDir(), "push.db")); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { storage.Close() }) + + // The stored blob mirrors localStorage: the disabledSources value is itself a + // JSON *string* encoding a {source: true} map (see prefs.js snapshot()). + blob := `{"pete.disabledSources.v1":"{\"Feed A\":true,\"Feed B\":false}","pete.weather.loc.v1":"1000-100"}` + if err := storage.PutUserPrefs("sub-1", blob, "u", "u@x"); err != nil { + t.Fatal(err) + } + + got := disabledSourcesFor("sub-1") + if !got["Feed A"] { + t.Error("Feed A should be disabled") + } + if got["Feed B"] { + t.Error("Feed B is false in prefs and must not be treated as disabled") + } + if len(got) != 1 { + t.Errorf("disabled set = %v, want just {Feed A}", got) + } + + // A user with no prefs disables nothing. + if len(disabledSourcesFor("nobody")) != 0 { + t.Error("unknown user should have an empty disabled set") + } +} + +func TestBuildDigestPayload(t *testing.T) { + single := string(buildDigestPayload(1, "Only Story")) + if !strings.Contains(single, "1 new story") || strings.Contains(single, "1 new stories") { + t.Errorf("singular wording wrong: %s", single) + } + if !strings.Contains(single, "Only Story") { + t.Errorf("top headline missing: %s", single) + } + + many := string(buildDigestPayload(7, "Big One")) + if !strings.Contains(many, "7 new stories") || !strings.Contains(many, "Big One") { + t.Errorf("plural payload wrong: %s", many) + } + if !strings.Contains(many, `"tag":"pete-digest"`) { + t.Errorf("expected digest tag in payload: %s", many) + } +} diff --git a/internal/web/pwa.go b/internal/web/pwa.go new file mode 100644 index 0000000..68d9006 --- /dev/null +++ b/internal/web/pwa.go @@ -0,0 +1,106 @@ +package web + +import ( + "io/fs" + "log/slog" + "net/http" + + "pete/internal/storage" +) + +// maxPushBodyBytes caps a subscription payload. A PushSubscription JSON is an +// endpoint URL plus two short base64 keys — a few hundred bytes — so 4 KiB is +// generous headroom for long endpoint URLs. +const maxPushBodyBytes = 4096 + +// handlePushSubscribe stores the caller's Web Push subscription. The body is the +// browser's PushSubscription.toJSON() shape: {endpoint, keys:{p256dh, auth}}. +func (s *Server) handlePushSubscribe(w http.ResponseWriter, r *http.Request) { + u := s.requireUser(w, r) + if u == nil { + return + } + if !s.cfg.Push.Enabled { + http.Error(w, `{"error":"push disabled"}`, http.StatusNotFound) + return + } + var req struct { + Endpoint string `json:"endpoint"` + Keys struct { + P256dh string `json:"p256dh"` + Auth string `json:"auth"` + } `json:"keys"` + } + if !decodeStateBodyN(w, r, &req, maxPushBodyBytes) { + return + } + if req.Endpoint == "" || req.Keys.P256dh == "" || req.Keys.Auth == "" { + http.Error(w, `{"error":"incomplete subscription"}`, http.StatusBadRequest) + return + } + if err := storage.AddPushSubscription(u.Sub, req.Endpoint, req.Keys.P256dh, req.Keys.Auth); err != nil { + slog.Error("push: subscribe failed", "sub", u.Sub, "err", err) + http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// handlePushUnsubscribe drops a stored subscription by endpoint. It doesn't +// require the endpoint to belong to the caller beyond being signed in; the +// endpoint is an unguessable capability URL, and dropping a stale one is benign. +func (s *Server) handlePushUnsubscribe(w http.ResponseWriter, r *http.Request) { + u := s.requireUser(w, r) + if u == nil { + return + } + var req struct { + Endpoint string `json:"endpoint"` + } + if !decodeStateBodyN(w, r, &req, maxPushBodyBytes) { + return + } + if req.Endpoint == "" { + http.Error(w, `{"error":"missing endpoint"}`, http.StatusBadRequest) + return + } + if err := storage.RemovePushSubscription(req.Endpoint); err != nil { + slog.Error("push: unsubscribe failed", "sub", u.Sub, "err", err) + http.Error(w, `{"error":"internal error"}`, http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) +} + +// handleManifest serves the web app manifest from the embedded static tree. It +// lives at the root so the installable scope covers the whole origin. +func (s *Server) handleManifest(w http.ResponseWriter, r *http.Request) { + s.serveEmbedded(w, r, "manifest.webmanifest", "application/manifest+json; charset=utf-8", "public, max-age=3600") +} + +// handleServiceWorker serves /sw.js from the root. Serving it here rather than +// under /static/ lets its scope be the whole origin (a worker's default scope +// is its own path), and we set Service-Worker-Allowed as a belt-and-braces in +// case it's ever moved. no-cache keeps updated workers from being pinned by the +// HTTP cache — the browser still byte-compares to decide whether to install. +func (s *Server) handleServiceWorker(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Service-Worker-Allowed", "/") + s.serveEmbedded(w, r, "sw.js", "text/javascript; charset=utf-8", "no-cache") +} + +// serveEmbedded writes a file from the embedded static FS with explicit headers. +func (s *Server) serveEmbedded(w http.ResponseWriter, _ *http.Request, name, contentType, cacheControl string) { + sub, err := fs.Sub(staticFS, "static") + if err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + b, err := fs.ReadFile(sub, name) + if err != nil { + http.NotFound(w, nil) + return + } + w.Header().Set("Content-Type", contentType) + w.Header().Set("Cache-Control", cacheControl) + _, _ = w.Write(b) +} diff --git a/internal/web/reader_test.go b/internal/web/reader_test.go index df5d1d1..18f30b8 100644 --- a/internal/web/reader_test.go +++ b/internal/web/reader_test.go @@ -42,7 +42,7 @@ func TestReaderCardDataAndArticleAPI(t *testing.T) { t.Fatal(err) } - s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil) + s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil, true) if err != nil { t.Fatal(err) } diff --git a/internal/web/server.go b/internal/web/server.go index bfc1bd1..a1db26b 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -52,11 +52,13 @@ type SourceInfo struct { // Server is the HTTP server for Pete's web UI. type Server struct { - cfg config.WebConfig - sources []SourceInfo - srv *http.Server - tpls map[string]*template.Template // keyed by page name (e.g. "index", "channel") - auth *Authenticator // nil when sign-in is disabled or unavailable + cfg config.WebConfig + sources []SourceInfo + postingEnabled bool // false = web-only mode; hides Matrix-posting UI + srv *http.Server + tpls map[string]*template.Template // keyed by page name (e.g. "index", "channel") + auth *Authenticator // nil when sign-in is disabled or unavailable + adminSubs map[string]bool // OIDC subjects allowed to view /status // Daily-rotated salt for the privacy-preserving unique-visitor estimate. // Guarded by metricsMu; never persisted (see metrics.go). @@ -68,8 +70,8 @@ type Server struct { // New builds the server. Templates are parsed once at startup. Each page // gets its own template set sharing layout.html + _card.html, which avoids // `main` define collisions between pages. -func New(cfg config.WebConfig, sources []config.SourceConfig) (*Server, error) { - pages := []string{"index", "channel", "weather"} +func New(cfg config.WebConfig, sources []config.SourceConfig, postingEnabled bool) (*Server, error) { + pages := []string{"index", "channel", "weather", "bookmarks", "for-you", "status"} tpls := make(map[string]*template.Template, len(pages)) for _, p := range pages { t, err := template.New("").Funcs(funcs).ParseFS(templateFS, @@ -89,7 +91,13 @@ func New(cfg config.WebConfig, sources []config.SourceConfig) (*Server, error) { } infos = append(infos, SourceInfo{Name: sc.Name, Channel: sc.DirectRoute}) } - s := &Server{cfg: cfg, sources: infos, tpls: tpls} + adminSubs := make(map[string]bool, len(cfg.AdminSubs)) + for _, sub := range cfg.AdminSubs { + if sub != "" { + adminSubs[sub] = true + } + } + s := &Server{cfg: cfg, sources: infos, postingEnabled: postingEnabled, tpls: tpls, adminSubs: adminSubs} // Optional OIDC sign-in (Authentik). Discovery is a network call; if the // provider is unreachable at boot we log and serve anonymously rather than @@ -115,15 +123,23 @@ func New(cfg config.WebConfig, sources []config.SourceConfig) (*Server, error) { mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub)))) mux.HandleFunc("GET /img/{name}", s.handleImg) + mux.HandleFunc("GET /manifest.webmanifest", s.handleManifest) + mux.HandleFunc("GET /sw.js", s.handleServiceWorker) + mux.HandleFunc("GET /{$}", s.handleIndex) mux.HandleFunc("GET /weather", s.handleWeatherDemo) mux.HandleFunc("GET /api/search", s.handleSearch) mux.HandleFunc("GET /api/article", s.handleArticle) + mux.HandleFunc("GET /api/related", s.handleRelated) + mux.HandleFunc("GET /feed.xml", func(w http.ResponseWriter, r *http.Request) { s.handleFeedXML(w, r, "") }) + mux.HandleFunc("GET /feed.json", func(w http.ResponseWriter, r *http.Request) { s.handleFeedJSON(w, r, "") }) for _, ch := range channels { ch := ch mux.HandleFunc("GET /"+ch.Slug, func(w http.ResponseWriter, r *http.Request) { s.handleChannel(w, r, ch) }) + mux.HandleFunc("GET /"+ch.Slug+"/feed.xml", func(w http.ResponseWriter, r *http.Request) { s.handleFeedXML(w, r, ch.Slug) }) + mux.HandleFunc("GET /"+ch.Slug+"/feed.json", func(w http.ResponseWriter, r *http.Request) { s.handleFeedJSON(w, r, ch.Slug) }) } mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) @@ -136,6 +152,16 @@ func New(cfg config.WebConfig, sources []config.SourceConfig) (*Server, error) { mux.HandleFunc("GET /auth/logout", s.auth.handleLogout) mux.HandleFunc("GET /api/preferences", s.handlePrefs) mux.HandleFunc("PUT /api/preferences", s.handlePrefs) + mux.HandleFunc("POST /api/read", s.handleRead) + mux.HandleFunc("POST /api/bookmark", s.handleBookmark) + mux.HandleFunc("GET /api/state", s.handleState) + mux.HandleFunc("GET /bookmarks", s.handleBookmarks) + mux.HandleFunc("GET /for-you", s.handleForYou) + mux.HandleFunc("GET /status", s.handleStatus) + if s.cfg.Push.Enabled { + mux.HandleFunc("POST /api/push/subscribe", s.handlePushSubscribe) + mux.HandleFunc("POST /api/push/unsubscribe", s.handlePushUnsubscribe) + } } s.srv = &http.Server{ diff --git a/internal/web/static/css/input.css b/internal/web/static/css/input.css index b98270b..1efd0fd 100644 --- a/internal/web/static/css/input.css +++ b/internal/web/static/css/input.css @@ -330,6 +330,62 @@ html[data-phase="night"] { .pete-reader-hint { display: none; } } + /* "You might also like" rail, shown under the article in feed mode. Lives + inside the reader's scroll area, below the article card. */ + .pete-reader-related { width: 100%; margin: 0.85rem auto 0; } + .pete-reader-related-title { + font-family: "Fredoka", "Nunito", system-ui, sans-serif; + font-weight: 700; + font-size: 0.95rem; + color: #fff; + margin: 0 0 0.6rem; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); + } + .pete-reader-related-grid { display: grid; gap: 0.6rem; } + .pete-reader-related-card { + display: flex; + align-items: center; + gap: 0.75rem; + background: var(--card); + color: var(--ink); + border-radius: 1rem; + border: 2px solid rgba(0, 0, 0, 0.06); + padding: 0.6rem; + text-decoration: none; + transition: transform 0.15s ease, box-shadow 0.15s ease; + } + .pete-reader-related-card:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(60, 40, 20, 0.16); + } + html[data-phase="night"] .pete-reader-related-card { border-color: rgba(255, 255, 255, 0.08); } + .pete-reader-related-thumb { + width: 4.5rem; + height: 3.25rem; + flex-shrink: 0; + object-fit: cover; + border-radius: 0.6rem; + background: rgba(0, 0, 0, 0.06); + } + .pete-reader-related-meta { min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; } + .pete-reader-related-eyebrow { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.4rem; + font-size: 0.7rem; + } + .pete-reader-related-source { font-weight: 700; opacity: 0.7; } + .pete-reader-related-headline { + font-weight: 700; + line-height: 1.25; + font-size: 0.92rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + /* Grid treatment for stories already read in feed mode: dimmed, with a small corner check. Hovering restores full opacity so nothing feels lost. */ [data-story-card][data-read="1"] { opacity: 0.5; transition: opacity 0.2s ease; } diff --git a/internal/web/static/css/output.css b/internal/web/static/css/output.css index b417a7b..bd6361c 100644 --- a/internal/web/static/css/output.css +++ b/internal/web/static/css/output.css @@ -1 +1 @@ -*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body{font-family:Nunito,system-ui,sans-serif}html{scroll-behavior:smooth}body,html{overflow-x:hidden}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pete-channel-nav{scrollbar-width:none;-ms-overflow-style:none}.pete-channel-nav::-webkit-scrollbar{display:none}.pete-reader-backdrop{position:absolute;inset:0;background:rgba(20,14,6,.55);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}html[data-phase=night] .pete-reader-backdrop{background:rgba(6,8,20,.65)}.pete-reader-shell{position:relative;height:100%;width:100%;max-width:44rem;margin:0 auto;display:flex;flex-direction:column;padding:max(env(safe-area-inset-top),.75rem) 1rem .75rem;gap:.75rem}.pete-reader-bar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;color:#fff}.pete-reader-progress{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:600;font-size:.85rem;letter-spacing:.02em;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-nav{display:flex;align-items:center;gap:.4rem}.pete-reader-btn{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 .7rem;border-radius:9999px;background:hsla(0,0%,100%,.14);color:#fff;font-weight:700;font-size:.95rem;border:2px solid hsla(0,0%,100%,.18);cursor:pointer;transition:background .15s ease,transform .15s ease,opacity .15s ease}.pete-reader-btn:hover{background:hsla(0,0%,100%,.28);transform:translateY(-1px)}.pete-reader-btn:disabled{opacity:.35;cursor:default;transform:none}.pete-reader-btn-open{background:var(--accent);border-color:transparent;color:#1c1305;text-decoration:none}.pete-reader-btn-open:hover{background:var(--accent);filter:brightness(1.08)}.pete-reader-scroll{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border-radius:1.75rem}.pete-reader-article{background:var(--card);color:var(--ink);border-radius:1.75rem;border:2px solid rgba(0,0,0,.06);box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.18);padding:clamp(1.25rem,4vw,2.5rem)}html[data-phase=night] .pete-reader-article{border-color:hsla(0,0%,100%,.08)}.pete-reader-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;font-size:.75rem;margin-bottom:.85rem}.pete-reader-chip{display:inline-flex;align-items:center;gap:.35rem;border-radius:9999px;padding:.15rem .65rem;color:#fff;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.pete-reader-source{font-weight:700;opacity:.75}.pete-reader-time{opacity:.55}.pete-reader-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;line-height:1.15;letter-spacing:-.01em;font-size:clamp(1.6rem,4.5vw,2.4rem);margin-bottom:1rem}.pete-reader-hero{width:100%;border-radius:1.1rem;margin-bottom:1.25rem;background:rgba(0,0,0,.05)}.pete-reader-body{font-size:1.08rem;line-height:1.75}.pete-reader-body p{margin-bottom:1.05em}.pete-reader-body p:last-child{margin-bottom:0}.pete-reader-note{margin-top:1.25rem;padding-top:1rem;border-top:1px solid rgba(0,0,0,.1);font-size:.85rem;opacity:.7}html[data-phase=night] .pete-reader-note{border-color:hsla(0,0%,100%,.12)}.pete-reader-note a{color:var(--accent);font-weight:700}.pete-reader-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:.75rem;padding:clamp(2rem,8vw,4rem) 1rem;color:var(--ink)}.pete-reader-empty-emoji{font-size:2.5rem}.pete-reader-hint{text-align:center;color:hsla(0,0%,100%,.85);font-size:.75rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-hint kbd{font-family:ui-monospace,monospace;background:hsla(0,0%,100%,.16);border-radius:.3rem;padding:.05rem .3rem}@media (max-width:640px){.pete-reader-hint{display:none}}[data-story-card][data-read="1"]{opacity:.5;transition:opacity .2s ease}[data-story-card][data-read="1"]:hover{opacity:1}[data-story-card][data-read="1"]:after{content:"✓";position:absolute;top:.6rem;right:.6rem;z-index:6;display:grid;place-items:center;height:1.5rem;width:1.5rem;border-radius:9999px;background:rgba(20,14,6,.72);color:#fff;font-size:.8rem;font-weight:700}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-right-6{right:-1.5rem}.-top-6{top:-1.5rem}.-z-10{z-index:-10}.-z-\[5\]{z-index:-5}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-8{margin-bottom:2rem}.mr-1{margin-right:.25rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.line-clamp-2{-webkit-line-clamp:2}.line-clamp-2,.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical}.line-clamp-3{-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-\[16\/10\]{aspect-ratio:16/10}.h-12{height:3rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-\[55vh\]{max-height:55vh}.max-h-\[60vh\]{max-height:60vh}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-0{min-width:0}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.max-w-\[7rem\]{max-width:7rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-0{--tw-translate-y:-0px}.-translate-y-0,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-transparent{border-color:transparent}.bg-\[color\:var\(--accent\)\]{background-color:var(--accent)}.bg-\[color\:var\(--bg\)\]{background-color:var(--bg)}.bg-\[color\:var\(--bg-grad\)\]{background-color:var(--bg-grad)}.bg-\[color\:var\(--card\)\]{background-color:var(--card)}.bg-transparent{background-color:transparent}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-1{padding-bottom:.25rem}.pb-10{padding-bottom:2.5rem}.pb-24{padding-bottom:6rem}.pb-4{padding-bottom:1rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-8{padding-top:2rem}.pt-\[10vh\]{padding-top:10vh}.pt-\[12vh\]{padding-top:12vh}.text-center{text-align:center}.align-\[-4px\]{vertical-align:-4px}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12rem\]{font-size:12rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-\[color\:var\(--ink\)\]{color:var(--ink)}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/85{color:hsla(0,0%,100%,.85)}.underline{text-decoration-line:underline}.decoration-4{text-decoration-thickness:4px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-\[color\:var\(--accent\)\]{accent-color:var(--accent)}.opacity-20{opacity:.2}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.blur{--tw-blur:blur(8px)}.blur,.grayscale{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.grayscale{--tw-grayscale:grayscale(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.font-display{font-family:Fredoka,Nunito,system-ui,sans-serif;letter-spacing:-.01em}.shadow-pete{box-shadow:0 4px 0 rgba(60,40,20,.1),0 8px 24px rgba(60,40,20,.08)}.shadow-pete-lg{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.bg-theme-gaming{background-color:#4caf7d}.bg-theme-tech{background-color:#5aa9e6}.bg-theme-politics{background-color:#e07a5f}.bg-theme-eu{background-color:#039}.bg-theme-music{background-color:#b079d6}.bg-theme-anime{background-color:#ec5e8a}.bg-theme-foss{background-color:#d97706}.bg-theme-kids{background-color:#14b8a6}.bg-theme-finance{background-color:#10b981}.bg-theme-lego{background-color:#d01012}.text-theme-gaming{color:#2d8a5a}.text-theme-tech{color:#2f7fb8}.text-theme-politics{color:#b8523a}.text-theme-eu{color:#039}.text-theme-music{color:#8a4fb8}.text-theme-anime{color:#c33a6a}.text-theme-foss{color:#b8530a}.text-theme-kids{color:#0f766e}.text-theme-finance{color:#059669}.text-theme-lego{color:#b00d0e}.decoration-theme-gaming{text-decoration-color:#4caf7d}.decoration-theme-tech{text-decoration-color:#5aa9e6}.decoration-theme-politics{text-decoration-color:#e07a5f}.decoration-theme-eu{text-decoration-color:#039}.decoration-theme-music{text-decoration-color:#b079d6}.decoration-theme-anime{text-decoration-color:#ec5e8a}.decoration-theme-foss{text-decoration-color:#d97706}.decoration-theme-kids{text-decoration-color:#14b8a6}.decoration-theme-finance{text-decoration-color:#10b981}.decoration-theme-lego{text-decoration-color:#d01012}.border-theme-gaming{border-color:#4caf7d}.border-theme-tech{border-color:#5aa9e6}.border-theme-politics{border-color:#e07a5f}.border-theme-eu{border-color:#039}.border-theme-music{border-color:#b079d6}.border-theme-anime{border-color:#ec5e8a}.border-theme-foss{border-color:#d97706}.border-theme-kids{border-color:#14b8a6}.border-theme-finance{border-color:#10b981}.border-theme-lego{border-color:#d01012}.glow-theme-gaming{box-shadow:0 0 0 2px rgba(76,175,125,.35),0 0 24px 4px rgba(76,175,125,.45)}.glow-theme-gaming,.glow-theme-tech{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-tech{box-shadow:0 0 0 2px rgba(90,169,230,.35),0 0 24px 4px rgba(90,169,230,.45)}.glow-theme-politics{box-shadow:0 0 0 2px rgba(224,122,95,.35),0 0 24px 4px rgba(224,122,95,.45)}.glow-theme-eu,.glow-theme-politics{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-eu{box-shadow:0 0 0 2px rgba(0,51,153,.35),0 0 24px 4px rgba(0,51,153,.45)}.glow-theme-music{box-shadow:0 0 0 2px rgba(176,121,214,.35),0 0 24px 4px rgba(176,121,214,.45)}.glow-theme-anime,.glow-theme-music{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-anime{box-shadow:0 0 0 2px rgba(236,94,138,.35),0 0 24px 4px rgba(236,94,138,.45)}.glow-theme-foss{box-shadow:0 0 0 2px rgba(217,119,6,.35),0 0 24px 4px rgba(217,119,6,.45)}.glow-theme-foss,.glow-theme-kids{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-kids{box-shadow:0 0 0 2px rgba(20,184,166,.35),0 0 24px 4px rgba(20,184,166,.45)}.glow-theme-finance{box-shadow:0 0 0 2px rgba(16,185,129,.35),0 0 24px 4px rgba(16,185,129,.45)}.glow-theme-finance,.glow-theme-lego{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-lego{box-shadow:0 0 0 2px rgba(208,16,18,.35),0 0 24px 4px rgba(208,16,18,.45)}@keyframes pete-glow-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.08)}}.line-clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.paywall-stamp{position:absolute;inset:0;pointer-events:none;display:flex;align-items:center;justify-content:center;z-index:5;background:rgba(255,250,240,.18)}.paywall-stamp:before{content:"PAYWALLED";font-family:var(--font-display,ui-sans-serif),system-ui,sans-serif;font-weight:900;font-size:clamp(1rem,3.2vw,1.75rem);letter-spacing:.12em;color:rgba(180,30,30,.85);border:.28rem double rgba(180,30,30,.85);padding:.3rem .9rem;transform:rotate(-15deg);text-shadow:0 1px 0 hsla(0,0%,100%,.4);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.15);background:rgba(255,240,230,.55);border-radius:.25rem;filter:contrast(1.05)}:root,html[data-phase=day]{--bg:#fff7e4;--bg-grad:#ffeec2;--card:#fff;--ink:#3a2e1f;--accent:#f2a541}html[data-phase=dawn]{--bg:#ffe7d6;--bg-grad:#ffc9c9;--card:#fff4ea;--ink:#4a2e2a;--accent:#ff8a65}html[data-phase=dusk]{--bg:#ffd6a8;--bg-grad:#f7a07e;--card:#fff1de;--ink:#3d2417;--accent:#e6553a}html[data-phase=night]{--bg:#1a1f3a;--bg-grad:#2a3358;--card:#2d365a;--ink:#f1ecd8;--accent:#f9d976}.empty\:hidden:empty{display:none}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:text-\[color\:var\(--ink\)\]:hover{color:var(--ink)}.hover\:brightness-105:hover{--tw-brightness:brightness(1.05);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:shadow-pete-lg:hover{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.focus\:border-\[color\:var\(--accent\)\]:focus{border-color:var(--accent)}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-rotate-6{--tw-rotate:-6deg}.group:hover .group-hover\:-rotate-6,.group:hover .group-hover\:scale-105{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05}.group:hover .group-hover\:underline{text-decoration-line:underline}@media (min-width:640px){.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:inline-flex{display:inline-flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:gap-2{gap:.5rem}.sm\:gap-4{gap:1rem}.sm\:p-10{padding:2.5rem}.sm\:pt-12{padding-top:3rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-5xl{font-size:3rem;line-height:1}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}} \ No newline at end of file +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}body{font-family:Nunito,system-ui,sans-serif}html{scroll-behavior:smooth}body,html{overflow-x:hidden}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pete-channel-nav{scrollbar-width:none;-ms-overflow-style:none}.pete-channel-nav::-webkit-scrollbar{display:none}.pete-reader-backdrop{position:absolute;inset:0;background:rgba(20,14,6,.55);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}html[data-phase=night] .pete-reader-backdrop{background:rgba(6,8,20,.65)}.pete-reader-shell{position:relative;height:100%;width:100%;max-width:44rem;margin:0 auto;display:flex;flex-direction:column;padding:max(env(safe-area-inset-top),.75rem) 1rem .75rem;gap:.75rem}.pete-reader-bar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;color:#fff}.pete-reader-progress{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:600;font-size:.85rem;letter-spacing:.02em;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-nav{display:flex;align-items:center;gap:.4rem}.pete-reader-btn{display:inline-flex;align-items:center;justify-content:center;min-width:2.25rem;height:2.25rem;padding:0 .7rem;border-radius:9999px;background:hsla(0,0%,100%,.14);color:#fff;font-weight:700;font-size:.95rem;border:2px solid hsla(0,0%,100%,.18);cursor:pointer;transition:background .15s ease,transform .15s ease,opacity .15s ease}.pete-reader-btn:hover{background:hsla(0,0%,100%,.28);transform:translateY(-1px)}.pete-reader-btn:disabled{opacity:.35;cursor:default;transform:none}.pete-reader-btn-open{background:var(--accent);border-color:transparent;color:#1c1305;text-decoration:none}.pete-reader-btn-open:hover{background:var(--accent);filter:brightness(1.08)}.pete-reader-scroll{flex:1 1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch;border-radius:1.75rem}.pete-reader-article{background:var(--card);color:var(--ink);border-radius:1.75rem;border:2px solid rgba(0,0,0,.06);box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.18);padding:clamp(1.25rem,4vw,2.5rem)}html[data-phase=night] .pete-reader-article{border-color:hsla(0,0%,100%,.08)}.pete-reader-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;font-size:.75rem;margin-bottom:.85rem}.pete-reader-chip{display:inline-flex;align-items:center;gap:.35rem;border-radius:9999px;padding:.15rem .65rem;color:#fff;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.pete-reader-source{font-weight:700;opacity:.75}.pete-reader-time{opacity:.55}.pete-reader-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;line-height:1.15;letter-spacing:-.01em;font-size:clamp(1.6rem,4.5vw,2.4rem);margin-bottom:1rem}.pete-reader-hero{width:100%;border-radius:1.1rem;margin-bottom:1.25rem;background:rgba(0,0,0,.05)}.pete-reader-body{font-size:1.08rem;line-height:1.75}.pete-reader-body p{margin-bottom:1.05em}.pete-reader-body p:last-child{margin-bottom:0}.pete-reader-note{margin-top:1.25rem;padding-top:1rem;border-top:1px solid rgba(0,0,0,.1);font-size:.85rem;opacity:.7}html[data-phase=night] .pete-reader-note{border-color:hsla(0,0%,100%,.12)}.pete-reader-note a{color:var(--accent);font-weight:700}.pete-reader-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:.75rem;padding:clamp(2rem,8vw,4rem) 1rem;color:var(--ink)}.pete-reader-empty-emoji{font-size:2.5rem}.pete-reader-hint{text-align:center;color:hsla(0,0%,100%,.85);font-size:.75rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-hint kbd{font-family:ui-monospace,monospace;background:hsla(0,0%,100%,.16);border-radius:.3rem;padding:.05rem .3rem}@media (max-width:640px){.pete-reader-hint{display:none}}.pete-reader-related{width:100%;margin:.85rem auto 0}.pete-reader-related-title{font-family:Fredoka,Nunito,system-ui,sans-serif;font-weight:700;font-size:.95rem;color:#fff;margin:0 0 .6rem;text-shadow:0 1px 2px rgba(0,0,0,.4)}.pete-reader-related-grid{display:grid;gap:.6rem}.pete-reader-related-card{display:flex;align-items:center;gap:.75rem;background:var(--card);color:var(--ink);border-radius:1rem;border:2px solid rgba(0,0,0,.06);padding:.6rem;text-decoration:none;transition:transform .15s ease,box-shadow .15s ease}.pete-reader-related-card:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(60,40,20,.16)}html[data-phase=night] .pete-reader-related-card{border-color:hsla(0,0%,100%,.08)}.pete-reader-related-thumb{width:4.5rem;height:3.25rem;flex-shrink:0;-o-object-fit:cover;object-fit:cover;border-radius:.6rem;background:rgba(0,0,0,.06)}.pete-reader-related-meta{min-width:0;display:flex;flex-direction:column;gap:.25rem}.pete-reader-related-eyebrow{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem;font-size:.7rem}.pete-reader-related-source{font-weight:700;opacity:.7}.pete-reader-related-headline{font-weight:700;line-height:1.25;font-size:.92rem;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}[data-story-card][data-read="1"]{opacity:.5;transition:opacity .2s ease}[data-story-card][data-read="1"]:hover{opacity:1}[data-story-card][data-read="1"]:after{content:"✓";position:absolute;top:.6rem;right:.6rem;z-index:6;display:grid;place-items:center;height:1.5rem;width:1.5rem;border-radius:9999px;background:rgba(20,14,6,.72);color:#fff;font-size:.8rem;font-weight:700}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-right-6{right:-1.5rem}.-top-6{top:-1.5rem}.-z-10{z-index:-10}.-z-\[5\]{z-index:-5}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.-mt-1{margin-top:-.25rem}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-8{margin-bottom:2rem}.mr-1{margin-right:.25rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.line-clamp-2{-webkit-line-clamp:2}.line-clamp-2,.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical}.line-clamp-3{-webkit-line-clamp:3}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-\[16\/10\]{aspect-ratio:16/10}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-20{height:5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-\[55vh\]{max-height:55vh}.max-h-\[60vh\]{max-height:60vh}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[52rem\]{min-width:52rem}.max-w-2xl{max-width:42rem}.max-w-6xl{max-width:72rem}.max-w-\[7rem\]{max-width:7rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.-translate-y-0{--tw-translate-y:-0px}.-translate-y-0,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-transparent{border-color:transparent}.bg-\[color\:var\(--accent\)\]{background-color:var(--accent)}.bg-\[color\:var\(--bg\)\]{background-color:var(--bg)}.bg-\[color\:var\(--bg-grad\)\]{background-color:var(--bg-grad)}.bg-\[color\:var\(--card\)\]{background-color:var(--card)}.bg-emerald-500\/15{background-color:rgba(16,185,129,.15)}.bg-red-500\/15{background-color:rgba(239,68,68,.15)}.bg-transparent{background-color:transparent}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-10{padding-bottom:2.5rem}.pb-24{padding-bottom:6rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.pt-8{padding-top:2rem}.pt-\[10vh\]{padding-top:10vh}.pt-\[12vh\]{padding-top:12vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.align-\[-4px\]{vertical-align:-4px}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12rem\]{font-size:12rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-\[\#1c1305\]{--tw-text-opacity:1;color:rgb(28 19 5/var(--tw-text-opacity,1))}.text-\[color\:var\(--ink\)\]{color:var(--ink)}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-emerald-700{--tw-text-opacity:1;color:rgb(4 120 87/var(--tw-text-opacity,1))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/85{color:hsla(0,0%,100%,.85)}.underline{text-decoration-line:underline}.decoration-4{text-decoration-thickness:4px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-\[color\:var\(--accent\)\]{accent-color:var(--accent)}.opacity-20{opacity:.2}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.blur{--tw-blur:blur(8px)}.blur,.grayscale{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.grayscale{--tw-grayscale:grayscale(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-sm,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.font-display{font-family:Fredoka,Nunito,system-ui,sans-serif;letter-spacing:-.01em}.shadow-pete{box-shadow:0 4px 0 rgba(60,40,20,.1),0 8px 24px rgba(60,40,20,.08)}.shadow-pete-lg{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.bg-theme-gaming{background-color:#4caf7d}.bg-theme-tech{background-color:#5aa9e6}.bg-theme-politics{background-color:#e07a5f}.bg-theme-eu{background-color:#039}.bg-theme-music{background-color:#b079d6}.bg-theme-anime{background-color:#ec5e8a}.bg-theme-foss{background-color:#d97706}.bg-theme-kids{background-color:#14b8a6}.bg-theme-finance{background-color:#10b981}.bg-theme-lego{background-color:#d01012}.text-theme-gaming{color:#2d8a5a}.text-theme-tech{color:#2f7fb8}.text-theme-politics{color:#b8523a}.text-theme-eu{color:#039}.text-theme-music{color:#8a4fb8}.text-theme-anime{color:#c33a6a}.text-theme-foss{color:#b8530a}.text-theme-kids{color:#0f766e}.text-theme-finance{color:#059669}.text-theme-lego{color:#b00d0e}.decoration-theme-gaming{text-decoration-color:#4caf7d}.decoration-theme-tech{text-decoration-color:#5aa9e6}.decoration-theme-politics{text-decoration-color:#e07a5f}.decoration-theme-eu{text-decoration-color:#039}.decoration-theme-music{text-decoration-color:#b079d6}.decoration-theme-anime{text-decoration-color:#ec5e8a}.decoration-theme-foss{text-decoration-color:#d97706}.decoration-theme-kids{text-decoration-color:#14b8a6}.decoration-theme-finance{text-decoration-color:#10b981}.decoration-theme-lego{text-decoration-color:#d01012}.border-theme-gaming{border-color:#4caf7d}.border-theme-tech{border-color:#5aa9e6}.border-theme-politics{border-color:#e07a5f}.border-theme-eu{border-color:#039}.border-theme-music{border-color:#b079d6}.border-theme-anime{border-color:#ec5e8a}.border-theme-foss{border-color:#d97706}.border-theme-kids{border-color:#14b8a6}.border-theme-finance{border-color:#10b981}.border-theme-lego{border-color:#d01012}.glow-theme-gaming{box-shadow:0 0 0 2px rgba(76,175,125,.35),0 0 24px 4px rgba(76,175,125,.45)}.glow-theme-gaming,.glow-theme-tech{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-tech{box-shadow:0 0 0 2px rgba(90,169,230,.35),0 0 24px 4px rgba(90,169,230,.45)}.glow-theme-politics{box-shadow:0 0 0 2px rgba(224,122,95,.35),0 0 24px 4px rgba(224,122,95,.45)}.glow-theme-eu,.glow-theme-politics{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-eu{box-shadow:0 0 0 2px rgba(0,51,153,.35),0 0 24px 4px rgba(0,51,153,.45)}.glow-theme-music{box-shadow:0 0 0 2px rgba(176,121,214,.35),0 0 24px 4px rgba(176,121,214,.45)}.glow-theme-anime,.glow-theme-music{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-anime{box-shadow:0 0 0 2px rgba(236,94,138,.35),0 0 24px 4px rgba(236,94,138,.45)}.glow-theme-foss{box-shadow:0 0 0 2px rgba(217,119,6,.35),0 0 24px 4px rgba(217,119,6,.45)}.glow-theme-foss,.glow-theme-kids{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-kids{box-shadow:0 0 0 2px rgba(20,184,166,.35),0 0 24px 4px rgba(20,184,166,.45)}.glow-theme-finance{box-shadow:0 0 0 2px rgba(16,185,129,.35),0 0 24px 4px rgba(16,185,129,.45)}.glow-theme-finance,.glow-theme-lego{animation:pete-glow-pulse 2.4s ease-in-out infinite}.glow-theme-lego{box-shadow:0 0 0 2px rgba(208,16,18,.35),0 0 24px 4px rgba(208,16,18,.45)}@keyframes pete-glow-pulse{0%,to{filter:brightness(1)}50%{filter:brightness(1.08)}}.line-clamp-3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.paywall-stamp{position:absolute;inset:0;pointer-events:none;display:flex;align-items:center;justify-content:center;z-index:5;background:rgba(255,250,240,.18)}.paywall-stamp:before{content:"PAYWALLED";font-family:var(--font-display,ui-sans-serif),system-ui,sans-serif;font-weight:900;font-size:clamp(1rem,3.2vw,1.75rem);letter-spacing:.12em;color:rgba(180,30,30,.85);border:.28rem double rgba(180,30,30,.85);padding:.3rem .9rem;transform:rotate(-15deg);text-shadow:0 1px 0 hsla(0,0%,100%,.4);box-shadow:inset 0 0 0 2px hsla(0,0%,100%,.15);background:rgba(255,240,230,.55);border-radius:.25rem;filter:contrast(1.05)}:root,html[data-phase=day]{--bg:#fff7e4;--bg-grad:#ffeec2;--card:#fff;--ink:#3a2e1f;--accent:#f2a541}html[data-phase=dawn]{--bg:#ffe7d6;--bg-grad:#ffc9c9;--card:#fff4ea;--ink:#4a2e2a;--accent:#ff8a65}html[data-phase=dusk]{--bg:#ffd6a8;--bg-grad:#f7a07e;--card:#fff1de;--ink:#3d2417;--accent:#e6553a}html[data-phase=night]{--bg:#1a1f3a;--bg-grad:#2a3358;--card:#2d365a;--ink:#f1ecd8;--accent:#f9d976}.last\:border-0:last-child{border-width:0}.empty\:hidden:empty{display:none}.hover\:-translate-y-0\.5:hover{--tw-translate-y:-0.125rem}.hover\:-translate-y-0\.5:hover,.hover\:-translate-y-1:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:text-\[color\:var\(--ink\)\]:hover{color:var(--ink)}.hover\:brightness-105:hover{--tw-brightness:brightness(1.05);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.hover\:shadow-pete-lg:hover{box-shadow:0 6px 0 rgba(60,40,20,.12),0 16px 32px rgba(60,40,20,.12)}.focus\:border-\[color\:var\(--accent\)\]:focus{border-color:var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-rotate-6{--tw-rotate:-6deg}.group:hover .group-hover\:-rotate-6,.group:hover .group-hover\:scale-105{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05}.group:hover .group-hover\:underline{text-decoration-line:underline}@media (min-width:640px){.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:inline-flex{display:inline-flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:gap-2{gap:.5rem}.sm\:gap-4{gap:1rem}.sm\:p-10{padding:2.5rem}.sm\:p-8{padding:2rem}.sm\:pt-12{padding-top:3rem}.sm\:text-2xl{font-size:1.5rem;line-height:2rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-5xl{font-size:3rem;line-height:1}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (prefers-color-scheme:dark){.dark\:text-amber-400{--tw-text-opacity:1;color:rgb(251 191 36/var(--tw-text-opacity,1))}.dark\:text-emerald-300{--tw-text-opacity:1;color:rgb(110 231 183/var(--tw-text-opacity,1))}.dark\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.dark\:text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}} \ No newline at end of file diff --git a/internal/web/static/img/apple-touch-icon.png b/internal/web/static/img/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1872373363e51ec8deed218899fe895e21e4d037 GIT binary patch literal 25986 zcmV*1KzP52P)j0@$yG$kdq1BS zCO~2HeOp@Xj#5ShL{KDXtiXBwNIfynZ|Oxxm^q5o+ur_mIFvNU;bC)xJ1}HinLUUK z*A!7eSiBcz5udYt0tGh}07MuC04Zn^2EY)3KnB2YlY>YOm3|%{pl4W$)~eBLed%An zIR9{Z4|02@)9x^fBFbCdg3byVz8;H{9C$7)yq^nQL<9(6?NLWP(O3Ead1AnU2#Ux( zf@_gfZZU@L(t}=o?(?22zyQnuB*N?wDcrpxAPki63fyCr78Dl}Z(;cnQ9@E>KXYuT z!|I`_w}^;>0G_!}EM51r>#o24dH~?S5B3N~sW?ff=RY6t!aPJE^ayJATgo)Q$VUL` zjTRhA@&te*#Iv1e1VsP@LI42hL3(iMmCcAC0s0`%^kX^b7vNNH@eKX3|Jzi@4e@qEKBDZya&Wo0buo- z)k%`HTJ3yC$XyidDTVMyf+8HgE`OKwA<4`FP%f7q|K<2kMrj z(v`F>SyQ^Rwl(WI88|>Br5sWwkdaALJWNpyOc6{$s0dgT5nu}-oU0Q6QXrwaH*7Z$ zu$sS!2mp89aR vw0ft8gAJ66Zx$^jdBPiKYtpDRvZ(6$?JNNC3DD!R_cn-J?J{2dz2G9uTfkNe( z#}A3l&iKPKXlUg_n`bK3YI^6#!Kdt6+P5_dkU~)ix>y;IatK~Apa8OD6f6(qEcSar ztjyl+_guy0RWPs#$2KTk@D4zHFE87VLIg_^QGgWy03s$y0*@AQB_d{j?$gda`Q(#3 z?aqM-lmlY+^Md7}ykvifw)eCU!O?5hJTWME40#+i2LcGJSTwM93Hj+xLIz|Gpe{TO z+!D@6QUnWv03>bWTSdT<=rWIQURhm rHOtYcD7*sTB+Y6ot iwx|0_HQBMbFN=JO1BKT0j) zGTFKr9)7Tzb>gUdKiu~z82^!0=mo(xMO-gb3c+PT0q^Wz{Ka3KciwrS=sbiqpa1;l zfBDN_S+;a(U!y#h>_57B3EKl+=_wCm4y~Bv<99S73; yEVyngl%pD<>#o zln6os;s7k50QkO-DL#)2AasFuNp~OG?Oa%c$ C;q<%?hZ?sva? z)m2wjs?~GPJ?Fe QXPbr!3^5-@ 28Worg#Y`0B`^( zpaDflKnRe5gI-bjNH+jsj QG}v%lx4r#)I6I!Ke~BG94ze!i z_c9540aS{*X*Z7J*Zt;izUH;B4eQ!yG}1IBMP68P%>f4?;6DL}NMLH&Z-@{ e*f;R8&@uV?Q@RXw0iK2 z *{dR~xIAg>Jp z%=7b)usQydtAGbqqcJln6(@&QZsiGUY9hqIEY2)TMhDl84n6PmMb9~7WND=hwoAsM zn`5jawG?I=Jc%O`6ok+sA5^-CXY`EBMqohcWja}|lR*`gLB+rT-m|#eV_^s4MSMu@ zqHzDt24MsT;81zyCirE1+UsTTR2CJQDH+i*^!=V(afEua+?v_Ht<2XG{c;UBz}g7a zVu1#Nh$u;tZl`_TGoH46+ty#MOmHFA@$rsx^gGarNE}Bj1*7*&&s}%#)_u*`rCo*f zM$_*bkGgiHj*s@Ttm}q4E>2lnue|^>GK2RlEFeSx1PTyE>fO$Rt+_i}GY@p je(g2aFf$e;ieN{DWV2zaZ-4_j{GE&s$*w*qF2AR^v*&eOZWe+mzf z0Yy-l<|E@EBXs^VFfaB+VY38g_KOw`eEVD9*uHJ6*5=T$IUc`lC2XaB9`%(0^qQ=7 zF{qEe XCA}YI!=fGqGwaVg*bB6awYI61*4$ zg@%1tY) za>~ZV%f|+ZX1iPmZW3ln2>^)g5%RG(wpQv4nPi==?L=A2L=_X2m5zx}#0fA70C*P9 zAr~Xir`%vfVJ1L!jm85%85!PS)d-5meg|yNX+a5A>H%)*yc+aJ^v=xdc<7rT!u|)} zzr54wgk;|1up);9ID*c2`O9Da!WX`<*=%y=5JV$`7SYA-_8-pP`Z=Ncu6=ytbu-iZ zOd&Fs*?LWc$es|G7(qyp(Um>ZcGpk$U3>G6b!!Gs-88m##ZbA>_E=Bd97vbIV;~!6 zU fRk#fQUBuK K-BlLm%Z%wfB*MetrihFn}K*FpmfgP_Boiovps{i{;WPRQAAy|((xXd zLt_E;z!m-@BJ|#iIEAF**t6NZ-EP^ovr@uUD@(_&8(q6 t*|M?P=wNDUt}^Bs$vGj& z`= %NBuDtg(X;>jnh7_8L>s#q??B&3x_1f>jvc3z37EaT~Y^+qe*w!OY{d}glRsn^@q z`LWUB`n9Fn01T9CE0>HVCDZIsmaHz6S6XDv8F~}C3l8eLl+brM&(1g~Ob(CmR`&Pb z)~a9r;y>SZ%Z-Ri 6(`ZPxWO_0(B%K0 r&RF1~z6m#6sc$ zh endhVfeDa-DA*CGL^QEQMufcYi2+a)2t{Nf5|OTx z)->~Ed&|R3kemkr^3EkBG&(B9F-1xM?2^U$*mb3mL0vAXk-?EtGC&f0iY;YOT2G~A z850t)u=j$H`=9*=Yk)lQ20ix`=Bbv0LZUDaCCofJI{Mx3e*dbgt^xq>AH7}bNe+(5 z$;mWLwNfI2gpkdcz1NYL2B(7HtST3lT8_3g)zHoKr*+5|NMyi4Y?xcC1j4Ak51W zMrIbJuxA~H?iK2tTD~Tfw=)5NXpLIAB$lJr(DJ3KQkH>QJY1_7OvK0D#4d_68iRpN zfQBR!QNY4L9&%=td4=ZerI)s!M_3Dw*h&uiHSkou=YPGc*_b;tNl0Nqb1T6y`|yW9 zJUu MfP*H?DtUmbPSKYGS0abD_4H~8(-ag-#uCz=0ncf2(p0S z5D}%+)YR1HKKHp=ZNNDf$Ql7PcqUXwZ;Tf&Ejh11DL^7pn5Rq>5h)0+3Lz>*T9MIY z3{gaw5G9IYMTt@|nV58>RF8ZMdniGrP!TF3jY@;kKt`mXaqZe{(}{k`5<4=)OO_5+ ziUSG?NC~LKt5S-?Tw324I 2+crg7SEz?7#N4uYKVQU+8wbNXny9#R?e%8nx!di{ h4C{*o6@?AsTp0(!}mLo31&WC1ltP@!*s``e%OtY_`nGw!`F8IvOF zVgWi@WzIWh*IvFfu2$n)Z*ARwpHHnJEdW5MfgAt=lfXPfPeP6@kg`y4AX$Wgm2l4Q zq%cIafrJR1L&EOS$EahDj*_?=7r0idM!JX+BWeI*QGf;ky{=@VC;4bdfPge&t`%}W z2l6m^&>Z 5q9lvgD7}yDJ_Q|A&ikxdPR=>2vV5_<{f_$hz8FLU0U #x6=n9D_sf{;0KTY*F=|d)$5ZwdR@% zDItPTh6AA-BZo*>5MW^v1QZp*BE6+xCQvBgguBBd*>RiVk;P7^Q!G}rj+qf5LW%?< zAtho!MIOnPm&(WnBBKbANH9cXd;St3;Q?Fl{?I3c;zj9o$Os@xK6wy=j{^WpmMr?y zKYjmQcif^);%s{8BLfz6TlwQZ{^Ov8wbo&to(N%7=BNb9n5arn2j2p)h#kb(IVcr6 zr=1!tUtWChVY~IAdZU2=1w?}iNqjH}19`NF5F(<=3xIPfA4DWz@xEKEw2nC@S+~9z z73y8 bWwzgFb|aMp9Kc{$xM4x zrlD*)ul>oXg*>wW#E}^otp4vm{gY3B^5dwYp3XQ_XLmh5;20PfIM~q-{zQ410)r5t z)JPa$2B|B$t;u*t!mXuC+Dn!u>o-((?}CT7XM6T_(iA` $&6 Snd{*_R3Z9x^)9f#w_Y)ma_Aaq6kqjD?kmX5hEspr~wtB$}qOo zby0aGi332N(@diDS|LPao x>`^2##X0C?bKnH4F>~5HCV;O$v*-F%DQ@WPl=qLum_i5v-sm?MXz~gEt8z zE0&mL%XByMbB%Q01k6rm^#(QSl6H0G8Q3MURH|xVkk%hFuz0Ztm~Ayvmmy=s7$GKr zh@b*341`({B1A*NG;n-nQx) zLmvtN^t+exEmA;0ys!{3GNNcwu_??ono+qhJ3amR&wcu)8*aMwmRsi&AV1No{7cFL zg5y)4`qU+tT(V@zl70L3iFjrt5kbsjk6zSkgU$Mlcc~01}Z<7AWWeQlTbc3LuCI zxgcEjE(TEwiAbUtDuoD%ym;@NO}RM-X(}iLl%OJ%h#_J$C=mh)Xh1Ef02PQr!CF#! z=+1j@zTyVgz2Rt#hA;Sy?%bRd)Skw~j_F;?hL#Lg29#D>o6u#$9A X@q4yx5kYWG>pd@tMgF!JuDwH1P+@eGxs(`9KIdSt 1Z+ zL6c(-8@Mb8IJDNzx%1CI{}2A)4`ya&vMfuIBu&%MC9j0Rl(}}4Z|qgv1vY>l&;uwz z4H(FNScE{x0}D6?CuBVcGkBj<(lS~vCF^1lB4q$Ih!RmC3IG9#5rq&CJ%NxQ3V{+( z9+NC7O TPq~A()7|4Qv!#Q`> zS!aFY8{a6EO3d7BHm$Xx5*?V8cmVJt$I{;0bO$^b0Vg1mL&PHqNC;*C2n(YZW&tnW zhxS7uC%{ZBMnHqoOa=m>Ef54hg9HeGfKh}H6q0BX17cW}8l{!Cb$f)+B|Uofb5@>r z{&f%D@Gn38w}+auIxeN&6CwgTn-81KGpk5|Vao_qw&J{#rp0bo49%%lJ)-#Nf{yg) z>J@7)+Aw+hc($!Ianl|Gowc&Gxu5qQ6-rLYf3Q53oBQ} Eh zOC^XXB%lBgh=d4{gaR|Y0HTtJBvGP4B3m43HPqxpE3;5K>5O$3UUb*=uCHA4wW+zC zC&XsTxA#yYB#Mj%>}Opu%=<(T=;u-MGIpR)A&;0sx~Jm|rG<3P%oUF*jBOlQe%`t$ zPNuf+oqTw&Z@I~ Yy9*l<2$xm4Fe~hxjCKt(pCSuV{X^87LUBBu 1F?W9Hl_LsT )3j&Vc{_WrHbUIq=ewR-0>L|=<)(Vlluo>K-%XiD% zRzLws0W^ar(1M-;LThu+kz@g3?4>e51>7JZpbGryVK68FAt*o+BoG256ir|NfD*OK z+8^$jS&=G#%~c2)3@zGbI$yo|i#wXzOcZP8!AcxAnG_h6&r#?ppKuy^aoT=lV4n>= z1n`5iiy(p_YLc>ZPHD`VKAo~Urh2& E^{i<6-k-dd&{B4 zksY&R^j 3QgaDgn*z?5h;ua3q(Z~N6}zZnZ0Ln7)D-w_N&$nAM=@yee$~Nt}7Kwx7~J2 zsNIL0_SF3+hR0#kD?2_uJ}@v4lCgbQK(DM)L_m-bkUWlVh!=0jcK$?ZBg`nwz`%mw zkpx))y@WX!`B;p8M2b;z(w&ggN1%MfFA$(&P=cDU0FZ!AtVzE3)7yW#JzKA{?#|s9 z7q$&WKO2>4u|+b>tQoqf@A6tHBqZ!7Ofdh}phyH!P!#kEUFS8U
p$`Fk4I6o zbLWm kp-y)``E`mR;^aU?3_m}u@nRn5J1+|;z<|z(T!qLqL@G HGg=+x8vz-Fx2$|Nj4!N+kfG zU}GJ=;JEqbn|JTtU8z*&H}gPll?U?+1(*>~ffR=-C%!nVt?|x@iUbURK?=7%36T&1 zDafbv^~bIf<^>j%SKA0lsO*A>!5}0E1;HW|3;*wD+rNI@OvM!2w$o7VnpN?wi`1@? zWO+eof97y*VFru=q0CFohwUWHrw^zJW=G!~k^VGN2qw@06Mhb?P7lQF;(^f#vLb6P zJmuL(S66D!T1VO_t#zy2?Q}90*2cW+_ug^-`4{Ae*2~u%mf*PJiYqR-;DYae|NC0& zpf x~(C4q3{?0Wsg{WwJycidy@$gNHaIO?9)Px`)o}GwC zl-z 1 zd*;zkebMPJAdf~V9m-}EMzJxGMg;5q#Pr-JKl#a>yT*U#EpH7bs#5w<`yLK;aI9Lj ziil=sXX7{yiP_9%?88_!;l6iBD&H2YqoMT!XZ)6}u3`41%7SI2GGPU&01$&j0Fgip z7y%jp1EdiZf+ma*6T+A<0*HhXqC_ZCCYq7t3qPE`>}OM|P|1*%M($l3n{Qg%8u3xu ziV6-A$jyP?&H)r`21F(Zngamx$&&pJw%iyc$n*3;%>WW02?l=3@GI``UdjJz-fU4I zM531OjwHixyWqE%;4q1hp)kWb{E$}0M4dEcgz`Xb+0w;-|M!3Qt?zvIjHjL*l+c2z zkOlEp?s;(fgcDB4vaHc)AYztf);g_CoFu`f13{&AVxnMVu`mOo0-1Sm?(Xl=j;j?3 zi(>ZdGxnAxW9f>wK_!bYATu+FMlb?KKmll>NL0jNDuwC;34GyOdv1-LWl&pT`=^5x zf@`t15NL5)f)w`#DaAdwyK5mxptwWP;#R!4JH?7qifhrL!Gg=?-I;x7_rrf?XU@!- z^Wi?3%$!M{b3fPbx?2nE5^J$jul+d1^KM3;3s-VFeaWLcEDLZ-DGYK!dkI8V-~dD4 zN&FBL`MkTp9T*v@x-Ln$MLc<+4a`%|GmZj~
W%~D9OzPUcgfH!CrQktH#hnoSSE3lIu&crb7)c?8)GnJ)6B(yM+om zO&>6KVj3!zR)>xYeO+Q)V;jz(zASs1tPTsXyjbB$R6%>zX0tom@K|c6*R~^X)WQd? zD>=E}v||tT@^`yB9Itpui5~$@%vI4jS|>q%_pkk0zckhgtx+ok!my(uH>bu`d!#;C ztfO^$=Rm6fDo-U{j)chltR1J!`=Gh(OSMKgF_YFtyQ?Q!pHH1dCAP+>9Imwak5%%W zwa*LERwBsJ^K9fOcH?rB#N9{`OA)FAx!U#m@}`PdH6%z(`sT%L>3F=th#DC|4AHBn z>^7$dHM6x?9g`X=^+HBd4gn594k(8Z2lt!kX9H=hGU3lD-1BB%y=)y{>S{SyR8|tL zFN!bv)T2HJ?Xoq#yeGIzs##m(cAg&n?X+feH%HfmRh!U6+jO`fU2G+I{tSeL+fREp zHnB>BL2?s)pdM3^A<4P39r1Guq~cAB&6=|b@30|6r>aFGy?0rk NT1_a0u<@hMc% ze(OA2ojRc9NL3saYN=|u&$Ii+DL`oGb(n}$J=fbrcHj)mNLkn79dR)PMF$r$guo5E z_14mlT%q}D9P+``syLA#5YmwsT~d;KU>PlC+4}h&!M7P*uR8bX`I><9-{J-)DZ+!n z^99tvH2l6HUO`c_%E}q6_PX?|1TNhd5OOrLe9Q6 4TsHfT-&VGq)_;wRerKSeCnO}uFH5#nRfJc@QzdHUvs?do zo&1w5OIr79t{M$yw@<+E{Ay|z?+Tl&(c)~!Tv %Ql*p(F2wb-1-_n_e3 zUbt=J0_~cpVfk!izgp8$1C(OWg&c*rTgsf~JthK+nV7X=9&b-;RFaM3%(?gYi!t(N z+4w)4Yh$X+^N?RW_qxB%D}A+HL)E7pq0_&KPgE4+^c}hQ^4;921bMQ^n^j8guJ>6m z;Bt}$f}Y7$mo+9?`lrD$&CZW`f{~+htqEIC@m4Q$C%X!AcC<}NI5dwXMtrrSnZ_$% zx8n4Ae&1+CTG+v|zPB2=YikHh_^5(MP09|)&N_EsP>pz_1BrqjcS(t>a95GHq{fl} ziZt3*pt{iexT^aDGVRPX3kBSBfC}d}6FR&sYjI`sj)82@1sx#I%Ei %?dk*|8vudPG2jRF29BgBN%iIGT=J9yK^ywIPd%dA%&M4$?Q-+w1d<9s^lS^e zS>zED?pFYqUGl7z4TMYe#>2+OTH<+3M&%c;^phIdnZdG^7fty3UR|-#rc;F~VYbp( zCWe|7IWYG^oF*kEvKXD`x(p0k=Tv(0*l*~7(!thAS0IKo9Qyr^8lyhmS+@x;{xnzu z^0CAY3n2Yx&plO(@@bR(>1->x*gS zXhXszfT8uEm^#_FOHB #0wJB2Ae?bkinDP_Bbe4l^kEde@M|MR?UVs%nVL%hA?M8(erT_{`6Z~ H9%GI_DJ4|77PM!UNM|N>sYJqXT!3kHt0XM> z6&aPRKOQI9pKha*i;L4I!c;>AY^yY5ML`>XMwe0BGba*b)~`6S!Fq%w1uT*IEdN$~ zZ-wA) KOr--LV1Sg7q|Ie1ccKJ+cX=%0<-l)`YaCO>++-&b-e!Qg~Pm|c^ zu-N)RZwC_~!@N48hY*OyFuH3F4pWUEUtCNy;qCUojuq^;A{weQePn_dWy|xk`yYj# zwT+OauIkM 9Q@s}gv%>l9*waxua+20dd-Um}~Tu1D0hvopVEyUQ_+ zw4-nql5WqsIxj@0d`sq=kN@6yjBm??hF`5xk)c9PZJQn1@{AuPS<$bN$|?H9&V4Sv zH`}R3sE`%HdjdwmY1r<2bC#Z-E>qCMu`SQRu*yTi4O<&gE1N%Dt~vkfw^UOi(&{T> zma|=_t@+h!YL`a|Q#6;X<}k}@!@Pc@!iqW5d#&hAUSv&zs)n3n#nNf%UX&JL=N>w2 z*MLah1+iWimjM-CoEPJ{Em%? GGn?pVfL#@|KAaaA zt6$B<4%{o4cw=lIjx8 4tSfj%hAtxczj1~?g&T<>Ik7lL7tC_9TvYW^jB@(|lAqu$uLi8}x=6`h1 z4EkZOVL-k7)>7 ueGN*LC;d@ns;@?PYf3P47EldZVG7C?Ha2x#KzsOS5XA+mqn(b45>siAlQ5C1 z^aYR78h|lGcu3s~zY;cn8mysHf|A%|EnY6L=iEG|b_Q(z!_9Ev%*tW5kvMBk2Y61~ zFEu(Wrs+DA xL!ZU@0 (Go`M4}_o|TGAvNpzmiy9S6? s1WG7}&?G1YAF9|W;=8_~Ed@o1CKj9uG9V4^%<2x8mX#xmJV`TDFpbf;4 zkieldYe+9!^73e!TcNPp|Ed2^$&-hdkG@Kh+>B*wOC&e_Y0>L7;~%g(=L3Dx*LDzq z`+4bnR*}8i_qo#n3+9I@yhmq68-Y4O?&?(jgk|z)0ENn>PO}#Pd_n^69f_lrs^QfM z-^euCP^p(P^Hg 9rvG%?l+_=Hl;!)?uc%)^79%u@r ToggQCS{*m zDd#vek+4`YYMAD6~q{3!*RRK zoDP+o*#WM6v0zLfm^lD$C zPfkxOO}nyWrMH^G*!+aw6t^AAMceKBG#E~Uf=x|q2O%lY-;r5s{zqEE*^S@z04hR_ zLFLJXhLCQzACDMucz{n-^V4m 6|e>-R@rO>v%wREmj=NC7h#`HUc9yT^^9fzo?Ef`dUU&WwklOdpDwNQ)ms zm^K~OvP!NBkW#fT)N@cT#q*>Pk@)Umx#5_v9z%`Bj 1DZoJMhXmS6=Wv3;0pjdduh>Zr27T{T$@33m;Q(@O+n_Q9&x+3LHe2~gs zue7eL<0arvp8bX?BO~j7HhmzD{H6SE(p2=(&B86c%3#^|IMN%}b-(8q#-pXU^m~b} ztY?4 7IW61=?TQyqrG3z+ za%^J8MdjML5yjdGb55jt)F_!;I#wOs2d|*qy4Wn@vgOr+_ObUTCD^f>{#bSa)O45o zS1RldEZp*TA3g?!gp*p}L*B>)M)(D-n6^Qeh4uVDf4`9EXzoBc)>e><5x5`h&;UBV zEjpbCjbj1lS9IA9?;CI{3UYx5$XNxmA40r3EEIPjU^O5;HSh4fTd 5-Ga*-x(WoV#Vb7cmt1(A=89Fj@gZ%*atPfIwRF*ARTdDyqaJO;A48$?7ZEa;RF- z*8KKZQluo;j;0_t5fD3k|6?pYMk1KmkaRG?i$*}r8lUE8go||Y^FnAtmg$^~&G*{a zqcdMpGx}Y9RZ3LeDu(WUQa0m=5ERj^-|_xEk3pr@roiRuge>y~>D%SanLaTtl{mfy z*qXvk7UH(UZNks8O;g)8FCHySNPsZqMwc-lzrDV8qfqmQLvpTZP^`Fx`Olo}$F6Zh zkMBCNn8a*iAK@tNjzm4>+Y$klmRuP%!E>L-Xmk~LxYVd_JJu|KCNPOOoZ@$3K9I~T zI+5i-Mo}{X`C<;_L-A{`J_6VPPDN@|~_0SfuU+=Kk5F=Bk^&(soA3u^8omGP+ zm{jfo<#IDi6quDKfivtQ2e+!r=UzmGmRI>8*-dE4j-`gkEZy!~gVVFTAVGxs-x$-{ z|CTxlwKhKHZyLG$e3(P*9WtrGC4T4p8v+DLl9?zSzkz#$g?Gb#uJWo48{mx|G)s6J zOOS-%QvHI37p~aQHu?Nhtnxp~np?X|<}@t(MfEBQ;0zi@N~=HY7pvgE+~h zi;`k_NeEK0vRwuizOC?#i#~J&T*sPL^p?m==kl^hv#2PrrTXm-uRW|>`ETWj9aXXv z(1)R4GO=%AX^~5t(BLtIzRb81yWYtIyjPqqBu*HfR>qUf@)!7mf!6Dnq7o+LhFl=S zR=OUWUMpdhdL_7P S?JBPtGd!b;Hkvw5$F3fx=z7~HyQPNy;uE3)(~p0B zzB!DXdwCb>)wM-58&mQ8LiHtDGg{e09Nt=IS^cu5LlKVVKN|Yn$%I)t&gL##JgGp{ z>%7iHVkU|+xR{E1DV1czk*8UPzWa{!ZjNi9J80_H^5Nll77>CrV0{(l G5MpZROnw&2CqG1o*1v9I;3L8)@CvNTed>okMNP&@j z7~<(AhJ}%^Wx6}zh0CRuj-bGJ8?$lheRM(47dzqPbb=3qnE}7ul#IMdErz7z|0Xg< zt=`EZ?q-|bNk@l6N_&4Z+G$scpxy{5PyXsj>MfONAhE?SQc)>p-!_N4aoeuFcDfs2 zTmeg&Rw94rX(GbN-@8R^__zJskE~_0JYX&H!RNFuAJ9I7<&9vQGY5PQuIc>4)mFQe zKv*qwj{W!MwD8lzH>Z{6quV1lVZC`IjS78fW*+vP%U6H>AN(mIP8q}|BnK4}R250? z-X0dn1=%dWfqixC4c)~Wd?^(6v^bu%WOTK!-MHkCFV~F&D`JVo4hz-7Cwi;KgcjMm zvU;pgsn>P*h3 zbB#REf3^BOd`U${NpPdMF=t^LszMto#%bQH^u zgKs8;5j`ss9`86+O!eJS&&jstBYpL;>O`fFOa7Y=aZ$sfaN9xWDeQRF?P@mNLAcmU zvFUqT<3Yc-Wuf80fj=F!MEF7ogEzr %?~n!XMpkU| zj$w_M?Y?X}%ru*D@@VC$Y%y>Xrp@# QlXoA*8^< z-t2k*vphA0Vy1*N?^pYQB`ZB^5gbkx4(8s$*49S@^mZ0|p*QHu?X1sMHcigzbvU<( z@PWa)RTKJNEAJ7HU%bZoDt@b0O{`B*d^}KUtFuIwNk>r}Ep+G+;uCXbzT8>=_l(S` z{dB-jeL6%+67ANTXJ(!U$RZW;4eHw#6)s&Q-|%<4A#XZ`4$*Ff;7PSeN1{9g76E=F zphPX)WK*mI5uO4{yj<4&Ay6KZ=|qQII|`%ELB`?{2@Louy#xc%`7-WwE2&9fKRxC| zPu3e5Kcy7fFn`>b9L>23mFE;0rPYyh_H0e? A=ctk~f z-HWHkm?2mcAO4kF1Bi<>sOC0dHcsRc18Exqu|=sY6V$|LI%gD`*c`#pK`f*+bKsnr zf`;nz^0|tZ?e7PUt$Q*MAIycD_EPwRJXnIE19laQm;F%jsp~SPAXbQts0c%X6G~Sx zts d(_kSR)8T&g>Y+xF$CDPC1gMDz)Pi_U9t}jEz7S)n7uItp z%qGm%@FmbI%Ci?eFI`45N-tWQMXTzvOGhTD6<%V$ff#=_-}?RO>1d|PTM@y&!vmcd zVHoEDNpsZkF!@~PVRzjZaT3l9A9;@4VO&s*z8 E7j<{ho$DY9WTHJ2h(3w3m_nHAF^8j`~z>4v~xg3 zYty!h$|l&T4GzpVi#K$u`Yo>$8M1-m#*Oawwf!E-jrf=v`9z(QrYGK0U6m6vkOqG8 zzZ-M9u3STf_%<76<=HPYp)YJK94?2Wj680xI?uyPukN~z GAD)Ew Ci1 z7x`_+tr)}|L~dXFRh=)^LLoz=AM-Bu=h`om)Rk?T7H4&L9}*cqb$ad Nkz$1N31^$~(b^7;@_ zLCW|*fr*Ajc!cP2RV@qzN9GsC %+>1YD G zoe!VR{bxX1cT}NFm;eFvSfgul+8Qtb#8ywnO_{irj A7N8;@@vzW_f48Oh`QWGFi7?dH1022zp zjilT}qzY3hj{_D_*0Z4E6RK1lfC@XhC3Ajw+Knb>oAva78K=+GvqDoBl_}gT2T$Rw zNid`&7+$ns?em;dw^N3EHX}ydcLKJvv(LZgs-AWsQwT^{7}N8)PkujGfHDZ*4;P=z zuMZi`%HI$>V1oMo>S~gkl%IPATwHE$TwcDYvR91JLuX#s+){)Ry~}av=~`}dThssP zExeS5ms5pWxn6lb?oJ4Y%8FL!z_y-l%Hwbey|;wp-mLo5@A2?OB56^0IXnWDfGQ!E zuZ0apuQ0^C<*L eF^_05n1)w*xgpY7ZI_`g}rH zf7iTw(q*fL`(QPrN|&j#lLdR1vMA_}2+?)T^Q7+|!FjvA{SiNQu)PCfUac!^<-usX zf+s@8D1jB2->egXYY^PA |^!+^xnZd*MSaJ#}1cw_EU+>Aw2jMp97_a-^8XeO}6 zm5YYupDX%ya5+w+WfNlF^TC^$G2rS4rHoW`95n8{FzF}N%R)I&^aqoFPr-#6+1}d{ zAsPGv=ofe27rqm=jeq6^$@Hb2P3G6sY=2_^G^|=l)xQnV9x2m$o^kBG9$22^=7U76 zAxSYYahRe+9JT(Ev4rI`k*ci%yR+lOZBGYgqE^ptwaG!!$91RK?CcT=(t+jwKF4(E+4lt!-hU34D%odvK_5XPo^4bj~7`Ju*&%oh+p^gWjoAu zuCpq0c3vS7_ub-66?X_YzQD^KH1CiS-|L4i jr1?{ZXqfg_-i?pv@Sx!h*i zaFqCctDRGN>tRZm7;>e2XOR#y^z}nZs`!e6!Sk=VaMi+g8pFn|fv^Lk$Ay%*`%9da zUn_O$Z5%w`-bDfn8{3>r?-V(O?t%^`0*1wn{0&lXx-i5Dm7R&vKe>74^I3Z%FdS{; z@V;Wd{EA_20*LF&ry41dR~C2KN@sSw&7!pT{rmJdFrFnmA@0-P{YU9|Tmq;2g`^_z z)7;yeEVtOlO24RRCJl{v^Tw}@Z=@+E P)^3beBj! ZElwB&m0=8ZxWG*mf@8oCcj!6SH`}p&$-fyyaswtQBzV@33<@WW}ojU z`()?vz&5t!g2@55inj3JC!g&~qkDRNm%HJsF7QIV Nm0fe6dIIDTK$2X!7{<~ zb(8|>-Vh(+vXXj2Q4xJHkzu=LdLFI$x&9vt&%?~C^wjt d(~&*>r^muvD>Y9nf9VV2}*B-oSmHt?!1h3QAU`;|NT z=4~X>-Ao(d8WqtIJ$luFPqVn!Vt&TXPE_<{P+nIyhyNp{4@VY0Q5x*TfF-+&!w!WvW0mnkDZ4FNl_`W}$3kV>YB~7m%5r0@2 z@PqBot#ZmumTw4{y1gW1i07nA9L@8+k@>tGQk+xsSD*>z2DALcTnVdTfa{w@Q!gZv zh4;kK(`~!Rkn18uzW{}LCM~s?wydoe3GcUl{0eyZJD$;`o)!e|s@#eYLxd!wRD`z- z_(R6{*|*W#U_Itw(~mX0zI;apl$r4ip#H oq9Qqtkx%}y-v{khNHUI zsfK(W{gtrq`m?SQeJnTaSOV3F*T2}5`J4d&kPiFRN~hfo^wVJ{4xGvPg+67)IblC^ zR#OEF2TqQMo5Z8xu=lUzZGYZUqY@&^X1PN>;ON$mCksm=yMAvfmaaNgq$TDzGrd6g zB djp<}8AraY;3ZvP^P0x3&5f94GTJf?V+{n%(Qq0_aKPxg(uGU6L_Ke(>qtAzpQ zTkgskyI9HL%q!~hWDFts;k9-*hS4~6Atw)nUDQMUJLQW7(d@G~nAltQGhk8er+s8o z0P^;W5vDapz0vib9 mh`^>3D*AEJK6i50A(n#_GP$;;1)PGo;D#Q<^RX6 z|NqGNpXk`G@Vds})AOj25sdEAI_~Bs?iNC3t`?{hfE&!s_ZG~LIyAt1Lc9V(T)gaH zun-u0Rv3mdg#BLy4o>Damfrt!!IvBH{|u#I`R^yV+c;RbxtlmR|Iaml4ry^vHBA4h zK@F)8GIMhFcD1pxb_Xaqf&P2J0rGIO09m?PSlD|Pe>(|6RcQR53UdoLGgljDcN-^1 zfUJe1g)8bynS<11rT#ks!_w8s0fd^P!t4gJl2nugu~?ZnIGBLA-*JP%U{;Wcqxt_H d%>gpA_HeXwvw4UZLERtlQCbP|L&`Yle*w`ARAvAG literal 0 HcmV?d00001 diff --git a/internal/web/static/img/icon-192.png b/internal/web/static/img/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e548b0a28b8f5116463c2cbad85af8587d9c3c GIT binary patch literal 38154 zcmV*FKx)5 Ek^mq&28fXoy<+ zIW#oa8Z
XQ14q!bmDyq>l3#g@vo(PAerR5dL z@d7)BXd{@B6+;Wg;}LWn@At3>e>Wv^b-(xT=u2k|1+esz&lF9skT1xQjfy7tgX~iB zavHEY$sU~o$pH~hwOD9# |20@$8_>r(rD`3MNSF 2^001Ck zNK#Dz0D2|>0Dy!50Qvv`0D$NK0Cg|`0P0`>06Lfe02gqax=}m;000JJOGiWi00000 z0Qp0^e*gdg32;bRa{vGU0RR910RT@W#MS@+00(qQO+^Rl2L}c)4rq@Ns{jB2>v~jJ zbW&k=AaHVTW@&6?Aar?fWgux|V*mg+jh$0bZUrF-{Pz?+0s?{_XIFQV-v0sx(bw14 z_M;^+#sP5{A-(sW-rS@@0yjLWbRii^z&s%taw#hoWMxDswX|{|fL@2W3qWqMBLUEv zBnv4ia4v>&hkw{@uR5c=fk8MP%>HQUkMXCl6eWp=GSq+%PF5HwY6z2$RT_6yQwTPG zuM3%tZ#6jrM_BRLlgT+{j5B>+_=0ck4A*B|VB?;M?Pr4OW@eo2!q50G@yV$w$_PhY z4rKd?u0>u&-yhSZ$W=;N{#uvV*{GW&Al4Uh#je&gVi*0@iL;Aht%DKE7-#i`mPl?} zR;*ZqNsmh&;Mh#&{;`tyM_iEzZF!r_ShYlKukwf;XMOGnHlARv 9pg%h%MN3256mouEPMy*7+H}8+xgGGM*DX#^Inb z6BX9eckX?TP-|^cN0FG;yY%z{SYRRqFop{;004jhNkl0_x8O8_|Bc?ZLKf+IAk=RzX`pKtKcp!yE_% zG7tCWCU>6BJyT74?|1n9@xFUkopbL6L=WLYR-Q`Lsj9oHYVWn)cMae5U4pd{zedMB zM?}=8KnR8RmMebXYPP-R5>EB!k)^ RIjwox??0EKos$ 2e4X-5amF4^ z39GVT*Hstr#5X^iJ?B4w* Hb7Ob&2=kU^_TM5m{CTY6?V<{rjAO-ny zAJbnX^_p}b5uF0Y#@Q&>@#R0c6>$n4`Ru-xUqZ=n??x_%h_Q(SL>;M&{3Abd^+dGg zZ;H$Lm;szik|YUbS@Mf-eiN_$<=5~Jdp^Wpe(vp@@TYLnBTA1c9KuR)LB~8Ehs8;U zQ=Vp)G1hL;Xl1DJ24yn@&So$ma|&BHe9zHv2@R8R^sXa_K0NZFJ@}rdd!QS>j1XZV z7g4!=umfLR{n6dvOpm`guI6Jw@VO}ShmWy6*~s_q_&4mD+Ri&Z|F?X8{})*HU6hdE zEka0~cR1s4#$v6-;^~VLm*kjs6FoM-wG)!@gl20JA;BAu8#9!HoI$?C*i)u)J GDpb~|5!3Cr8az<6`n@922R36&_)<}d9G@1=Q`cEI_ z(n~KT)hW%a$#UM|mZP8Koe#c;d+xuR#qJ`;8YbEkOpnjd&<&(lASCmhQ=D9!r Cq}dH5GbHquME| znW8g^mJ%rh-a6 -oJSz&w1|CF=jhi z`w9@_2n0g-pzKlOop(?yV;{PglLz)t_LgwbEn2_pQrNN!W@a#?EYN4~@?Ji5`zQIp zXa9kRJNuDM$Q+c~ArgT|1xX_%ZKq_71~OHUNt6noWt?Maxr5AMDxE<0HJhe3apJC{ z9NK?~Q}> PaL|7-~a3%^Qn7oVah6O<}t!idW*$kl_yCwjb=tV-awCOl17453aJzxPhm>( zvS2VMNL5Pn@-f;+Qi^via@#vUgU=oL@la=wQq);e@4r&&Xia2IbXBd (BJC-|(u=4_Xtx3+GSkH4W{SHzDA3gjTQm;UOa~9{oc#qE= z3>;>;#LgF#9fQeBj4Sa15~XM*4cb`~A)vQ!kzz1FU)f;O_wC}mXI#Y8>^R#l-o~LX z9~!E+6bP+WrH|Hh-TT$GIOt;r@UJXFhT`c>-_ho&*PcKubxBGOy)K-%5Btc6QD#4C z({phG#F2wM^sYZgFCIk>I*7qC%Jjflq_apoQVN8Y3_6R }Ba#El2 z`17wudBvypeGXI%v;pZr34!+>V=YBlQ1lD(PJvl2F-s*yx5O3}fj}pUY`lrg6ooMq zg93wR>yvjfdDRS!Cr;s(3No88Sn7jw2;>OlVyzqs;4uUE<`*HrJHhsCP2TxO6Uc=l z=z&8HKzK*udicD9Y;Qwu`(C6FbZ-4Dr#|+67%bts3s7{Bu0VJX-qp%wy+;bqm^UmP z*w67hKF`>;on%{gU_{E3&bx*s+vWEAZ>LO3q>#~fuW=F8w*=>Lr6cbS=r8vebo%s{ zdX(K9pF4zwL?&dqfl4yw7Z*4+n5Vg;&928^#AVODmULUj)RpTvdCw6jJt16QQ+dn) z9#s)v{T6gpp_JhzFI}cA1 zeE7qt|wo{M=rdI_aAFv3eCl~Poq!>o6vJ($Sv29PJIVv zFyQECK1TP!JMqN;TP%Pt!m$K+A1upzFn*XKEuF`u8V;SpuiwSSYo12#J?*T`lXhLt z3om;CH*9+{=S`l6QG&w@N9mV6jB(%u8i|w=LAWU|&_W0Q%>$*w^$Lpl4ug~P -1E?#eEI&{ z`253PV(+PaoEV&<5QZd?BvK*c=ZFf$ia !{JOpxiny~RN#&+qOydn)gWs%aE*jq zd&CTkT{;!d!?~3Je9Qp82GI@8(|+T-&{ %o>CN??9P$N7D|n?C n0twj_1dZl#e0vHA#nAHC3A+P@Z`B?peMLXH+i_b% )9dla=21#flnw`I>CpIy3j)K4ZpF-_DnTa=NRsfH^OWTPDK#?FfG5vO3K0}} zy EgQLU%MC0&xx>S!_H*y?y?kNsm$>)P{p>$+h`EImSX+=KiX@Rpp>Q!?kV>GG zLTH7MA!|e;7!xh-|L|Rp_GS9I IB)6#F4}$xjoD4)PO$XFk6}b7bdXhPMIYh-=OrZ; z%dq7arBPi^`)SW*$Mb)X(mSLu6viQhCes>kOFRzm9oAWL>&cBFHzg9q#?}lM&F 1d@V13#!qtNdCy?IXpw*B(|r1GexJQx{3vGr2n{R^2~7 +n9{}0u 49A1`)s@)5ZN8tw!vsB;~4JVf8+2A+w!%zMZo`3oC*g3Ney2~uz`B{#9=0iOA z#ZOb5IEYz1h3WK3bwYD;g4y#fWcT;}80((@;|weeN=rL6EPeUUIQ7|IWtJlt0ZwR& z#x~sK6(k!UPkYnjXiV=ww`LH!MQIdyAu*N^%!>epHl+z|npPl$10-6ds6>;h6eleM z-^1#X3nn-5OaI}G{FgWXI$9@KQ?Av;f2JeQ(cTqan>NLBkev6y@&CK-bbz=pS%#Y@$tBsYiUnk$@tjixMUMu zuPMv`D|>h~K$7Exr?3)8Fy7(|&_be=Mx~lWDy%VRkpQt0{%BvYf2KUP1ALA0xlezZ z$KP;00!cR?pj3((7>=Jj#-Zbf=yw)b&ifo)I?ldR53#?qk0b6l-LV{|K~E&8EMop$ z d8EO4vpc!+yvw;_%avTR`AXI|*D mGj{xyiABOdALbRtT&RXr*zb#P>W?OmfwRtJpcQokx}q@JR0fM-CjNpAN8N zpeIvQJAuRn3KQ#*ZOPQs6imT^ lG5+> zSne#b=g u~ ze2BEM4K==t=JW+LX3k@Ld?O_5@#z$#6Ij_s$P}rY;5`|N-~OL(W$xr0Qi|AF`OVWq z`L)PnJHQ#FYH0-6yJs)wUvNJCeh;Y?Lg@feu%3ajEG*8muyBf{g(a4km*{r8^acY= zX~})gvR&dteuAU 4><-lUM$+;1q|GT*GS1Yd%Q^4-ojkm6KT^riV83?q z*beZGkV^S?@7~Q%|MX9@dCO*cy&gJE5fLunyu;y;7&5JBXANAd4CqZfr8AVa1S807 zlTB(f(~W6*Nte07NsjlAb9CW2OKO>3yN{kuXwEc98Yv`>A~CpG&-l~?z69TQxFthA z-(#t>Og_jlO@m4lN@$RZd7pC9ZeWl1yt`}Iu *rp=tCc3`}XY=MG<**#0m;RYr|@)lp@VC8pdE DGF7WILHJ=Q-Is$*JXex`%so$GfDH8G0;1wG^Zh*8q`e )T@zcB4EjB~ z-45mQ0Qxyth4U%egA$-p#k_NT(r9k&DJGT^HYSst-++tShKm~o+f+YPn`Mc&HsFl# zSm6*NbbpBqO28-3Y-mspZ}S+?FZ14aypv 07fLsG6sVI|KUIU z2exnD&ceb1?N$q*HUJ+nHsR)i0i-F`26i{+9Zmp**g*Ar$b3MNmz2gJ3 -_WN{uJqCjT{lS24ug7v{nX)J_#^78qAm<& 19O9L3XGu44YpfiPAyY(2K0@? z6sC4fq?Skwn};RT!pimuWv>bK=?2g6&PgI&GOrJ(DRkr6WPm)CFUWf@V{aC6G*M zjx9HN{P&m4b-R?JP5`axC0dJvKDGle^!s1_@|Sb<)mM|}Io4XFlr$QR`hAWO24)DU zOy?0EB$7lY2oV}jwALg^f>H{t6sgwqqEQ%Qzy~;jkq#{_)}|!3N!w4dG1-ifhMrkw zsaRy;)FMmsODw8oI*kEE)1X?CbX<|P6iJqlwK8<0fthLHjKg;folYOKJYcZY#SFr4 z(g-pc_ p#WG{;bCOSX1-_8X+74S(&eqhiqwUz n=yS z3jwbE&A}b|b;-B=4j`pO2*J%a-^?HX@gLLg_p#P8F)_j7;v#?gr+>=FKk*5kbJI<{ z;wOFr2N+|*4qzS5d5ksK(qf#&x $n%_{D6rPX9l-?` z%KO@lvJR{>SnnuYNlu@RS!Ow3WZ5p$m0gyTKD~5+9dn3rMb=8t4TZ`iQmfcK zT5Gh{?BBniBuUU((`vQ2=bn3b)0^JJd*AzB0Is_FYCid?PchbR zGsyFxxX}QtjeMRyHm*2_@gX2Cih_JFpx^H^7z`-OlA F&eO$#9l`&KX<-J2WN#Yev-@rR4ZP;m=vc;sF!svp+ z=L~$#qF7?VF49?EBA**jxRQ=of(%qkBU=JBrh !=arc}|vPry0QL-<6Bxy$?Y; zp46+DhE1?Gutiur)*03@fYjkLhb=9A+ofv<^jx2Aw@2BxluHKNu-L@1>^k&a7biSA zO&HrSLD4T*T3n{cOO$b7H8MgUY+JGL_C1{Zr$Yfp|ELnh{p-lLr2$k5eEH><^YcId z^9Ug*%Mu|3Kl`&k%Wb#aR;M Oz0qW(N7 zWe7sGCQZ|N_o?EE)kZLC2$d03)oJG(QYy6eLzl@*tkZ#JQ34vqnToBN1mlz;Hc(0i zsE5)>5y-rn%@$d^NzZjy%9mIeEb_?wKCE;&?eR*`nwa2EzW6^8QsVF{2XCCC@@;7V zN-2yn{PHjVGHIH!u&}`F>@2_cd%wrK-t{h&Qnf(@Ws^MjrkhsO0Y>Wr5RJ-<`ZXtp zpH;nHWt)^zb^W07|IfAyRF6kpBj?--H>~ Q#j!tT@YL;;*bCM zkMo fV{^(^-6*;6ZOb${iRSMpCE|2WfAQxwrPsm7~}2ClXuj}_Dg zPzA-6(pI;+a8?D)(GZL=r@2L=?oee2Wm&E;0_GFGJ&S}U{;>DesHNYfM@ zgMXzINt%Y8K}xjGLZiHvB#o5DSQC|(+;H7>Jo3oH@iQI^g3l?bl W|7xUh)#ACMPjxU`MW8Bq;+6TRo;XDu)mv^!V0p($JNu3T@td zwAS?spC$=q+!ZP}sm?U5GKA`QqsLWB4P%dJ@KVZX03quoV@#s8LQ0L+DM_Luajry5 zjT8w&N|ewjn^4Y|Y}+=)Pyh7mc;g#ihm?x4EFSGk`Y)1is~w A@Lo*UgkDuWm`fGQBL44}G=8h{Wg?f^Or-iSMZlu2Np zAaDmcsc5Exc2jWC#aD3n(8DW`vul0%*5aVwRy%;!nzAhU A?-elz3_OPSgH8Lx8OZa1ok&A zRb+N{IHj>V3B@&Iu+HMFTd@<=1+-Q7tK$Qp-UXx#E54K>x;hFe6+&o)P=QxS%6hP# z5aCxlADRlv5|Zgje(6nb OZ<`tagbi1E^O0p!|^ q9fde5q zIk(7be(ERLzi$t}@!$R{TI&GsI0xn1#sI2q`d5GTS7|gFbsqkcpZp|W_`( @|^d zM7}Kypi-(UuDGI3Ii|6}<(6A+p)AYVm#u=}AN|oE<$2G0UcIu7D(C1iM<2_mR31Qj zgbzCaQmm{XSM`CB?^qe==y6xOOkPT)A0kP;8>s_S1|YzW7{h34)O#0QnrLK*i2YZg z=Xa#vVfgQ=%68~wb~u#Q%q Mulx!?T?|{T@S_ScDl1|L5s;#z!`DUJJwky> ztWdJm!Q`2YVf1sVU7*S)2?S1f>?-4^j#D-9RHj$$03cQvKsbK&4o>@A?;K^}kz;Li zlGZ8Xb1@!R^Qi`pwp2y0OD?&Dm%j9+EG{mRW!Xse*4G&l=Ny|hZQ{3o`?uM&X;X~> zTB#VrLfGhYqiM&=WdLGqK>gh6?98aFfKHqtg<1LW#A7uY4W_21n4FxX(P)I#Tm)El zmF`TEqz3s_x6yRy=y6BguTV$}w2Bo0aQwBkVGZ(vx4rEzx#W^dPCF;(ihRo(K((s= z%CG!Nt@zc2s_vb0lx4||9Xt5bKmAi~xZ#G-3du@0 !~>gNK>+VJ>7#IZ?;(~Ll@ zc;Cv$XS+cF@wMA+I-L%m_{1mp$VWcH@e?N)8y_c2Q?!t$DSsM=T%>7Q_bChmz0#M}ns#WlxX8_^N zBdNm`*`xZ!upiV+YJQdbu~MXz4Dy_*$w?l3@Iij! IQ_TKcn(jX~8r8#F5O-usk+Qk@{E>hQJ{02|;iedU19( zwMv1T%_av99N-6k;0O4^=Rb$i0jgcC*itIIcT7x9@vis0hie~y9sPbkK)P3?O2g_N zs@Acb=R)KAYLHz8!IsFnAU5jfCrN^of>tx(h9_Rf{rBB}#$w{XlB|iYeoMyy)y3%d z`?aqNr)N|uU|Chbs+6i){VJ(ubOq?b@CH$X>Y#C$j~7wFC1GS~@F?O+?0q1n;_NYj zl#(<}Sz27=^{;y!U- M#-}bf@tN+U54g>A8HTKlMx8A8v zPXYjS25MrwU7uUdu|W7+)&Qzh<+^q2xbC{^FviqD+Gx=Th8 #2=f7aJQfb`L(ebq7%N~EaU4?hA$}zNsFb47Xz<_v$N%61@B2HH zRIxH$)tFD8(ffrje1V091+>;HD#YPt>ziCfWpcU!R@FOyR`<+%k2Ri!`Gxwu=aPKO z8bAeoxZ;W{xap>w==FMa@H<*Dg)qYx@A1y!ZMfkb-Z_K=>3mqNU0AUtv4AT;$jTBT z#~=>j-3leDUanXqs%n{tGr(l!9r?f+Bzz1GoF_|DZvW!#{L%0KelY5hRe!ZtQ2m|* z2M%)Jz(LZqf%js?Ed;ayd*of5xOWDf9rf|uhyjecQ}LZ31po0r{dzc;=hzPLEgJ+^ z3sJY*t@~@w1QPRc)pyp%$6;ml^@>~tt(62fN5r+Ga%t+!31L;Q2HI7Av>5gCtC(00 z{~QLJE7e*_$vgh$Z&;W=g^XaiujUR9`UCR3SWz=LJ;1FDB2pu+3UgQf8-LouK2-D& z%s=7!>mTh){&mSWr2&YuJo&ZnuEXtndU`tg!!`hwBaFFHBoY3ImDAv=$dL>V-i~~q zN|{F?Zlcm5MEx%-g|9R`?he%|TodVw`m0L73hFrm-b0dV4j(zf``-7y_}p0n@YQE+ zmCt^5IrH((wor&OI1JTWPtDDLJ(v1Xmq*_KMqEv;WuyW*)1DmPqvVQJ(ptCb@`VFd zJM{nft^dKG=*8>~Apor%^$ONpft#b3?_CXeld-`)1i4{MoyXP4&)TE+0xw+U7Xfnk z9uYD>)nh>f%0=4p$_o)ASRv~y?RJ|x?)W%+_C6R+!3wO+*>{1-sVT9j|V$nwFgd->B=-AUckfaPF zLGhI_D7=U9@fOWi6OTm*ixeg*XcE{RyusT6$_&8ecyAG61o~SY(>ss}Qcd7R7V25f z1%)RJ#RDIeIU1E1aaCgaGxSM@$H7$!{}X g3eSqpe9_mptkQQ2Ck-1V8%y zPiAIJ^9!$eEx10)mnd&=wq$I4lK1`nExiAepC^$T>%q#Dzk0`KSafH?qFa9XO~1yj zo#(N%JRc^T;1Tq|7I?FSFMCjQF}(#$uaC (Tqu4>6J)I0Fg5>2D9yE8p|pzk9S*>Fbh5*8t-Al)xYSrVBBSE5XgH(g9|5;r&s jO?q>c3!gp3U)GWs?lOHzA4h?*`Xj8t)PH;P)iYK5Jj z55D!+l3j7mgHSx)=KxPg4k)%IV)=dJ9ln 5|94Lz zPo9TMr+MdJf0FNg_6)rT-iZ_jUo4}FC8Qg~ruD$AEUYBclZ;JH;3q;53>G0kB_K+V zw`my5?iIp2wCSMj0s@CyybE_U!?m~KCoaQJUx3ZV5M~fp&QLiJ! uZG)6?ISL zorvM;ThXd~E$7mE$0e6u#xtJr3 fs4);a4%hCp_`7EYLqoss;<` zEiZe)PR!9c8ZtrH0^Bm#C9q3)-$l3*Oo8$ag^w(*5^RD!p5jFdR11+zpp!|woZ;pd zT|{x{ZlnSs4K~oWp@gV2Hedo~Upu5W;1cjUXrYimf%j!l0Bb@J>@32W&=~KEm=O}l zd>$4a#Gl%U+;lC>UXJk^TlCSwW8_3_1j5T$GpNo4BLM(0;}swp+qZ8A_^R$t<^MnD zInQCsmd(sBE+R#^yk}DQGs$VtkNUp)jI(^Uhwf76(Nd* $IGPJ(NK5GbJ# zUeyIDk0F$B4L(+Xg*V_-3|0lm__}vd(H+8B@Rb2Xqwt{$EJTOm AFks z?VXfm18+){($OpOF$^B5=88%=hhvxkuD<%}uZpjcO5vm;8$un#OHLc6nxKn+z$)o22$-pf9$`?NyDVn%Jj=+K$fGhE$!21H>OPtNICMS^= zA;a@E_XvPWaZ(4jh*OAs9w%q 8VF13e7*;SOG3`yo46V~yoo-# zH%RAeSVPD*Rjx%?*R7X$FA )={ zFAyk%@bvpVuD||CTyVh!HD}RkMXox`0N(h ^m48#EO zv?NLLXsOb_NY=`6d&^gSM?8sN{PNwr`bF1Jm=Y-rk^*l^gwJulz&R7}=-wf)$mlnV z2o{5|Ayfq{AF*kp1ufxvcsmbf5#jo=Sv|vhS<_lor6_?z3aGqv5h2ZISU1C6-SzI# zTCg}*a_1M1ar?b}9yr(~&BoX;sgV5#$d^urR>iRy92wRDLJiv`tD_2mlp$Vm-jih+ zPk;K;x$vTkD9REOJHVdvoagYCx4eb#|NfWJkE55QlyMiM-T}Urw$3xdA4Z=aLaAY8 z|8l$4;`s4n{K+5x;ac_s{OVd9^ikgdav?dkWSAMxFoPaiN`%iqWLTTxX<~hW@;30& zIExf6Hk1Ik5QnN7*?@P$4+zy=gP;TD0^Xd&l_wF|MTm4GLbvcz)CkSs)5|&&!^msH zZ@&6Iq%^H{Oii@ex=C=jpK|ZvB@P`v$TJ?- #gn#?^^Vrx GefL>@24IsnfF{Z>@1K#0W zXaHG>(i~-92!H#EeNA|TS0TqQdSK@$$^*C}LAptlND;zCoPQ*biql@81}@;;XoH9x z`PBH>3ev J>=-UT0mtvB-J7FWw;PgH2*zW(WR(rAaiI}+ z?Fj=tq!3I_O_3xC{eGXaC}Ug@=X}L9FPyHEXHRpUE%+UQM+mv1nP!wlVr;BMQS|xW zzx}_?xgFq9H2?@?Ql}Oc_@~<+;lj(VVf##riODf~y&PK-id7=PIT@#+gymhJaaM)^ zIa XBM$T>fZCFHyb7x+M+#daW-%jq zly@YlVsb(=H6dv=9Xjzy(PVaJ9Orv@*@xmJdhRf`F^wDFg7hWCEESa!S1^W3sYI9v zQyPP@wzhOvk?`V&?FQac%`5myD}8+>zJd{iuOv=000^`*S(@=9H{Z;@eS2A1?E6=h zH8Fs1f}UVeS1|#8{2AAUkt|MgpM%k$lw;yqSm>Q zcFfa}nqeNW$3`O!1Xe@ZmoxS_uWAlDTiu{4%2oWK)vN&_1X^o0te@r5i!S7mhxe={ zL)@C^>Z1;Zg9_#wR=Yp>XaIkG@Yemj{zsm|nCx=>)#oGaGQEW(WJ)7tAISh?`v?-0 zNb$alvpK?9v=jlxj2|NOt1TgBS)7*y$U_h5N?e|kn+0UC72Vv7N+u908M-EM_Yl(8 zezu4(=_+6jzd wB-Qpb38p9T~glx-9v~z_#7=kIYR1Gf_D*mAbc2N2X&jD^CASI z&f|Owm_esoQ0Z25V*-(ci78$rvH#bH$}}!0BM$E@!o{w$P!@}d6Vj=!Zng=#g~Ud< zv_~X8Y*CQP3+X-amB{f6a7Kf4As$h|uMcyBeVyJsZ3n1apenWN!8^g3g5cG;{_5YK zk?3orPgqJ_W0NXUEe~wnyn*Ykxt4qGx_vF@1+0m#KFTq`86+&vWFkO<3v2npJrD7b zFFnGMBa*9jwb-(5iM(GRNlEn>juhh+#wU2vkj(UD@P~bsVyt{(@UhKNR&Iv(IG09) zD6nM@V>;Nfz#EP5W8l+>AF5EE6{3@HTv6q6d~AuVGNjRs0;*s+WPD=I-fE!5U@)Mu zbSI)ciEC~FV!S z KG={0s(ce=mx{FFFMka}h0I8@d$BgVERkv0$hFN%sV&BJ*7k(dB zB*CF_79k@ciyuyZ^urdi;fn8u4eVb}{{JhbF<0}H`iPI%w0UFo3(q iu8?N6Z~3uslqiBV#&TJ={cdTi4F3cmlDvO%N%{^~BUQI_gaBHebojc_F|`JV6Q zGoSu6Lddo34PFyn{e~Jq6(FT)%G=-eHg0(Gli9R+^NKHV^yo4E;;;XjfB5r1;J03V zEtgI%aANKlWobEhO!LLNx*Xm=z&L{P8;A8i{9PKX>60Xu3on@Ay2q_!_j#H|wm^Td zh;bd1=#$7kT7Zffq0vQJiU7Vdwo+(`C#CS?zywlFktCBe8WX6rg-{wPbZCJWGE}R5 zQ5!&Lfef5O&X+M|>Z$xNAKfD3BWV?eytFjOH^TM*4%gmZH~5c&+rr-?B)3&c)`_$F z|6j=fR$RQ1itlRWuk!y=N~Wf!`TO_1o11^&`$s5S)=bWA1E`e$$A0X`_@N*AA)fy9 zr_=2XFxF9)Ie3e+hOuUgR$~J4+c@}+|BPOK1f4b5v$x<&cQ0_@aETzp3n-nXbQbG8 zr3p+B&Vluge%>Q% TQj%3}z>gp@f-8gNA{k_}IYw-p6qP&(@b zISHO7-j5-~1X(tPPRCHXg-#kMl?MC{4)04UE})9R&3TJ+(Exl%{f6m|Hstt4%<)$o zMp(*=e~9&$-9(ScSiGxFt{>TTeEoOW4bE-=U#T9jLMcXKveB7gD~!N U(>aAnTL|GINv#;h9~BgH z74mZ!E1VhlRy{X*eO9|bzzTO~^xvmx%F@yzpZxeOG_q{1pby^=`Gy!kHAV2*&we(S zTyhC>b0=vw$FR=fW4oge79kZ{ 0F&rmAE2^*me3gKm( z#AxsWoU|zKvC@O J* zvP5E{ml9B?-h;Is8>csh-h>ioN}Mk+R)Ms^6;f)16Jpc=N{lISHstrE6jbxNoU>T# zpzxHv0+U-BV{NqDK;8xC2FO$g&|Ww&5L2Kd9H*7?yRV@5)$^nGaJmtU?gFccdk2G@ zi!VHnKmXG|;of`iAxY9WF#FsKgTEmLP+g?+&p#h)Em|j7X9Bp*mUs+KwrROz^zL~# zZvSUV@EkspbLX9NESy>o%HoB`IRRe9^rb`A!CDv( Q>W zTW&qV7w=ST-`e7$3#Qq #)!d)emEN;efQ1C(Jckb-X6X8yd&&>r00XG9K5ux# z8~C+f`!%}VZs6lD%i6CMQjq#C=HXA`_kI8+3Qin0-1UV;P9AOHWlRTJi}NC2T*T@N z7IFP?WfgdoRCpUkd%D>1B~?PACg~N1dmrp@|DJiq#wELUOmflgjhw&pd?v>nO7-xr zgE7lEHvpmVJ_FKFY?EQIba0VOP!uT<%9CgrU79A=Ypj(lE%jJj>T~q?B1ey&qUdyJ zj|~_f*JP<=YI2+{TQ)E~J42xw6pan|<}A7QNLPRXC;W;)6h@VLWm@)Y=>;Bba5+=) zYt;Rz9$RbKv|%H!{+U MK5~M=>wc=xQim~-o^3z-_J~b zh`GT8_ujF{@xvPLrf|}RHpHq?%LR-~&Cp!U;))mmDMA8M0Z57S0YzzDoRa9Hk;pdI zIp&ugb9Z*R>%Iq>8dq#w*Wjuvwz6~kHWWRkCsUM`n9?I6 =F|g_ywA=%=$4zE(dTy+)&oex_^ay=Q!UoP!4r@VD>yn>duC zGQM+DzF~#1Q>RYVoc%Rax2E5h^!L6C+5Ix58e{*S9)}KVN|%8w2w8haHL}X$QC{MN zLrBm>sy`7rxKI{C*Ca{^toH~fkc6QTHew2-0-*%TYmAUM 6(`u&7&W pj+bl<-(30>nZ@WM!Q` zbSOn=00=?%zC~1$BvlyaIMpebTe9psw1mf^l|m^Q(58Vk7h^1C85-h2Aw-H!GPIDS zEsYQw>k8J-!UelBwrp !EMxWAuK>EOmX+OJUpertq}^^KBpf<;puVQ(qSBAbH_QOW#>R%Lf9%w9 zSfuaa4t^ZjUq*V(!oo63OA~mfkZK5(t{Skb(LzKw$NP|HiH=DmdT@bmJU%a_LwbSq zF>sb1Cq}EL;dA83%q}TJfJ3X4Bn>NESXmv`g~A(v5D7^pNd)23ghZ Vx;vPISEP*ui^B^Ulu$<3MGOl((htKu8LR@~1zv*m65$oj2c@^w z#diYfV+zuXngYeuMWe8)`wH0*q0&AoJygwEG1w*ES9$r^Hz0%{(GDdG+7qyAN1M%? z9L+|bW>YXbJ;8Wu4Cxc_SqShGjZIQ48&H$GG3h$+1|f9FKvi&U)C!m6Oi*U$?Fw+* z>g!O+nO6NXy EX&xpZy$gC_P52M;^(Hzvs%`i7pTJh{Q9r|I?ZO23eWEF0hre7 zBkdxo$;pIca%PP2SxMmrSVDuk&?@H5<3Vc-*$7*Ms4_zyB_uK$kcx4DmY|eCOGzTa zO$&*R{5n!b<*I^fDNqWO0<8pE36u=fCrbLTt5m;1fD$TTjD$sN%eooG<(Elz?kZ?B zdrVF!Hf>62w-q>r5E 86eQ@OK41-M}Y*ThXpPlh;K*76IBMV`gS(x^J-bW zBdmUHRGf3{*uIHB{KMbl#IeI`+4c1emv0C$R#-6{uYdjPnV6WUmDZRtY@;H>xscg! zG&L7rl`t_ehI0vC>3~ubDy+CQrdc6`LJAQlEfVqi;dv!AN`_k^C5e(`QqYisOiEHE zNtGg#nhZ@U6^T;8&)2dxI4OgI%V *R Cr-7jN&dv^0hkwq|@#QXqtnzHn7-4bN3@01 EvGf($7#q--H*;C&eXVMGIyZNSZ33`rZ~shdx#-&|F81uI>j(c#oX<@WXW zJd>Y4n)@GpZPcx*vP{-m&bwe2A9(-!`Qev+UjXa*b2sC2Ci#Z!0M=TRQoQq>@8q`I zZez=qEtI7R3MM5YYa){bWJpc68kXG`3LbZLo2khGQU)r+;GTGt3@*vaRXX_MQdF7x zIDwI{S|>uIg+il-E|3(Yf_M|*CMB7wb_7YH6p2 xSB16aqR%DdfI>glZl<7KL#R4J45cuX-7Pm%s zfYq)~ x>y*dCk5+fHFFCo-EOD$&GD)(fK?I r}-BAz?E@@liE9Mu@ZwNVZ`tfkI1#iZ?AKT1ljbcGGatMNKX^-_mLg$Qp*3 z=@#Q-O%l;Y(nN|zK(LYmS>QVo2}L@Ao4J^L?0gV9QYESgt&ZoeVz;P%&sl=kuN2IR zvl+wi 8i S zAWA@n^rnl@=Gd)N=gR9otk8xj>nx7~ANZnWM1&$d+KZZGDuB*HV|~y;Nl|#l$Gcp5 zd5bMu3K9ufCTNYdX*8M$-#`c*39BN~l)x+@r9@fFVCr)G*iM`m2qj}xnb2k#r^*N) zjiK&0@U<(nS^||&-RibdA8P } z`_|b3*0di$Yt6*O1Rwao2kI5SLWY)Qi8Th(ImX!G_oGhUj_V$vEISx801+7C1Svv} zQx+Nf_I0@D-X)G6Z{e*D_#6=%Nk*DQDnE6!eK0(Krl4@7sU$ugc3O#%sw#LN#;poH zVEqQc&fTzii$c;TOH $&ef%ig_9EG~OI8B!$C5Goq)nBH^5=S5vS8Z`nTRt_%;8JAdm z^8^aU__UBd%qU2DY}=V|@g*&?@h)C;Xf@lUX)`v^D5T5~A_-I~LPdN|f>atO6G}af z+HfhPlSq-GRamusjNa>XY-~|GD=KsJxmX(sWWIr~h>_lbYDWy`W#xikV+ 6shnaOd<6CN+vw7Jcmy@o@FEz6w{uqmY@H+~!#C#%fKO2|N% z8C(I|0u*A@RjQa4qznZ%=SsZqv0)=zbn!S_w-q3dQ#wJjJ%LUec$>uIX@pOaBE^dY zFH|79lEENyGLornl+A4jbSMT^D|_rs4*Xif7^UJPJ)56j0n$cV?1_h|*G)IlG&7pT zDT)HEHItJQG+PaR_SLWEum9>V5jw-0bCdJ`pCfB#0LB=$Z{N uB%s9 zr}pYy&kn2F#)QQ1#Ka_r4(;b7AAUc3_U`4c|LQNv8g24C=UaK@uVhVw;C{bf=ape) zA;uB?X>W1u%}CwE7ZO~CaBV~w$_^}nFi2^K#V~6F>lvC28^@Sk*P`3A96R3Uz=0)> z9V=N}R9G{H6s;OJErds@h?$8^>vg;+G(-+jYx{V5OiUVfZqL|#L7VlPC6a=oba+y< z)B(oeV)2bY;_C^|LvVeFl!_8dp6wu=JdZ*o2yZK0A7)`{mq)Jh@x@BluJ&j|y<=3m zTE{OUWVWgjMwoz8;+!WR449gp!a2vWBl~&fD_+jS`yK*7>Xdviph8%Es~f?Z8o*iE z2IAj=bKc<_GpNQCc3B~u#QRpvgO}kZ`iRK$A6Uy>B#`x3vxK0- v+IgTlWJ TBNn_Iu zG}mvWTPmzAB8O)j*%}LAq9V@{v6{WYtB+v4k)0r @GfSBm zpTK*^fddD4)0^JN$8Y&3tO+=+6)^P`@~u_$I+Lu40aR|!gAYE)Bab}7)YKHcUJtD` z6@R4G5=5j5^a3U}W6~M2eg~%l%F|l~w6G+E0#Xlr4@4Q&Cjx|p24ShWKfT14j)qh; z+9}iHlJmBQX^(|1D9VyvufXIbT1gTuX=IA5F(At%)(+?mEQ5i^m@JgZl#bsajV+p0 zx-oQxd7 MBYw;@}N}8!if{?``BA)UG*f!E`0)KVk0_fkw}5^KK2)=VOp|cx2>cy zkQHu_$5kv90$d#KEeI24q>`i)DHm+Jg8S~en}7V Sedt5{ ;OGL2U$OX~K#|Xm0V%jfmD}=pO@NVg@A*85|T< zNmSqvw$cUzA_vhyim-JDYVic`nt0a?ljUvI#^`A%XzG-SmX1sm0qf%nkFl1ej-fxW zIOFkXgbcy0AjP3^B;mAq9dk$;k0$I06|cWWcu7MhG_2$^pS+bjZas?r^le=FvX?XQ z)aOF~6eW&qtU=EX=(sME=>!w4vB;7)1f^9mLJfO}EYT$f;|;kjKnwCR$LF32*~Tmx z-ttRtVe_`l96Wr0d+)iYzCM-WuMvy$IVEf60vTgaO7ZhQ|MQ$Uae|k; ?|f-hnm=mo4*f7iI54-mv|Rx!let7++7x3g+M{>MHz#>IP+12^eHaJ z4pK!IYB XoC&?Z5CUBxbO72RLCQMdMQHE} z<)NW94}JMQ9=`J^(+!306)Zluhly)*@ (-dP2qT)~%5-A- B!+$HNEJ)F?fL2O>2lqStm=nGm&hS`yjUbYpv zYZIGQlj)~T)4lB!_kZ9X(xibISWGttx>b^A*i^x{gVK-6nixPug|cD820rwm4{_C1 zS1~s?S5u)^tC@8URpsiuM~rQx7&{NScrQ{Wc%R_0cv&Kh0yIvB4yk~%@v(RmKw1@7 zzB3XpJl2Mhp$<>Tdd3*R37iahaBBrNGE;b!0umWhn|M`cW}J|CImFsHA+QpJ@_<8n zO-OyFc-f>kHcoNdKK#j?Bo!!QncuOCOTX(msF@~5kM8B|pZOggoI66&m_Rs(Btz>Y zre{Ts`vGEIU r z^EwMu(E}RmP|5|sSaTXTb3W<(od_>+K9HMu?*k(oD!3xpxU#ztD G{Xhh?# zdjxRt>-v~djj)b5s6(j)DFyw7KBAMOTN;}S$Y9fp&L>p~jcZ$|MR?>R4?!LX0hSLh z