diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index 04b8ebfdc54db17dd3e2c5be0899e37cf93fb3a7..9234895e8f73e2cfb305b4398a512a2d052aa848 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -114,6 +114,9 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
                 ],
             ],
         ]);
+        $routes->get('activity', 'EpisodeController::activity/$1/$2', [
+            'as' => 'episode-activity',
+        ]);
         $routes->options('comments', 'ActivityPubController::preflight');
         $routes->get('comments', 'EpisodeController::comments/$1/$2', [
             'as' => 'episode-comments',
@@ -128,7 +131,7 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
             ],
         ]);
         $routes->get('comments/(:uuid)', 'EpisodeCommentController::view/$1/$2/$3', [
-            'as' => 'comment',
+            'as' => 'episode-comment',
             'application/activity+json' => [
                 'controller-method' => 'EpisodeController::commentObject/$1/$2',
             ],
@@ -140,10 +143,10 @@ $routes->group('@(:podcastHandle)', function ($routes): void {
             ],
         ]);
         $routes->get('comments/(:uuid)/replies', 'EpisodeCommentController::replies/$1/$2/$3', [
-            'as' => 'comment-replies',
+            'as' => 'episode-comment-replies',
         ]);
         $routes->post('comments/(:uuid)/like', 'EpisodeCommentController::attemptLike/$1/$2/$3', [
-            'as' => 'comment-attempt-like',
+            'as' => 'episode-comment-attempt-like',
         ]);
         $routes->get('oembed.json', 'EpisodeController::oembedJSON/$1/$2', [
             'as' => 'episode-oembed-json',
diff --git a/app/Controllers/EpisodeCommentController.php b/app/Controllers/EpisodeCommentController.php
index 4dcef8619884982d8254a784bb89d4d5911969fc..f64c1db30428f4514c9f7ce0e827133df6188577 100644
--- a/app/Controllers/EpisodeCommentController.php
+++ b/app/Controllers/EpisodeCommentController.php
@@ -104,9 +104,8 @@ class EpisodeCommentController extends BaseController
             // if user is logged in then send to the authenticated activity view
             if (can_user_interact()) {
                 helper('form');
-                return view('podcast/comment_authenticated', $data);
             }
-            return view('podcast/comment', $data, [
+            return view('episode/comment', $data, [
                 'cache' => DECADE,
                 'cache_name' => $cacheName,
             ]);
diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php
index 9aacdc684802b06e39f60562a8c8a0ac7b70924e..d5ddd9f780253ad6a7fd74cd790e53e008812772 100644
--- a/app/Controllers/EpisodeController.php
+++ b/app/Controllers/EpisodeController.php
@@ -87,10 +87,47 @@ class EpisodeController extends BaseController
 
             if (can_user_interact()) {
                 helper('form');
-                return view('podcast/episode_authenticated', $data);
             }
             // The page cache is set to a decade so it is deleted manually upon podcast update
-            return view('podcast/episode', $data, [
+            return view('episode/comments', $data, [
+                'cache' => $secondsToNextUnpublishedEpisode
+                    ? $secondsToNextUnpublishedEpisode
+                    : DECADE,
+                'cache_name' => $cacheName,
+            ]);
+        }
+
+        return $cachedView;
+    }
+
+    public function activity(): string
+    {
+        // Prevent analytics hit when authenticated
+        if (! can_user_interact()) {
+            $this->registerPodcastWebpageHit($this->episode->podcast_id);
+        }
+
+        $locale = service('request')
+            ->getLocale();
+        $cacheName =
+            "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" .
+            (can_user_interact() ? '_authenticated' : '');
+
+        if (! ($cachedView = cache($cacheName))) {
+            $data = [
+                'podcast' => $this->podcast,
+                'episode' => $this->episode,
+            ];
+
+            $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
+                $this->podcast->id,
+            );
+
+            if (can_user_interact()) {
+                helper('form');
+            }
+            // The page cache is set to a decade so it is deleted manually upon podcast update
+            return view('episode/activity', $data, [
                 'cache' => $secondsToNextUnpublishedEpisode
                     ? $secondsToNextUnpublishedEpisode
                     : DECADE,
diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php
index 21ce0ed08127fcb2619565ee0fa2783a299bf6c7..f330064d436f8e4861d1a422e5dbe21f95e6f84f 100644
--- a/app/Controllers/PodcastController.php
+++ b/app/Controllers/PodcastController.php
@@ -87,7 +87,6 @@ class PodcastController extends BaseController
             // if user is logged in then send to the authenticated activity view
             if (can_user_interact()) {
                 helper('form');
-                return view('podcast/activity_authenticated', $data);
             }
 
             $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@@ -129,10 +128,9 @@ class PodcastController extends BaseController
                 'podcast' => $this->podcast,
             ];
 
-            // if user is logged in then send to the authenticated activity view
+            // // if user is logged in then send to the authenticated activity view
             if (can_user_interact()) {
                 helper('form');
-                return view('podcast/about_authenticated', $data);
             }
 
             $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
@@ -256,11 +254,6 @@ class PodcastController extends BaseController
             $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
                 $this->podcast->id,
             );
-
-            // if user is logged in then send to the authenticated episodes view
-            if (can_user_interact()) {
-                return view('podcast/episodes_authenticated', $data);
-            }
             return view('podcast/episodes', $data, [
                 'cache' => $secondsToNextUnpublishedEpisode
                     ? $secondsToNextUnpublishedEpisode
diff --git a/app/Controllers/PostController.php b/app/Controllers/PostController.php
index 184825adca66fc22c76ae8c91e95bf7cb7dafc89..69837feb0d369e143df1502f8af8e0a3eaabd4f5 100644
--- a/app/Controllers/PostController.php
+++ b/app/Controllers/PostController.php
@@ -88,9 +88,8 @@ class PostController extends FediversePostController
             // if user is logged in then send to the authenticated activity view
             if (can_user_interact()) {
                 helper('form');
-                return view('podcast/post_authenticated', $data);
             }
-            return view('podcast/post', $data, [
+            return view('post/post', $data, [
                 'cache' => DECADE,
                 'cache_name' => $cacheName,
             ]);
@@ -242,7 +241,7 @@ class PostController extends FediversePostController
 
             helper('form');
 
-            return view('podcast/post_remote_action', $data, [
+            return view('post/remote_action', $data, [
                 'cache' => DECADE,
                 'cache_name' => $cacheName,
             ]);
diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php
index 657eca2d32bea61b05532cc91b344c8cf850db4a..6a85dbf301a53ec4ce6efc77d2497b42934dd78c 100644
--- a/app/Helpers/components_helper.php
+++ b/app/Helpers/components_helper.php
@@ -8,7 +8,6 @@ declare(strict_types=1);
  * @link       https://castopod.org/
  */
 use App\Entities\Location;
-use App\Entities\Person;
 use CodeIgniter\I18n\Time;
 use CodeIgniter\View\Table;
 
@@ -189,11 +188,11 @@ if (! function_exists('episode_numbering')) {
         $transKey = '';
         $args = [];
         if ($episodeNumber !== null) {
-            $args['episodeNumber'] = $episodeNumber;
+            $args['episodeNumber'] = sprintf('%02d', $episodeNumber);
         }
 
         if ($seasonNumber !== null) {
-            $args['seasonNumber'] = $seasonNumber;
+            $args['seasonNumber'] = sprintf('%02d', $seasonNumber);
         }
 
         if ($episodeNumber !== null && $seasonNumber !== null) {
@@ -250,95 +249,6 @@ if (! function_exists('location_link')) {
 
 // ------------------------------------------------------------------------
 
-if (! function_exists('person_list')) {
-    /**
-     * Returns list of persons images
-     *
-     * @param Person[] $persons
-     */
-    function person_list(array $persons, string $class = ''): string
-    {
-        if ($persons === []) {
-            return '';
-        }
-
-        $personList = "<div class='flex w-full space-x-2 overflow-y-auto {$class}'>";
-
-        foreach ($persons as $person) {
-            $personList .= anchor(
-                $person->information_url ?? '#',
-                "<img
-                    src='{$person->image->thumbnail_url}'
-                    alt='{$person->full_name}'
-                    class='object-cover w-12 h-12 rounded-full' />",
-                [
-                    'class' =>
-                        'flex-shrink-0 focus:outline-none focus:ring focus:ring-inset',
-                    'target' => '_blank',
-                    'rel' => 'noreferrer noopener',
-                    'title' =>
-                        '<strong>' .
-                        $person->full_name .
-                        '</strong>' .
-                        implode(
-                            '',
-                            array_map(function ($role) {
-                                return '<br />' .
-                                    lang(
-                                        'PersonsTaxonomy.persons.' .
-                                            $role->group .
-                                            '.roles.' .
-                                            $role->role .
-                                            '.label',
-                                    );
-                            }, $person->roles),
-                        ),
-                    'data-toggle' => 'tooltip',
-                    'data-placement' => 'bottom',
-                ],
-            );
-        }
-
-        return $personList . '</div>';
-    }
-}
-
-// ------------------------------------------------------------------------
-
-if (! function_exists('play_episode_button')) {
-    /**
-     * Returns play episode button
-     */
-    function play_episode_button(
-        string $episodeId,
-        string $episodeThumbnail,
-        string $episodeTitle,
-        string $podcastTitle,
-        string $source,
-        string $mediaType,
-        string $class = ''
-    ): string {
-        $playLabel = lang('Common.play_episode_button.play');
-        $playingLabel = lang('Common.play_episode_button.playing');
-
-        return <<<CODE_SAMPLE
-            <play-episode-button
-                class="{$class}"
-                id="{$episodeId}"
-                imageSrc={$episodeThumbnail}
-                title="{$episodeTitle}"
-                podcast="{$podcastTitle}"
-                src="{$source}"
-                mediaType="{$mediaType}"
-                playLabel="{$playLabel}"
-                playingLabel="{$playingLabel}"
-            ></play-episode-button>
-        CODE_SAMPLE;
-    }
-}
-
-// ------------------------------------------------------------------------
-
 if (! function_exists('audio_player')) {
     /**
      * Returns audio player
diff --git a/app/Helpers/misc_helper.php b/app/Helpers/misc_helper.php
index 21a7ae5c055b8f4e8f84b9de041d8a60029f2b4a..f988de62aaf73c2269120631aacb517bcd5a9d8f 100644
--- a/app/Helpers/misc_helper.php
+++ b/app/Helpers/misc_helper.php
@@ -159,6 +159,34 @@ if (! function_exists('format_duration')) {
     }
 }
 
+
+if (! function_exists('format_duration_symbol')) {
+    /**
+     * Formats duration in seconds to an hh(h) mm(min) ss(s) string. Doesn't show leading zeros if any.
+     *
+     * ⚠️ This uses php's gmdate function so any duration > 86000 seconds (24 hours) will not be formatted properly.
+     *
+     * @param int $seconds seconds to format
+     */
+    function format_duration_symbol(int $seconds): string
+    {
+        if ($seconds < 60) {
+            return $seconds . 's';
+        }
+        if ($seconds < 3600) {
+            // < 1 hour: returns MM:SS
+            return ltrim(gmdate('i\m\i\n s\s', $seconds), '0');
+        }
+        if ($seconds < 36000) {
+            // < 10 hours: returns H:MM:SS
+            return ltrim(gmdate('h\h i\min s\s', $seconds), '0');
+        }
+        return gmdate('h\h i\min s\s', $seconds);
+    }
+}
+
+//--------------------------------------------------------------------
+
 if (! function_exists('podcast_uuid')) {
     /**
      * Generate UUIDv5 for podcast. For more information, see
diff --git a/app/Language/en/Comment.php b/app/Language/en/Comment.php
index aff97d5ee68119c99b468e3443dd676ea4f4bc98..741f78961cab027061ac5014238dec431eaa9127 100644
--- a/app/Language/en/Comment.php
+++ b/app/Language/en/Comment.php
@@ -14,7 +14,7 @@ return [
     'form' => [
         'episode_message_placeholder' => 'Write a comment...',
         'reply_to_placeholder' => 'Reply to @{actorUsername}',
-        'submit' => 'Send!',
+        'submit' => 'Send',
         'submit_reply' => 'Reply',
     ],
     'likes' => '{numberOfLikes, plural,
diff --git a/app/Language/en/Common.php b/app/Language/en/Common.php
index debed34830337d3d1d850ca6c2e8eb49f2b3e5b8..3284040fa9e8d5f2e64b475e9106248d082a4839 100644
--- a/app/Language/en/Common.php
+++ b/app/Language/en/Common.php
@@ -22,7 +22,7 @@ return [
     'home' => 'Home',
     'explicit' => 'Explicit',
     'mediumDate' => '{0,date,medium}',
-    'powered_by' => 'Powered by {castopod}.',
+    'powered_by' => 'Powered by {castopod}',
     'actions' => 'Actions',
     'pageInfo' => 'Page {currentPage} out of {pageCount}',
     'go_back' => 'Go back',
@@ -30,4 +30,8 @@ return [
         'play' => 'Play',
         'playing' => 'Playing',
     ],
+    'read_more' => 'Read more',
+    'read_less' => 'Read less',
+    'see_more' => 'See more',
+    'see_less' => 'See less',
 ];
diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php
index 565b7ba1f63cdc5492f090ee8102e1961eac2aaa..74c32b3f920e9e99214a7dbc9ac15106a0f9eb77 100644
--- a/app/Language/en/Episode.php
+++ b/app/Language/en/Episode.php
@@ -15,10 +15,15 @@ return [
     'number_abbr' => 'Ep. {episodeNumber}',
     'season_episode' => 'Season {seasonNumber} episode {episodeNumber}',
     'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
+    'persons' => '{personsCount, plural,
+        one {# person}
+        other {# persons}
+    }',
+    'persons_list' => 'Persons',
     'back_to_episodes' => 'Back to episodes of {podcast}',
     'comments' => 'Comments',
     'activity' => 'Activity',
-    'description' => 'Description',
+    'description' => 'Episode description',
     'number_of_comments' => '{numberOfComments, plural,
         one {# comment}
         other {# comments}
diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php
index 0c311ed83257cb8ef4863c809343013eed23b95c..9896a00a4635df6ddf46294791f7a91a1fa009d5 100644
--- a/app/Language/en/Podcast.php
+++ b/app/Language/en/Podcast.php
@@ -14,7 +14,7 @@ return [
     'create' => 'Create podcast',
     'import' => 'Import podcast',
     'new_episode' => 'New Episode',
-    'feed' => 'RSS',
+    'feed' => 'RSS Podcast feed',
     'view' => 'View podcast',
     'edit' => 'Edit podcast',
     'delete' => 'Delete podcast',
@@ -48,4 +48,9 @@ return [
     'funding_links' => 'Funding links for {podcastTitle}',
     'find_on' => 'Find {podcastTitle} on',
     'listen_on' => 'Listen on',
+    'persons' => '{personsCount, plural,
+        one {# person}
+        other {# persons}
+    }',
+    'persons_list' => 'Persons',
 ];
diff --git a/app/Language/en/Post.php b/app/Language/en/Post.php
index a60ef30fec25eb5bbcd3f54e7fb2efc53e559174..dc348d91bd9edb60d424d7ab4080de37477499fc 100644
--- a/app/Language/en/Post.php
+++ b/app/Language/en/Post.php
@@ -18,7 +18,7 @@ return [
         'episode_message_placeholder' => 'Write a message for the episode...',
         'episode_url_placeholder' => 'Episode URL',
         'reply_to_placeholder' => 'Reply to @{actorUsername}',
-        'submit' => 'Send!',
+        'submit' => 'Send',
         'submit_reply' => 'Reply',
     ],
     'favourites' => '{numberOfFavourites, plural,
diff --git a/app/Language/fr/Common.php b/app/Language/fr/Common.php
index 6fc0c30d3834d424677718a55e20901fedbe3392..c5694c3974a11f72b8a17f4a89eb0293945849e5 100644
--- a/app/Language/fr/Common.php
+++ b/app/Language/fr/Common.php
@@ -22,7 +22,7 @@ return [
     'home' => 'Accueil',
     'explicit' => 'Explicite',
     'mediumDate' => '{0,date,medium}',
-    'powered_by' => 'Propulsé par {castopod}.',
+    'powered_by' => 'Propulsé par {castopod}',
     'actions' => 'Actions',
     'pageInfo' => 'Page {currentPage} sur {pageCount}',
     'go_back' => 'Retour en arrière',
@@ -30,4 +30,8 @@ return [
         'play' => 'Lire',
         'playing' => 'En cours',
     ],
+    'read_more' => 'Lire plus',
+    'read_less' => 'Lire moins',
+    'see_more' => 'Voir plus',
+    'see_less' => 'Voir moins',
 ];
diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php
index c6f565c855802ab41d65f060e74a038d91e57d2a..ee8a1e4bd2361557c42eedf8d3e9e372d27e1664 100644
--- a/app/Language/fr/Episode.php
+++ b/app/Language/fr/Episode.php
@@ -15,10 +15,15 @@ return [
     'number_abbr' => 'Ep. {episodeNumber}',
     'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}',
     'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
+    'persons' => '{personsCount, plural,
+        one {# intervenant·e}
+        other {# intervenant·e·s}
+    }',
+    'persons_list' => 'Liste des intervenant·e·s',
     'back_to_episodes' => 'Retour aux épisodes de {podcast}',
     'comments' => 'Commentaires',
     'activity' => 'Activité',
-    'description' => 'Description',
+    'description' => 'Description de l’épisode',
     'number_of_comments' => '{numberOfComments, plural,
         one {# commentaire}
         other {# commentaires}
diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php
index d332a9336297b50ef01b08f0bf21f298f2f69644..d9d0a35d2b964ae79f251227b848acbfecd79c1b 100644
--- a/app/Language/fr/Podcast.php
+++ b/app/Language/fr/Podcast.php
@@ -14,7 +14,7 @@ return [
     'create' => 'Créer un podcast',
     'import' => 'Importer un podcast',
     'new_episode' => 'Créer un épisode',
-    'feed' => 'RSS',
+    'feed' => 'Podcast RSS feed',
     'view' => 'Voir le podcast',
     'edit' => 'Modifier le podcast',
     'delete' => 'Supprimer le podcast',
@@ -48,4 +48,9 @@ return [
     'funding_links' => 'Liens de financement pour {podcastTitle}',
     'find_on' => 'Trouvez {podcastTitle} sur',
     'listen_on' => 'Écoutez sur',
+    'persons' => '{personsCount, plural,
+        one {# intervenant·e}
+        other {# intervenant·e·s}
+    }',
+    'persons_list' => 'Liste des intervenant·e·s',
 ];
diff --git a/app/Libraries/CommentObject.php b/app/Libraries/CommentObject.php
index 7a4b7ce2d55a663868a1fa245f314593597eb0fb..e7fcac7ffb82a27aed508ab5474b3ac5089e058f 100644
--- a/app/Libraries/CommentObject.php
+++ b/app/Libraries/CommentObject.php
@@ -35,7 +35,12 @@ class CommentObject extends ObjectType
             $this->inReplyTo = $comment->reply_to_comment->uri;
         }
 
-        $this->replies = url_to('comment-replies', $comment->actor->username, $comment->episode->slug, $comment->id);
+        $this->replies = url_to(
+            'episode-comment-replies',
+            $comment->actor->username,
+            $comment->episode->slug,
+            $comment->id
+        );
 
         $this->cc = [$comment->actor->followers_url];
     }
diff --git a/app/Models/EpisodeCommentModel.php b/app/Models/EpisodeCommentModel.php
index cd950438e76cbc570c2542419195ff67e3e3d773..3440802513e2cc5c2e83944e4fe6c363b27164d4 100644
--- a/app/Models/EpisodeCommentModel.php
+++ b/app/Models/EpisodeCommentModel.php
@@ -93,7 +93,7 @@ class EpisodeCommentModel extends UuidModel
         if ($registerActivity) {
             // set post id and uri to construct NoteObject
             $comment->id = $newCommentId;
-            $comment->uri = url_to('comment', $comment->actor->username, $comment->episode->slug, $comment->id);
+            $comment->uri = url_to('episode-comment', $comment->actor->username, $comment->episode->slug, $comment->id);
 
             $createActivity = new CreateActivity();
             $createActivity
@@ -193,7 +193,7 @@ class EpisodeCommentModel extends UuidModel
             $episode = model('EpisodeModel', false)
                 ->find((int) $data['data']['episode_id']);
 
-            $data['data']['uri'] = url_to('comment', $actor->username, $episode->slug, $uuid4->toString());
+            $data['data']['uri'] = url_to('episode-comment', $actor->username, $episode->slug, $uuid4->toString());
         }
 
         return $data;
diff --git a/app/Resources/icons/send-plane.svg b/app/Resources/icons/send-plane.svg
new file mode 100644
index 0000000000000000000000000000000000000000..74ffbcf9bd622ebb1d66ac500eef82eb2b742b59
--- /dev/null
+++ b/app/Resources/icons/send-plane.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g>
+        <path fill="none" d="M0 0h24v24H0z"/>
+        <path d="M3 13h6v-2H3V1.846a.5.5 0 0 1 .741-.438l18.462 10.154a.5.5 0 0 1 0 .876L3.741 22.592A.5.5 0 0 1 3 22.154V13z"/>
+    </g>
+</svg>
diff --git a/app/Resources/js/app.ts b/app/Resources/js/app.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f1093cda325ef6a8213e0f524675fac82de73c10
--- /dev/null
+++ b/app/Resources/js/app.ts
@@ -0,0 +1,3 @@
+import Dropdown from "./modules/Dropdown";
+
+Dropdown();
diff --git a/app/Resources/js/audio-player.ts b/app/Resources/js/audio-player.ts
index 0a120376b43693ad804e53a60a5fa8ae26fc80e1..41a17ab8d62b3f56d74257c2df0c4c00ba777361 100644
--- a/app/Resources/js/audio-player.ts
+++ b/app/Resources/js/audio-player.ts
@@ -39,7 +39,7 @@ import "./modules/play-episode-button";
 
 const player = html`<div
   id="castopod-audio-player"
-  class="fixed bottom-0 left-0 flex flex-col w-full bg-white border-t sm:flex-row"
+  class="fixed bottom-0 left-0 flex flex-col w-full bg-white border-t sm:flex-row z-50"
   data-episode="-1"
   style="display: none;"
 >
diff --git a/app/Resources/js/modules/play-episode-button.ts b/app/Resources/js/modules/play-episode-button.ts
index 7ec0d41705620e31db24c6259919e654115dbff1..d8c51fd49052184c6f67a0d8007e4c65b97ba45a 100644
--- a/app/Resources/js/modules/play-episode-button.ts
+++ b/app/Resources/js/modules/play-episode-button.ts
@@ -188,35 +188,43 @@ export class PlayEpisodeButton extends LitElement {
 
   static styles = css`
     button {
-      background-color: #ffffff;
+      background-color: #009486;
       cursor: pointer;
       display: inline-flex;
       align-items: center;
-      padding: 0.25rem 0.5rem;
+      padding: 0.5rem 0.5rem;
       font-size: 0.875rem;
       line-height: 1.25rem;
-      font-weight: 600;
-      border-width: 2px;
-      border-style: solid;
+      border: 2px solid transparent;
       border-radius: 9999px;
-      border-color: rgba(207, 247, 243, 1);
 
       box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
     }
 
     button:hover {
-      border-color: #009486;
-      background-color: #ebf8f8;
+      background-color: #00564a;
     }
 
     button:focus {
-      background-color: #ebf8f8;
+      box-shadow: 0 0 0 2px #e7f9e4, 0 0 0 calc(2px + 2px) #009486;
+    }
+
+    button.playing {
+      background-color: #f2faf9;
+      border: 2px solid #009486;
+    }
+
+    button.playing:hover {
+      background-color: #e7f9e4;
+    }
+
+    button.playing svg {
+      color: #009486;
     }
 
     svg {
       font-size: 1.5rem;
-      margin-right: 0.25rem;
-      color: #009486;
+      color: #ffffff;
     }
 
     @keyframes spin {
@@ -231,33 +239,37 @@ export class PlayEpisodeButton extends LitElement {
   `;
 
   render(): TemplateResult<1> {
-    return html`<button @click="${this.isPlaying ? this.pause : this.play}">
+    return html`<button
+      class="${this.isPlaying ? "playing" : ""}"
+      @click="${this.isPlaying ? this.pause : this.play}"
+      title="${this.isPlaying ? this.playingLabel : this.playLabel}"
+    >
       ${this.isPlaying
         ? html`<svg
-              class="animate-spin"
-              viewBox="0 0 24 24"
-              fill="currentColor"
-              width="1em"
-              height="1em"
-            >
-              <g>
-                <path fill="none" d="M0 0h24v24H0z" />
-                <path
-                  d="M13 9.17A3 3 0 1 0 15 12V2.458c4.057 1.274 7 5.064 7 9.542 0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2c.337 0 .671.017 1 .05v7.12z"
-                />
-              </g></svg
-            >${this.playingLabel}`
-        : html`<svg
-              viewBox="0 0 24 24"
-              fill="currentColor"
-              width="1em"
-              height="1em"
-            >
+            class="animate-spin"
+            viewBox="0 0 24 24"
+            fill="currentColor"
+            width="1em"
+            height="1em"
+          >
+            <g>
               <path fill="none" d="M0 0h24v24H0z" />
               <path
-                d="M7.752 5.439l10.508 6.13a.5.5 0 0 1 0 .863l-10.508 6.13A.5.5 0 0 1 7 18.128V5.871a.5.5 0 0 1 .752-.432z"
-              /></svg
-            >${this.playLabel}`}
+                d="M13 9.17A3 3 0 1 0 15 12V2.458c4.057 1.274 7 5.064 7 9.542 0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2c.337 0 .671.017 1 .05v7.12z"
+              />
+            </g>
+          </svg>`
+        : html`<svg
+            viewBox="0 0 24 24"
+            fill="currentColor"
+            width="1em"
+            height="1em"
+          >
+            <path fill="none" d="M0 0h24v24H0z" />
+            <path
+              d="M7.752 5.439l10.508 6.13a.5.5 0 0 1 0 .863l-10.508 6.13A.5.5 0 0 1 7 18.128V5.871a.5.5 0 0 1 .752-.432z"
+            />
+          </svg>`}
     </button>`;
   }
 }
diff --git a/app/Resources/js/podcast.ts b/app/Resources/js/podcast.ts
index f3036ec29722eb51b69344ec5eeececbb1f3dd64..c5250eed802e7c67580f172fcf71054d244239cc 100644
--- a/app/Resources/js/podcast.ts
+++ b/app/Resources/js/podcast.ts
@@ -1,10 +1,8 @@
 import "@github/time-elements";
-import Dropdown from "./modules/Dropdown";
 import Time from "./modules/Time";
 import Toggler from "./modules/Toggler";
 import Tooltip from "./modules/Tooltip";
 
-Dropdown();
 Time();
 Toggler();
 Tooltip();
diff --git a/app/Resources/styles/index.css b/app/Resources/styles/index.css
index ec4dd9494bef4587b82f221d814b1099046dcd22..1ce07be46fee2cde8f1e79ab7353cd4298a02f3c 100644
--- a/app/Resources/styles/index.css
+++ b/app/Resources/styles/index.css
@@ -12,3 +12,5 @@
 @import "./radioToggler.css";
 @import "./formInputTabs.css";
 @import "./stickyHeader.css";
+@import "./readMore.css";
+@import "./seeMore.css";
diff --git a/app/Resources/styles/layout.css b/app/Resources/styles/layout.css
index 61b88e4c28fd46cd36a01f33e3d511d8f69f2c25..75875d4e23168f010bb5a563b154e43b717f2e86 100644
--- a/app/Resources/styles/layout.css
+++ b/app/Resources/styles/layout.css
@@ -1,34 +1,36 @@
 /* Admin layout */
-.holy-grail-grid {
-  @apply grid min-h-screen overflow-y-auto;
-  grid-template: auto 1fr auto / auto 1fr;
+@layer base {
+  .holy-grail-grid {
+    @apply grid min-h-screen overflow-y-auto;
+    grid-template: 40px 1fr / 300px 1fr;
 
-  & .holy-grail__header {
-    @apply h-10 col-start-1 col-end-4 row-start-1 row-end-2;
-  }
-
-  & .holy-grail__sidebar {
-    @apply col-start-1 col-end-2 row-start-2 row-end-4;
-
-    width: 300px;
-    max-height: calc(100vh - 2.5rem);
-  }
+    & .holy-grail__header {
+      @apply h-10 col-start-1 col-end-4 row-start-1 row-end-2;
+    }
 
-  & .holy-grail__main {
-    @apply col-start-1 col-end-3 row-start-2 row-end-4;
-  }
+    & .holy-grail__sidebar {
+      @apply col-start-1 col-end-2 row-start-2 row-end-4;
 
-  & .holy-grail__footer {
-    @apply col-start-1 col-end-3 row-start-3 row-end-4;
-  }
+      width: 300px;
+      max-height: calc(100vh - 2.5rem);
+    }
 
-  @screen md {
     & .holy-grail__main {
-      @apply col-start-2;
+      @apply col-start-1 col-end-3 row-start-2 row-end-3;
     }
 
     & .holy-grail__footer {
-      @apply col-start-2;
+      @apply col-start-1 col-end-3 row-start-3 row-end-3;
+    }
+
+    @screen md {
+      & .holy-grail__main {
+        @apply col-start-2;
+      }
+
+      & .holy-grail__footer {
+        @apply col-start-2;
+      }
     }
   }
 }
diff --git a/app/Resources/styles/readMore.css b/app/Resources/styles/readMore.css
new file mode 100644
index 0000000000000000000000000000000000000000..d83e0bee0839fdc36603a4c72f96a76888d31c3e
--- /dev/null
+++ b/app/Resources/styles/readMore.css
@@ -0,0 +1,53 @@
+/*
+Read more component (basic unstyled component)
+-- see https://codepen.io/yassinedoghri/pen/QWpwoxp for more info and possible caveats
+*/
+
+@layer components {
+  .read-more {
+    @apply flex flex-col items-start;
+    /* You can update this variable directly in the html with the style attribute: style="--line-clamp: 3" */
+    --line-clamp: 3;
+  }
+
+  .read-more__text {
+    @apply overflow-hidden;
+    display: -webkit-box;
+    -webkit-line-clamp: var(--line-clamp);
+    -webkit-box-orient: vertical;
+  }
+
+  .read-more__checkbox {
+    @apply absolute overflow-hidden whitespace-nowrap;
+    clip: rect(0 0 0 0);
+    clip-path: inset(100%);
+    height: 1px;
+    width: 1px;
+  }
+
+  .read-more__checkbox ~ .read-more__label {
+    @apply text-xs font-semibold underline cursor-pointer;
+  }
+
+  /* Don't forget focus and hover styles for accessibility! */
+  .read-more__checkbox:focus ~ .read-more__label {
+    @apply ring;
+  }
+
+  .read-more__checkbox:hover ~ .read-more__label {
+    @apply no-underline;
+  }
+
+  .read-more__checkbox ~ .read-more__label::before {
+    content: attr(data-read-more);
+  }
+
+  .read-more__checkbox:checked ~ .read-more__label::before {
+    content: attr(data-read-less);
+  }
+
+  .read-more__checkbox:checked ~ .read-more__text {
+    --line-clamp: none;
+    -webkit-line-clamp: var(--line-clamp);
+  }
+}
diff --git a/app/Resources/styles/seeMore.css b/app/Resources/styles/seeMore.css
new file mode 100644
index 0000000000000000000000000000000000000000..2a0d78477d8648430ef47f68ef60488b82fa4720
--- /dev/null
+++ b/app/Resources/styles/seeMore.css
@@ -0,0 +1,52 @@
+@layer components {
+  .see-more {
+    @apply flex flex-col items-start;
+  }
+
+  .see-more__content {
+    @apply relative overflow-hidden;
+    height: 5rem;
+  }
+
+  .see-more_content-fade {
+    @apply absolute bottom-0 left-0 w-full h-full pointer-events-none;
+    background-image: linear-gradient(to bottom, transparent, #00564a);
+  }
+
+  .see-more__checkbox {
+    @apply absolute overflow-hidden whitespace-nowrap;
+    clip: rect(0 0 0 0);
+    clip-path: inset(100%);
+    height: 1px;
+    width: 1px;
+  }
+
+  .see-more__checkbox ~ .see-more__label {
+    @apply text-xs font-semibold underline cursor-pointer;
+  }
+
+  /* Don't forget focus and hover styles for accessibility! */
+  .see-more__checkbox:focus ~ .see-more__label {
+    @apply ring;
+  }
+
+  .see-more__checkbox:hover ~ .see-more__label {
+    @apply no-underline;
+  }
+
+  .see-more__checkbox ~ .see-more__label::before {
+    content: attr(data-see-more);
+  }
+
+  .see-more__checkbox:checked ~ .see-more__label::before {
+    content: attr(data-see-less);
+  }
+
+  .see-more__checkbox:checked ~ .see-more__content {
+    @apply h-auto;
+  }
+
+  .see-more__checkbox:checked ~ .see-more__content .see-more_content-fade {
+    @apply bg-none;
+  }
+}
diff --git a/app/Views/Components/Button.php b/app/Views/Components/Button.php
index dc2080fccce036d9b04fb84e29c81ae69658c37d..c7eb5107198a75bebee9e90b15cc097114c14706 100644
--- a/app/Views/Components/Button.php
+++ b/app/Views/Components/Button.php
@@ -48,7 +48,7 @@ class Button extends Component
         ];
 
         $basePaddings = [
-            'small' => 'px-2 py-1',
+            'small' => 'px-3 py-1',
             'base' => 'px-3 py-2',
             'large' => 'px-4 py-2',
         ];
diff --git a/app/Views/Components/DropdownMenu.php b/app/Views/Components/DropdownMenu.php
index d806220ec17f8bcef29fad9f32eb9a353e692313..62a0997c49b49151b9c4a0258324319b1f2d1f36 100644
--- a/app/Views/Components/DropdownMenu.php
+++ b/app/Views/Components/DropdownMenu.php
@@ -32,6 +32,9 @@ class DropdownMenu extends Component
                         'class' => 'px-4 py-1 hover:bg-gray-100' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''),
                     ]);
                     break;
+                case 'html':
+                    $menuItems .= html_entity_decode($item['content']);
+                    break;
                 case 'separator':
                     $menuItems .= '<hr class="my-2 border border-gray-100">';
                     break;
diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php
index a42bc041f21009962f1aa18b582d7521ed63b573..5307742d2e8bcdd2d65a59f92677219e8da0f5ed 100644
--- a/app/Views/Components/Forms/MarkdownEditor.php
+++ b/app/Views/Components/Forms/MarkdownEditor.php
@@ -13,7 +13,7 @@ class MarkdownEditor extends FormComponent
         $this->attributes['class'] = 'border-none outline-none focus:border-none focus:outline-none focus:ring-0 w-full h-full';
         $this->attributes['rows'] = 6;
 
-        $textarea = form_textarea($this->attributes, old($this->name, $this->value, false));
+        $textarea = form_textarea($this->attributes, old($this->name, html_entity_decode($this->value), null));
         $icons = [
             'heading' => icon('heading'),
             'bold' => icon('bold'),
diff --git a/app/Views/Components/ReadMore.php b/app/Views/Components/ReadMore.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf9685e89681b5d1534c543633df8c854302aff9
--- /dev/null
+++ b/app/Views/Components/ReadMore.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Views\Components;
+
+use ViewComponents\Component;
+
+class ReadMore extends Component
+{
+    public function render(): string
+    {
+        $readMoreLabel = lang('Common.read_more');
+        $readLessLabel = lang('Common.read_less');
+        return <<<HTML
+            <div class="read-more {$this->class}" style="--line-clamp: 3">
+                <input id="read-more-checkbox_{$this->id}" type="checkbox" class="read-more__checkbox" aria-hidden="true">
+                <div class="mb-2 read-more__text">{$this->slot}</div>
+                <label for="read-more-checkbox_{$this->id}" class="read-more__label" data-read-more="{$readMoreLabel}" data-read-less="{$readLessLabel}" aria-hidden="true"></label>
+            </div>
+        HTML;
+    }
+}
diff --git a/app/Views/Components/SeeMore.php b/app/Views/Components/SeeMore.php
new file mode 100644
index 0000000000000000000000000000000000000000..22481be711895c391a2c65062bb2ab9855603de9
--- /dev/null
+++ b/app/Views/Components/SeeMore.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Views\Components;
+
+use ViewComponents\Component;
+
+class SeeMore extends Component
+{
+    public function render(): string
+    {
+        $seeMoreLabel = lang('Common.see_more');
+        $seeLessLabel = lang('Common.see_less');
+        return <<<HTML
+            <div class="see-more" style="--line-clamp: 3">
+                <input id="see-more-checkbox" type="checkbox" class="see-more__checkbox" aria-hidden="true">
+                <div class="mb-2 see-more__content {$this->class}"><div class="see-more_content-fade"></div>{$this->slot}</div>
+                <label for="see-more-checkbox" class="see-more__label" data-see-more="{$seeMoreLabel}" data-see-less="{$seeLessLabel}" aria-hidden="true"></label>
+            </div>
+        HTML;
+    }
+}
diff --git a/modules/Admin/Language/en/AdminNavigation.php b/modules/Admin/Language/en/AdminNavigation.php
index 92a5f59b676bb571ea162dcbdf4a5ebbb12e98f2..68cbefb3ebcfd0a033bb214d5ba740ed8809206a 100644
--- a/modules/Admin/Language/en/AdminNavigation.php
+++ b/modules/Admin/Language/en/AdminNavigation.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
 
 return [
     'go_to_website' => 'View site',
+    'go_to_admin' => 'Go to admin',
     'dashboard' => 'Dashboard',
     'admin' => 'Home',
     'podcasts' => 'Podcasts',
diff --git a/modules/Admin/Language/en/Common.php b/modules/Admin/Language/en/Common.php
index f05e5d6a7c749ed2ca2f01cf34aabb63f0beee7f..c5048cb9f38564cdef5e439c71cdbf66f544d5c6 100644
--- a/modules/Admin/Language/en/Common.php
+++ b/modules/Admin/Language/en/Common.php
@@ -22,7 +22,7 @@ return [
     'home' => 'Home',
     'explicit' => 'Explicit',
     'mediumDate' => '{0,date,medium}',
-    'powered_by' => 'Powered by {castopod}.',
+    'powered_by' => 'Powered by {castopod}',
     'actions' => 'Actions',
     'pageInfo' => 'Page {currentPage} out of {pageCount}',
     'go_back' => 'Go back',
diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php
index 1761148314024a84239eca8ba7a63320034c2e29..990297f8b9171e3a3b4591df72bb3ff3b0a50e44 100644
--- a/modules/Admin/Language/en/Episode.php
+++ b/modules/Admin/Language/en/Episode.php
@@ -15,10 +15,6 @@ return [
     'number_abbr' => 'Ep. {episodeNumber}',
     'season_episode' => 'Season {seasonNumber} episode {episodeNumber}',
     'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
-    'back_to_episodes' => 'Back to episodes of {podcast}',
-    'comments' => 'Comments',
-    'activity' => 'Activity',
-    'description' => 'Description',
     'number_of_comments' => '{numberOfComments, plural,
         one {# comment}
         other {# comments}
diff --git a/modules/Admin/Language/en/Podcast.php b/modules/Admin/Language/en/Podcast.php
index 2a107c52d1c037892edc7f2d0098a25f8b46cc59..d2aaa10126b40522c520474f5181d2c2fb2bc0d0 100644
--- a/modules/Admin/Language/en/Podcast.php
+++ b/modules/Admin/Language/en/Podcast.php
@@ -14,7 +14,6 @@ return [
     'create' => 'Create podcast',
     'import' => 'Import podcast',
     'new_episode' => 'New Episode',
-    'feed' => 'RSS',
     'view' => 'View podcast',
     'edit' => 'Edit podcast',
     'delete' => 'Delete podcast',
diff --git a/modules/Admin/Language/fr/Common.php b/modules/Admin/Language/fr/Common.php
index 2ab2171c708e377953d98f7ab6ac0bb284bfb98c..e02f1b02f0764faad08d7a573ffc218c7861df8c 100644
--- a/modules/Admin/Language/fr/Common.php
+++ b/modules/Admin/Language/fr/Common.php
@@ -22,7 +22,7 @@ return [
     'home' => 'Accueil',
     'explicit' => 'Explicite',
     'mediumDate' => '{0,date,medium}',
-    'powered_by' => 'Propulsé par {castopod}.',
+    'powered_by' => 'Propulsé par {castopod}',
     'actions' => 'Actions',
     'pageInfo' => 'Page {currentPage} sur {pageCount}',
     'go_back' => 'Retour en arrière',
diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php
index bafa62319b8e6bfb1f0d7578f050630cbf2c98d8..857b6754ffaee2519c70f402cf04ad029acc9f02 100644
--- a/modules/Admin/Language/fr/Episode.php
+++ b/modules/Admin/Language/fr/Episode.php
@@ -16,9 +16,6 @@ return [
     'season_episode' => 'Saison {seasonNumber} épisode {episodeNumber}',
     'season_episode_abbr' => 'S{seasonNumber}E{episodeNumber}',
     'back_to_episodes' => 'Retour aux épisodes de {podcast}',
-    'comments' => 'Commentaires',
-    'activity' => 'Activité',
-    'description' => 'Description',
     'number_of_comments' => '{numberOfComments, plural,
         one {# commentaire}
         other {# commentaires}
diff --git a/modules/Admin/Language/fr/Podcast.php b/modules/Admin/Language/fr/Podcast.php
index a4919c6d14601883c65eb07df0539bfa085b2aa3..0d332120f9e7ec2a15c58f4fcb911d16c250459a 100644
--- a/modules/Admin/Language/fr/Podcast.php
+++ b/modules/Admin/Language/fr/Podcast.php
@@ -14,7 +14,6 @@ return [
     'create' => 'Créer un podcast',
     'import' => 'Importer un podcast',
     'new_episode' => 'Créer un épisode',
-    'feed' => 'RSS',
     'view' => 'Voir le podcast',
     'edit' => 'Modifier le podcast',
     'delete' => 'Supprimer le podcast',
diff --git a/modules/Fediverse/Entities/Post.php b/modules/Fediverse/Entities/Post.php
index c6639646078cbb8ca59b5709f7cc4549db18d02d..6177aa371bc85037aac1d3fa2ba0b9cac746fc6e 100644
--- a/modules/Fediverse/Entities/Post.php
+++ b/modules/Fediverse/Entities/Post.php
@@ -31,7 +31,6 @@ use RuntimeException;
  * @property Time $published_at
  * @property Time $created_at
  *
- * @property bool $has_preview_card
  * @property PreviewCard|null $preview_card
  *
  * @property bool $has_replies
@@ -48,8 +47,6 @@ class Post extends UuidEntity
 
     protected ?PreviewCard $preview_card = null;
 
-    protected bool $has_preview_card = false;
-
     /**
      * @var Post[]|null
      */
@@ -119,11 +116,6 @@ class Post extends UuidEntity
         return $this->preview_card;
     }
 
-    public function getHasPreviewCard(): bool
-    {
-        return $this->getPreviewCard() !== null;
-    }
-
     /**
      * @return Post[]
      */
diff --git a/package-lock.json b/package-lock.json
index 76d0a2a41d219daaeb859d0590fb37723d79d185..90940192987b76e186da9379a2616d6e4b125b3d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,34 +15,35 @@
         "@codemirror/commands": "^0.19.5",
         "@codemirror/lang-xml": "^0.19.2",
         "@codemirror/state": "^0.19.2",
-        "@codemirror/view": "^0.19.8",
+        "@codemirror/view": "^0.19.9",
         "@github/clipboard-copy-element": "^1.1.2",
-        "@github/markdown-toolbar-element": "^1.5.1",
+        "@github/markdown-toolbar-element": "^1.5.3",
         "@github/time-elements": "^3.1.2",
         "@popperjs/core": "^2.10.2",
-        "@vime/core": "^5.0.33",
+        "@vime/core": "^5.0.34",
         "choices.js": "^9.0.1",
         "flatpickr": "^4.6.9",
         "leaflet": "^1.7.1",
         "leaflet.markercluster": "^1.5.1",
-        "lit": "^2.0.0",
-        "marked": "^3.0.4",
+        "lit": "^2.0.2",
+        "marked": "^3.0.7",
         "xml-formatter": "^2.4.1"
       },
       "devDependencies": {
-        "@commitlint/cli": "^13.2.0",
+        "@commitlint/cli": "^13.2.1",
         "@commitlint/config-conventional": "^13.2.0",
         "@semantic-release/changelog": "^6.0.0",
         "@semantic-release/exec": "^6.0.1",
         "@semantic-release/git": "^10.0.0",
         "@semantic-release/gitlab": "^7.0.3",
+        "@tailwindcss/aspect-ratio": "^0.3.0",
         "@tailwindcss/forms": "^0.3.4",
-        "@tailwindcss/line-clamp": "^0.2.1",
+        "@tailwindcss/line-clamp": "^0.2.2",
         "@tailwindcss/typography": "^0.4.1",
         "@types/leaflet": "^1.7.5",
         "@types/marked": "^3.0.1",
-        "@typescript-eslint/eslint-plugin": "^4.32.0",
-        "@typescript-eslint/parser": "^4.32.0",
+        "@typescript-eslint/eslint-plugin": "^4.33.0",
+        "@typescript-eslint/parser": "^4.33.0",
         "cross-env": "^7.0.3",
         "cssnano": "^5.0.8",
         "cz-conventional-changelog": "^3.3.0",
@@ -51,7 +52,7 @@
         "eslint-plugin-prettier": "^4.0.0",
         "husky": "^7.0.2",
         "is-ci": "^3.0.0",
-        "lint-staged": "^11.1.2",
+        "lint-staged": "^11.2.3",
         "postcss-import": "^14.0.2",
         "postcss-preset-env": "^6.7.0",
         "postcss-reporter": "^7.0.3",
@@ -64,7 +65,7 @@
         "tailwindcss": "^2.2.16",
         "tailwindcss-scroll-snap": "^1.1.0",
         "typescript": "^4.4.3",
-        "vite": "^2.6.1"
+        "vite": "^2.6.5"
       }
     },
     "node_modules/@amcharts/amcharts4": {
@@ -2098,6 +2099,15 @@
         "node": ">=10"
       }
     },
+    "node_modules/@tailwindcss/aspect-ratio": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.3.0.tgz",
+      "integrity": "sha512-DMgWskNJR6FNPLbQ8Xoq/PKV/9DfNKh5dvKB+SM8x7lVl4+pnxlZ3Ns4+yGmurA/ze708HrnCG1tXk85HolJmw==",
+      "dev": true,
+      "peerDependencies": {
+        "tailwindcss": ">=2.0.0 || >=3.0.0-alpha.1"
+      }
+    },
     "node_modules/@tailwindcss/forms": {
       "version": "0.3.4",
       "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz",
@@ -5947,20 +5957,6 @@
       "resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz",
       "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg=="
     },
-    "node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "dev": true,
-      "hasInstallScript": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
     "node_modules/function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -8205,12 +8201,6 @@
       "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
       "dev": true
     },
-    "node_modules/nanocolors": {
-      "version": "0.2.12",
-      "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz",
-      "integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==",
-      "dev": true
-    },
     "node_modules/nanoid": {
       "version": "3.1.30",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
@@ -18922,6 +18912,13 @@
         "defer-to-connect": "^2.0.0"
       }
     },
+    "@tailwindcss/aspect-ratio": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.3.0.tgz",
+      "integrity": "sha512-DMgWskNJR6FNPLbQ8Xoq/PKV/9DfNKh5dvKB+SM8x7lVl4+pnxlZ3Ns4+yGmurA/ze708HrnCG1tXk85HolJmw==",
+      "dev": true,
+      "requires": {}
+    },
     "@tailwindcss/forms": {
       "version": "0.3.4",
       "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.4.tgz",
@@ -21927,13 +21924,6 @@
       "resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz",
       "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg=="
     },
-    "fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "dev": true,
-      "optional": true
-    },
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -23646,12 +23636,6 @@
       "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
       "dev": true
     },
-    "nanocolors": {
-      "version": "0.2.12",
-      "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.12.tgz",
-      "integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==",
-      "dev": true
-    },
     "nanoid": {
       "version": "3.1.30",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
diff --git a/package.json b/package.json
index e9ba0a513a0c57fbf0746d6add3776a76f4d7053..9af9cb361fd711688906827b9bf658ce4f770845 100644
--- a/package.json
+++ b/package.json
@@ -27,62 +27,63 @@
     "prepare": "is-ci || husky install"
   },
   "dependencies": {
-    "@amcharts/amcharts4-geodata": "^4.1.22",
     "@amcharts/amcharts4": "^4.10.22",
+    "@amcharts/amcharts4-geodata": "^4.1.22",
     "@codemirror/basic-setup": "^0.19.0",
     "@codemirror/commands": "^0.19.5",
     "@codemirror/lang-xml": "^0.19.2",
     "@codemirror/state": "^0.19.2",
-    "@codemirror/view": "^0.19.8",
+    "@codemirror/view": "^0.19.9",
     "@github/clipboard-copy-element": "^1.1.2",
-    "@github/markdown-toolbar-element": "^1.5.1",
+    "@github/markdown-toolbar-element": "^1.5.3",
     "@github/time-elements": "^3.1.2",
     "@popperjs/core": "^2.10.2",
-    "@vime/core": "^5.0.33",
+    "@vime/core": "^5.0.34",
     "choices.js": "^9.0.1",
     "flatpickr": "^4.6.9",
-    "leaflet.markercluster": "^1.5.1",
     "leaflet": "^1.7.1",
-    "lit": "^2.0.0",
-    "marked": "^3.0.4",
+    "leaflet.markercluster": "^1.5.1",
+    "lit": "^2.0.2",
+    "marked": "^3.0.7",
     "xml-formatter": "^2.4.1"
   },
   "devDependencies": {
-    "@commitlint/cli": "^13.2.0",
+    "@commitlint/cli": "^13.2.1",
     "@commitlint/config-conventional": "^13.2.0",
     "@semantic-release/changelog": "^6.0.0",
     "@semantic-release/exec": "^6.0.1",
     "@semantic-release/git": "^10.0.0",
     "@semantic-release/gitlab": "^7.0.3",
+    "@tailwindcss/aspect-ratio": "^0.3.0",
     "@tailwindcss/forms": "^0.3.4",
-    "@tailwindcss/line-clamp": "^0.2.1",
+    "@tailwindcss/line-clamp": "^0.2.2",
     "@tailwindcss/typography": "^0.4.1",
     "@types/leaflet": "^1.7.5",
     "@types/marked": "^3.0.1",
-    "@typescript-eslint/eslint-plugin": "^4.32.0",
-    "@typescript-eslint/parser": "^4.32.0",
+    "@typescript-eslint/eslint-plugin": "^4.33.0",
+    "@typescript-eslint/parser": "^4.33.0",
     "cross-env": "^7.0.3",
     "cssnano": "^5.0.8",
     "cz-conventional-changelog": "^3.3.0",
+    "eslint": "^7.32.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint": "^7.32.0",
     "husky": "^7.0.2",
     "is-ci": "^3.0.0",
-    "lint-staged": "^11.1.2",
+    "lint-staged": "^11.2.3",
     "postcss-import": "^14.0.2",
     "postcss-preset-env": "^6.7.0",
     "postcss-reporter": "^7.0.3",
-    "prettier-plugin-organize-imports": "^2.3.4",
     "prettier": "2.4.1",
+    "prettier-plugin-organize-imports": "^2.3.4",
     "semantic-release": "^18.0.0",
-    "stylelint-config-standard": "^22.0.0",
     "stylelint": "^13.13.1",
+    "stylelint-config-standard": "^22.0.0",
     "svgo": "^2.7.0",
-    "tailwindcss-scroll-snap": "^1.1.0",
     "tailwindcss": "^2.2.16",
+    "tailwindcss-scroll-snap": "^1.1.0",
     "typescript": "^4.4.3",
-    "vite": "^2.6.1"
+    "vite": "^2.6.5"
   },
   "lint-staged": {
     "*.{js,ts,css,md,json}": "prettier --write",
diff --git a/tailwind.config.js b/tailwind.config.js
index ea55a83cccf00bd5c5ad5fe78510f622ff63a6a0..dcb69af660323e669c1552ce37c8ac2993e97a7f 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -16,7 +16,6 @@ module.exports = {
       fontFamily: {
         sans: ["Inter", ...defaultTheme.fontFamily.sans],
         display: ["Kumbh Sans", ...defaultTheme.fontFamily.sans],
-        // body: ["Inter", ...defaultTheme.fontFamily.sans],
       },
       colors: {
         pine: {
@@ -49,6 +48,7 @@ module.exports = {
         112: "28rem",
       },
       gridTemplateColumns: {
+        podcastLayout: "1fr minmax(auto, 768px) 1fr",
         podcasts: "repeat(auto-fill, minmax(14rem, 1fr))",
       },
       zIndex: {
@@ -67,6 +67,7 @@ module.exports = {
     require("@tailwindcss/forms"),
     require("@tailwindcss/typography"),
     require("@tailwindcss/line-clamp"),
+    require("@tailwindcss/aspect-ratio"),
     require("tailwindcss-scroll-snap"),
   ],
 };
diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php
index 5269b9e9c067143a6442b0dd8fab91736184767f..c2f212247d8264a12d55bfa1bf82f844fd85b74c 100644
--- a/themes/cp_admin/_layout.php
+++ b/themes/cp_admin/_layout.php
@@ -19,48 +19,7 @@
 
 <body class="relative bg-pine-50 holy-grail-grid">
     <div id="sidebar-backdrop" role="button" tabIndex="0" aria-label="Close" class="fixed z-50 hidden w-full h-full bg-gray-900 bg-opacity-50 md:hidden"></div>
-    <header class="sticky top-0 z-50 flex items-center justify-between h-10 text-white border-b holy-grail__header bg-pine-800 border-pine-900">
-        <div class="inline-flex items-center h-full">
-            <a href="<?= route_to(
-            'admin',
-        ) ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
-                <?= (isset($podcast) ? icon('arrow-left', 'mr-2') : '') . svg('castopod-logo-base', 'h-6') ?>
-            </a>
-            <a href="<?= route_to(
-            'home',
-        ) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
-                    <?= lang('AdminNavigation.go_to_website') ?>
-                    <?= icon('external-link', 'ml-1 opacity-60') ?>
-            </a>
-        </div>
-        <button
-            type="button"
-            class="inline-flex items-center h-full px-6 mt-auto font-semibold outline-none focus:ring"
-            id="my-account-dropdown"
-            data-dropdown="button"
-            data-dropdown-target="my-account-dropdown-menu"
-            aria-haspopup="true"
-            aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button>
-        <DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode([
-            [
-                'type' => 'link',
-                'title' => lang('AdminNavigation.account.my-account'),
-                'uri' => route_to('my-account'),
-            ],
-            [
-                'type' => 'link',
-                'title' => lang('AdminNavigation.account.change-password'),
-                'uri' => route_to('change-password'),
-            ],
-            [
-                'type' => 'separator',
-            ],
-            [
-                'type' => 'link',
-                'title' => lang('AdminNavigation.account.logout'),
-                'uri' => route_to('logout'),
-            ], ])) ?>" />
-    </header>
+    <?= $this->include('_partials/_nav_header') ?>
     <aside id="admin-sidebar" class="sticky z-50 flex flex-col text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-pine-900 bg-pine-800 holy-grail__sidebar md:translate-x-0">
         <?php if (isset($podcast) && isset($episode)): ?>
             <?= $this->include('episode/_sidebar') ?>
@@ -72,7 +31,7 @@
         <footer class="px-2 py-2 mx-auto text-xs text-right">
             <?= lang('Common.powered_by', [
                 'castopod' =>
-                    '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a> ' .
+                    '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a> ' .
                     CP_VERSION,
             ]) ?>
         </footer>
diff --git a/themes/cp_admin/_partials/_nav_header.php b/themes/cp_admin/_partials/_nav_header.php
new file mode 100644
index 0000000000000000000000000000000000000000..1fcac6ae1fd8b734eb0f26556baf9c5d97f68e96
--- /dev/null
+++ b/themes/cp_admin/_partials/_nav_header.php
@@ -0,0 +1,87 @@
+<header class="sticky top-0 z-50 flex items-center justify-between h-10 text-white border-b holy-grail__header bg-pine-800 border-pine-900">
+    <div class="inline-flex items-center h-full">
+        <a href="<?= route_to(
+    'admin',
+) ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
+            <?= (isset($podcast) ? icon('arrow-left', 'mr-2') : '') . svg('castopod-logo-base', 'h-6') ?>
+        </a>
+        <a href="<?= route_to(
+    'home',
+) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
+                <?= lang('AdminNavigation.go_to_website') ?>
+                <?= icon('external-link', 'ml-1 opacity-60') ?>
+        </a>
+    </div>
+    <button
+        type="button"
+        class="inline-flex items-center h-full px-3 text-sm font-semibold outline-none focus:ring gap-x-2"
+        id="my-account-dropdown"
+        data-dropdown="button"
+        data-dropdown-target="my-account-dropdown-menu"
+        aria-haspopup="true"
+        aria-expanded="false"><div class="relative mr-1">
+            <?= icon('account-circle', 'text-3xl opacity-60') ?>
+            <?= user()
+                ->podcasts === [] ? '' : '<img src="' . interact_as_actor()->avatar_image_url . '" class="absolute bottom-0 w-4 h-4 rounded-full -right-1" />' ?>
+        </div>
+        <?= user()->username ?>
+        <?= icon('caret-down', 'ml-auto text-2xl') ?></button>
+    <?php
+        $interactButtons = '';
+        foreach (user()->podcasts as $userPodcast) {
+            $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-pine-800 text-white rounded-full') : '';
+
+            $interactButtons .= <<<CODE_SAMPLE
+                <button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
+                    <span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->image->thumbnail_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
+                </button>
+            CODE_SAMPLE;
+        }
+
+        $interactAsText = lang('Admin.choose_interact');
+        $route = route_to('interact-as-actor');
+        $csrfField = csrf_field();
+
+        $menuItems = [
+            [
+                'type' => 'link',
+                'title' => lang('AdminNavigation.account.my-account'),
+                'uri' => route_to('my-account'),
+            ],
+            [
+                'type' => 'link',
+                'title' => lang('AdminNavigation.account.change-password'),
+                'uri' => route_to('change-password'),
+            ],
+            [
+                'type' => 'separator',
+            ],
+            [
+                'type' => 'link',
+                'title' => lang('AdminNavigation.account.logout'),
+                'uri' => route_to('logout'),
+            ],
+        ];
+
+        if (user()->podcasts !== []) {
+            $menuItems = array_merge([
+                [
+                    'type' => 'html',
+                    'content' => esc(<<<CODE_SAMPLE
+                        <nav class="flex flex-col py-2 text-black whitespace-no-wrap">
+                            <span class="px-4 mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase">{$interactAsText}</span>
+                            <form action="{$route}" method="POST" class="flex flex-col">
+                                {$csrfField}
+                                {$interactButtons}
+                            </form>
+                        </nav>
+                    CODE_SAMPLE),
+                ],
+                [
+                    'type' => 'separator',
+                ],
+            ], $menuItems);
+        }
+    ?>
+    <DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode($menuItems)) ?>" />
+</header>
\ No newline at end of file
diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php
index e82e2aee2c67c1556d23ed6b66b07d628a0990aa..88022107f9a42de3a4093b939bbefa5a98a0b359 100644
--- a/themes/cp_admin/episode/edit.php
+++ b/themes/cp_admin/episode/edit.php
@@ -120,7 +120,7 @@
     as="MarkdownEditor"
     name="description"
     label="<?= lang('Episode.form.description') ?>"
-    value="<?= $episode->description_markdown ?>"
+    value="<?= htmlspecialchars($episode->description_markdown) ?>"
     required="true" />
 
 <Forms.Field
@@ -128,7 +128,7 @@
     name="description_footer"
     label="<?= lang('Episode.form.description_footer') ?>"
     hint="<?= lang('Episode.form.description_footer_hint') ?>"
-    value="<?= $podcast->episode_description_footer_markdown ?? '' ?>" />
+    value="<?= htmlspecialchars($podcast->episode_description_footer_markdown) ?? '' ?>" />
 
 </Forms.Section>
 
diff --git a/themes/cp_admin/page/edit.php b/themes/cp_admin/page/edit.php
index a8957e20b961fa3c088d3a630f8e70ee58c8d1d6..fc9a8254b946c9f9971fd89b0468d76ea33cb7ec 100644
--- a/themes/cp_admin/page/edit.php
+++ b/themes/cp_admin/page/edit.php
@@ -35,7 +35,7 @@
     as="MarkdownEditor"
     name="content"
     label="<?= lang('Page.form.content') ?>"
-    value="<?= $page->content_markdown ?>"
+    value="<?= htmlspecialchars($page->content_markdown) ?>"
     required="true"
     rows="20" />
 
diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php
index 8809d9b5d836be026ad923521a1971292cd8d511..71245af11b96627e867273a78756c83f2e79d4b8 100644
--- a/themes/cp_admin/podcast/edit.php
+++ b/themes/cp_admin/podcast/edit.php
@@ -44,7 +44,7 @@
     as="MarkdownEditor"
     name="description"
     label="<?= lang('Podcast.form.description') ?>"
-    value="<?= $podcast->title ?>"
+    value="<?= htmlspecialchars($podcast->description) ?>"
     required="true" />
 
 <fieldset>
diff --git a/themes/cp_admin/podcast/import.php b/themes/cp_admin/podcast/import.php
index 3b96be3e64e4cc42c6f04d9af39b2fba4186e68e..d01838c35cde0b4cc138c3098717bee82b13b86e 100644
--- a/themes/cp_admin/podcast/import.php
+++ b/themes/cp_admin/podcast/import.php
@@ -63,16 +63,16 @@
 
 <fieldset class="flex flex-col mb-4">
     <legend><?= lang('PodcastImport.slug_field') ?></legend>
-    <Forms.Radio id="title" name="slug_field" isChecked="true">&lt;title&gt;</span></Forms.Radio>
-    <Forms.Radio id="link" name="slug_field">&lt;link&gt;</span></Forms.Radio>
+    <Forms.Radio value="title" name="slug_field" isChecked="true">&lt;title&gt;</span></Forms.Radio>
+    <Forms.Radio value="link" name="slug_field">&lt;link&gt;</span></Forms.Radio>
 </fieldset>
 
 <fieldset class="flex flex-col mb-4">
     <legend><?= lang('PodcastImport.description_field') ?></legend>
-    <Forms.Radio id="description" name="description_field" isChecked="true">&lt;description&gt;</Forms.Radio>
-    <Forms.Radio id="summary" name="description_field">&lt;itunes:summary&gt;</Forms.Radio>
-    <Forms.Radio id="subtitle_summary" name="description_field">&lt;itunes:subtitle&gt; + &lt;itunes:summary&gt;</Forms.Radio>
-    <Forms.Radio id="content" name="description_field">&lt;content:encoded&gt;</Forms.Radio>
+    <Forms.Radio value="description" name="description_field" isChecked="true">&lt;description&gt;</Forms.Radio>
+    <Forms.Radio value="summary" name="description_field">&lt;itunes:summary&gt;</Forms.Radio>
+    <Forms.Radio value="subtitle_summary" name="description_field">&lt;itunes:subtitle&gt; + &lt;itunes:summary&gt;</Forms.Radio>
+    <Forms.Radio value="content" name="description_field">&lt;content:encoded&gt;</Forms.Radio>
 </fieldset>
 
 <Forms.Checkbox name="force_renumber" hint="<?= lang('PodcastImport.force_renumber_hint') ?>"><?= lang('PodcastImport.force_renumber') ?></Forms.Checkbox>
diff --git a/themes/cp_app/_admin_navbar.php b/themes/cp_app/_admin_navbar.php
index aedbab7bb1cb9966deea970125b01bef32af62ae..08a928defb901800d31f35a0ca0d8314a55191ea 100644
--- a/themes/cp_app/_admin_navbar.php
+++ b/themes/cp_app/_admin_navbar.php
@@ -1,46 +1,86 @@
-<div class="sticky top-0 left-0 z-50 flex items-center justify-between w-full h-12 px-4 text-white border-b shadow bg-pine-800 border-pine-900">
-        <?= anchor(
-    route_to('admin'),
-    'castopod' . svg('castopod-logo-base', 'h-5 ml-1'),
-    [
-        'class' =>
-            'text-2xl inline-flex items-baseline font-bold font-display',
-    ],
-) ?>
-        <?php if (user()->podcasts !== []): ?>
-            <button type="button" class="inline-flex items-center px-6 py-2 mt-auto font-semibold outline-none focus:ring" id="interact-as-dropdown" data-dropdown="button" data-dropdown-target="interact-as-dropdown-menu" aria-haspopup="true" aria-expanded="false">
-                <img src="<?= interact_as_actor()
-            ->avatar_image_url ?>" class="w-8 h-8 mr-2 rounded-full" />
-                <?= '@' . interact_as_actor()->username ?>
-                <?= icon('caret-down', 'ml-auto') ?>
-            </button>
-            <nav id="interact-as-dropdown-menu" class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="my-accountDropdown" data-dropdown="menu" data-dropdown-placement="bottom-end">
-                <span class="px-4 text-xs tracking-wider text-gray-700 uppercase"><?= lang(
-                'Admin.choose_interact',
-            ) ?></span>
-                <form action="<?= route_to(
-                'interact-as-actor',
-            ) ?>" method="POST" class="flex flex-col">
-                    <?= csrf_field() ?>
-                    <?php foreach (user()->podcasts as $userPodcast): ?>
-                        <button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="<?= "interact-as-actor-{$userPodcast->id}" ?>" name="actor_id" value="<?= $userPodcast->actor_id ?>">
-                            <span class="inline-flex items-center flex-1">
-                                <img src="<?= $userPodcast->image
-                        ->thumbnail_url ?>" class="w-8 h-8 mr-2 rounded-full" /><?= $userPodcast->title ?>
-                                <?php if (
-                                    interact_as_actor()
-                                        ->id ===
-                                    $userPodcast->actor_id
-                                ): ?>
-                            </span>
-                            <?= icon(
-                                    'check',
-                                    'ml-4 bg-pine-800 text-white rounded-full',
-                                ) ?>
-                        <?php endif; ?>
-                        </button>
-                    <?php endforeach; ?>
-                </form>
-            </nav>
-        <?php endif; ?>
-    </div>
\ No newline at end of file
+<div class="sticky top-0 left-0 z-50 flex items-center justify-between w-full h-10 text-white border-b shadow bg-pine-800 border-pine-900">
+        <div class="inline-flex items-center h-full">
+            <a href="<?= route_to('home') ?>" class="inline-flex items-center h-full px-2 border-r border-pine-900">
+                    <?= svg('castopod-logo-base', 'h-6') ?>
+            </a>
+            <a href="<?= route_to('admin', ) ?>" class="inline-flex items-center h-full px-6 text-sm font-semibold outline-none hover:underline focus:ring">
+                <?= lang('AdminNavigation.go_to_admin') ?>
+                <?= icon('external-link', 'ml-1 opacity-60') ?>
+            </a>
+        </div>
+
+        <div class="inline-flex items-center h-full">
+        <button
+            type="button"
+            class="inline-flex items-center h-full px-3 text-sm font-semibold outline-none focus:ring gap-x-2"
+            id="my-account-dropdown"
+            data-dropdown="button"
+            data-dropdown-target="my-account-dropdown-menu"
+            aria-haspopup="true"
+            aria-expanded="false"><div class="relative mr-1">
+                <?= icon('account-circle', 'text-3xl opacity-60') ?>
+                <?= user()
+                    ->podcasts === [] ? '' : '<img src="' . interact_as_actor()->avatar_image_url . '" class="absolute bottom-0 w-4 h-4 rounded-full -right-1" />' ?>
+            </div>
+            <?= user()->username ?>
+            <?= icon('caret-down', 'ml-auto text-2xl') ?></button>
+        <?php
+            $interactButtons = '';
+            foreach (user()->podcasts as $userPodcast) {
+                $checkMark = interact_as_actor_id() === $userPodcast->actor_id ? icon('check', 'ml-2 bg-pine-800 text-white rounded-full') : '';
+
+                $interactButtons .= <<<CODE_SAMPLE
+                    <button class="inline-flex items-center w-full px-4 py-1 hover:bg-gray-100" id="interact-as-actor-{$userPodcast->id}" name="actor_id" value="{$userPodcast->actor_id}">
+                        <span class="inline-flex items-center flex-1 text-sm"><img src="{$userPodcast->image->thumbnail_url}" class="w-6 h-6 mr-2 rounded-full" />{$userPodcast->title}{$checkMark}</span>
+                    </button>
+                CODE_SAMPLE;
+            }
+
+            $interactAsText = lang('Admin.choose_interact');
+            $route = route_to('interact-as-actor');
+            $csrfField = csrf_field();
+
+            $menuItems = [
+                [
+                    'type' => 'link',
+                    'title' => lang('AdminNavigation.account.my-account'),
+                    'uri' => route_to('my-account'),
+                ],
+                [
+                    'type' => 'link',
+                    'title' => lang('AdminNavigation.account.change-password'),
+                    'uri' => route_to('change-password'),
+                ],
+                [
+                    'type' => 'separator',
+                ],
+                [
+                    'type' => 'link',
+                    'title' => lang('AdminNavigation.account.logout'),
+                    'uri' => route_to('logout'),
+                ],
+            ];
+
+            if (user()->podcasts !== []) {
+                $menuItems = array_merge([
+                    [
+                        'type' => 'html',
+                        'content' => esc(<<<CODE_SAMPLE
+                            <nav class="flex flex-col py-2 text-black whitespace-no-wrap">
+                                <span class="px-4 mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase">{$interactAsText}</span>
+                                <form action="{$route}" method="POST" class="flex flex-col">
+                                    {$csrfField}
+                                    {$interactButtons}
+                                </form>
+                            </nav>
+                        CODE_SAMPLE),
+                    ],
+                    [
+                        'type' => 'separator',
+                    ],
+                ], $menuItems);
+            }
+        ?>
+        <DropdownMenu id="my-account-dropdown-menu" labelledby="my-account-dropdown" items="<?= esc(json_encode($menuItems)) ?>" />
+    </div>
+</div>
\ No newline at end of file
diff --git a/themes/cp_app/_layout.php b/themes/cp_app/_layout.php
index bc8377c60caec9f131607b210ac82f1ec0cc0376..ea8850086325818a47e089edeb32caf70e282d87 100644
--- a/themes/cp_app/_layout.php
+++ b/themes/cp_app/_layout.php
@@ -12,11 +12,13 @@
 
     <?= service('vite')
         ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
     <?= service('vite')
         ->asset('js/audio-player.ts', 'js') ?>
 </head>
 
-<body class="flex flex-col min-h-screen mx-auto bg-gray-100">
+<body class="flex flex-col min-h-screen mx-auto bg-pine-50">
     <?php if (service('authentication')->check()): ?>
         <?= $this->include('_admin_navbar') ?>
     <?php endif; ?>
@@ -28,7 +30,7 @@
             'arrow-left',
             'mr-2',
         ) . lang('Page.back_to_home') ?></a>
-            <h1 class="text-3xl font-semibold"><?= isset($page)
+            <h1 class="text-3xl font-bold font-display"><?= isset($page)
     ? $page->title
     : 'Castopod' ?></h1>
         </div>
diff --git a/themes/cp_app/_persons_modal.php b/themes/cp_app/_persons_modal.php
new file mode 100644
index 0000000000000000000000000000000000000000..973ddaba49d7346cadadaf6f767d829e80c7b863
--- /dev/null
+++ b/themes/cp_app/_persons_modal.php
@@ -0,0 +1,46 @@
+<div id="persons-list" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
+    <div
+    class="absolute w-full h-full bg-pine-800/75"
+    role="button"
+    data-toggle="persons-list"
+    data-toggle-class="hidden"
+    aria-label="<?= lang('Common.close') ?>"></div>
+    <div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
+        <div class="flex justify-between px-4 py-2 border-b">
+            <h3 class="self-center text-lg"><?= $title ?></h3>
+            <button
+            data-toggle="persons-list"
+            data-toggle-class="hidden"
+            aria-label="<?= lang('Common.close') ?>"
+            class="self-start p-1 text-2xl"><?= icon('close') ?></button>
+        </div>
+        <div class="flex flex-col items-start p-4 gap-y-4">
+            <?php foreach ($persons as $person): ?>
+                <div class="flex gap-x-2">
+                    <img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-10 h-10 rounded-full" />
+                    <div class="flex flex-col">
+                        <h4 class="text-sm font-semibold hover:underline focus:ring">
+                            <?php if ($person->information_url): ?>
+                                <a href="<?= $person->information_url ?>" target="_blank" rel="noopener noreferrer"><?= $person->full_name ?></a>
+                            <?php else: ?>
+                                <?= $person->full_name ?>
+                            <?php endif; ?>
+                        </h4>
+                        <p class="text-xs text-gray-500"><?= implode(
+    ', ',
+    array_map(function ($role) {
+        return lang(
+            'PersonsTaxonomy.persons.' .
+                                            $role->group .
+                                            '.roles.' .
+                                            $role->role .
+                                            '.label',
+        );
+    }, $person->roles),
+) ?></p>
+                    </div>
+                </div>
+            <?php endforeach; ?>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/themes/cp_app/episode/_layout.php b/themes/cp_app/episode/_layout.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd6a08e36dba2f2769450f25480dbe584a5304b5
--- /dev/null
+++ b/themes/cp_app/episode/_layout.php
@@ -0,0 +1,143 @@
+<?= helper('page') ?>
+
+<!DOCTYPE html>
+<html lang="<?= service('request')
+    ->getLocale() ?>">
+
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
+
+    <?= $this->renderSection('meta-tags') ?>
+    <?php if ($podcast->payment_pointer): ?>
+        <meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
+    <?php endif; ?>
+
+    <?= service('vite')
+        ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
+    <?= service('vite')
+        ->asset('js/podcast.ts', 'js') ?>
+    <?= service('vite')
+        ->asset('js/audio-player.ts', 'js') ?>
+</head>
+
+<body class="grid items-start mx-auto grid-cols-podcastLayout bg-pine-50">
+    <?php if (can_user_interact()): ?>
+        <div class="col-span-full">
+            <?= $this->include('_admin_navbar') ?>
+        </div>
+    <?php endif; ?>
+
+    <nav class="flex items-center justify-between h-10 col-start-2 px-2 text-white bg-pine-800">
+        <a href="<?= route_to('podcast-episodes', $podcast->handle) ?>" class="inline-flex items-center" title="<?= lang('Episode.back_to_episodes', [
+            'podcast' => $podcast->title,
+        ]) ?>">
+            <?= icon('arrow-left', 'mr-2 text-lg') ?>
+            <div class="inline-flex items-center gap-x-2">
+                <img class="w-8 h-8 rounded-full" src="<?= $episode->podcast->image->thumbnail_url ?>" alt="<?= $episode->podcast->title ?>" />
+                <div class="flex flex-col">
+                    <span class="text-sm font-semibold leading-none"><?= $episode->podcast->title ?></span>
+                    <span class="text-xs"><?= lang('Podcast.followers', [
+                        'numberOfFollowers' => $podcast->actor->followers_count,
+                    ]) ?></span>
+                </div>
+            </div>
+        </a>
+        <div class="inline-flex items-center self-end h-full gap-x-2">
+            <?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
+                <IconButton glyph="heart" variant="accent" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
+            <?php endif; ?>
+            <?= anchor_popup(
+                        route_to('follow', $podcast->handle),
+                        icon(
+                            'social/castopod',
+                            'mr-2 text-xl text-rose-200 group-hover:text-rose-50',
+                        ) . lang('Podcast.follow'),
+                        [
+                            'width' => 420,
+                            'height' => 620,
+                            'class' =>
+                                'group inline-flex items-center px-2 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
+                        ],
+                    ) ?>
+        </div>
+    </nav>
+    <header class="relative z-50 flex flex-col col-start-2 px-8 pt-8 pb-4 overflow-hidden bg-pine-500 gap-y-4">
+        <div class="absolute top-0 left-0 w-full h-full bg-center bg-no-repeat bg-cover blur-lg mix-blend-luminosity" style="background-image: url('<?= $episode->podcast->image->thumbnail_url ?>');"></div>
+        <div class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-pine-800 to-transparent"></div>
+        <div class="z-10 flex flex-col items-start gap-y-2 gap-x-4 sm:flex-row">
+            <img src="<?= $episode->image->medium_url ?>" alt="<?= $episode->title ?>" loading="lazy" class="rounded-md h-36" />
+            <div class="flex flex-col items-start text-white">
+                <?= episode_numbering($episode->number, $episode->season_number, 'bg-pine-50 text-sm leading-none font-semibold text-gray-700 border !no-underline border-pine-100', true) ?>
+                <h1 class="inline-flex items-baseline max-w-md mt-2 text-2xl font-bold leading-none sm:text-3xl font-display line-clamp-2"><?= $episode->title ?></h1>
+                <div class="flex items-center mt-4 gap-x-8">
+                <?php if ($episode->persons !== []): ?>
+                    <button class="flex items-center text-xs font-semibold gap-x-2 hover:underline" data-toggle="persons-list" data-toggle-class="hidden">
+                        <div class="inline-flex flex-row-reverse">
+                            <?php $i = 0; ?>
+                            <?php foreach ($episode->persons as $person): ?>
+                                <img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-8 h-8 -ml-5 border-2 rounded-full border-pine-100 last:ml-0" />
+                                <?php $i++; if ($i === 3) {
+                        break;
+                    }?>
+                            <?php endforeach; ?>
+                        </div>
+                        <?= lang('Episode.persons', [
+                            'personsCount' => count($episode->persons),
+                        ]) ?>
+                    </button>
+                <?php endif; ?>
+                <?php if ($episode->location): ?>
+                    <?= location_link($episode->location, 'text-xs font-semibold p-2') ?>
+                <?php endif; ?>
+                </div>
+            </div>
+        </div>
+        <div class="z-10 inline-flex items-center text-white gap-x-4">
+            <play-episode-button
+                id="<?= $episode->id ?>"
+                imageSrc="<?= $episode->image->thumbnail_url ?>"
+                title="<?= $episode->title ?>"
+                podcast="<?= $episode->podcast->title ?>"
+                src="<?= $episode->audio_file_web_url ?>"
+                mediaType="<?= $episode->audio_file_mimetype ?>"
+                playLabel="<?= lang('Common.play_episode_button.play') ?>"
+                playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
+            <div class="text-xs">
+                <?= relative_time($episode->published_at) ?>
+                <span class="mx-1">•</span>
+                <time datetime="PT<?= $episode->audio_file_duration ?>S">
+                    <?= format_duration_symbol($episode->audio_file_duration) ?>
+                </time>
+            </div>
+        </div>
+    </header>
+    <div class="col-start-2 px-8 py-4 text-white bg-pine-800">
+        <h2 class="text-xs font-bold tracking-wider uppercase whitespace-pre-line font-display"><?= lang('Episode.description') ?></h2>
+        <?php if (substr_count($episode->description_markdown, "\n") > 3 || strlen($episode->description) > 250): ?>
+            <SeeMore class="max-w-xl prose-sm text-white whitespace-pre-line"><?= $episode->getDescriptionHtml('-+Website+-') ?></SeeMore>
+        <?php else: ?>
+            <div class="max-w-xl prose-sm text-white whitespace-pre-line"><?= $episode->getDescriptionHtml('-+Website+-') ?></div>
+        <?php endif; ?>
+    </div>
+    <?= $this->include('episode/_partials/navigation') ?>
+    <div class="relative grid items-start grid-cols-3 col-start-2 pt-6 pb-12 gap-x-6">
+        <main class="col-span-full sm:col-span-2">
+            <?= $this->renderSection('content') ?>
+        </main>
+        <?= $this->include('podcast/_partials/sidebar') ?>
+    </div>
+    <?= view('_persons_modal', [
+        'title' => lang('Episode.persons_list', [
+            'episodeTitle' => $episode->title,
+        ]),
+        'persons' => $episode->
+persons,
+    ]) ?>
+    <?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
+        <?= $this->include('podcast/_partials/funding_links_modal') ?>
+    <?php endif; ?>
+</body>
diff --git a/themes/cp_app/episode/_partials/card.php b/themes/cp_app/episode/_partials/card.php
new file mode 100644
index 0000000000000000000000000000000000000000..90f26cfc48143bacb3a63e48117dd58602c23916
--- /dev/null
+++ b/themes/cp_app/episode/_partials/card.php
@@ -0,0 +1,29 @@
+<article class="w-full bg-white shadow sm:rounded-lg">
+    <div class="flex p-4 gap-x-2">
+        <div class="relative">
+            <time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio_file_duration ?>S">
+                <?= format_duration($episode->audio_file_duration) ?>
+            </time>
+            <img loading="lazy" src="<?= $episode->image
+                    ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 h-20 rounded-lg" />
+        </div>
+        <div class="flex items-center flex-1 gap-x-4">
+            <div class="flex flex-col flex-1">
+                <div class="inline-flex items-center">
+                    <?= episode_numbering($episode->number, $episode->season_number, 'text-xs font-semibold text-gray-700 px-1 border mr-2 !no-underline', true) ?>
+                    <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
+                </div>
+                <h2 class="flex-1 font-semibold line-clamp-2"><a class="hover:underline" href="<?= $episode->link ?>"><?= $episode->title ?></a></h2>
+            </div>
+            <play-episode-button
+                id="<?= $episode->id ?>"
+                imageSrc="<?= $episode->image->thumbnail_url ?>"
+                title="<?= $episode->title ?>"
+                podcast="<?= $episode->podcast->title ?>"
+                src="<?= $episode->audio_file_web_url ?>"
+                mediaType="<?= $episode->audio_file_mimetype ?>"
+                playLabel="<?= lang('Common.play_episode_button.play') ?>"
+                playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
+        </div>
+    </div>
+</article>
diff --git a/themes/cp_app/podcast/_partials/comment.php b/themes/cp_app/episode/_partials/comment.php
similarity index 77%
rename from themes/cp_app/podcast/_partials/comment.php
rename to themes/cp_app/episode/_partials/comment.php
index 8109c99787a328863e6e90f1dd81efb9acbad26a..a7c0e878f6db1297fc20f1fc932f413aeb27194d 100644
--- a/themes/cp_app/podcast/_partials/comment.php
+++ b/themes/cp_app/episode/_partials/comment.php
@@ -1,5 +1,5 @@
-<article class="relative z-10 flex w-full px-4 py-2 rounded-2xl">
-    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+<article class="relative z-10 flex w-full px-4 py-2 sm:rounded-2xl">
+    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-10 h-10 mr-2 rounded-full" />
     <div class="flex-1">
         <header class="w-full mb-2 text-sm">
             <a href="<?= $comment->actor
@@ -13,14 +13,14 @@
                     ($comment->actor->is_local
                         ? ''
                         : '@' . $comment->actor->domain) ?></span>
-                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
+                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto flex-shrink-0') ?>
             </a>
         </header>
         <div class="mb-2 post-content"><?= $comment->message_html ?></div>
         <?php if ($comment->is_from_post): ?>
-            <?= $this->include('podcast/_partials/comment_actions_from_post') ?>
+            <?= $this->include('episode/_partials/comment_actions_from_post') ?>
         <?php else: ?>
-            <?= $this->include('podcast/_partials/comment_actions') ?>
+            <?= $this->include('episode/_partials/comment_actions') ?>
         <?php endif; ?>
     </div>
 </article>
diff --git a/themes/cp_app/episode/_partials/comment_actions.php b/themes/cp_app/episode/_partials/comment_actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..245ced8509ef1cc8b74be1b702df6264d4ecd87d
--- /dev/null
+++ b/themes/cp_app/episode/_partials/comment_actions.php
@@ -0,0 +1,42 @@
+<footer>
+    <?php if (can_user_interact()): ?>
+        <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $comment->episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+            <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
+    'Comment.likes',
+    [
+        'numberOfLikes' => $comment->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
+            <Button uri="<?= route_to('episode-comment', $comment->episode->podcast->handle, $comment->episode->slug, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
+        </form>
+        <?php if ($comment->replies_count): ?>
+            <?= anchor(
+    route_to('episode-comment', $comment->episode->podcast->handle, $comment->episode->slug, $comment->id),
+    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
+        'numberOfReplies' => $comment->replies_count,
+    ]),
+    [
+        'class' => 'inline-flex items-center text-xs hover:underline',
+    ]
+) ?>
+        <?php endif; ?>
+    <?php else: ?>
+        <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
+    'Comment.like',
+    [
+        'numberOfLikes' => $comment->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
+        <?php if ($comment->replies_count): ?>
+            <?= anchor(
+    route_to('episode-comment', $comment->episode->podcast->handle, $episode->slug, $comment->id),
+    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
+        'numberOfReplies' => $comment->replies_count,
+    ]),
+    [
+        'class' => 'inline-flex items-center text-xs hover:underline',
+    ]
+) ?>
+        <?php endif; ?>
+    <?php endif; ?>
+</footer>
diff --git a/themes/cp_app/episode/_partials/comment_actions_from_post.php b/themes/cp_app/episode/_partials/comment_actions_from_post.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb70838f5183708aea586c2707864d27363a02cb
--- /dev/null
+++ b/themes/cp_app/episode/_partials/comment_actions_from_post.php
@@ -0,0 +1,42 @@
+<footer>
+    <?php if (can_user_interact()): ?>
+        <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
+    'Comment.likes',
+    [
+        'numberOfLikes' => $comment->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
+            <Button uri="<?= route_to('post', $podcast->handle, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
+        </form>
+        <?php if ($comment->replies_count): ?>
+            <?= anchor(
+    route_to('post', $podcast->handle, $comment->id),
+    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
+        'numberOfReplies' => $comment->replies_count,
+    ]),
+    [
+        'class' => 'inline-flex items-center text-xs hover:underline',
+    ]
+) ?>
+        <?php endif; ?>
+    <?php else: ?>
+        <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
+    'Comment.like',
+    [
+        'numberOfLikes' => $comment->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
+        <?php if ($comment->replies_count): ?>
+            <?= anchor(
+    route_to('post', $podcast->handle, $comment->id),
+    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
+        'numberOfReplies' => $comment->replies_count,
+    ]),
+    [
+        'class' => 'inline-flex items-center text-xs hover:underline',
+    ]
+) ?>
+        <?php endif; ?>
+    <?php endif; ?>
+</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_card_authenticated.php b/themes/cp_app/episode/_partials/comment_card.php
similarity index 58%
rename from themes/cp_app/podcast/_partials/comment_card_authenticated.php
rename to themes/cp_app/episode/_partials/comment_card.php
index 996d2a59da68168e8eeaf97e0fa2ec81fa68c101..e68759e283512a31af455df66ca76a2c7b4728e2 100644
--- a/themes/cp_app/podcast/_partials/comment_card_authenticated.php
+++ b/themes/cp_app/episode/_partials/comment_card.php
@@ -1,9 +1,8 @@
-<article class="relative z-10 flex w-full p-4 bg-white shadow rounded-2xl">
+<article class="relative z-10 flex w-full p-4 bg-white shadow sm:rounded-2xl">
     <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
     <div class="flex-1">
         <header class="w-full mb-2 text-sm">
-            <a href="<?= $comment->actor
-    ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
+            <a href="<?= $comment->actor->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
                 ? ''
                 : 'target="_blank" rel="noopener noreferrer"' ?>>
                 <span class="mr-2 font-semibold truncate"><?= $comment->actor
@@ -18,10 +17,11 @@
         </header>
         <div class="mb-2 post-content"><?= $comment->message_html ?></div>
         <?php if ($comment->is_from_post): ?>
-            <?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
+            <?= $this->include('episode/_partials/comment_actions_from_post') ?>
         <?php else: ?>
             <footer>
-                <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
+                <?php if (can_user_interact()): ?>
+                    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
                     <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
                             'Comment.likes',
                             [
@@ -34,6 +34,19 @@
                             ],
                         ) ?></button>
                 </form>
+                <?php else: ?>
+                    <button class="inline-flex items-center opacity-50 cursor-not-allowed" title="<?= lang(
+                            'Comment.likes',
+                            [
+                                'numberOfLikes' => $comment->likes_count,
+                            ],
+                        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . lang(
+                            'Comment.likes',
+                            [
+                                'numberOfLikes' => $comment->likes_count,
+                            ],
+                        ) ?></button>
+                <?php endif; ?>
             </footer>
         <?php endif; ?>
     </div>
diff --git a/themes/cp_app/podcast/_partials/comment_reply.php b/themes/cp_app/episode/_partials/comment_reply.php
similarity index 93%
rename from themes/cp_app/podcast/_partials/comment_reply.php
rename to themes/cp_app/episode/_partials/comment_reply.php
index 3b8f1b037197bc6f0b4532fc19fdc903235e4ae9..8f6d464bfd12bd0b00d8ca1f50a65eefefd9aa4f 100644
--- a/themes/cp_app/podcast/_partials/comment_reply.php
+++ b/themes/cp_app/episode/_partials/comment_reply.php
@@ -14,6 +14,6 @@
             <?= relative_time($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
         </header>
         <p class="mb-2 post-content"><?= $reply->message_html ?></p>
-        <?= $this->include('podcast/_partials/comment_reply_actions') ?>
+        <?= $this->include('episode/_partials/comment_reply_actions') ?>
     </div>
 </article>
diff --git a/themes/cp_app/episode/_partials/comment_reply_actions.php b/themes/cp_app/episode/_partials/comment_reply_actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..126167a254dd03b7b38ea82ed0118a99b419ea1a
--- /dev/null
+++ b/themes/cp_app/episode/_partials/comment_reply_actions.php
@@ -0,0 +1,32 @@
+<footer>
+    <?php if (can_user_interact()): ?>
+        <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $reply->id) ?>" method="POST" class="flex items-center gap-x-4">
+            <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
+    'Comment.likes',
+    [
+        'numberOfLikes' => $reply->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $reply->likes_count ?></button>
+            <Button uri="<?= route_to('episode-comment', $podcast->handle, $episode->slug, $reply->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
+        </form>
+    <?php else: ?>
+        <button type="submit" name="action" class="inline-flex items-center opacity-50 cursor-not-allowed" disabled="disabled" title="<?= lang(
+    'Comment.likes',
+    [
+        'numberOfLikes' => $reply->likes_count,
+    ],
+) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $reply->likes_count ?></button>
+            <?php if ($reply->replies_count): ?>
+                    <?= anchor(
+    route_to('episode-comment', $podcast->handle, $episode->slug, $reply->id),
+    icon('chat', 'text-2xl mr-1 text-gray-400') . $reply->replies_count,
+    [
+        'class' => 'inline-flex items-center hover:underline',
+        'title' => lang('Comment.replies', [
+            'numberOfReplies' => $reply->replies_count,
+        ]),
+    ],
+) ?>
+            <?php endif; ?>
+    <?php endif; ?>
+</footer>
diff --git a/themes/cp_app/episode/_partials/comment_with_replies.php b/themes/cp_app/episode/_partials/comment_with_replies.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c7aed513944cda5944a4d5130183297d8d6b4f0
--- /dev/null
+++ b/themes/cp_app/episode/_partials/comment_with_replies.php
@@ -0,0 +1,29 @@
+<?= $this->include('episode/_partials/comment_card') ?>
+<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
+
+<?php if (can_user_interact()): ?>
+<form action="<?= route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50">
+    <img src="<?= interact_as_actor()
+    ->avatar_image_url ?>" alt="<?= interact_as_actor()
+    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
+    <div class="flex flex-col flex-1">
+        <Forms.Textarea
+            name="message"
+            required="true"
+            class="w-full mb-4"
+            placeholder="<?= lang('Comment.form.reply_to_placeholder', [
+                'actorUsername' => $comment->actor->username,
+            ]) ?>"
+            rows="1" />
+        <Button variant="primary" size="small" type="submit" name="action" value="reply" iconRight="send-plane"><?= lang('Comment.form.submit_reply') ?></Button>
+    </div>
+</form>
+<?php endif; ?>
+
+<?php foreach ($comment->replies as $reply): ?>
+    <?= view('episode/_partials/comment_reply', [
+        'reply' => $reply,
+    ]) ?>
+<?php endforeach; ?>
+
+</div>
diff --git a/themes/cp_app/episode/_partials/navigation.php b/themes/cp_app/episode/_partials/navigation.php
new file mode 100644
index 0000000000000000000000000000000000000000..e39db5d95570a598ddba385520384146d9779728
--- /dev/null
+++ b/themes/cp_app/episode/_partials/navigation.php
@@ -0,0 +1,22 @@
+<?php declare(strict_types=1);
+
+$navigationItems = [
+    [
+        'uri' => route_to('episode', $podcast->handle, $episode->slug),
+        'label' => lang('Episode.comments'),
+        'labelInfo' => $episode->comments_count,
+    ],
+    [
+        'uri' => route_to('episode-activity', $podcast->handle, $episode->slug),
+        'label' => lang('Episode.activity'),
+        'labelInfo' => $episode->posts_count,
+    ],
+]
+?>
+<nav class="sticky z-40 flex col-start-2 px-4 pt-4 bg-white shadow md:px-8 gap-x-2 md:gap-x-4 -top-4 md:rounded-b-xl">
+    <?php foreach ($navigationItems as $item): ?>
+        <?php $isActive = url_is($item['uri']); ?>
+        <a href="<?= $item['uri'] ?>" class="px-4 py-1 text-sm font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?><span class="px-2 ml-1 font-semibold rounded-full <?= $isActive ? ' bg-pine-100' : ' bg-gray-100' ?>"><?= $item['labelInfo'] ?></span></a>
+    <?php endforeach; ?>
+    <button type="button" class="p-2 ml-auto rotate-180 rounded-full sm:hidden focus:outline-none focus:ring-2 focus:ring-pine-500 focus:ring-offset-2 focus:ring-offset-pine-100" data-toggle="podcast-sidebar" data-toggle-class="absolute sticky top-0 right-0 hidden bg-white top-12"><?= icon('menu') ?></button>
+</nav>
\ No newline at end of file
diff --git a/themes/cp_app/episode/_partials/preview_card.php b/themes/cp_app/episode/_partials/preview_card.php
new file mode 100644
index 0000000000000000000000000000000000000000..a9dfe3b9cff08cfa84f861aa88d42cba53cbe202
--- /dev/null
+++ b/themes/cp_app/episode/_partials/preview_card.php
@@ -0,0 +1,27 @@
+<div class="flex items-center border-t border-b">
+    <div class="relative">
+        <time class="absolute px-1 text-sm font-semibold text-white bg-black/50 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S">
+                    <?= format_duration($episode->audio_file_duration) ?>
+        </time>
+        <img
+        src="<?= $episode->image->thumbnail_url ?>"
+        alt="<?= $episode->title ?>" class="w-24 h-24"/>
+    </div>
+    <div class="flex flex-col flex-1 px-4 py-2">
+        <div class="inline-flex">
+            <?= episode_numbering($episode->number, $episode->season_number, 'text-xs font-semibold text-gray-700 px-1 border mr-2 !no-underline', true) ?>
+            <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap text-gray-500') ?>
+        </div>
+        <a href="<?= $episode->link ?>" class="flex items-baseline font-semibold line-clamp-2" title="<?= $episode->title ?>"><?= $episode->title ?></a>
+    </div>
+    <play-episode-button
+        class="mr-4"
+        id="<?= $index . '_' . $episode->id ?>"
+        imageSrc="<?= $episode->image->thumbnail_url ?>"
+        title="<?= $episode->title ?>"
+        podcast="<?= $episode->podcast->title ?>"
+        src="<?= $episode->audio_file_web_url ?>"
+        mediaType="<?= $episode->audio_file_mimetype ?>"
+        playLabel="<?= lang('Common.play_episode_button.play') ?>"
+        playingLabel="<?= lang('Common.play_episode_button.playing') ?>"></play-episode-button>
+</div>
\ No newline at end of file
diff --git a/themes/cp_app/episode/activity.php b/themes/cp_app/episode/activity.php
new file mode 100644
index 0000000000000000000000000000000000000000..94c9181894c933860c62f35bfdff63fe7032fe3b
--- /dev/null
+++ b/themes/cp_app/episode/activity.php
@@ -0,0 +1,68 @@
+<?= $this->extend('episode/_layout') ?>
+
+<?= $this->section('meta-tags') ?>
+<title><?= $episode->title ?></title>
+<meta name="description" content="<?= htmlspecialchars($episode->description) ?>" />
+<link rel="canonical" href="<?= $episode->link ?>" />
+<meta property="og:title" content="<?= $episode->title ?>" />
+<meta property="og:description" content="<?= $episode->description ?>" />
+<meta property="og:locale" content="<?= $podcast->language_code ?>" />
+<meta property="og:site_name" content="<?= $podcast->title ?>" />
+<meta property="og:url" content="<?= current_url() ?>" />
+<meta property="og:image" content="<?= $episode->image->large_url ?>" />
+<meta property="og:image:width" content="<?= config('Images')
+    ->largeSize ?>" />
+<meta property="og:image:height" content="<?= config('Images')
+    ->largeSize ?>" />
+<meta property="og:description" content="$description" />
+<meta property="article:published_time" content="<?= $episode->published_at ?>" />
+<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
+<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
+<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
+<link rel="alternate" type="application/json+oembed" href="<?= base_url(route_to('episode-oembed-json', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed json" />
+<link rel="alternate" type="text/xml+oembed" href="<?= base_url(route_to('episode-oembed-xml', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed xml" />
+<meta name="twitter:title" content="<?= $episode->title ?>" />
+<meta name="twitter:description" content="<?= $episode->description ?>" />
+<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
+<meta name="twitter:card" content="player" />
+<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
+<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
+<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl('light') ?>" />
+<meta name="twitter:player:width" content="600" />
+<meta name="twitter:player:height" content="200" />
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<div class="flex flex-col gap-y-4">
+<?php if (can_user_interact()): ?>
+    <form action="<?= route_to('post-attempt-create', $podcast->handle) ?>" method="POST" class="flex p-4 bg-white shadow sm:rounded-2xl">
+        <?= csrf_field() ?>
+
+        <?= view('_message_block') ?>
+
+        <img src="<?= interact_as_actor()
+            ->avatar_image_url ?>" alt="<?= interact_as_actor()
+            ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+        <div class="flex flex-col flex-1 min-w-0 gap-y-2">
+            <input name="episode_url" value="<?= $episode->link ?>" type="hidden" />
+            <Forms.Textarea
+                name="message"
+                placeholder="<?= lang('Post.form.episode_message_placeholder') ?>"
+                required="true"
+                rows="2" />
+            <Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane"><?= lang('Post.form.submit') ?></Button>
+        </div>
+    </form>
+<?php endif; ?>
+
+<?php foreach ($episode->posts as $key => $post): ?>
+    <?= view('post/_partials/card', [
+    'index' => $key,
+        'post' => $post,
+        'podcast' => $podcast,
+]) ?>
+<?php endforeach; ?>
+</div>
+
+<?= $this->endSection() ?>
diff --git a/themes/cp_app/podcast/comment.php b/themes/cp_app/episode/comment.php
similarity index 92%
rename from themes/cp_app/podcast/comment.php
rename to themes/cp_app/episode/comment.php
index b7e66a6589ba590c2a1c3156d3c3aab808b9bccd..87e31e34d6f39634a5e67356f323b9585ddfa072 100644
--- a/themes/cp_app/podcast/comment.php
+++ b/themes/cp_app/episode/comment.php
@@ -1,4 +1,4 @@
-<?= $this->extend('podcast/_layout') ?>
+<?= $this->extend('episode/_layout') ?>
 
 <?= $this->section('meta-tags') ?>
     <title><?= lang('Comment.title', [
@@ -30,7 +30,7 @@
             ]) ?></a>
     </nav>
     <div class="pb-12">
-        <?= $this->include('podcast/_partials/comment_with_replies') ?>
+        <?= $this->include('episode/_partials/comment_with_replies') ?>
     </div>
 </div>
 
diff --git a/themes/cp_app/episode/comments.php b/themes/cp_app/episode/comments.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b0e7a317663c06d0a8fbf400e71f05385f617cd
--- /dev/null
+++ b/themes/cp_app/episode/comments.php
@@ -0,0 +1,69 @@
+<?= $this->extend('episode/_layout') ?>
+
+<?= $this->section('meta-tags') ?>
+<title><?= $episode->title ?></title>
+<meta name="description" content="<?= htmlspecialchars(
+    $episode->description,
+) ?>" />
+<link rel="canonical" href="<?= $episode->link ?>" />
+<meta property="og:title" content="<?= $episode->title ?>" />
+<meta property="og:description" content="<?= $episode->description ?>" />
+<meta property="og:locale" content="<?= $podcast->language_code ?>" />
+<meta property="og:site_name" content="<?= $podcast->title ?>" />
+<meta property="og:url" content="<?= current_url() ?>" />
+<meta property="og:image" content="<?= $episode->image->large_url ?>" />
+<meta property="og:image:width" content="<?= config('Images')
+    ->largeSize ?>" />
+<meta property="og:image:height" content="<?= config('Images')
+    ->largeSize ?>" />
+<meta property="og:description" content="$description" />
+<meta property="article:published_time" content="<?= $episode->published_at ?>" />
+<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
+<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
+<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
+<link rel="alternate" type="application/json+oembed" href="<?= base_url(route_to('episode-oembed-json', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed json" />
+<link rel="alternate" type="text/xml+oembed" href="<?= base_url(route_to('episode-oembed-xml', $podcast->handle, $episode->slug)) ?>" title="<?= $episode->title ?> oEmbed xml" />
+<meta name="twitter:title" content="<?= $episode->title ?>" />
+<meta name="twitter:description" content="<?= $episode->description ?>" />
+<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
+<meta name="twitter:card" content="player" />
+<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
+<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
+<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl('light') ?>" />
+<meta name="twitter:player:width" content="600" />
+<meta name="twitter:player:height" content="200" />
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<div class="flex flex-col gap-y-4">
+    <?php if (can_user_interact()): ?>
+    <form action="<?= route_to('comment-attempt-create', $podcast->id, $episode->id)  ?>" method="POST" class="flex p-4">
+        <?= csrf_field() ?>
+
+        <?= view('_message_block') ?>
+
+        <img src="<?= interact_as_actor()
+            ->avatar_image_url ?>" alt="<?= interact_as_actor()
+            ->display_name ?>" class="w-10 h-10 mr-2 rounded-full" />
+        <div class="flex flex-col flex-1 min-w-0 gap-y-2">
+            <Forms.Textarea
+                name="message"
+                required="true"
+                placeholder="<?= lang('Comment.form.episode_message_placeholder') ?>"
+                rows="2" />
+            <Button class="self-end" variant="primary" size="small" type="submit" iconRight="send-plane"><?= lang('Comment.form.submit') ?></Button>
+        </div>
+    </form>
+    <?php endif; ?>
+
+    <?php foreach ($episode->comments as $comment): ?>
+    <?= view('episode/_partials/comment', [
+    'comment' => $comment,
+        'podcast' => $podcast,
+]) ?>
+    <?php endforeach; ?>
+</div>
+
+<?= $this->endSection()
+?>
diff --git a/themes/cp_app/home.php b/themes/cp_app/home.php
index e903879469ef173bb7b379946de5c8288b370998..61c03e3f1a3504918ba55861dfb87850323ae33c 100644
--- a/themes/cp_app/home.php
+++ b/themes/cp_app/home.php
@@ -11,6 +11,8 @@
     <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
     <?= service('vite')
         ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
 </head>
 
 <body class="flex flex-col min-h-screen mx-auto bg-pine-50">
@@ -52,7 +54,7 @@
         <?= render_page_links() ?>
         <small><?= lang('Common.powered_by', [
             'castopod' =>
-                '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
         ]) ?></small>
     </footer>
 </body>
diff --git a/themes/cp_app/map.php b/themes/cp_app/map.php
index b82e424b4f9c7355a20eaea1deef7a40e5a05edc..dc5ec1cbeaf873151189174e10ba2086e438881d 100644
--- a/themes/cp_app/map.php
+++ b/themes/cp_app/map.php
@@ -11,6 +11,8 @@
     <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
     <?= service('vite')
         ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
     <?= service('vite')
         ->asset('js/map.ts', 'js') ?>
 </head>
@@ -37,7 +39,7 @@
         <?= render_page_links() ?>
         <small><?= lang('Common.powered_by', [
             'castopod' =>
-                '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
         ]) ?></small>
     </footer> 
 </body>
diff --git a/themes/cp_app/page.php b/themes/cp_app/page.php
index 49cea85027a19d95859b3f1bf66c3342733793ef..1cef93164c7ae55434c06a7cff721b9f4f81f5dd 100644
--- a/themes/cp_app/page.php
+++ b/themes/cp_app/page.php
@@ -10,6 +10,8 @@
     <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
     <?= service('vite')
         ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
 </head>
 
 <body class="flex flex-col min-h-screen mx-auto">
@@ -36,7 +38,7 @@
         <?= render_page_links() ?>
         <small><?= lang('Common.powered_by', [
             'castopod' =>
-                '<a class="underline hover:no-underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org/" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
         ]) ?></small>
     </footer>
 </body>
diff --git a/themes/cp_app/podcast/_layout copy.php b/themes/cp_app/podcast/_layout copy.php
deleted file mode 100644
index a37e77b4ac315fd2f0047a819f55c1a756d200af..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_layout copy.php	
+++ /dev/null
@@ -1,123 +0,0 @@
-<?= helper('page') ?>
-
-<!DOCTYPE html>
-<html lang="<?= service('request')
-    ->getLocale() ?>">
-
-<head>
-    <meta charset="UTF-8"/>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
-
-    <?= $this->renderSection('meta-tags') ?>
-    <?php if ($podcast->payment_pointer): ?>
-        <meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
-    <?php endif; ?>
-
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/audio-player.ts', 'js') ?>
-</head>
-
-<body class="flex w-full min-h-screen pb-20 overflow-x-hidden lg:mx-auto lg:container bg-pine-50 sm:pb-0">
-    <?= $this->include('podcast/_partials/header') ?>
-
-    <main class="flex-shrink-0 w-full min-w-0 sm:w-auto sm:flex-1 sm:flex-shrink">
-        <nav class="sticky top-0 left-0 z-50 flex items-center w-full h-12 px-2 py-1 sm:hidden bg-pine-800">
-                <button
-                data-toggle="main-header"
-                data-toggle-class="sticky -translate-x-full"
-                class="flex-shrink-0 mr-3 overflow-hidden rounded-full focus:ring-2 focus:outline-none focus:ring-pine-50">
-                <img src="<?= $podcast->image
-        ->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-10"/>
-                </button>
-                <p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
-                    <span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
-                    <span class="text-xs">@<?= $podcast->handle ?></span>
-                </p>
-                <?= anchor_popup(
-            route_to('follow', $podcast->handle),
-            icon(
-                'social/castopod',
-                'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
-            ) . lang('Podcast.follow'),
-            [
-                'width' => 420,
-                'height' => 620,
-                'class' =>
-                    'group inline-flex mr-2 items-center px-3 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
-            ],
-        ) ?>
-                <button
-                data-toggle="main-sidebar"
-                data-toggle-class="translate-x-full"
-                data-toggle-body-class="-ml-64"
-                class="p-2 text-xl rounded-full focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
-            'menu',
-        ) ?><span class="sr-only"><?= lang('Podcast.toggle_podcast_sidebar') ?></span></button>
-        </nav>
-        <?= $this->renderSection('content') ?>
-    </main>
-
-    <?= $this->include('podcast/_partials/sidebar') ?>
-
-    <button
-    data-toggle="main-sidebar"
-    data-toggle-class="translate-x-full"
-    data-toggle-body-class="-ml-64"
-    class="fixed z-40 hidden p-4 text-xl rounded-full shadow-2xl sm:block lg:hidden bottom-4 left-4 bg-pine-800 focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
-            'menu',
-        ) ?><span class="sr-only"><?= lang(
-            'Podcast.toggle_podcast_sidebar',
-        ) ?></span></button>
-
-    <!-- Funding links modal -->
-    <div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
-        <div
-        class="absolute w-full h-full bg-pine-800 bg-opacity-90"
-        role="button"
-        data-toggle="funding-links"
-        data-toggle-class="hidden"
-        aria-label="<?= lang('Common.close') ?>"></div>
-        <div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
-            <div class="flex justify-between px-4 py-2 border-b">
-                <h3 class="self-center text-lg"><?= lang(
-            'Podcast.funding_links',
-            [
-                'podcastTitle' => $podcast->title,
-            ],
-        ) ?></h3>
-                <button
-                data-toggle="funding-links"
-                data-toggle-class="hidden"
-                aria-label="<?= lang('Common.close') ?>"
-                class="self-start p-1 text-2xl"><?= icon('close') ?></button>
-            </div>
-            <div class="flex flex-col items-start p-4 space-y-4">
-                <?php foreach (
-                    $podcast->fundingPlatforms
-                    as $fundingPlatform
-                ): ?>
-                    <?php if ($fundingPlatform->is_visible): ?>
-                        <a
-                        href="<?= $fundingPlatform->link_url ?>"
-                        title="<?= $fundingPlatform->link_content ?>"
-                        target="_blank"
-                        rel="noopener noreferrer"
-                        class="inline-flex items-center font-semibold text-pine-900">
-                        <?= icon(
-                    $fundingPlatform->type .
-                                '/' .
-                                $fundingPlatform->slug,
-                    'mr-2',
-                ) . $fundingPlatform->link_url ?>
-                        </a>
-                    <?php endif; ?>
-                <?php endforeach; ?>
-            </div>
-        </div>
-    </div>
-</body>
diff --git a/themes/cp_app/podcast/_layout.php b/themes/cp_app/podcast/_layout.php
index 7a632a054d6ae90b35c19709a0142cc62e220227..1629b5137868178d8e0c9630517fb775c6929aa8 100644
--- a/themes/cp_app/podcast/_layout.php
+++ b/themes/cp_app/podcast/_layout.php
@@ -16,118 +16,60 @@
 
     <?= service('vite')
         ->asset('styles/index.css', 'css') ?>
+    <?= service('vite')
+        ->asset('js/app.ts', 'js') ?>
     <?= service('vite')
         ->asset('js/podcast.ts', 'js') ?>
     <?= service('vite')
         ->asset('js/audio-player.ts', 'js') ?>
 </head>
 
-<body class="grid items-start w-1/2 grid-cols-9 mx-auto bg-pine-50 gap-y-8 gap-x-6">
-    <header class="sticky z-50 flex flex-col bg-white shadow -top-96 rounded-b-xl col-span-full">
-        <div style="background-image: url('<?= $podcast->actor->cover_image_url ?>'); background-size: auto 320px;" class="w-full bg-fixed bg-top bg-no-repeat bg-cover bg-pine-800 h-80"></div>
-        <div class="flex items-center justify-between py-4 ml-8 -mt-28">
-            <div class="flex items-center gap-x-4">
-                <img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" loading="lazy" class="rounded-full h-36 ring-4 ring-white" />
-                <div class="flex flex-col -mt-4 text-white">
-                    <h1 class="inline-flex items-center text-2xl font-bold leading-none font-display"><?= $podcast->title . ($podcast->parental_advisory === 'explicit' ? '<span class="px-1 ml-2 text-xs font-semibold leading-tight tracking-wider text-gray-600 uppercase border-2 border-gray-500">' . lang('Common.explicit') . '</span>' : '') ?></h1>
-                    <a href="#" class="hover:underline"><?= lang('Podcast.followers', [
-                        'numberOfFollowers' => $podcast->actor->followers_count,
-                    ]) ?></a>
-                </div>
-            </div>
+<body class="grid items-start mx-auto grid-cols-podcastLayout bg-pine-50">
+    <?php if (can_user_interact()): ?>
+        <div class="col-span-full">
+            <?= $this->include('_admin_navbar') ?>
+        </div>
+    <?php endif; ?>
 
-            <div class="inline-flex items-center mr-4 -mt-4 gap-x-2">
-                <?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
-                    <IconButton glyph="heart" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
-                <?php endif; ?>
-                <?= anchor_popup(
-                        route_to('follow', $podcast->handle),
-                        icon(
-                            'social/castopod',
-                            'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
-                        ) . lang('Podcast.follow'),
-                        [
-                            'width' => 420,
-                            'height' => 620,
-                            'class' =>
-                                'group inline-flex items-center px-4 py-2 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
-                        ],
-                    ) ?>
+    <header class="z-50 flex flex-col-reverse justify-between w-full col-start-2 bg-top bg-no-repeat bg-cover sm:flex-row sm:items-end bg-pine-800" style="background-image: url('<?= $podcast->actor->cover_image_url ?>'); aspect-ratio: 15 / 5;">
+        <div class="flex items-center pl-4 -mb-6 md:pl-8 md:-mb-8 gap-x-4">
+            <img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" loading="lazy" class="h-24 rounded-full md:h-28 ring-4 ring-white" />
+            <div class="relative flex flex-col text-white -top-2">
+                <h1 class="text-lg font-bold leading-none line-clamp-2 md:leading-none md:text-2xl font-display"><?= $podcast->title ?><span class="ml-1 font-sans text-base font-normal">@<?= $podcast->handle ?></span></h1>
+                <span class="text-xs"><?= lang('Podcast.followers', [
+                    'numberOfFollowers' => $podcast->actor->followers_count,
+                ]) ?></span>
             </div>
         </div>
-        <?= $this->include('podcast/_navigation') ?>
-    </header>
-    
-    <main class="col-span-6">
-        <?= $this->renderSection('content') ?>
-    </main>
-
-    <aside class="sticky col-span-3 top-12">
-        <?php if (
-            in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
-        ): ?>
-        <h2 class="font-semibold"> <?= lang('Podcast.find_on', [
-            'podcastTitle' => $podcast->title,
-        ]) ?></h2>
-        <div class="grid items-center justify-center grid-cols-6 gap-3 mt-2">
-        <?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
-            <?php if ($socialPlatform->is_visible): ?>
-                <?= anchor(
-            $socialPlatform->link_url,
-            icon("{$socialPlatform->type}/{$socialPlatform->slug}"),
-            [
-                'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
-                'target' => '_blank',
-                'rel' => 'noopener noreferrer',
-                'data-toggle' => 'tooltip',
-                'data-placement' => 'bottom',
-                'title' => $socialPlatform->label,
-            ],
-        ) ?>
+        <div class="inline-flex items-center self-end mt-2 mb-2 mr-2 gap-x-2">
+            <?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
+                <IconButton glyph="heart" variant="accent" data-toggle="funding-links" data-toggle-class="hidden"><?= lang('Podcast.sponsor') . lang('Podcast.sponsor_title') ?></IconButton>
             <?php endif; ?>
-        <?php endforeach; ?>
+            <?= anchor_popup(
+                    route_to('follow', $podcast->handle),
+                    icon(
+                        'social/castopod',
+                        'mr-2 text-xl text-rose-200 group-hover:text-rose-50',
+                    ) . lang('Podcast.follow'),
+                    [
+                        'width' => 420,
+                        'height' => 620,
+                        'class' =>
+                            'group inline-flex items-center px-2 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
+                    ],
+                ) ?>
         </div>
-        <?php endif; ?>
+    </header>
+    <?= $this->include('podcast/_partials/navigation') ?>
+    <div class="grid items-start grid-cols-3 col-start-2 pb-12 mt-6 gap-x-6">
+        <main class="col-span-full sm:col-span-2">
+            <?= $this->renderSection('content') ?>
+        </main>
+        <?= $this->include('podcast/_partials/sidebar') ?>
+    </div>
+
+    <?php if (in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)): ?>
+        <?= $this->include('podcast/_partials/funding_links_modal') ?>
+    <?php endif; ?>
 
