about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-04-22 21:29:27 +0300
committerEric Bailey <git@esb.lol>2025-04-23 16:36:27 -0500
commit91f9bc7ccbfcfb07434a707e43fb3244e7722d6f (patch)
tree48434adcb97eff3a104f77f4fb00fabb9a69250b
parentc34c67e4dfdfe71040e088d3d7e6e39276f93f6b (diff)
downloadvoidsky-91f9bc7ccbfcfb07434a707e43fb3244e7722d6f.tar.zst
demo mode
-rw-r--r--src/lib/api/feed/demo.ts20
-rw-r--r--src/lib/demo.ts516
-rw-r--r--src/state/queries/post-feed.ts22
-rw-r--r--src/view/screens/Home.tsx65
-rw-r--r--src/view/shell/bottom-bar/BottomBar.tsx14
5 files changed, 616 insertions, 21 deletions
diff --git a/src/lib/api/feed/demo.ts b/src/lib/api/feed/demo.ts
new file mode 100644
index 000000000..049e0f116
--- /dev/null
+++ b/src/lib/api/feed/demo.ts
@@ -0,0 +1,20 @@
+import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api'
+
+import {DEMO_FEED} from '#/lib/demo'
+import {type FeedAPI, type FeedAPIResponse} from './types'
+
+export class DemoFeedAPI implements FeedAPI {
+  agent: BskyAgent
+
+  constructor({agent}: {agent: BskyAgent}) {
+    this.agent = agent
+  }
+
+  async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
+    return DEMO_FEED.feed[0]
+  }
+
+  async fetch(): Promise<FeedAPIResponse> {
+    return DEMO_FEED
+  }
+}
diff --git a/src/lib/demo.ts b/src/lib/demo.ts
new file mode 100644
index 000000000..96bb7f9b1
--- /dev/null
+++ b/src/lib/demo.ts
@@ -0,0 +1,516 @@
+import {type AppBskyFeedGetFeed} from '@atproto/api'
+
+export const DEMO_FEED = {
+  feed: [
+    {
+      post: {
+        uri: 'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.feed.post/3lng6kvb6uc2a',
+        cid: 'bafyreifqyej7jivzucaagu22f7jj7rvjcpbzv2kxo27wt47ktduwwdpdae',
+        author: {
+          did: 'did:plc:vwzwgnygau7ed7b7wt5ux7y2',
+          handle: 'someoneelse.bsky.social',
+          displayName: 'Not David',
+          avatar:
+            'https://cdn.bsky.app/img/avatar/plain/did:plc:vwzwgnygau7ed7b7wt5ux7y2/bafkreifrtz3ngpzz5qmhjliv5toj4nvyjovijxs5e2la67wddhmdhro5he@jpeg',
+          associated: {
+            chat: {
+              allowIncoming: 'following',
+            },
+          },
+          viewer: {
+            muted: false,
+            blockedBy: false,
+            following:
+              'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3kcvvfzq6o32a',
+            followedBy:
+              'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.graph.follow/3jwawchotz22h',
+          },
+          labels: [],
+          createdAt: '2023-04-27T09:23:54.423Z',
+          verification: {
+            verifications: [
+              {
+                issuer: 'did:plc:z72i7hdynmk6r22z27h6tvur',
+                uri: 'at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.graph.verification/3lndpxt7ur22f',
+                isValid: true,
+                createdAt: '2025-04-21T10:48:58.775Z',
+              },
+            ],
+            verifiedStatus: 'valid',
+            trustedVerifierStatus: 'none',
+          },
+        },
+        record: {
+          $type: 'app.bsky.feed.post',
+          createdAt: '2025-04-22T17:15:30.525Z',
+          langs: ['en'],
+          reply: {
+            parent: {
+              cid: 'bafyreic7wmhywvu5fi4lupswxpmyydqn2gl5kwnt4n4jxvb3lpej2l72ku',
+              uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lng66dkzu222',
+            },
+            root: {
+              cid: 'bafyreic7wmhywvu5fi4lupswxpmyydqn2gl5kwnt4n4jxvb3lpej2l72ku',
+              uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lng66dkzu222',
+            },
+          },
+          text: 'sometimes I go to the apple store just to look at them',
+        },
+        replyCount: 0,
+        repostCount: 0,
+        likeCount: 6,
+        quoteCount: 0,
+        indexedAt: '2025-04-22T17:15:31.251Z',
+        viewer: {
+          threadMuted: false,
+          embeddingDisabled: false,
+        },
+        labels: [],
+      },
+      reply: {
+        root: {
+          uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lng66dkzu222',
+          cid: 'bafyreic7wmhywvu5fi4lupswxpmyydqn2gl5kwnt4n4jxvb3lpej2l72ku',
+          author: {
+            did: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+            handle: 'jasalterego.bsky.social',
+            displayName: 'Jerry Appleseed',
+            avatar:
+              'https://cdn.bsky.app/img/avatar/plain/did:plc:vc7f4oafdgxsihk4cry2xpze/bafkreicwxwecqiko2rwwln5y3fqqb2zx6wfg5rxf5r7lukakkq2slqy5hy@jpeg',
+            associated: {
+              chat: {
+                allowIncoming: 'following',
+              },
+            },
+            viewer: {
+              muted: false,
+              blockedBy: false,
+              following:
+                'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3jx4rnvlnhl25',
+              followedBy:
+                'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.graph.follow/3jx4siiniuc2e',
+            },
+            labels: [
+              {
+                cts: '2024-05-17T21:53:59.049Z',
+                src: 'did:plc:skibpmllbhxvbvwgtjxl3uao',
+                uri: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                val: 'cringe',
+                ver: 1,
+              },
+              {
+                cts: '2024-05-17T21:53:59.049Z',
+                src: 'did:plc:skibpmllbhxvbvwgtjxl3uao',
+                uri: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                val: 'elder-watch',
+                ver: 1,
+              },
+              {
+                src: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.actor.profile/self',
+                cid: 'bafyreidfiuv3c22vliyu2onazf23zrp35rr7i3upsqa2dsn5cqimmlgugm',
+                val: '!no-unauthenticated',
+                cts: '1970-01-01T00:00:00.000Z',
+              },
+            ],
+            createdAt: '2023-04-23T20:11:04.375Z',
+          },
+          record: {
+            $type: 'app.bsky.feed.post',
+            createdAt: '2025-04-22T17:08:29.321Z',
+            langs: ['en'],
+            text: "(studying the blade) ow that's the sharp side",
+          },
+          replyCount: 8,
+          repostCount: 37,
+          likeCount: 252,
+          quoteCount: 1,
+          indexedAt: '2025-04-22T17:08:29.458Z',
+          viewer: {
+            threadMuted: false,
+            embeddingDisabled: false,
+          },
+          labels: [],
+          $type: 'app.bsky.feed.defs#postView',
+        },
+        parent: {
+          uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.feed.post/3lng66dkzu222',
+          cid: 'bafyreic7wmhywvu5fi4lupswxpmyydqn2gl5kwnt4n4jxvb3lpej2l72ku',
+          author: {
+            did: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+            handle: 'jasalterego.bsky.social',
+            displayName: 'Jerry Appleseed',
+            avatar:
+              'https://cdn.bsky.app/img/avatar/plain/did:plc:vc7f4oafdgxsihk4cry2xpze/bafkreicwxwecqiko2rwwln5y3fqqb2zx6wfg5rxf5r7lukakkq2slqy5hy@jpeg',
+            associated: {
+              chat: {
+                allowIncoming: 'following',
+              },
+            },
+            viewer: {
+              muted: false,
+              blockedBy: false,
+              following:
+                'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3jx4rnvlnhl25',
+              followedBy:
+                'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.graph.follow/3jx4siiniuc2e',
+            },
+            labels: [
+              {
+                cts: '2024-05-17T21:53:59.049Z',
+                src: 'did:plc:skibpmllbhxvbvwgtjxl3uao',
+                uri: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                val: 'cringe',
+                ver: 1,
+              },
+              {
+                cts: '2024-05-17T21:53:59.049Z',
+                src: 'did:plc:skibpmllbhxvbvwgtjxl3uao',
+                uri: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                val: 'elder-watch',
+                ver: 1,
+              },
+              {
+                src: 'did:plc:vc7f4oafdgxsihk4cry2xpze',
+                uri: 'at://did:plc:vc7f4oafdgxsihk4cry2xpze/app.bsky.actor.profile/self',
+                cid: 'bafyreidfiuv3c22vliyu2onazf23zrp35rr7i3upsqa2dsn5cqimmlgugm',
+                val: '!no-unauthenticated',
+                cts: '1970-01-01T00:00:00.000Z',
+              },
+            ],
+            createdAt: '2023-04-23T20:11:04.375Z',
+          },
+          record: {
+            $type: 'app.bsky.feed.post',
+            createdAt: '2025-04-22T17:08:29.321Z',
+            langs: ['en'],
+            text: '*sees the studio display* i think i hauve covid',
+          },
+          replyCount: 8,
+          repostCount: 37,
+          likeCount: 252,
+          quoteCount: 1,
+          indexedAt: '2025-04-22T17:08:29.458Z',
+          viewer: {
+            threadMuted: false,
+            embeddingDisabled: false,
+          },
+          labels: [],
+          $type: 'app.bsky.feed.defs#postView',
+        },
+      },
+    },
+    {
+      post: {
+        uri: 'at://did:plc:qvzn322kmcvd7xtnips5xaun/app.bsky.feed.post/3lnehbwkvzk2z',
+        cid: 'bafyreidshyla4xoolb7fexhtlqcjjbn6ts7z4xja7gnlinroms5cuqg3fq',
+        author: {
+          did: 'did:plc:qvzn322kmcvd7xtnips5xaun',
+          handle: 'scalzi.com',
+          displayName: 'John Scalzi',
+          avatar:
+            'https://cdn.bsky.app/img/avatar/plain/did:plc:qvzn322kmcvd7xtnips5xaun/bafkreih4dn5gllculyzb6wlqcqparkax35zloe3bzn2nufeqeilz4sutsu@jpeg',
+          associated: {
+            chat: {
+              allowIncoming: 'following',
+            },
+          },
+
+          viewer: {
+            muted: false,
+            blockedBy: false,
+            following:
+              'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3kcvvfzq6o32a',
+            followedBy:
+              'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.graph.follow/3jwawchotz22h',
+          },
+          labels: [],
+          createdAt: '2023-04-27T16:05:17.859Z',
+        },
+        record: {
+          $type: 'app.bsky.feed.post',
+          createdAt: '2025-04-22T00:46:14.095Z',
+          embed: {
+            $type: 'app.bsky.embed.images',
+            images: [
+              {
+                alt: 'Smudge napping on the Eames chair, which is covered in a blanket to avoid getting cat hair on it. ',
+                aspectRatio: {
+                  height: 2000,
+                  width: 1505,
+                },
+                image: {
+                  $type: 'blob',
+                  ref: {
+                    $link:
+                      'bafkreigkfbcmttc4r4avknp4y2mlcjlws3sdcat6jcyd2mjr7yvz6sje4q',
+                  },
+                  mimeType: 'image/jpeg',
+                  size: 833834,
+                },
+              },
+            ],
+          },
+          langs: ['en'],
+          text: 'Smudge found the Eames chair',
+        },
+        embed: {
+          $type: 'app.bsky.embed.images#view',
+          images: [
+            {
+              thumb:
+                'https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:qvzn322kmcvd7xtnips5xaun/bafkreigkfbcmttc4r4avknp4y2mlcjlws3sdcat6jcyd2mjr7yvz6sje4q@jpeg',
+              fullsize:
+                'https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:qvzn322kmcvd7xtnips5xaun/bafkreigkfbcmttc4r4avknp4y2mlcjlws3sdcat6jcyd2mjr7yvz6sje4q@jpeg',
+              alt: 'Smudge napping on the Eames chair, which is covered in a blanket to avoid getting cat hair on it. ',
+              aspectRatio: {
+                height: 2000,
+                width: 1505,
+              },
+            },
+          ],
+        },
+        replyCount: 47,
+        repostCount: 31,
+        likeCount: 1543,
+        quoteCount: 0,
+        indexedAt: '2025-04-22T00:46:17.457Z',
+        viewer: {
+          threadMuted: false,
+          embeddingDisabled: false,
+        },
+        labels: [],
+      },
+    },
+    {
+      post: {
+        uri: 'at://did:plc:jb2q4yqmgpmefxd4xx66gepm/app.bsky.feed.post/3lng4m2memc2k',
+        cid: 'bafyreicvgwkd5xkbtu3eh756yodnynqbxoegzzqhhees5ou45t3gq3j6um',
+        author: {
+          did: 'did:plc:jb2q4yqmgpmefxd4xx66gepm',
+          handle: 'visionprofan.bsky.social',
+          displayName: 'Visionary',
+          avatar:
+            'https://cdn.bsky.app/img/avatar/plain/did:plc:jb2q4yqmgpmefxd4xx66gepm/bafkreibqdpwilgj43m37ksjilzcnzqx2g3njcyiyifvazdar23rs5hlokm@jpeg',
+          associated: {
+            chat: {
+              allowIncoming: 'following',
+            },
+          },
+
+          viewer: {
+            muted: false,
+            blockedBy: false,
+            following:
+              'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3kcvvfzq6o32a',
+            followedBy:
+              'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.graph.follow/3jwawchotz22h',
+          },
+          labels: [
+            {
+              cts: '2024-07-14T04:31:55.599Z',
+              src: 'did:plc:skibpmllbhxvbvwgtjxl3uao',
+              uri: 'did:plc:jb2q4yqmgpmefxd4xx66gepm',
+              val: 'cringe',
+              ver: 1,
+            },
+          ],
+          createdAt: '2023-04-13T05:48:48.827Z',
+        },
+        record: {
+          $type: 'app.bsky.feed.post',
+          createdAt: '2025-04-22T16:40:22.205Z',
+          embed: {
+            $type: 'app.bsky.embed.record',
+            record: {
+              cid: 'bafyreif6ks7dhjrgrio5guztdhf3byoygbwebbvehi4al2c5kmkevlqdky',
+              uri: 'at://did:plc:4llrhdclvdlmmynkwsmg5tdc/app.bsky.feed.post/3lnfspxzdd42p',
+            },
+          },
+          langs: ['en'],
+          text: 'Just got my Vision Pro! 🤯\n\nAny recommendations for apps to try out?',
+        },
+        embed: {
+          $type: 'app.bsky.embed.record#view',
+          record: {
+            $type: 'app.bsky.embed.record#viewRecord',
+            uri: 'at://did:plc:4llrhdclvdlmmynkwsmg5tdc/app.bsky.feed.post/3lnfspxzdd42p',
+            cid: 'bafyreif6ks7dhjrgrio5guztdhf3byoygbwebbvehi4al2c5kmkevlqdky',
+            author: {
+              did: 'did:plc:4llrhdclvdlmmynkwsmg5tdc',
+              handle: 'atrupar.com',
+              displayName: 'Aaron Rupar',
+              avatar:
+                'https://cdn.bsky.app/img/avatar/plain/did:plc:4llrhdclvdlmmynkwsmg5tdc/bafkreibmhm3h6ar52pogvolisrzjdhwa2myras5vkxzj67twxn2l6pogwu@jpeg',
+              associated: {
+                chat: {
+                  allowIncoming: 'following',
+                },
+              },
+              viewer: {
+                muted: false,
+                blockedBy: false,
+                following:
+                  'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3l7eals2t4g2k',
+              },
+              labels: [],
+              createdAt: '2023-04-28T00:47:57.437Z',
+            },
+            value: {
+              $type: 'app.bsky.feed.post',
+              createdAt: '2025-04-22T13:43:36.261Z',
+              embed: {
+                $type: 'app.bsky.embed.video',
+                alt: '',
+                aspectRatio: {
+                  height: 720,
+                  width: 1280,
+                },
+                captions: [
+                  {
+                    $type: 'app.bsky.embed.video#caption',
+                    file: {
+                      $type: 'blob',
+                      ref: {
+                        $link:
+                          'bafkreihm7npnefqxqmn7d45lcbxzn4wnowc2abe5hxmd63qlob7fbszola',
+                      },
+                      mimeType: 'text/vtt',
+                      size: 1339,
+                    },
+                    lang: 'en',
+                  },
+                ],
+                video: {
+                  $type: 'blob',
+                  ref: {
+                    $link:
+                      'bafkreihovdkyvql2yswuimm5m35lqyo7tgsfiujm7vrxtfmi4gwej4hkpa',
+                  },
+                  mimeType: 'video/mp4',
+                  size: 6026932,
+                },
+              },
+              facets: [],
+              langs: ['en'],
+              text: 'Austin Scott previews how House Rs plan to cut Medicaid: "The federal govt is paying 90% of the Medicaid expansion. What we\'ve talked about is moving that 90% level of the expansion back... nobody would be kicked off Medicaid as long as governors decided they wanted to continue to fund the program"',
+            },
+            labels: [],
+            likeCount: 880,
+            replyCount: 230,
+            repostCount: 347,
+            quoteCount: 145,
+            indexedAt: '2025-04-22T13:43:37.350Z',
+            embeds: [
+              {
+                $type: 'app.bsky.embed.video#view',
+                cid: 'bafkreihovdkyvql2yswuimm5m35lqyo7tgsfiujm7vrxtfmi4gwej4hkpa',
+                playlist:
+                  'https://video.bsky.app/watch/did%3Aplc%3A4llrhdclvdlmmynkwsmg5tdc/bafkreihovdkyvql2yswuimm5m35lqyo7tgsfiujm7vrxtfmi4gwej4hkpa/playlist.m3u8',
+                thumbnail:
+                  'https://video.bsky.app/watch/did%3Aplc%3A4llrhdclvdlmmynkwsmg5tdc/bafkreihovdkyvql2yswuimm5m35lqyo7tgsfiujm7vrxtfmi4gwej4hkpa/thumbnail.jpg',
+                alt: '',
+                aspectRatio: {
+                  height: 720,
+                  width: 1280,
+                },
+              },
+            ],
+          },
+        },
+        replyCount: 11,
+        repostCount: 97,
+        likeCount: 399,
+        quoteCount: 3,
+        indexedAt: '2025-04-22T16:40:22.648Z',
+        viewer: {
+          threadMuted: false,
+          embeddingDisabled: false,
+        },
+        labels: [],
+      },
+    },
+    {
+      post: {
+        uri: 'at://did:plc:5o6k7jvowuyaquloafzn3cfw/app.bsky.feed.post/3lng6lkuhxc2s',
+        cid: 'bafyreifwapmjx76kz5lkoeejfoes4ct2xhyycfyxwccmyr3mtxht5juyli',
+        author: {
+          did: 'did:plc:5o6k7jvowuyaquloafzn3cfw',
+          handle: 'johndoe.org',
+          displayName: 'John Doe',
+          avatar:
+            'https://cdn.bsky.app/img/avatar/plain/did:plc:5o6k7jvowuyaquloafzn3cfw/bafkreierrwtdsf5quwprs2xqmmh2lu2k7au2cibyegfpard6wqyjs7nd6i@jpeg',
+
+          viewer: {
+            muted: false,
+            blockedBy: false,
+            following:
+              'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3kcvvfzq6o32a',
+            followedBy:
+              'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.graph.follow/3jwawchotz22h',
+          },
+          labels: [],
+          createdAt: '2023-05-16T02:37:39.269Z',
+        },
+        record: {
+          $type: 'app.bsky.feed.post',
+          createdAt: '2025-04-22T17:15:53.178Z',
+          langs: ['en'],
+          text: "I'm running out of ideas for these fake posts. Alas, such is the demands of modern life. Do you like blueberries? Just remembered that blueberries are delicious! They're so tasty! I can't wait to try them!",
+        },
+        replyCount: 13,
+        repostCount: 121,
+        likeCount: 345,
+        quoteCount: 6,
+        indexedAt: '2025-04-22T17:15:53.351Z',
+        viewer: {
+          threadMuted: false,
+          embeddingDisabled: false,
+        },
+        labels: [],
+      },
+    },
+    {
+      post: {
+        uri: 'at://did:plc:5ywatwbfxoecxgb4xq6ods72/app.bsky.feed.post/3lng5w2fbvs2g',
+        cid: 'bafyreigi7d57fudzoybe4u6w7friw6ydz3pmzrdu3bvjwb5mvsvlprk23y',
+        author: {
+          did: 'did:plc:5ywatwbfxoecxgb4xq6ods72',
+          handle: 'cooking.bsky.social',
+          displayName: 'cooking tips',
+          avatar:
+            'https://cdn.bsky.app/img/avatar/plain/did:plc:5ywatwbfxoecxgb4xq6ods72/bafkreibbsuyy25elibbys5vx25cnkcs6g4ih6dozypa4bomwhkwsi6f5wa@jpeg',
+
+          viewer: {
+            muted: false,
+            blockedBy: false,
+            following:
+              'at://did:plc:p2cp5gopk7mgjegy6wadk3ep/app.bsky.graph.follow/3kcvvfzq6o32a',
+            followedBy:
+              'at://did:plc:vwzwgnygau7ed7b7wt5ux7y2/app.bsky.graph.follow/3jwawchotz22h',
+          },
+          labels: [],
+          createdAt: '2024-05-28T00:18:08.531Z',
+        },
+        record: {
+          $type: 'app.bsky.feed.post',
+          createdAt: '2025-04-22T17:03:51.259Z',
+          langs: ['en'],
+          text: 'and another thing. I have more things to say. I think. I forget :/',
+        },
+        replyCount: 1,
+        repostCount: 4,
+        likeCount: 20,
+        quoteCount: 0,
+        indexedAt: '2025-04-22T17:03:52.050Z',
+        viewer: {
+          threadMuted: false,
+          embeddingDisabled: false,
+        },
+        labels: [],
+      },
+    },
+  ],
+} satisfies AppBskyFeedGetFeed.OutputSchema
+
+export const BOTTOM_BAR_AVI = 'https://bsky.social/about/hero-social-card.webp'
diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts
index 82a118ec2..f3fa13cfb 100644
--- a/src/state/queries/post-feed.ts
+++ b/src/state/queries/post-feed.ts
@@ -1,31 +1,32 @@
 import React, {useCallback, useEffect, useRef} from 'react'
 import {AppState} from 'react-native'
 import {
-  AppBskyActorDefs,
+  type AppBskyActorDefs,
   AppBskyFeedDefs,
-  AppBskyFeedPost,
+  type AppBskyFeedPost,
   AtUri,
-  BskyAgent,
+  type BskyAgent,
   moderatePost,
-  ModerationDecision,
+  type ModerationDecision,
 } from '@atproto/api'
 import {
-  InfiniteData,
-  QueryClient,
-  QueryKey,
+  type InfiniteData,
+  type QueryClient,
+  type QueryKey,
   useInfiniteQuery,
 } from '@tanstack/react-query'
 
 import {AuthorFeedAPI} from '#/lib/api/feed/author'
 import {CustomFeedAPI} from '#/lib/api/feed/custom'
