diff --git a/src/components/board-pagination/board-pagination.tsx b/src/components/board-pagination/board-pagination.tsx
index 6f26764b..1cad3230 100644
--- a/src/components/board-pagination/board-pagination.tsx
+++ b/src/components/board-pagination/board-pagination.tsx
@@ -1,7 +1,7 @@
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import StyleSelector from '../style-selector/style-selector';
-import footerStyles from '../footer-first-row/footer-first-row.module.css';
+import footerStyles from '../footer/footer.module.css';
import styles from './board-pagination.module.css';
export interface BoardPaginationProps {
diff --git a/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx b/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx
deleted file mode 100644
index 9df6f278..00000000
--- a/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import StyleSelector from '../style-selector/style-selector';
-import styles from '../footer-first-row/footer-first-row.module.css';
-
-/** Catalog footer first row: Style selector on right only (no pagination; catalog shows all pages at once). */
-const CatalogFooterFirstRow = () => {
- const { t } = useTranslation();
-
- return (
-
-
-
- {t('style')}:
-
-
-
- );
-};
-
-export default CatalogFooterFirstRow;
diff --git a/src/components/catalog-footer-first-row/index.ts b/src/components/catalog-footer-first-row/index.ts
deleted file mode 100644
index 46fb1a18..00000000
--- a/src/components/catalog-footer-first-row/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './catalog-footer-first-row';
diff --git a/src/components/footer-first-row/footer-first-row.module.css b/src/components/footer-first-row/footer-first-row.module.css
deleted file mode 100644
index 7724873e..00000000
--- a/src/components/footer-first-row/footer-first-row.module.css
+++ /dev/null
@@ -1,27 +0,0 @@
-/* Shared footer first-row layout styles (catalog footer, board pagination footer mode) */
-.footerRow {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 8px;
- width: 100%;
-}
-
-.footerLeft {
- display: flex;
- align-items: center;
- gap: 4px;
- flex-wrap: wrap;
-}
-
-.footerRight {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-
-.styleLabel {
- font-size: 12px;
- text-transform: capitalize;
-}
diff --git a/src/components/footer/footer.module.css b/src/components/footer/footer.module.css
new file mode 100644
index 00000000..d301e91f
--- /dev/null
+++ b/src/components/footer/footer.module.css
@@ -0,0 +1,94 @@
+/* Page footer (desktop) */
+.footer {
+ margin-top: 16px;
+ padding-bottom: 24px;
+}
+
+.firstRow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 8px 0;
+ text-align: center;
+}
+
+.boardsBarRow {
+ padding: 8px 0;
+}
+
+.legalMeta {
+ padding: 12px 0 0;
+ font-size: 11px;
+ color: var(--body-font-color);
+ text-align: center;
+}
+
+/* Shared footer first-row layout (catalog, board pagination footer mode) */
+.footerRow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 8px;
+ width: 100%;
+}
+
+.footerLeft {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+
+.footerRight {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.styleLabel {
+ font-size: 12px;
+ text-transform: capitalize;
+}
+
+/* Thread footer first row */
+.threadRow {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 12px;
+ width: 100%;
+ text-transform: capitalize;
+}
+
+.threadLeft {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex-wrap: wrap;
+}
+
+.threadCenter {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.threadRight {
+ display: flex;
+ align-items: center;
+}
+
+.threadRow .button,
+.button {
+ text-transform: capitalize;
+}
+
+@media (max-width: 640px) {
+ .footer {
+ display: none;
+ }
+}
diff --git a/src/components/footer/footer.tsx b/src/components/footer/footer.tsx
new file mode 100644
index 00000000..19c7c47e
--- /dev/null
+++ b/src/components/footer/footer.tsx
@@ -0,0 +1,99 @@
+import { useTranslation } from 'react-i18next';
+import { useLocation, useParams } from 'react-router-dom';
+import BoardsBar from '../boardsbar';
+import SiteLegalMeta from '../site-legal-meta';
+import StyleSelector from '../style-selector/style-selector';
+import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats } from '../board-buttons/board-buttons';
+import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
+import useReplyModalStore from '../../stores/use-reply-modal-store';
+import styles from './footer.module.css';
+
+/* -----------------------------------------------------------------------------
+ * PageFooterDesktop
+ * Main page footer wrapper: hr, first row slot, BoardsBar, SiteLegalMeta.
+ * -------------------------------------------------------------------------- */
+
+export interface PageFooterDesktopProps {
+ /** Mode-specific first row content (e.g. board pagination or thread controls) */
+ firstRow: React.ReactNode;
+}
+
+export const PageFooterDesktop = ({ firstRow }: PageFooterDesktopProps) => (
+
+);
+
+/* -----------------------------------------------------------------------------
+ * CatalogFooterFirstRow
+ * Catalog footer first row: Style selector on right only (no pagination).
+ * -------------------------------------------------------------------------- */
+
+export const CatalogFooterFirstRow = () => {
+ const { t } = useTranslation();
+ return (
+
+
+
+ {t('style')}:
+
+
+
+ );
+};
+
+/* -----------------------------------------------------------------------------
+ * ThreadFooterFirstRow
+ * Thread page footer first row: Return, Catalog, Top, Update, Auto, Post a Reply, stats.
+ * -------------------------------------------------------------------------- */
+
+export interface ThreadFooterFirstRowProps {
+ postCid: string;
+ threadNumber: number | undefined;
+ subplebbitAddress: string;
+ /** Thread closed - disable Post a Reply */
+ isThreadClosed?: boolean;
+}
+
+export const ThreadFooterFirstRow = ({ postCid, threadNumber, subplebbitAddress, isThreadClosed = false }: ThreadFooterFirstRowProps) => {
+ const { t } = useTranslation();
+ const location = useLocation();
+ const params = useParams();
+ const { openReplyModalEmpty } = useReplyModalStore();
+
+ const isInAllView = isAllView(location.pathname);
+ const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
+ const isInModView = isModView(location.pathname);
+
+ const handlePostReplyClick = () => {
+ if (isThreadClosed) return;
+ openReplyModalEmpty(postCid, threadNumber, subplebbitAddress);
+ };
+
+ return (
+
+
+
+ [
+
+ ]
+
+
+
+ );
+};
diff --git a/src/components/footer/index.ts b/src/components/footer/index.ts
new file mode 100644
index 00000000..70454ba3
--- /dev/null
+++ b/src/components/footer/index.ts
@@ -0,0 +1,2 @@
+export { PageFooterDesktop, CatalogFooterFirstRow, ThreadFooterFirstRow } from './footer';
+export type { PageFooterDesktopProps, ThreadFooterFirstRowProps } from './footer';
diff --git a/src/components/page-footer-desktop/index.ts b/src/components/page-footer-desktop/index.ts
deleted file mode 100644
index 56b45e60..00000000
--- a/src/components/page-footer-desktop/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './page-footer-desktop';
-export type { PageFooterDesktopProps } from './page-footer-desktop';
diff --git a/src/components/page-footer-desktop/page-footer-desktop.module.css b/src/components/page-footer-desktop/page-footer-desktop.module.css
deleted file mode 100644
index 158841cd..00000000
--- a/src/components/page-footer-desktop/page-footer-desktop.module.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.footer {
- margin-top: 16px;
- padding-bottom: 24px;
-}
-
-.footer hr {
- width: 90%;
- margin: 16px auto;
- border: none;
- border-top: 1px solid var(--border-color, #ccc);
-}
-
-.firstRow {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 8px;
- padding: 8px 0;
- text-align: center;
-}
-
-.boardsBarRow {
- padding: 8px 0;
-}
-
-.legalMeta {
- padding: 12px 0 0;
- font-size: 11px;
- color: var(--body-font-color);
- text-align: center;
-}
-
-@media (max-width: 640px) {
- .footer {
- display: none;
- }
-}
diff --git a/src/components/page-footer-desktop/page-footer-desktop.tsx b/src/components/page-footer-desktop/page-footer-desktop.tsx
deleted file mode 100644
index 789af537..00000000
--- a/src/components/page-footer-desktop/page-footer-desktop.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import BoardsBar from '../boardsbar';
-import SiteLegalMeta from '../site-legal-meta';
-import styles from './page-footer-desktop.module.css';
-
-export interface PageFooterDesktopProps {
- /** Mode-specific first row content (e.g. board pagination or thread controls) */
- firstRow: React.ReactNode;
-}
-
-const PageFooterDesktop = ({ firstRow }: PageFooterDesktopProps) => {
- return (
-
- );
-};
-
-export default PageFooterDesktop;
diff --git a/src/components/thread-footer-first-row/index.ts b/src/components/thread-footer-first-row/index.ts
deleted file mode 100644
index 33f1aea1..00000000
--- a/src/components/thread-footer-first-row/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default } from './thread-footer-first-row';
-export type { ThreadFooterFirstRowProps } from './thread-footer-first-row';
diff --git a/src/components/thread-footer-first-row/thread-footer-first-row.module.css b/src/components/thread-footer-first-row/thread-footer-first-row.module.css
deleted file mode 100644
index e0d5a376..00000000
--- a/src/components/thread-footer-first-row/thread-footer-first-row.module.css
+++ /dev/null
@@ -1,31 +0,0 @@
-.row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
- gap: 12px;
- width: 100%;
- text-transform: capitalize;
-}
-
-.left {
- display: flex;
- align-items: center;
- gap: 4px;
- flex-wrap: wrap;
-}
-
-.center {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.right {
- display: flex;
- align-items: center;
-}
-
-.row .button {
- text-transform: capitalize;
-}
diff --git a/src/components/thread-footer-first-row/thread-footer-first-row.tsx b/src/components/thread-footer-first-row/thread-footer-first-row.tsx
deleted file mode 100644
index 0970bd00..00000000
--- a/src/components/thread-footer-first-row/thread-footer-first-row.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { useLocation, useParams } from 'react-router-dom';
-import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
-import useReplyModalStore from '../../stores/use-reply-modal-store';
-import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats } from '../board-buttons/board-buttons';
-import styles from './thread-footer-first-row.module.css';
-
-export interface ThreadFooterFirstRowProps {
- postCid: string;
- threadNumber: number | undefined;
- subplebbitAddress: string;
- /** Thread closed - disable Post a Reply */
- isThreadClosed?: boolean;
-}
-
-const ThreadFooterFirstRow = ({ postCid, threadNumber, subplebbitAddress, isThreadClosed = false }: ThreadFooterFirstRowProps) => {
- const { t } = useTranslation();
- const location = useLocation();
- const params = useParams();
- const { openReplyModalEmpty } = useReplyModalStore();
-
- const isInAllView = isAllView(location.pathname);
- const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
- const isInModView = isModView(location.pathname);
-
- const handlePostReplyClick = () => {
- if (isThreadClosed) return;
- openReplyModalEmpty(postCid, threadNumber, subplebbitAddress);
- };
-
- return (
-
-
-
- [
-
- ]
-
-
-
- );
-};
-
-export default ThreadFooterFirstRow;
diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx
index 57990181..cf03e798 100644
--- a/src/views/board/board.tsx
+++ b/src/views/board/board.tsx
@@ -18,7 +18,7 @@ import { getPageFromFeedPath, getSubplebbitAddress, isDirectoryBoard, normalizeM
import ErrorDisplay from '../../components/error-display/error-display';
import LoadingEllipsis from '../../components/loading-ellipsis';
import BoardPagination from '../../components/board-pagination';
-import PageFooterDesktop from '../../components/page-footer-desktop';
+import { PageFooterDesktop } from '../../components/footer';
import { Post } from '../post';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
diff --git a/src/views/catalog/catalog.tsx b/src/views/catalog/catalog.tsx
index 7e387154..31dfde2d 100644
--- a/src/views/catalog/catalog.tsx
+++ b/src/views/catalog/catalog.tsx
@@ -17,10 +17,9 @@ import useSortingStore from '../../stores/use-sorting-store';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import { getSubplebbitAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils';
import CatalogRow from '../../components/catalog-row';
-import CatalogFooterFirstRow from '../../components/catalog-footer-first-row';
+import { CatalogFooterFirstRow, PageFooterDesktop } from '../../components/footer';
import LoadingEllipsis from '../../components/loading-ellipsis';
import ErrorDisplay from '../../components/error-display/error-display';
-import PageFooterDesktop from '../../components/page-footer-desktop';
import styles from './catalog.module.css';
import { commentMatchesPattern } from '../../lib/utils/pattern-utils';
import { sortCatalogFeedForDisplay } from '../../lib/utils/catalog-sort';
diff --git a/src/views/post/post.tsx b/src/views/post/post.tsx
index 99775e54..f2847345 100644
--- a/src/views/post/post.tsx
+++ b/src/views/post/post.tsx
@@ -10,10 +10,9 @@ import { useDirectories } from '../../hooks/use-directories';
import { isDirectoryBoard } from '../../lib/utils/route-utils';
import useIsMobile from '../../hooks/use-is-mobile';
import ErrorDisplay from '../../components/error-display/error-display';
-import PageFooterDesktop from '../../components/page-footer-desktop';
+import { PageFooterDesktop, ThreadFooterFirstRow } from '../../components/footer';
import PostDesktop from '../../components/post-desktop';
import PostMobile from '../../components/post-mobile';
-import ThreadFooterFirstRow from '../../components/thread-footer-first-row';
import styles from './post.module.css';
// useComment may not return cached feed data immediately due to its updatedAt comparison logic.