-        <h2 class="mt-6 font-semibold"><?= lang('Podcast.listen_on') ?></h2>
-        <div class="grid items-center justify-center grid-cols-6 gap-3 mt-2">
-            <?= anchor(route_to('podcast_feed', $podcast->handle), icon('rss'), [
-                'class' =>
-                    'bg-orange-500 text-xl text-white hover:bg-orange-700 w-8 h-8 inline-flex items-center justify-center rounded-lg',
-                'target' => '_blank',
-                'rel' => 'noopener noreferrer',
-                'data-toggle' => 'tooltip',
-                'data-placement' => 'bottom',
-                'title' => lang('Podcast.feed'),
-            ]) ?>
-            <?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
-                <?php if ($podcastingPlatform->is_visible): ?>
-                    <?= anchor(
-                $podcastingPlatform->link_url,
-                icon(
-                    "{$podcastingPlatform->type}/{$podcastingPlatform->slug}",
-                ),
-                [
-                    'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
-                    'target' => '_blank',
-                    'rel' => 'noopener noreferrer',
-                    'data-toggle' => 'tooltip',
-                    'data-placement' => 'bottom',
-                    'title' => $podcastingPlatform->label,
-                ],
-            ) ?>
-                <?php endif; ?>
-        <?php endforeach; ?>
-        </div>
-        <footer class="flex flex-col items-center py-2 mt-8 text-xs text-center text-gray-600 border-t">
-                <?= render_page_links('inline-flex mb-2 flex-wrap gap-y-1') ?>
-                <div class="flex flex-col">
-                    <p><?= $podcast->copyright ?></p>
-                    <p><?= lang('Common.powered_by', [
-                        'castopod' =>
-                            '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
-                    ]) ?></p>
-                </div>
-        </footer>
-    </aside>
 </body>