+import {DemoFeedAPI} from '#/lib/api/feed/demo'
 import {FollowingFeedAPI} from '#/lib/api/feed/following'
 import {HomeFeedAPI} from '#/lib/api/feed/home'
 import {LikesFeedAPI} from '#/lib/api/feed/likes'
 import {ListFeedAPI} from '#/lib/api/feed/list'
 import {MergeFeedAPI} from '#/lib/api/feed/merge'
-import {FeedAPI, ReasonFeedSource} from '#/lib/api/feed/types'
+import {type FeedAPI, type ReasonFeedSource} from '#/lib/api/feed/types'
 import {aggregateUserInterests} from '#/lib/api/feed/utils'
-import {FeedTuner, FeedTunerFn} from '#/lib/api/feed-manip'
+import {FeedTuner, type FeedTunerFn} from '#/lib/api/feed-manip'
 import {DISCOVER_FEED_URI} from '#/lib/constants'
 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants'
 import {logger} from '#/logger'
@@ -59,6 +60,7 @@ export type FeedDescriptor =
   | `feedgen|${FeedUri}`
   | `likes|${ActorDid}`
   | `list|${ListUri}`
+  | 'demo'
 export interface FeedParams {
   mergeFeedEnabled?: boolean
   mergeFeedSources?: string[]
@@ -483,6 +485,8 @@ function createApi({
   } else if (feedDesc.startsWith('list')) {
     const [_, list] = feedDesc.split('|')
     return new ListFeedAPI({agent, feedParams: {list}})
+  } else if (feedDesc === 'demo') {
+    return new DemoFeedAPI({agent})
   } else {
     // shouldnt happen
     return new FollowingFeedAPI({agent})
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index a6e2595ee..e058e2883 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -8,28 +8,36 @@ import {useOTAUpdates} from '#/lib/hooks/useOTAUpdates'
 import {useSetTitle} from '#/lib/hooks/useSetTitle'
 import {useRequestNotificationsPermission} from '#/lib/notifications/notifications'
 import {
-  HomeTabNavigatorParams,
-  NativeStackScreenProps,
+  type HomeTabNavigatorParams,
+  type NativeStackScreenProps,
 } from '#/lib/routes/types'
 import {logEvent} from '#/lib/statsig/statsig'
 import {isWeb} from '#/platform/detection'
 import {emitSoftReset} from '#/state/events'
-import {SavedFeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
-import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
+import {
+  type SavedFeedSourceInfo,
+  usePinnedFeedsInfos,
+} from '#/state/queries/feed'
+import {type FeedDescriptor, type FeedParams} from '#/state/queries/post-feed'
 import {usePreferencesQuery} from '#/state/queries/preferences'
-import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
+import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
 import {useSession} from '#/state/session'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {useLoggedOutViewControls} from '#/state/shell/logged-out'
 import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
 import {FeedPage} from '#/view/com/feeds/FeedPage'
 import {HomeHeader} from '#/view/com/home/HomeHeader'
-import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
+import {
+  Pager,
+  type PagerRef,
+  type RenderTabBarFnProps,
+} from '#/view/com/pager/Pager'
 import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
 import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
 import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
 import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
 import * as Layout from '#/components/Layout'
+import {useDemoMode} from '#/storage/hooks/demo-mode'
 
 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
 export function HomeScreen(props: Props) {
@@ -184,8 +192,22 @@ function HomeScreenReady({
     [setMinimalShellMode],
   )
 
+  const [demoMode] = useDemoMode()
+
   const renderTabBar = React.useCallback(
     (props: RenderTabBarFnProps) => {
+      if (demoMode) {
+        return (
+          <HomeHeader
+            key="FEEDS_TAB_BAR"
+            {...props}
+            testID="homeScreenFeedTabs"
+            onPressSelected={onPressSelected}
+            // @ts-ignore
+            feeds={[{displayName: 'Following'}, {displayName: 'Discover'}]}
+          />
+        )
+      }
       return (
         <HomeHeader
           key="FEEDS_TAB_BAR"
@@ -196,7 +218,7 @@ function HomeScreenReady({
         />
       )
     },
-    [onPressSelected, pinnedFeedInfos],
+    [onPressSelected, pinnedFeedInfos, demoMode],
   )
 
   const renderFollowingEmptyState = React.useCallback(() => {
@@ -218,6 +240,35 @@ function HomeScreenReady({
     }
   }, [preferences])
 
+  if (demoMode) {
+    return (
+      <Pager
+        ref={pagerRef}
+        testID="homeScreen"
+        onPageSelected={onPageSelected}
+        onPageScrollStateChanged={onPageScrollStateChanged}
+        renderTabBar={renderTabBar}
+        initialPage={selectedIndex}>
+        <FeedPage
+          testID="demoFeedPage"
+          isPageFocused
+          isPageAdjacent={false}
+          feed="demo"
+          renderEmptyState={renderCustomFeedEmptyState}
+          feedInfo={pinnedFeedInfos[0]}
+        />
+        <FeedPage
+          testID="customFeedPage"
+          isPageFocused
+          isPageAdjacent={false}
+          feed={`feedgen|${PROD_DEFAULT_FEED('whats-hot')}`}
+          renderEmptyState={renderCustomFeedEmptyState}
+          feedInfo={pinnedFeedInfos[0]}
+        />
+      </Pager>
+    )
+  }
+
   return hasSession ? (
     <Pager
       key={allFeeds.join(',')}
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 822547d93..df6a045dc 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -1,13 +1,14 @@
-import React, {ComponentProps} from 'react'
-import {GestureResponderEvent, View} from 'react-native'
+import React, {type ComponentProps} from 'react'
+import {type GestureResponderEvent, View} from 'react-native'
 import Animated from 'react-native-reanimated'
 import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {msg, plural, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
-import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
+import {type BottomTabBarProps} from '@react-navigation/bottom-tabs'
 import {StackActions} from '@react-navigation/native'
 
 import {PressableScale} from '#/lib/custom-animations/PressableScale'
+import {BOTTOM_BAR_AVI} from '#/lib/demo'
 import {useHaptics} from '#/lib/haptics'
 import {useDedupe} from '#/lib/hooks/useDedupe'
 import {useMinimalShellFooterTransform} from '#/lib/hooks/useMinimalShellTransform'
@@ -47,6 +48,7 @@ import {
   Message_Stroke2_Corner0_Rounded as Message,
   Message_Stroke2_Corner0_Rounded_Filled as MessageFilled,
 } from '#/components/icons/Message'
+import {useDemoMode} from '#/storage/hooks/demo-mode'
 import {styles} from './BottomBarStyles'
 
 type TabOptions =
@@ -124,6 +126,8 @@ export function BottomBar({navigation}: BottomTabBarProps) {
     accountSwitchControl.open()
   }, [accountSwitchControl, playHaptic])
 
+  const [demoMode] = useDemoMode()
+
   return (
     <>
       <SwitchAccountDialog control={accountSwitchControl} />
@@ -259,7 +263,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
                         {borderColor: pal.text.color},
                       ]}>
                       <UserAvatar
-                        avatar={profile?.avatar}
+                        avatar={demoMode ? BOTTOM_BAR_AVI : profile?.avatar}
                         size={iconWidth - 3}
                         // See https://github.com/bluesky-social/social-app/pull/1801:
                         usePlainRNImage={true}
@@ -270,7 +274,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
                     <View
                       style={[styles.ctrlIcon, pal.text, styles.profileIcon]}>
                       <UserAvatar
-                        avatar={profile?.avatar}
+                        avatar={demoMode ? BOTTOM_BAR_AVI : profile?.avatar}
                         size={iconWidth - 3}
                         // See https://github.com/bluesky-social/social-app/pull/1801:
                         usePlainRNImage={true}