diff --git a/themes/cp_app/podcast/_layout_authenticated.php b/themes/cp_app/podcast/_layout_authenticated.php
deleted file mode 100644
index eef37085a7e471e6d73122e14652299f25242a8d..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_layout_authenticated.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?= helper('page') ?>
-
-<!DOCTYPE html>
-<html lang="<?= service('request')
-    ->getLocale() ?>">
-
-<head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <link rel="shortcut icon" type="image/png" href="/favicon.ico" />
-
-    <?= $this->renderSection('meta-tags') ?>
-    <?php if ($podcast->payment_pointer): ?>
-        <meta name="monetization" content="<?= $podcast->payment_pointer ?>" />
-    <?php endif; ?>
-
-    <?= service('vite')
-        ->asset('styles/index.css', 'css') ?>
-    <?= service('vite')
-        ->asset('js/podcast.ts', 'js') ?>
-    <?= service('vite')
-        ->asset('js/audio-player.ts', 'js') ?>
-</head>
-
-<body class="w-full min-h-screen pb-20 overflow-x-hidden bg-pine-50 lg:mx-auto sm:pb-0">
-    <?= $this->include('_admin_navbar') ?>
-    <div class="flex">
-
-    <?= $this->include('podcast/_partials/header') ?>
-
-    <main class="flex-shrink-0 w-full min-w-0 sm:w-auto sm:flex-1 sm:flex-shrink">
-        <nav class="sticky top-0 left-0 z-50 flex items-center w-full h-12 px-2 py-1 sm:hidden bg-pine-800">
-                <button
-                data-toggle="main-header"
-                data-toggle-class="sticky -translate-x-full"
-                class="flex-shrink-0 mr-3 overflow-hidden rounded-full focus:ring-2 focus:outline-none focus:ring-pine-50">
-                <img src="<?= $podcast->image
-        ->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-10"/>
-                </button>
-                <p class="flex flex-col flex-1 min-w-0 mr-2 text-white">
-                    <span class="text-sm font-semibold truncate"><?= $podcast->title ?></span>
-                    <span class="text-xs">@<?= $podcast->handle ?></span>
-                </p>
-                <?= anchor_popup(
-            route_to('follow', $podcast->handle),
-            icon(
-                'social/castopod',
-                'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
-            ) . lang('Podcast.follow'),
-            [
-                'width' => 420,
-                'height' => 620,
-                'class' =>
-                    'group inline-flex mr-2 items-center px-3 py-1 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
-            ],
-        ) ?>
-                <button
-                data-toggle="main-sidebar"
-                data-toggle-class="translate-x-full"
-                data-toggle-body-class="-ml-64"
-                class="p-2 text-xl rounded-full focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
-            'menu',
-        ) ?><span class="sr-only"><?= lang('Podcast.toggle_podcast_sidebar') ?></span></button>
-        </nav>
-        <?= $this->renderSection('content') ?>
-    </main>
-
-    <?= $this->include('podcast/_partials/sidebar') ?>
-
-    <button data-toggle="main-sidebar" data-toggle-class="translate-x-full" data-toggle-body-class="-ml-64" class="fixed z-40 hidden p-4 text-xl rounded-full shadow-2xl sm:block lg:hidden bottom-4 left-4 bg-pine-800 focus:outline-none focus:ring-2 focus:ring-pine-600 text-pine-200 hover:text-pine-50"><?= icon(
-            'menu',
-        ) ?><span class="sr-only"><?= lang(
-            'Podcast.toggle_podcast_sidebar',
-        ) ?></span></button>
-
-    <!-- Funding links modal -->
-    <div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
-        <div class="absolute w-full h-full bg-pine-800 bg-opacity-90" role="button" data-toggle="funding-links" data-toggle-class="hidden" aria-label="<?= lang(
-            'Common.close',
-        ) ?>"></div>
-        <div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
-            <div class="flex justify-between px-4 py-2 border-b">
-                <h3 class="self-center text-lg"><?= lang(
-            'Podcast.funding_links',
-            [
-                'podcastTitle' => $podcast->title,
-            ],
-        ) ?></h3>
-                <button data-toggle="funding-links" data-toggle-class="hidden" aria-label="<?= lang(
-            'Common.close',
-        ) ?>" class="self-start p-1 text-2xl">
-                    <?= icon('close') ?>
-                </button>
-            </div>
-            <div class="flex flex-col items-start p-4 space-y-4">
-                <?php foreach (
-                    $podcast->fundingPlatforms
-                    as $fundingPlatform
-                ): ?>
-                    <?php if ($fundingPlatform->is_visible): ?>
-                        <a href="<?= $fundingPlatform->link_url ?>" title="<?= $fundingPlatform->link_content ?>" target="_blank" rel="noopener noreferrer" class="inline-flex items-center font-semibold text-pine-900">
-                            <?= icon(
-                    $fundingPlatform->type .
-                                    '/' .
-                                    $fundingPlatform->slug,
-                    'text-2xl text-gray-400 mr-2',
-                ) . $fundingPlatform->link_url ?>
-                        </a>
-                    <?php endif; ?>
-                <?php endforeach; ?>
-            </div>
-        </div>
-    </div>
-</div>
-</body>
diff --git a/themes/cp_app/podcast/_navigation.php b/themes/cp_app/podcast/_navigation.php
deleted file mode 100644
index 66ce97f9688d0f592beb610bf8df6d97cba03556..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_navigation.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php declare(strict_types=1);
-
-$navigationItems = [
-    [
-        'uri' => route_to('podcast-activity', $podcast->handle),
-        'label' => lang('Podcast.activity'),
-    ],
-    [
-        'uri' => route_to('podcast-episodes', $podcast->handle),
-        'label' => lang('Podcast.episodes'),
-    ],
-    [
-        'uri' => route_to('podcast-about', $podcast->handle),
-        'label' => lang('Podcast.about'),
-    ],
-]
-?>
-<nav class="flex gap-4 px-8">
-    <?php foreach ($navigationItems as $item): ?>
-        <?php $isActive = url_is($item['uri']); ?>
-        <a href="<?= $item['uri'] ?>" class="px-4 py-1 font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?></a>
-    <?php endforeach; ?>
-</nav>
\ No newline at end of file
diff --git a/themes/cp_app/podcast/_partials/comment_actions.php b/themes/cp_app/podcast/_partials/comment_actions.php
deleted file mode 100644
index 93d9eb4a521dc42a4db636d480942e674f5408e0..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_actions.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<footer>
-    <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
-    'Comment.like',
-    [
-        'numberOfLikes' => $comment->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
-    <?php if ($comment->replies_count): ?>
-        <?= anchor(
-    route_to('comment', $podcast->handle, $episode->slug, $comment->id),
-    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
-        'numberOfReplies' => $comment->replies_count,
-    ]),
-    [
-        'class' => 'inline-flex items-center text-xs hover:underline',
-    ]
-) ?>
-    <?php endif; ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_actions_authenticated.php b/themes/cp_app/podcast/_partials/comment_actions_authenticated.php
deleted file mode 100644
index d4812080ee2f92a72ed70c09f6389a2a9d6f168a..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_actions_authenticated.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<footer>
-    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $comment->episode->slug, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
-        <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
-    'Comment.likes',
-    [
-        'numberOfLikes' => $comment->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
-        <Button uri="<?= route_to('comment', $podcast->handle, $comment->episode->slug, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
-    </form>
-    <?php if ($comment->replies_count): ?>
-        <?= anchor(
-    route_to('comment', $podcast->handle, $comment->episode->slug, $comment->id),
-    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
-        'numberOfReplies' => $comment->replies_count,
-    ]),
-    [
-        'class' => 'inline-flex items-center text-xs hover:underline',
-    ]
-) ?>
-    <?php endif; ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_actions_from_post.php b/themes/cp_app/podcast/_partials/comment_actions_from_post.php
deleted file mode 100644
index 5026e64e9ae07d7a202dcbaf4d02cdbd78f0491a..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_actions_from_post.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<footer>
-    <button class="inline-flex items-center opacity-50 cursor-not-allowed hover:underline" title="<?= lang(
-    'Comment.like',
-    [
-        'numberOfLikes' => $comment->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $comment->likes_count ?></button>
-    <?php if ($comment->replies_count): ?>
-        <?= anchor(
-    route_to('post', $podcast->handle, $comment->id),
-    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
-        'numberOfReplies' => $comment->replies_count,
-    ]),
-    [
-        'class' => 'inline-flex items-center text-xs hover:underline',
-    ]
-) ?>
-    <?php endif; ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php b/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php
deleted file mode 100644
index a5783ef4577a2a0998cbebe573bb7c6b32a1822e..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_actions_from_post_authenticated.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<footer>
-    <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $comment->id) ?>" method="POST" class="flex items-center gap-x-4">
-        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline group" title="<?= lang(
-    'Comment.likes',
-    [
-        'numberOfLikes' => $comment->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $comment->likes_count ?></button>
-        <Button uri="<?= route_to('post', $podcast->handle, $comment->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
-    </form>
-    <?php if ($comment->replies_count): ?>
-        <?= anchor(
-    route_to('post', $podcast->handle, $comment->id),
-    icon('caret-down', 'text-xl mr-1') . lang('Comment.view_replies', [
-        'numberOfReplies' => $comment->replies_count,
-    ]),
-    [
-        'class' => 'inline-flex items-center text-xs hover:underline',
-    ]
-) ?>
-    <?php endif; ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_authenticated.php b/themes/cp_app/podcast/_partials/comment_authenticated.php
deleted file mode 100644
index 0958e5465e565999262f4c025d8c7b3b911110f3..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_authenticated.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<article class="relative z-10 flex w-full px-4 py-2 rounded-2xl">
-    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-    <div class="flex-1">
-        <header class="w-full mb-2 text-sm">
-            <a href="<?= $comment->actor
-    ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
-                ? ''
-                : 'target="_blank" rel="noopener noreferrer"' ?>>
-                <span class="mr-2 font-semibold truncate"><?= $comment->actor
-                    ->display_name ?></span>
-                <span class="text-sm text-gray-500 truncate">@<?= $comment->actor
-                    ->username .
-                    ($comment->actor->is_local
-                        ? ''
-                        : '@' . $comment->actor->domain) ?></span>
-                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
-            </a>
-        </header>
-        <div class="mb-2 post-content"><?= $comment->message_html ?></div>
-        <?php if ($comment->is_from_post): ?>
-            <?= $this->include('podcast/_partials/comment_actions_from_post_authenticated') ?>
-        <?php else: ?>
-            <?= $this->include('podcast/_partials/comment_actions_authenticated') ?>
-        <?php endif; ?>
-    </div>
-</article>
diff --git a/themes/cp_app/podcast/_partials/comment_card.php b/themes/cp_app/podcast/_partials/comment_card.php
deleted file mode 100644
index c587234b8545f66a87ecd1b3517b9129365ee5fc..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_card.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<article class="relative z-10 flex w-full p-4 bg-white shadow rounded-2xl">
-    <img src="<?= $comment->actor->avatar_image_url ?>" alt="<?= $comment->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-    <div class="flex-1">
-        <header class="w-full mb-2 text-sm">
-            <a href="<?= $comment->actor
-    ->uri ?>" class="flex items-baseline hover:underline" <?= $comment->actor->is_local
-                ? ''
-                : 'target="_blank" rel="noopener noreferrer"' ?>>
-                <span class="mr-2 font-semibold truncate"><?= $comment->actor
-                    ->display_name ?></span>
-                <span class="text-sm text-gray-500 truncate">@<?= $comment->actor
-                    ->username .
-                    ($comment->actor->is_local
-                        ? ''
-                        : '@' . $comment->actor->domain) ?></span>
-                <?= relative_time($comment->created_at, 'text-xs text-gray-500 ml-auto') ?>
-            </a>
-        </header>
-        <div class="mb-2 post-content"><?= $comment->message_html ?></div>
-        <?php if ($comment->is_from_post): ?>
-            <?= $this->include('podcast/_partials/comment_actions_from_post') ?>
-        <?php else: ?>
-            <footer>
-                <button class="inline-flex items-center opacity-50 cursor-not-allowed" title="<?= lang(
-                            'Comment.likes',
-                            [
-                                'numberOfLikes' => $comment->likes_count,
-                            ],
-                        ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . lang(
-                            'Comment.likes',
-                            [
-                                'numberOfLikes' => $comment->likes_count,
-                            ],
-                        ) ?></button>
-            </footer>
-        <?php endif; ?>
-    </div>
-</article>
diff --git a/themes/cp_app/podcast/_partials/comment_reply_actions.php b/themes/cp_app/podcast/_partials/comment_reply_actions.php
deleted file mode 100644
index 80213166d987a66a846b29e57ce130d29a02daa9..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_reply_actions.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<footer class="flex items-center gap-x-4">
-    <button type="submit" name="action" class="inline-flex items-center opacity-50 cursor-not-allowed" disabled="disabled" title="<?= lang(
-    'Comment.likes',
-    [
-        'numberOfLikes' => $reply->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-500') . $reply->likes_count ?></button>
-    <?php if ($reply->replies_count): ?>
-    <?= anchor(
-    route_to('comment', $podcast->handle, $episode->slug, $reply->id),
-    icon('chat', 'text-2xl mr-1 text-gray-400') . $reply->replies_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'title' => lang('Comment.replies', [
-            'numberOfReplies' => $reply->replies_count,
-        ]),
-    ],
-) ?>
-    <?php endif; ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php b/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php
deleted file mode 100644
index b11b9c01fd4695a48b5e41193e6584639b24de3d..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_reply_actions_authenticated.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<footer>
-    <form action="<?= route_to('comment-attempt-like', interact_as_actor()->username, $episode->slug, $reply->id) ?>" method="POST" class="flex items-center gap-x-4">
-        <button type="submit" name="action" class="inline-flex items-center hover:underline group" title="<?= lang(
-    'Comment.likes',
-    [
-        'numberOfLikes' => $reply->likes_count,
-    ],
-) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400 group-hover:text-red-600') . $reply->likes_count ?></button>
-        <Button uri="<?= route_to('comment', $podcast->handle, $episode->slug, $reply->id) ?>" size="small"><?= lang('Comment.reply') ?></Button>
-    </form>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/comment_reply_authenticated.php b/themes/cp_app/podcast/_partials/comment_reply_authenticated.php
deleted file mode 100644
index 9602808831cdcc2cc5e3c30b7fbfcbfeaedf6b15..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_reply_authenticated.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<article class="flex px-6 py-4 bg-gray-50">
-    <img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
-    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
-    <div class="flex flex-col flex-1 min-w-0">
-        <header class="flex items-center mb-2">
-            <a href="<?= $reply->actor
-    ->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
-    ->actor->is_local
-    ? ''
-    : 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
-        ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
-        ->actor->username .
-    ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
-            <?= relative_time($reply->created_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
-        </header>
-        <p class="mb-2 post-content"><?= $reply->message_html ?></p>
-        <?= $this->include('podcast/_partials/comment_reply_actions_authenticated') ?>
-    </div>
-</article>
diff --git a/themes/cp_app/podcast/_partials/comment_with_replies.php b/themes/cp_app/podcast/_partials/comment_with_replies.php
deleted file mode 100644
index 305b988fb171cb6a6fa5410d5a9d6250683003e2..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_with_replies.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?= $this->include('podcast/_partials/comment_card') ?>
-<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
-
-<?php foreach ($comment->replies as $reply): ?>
-    <?= view('podcast/_partials/comment_reply', [
-        'reply' => $reply,
-    ]) ?>
-<?php endforeach; ?>
-
-</div>
diff --git a/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php b/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php
deleted file mode 100644
index dfd69dcc93f8674202fa38518eb448a9b5b15771..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/comment_with_replies_authenticated.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?=  $this->include('podcast/_partials/comment_card_authenticated') ?>
-<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
-<form action="<?= route_to('comment-attempt-reply', $podcast->id, $episode->id, $comment->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50">
-<img src="<?= interact_as_actor()
-    ->avatar_image_url ?>" alt="<?= interact_as_actor()
-    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
-<div class="flex flex-col flex-1">
-    <Forms.Textarea
-        name="message"
-        required="true"
-        class="w-full mb-4"
-        placeholder="<?= lang('Comment.form.reply_to_placeholder', [
-            'actorUsername' => $comment->actor->username,
-        ]) ?>"
-        rows="1" />
-    <Button variant="primary" size="small" type="submit" name="action" value="reply"><?= lang('Comment.form.submit_reply') ?></Button>
-</div>
-</form>
-
-<?php foreach ($comment->replies as $reply): ?>
-    <?= view('podcast/_partials/comment_reply_authenticated', [
-        'reply' => $reply,
-    ]) ?>
-<?php endforeach; ?>
-</div>
diff --git a/themes/cp_app/podcast/_partials/episode_card.php b/themes/cp_app/podcast/_partials/episode_card.php
deleted file mode 100644
index a77d2b15d0aeafe7b733a06ab8a306d0a17aff87..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/episode_card.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<article class="w-full mb-4 bg-white rounded-lg shadow">
-    <div class="flex p-4">
-        <div class="relative mr-2">
-            <time class="absolute px-1 text-xs font-semibold text-white rounded bottom-2 right-2 bg-black/50" datetime="PT<?= $episode->audio_file_duration ?>S">
-                <?= format_duration(
-    $episode->audio_file_duration,
-) ?>
-            </time>
-            <img loading="lazy" src="<?= $episode->image
-                    ->thumbnail_url ?>" alt="<?= $episode->title ?>" class="object-cover w-20 h-20 rounded-lg" />
-        </div>
-        <div class="flex flex-col flex-1">
-            <a class="flex justify-between text-sm" href="<?= $episode->link ?>">
-                <h2 class="flex-1 font-semibold hover:underline">
-                    <?= episode_numbering(
-                        $episode->number,
-                        $episode->season_number,
-                        'text-xs font-semibold text-gray-600',
-                        true,
-                    ) ?>
-                    <span class="mx-1">-</span>
-                    <?= $episode->title ?>
-                </h2>
-                <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
-            </a>
-            <div class="flex mt-auto gap-x-4">
-                <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
-                <?= anchor(
-                        route_to('episode', $podcast->handle, $episode->slug),
-                        icon('chat', 'text-xl mr-1 text-gray-400') .
-                    $episode->comments_count,
-                        [
-                            'class' =>
-                                'inline-flex items-center hover:underline',
-                            'title' => lang('Episode.number_of_comments', [
-                                'numberOfComments' => $episode->comments_count,
-                            ]),
-                        ],
-                    ) ?>
-            </div>
-        </div>
-    </div>
-</article>
diff --git a/themes/cp_app/podcast/_partials/episode_preview_card.php b/themes/cp_app/podcast/_partials/episode_preview_card.php
deleted file mode 100644
index 6ad4b9092ba5ba1fc4e497757972e952c1cb1aae..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/episode_preview_card.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<div class="flex">
-    <div class="relative">
-        <time class="absolute px-1 text-sm font-semibold text-white bg-black/50 bottom-2 right-2" datetime="PT<?= $episode->audio_file_duration ?>S">
-                    <?= format_duration($episode->audio_file_duration) ?>
-        </time>
-        <img
-        src="<?= $episode->image->thumbnail_url ?>"
-        alt="<?= $episode->title ?>" class="w-24 h-24"/>
-    </div>
-    <div class="flex flex-col flex-1 px-4 py-2 border-t border-b">
-        <a href="<?= $episode->link ?>" class="flex justify-between flex-1">
-            <div class="flex items-baseline font-semibold">
-                <?= episode_numbering(
-    $episode->number,
-    $episode->season_number,
-    'text-xs font-semibold text-gray-600',
-    true,
-) ?>
-                <span class="mx-1">-</span>
-                <?= $episode->title ?>
-            </div>
-            <?= relative_time($episode->published_at, 'text-xs whitespace-nowrap') ?>
-        </a>
-        <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $episode->podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype, 'mt-auto') ?>
-    </div>
-</div>
diff --git a/themes/cp_app/podcast/_partials/funding_links_modal.php b/themes/cp_app/podcast/_partials/funding_links_modal.php
new file mode 100644
index 0000000000000000000000000000000000000000..371b191bb7f345e63d9e723d94eefd9c309f1ff9
--- /dev/null
+++ b/themes/cp_app/podcast/_partials/funding_links_modal.php
@@ -0,0 +1,39 @@
+<div id="funding-links" class="fixed top-0 left-0 z-50 flex items-center justify-center hidden w-screen h-screen">
+    <div
+    class="absolute w-full h-full bg-pine-800/75"
+    role="button"
+    data-toggle="funding-links"
+    data-toggle-class="hidden"
+    aria-label="<?= lang('Common.close') ?>"></div>
+    <div class="z-10 w-full max-w-xl bg-white rounded-lg shadow-2xl">
+        <div class="flex justify-between px-4 py-2 border-b">
+            <h3 class="self-center text-lg"><?= lang('Podcast.funding_links', [
+                'podcastTitle' => $podcast->title,
+            ]) ?></h3>
+            <button
+            data-toggle="funding-links"
+            data-toggle-class="hidden"
+            aria-label="<?= lang('Common.close') ?>"
+            class="self-start p-1 text-2xl"><?= icon('close') ?></button>
+        </div>
+        <div class="flex flex-col items-start p-4 space-y-4">
+            <?php foreach ($podcast->fundingPlatforms as $fundingPlatform): ?>
+                <?php if ($fundingPlatform->is_visible): ?>
+                    <a
+                    href="<?= $fundingPlatform->link_url ?>"
+                    title="<?= $fundingPlatform->link_content ?>"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    class="inline-flex items-center font-semibold text-pine-900">
+                    <?= icon(
+                $fundingPlatform->type .
+                            '/' .
+                            $fundingPlatform->slug,
+                'mr-2',
+            ) . $fundingPlatform->link_url ?>
+                    </a>
+                <?php endif; ?>
+            <?php endforeach; ?>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/themes/cp_app/podcast/_partials/header.php b/themes/cp_app/podcast/_partials/header.php
deleted file mode 100644
index f5d57ea96bf8b464c0b2a954f755dc987a94213c..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/header.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<header id="main-header" class="fixed top-0 left-0 flex-col flex-shrink-0 h-screen transform -translate-x-full sm:left-auto sm:-translate-x-0 sm:sticky w-80 sm:w-64 lg:w-80 xl:w-112 sm:flex">
-    <img src="<?= $podcast->actor
-    ->cover_image_url ?>" alt="" class="object-cover w-full h-48 bg-pine-800"/>
-    <div class="flex items-center justify-between px-4 py-2 mb-4 lg:px-6 -mt-14 lg:-mt-16 xl:-mt-20">
-        <img src="<?= $podcast->image
-    ->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="h-24 rounded-full shadow-xl xl:h-36 lg:h-28 ring-4 ring-pine-50" />
-        <?= anchor_popup(
-        route_to('follow', $podcast->handle),
-        icon(
-            'social/castopod',
-            'mr-2 text-xl text-pink-200 group-hover:text-pink-50',
-        ) . lang('Podcast.follow'),
-        [
-            'width' => 420,
-            'height' => 620,
-            'class' =>
-                'group inline-flex items-center px-4 py-2 text-xs tracking-wider font-semibold text-white uppercase rounded-full shadow focus:outline-none focus:ring bg-rose-600',
-        ],
-    ) ?>
-    </div>
-    <div class="px-6">
-        <h1 class="inline-flex items-center text-2xl font-bold leading-none font-display"><?= $podcast->title .
-            ($podcast->parental_advisory === 'explicit'
-                ? '<span class="px-1 ml-2 text-xs font-semibold leading-tight tracking-wider text-gray-600 uppercase border-2 border-gray-500">' .
-                    lang('Common.explicit') .
-                    '</span>'
-                : '') ?></h1>
-        <p class="mb-4 font-semibold text-gray-600">@<?= $podcast->handle ?></p>
-        <div class="mb-2"><?= $podcast->description_html ?></div>
-        <?= location_link($podcast->location, 'text-sm mb-4') ?>
-        <div class="mb-6 space-x-4">
-            <span class="px-2 py-1 text-sm text-gray-800 bg-gray-200">
-                <?= lang(
-                    'Podcast.category_options.' . $podcast->category->code,
-                ) ?>
-            </span>
-            <?php foreach ($podcast->other_categories as $other_category): ?>
-                <span class="px-2 py-1 text-sm text-gray-800 bg-gray-200">
-                    <?= lang(
-                    'Podcast.category_options.' . $other_category->code,
-                ) ?>
-                </span>
-            <?php endforeach; ?>
-        </div>
-        <?= person_list($podcast->persons, 'mb-6') ?>
-        <div class="space-x-4">
-            <a href="#" class="hover:underline"><?= lang('Podcast.followers', [
-                'numberOfFollowers' => $podcast->actor->followers_count,
-            ]) ?></a>
-            <a href="<?= route_to(
-                'podcast-activity',
-                $podcast->handle,
-            ) ?>" class="hover:underline"><?= lang('Podcast.posts', [
-                'numberOfPosts' => $podcast->actor->posts_count,
-            ]) ?></a>
-        </div>
-    </div>
-</header>
diff --git a/themes/cp_app/podcast/_partials/navigation.php b/themes/cp_app/podcast/_partials/navigation.php
new file mode 100644
index 0000000000000000000000000000000000000000..21de98414039ee5405032e17114971a9f4771a96
--- /dev/null
+++ b/themes/cp_app/podcast/_partials/navigation.php
@@ -0,0 +1,24 @@
+<?php declare(strict_types=1);
+
+$navigationItems = [
+    [
+        'uri' => route_to('podcast-activity', $podcast->handle),
+        'label' => lang('Podcast.activity'),
+    ],
+    [
+        'uri' => route_to('podcast-episodes', $podcast->handle),
+        'label' => lang('Podcast.episodes'),
+    ],
+    [
+        'uri' => route_to('podcast-about', $podcast->handle),
+        'label' => lang('Podcast.about'),
+    ],
+]
+?>
+<nav class="sticky z-40 flex col-start-2 px-4 pt-8 bg-white shadow gap-x-2 md:gap-x-4 md:px-8 -top-8 md:-top-12 md:rounded-b-xl md:pt-12 ">
+    <?php foreach ($navigationItems as $item): ?>
+        <?php $isActive = url_is($item['uri']); ?>
+        <a href="<?= $item['uri'] ?>" class="px-4 py-1 text-sm font-semibold uppercase border-b-4<?= $isActive ? ' border-b-4 text-pine-500 border-pine-500' : ' text-gray-500 hover:text-gray-900 hover:border-gray-200 border-transparent' ?>"><?= $item['label'] ?></a>
+    <?php endforeach; ?>
+    <button type="button" class="p-2 ml-auto rotate-180 rounded-full sm:hidden focus:outline-none focus:ring-2 focus:ring-pine-500 focus:ring-offset-2 focus:ring-offset-pine-100"><?= icon('menu') ?></button>
+</nav>
\ No newline at end of file
diff --git a/themes/cp_app/podcast/_partials/post.php b/themes/cp_app/podcast/_partials/post.php
deleted file mode 100644
index 3d40f4604bc06682677386b65a81b982c9f20717..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/post.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<article class="relative z-10 w-full bg-white shadow rounded-2xl">
-    <header class="flex px-6 py-4">
-        <img src="<?= $post->actor
-    ->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-        <div class="flex flex-col min-w-0">
-            <a href="<?= $post->actor
-    ->uri ?>" class="flex items-baseline hover:underline" <?= $post
-    ->actor->is_local
-    ? ''
-    : 'target="_blank" rel="noopener noreferrer"' ?>>
-                <span class="mr-2 font-semibold truncate"><?= $post->actor
-        ->display_name ?></span>
-                <span class="text-sm text-gray-500 truncate">@<?= $post->actor
-        ->username .
-                    ($post->actor->is_local
-                        ? ''
-                        : '@' . $post->actor->domain) ?></span>
-            </a>
-            <a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
-            class="text-xs text-gray-500">
-                <?= relative_time($post->published_at) ?>
-            </a>
-        </div>
-    </header>
-    <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
-    <?php if ($post->episode_id): ?>
-        <?= view('podcast/_partials/episode_preview_card', [
-            'episode' => $post->episode,
-        ]) ?>
-    <?php elseif ($post->has_preview_card): ?>
-        <?= view('podcast/_partials/preview_card', [
-            'preview_card' => $post->preview_card,
-        ]) ?>
-    <?php endif; ?>
-    <?= $this->include('podcast/_partials/post_actions') ?>
-</article>
diff --git a/themes/cp_app/podcast/_partials/post_actions.php b/themes/cp_app/podcast/_partials/post_actions.php
deleted file mode 100644
index bf29cccca33b8cf5f9d162c5c2e9f5b6b9595b3a..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/post_actions.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<footer class="flex justify-around px-6 py-3">
-    <?= anchor(
-    route_to('post', $podcast->handle, $post->id),
-    icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'title' => lang('Post.replies', [
-            'numberOfReplies' => $post->replies_count,
-        ]),
-    ],
-) ?>
-    <?= anchor_popup(
-    route_to('post-remote-action', $podcast->handle, $post->id, 'reblog'),
-    icon('repeat', 'text-2xl mr-1 text-gray-400') . $post->reblogs_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'width' => 420,
-        'height' => 620,
-        'title' => lang('Post.reblogs', [
-            'numberOfReblogs' => $post->reblogs_count,
-        ]),
-    ],
-) ?>
-    <?= anchor_popup(
-    route_to('post-remote-action', $podcast->handle, $post->id, 'favourite'),
-    icon('heart', 'text-2xl mr-1 text-gray-400') . $post->favourites_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'width' => 420,
-        'height' => 620,
-        'title' => lang('Post.favourites', [
-            'numberOfFavourites' => $post->favourites_count,
-        ]),
-    ],
-) ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/post_actions_authenticated.php b/themes/cp_app/podcast/_partials/post_actions_authenticated.php
deleted file mode 100644
index a50c87db2ecaed67c4737a1b1127c6631f38f590..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/post_actions_authenticated.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<footer class="px-6 py-3">
-    <form action="<?= route_to(
-    'post-attempt-action',
-    interact_as_actor()
-        ->username,
-    $post->id,
-) ?>" method="POST" class="flex justify-around">
-        <?= csrf_field() ?>
-        <?= anchor(
-    route_to('post', $podcast->handle, $post->id),
-    icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'title' => lang('Post.replies', [
-            'numberOfReplies' => $post->replies_count,
-        ]),
-    ],
-) ?>
-        <button type="submit" name="action" value="reblog" class="inline-flex items-center hover:underline" title="<?= lang(
-    'Post.reblogs',
-    [
-        'numberOfReblogs' => $post->reblogs_count,
-    ],
-) ?>"><?= icon('repeat', 'text-2xl mr-1 text-gray-400') .
-    $post->reblogs_count ?></button>
-        <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline" title="<?= lang(
-        'Post.favourites',
-        [
-            'numberOfFavourites' => $post->favourites_count,
-        ],
-    ) ?>"><?= icon('heart', 'text-2xl mr-1 text-gray-400') .
-    $post->favourites_count ?></button>
-        <button id="<?= $post->id .
-            '-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $post->id .
-    '-more-dropdown-menu' ?>" aria-label="<?= lang(
-        'Common.more',
-    ) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
-        </button>
-    </form>
-    <nav id="<?= $post->id .
-        '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $post->id .
-    '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
-        <?= anchor(
-        route_to('post', $podcast->handle, $post->id),
-        lang('Post.expand'),
-        [
-            'class' => 'px-4 py-1 hover:bg-gray-100',
-        ],
-    ) ?>
-        <form action="<?= route_to(
-        'post-attempt-block-actor',
-        interact_as_actor()
-            ->username,
-        $post->id,
-    ) ?>" method="POST">
-            <?= csrf_field() ?>
-            <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
-        'Post.block_actor',
-        [
-            'actorUsername' => $post->actor->username,
-        ],
-    ) ?></button>
-        </form>
-        <form action="<?= route_to(
-        'post-attempt-block-domain',
-        interact_as_actor()
-            ->username,
-        $post->id,
-    ) ?>" method="POST">
-            <?= csrf_field() ?>
-            <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
-        'Post.block_domain',
-        [
-            'actorDomain' => $post->actor->domain,
-        ],
-    ) ?></button>
-        </form>
-        <?php if ($post->actor->is_local): ?>
-            <hr class="my-2" />
-            <form action="<?= route_to(
-        'post-attempt-delete',
-        $post->actor->username,
-        $post->id,
-    ) ?>" method="POST">
-                <?= csrf_field() ?>
-                <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
-        'Post.delete',
-    ) ?></button>
-            </form>
-        <?php endif; ?>
-    </nav>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/post_with_replies.php b/themes/cp_app/podcast/_partials/post_with_replies.php
deleted file mode 100644
index ddf3489fe557bea1a1db00133a7560487627f172..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/post_with_replies.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?= $this->include('podcast/_partials/post') ?>
-<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
-
-<div class="px-6 pt-8 pb-4 bg-gray-50">
-<?= anchor_popup(
-    route_to('post-remote-action', $podcast->handle, $post->id, 'reply'),
-    lang('Post.reply_to', [
-        'actorUsername' => $post->actor->username,
-    ]),
-    [
-        'class' =>
-            'text-center justify-center font-semibold rounded-full shadow relative z-10 px-4 py-2 w-full bg-rose-600 text-white inline-flex items-center hover:bg-rose-700',
-        'width' => 420,
-        'height' => 620,
-    ],
-) ?>
-</div>
-
-
-<?php if ($post->has_replies): ?>
-    <?php foreach ($post->replies as $reply): ?>
-        <?= view('podcast/_partials/reply', [
-            'reply' => $reply,
-        ]) ?>
-    <?php endforeach; ?>
-<?php endif; ?>
-</div>
diff --git a/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php b/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php
deleted file mode 100644
index 9dbd498a434bec7dfdeeff4d87977d4b077993e9..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/post_with_replies_authenticated.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?= $this->include('podcast/_partials/post_authenticated') ?>
-<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
-<form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex px-6 pt-8 pb-4 bg-gray-50" >
-<img src="<?= interact_as_actor()
-    ->avatar_image_url ?>" alt="<?= interact_as_actor()
-    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
-<div class="flex flex-col flex-1">
-    <Forms.Textarea
-        name="message"
-        class="w-full mb-4"
-        required="true"
-        placeholder="<?= lang('Post.form.reply_to_placeholder', [
-            'actorUsername' => $post->actor->username,
-        ]) ?>"
-        rows="1" />
-    <Button variant="primary" size="small" type="submit" name="action" value="reply" class="self-end"><?= lang('Post.form.submit_reply') ?></Button>
-</div>
-</form>
-
-<?php if ($post->has_replies): ?>
-    <?php foreach ($post->replies as $reply): ?>
-        <?= view('podcast/_partials/reply_authenticated', [
-            'reply' => $reply,
-        ]) ?>
-    <?php endforeach; ?>
-<?php endif; ?>
-</div>
diff --git a/themes/cp_app/podcast/_partials/reblog_authenticated.php b/themes/cp_app/podcast/_partials/reblog_authenticated.php
deleted file mode 100644
index cc8a4b4132cf33cb4c049d7e114630484ddec6a0..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/reblog_authenticated.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<article class="relative z-10 w-full bg-white shadow rounded-2xl">
-    <p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon(
-    'repeat',
-    'text-lg mr-2 text-gray-400',
-) .
-        lang('Post.actor_shared', [
-            'actor' => $post->actor->display_name,
-        ]) ?></p>
-    <header class="flex px-6 py-4">
-        <img src="<?= $post->actor
-            ->avatar_image_url ?>" alt="<?= $post->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-        <div class="flex flex-col min-w-0">
-            <a href="<?= $post->actor
-            ->uri ?>" class="flex items-baseline hover:underline" <?= $post
-            ->actor->is_local
-    ? ''
-    : 'target="_blank" rel="noopener noreferrer"' ?>>
-                <span class="mr-2 font-semibold truncate"><?= $post->actor
-        ->display_name ?></span>
-                <span class="text-sm text-gray-500 truncate">@<?= $post->actor
-        ->username .
-                    ($post->actor->is_local
-                        ? ''
-                        : '@' . $post->actor->domain) ?></span>
-            </a>
-            <a href="<?= route_to('post', $podcast->handle, $post->id) ?>"
-            class="text-xs text-gray-500">
-                <?= relative_time($post->published_at) ?>
-            </a>
-        </div>
-    </header>
-    <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
-    <?php if ($post->episode_id): ?>
-        <?= view('podcast/_partials/episode_preview_card', [
-            'episode' => $post->episode,
-        ]) ?>
-    <?php elseif ($post->has_preview_card): ?>
-        <?= view('podcast/_partials/preview_card', [
-            'preview_card' => $post->preview_card,
-        ]) ?>
-    <?php endif; ?>
-    <?= $this->include('podcast/_partials/post_actions_authenticated') ?>
-</article>
diff --git a/themes/cp_app/podcast/_partials/reply_actions.php b/themes/cp_app/podcast/_partials/reply_actions.php
deleted file mode 100644
index a11b9da360c948af790c46d5a0978bdbdabc7f0d..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/reply_actions.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<footer class="mt-2 space-x-6 text-sm">
-    <?= anchor(
-    route_to('post', $podcast->handle, $reply->id),
-    icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'title' => lang('Post.replies', [
-            'numberOfReplies' => $reply->replies_count,
-        ]),
-    ],
-) ?>
-    <?= anchor_popup(
-    route_to('post-remote-action', $podcast->handle, $reply->id, 'reblog'),
-    icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'width' => 420,
-        'height' => 620,
-        'title' => lang('Post.reblogs', [
-            'numberOfReblogs' => $reply->reblogs_count,
-        ]),
-    ],
-) ?>
-    <?= anchor_popup(
-    route_to('post-remote-action', $podcast->handle, $reply->id, 'favourite'),
-    icon('heart', 'text-xl mr-1 text-gray-400') . $reply->favourites_count,
-    [
-        'class' => 'inline-flex items-center hover:underline',
-        'width' => 420,
-        'height' => 620,
-        'title' => lang('Post.favourites', [
-            'numberOfFavourites' => $reply->favourites_count,
-        ]),
-    ],
-) ?>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/reply_actions_authenticated.php b/themes/cp_app/podcast/_partials/reply_actions_authenticated.php
deleted file mode 100644
index ea54f20d232a217490f6c5dfd1fd445d883d0ba6..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/reply_actions_authenticated.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<footer class="mt-2 text-sm">
-    <form action="<?= route_to(
-    'post-attempt-action',
-    interact_as_actor()
-        ->username,
-    $reply->id,
-) ?>" method="POST" class="flex items-start">
-        <?= csrf_field() ?>
-        <?= anchor(
-    route_to('post', $podcast->handle, $reply->id),
-    icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
-    [
-        'class' => 'inline-flex items-center mr-6 hover:underline',
-        'title' => lang('Post.replies', [
-            'numberOfReplies' => $reply->replies_count,
-        ]),
-    ],
-) ?>
-        <button type="submit" name="action" value="reblog" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
-    'Post.reblogs',
-    [
-        'numberOfReblogs' => $reply->reblogs_count,
-    ],
-) ?>"><?= icon('repeat', 'text-xl mr-1 text-gray-400') .
-    $reply->reblogs_count ?></button>
-        <button type="submit" name="action" value="favourite" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
-        'Post.favourites',
-        [
-            'numberOfFavourites' => $reply->favourites_count,
-        ],
-    ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400') .
-    $reply->favourites_count ?></button>
-        <button id="<?= $reply->id .
-            '-more-dropdown' ?>" type="button" class="text-xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $reply->id .
-    '-more-dropdown-menu' ?>" aria-label="<?= lang(
-        'Common.more',
-    ) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
-        </button>
-    </form>
-    <nav id="<?= $reply->id .
-        '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $reply->id .
-    '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
-        <?= anchor(
-        route_to('post', $podcast->handle, $reply->id),
-        lang('Post.expand'),
-        [
-            'class' => 'px-4 py-1 hover:bg-gray-100',
-        ],
-    ) ?>
-        <form action="<?= route_to(
-        'post-attempt-block-actor',
-        interact_as_actor()
-            ->username,
-        $reply->id,
-    ) ?>" method="POST">
-            <?= csrf_field() ?>
-            <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
-        'Post.block_actor',
-        [
-            'actorUsername' => $reply->actor->username,
-        ],
-    ) ?></button>
-        </form>
-        <form action="<?= route_to(
-        'post-attempt-block-domain',
-        interact_as_actor()
-            ->username,
-        $reply->id,
-    ) ?>" method="POST">
-            <?= csrf_field() ?>
-            <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
-        'Post.block_domain',
-        [
-            'actorDomain' => $reply->actor->domain,
-        ],
-    ) ?></button>
-        </form>
-        <?php if ($reply->actor->is_local): ?>
-            <hr class="my-2" />
-            <form action="<?= route_to(
-        'post-attempt-delete',
-        $reply->actor->username,
-        $reply->id,
-    ) ?>" method="POST">
-                <?= csrf_field() ?>
-                <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
-        'Post.delete',
-    ) ?></button>
-            </form>
-        <?php endif; ?>
-    </nav>
-</footer>
diff --git a/themes/cp_app/podcast/_partials/reply_authenticated.php b/themes/cp_app/podcast/_partials/reply_authenticated.php
deleted file mode 100644
index 582c7594812e021b0b570d0dd8affc0b886a10ae..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/_partials/reply_authenticated.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<article class="flex px-6 py-4 bg-gray-50">
-    <img src="<?= $reply->actor->avatar_image_url ?>" alt="<?= $reply->actor
-    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
-    <div class="flex flex-col flex-1 min-w-0">
-        <header class="flex items-center mb-2">
-            <a href="<?= $reply->actor
-    ->uri ?>" class="mr-2 text-base font-semibold truncate hover:underline" <?= $reply
-    ->actor->is_local
-    ? ''
-    : 'target="_blank" rel="noopener noreferrer"' ?>><?= $reply->actor
-        ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
-        ->actor->username .
-    ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
-            <?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
-        </header>
-        <p class="mb-2 post-content"><?= $reply->message_html ?></p>
-        <?php if ($reply->has_preview_card): ?>
-            <?= view('podcast/_partials/preview_card', [
-                'preview_card' => $reply->preview_card,
-            ]) ?>
-        <?php endif; ?>
-        <?= $this->include('podcast/_partials/reply_actions_authenticated') ?>
-    </div>
-</article>
diff --git a/themes/cp_app/podcast/_partials/sidebar.php b/themes/cp_app/podcast/_partials/sidebar.php
index 6e0853f59e841b39f9e6efec7bcd6cc636e6462a..2af44f6b483ec5815e22ebb8a3ff0db89f4bc385 100644
--- a/themes/cp_app/podcast/_partials/sidebar.php
+++ b/themes/cp_app/podcast/_partials/sidebar.php
@@ -1,65 +1,49 @@
-<aside id="main-sidebar" class="fixed top-0 right-0 flex flex-col items-start flex-shrink-0 w-64 h-screen px-6 py-4 overflow-y-auto transform translate-x-full lg:sticky lg:translate-x-0">
-    <?php if (
-        in_array(true, array_column($podcast->fundingPlatforms, 'is_visible'), true)
-    ): ?>
-    <h2 class="mb-2 text-sm font-semibold"><?= lang(
-        'Podcast.sponsor_title',
-    ) ?></h2>
-    <button
-    class="inline-flex items-center px-2 py-1 mb-8 text-sm font-semibold text-gray-600 border rounded-full shadow-sm focus:outline-none focus:ring focus:ring-pine-600 hover:bg-rose-200 hover:text-gray-800 border-rose-600 bg-rose-100"
-    data-toggle="funding-links"
-    data-toggle-class="hidden"><?= icon('heart', 'mr-2 text-rose-600') .
-        lang('Podcast.sponsor') ?></button>
-    <?php endif; ?>
-
-    <?php if (
-        in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
-    ): ?>
-    <h2 class="mb-2 text-sm font-semibold"> <?= lang('Podcast.find_on', [
-        'podcastTitle' => $podcast->title,
-    ]) ?></h2>
-    <div class="grid items-center justify-center grid-cols-5 gap-3 mb-8">
-    <?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
-        <?php if ($socialPlatform->is_visible): ?>
-            <?= anchor(
-        $socialPlatform->link_url,
-        icon($socialPlatform->type . '/' . $socialPlatform->slug),
-        [
-            'class' => 'text-2xl text-gray-500 hover:text-gray-700',
-            'target' => '_blank',
-            'rel' => 'noopener noreferrer',
-            'data-toggle' => 'tooltip',
-            'data-placement' => 'bottom',
-            'title' => $socialPlatform->label,
-        ],
-    ) ?>
+<aside id="podcast-sidebar" class="sticky hidden col-span-1 sm:block top-12">
+    <div class="absolute z-0 w-full h-full sm:hidden bg-pine-800/50"></div>
+    <div class="z-10 bg-pine-50">
+        <a href="<?= route_to('podcast_feed', $podcast->handle) ?>" class="inline-flex items-center mb-6 text-sm font-semibold text-pine-800 group" target="_blank" rel="noopener noreferrer">
+            <?= icon('rss', ' mr-2 bg-orange-500 text-xl text-white group-hover:bg-orange-700 p-1 w-6 h-6 inline-flex items-center justify-center rounded-lg') . lang('Podcast.feed') ?>
+        </a>
+        <?php if (
+            in_array(true, array_column($podcast->socialPlatforms, 'is_visible'), true)
+        ): ?>
+        <h2 class="mb-2 font-bold font-display text-pine-900"> <?= lang('Podcast.find_on', [
+            'podcastTitle' => $podcast->title,
+        ]) ?></h2>
+        <div class="grid items-center justify-center grid-cols-6 gap-3 mb-6">
+        <?php foreach ($podcast->socialPlatforms as $socialPlatform): ?>
+            <?php if ($socialPlatform->is_visible): ?>
+                <?= anchor(
+            $socialPlatform->link_url,
+            icon("{$socialPlatform->type}/{$socialPlatform->slug}"),
+            [
+                'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
+                'target' => '_blank',
+                'rel' => 'noopener noreferrer',
+                'data-toggle' => 'tooltip',
+                'data-placement' => 'bottom',
+                'title' => $socialPlatform->label,
+            ],
+        ) ?>
+            <?php endif; ?>
+        <?php endforeach; ?>
+        </div>
         <?php endif; ?>
-    <?php endforeach; ?>
-    </div>
-    <?php endif; ?>
 
-    <h2 class="mb-2 text-sm font-semibold"><?= lang('Podcast.listen_on') ?></h2>
-    <div class="grid items-center justify-center grid-cols-5 gap-3 mb-8">
-        <?= anchor(route_to('podcast_feed', $podcast->handle), icon('rss'), [
-            'class' =>
-                'bg-yellow-500 text-xl text-yellow-900 hover:bg-yellow-600 w-8 h-8 inline-flex items-center justify-center rounded-lg',
-            'target' => '_blank',
-            'rel' => 'noopener noreferrer',
-            'data-toggle' => 'tooltip',
-            'data-placement' => 'bottom',
-            'title' => lang('Podcast.feed'),
-        ]) ?>
-        <?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
-            <?php if ($podcastingPlatform->is_visible): ?>
-                <?= anchor(
+        <?php if (
+            in_array(true, array_column($podcast->podcastingPlatforms, 'is_visible'), true)
+        ): ?>
+        <h2 class="mb-2 font-bold font-display text-pine-900"><?= lang('Podcast.listen_on') ?></h2>
+        <div class="grid items-center justify-center grid-cols-6 gap-3 mb-6">
+            <?php foreach ($podcast->podcastingPlatforms as $podcastingPlatform): ?>
+                <?php if ($podcastingPlatform->is_visible): ?>
+                    <?= anchor(
             $podcastingPlatform->link_url,
             icon(
-                $podcastingPlatform->type .
-                            '/' .
-                            $podcastingPlatform->slug,
+                "{$podcastingPlatform->type}/{$podcastingPlatform->slug}",
             ),
             [
-                'class' => 'text-2xl text-gray-500 hover:text-gray-700',
+                'class' => 'text-2xl text-gray-500 hover:text-gray-700 w-8 h-8 items-center inline-flex justify-center',
                 'target' => '_blank',
                 'rel' => 'noopener noreferrer',
                 'data-toggle' => 'tooltip',
@@ -67,19 +51,19 @@
                 'title' => $podcastingPlatform->label,
             ],
         ) ?>
-            <?php endif; ?>
-    <?php endforeach; ?>
-    </div>
-    <footer class="px-2 py-4 mt-auto text-gray-600 border-t">
-        <div class="container flex flex-col justify-between mx-auto text-xs">
+                <?php endif; ?>
+            <?php endforeach; ?>
+        </div>
+        <?php endif; ?>
+        <footer class="flex flex-col items-center py-2 text-xs text-center text-gray-600 border-t">
             <?= render_page_links('inline-flex mb-2 flex-wrap gap-y-1') ?>
             <div class="flex flex-col">
                 <p><?= $podcast->copyright ?></p>
                 <p><?= lang('Common.powered_by', [
                     'castopod' =>
-                        '<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                        '<a class="inline-flex font-semibold text-gray-500 hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
                 ]) ?></p>
             </div>
-        </div>
-    </footer>
-</aside>
+        </footer>
+    </div>
+</aside>
\ No newline at end of file
diff --git a/themes/cp_app/podcast/about.php b/themes/cp_app/podcast/about.php
index 436beeae4b46a18d7fb871808ca38643c0ae5f75..a616a64f5afbcf80092ba385692da3a7d8d09ecd 100644
--- a/themes/cp_app/podcast/about.php
+++ b/themes/cp_app/podcast/about.php
@@ -29,7 +29,53 @@
 
 <?= $this->section('content') ?>
 
+<div class="px-2 sm:px-4">
+<div class="mb-2"><?= $podcast->description_html ?></div>
+<div class="flex gap-x-4 gap-y-2">
+    <span class="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border">
+        <?= lang(
+        'Podcast.category_options.' . $podcast->category->code,
+    ) ?>
+    </span>
+    <?php foreach ($podcast->other_categories as $other_category): ?>
+        <span class="px-2 py-1 text-sm font-semibold text-gray-800 bg-gray-100 border">
+            <?= lang(
+        'Podcast.category_options.' . $other_category->code,
+    ) ?>
+        </span>
+    <?php endforeach; ?>
+</div>
 
+<div class="flex items-center mt-4 gap-x-8">
+    <?php if ($podcast->persons !== []): ?>
+        <button class="flex items-center text-xs font-semibold gap-x-2 hover:underline" data-toggle="persons-list" data-toggle-class="hidden">
+            <div class="inline-flex flex-row-reverse">
+                <?php $i = 0; ?>
+                <?php foreach ($podcast->persons as $person): ?>
+                    <img src="<?= $person->image->thumbnail_url ?>" alt="<?= $person->full_name ?>" class="object-cover w-8 h-8 -ml-5 border-2 rounded-full border-pine-100 last:ml-0" />
+                    <?php $i++; if ($i === 3) {
+        break;
+    }?>
+                <?php endforeach; ?>
+            </div>
+            <?= lang('Podcast.persons', [
+                'personsCount' => count($podcast->persons),
+            ]) ?>
+        </button>
+    <?php endif; ?>
+    <?php if ($podcast->location): ?>
+        <?= location_link($podcast->location, 'text-xs font-semibold p-2') ?>
+    <?php endif; ?>
+</div>
+</div>
+
+
+<?= view('_persons_modal', [
+    'title' => lang('Podcast.persons_list', [
+        'podcastTitle' => $podcast->title,
+    ]),
+    'persons' => $podcast->persons,
+]) ?>
 
 <?= $this->endSection()
 ?>
diff --git a/themes/cp_app/podcast/activity.php b/themes/cp_app/podcast/activity.php
index 1a1b5176fad0b225135447f9f04e076db071e300..1fa7a529c2ce7c7bd02c675718f8cc04fb0fd80c 100644
--- a/themes/cp_app/podcast/activity.php
+++ b/themes/cp_app/podcast/activity.php
@@ -27,21 +27,52 @@
 
 <?= $this->section('content') ?>
 
-<section class="max-w-2xl mx-auto space-y-8">
+<section class="w-full">
 
-<?php foreach ($posts as $post): ?>
-    <?php if ($post->reblog_of_id !== null): ?>
-        <?= view('podcast/_partials/reblog', [
-    'post' => $post->reblog_of_post,
-            'podcast' => $podcast,
+<?php if (can_user_interact()): ?>
+
+    <form action="<?= route_to('post-attempt-create', interact_as_actor()->username) ?>" method="POST" class="flex p-4 bg-white shadow rounded-xl">
+    <?= csrf_field() ?>
+
+    <?= view('_message_block') ?>
+
+    <img src="<?= interact_as_actor()
+        ->avatar_image_url ?>" alt="<?= interact_as_actor()
+        ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+    <div class="flex flex-col flex-1 min-w-0 gap-y-2">
+        <Forms.Textarea
+            name="message"
+            required="true"
+            placeholder="<?= lang('Post.form.message_placeholder') ?>"
+            rows="2" />
+        <Forms.Input
+            name="episode_url"
+            type="url"
+            placeholder="<?= lang('Post.form.episode_url_placeholder') . ' (' . lang('Common.optional') . ')' ?>" />
+        <Button variant="primary" size="small" type="submit" class="self-end" iconRight="send-plane"><?= lang('Post.form.submit') ?></Button>
+    </div>
+</form>
+<hr class="my-4 border-2 border-pine-100">
+
+<?php endif; ?>
+<div class="flex flex-col gap-y-6">
+    <?php foreach ($posts as $key => $post): ?>
+        <?php if ($post->reblog_of_id !== null): ?>
+            <?= view('post/_partials/reblog', [
+    'index' => $key,
+                'post' => $post->reblog_of_post,
+                'podcast' => $podcast,
 ]) ?>
-    <?php else: ?>
-        <?= view('podcast/_partials/post', [
-    'post' => $post,
-            'podcast' => $podcast,
+        <?php else: ?>
+            <?= view('post/_partials/card', [
+    'index' => $key,
+                'post' => $post,
+                'podcast' => $podcast,
 ]) ?>
-    <?php endif; ?>
-<?php endforeach; ?>
+        <?php endif; ?>
+    <?php endforeach; ?>
+</div>
+
 </section>
 
 <?= $this->endSection() ?>
diff --git a/themes/cp_app/podcast/activity_authenticated.php b/themes/cp_app/podcast/activity_authenticated.php
deleted file mode 100644
index 8e582c7020d4462f68fc9cbe0fd1a9b0a5853d0c..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/activity_authenticated.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?= $this->extend('podcast/_layout_authenticated') ?>
-
-<?= $this->section('meta-tags') ?>
-<link type="application/rss+xml" rel="alternate" title="<?= $podcast->title ?>" href="<?= $podcast->feed_url ?>"/>
-
-<title><?= $podcast->title ?></title>
-<meta name="description" content="<?= htmlspecialchars(
-    $podcast->description,
-) ?>" />
-<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
-<link rel="canonical" href="<?= current_url() ?>" />
-<meta property="og:title" content="<?= $podcast->title ?>" />
-<meta property="og:description" content="<?= $podcast->description ?>" />
-<meta property="og:locale" content="<?= $podcast->language_code ?>" />
-<meta property="og:site_name" content="<?= $podcast->title ?>" />
-<meta property="og:url" content="<?= current_url() ?>" />
-<meta property="og:image" content="<?= $podcast->image->large_url ?>" />
-<meta property="og:image:width" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:image:height" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta name="twitter:card" content="summary_large_image" />
-
-<?= service('vite')
-    ->asset('styles/index.css', 'css') ?>
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-
-<?= $this->include('podcast/_navigation') ?>
-
-<section class="max-w-2xl px-6 py-8 mx-auto">
-<form action="<?= route_to('post-attempt-create', interact_as_actor()->username) ?>" method="POST" class="flex p-4 bg-white shadow rounded-xl">
-    <?= csrf_field() ?>
-
-    <?= view('_message_block') ?>
-
-    <img src="<?= interact_as_actor()
-        ->avatar_image_url ?>" alt="<?= interact_as_actor()
-        ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-    <div class="flex flex-col flex-1 min-w-0 gap-y-2">
-        <Forms.Textarea
-            name="message"
-            required="true"
-            placeholder="<?= lang('Post.form.message_placeholder') ?>"
-            rows="2" />
-        <Forms.Input
-            name="episode_url"
-            type="url"
-            placeholder="<?= lang('Post.form.episode_url_placeholder') . ' (' . lang('Common.optional') . ')' ?>" />
-        <Button variant="primary" size="small" type="submit" class="self-end"><?= lang('Post.form.submit') ?></Button>
-    </div>
-</form>
-<hr class="my-4 border-2 border-pine-100">
-
-<div class="space-y-8">
-<?php foreach ($posts as $post): ?>
-    <?php if ($post->reblog_of_id !== null): ?>
-        <?= view('podcast/_partials/reblog_authenticated', [
-    'post' => $post->reblog_of_post,
-            'podcast' => $podcast,
-]) ?>
-    <?php else: ?>
-        <?= view('podcast/_partials/post_authenticated', [
-    'post' => $post,
-            'podcast' => $podcast,
-]) ?>
-    <?php endif; ?>
-<?php endforeach; ?>
-</div>
-</section>
-
-<?= $this->endSection() ?>
diff --git a/themes/cp_app/podcast/comment_authenticated.php b/themes/cp_app/podcast/comment_authenticated.php
deleted file mode 100644
index ea145a420b7bb152d1049fdd7a888c5fde154f86..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/comment_authenticated.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?= $this->extend('podcast/_layout_authenticated') ?>
-
-<?= $this->section('meta-tags') ?>
-    <title><?= lang('Comment.title', [
-    'actorDisplayName' => $comment->actor->display_name,
-        'episodeTitle' => $episode->title,
-]) ?></title>
-    <meta name="description" content="<?= $comment->message ?>"/>
-    <meta property="og:title" content="<?= lang('Comment.title', [
-        'actorDisplayName' => $comment->actor->display_name,
-        'episodeTitle' => $episode->title,
-    ]) ?>"/>
-    <meta property="og:locale" content="<?= service(
-        'request',
-    )->getLocale() ?>" />
-    <meta property="og:site_name" content="<?= $comment->actor->display_name ?>" />
-    <meta property="og:url" content="<?= current_url() ?>" />
-    <meta property="og:image" content="<?= $comment->actor->avatar_image_url ?>" />
-    <meta property="og:description" content="<?= $comment->message ?>" />
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-<div class="max-w-2xl px-6 mx-auto">
-    <nav class="py-3">
-        <a href="<?= route_to('episode', $podcast->handle, $episode->slug) ?>"
-        class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
-        'arrow-left',
-        'mr-2 text-lg',
-    ) .
-            lang('Comment.back_to_episode', [
-                'episodeTitle' => $episode->title,
-            ]) ?></a>
-    </nav>
-    <div class="pb-12">
-        <?= $this->include(
-                'podcast/_partials/comment_with_replies_authenticated',
-            ) ?>
-    </div>
-</div>
-
-<?= $this->endSection() ?>
diff --git a/themes/cp_app/podcast/episode.php b/themes/cp_app/podcast/episode.php
deleted file mode 100644
index dcccf80c17725ec7593d55ee3497c288eab019d7..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/episode.php
+++ /dev/null
@@ -1,113 +0,0 @@
-<?= $this->extend('podcast/_layout') ?>
-
-<?= $this->section('meta-tags') ?>
-<title><?= $episode->title ?></title>
-<meta name="description" content="<?= htmlspecialchars(
-    $episode->description,
-) ?>" />
-<link rel="canonical" href="<?= $episode->link ?>" />
-<meta property="og:title" content="<?= $episode->title ?>" />
-<meta property="og:description" content="<?= $episode->description ?>" />
-<meta property="og:locale" content="<?= $podcast->language_code ?>" />
-<meta property="og:site_name" content="<?= $podcast->title ?>" />
-<meta property="og:url" content="<?= current_url() ?>" />
-<meta property="og:image" content="<?= $episode->image->large_url ?>" />
-<meta property="og:image:width" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:image:height" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:description" content="$description" />
-<meta property="article:published_time" content="<?= $episode->published_at ?>" />
-<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
-<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
-<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
-<link rel="alternate" type="application/json+oembed" href="<?= base_url(
-        route_to('episode-oembed-json', $podcast->handle, $episode->slug),
-    ) ?>" title="<?= $episode->title ?> oEmbed json" />
-<link rel="alternate" type="text/xml+oembed" href="<?= base_url(
-        route_to('episode-oembed-xml', $podcast->handle, $episode->slug),
-    ) ?>" title="<?= $episode->title ?> oEmbed xml" />
-<meta name="twitter:title" content="<?= $episode->title ?>" />
-<meta name="twitter:description" content="<?= $episode->description ?>" />
-<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
-<meta name="twitter:card" content="player" />
-<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
-<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
-<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl(
-        'light',
-    ) ?>" />
-<meta name="twitter:player:width" content="600" />
-<meta name="twitter:player:height" content="200" />
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-<div class="max-w-2xl mx-auto">
-    <a href="<?= route_to(
-        'podcast-episodes',
-        $podcast->handle,
-    ) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon(
-        'arrow-left',
-        'mr-2 text-lg',
-    ) . lang('Episode.back_to_episodes', [
-        'podcast' => $podcast->title,
-    ]) ?></a>
-    <header class="flex flex-col px-6 mb-4 rounded-b-xl">
-        <div class="flex flex-wrap items-start">
-            <img src="<?= $episode->image
-    ->medium_url ?>" alt="<?= $episode->title ?>" class="mb-4 mr-6 rounded-xl w-52" />
-            <div class="flex flex-col items-start flex-1 mb-4" style="min-width: 14rem">
-                <h1 class="text-xl font-bold leading-none font-display line-clamp-2"><?= $episode->title ?></h1>
-                <?= episode_numbering(
-        $episode->number,
-        $episode->season_number,
-        'text-gray-700',
-    ) ?>
-                <div class="mb-4 text-xs">
-                    <?= relative_time($episode->published_at) ?>
-                    <span class="mx-1">•</span>
-                    <time datetime="PT<?= $episode->audio_file_duration ?>S">
-                        <?= format_duration($episode->audio_file_duration) ?>
-                    </time>
-                </div>
-                <?= location_link($episode->location, 'text-sm mb-4') ?>
-                <?= person_list($episode->persons) ?>
-                <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?>
-            </div>
-        </div>
-    </header>
-
-    <div class="tabset">
-        <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" />
-        <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label>
-        
-        <input type="radio" name="tabset" id="activity" aria-controls="activity" />
-        <label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label>
-
-        <input type="radio" name="tabset" id="description" aria-controls="description" />
-        <label for="description"><?= lang('Episode.description') ?></label>
-
-        <div class="tab-panels">
-            <section id="comments" class="space-y-6 tab-panel">
-                <?php foreach ($episode->comments as $comment): ?>
-                    <?= view('podcast/_partials/comment', [
-                        'comment' => $comment,
-                    ]) ?>
-                <?php endforeach; ?>
-            </section>
-            <section id="activity" class="space-y-8 tab-panel">
-                <?php foreach ($episode->posts as $post): ?>
-                    <?= view('podcast/_partials/post', [
-                        'post' => $post,
-                        'podcast' => $podcast,
-                    ]) ?>
-                <?php endforeach; ?>
-            </section>
-            <section id="description" class="prose tab-panel">
-                <?= $episode->getDescriptionHtml('-+Website+-') ?>
-            </section>
-        </div>
-    </div>
-</div>
-
-<?= $this->endSection()
-?>
diff --git a/themes/cp_app/podcast/episode_authenticated.php b/themes/cp_app/podcast/episode_authenticated.php
deleted file mode 100644
index 0ffc7ed58dba18c0edaa27d11953c6f92d3a1451..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/episode_authenticated.php
+++ /dev/null
@@ -1,150 +0,0 @@
-<?= $this->extend('podcast/_layout_authenticated') ?>
-
-<?= $this->section('meta-tags') ?>
-<title><?= $episode->title ?></title>
-<meta name="description" content="<?= htmlspecialchars(
-    $episode->description,
-) ?>" />
-<link rel="canonical" href="<?= $episode->link ?>" />
-<meta property="og:title" content="<?= $episode->title ?>" />
-<meta property="og:description" content="<?= $episode->description ?>" />
-<meta property="og:locale" content="<?= $podcast->language_code ?>" />
-<meta property="og:site_name" content="<?= $podcast->title ?>" />
-<meta property="og:url" content="<?= current_url() ?>" />
-<meta property="og:image" content="<?= $episode->image->large_url ?>" />
-<meta property="og:image:width" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:image:height" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:description" content="$description" />
-<meta property="article:published_time" content="<?= $episode->published_at ?>" />
-<meta property="article:modified_time" content="<?= $episode->updated_at ?>" />
-<meta property="og:audio" content="<?= $episode->audio_file_opengraph_url ?>" />
-<meta property="og:audio:type" content="<?= $episode->audio_file_mimetype ?>" />
-<link rel="alternate" type="application/json+oembed" href="<?= base_url(
-        route_to('episode-oembed-json', $podcast->handle, $episode->slug),
-    ) ?>" title="<?= $episode->title ?> oEmbed json" />
-<link rel="alternate" type="text/xml+oembed" href="<?= base_url(
-        route_to('episode-oembed-xml', $podcast->handle, $episode->slug),
-    ) ?>" title="<?= $episode->title ?> oEmbed xml" />
-<meta name="twitter:title" content="<?= $episode->title ?>" />
-<meta name="twitter:description" content="<?= $episode->description ?>" />
-<meta name="twitter:image" content="<?= $episode->image->large_url ?>" />
-<meta name="twitter:card" content="player" />
-<meta property="twitter:audio:partner" content="<?= $podcast->publisher ?>" />
-<meta property="twitter:audio:artist_name" content="<?= $podcast->owner_name ?>" />
-<meta name="twitter:player" content="<?= $episode->getEmbeddablePlayerUrl(
-        'light',
-    ) ?>" />
-<meta name="twitter:player:width" content="600" />
-<meta name="twitter:player:height" content="200" />
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-<div class="max-w-2xl mx-auto">
-    <a href="<?= route_to(
-        'podcast-episodes',
-        $podcast->handle,
-    ) ?>" class="inline-flex items-center px-4 py-2 mb-2 text-sm"><?= icon(
-        'arrow-left',
-        'mr-2 mb- text-lg',
-    ) . lang('Episode.back_to_episodes', [
-        'podcast' => $podcast->title,
-    ]) ?></a>
-    <header class="flex flex-col px-6 mb-4 rounded-b-xl">
-        <div class="flex flex-wrap items-start">
-            <img src="<?= $episode->image
-    ->medium_url ?>" alt="<?= $episode->title ?>" class="mb-4 mr-6 rounded-xl w-52" />
-            <div class="flex flex-col items-start flex-1 mb-4" style="min-width: 14rem">
-                <h1 class="text-xl font-bold leading-none font-display line-clamp-2"><?= $episode->title ?></h1>
-                <?= episode_numbering(
-        $episode->number,
-        $episode->season_number,
-        'text-gray-700',
-    ) ?>
-                <div class="mb-4 text-xs">
-                    <?= relative_time($episode->published_at) ?>
-                    <span class="mx-1">•</span>
-                    <time datetime="PT<?= $episode->audio_file_duration ?>S">
-                        <?= format_duration($episode->audio_file_duration) ?>
-                    </time>
-                </div>
-                <?= location_link($episode->location, 'text-sm mb-4') ?>
-                <?= person_list($episode->persons) ?>
-                <?= play_episode_button($episode->id, $episode->image->thumbnail_url, $episode->title, $podcast->title, $episode->audio_file_web_url, $episode->audio_file_mimetype) ?>
-            </div>
-        </div>
-    </header>
-
-    <div class="tabset">
-        <input type="radio" name="tabset" id="comments" aria-controls="comments" checked="checked" />
-        <label for="comments"><?= lang('Episode.comments') . ' (' . $episode->comments_count . ')' ?></label>
-        
-        <input type="radio" name="tabset" id="activity" aria-controls="activity" />
-        <label for="activity"><?= lang('Episode.activity') . ' (' . $episode->posts_count . ')' ?></label>
-
-        <input type="radio" name="tabset" id="description" aria-controls="description" />
-        <label for="description"><?= lang('Episode.description') ?></label>
-
-        <div class="tab-panels">
-            <section id="comments" class="space-y-6 tab-panel">
-            <form action="<?= route_to('comment-attempt-create', $podcast->id, $episode->id)  ?>" method="POST" class="flex p-4">
-                <?= csrf_field() ?>
-
-                <?= view('_message_block') ?>
-
-                <img src="<?= interact_as_actor()
-                    ->avatar_image_url ?>" alt="<?= interact_as_actor()
-                    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-                <div class="flex flex-col flex-1 min-w-0 gap-y-2">
-                    <Forms.Textarea
-                        name="message"
-                        required="true"
-                        placeholder="<?= lang('Comment.form.episode_message_placeholder') ?>"
-                        rows="2" />
-                    <Button class="self-end" variant="primary" size="small" type="submit"><?= lang('Comment.form.submit') ?></Button>
-                </div>
-            </form>
-            <?php foreach ($episode->comments as $comment): ?>
-                <?= view('podcast/_partials/comment_authenticated', [
-                    'comment' => $comment,
-                    'podcast' => $podcast,
-                ]) ?>
-            <?php endforeach; ?>
-            </section>
-            <section id="activity" class="space-y-8 tab-panel">
-                <form action="<?= route_to('post-attempt-create', $podcast->handle) ?>" method="POST" class="flex p-4 bg-white shadow rounded-xl">
-                <?= csrf_field() ?>
-
-                <?= view('_message_block') ?>
-
-                <img src="<?= interact_as_actor()
-                    ->avatar_image_url ?>" alt="<?= interact_as_actor()
-                    ->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
-                <div class="flex flex-col flex-1 min-w-0 gap-y-2">
-                    <input name="episode_url" value="<?= $episode->link ?>" type="hidden" />
-                    <Forms.Textarea
-                        name="message"
-                        placeholder="<?= lang('Post.form.episode_message_placeholder') ?>"
-                        required="true"
-                        rows="2" />
-                    <Button variant="primary" size="small" type="submit" class="self-end"><?= lang('Post.form.submit') ?></Button>
-                </div>
-                </form>
-                <hr class="my-4 border border-pine-100">
-                <?php foreach ($episode->posts as $post): ?>
-                    <?= view('podcast/_partials/post_authenticated', [
-                        'post' => $post,
-                        'podcast' => $podcast,
-                    ]) ?>
-                <?php endforeach; ?>
-            </section>
-            <section id="description" class="prose tab-panel">
-                <?= $episode->getDescriptionHtml('-+Website+-') ?>
-            </section>
-        </div>
-    </div>
-</div>
-
-<?= $this->endSection()
-?>
diff --git a/themes/cp_app/podcast/episodes.php b/themes/cp_app/podcast/episodes.php
index ed4a9a523e897edebde1a50f1d3be2360ab76a6a..d4c26f623b20ce8764e487396da52af606981bcc 100644
--- a/themes/cp_app/podcast/episodes.php
+++ b/themes/cp_app/podcast/episodes.php
@@ -27,10 +27,10 @@
 
 <?= $this->section('content') ?>
 
-<section class="flex flex-col max-w-2xl">
+<section class="flex flex-col max-w-2xl gap-y-4">
     <?php if ($episodes): ?>
-        <div class="flex items-center justify-between mb-4">
-            <h1 class="text-xl font-semibold">
+        <div class="flex items-center justify-between">
+            <h1 class="font-semibold">
                 <?php if ($activeQuery['type'] === 'year'): ?>
                     <?= lang('Podcast.list_of_episodes_year', [
     'year' => $activeQuery['value'],
@@ -44,34 +44,28 @@
                 <?php endif; ?>
             </h1>
             <?php if ($activeQuery): ?>
-                <button id="episode-lists-dropdown" type="button" class="inline-flex items-center px-2 py-1 text-sm font-semibold outline-none focus:ring" data-dropdown="button" data-dropdown-target="episode-lists-dropdown-menu" aria-label="<?= lang(
-    'Common.more',
-) ?>" aria-haspopup="true" aria-expanded="false">
-                    <?= $activeQuery['label'] .
-                        ' (' .
-                        $activeQuery['number_of_episodes'] .
-                        ')' .
-                        icon('caret-down', 'ml-2 text-xl') ?>
+                <button id="episode-lists-dropdown" type="button" class="inline-flex items-center px-2 py-1 text-sm font-semibold outline-none focus:ring" data-dropdown="button" data-dropdown-target="episode-lists-dropdown-menu" aria-label="<?= lang('Common.more') ?>" aria-haspopup="true" aria-expanded="false">
+                    <?= $activeQuery['label'] . icon('caret-down', 'ml-2 text-xl') ?>
                 </button>
                 <nav id="episode-lists-dropdown-menu" class="flex flex-col py-2 text-black bg-white border rounded shadow" aria-labelledby="episode-lists-dropdown" data-dropdown="menu" data-dropdown-placement="bottom-end">
                     <?php foreach ($episodesNav as $link): ?>
                         <?= anchor(
-                            $link['route'],
-                            $link['label'] . ' (' . $link['number_of_episodes'] . ')',
-                            [
-                                'class' =>
-                                    'px-2 py-1 whitespace-nowrap ' .
-                                    ($link['is_active']
-                                        ? 'font-semibold'
-                                        : 'text-gray-600 hover:text-gray-900'),
-                            ],
-                        ) ?>
+    $link['route'],
+    $link['label'] . ' (' . $link['number_of_episodes'] . ')',
+    [
+        'class' =>
+            'px-2 py-1 whitespace-nowrap ' .
+            ($link['is_active']
+                ? 'font-semibold'
+                : 'text-gray-600 hover:text-gray-900'),
+    ],
+) ?>
                     <?php endforeach; ?>
                 </nav>
             <?php endif; ?>
         </div>
         <?php foreach ($episodes as $episode): ?>
-            <?= view('podcast/_partials/episode_card', [
+            <?= view('episode/_partials/card', [
                 'episode' => $episode,
                 'podcast' => $podcast,
             ]) ?>
diff --git a/themes/cp_app/podcast/episodes_authenticated.php b/themes/cp_app/podcast/episodes_authenticated.php
deleted file mode 100644
index 0986f2eb0d372df29fe47e4d776d1a679361fc91..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/episodes_authenticated.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?= $this->extend('podcast/_layout_authenticated') ?>
-
-<?= $this->section('meta-tags') ?>
-<link type="application/rss+xml" rel="alternate" title="<?= $podcast->title ?>" href="<?= $podcast->feed_url ?>" />
-
-<title><?= $podcast->title ?></title>
-<meta name="description" content="<?= htmlspecialchars(
-    $podcast->description,
-) ?>" />
-<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
-<link rel="canonical" href="<?= current_url() ?>" />
-<meta property="og:title" content="<?= $podcast->title ?>" />
-<meta property="og:description" content="<?= $podcast->description ?>" />
-<meta property="og:locale" content="<?= $podcast->language_code ?>" />
-<meta property="og:site_name" content="<?= $podcast->title ?>" />
-<meta property="og:url" content="<?= current_url() ?>" />
-<meta property="og:image" content="<?= $podcast->image->large_url ?>" />
-<meta property="og:image:width" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta property="og:image:height" content="<?= config('Images')
-    ->largeSize ?>" />
-<meta name="twitter:card" content="summary_large_image" />
-
-<?= service('vite')
-    ->asset('styles/index.css', 'css') ?>
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-
-<?= $this->include('podcast/_navigation') ?>
-
-<section class="flex flex-col max-w-2xl px-6 py-8 mx-auto">
-
-    <?php if ($episodes) : ?>
-        <h1 class="mb-4 text-xl font-semibold">
-            <?php if ($activeQuery['type'] === 'year') : ?>
-                <?= lang('Podcast.list_of_episodes_year', [
-    'year' => $activeQuery['value'],
-                    'episodeCount' => count($episodes),
-]) ?>
-            <?php elseif ($activeQuery['type'] === 'season') : ?>
-                <?= lang('Podcast.list_of_episodes_season', [
-    'seasonNumber' => $activeQuery['value'],
-                    'episodeCount' => count($episodes),
-]) ?>
-            <?php endif; ?>
-        </h1>
-        <?php foreach ($episodes as $episode) : ?>
-            <?= view('podcast/_partials/episode_card', [
-    'episode' => $episode,
-                'podcast' => $podcast,
-]) ?>
-        <?php endforeach; ?>
-    <?php else : ?>
-        <h1 class="px-4 mb-2 text-xl text-center"><?= lang(
-    'Podcast.no_episode',
-) ?></h1>
-        <p class="italic text-center"><?= lang('Podcast.no_episode_hint') ?></p>
-    <?php endif; ?>
-</section>
-
-<?= $this->endSection()
-?>
diff --git a/themes/cp_app/podcast/follow.php b/themes/cp_app/podcast/follow.php
index 32682ae9b756bfa2c2d4eac358135674d488a0e0..e98440f71aeca44487e43e84ed80bf2ec0802f2f 100644
--- a/themes/cp_app/podcast/follow.php
+++ b/themes/cp_app/podcast/follow.php
@@ -49,16 +49,16 @@
 
     <main class="w-full max-w-md px-4 mx-auto">
         <form action="<?= route_to('attempt-follow', $actor->username) ?>" method="POST" class="flex flex-col">
-        <?= csrf_field() ?>
-        <?= view('_message_block') ?>
+            <?= csrf_field() ?>
+            <?= view('_message_block') ?>
 
-        <Forms.Field
-            name="handle"
-            label="<?= lang('Fediverse.your_handle') ?>"
-            hint="<?= lang('Fediverse.your_handle_hint') ?>"
-            required="true"
-        />
-        <Button variant="primary" type="submit" class="self-end"><?= lang('Fediverse.follow.submit') ?></Button>
+            <Forms.Field
+                name="handle"
+                label="<?= lang('Fediverse.your_handle') ?>"
+                hint="<?= lang('Fediverse.your_handle_hint') ?>"
+                required="true"
+            />
+            <Button variant="primary" type="submit" class="self-end"><?= lang('Fediverse.follow.submit') ?></Button>
         </form>
     </main>
 
@@ -68,7 +68,7 @@
         <p>
             <?= lang('Common.powered_by', [
                 'castopod' =>
-                    '<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                    '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
             ]) ?>
         </p>
     </footer>
diff --git a/themes/cp_app/podcast/post_authenticated.php b/themes/cp_app/podcast/post_authenticated.php
deleted file mode 100644
index f04699351cdfbd492cb481d211aeaf85d0d28e14..0000000000000000000000000000000000000000
--- a/themes/cp_app/podcast/post_authenticated.php
+++ /dev/null
@@ -1,40 +0,0 @@
-<?= $this->extend('podcast/_layout_authenticated') ?>
-
-<?= $this->section('meta-tags') ?>
-    <title><?= lang('Post.title', [
-        'actorDisplayName' => $post->actor->display_name,
-    ]) ?></title>
-    <meta name="description" content="<?= $post->message ?>"/>
-    <meta property="og:title" content="<?= lang('Post.title', [
-        'actorDisplayName' => $post->actor->display_name,
-    ]) ?>"/>
-    <meta property="og:locale" content="<?= service(
-        'request',
-    )->getLocale() ?>" />
-    <meta property="og:site_name" content="<?= $post->actor->display_name ?>" />
-    <meta property="og:url" content="<?= current_url() ?>" />
-    <meta property="og:image" content="<?= $post->actor->avatar_image_url ?>" />
-    <meta property="og:description" content="<?= $post->message ?>" />
-<?= $this->endSection() ?>
-
-<?= $this->section('content') ?>
-<div class="max-w-2xl px-6 mx-auto">
-    <nav class="py-3">
-        <a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
-        class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
-        'arrow-left',
-        'mr-2 text-lg',
-    ) .
-            lang('Post.back_to_actor_posts', [
-                'actor' => $post->actor->display_name,
-            ]) ?></a>
-    </nav>
-    <div class="pb-12">
-        <?= $this->include(
-                'podcast/_partials/post_with_replies_authenticated',
-            ) ?>
-    </div>
-</div>
-
-<?= $this->endSection()
-?>
diff --git a/themes/cp_app/post/_partials/actions.php b/themes/cp_app/post/_partials/actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f92da4bcf3436f46a466b28abd6e8d8a9ee195d
--- /dev/null
+++ b/themes/cp_app/post/_partials/actions.php
@@ -0,0 +1,124 @@
+<footer class="flex justify-around px-6 py-3">
+    <?php if (can_user_interact()): ?>
+        <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex justify-around w-full">
+            <?= csrf_field() ?>
+            <?= anchor(
+    route_to('post', $podcast->handle, $post->id),
+    icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
+    [
+        'class' => 'inline-flex items-center hover:underline',
+        'title' => lang('Post.replies', [
+            'numberOfReplies' => $post->replies_count,
+        ]),
+    ],
+) ?>
+            <button type="submit" name="action" value="reblog" class="inline-flex items-center hover:underline" title="<?= lang(
+    'Post.reblogs',
+    [
+        'numberOfReblogs' => $post->reblogs_count,
+    ],
+) ?>"><?= icon('repeat', 'text-2xl mr-1 text-gray-400') .
+        $post->reblogs_count ?></button>
+            <button type="submit" name="action" value="favourite" class="inline-flex items-center hover:underline" title="<?= lang(
+            'Post.favourites',
+            [
+                'numberOfFavourites' => $post->favourites_count,
+            ],
+        ) ?>"><?= icon('heart', 'text-2xl mr-1 text-gray-400') .
+        $post->favourites_count ?></button>
+            <button id="<?= $post->id .
+                '-more-dropdown' ?>" type="button" class="px-2 py-1 text-2xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $post->id .
+        '-more-dropdown-menu' ?>" aria-label="<?= lang(
+            'Common.more',
+        ) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
+            </button>
+        </form>
+        <nav id="<?= $post->id .
+            '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $post->id .
+        '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
+            <?= anchor(
+            route_to('post', $podcast->handle, $post->id),
+            lang('Post.expand'),
+            [
+                'class' => 'px-4 py-1 hover:bg-gray-100',
+            ],
+        ) ?>
+            <form action="<?= route_to(
+            'post-attempt-block-actor',
+            interact_as_actor()
+                ->username,
+            $post->id,
+        ) ?>" method="POST">
+                <?= csrf_field() ?>
+                <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
+            'Post.block_actor',
+            [
+                'actorUsername' => $post->actor->username,
+            ],
+        ) ?></button>
+            </form>
+            <form action="<?= route_to(
+            'post-attempt-block-domain',
+            interact_as_actor()
+                ->username,
+            $post->id,
+        ) ?>" method="POST">
+                <?= csrf_field() ?>
+                <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
+            'Post.block_domain',
+            [
+                'actorDomain' => $post->actor->domain,
+            ],
+        ) ?></button>
+            </form>
+            <?php if ($post->actor->is_local): ?>
+                <hr class="my-2" />
+                <form action="<?= route_to(
+            'post-attempt-delete',
+            $post->actor->username,
+            $post->id,
+        ) ?>" method="POST">
+                    <?= csrf_field() ?>
+                    <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
+            'Post.delete',
+        ) ?></button>
+                </form>
+            <?php endif; ?>
+        </nav>
+    <?php else: ?>
+    <?= anchor(
+            route_to('post', $podcast->handle, $post->id),
+            icon('chat', 'text-2xl mr-1 text-gray-400') . $post->replies_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'title' => lang('Post.replies', [
+                    'numberOfReplies' => $post->replies_count,
+                ]),
+            ],
+        ) ?>
+    <?= anchor_popup(
+            route_to('post-remote-action', $podcast->handle, $post->id, 'reblog'),
+            icon('repeat', 'text-2xl mr-1 text-gray-400') . $post->reblogs_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'width' => 420,
+                'height' => 620,
+                'title' => lang('Post.reblogs', [
+                    'numberOfReblogs' => $post->reblogs_count,
+                ]),
+            ],
+        ) ?>
+    <?= anchor_popup(
+            route_to('post-remote-action', $podcast->handle, $post->id, 'favourite'),
+            icon('heart', 'text-2xl mr-1 text-gray-400') . $post->favourites_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'width' => 420,
+                'height' => 620,
+                'title' => lang('Post.favourites', [
+                    'numberOfFavourites' => $post->favourites_count,
+                ]),
+            ],
+        ) ?>
+    <?php endif; ?>
+</footer>
diff --git a/themes/cp_app/podcast/_partials/post_authenticated.php b/themes/cp_app/post/_partials/card.php
similarity index 63%
rename from themes/cp_app/podcast/_partials/post_authenticated.php
rename to themes/cp_app/post/_partials/card.php
index 9a60f6b36fb547693d7db7054c71cc32f8196721..74e4f4de5a61a109582af52539d466115b6e4ff0 100644
--- a/themes/cp_app/podcast/_partials/post_authenticated.php
+++ b/themes/cp_app/post/_partials/card.php
@@ -1,7 +1,7 @@
-<article class="relative z-10 w-full bg-white shadow rounded-2xl">
+<article class="relative z-10 w-full bg-white shadow sm:rounded-2xl">
     <header class="flex px-6 py-4">
         <img src="<?= $post->actor
-    ->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-12 h-12 mr-4 rounded-full" />
+    ->avatar_image_url ?>" alt="<?= $post->actor->display_name ?>" class="w-10 h-10 mr-2 rounded-full" />
         <div class="flex flex-col min-w-0">
             <a href="<?= $post->actor
     ->uri ?>" class="flex items-baseline hover:underline" <?= $post
@@ -22,15 +22,20 @@
             </a>
         </div>
     </header>
-    <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
+    <?php if (substr_count($post->message, "\n") >= 3 || strlen($post->message) > 250): ?>
+        <ReadMore id="<?= $index ?>" class="px-6 mb-4 post-content"><?= $post->message_html ?></ReadMore>
+    <?php else: ?>
+        <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
+    <?php endif; ?>
     <?php if ($post->episode_id): ?>
-        <?= view('podcast/_partials/episode_preview_card', [
+        <?= view('episode/_partials/preview_card', [
+    'index' => $index,
             'episode' => $post->episode,
-        ]) ?>
-    <?php elseif ($post->has_preview_card): ?>
-        <?= view('podcast/_partials/preview_card', [
+]) ?>
+    <?php elseif ($post->preview_card_id): ?>
+        <?= view('post/_partials/preview_card', [
             'preview_card' => $post->preview_card,
         ]) ?>
     <?php endif; ?>
-    <?= $this->include('podcast/_partials/post_actions_authenticated') ?>
+    <?= $this->include('post/_partials/actions') ?>
 </article>
diff --git a/themes/cp_app/post/_partials/post_with_replies.php b/themes/cp_app/post/_partials/post_with_replies.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc3b66a22fd9f3c848e08d8c6386efa294799342
--- /dev/null
+++ b/themes/cp_app/post/_partials/post_with_replies.php
@@ -0,0 +1,49 @@
+<?= view('post/_partials/card', [
+    'index' => $index,
+    'post' => $post,
+    'podcast' => $podcast,
+]) ?>
+<div class="-mt-2 overflow-hidden border-b border-l border-r post-replies rounded-b-xl">
+    <div class="px-6 pt-8 pb-4 bg-gray-50">
+        <?php if (can_user_interact()): ?>
+            <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $post->id) ?>" method="POST" class="flex" >
+                <img src="<?= interact_as_actor()
+            ->avatar_image_url ?>" alt="<?= interact_as_actor()
+            ->display_name ?>" class="w-12 h-12 mr-4 rounded-full ring-gray-50 ring-2" />
+                <div class="flex flex-col flex-1">
+                    <Forms.Textarea
+                        name="message"
+                        class="w-full mb-4"
+                        required="true"
+                        placeholder="<?= lang('Post.form.reply_to_placeholder', [
+                            'actorUsername' => $post->actor->username,
+                        ]) ?>"
+                        rows="1" />
+                    <Button variant="primary" size="small" type="submit" name="action" value="reply" class="self-end" iconRight="send-plane"><?= lang('Post.form.submit_reply') ?></Button>
+                </div>
+            </form>
+        <?php else: ?>
+            <?= anchor_popup(
+                            route_to('post-remote-action', $podcast->handle, $post->id, 'reply'),
+                            lang('Post.reply_to', [
+                                'actorUsername' => $post->actor->username,
+                            ]),
+                            [
+                                'class' =>
+                                    'text-center justify-center font-semibold rounded-full shadow relative z-10 px-4 py-2 w-full bg-rose-600 text-white inline-flex items-center hover:bg-rose-700',
+                                'width' => 420,
+                                'height' => 620,
+                            ],
+                        ) ?>
+        <?php endif; ?>
+    </div>
+
+    <?php if ($post->has_replies): ?>
+        <?php foreach ($post->replies as $reply): ?>
+            <?= view('post/_partials/reply', [
+                'podcast' => $podcast,
+                'reply' => $reply,
+            ]) ?>
+        <?php endforeach; ?>
+    <?php endif; ?>
+</div>
\ No newline at end of file
diff --git a/themes/cp_app/podcast/_partials/preview_card.php b/themes/cp_app/post/_partials/preview_card.php
similarity index 100%
rename from themes/cp_app/podcast/_partials/preview_card.php
rename to themes/cp_app/post/_partials/preview_card.php
diff --git a/themes/cp_app/podcast/_partials/reblog.php b/themes/cp_app/post/_partials/reblog.php
similarity index 83%
rename from themes/cp_app/podcast/_partials/reblog.php
rename to themes/cp_app/post/_partials/reblog.php
index d5ef3ba136f1218a5a9cbd1dca687452720f747e..71914ded4c14df9eb4cccfac651507f147f2fb6a 100644
--- a/themes/cp_app/podcast/_partials/reblog.php
+++ b/themes/cp_app/post/_partials/reblog.php
@@ -1,4 +1,4 @@
-<article class="relative z-10 w-full bg-white shadow rounded-2xl">
+<article class="relative z-10 w-full bg-white shadow sm:rounded-2xl">
     <p class="inline-flex px-6 pt-4 text-xs text-gray-700"><?= icon(
     'repeat',
     'text-lg mr-2 text-gray-400',
@@ -31,13 +31,13 @@
     </header>
     <div class="px-6 mb-4 post-content"><?= $post->message_html ?></div>
     <?php if ($post->episode_id): ?>
-        <?= view('podcast/_partials/episode_preview_card', [
+        <?= view('episode/_partials/preview_card', [
             'episode' => $post->episode,
         ]) ?>
-    <?php elseif ($post->has_preview_card): ?>
-        <?= view('podcast/_partials/preview_card', [
+    <?php elseif ($post->preview_card_id): ?>
+        <?= view('post/_partials/preview_card', [
             'preview_card' => $post->preview_card,
         ]) ?>
     <?php endif; ?>
-    <?= $this->include('podcast/_partials/post_actions') ?>
+    <?= $this->include('post/_partials/actions') ?>
 </article>
diff --git a/themes/cp_app/podcast/_partials/reply.php b/themes/cp_app/post/_partials/reply.php
similarity index 77%
rename from themes/cp_app/podcast/_partials/reply.php
rename to themes/cp_app/post/_partials/reply.php
index 912276ad7e8b72bb7b5316b6af2a3e044dcce1a3..e39bc418a8d1b8947859ddc643989aae83d7af49 100644
--- a/themes/cp_app/podcast/_partials/reply.php
+++ b/themes/cp_app/post/_partials/reply.php
@@ -11,14 +11,14 @@
         ->display_name ?><span class="ml-1 text-sm font-normal text-gray-600">@<?= $reply
         ->actor->username .
     ($reply->actor->is_local ? '' : '@' . $reply->actor->domain) ?></span></a>
-            <?= relative_time($post->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
+            <?= relative_time($reply->published_at, 'flex-shrink-0 ml-auto text-xs text-gray-600') ?>
         </header>
         <p class="mb-2 post-content"><?= $reply->message_html ?></p>
-        <?php if ($reply->has_preview_card): ?>
-            <?= view('podcast/_partials/preview_card', [
+        <?php if ($reply->preview_card_id): ?>
+            <?= view('post/_partials/preview_card', [
                 'preview_card' => $reply->preview_card,
             ]) ?>
         <?php endif; ?>
-        <?= $this->include('podcast/_partials/reply_actions') ?>
+        <?= $this->include('post/_partials/reply_actions') ?>
     </div>
 </article>
diff --git a/themes/cp_app/post/_partials/reply_actions.php b/themes/cp_app/post/_partials/reply_actions.php
new file mode 100644
index 0000000000000000000000000000000000000000..322f1ed3be8741977935c779270dbe88f76066c0
--- /dev/null
+++ b/themes/cp_app/post/_partials/reply_actions.php
@@ -0,0 +1,124 @@
+<footer>
+    <?php if (can_user_interact()): ?>
+        <form action="<?= route_to('post-attempt-action', interact_as_actor()->username, $reply->id) ?>" method="POST" class="flex items-start">
+            <?= csrf_field() ?>
+            <?= anchor(
+    route_to('post', $podcast->handle, $reply->id),
+    icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
+    [
+        'class' => 'inline-flex items-center mr-6 hover:underline',
+        'title' => lang('Post.replies', [
+            'numberOfReplies' => $reply->replies_count,
+        ]),
+    ],
+) ?>
+            <button type="submit" name="action" value="reblog" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
+    'Post.reblogs',
+    [
+        'numberOfReblogs' => $reply->reblogs_count,
+    ],
+) ?>"><?= icon('repeat', 'text-xl mr-1 text-gray-400') .
+            $reply->reblogs_count ?></button>
+                <button type="submit" name="action" value="favourite" class="inline-flex items-center mr-6 hover:underline" title="<?= lang(
+                'Post.favourites',
+                [
+                    'numberOfFavourites' => $reply->favourites_count,
+                ],
+            ) ?>"><?= icon('heart', 'text-xl mr-1 text-gray-400') .
+            $reply->favourites_count ?></button>
+                <button id="<?= $reply->id .
+                    '-more-dropdown' ?>" type="button" class="text-xl text-gray-500 outline-none focus:ring" data-dropdown="button" data-dropdown-target="<?= $reply->id .
+            '-more-dropdown-menu' ?>" aria-label="<?= lang(
+                'Common.more',
+            ) ?>" aria-haspopup="true" aria-expanded="false"><?= icon('more') ?>
+                </button>
+        </form>
+        <nav id="<?= $reply->id .
+            '-more-dropdown-menu' ?>" class="flex flex-col py-2 text-sm bg-white border rounded-lg shadow" aria-labelledby="<?= $reply->id .
+        '-more-dropdown' ?>" data-dropdown="menu" data-dropdown-placement="bottom">
+            <?= anchor(
+            route_to('post', $podcast->handle, $reply->id),
+            lang('Post.expand'),
+            [
+                'class' => 'px-4 py-1 hover:bg-gray-100',
+            ],
+        ) ?>
+            <form action="<?= route_to(
+            'post-attempt-block-actor',
+            interact_as_actor()
+                ->username,
+            $reply->id,
+        ) ?>" method="POST">
+                <?= csrf_field() ?>
+                <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
+            'Post.block_actor',
+            [
+                'actorUsername' => $reply->actor->username,
+            ],
+        ) ?></button>
+            </form>
+            <form action="<?= route_to(
+            'post-attempt-block-domain',
+            interact_as_actor()
+                ->username,
+            $reply->id,
+        ) ?>" method="POST">
+                <?= csrf_field() ?>
+                <button class="w-full px-4 py-1 text-left hover:bg-gray-100"><?= lang(
+            'Post.block_domain',
+            [
+                'actorDomain' => $reply->actor->domain,
+            ],
+        ) ?></button>
+            </form>
+            <?php if ($reply->actor->is_local): ?>
+                <hr class="my-2" />
+                <form action="<?= route_to(
+            'post-attempt-delete',
+            $reply->actor->username,
+            $reply->id,
+        ) ?>" method="POST">
+                    <?= csrf_field() ?>
+                    <button class="w-full px-4 py-1 font-semibold text-left text-red-600 hover:bg-gray-100"><?= lang(
+            'Post.delete',
+        ) ?></button>
+                </form>
+            <?php endif; ?>
+        </nav>
+    <?php else: ?>
+        <?= anchor(
+            route_to('post', $podcast->handle, $reply->id),
+            icon('chat', 'text-xl mr-1 text-gray-400') . $reply->replies_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'title' => lang('Post.replies', [
+                    'numberOfReplies' => $reply->replies_count,
+                ]),
+            ],
+        ) ?>
+        <?= anchor_popup(
+            route_to('post-remote-action', $podcast->handle, $reply->id, 'reblog'),
+            icon('repeat', 'text-xl mr-1 text-gray-400') . $reply->reblogs_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'width' => 420,
+                'height' => 620,
+                'title' => lang('Post.reblogs', [
+                    'numberOfReblogs' => $reply->reblogs_count,
+                ]),
+            ],
+        ) ?>
+        <?= anchor_popup(
+            route_to('post-remote-action', $podcast->handle, $reply->id, 'favourite'),
+            icon('heart', 'text-xl mr-1 text-gray-400') . $reply->favourites_count,
+            [
+                'class' => 'inline-flex items-center hover:underline',
+                'width' => 420,
+                'height' => 620,
+                'title' => lang('Post.favourites', [
+                    'numberOfFavourites' => $reply->favourites_count,
+                ]),
+            ],
+        ) ?>
+    <?php endif; ?>
+</footer>
diff --git a/themes/cp_app/podcast/post.php b/themes/cp_app/post/post.php
similarity index 65%
rename from themes/cp_app/podcast/post.php
rename to themes/cp_app/post/post.php
index 3990e38d8c57a93612df602af24ee2cef642e4a1..3cc1299f10393a237e7b7c91ba038a9748f8a5ff 100644
--- a/themes/cp_app/podcast/post.php
+++ b/themes/cp_app/post/post.php
@@ -18,21 +18,21 @@
 <?= $this->endSection() ?>
 
 <?= $this->section('content') ?>
-<div class="max-w-2xl px-6 mx-auto">
-    <nav class="py-3">
-        <a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
-        class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
+<nav class="py-2">
+    <a href="<?= route_to('podcast-activity', $podcast->handle) ?>"
+    class="inline-flex items-center px-4 py-2 text-sm"><?= icon(
         'arrow-left',
         'mr-2 text-lg',
     ) .
-            lang('Post.back_to_actor_posts', [
-                'actor' => $post->actor->display_name,
-            ]) ?></a>
-    </nav>
-    <div class="pb-12">
-        <?= $this->include('podcast/_partials/post_with_replies') ?>
-    </div>
+        lang('Post.back_to_actor_posts', [
+            'actor' => $post->actor->display_name,
+        ]) ?></a>
+</nav>
+<div class="pb-12">
+    <?= view('post/_partials/post_with_replies', [
+        'index' => 1,
+        'post' => $post,
+        'podcast' => $podcast,
+    ]) ?>
 </div>
-
-<?= $this->endSection()
-?>
+<?= $this->endSection() ?>
diff --git a/themes/cp_app/podcast/post_remote_action.php b/themes/cp_app/post/remote_action.php
similarity index 97%
rename from themes/cp_app/podcast/post_remote_action.php
rename to themes/cp_app/post/remote_action.php
index 6e2205abe65d10ece9ee6f041f8bc00809ff9fe0..36c7ffd3614d28dec1fcb26ecb3323d5bfca0fd1 100644
--- a/themes/cp_app/podcast/post_remote_action.php
+++ b/themes/cp_app/post/remote_action.php
@@ -38,7 +38,7 @@
         ) ?></h1>
     </header>
     <main class="flex-1 max-w-xl px-4 pb-8 mx-auto -mt-24">
-        <?= $this->include('podcast/_partials/post') ?>
+        <?= $this->include('post/_partials/card') ?>
 
         <form action="<?= route_to('post-attempt-remote-action', $post->id, $action) ?>" method="POST" class="flex flex-col mt-8">
             <?= csrf_field() ?>
diff --git a/themes/cp_auth/_layout.php b/themes/cp_auth/_layout.php
index 30fca7a6e16a023aa6694c814b1ea5eeff4ea26d..6df0641baae40cd6819beea9653b6ecae932ef98 100644
--- a/themes/cp_auth/_layout.php
+++ b/themes/cp_auth/_layout.php
@@ -31,7 +31,7 @@
 		<?= $this->renderSection('footer') ?>
 		<small class="py-4 text-center border-t-2 border-pine-100"><?= lang('Common.powered_by', [
 		    'castopod' =>
-		        '<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
+		        '<a class="inline-flex font-semibold hover:underline" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
 		]) ?></small>
 	</footer>
 </body>
diff --git a/themes/cp_install/_layout.php b/themes/cp_install/_layout.php
index eccc912fbf58d28d6eca94ea6a46ec6248580413..dd384d75e9462a33b86168be53cdfa7b8e2755e9 100644
--- a/themes/cp_install/_layout.php
+++ b/themes/cp_install/_layout.php
@@ -26,7 +26,7 @@
     <footer class="container px-2 py-4 mx-auto text-sm text-right border-t">
         <small><?= lang('Common.powered_by', [
             'castopod' =>
-                '<a class="underline hover:no-underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod</a>',
+                '<a class="inline-flex font-semibold hover:underline" href="https://castopod.org" target="_blank" rel="noreferrer noopener">Castopod' . icon('social/castopod', 'ml-1 text-lg') . '</a>',
         ]) ?></small>
     </footer>
 </body>
diff --git a/vite.config.ts b/vite.config.ts
index f8b3ed543f8cf044178fc82c500f573f088ba220..617f6fa34469bc7cdd69f247a81cb2f8451fcaef 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -14,6 +14,7 @@ export default defineConfig({
       input: {
         "podcast.ts": "app/Resources/js/podcast.ts",
         "install.ts": "app/Resources/js/install.ts",
+        "app.ts": "app/Resources/js/app.ts",
         "admin.ts": "app/Resources/js/admin.ts",
         "charts.ts": "app/Resources/js/charts.ts",
         "map.ts": "app/Resources/js/map.ts",