* {
+ flex: 1 1 auto;
+ min-height: 0;
+}
+
+.mobile-bottom-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ padding: 0.5rem;
+ border-top: 1px solid var(--border-subtle);
+ background: var(--bg-toolbar);
+ gap: 0.5rem;
+ position: relative;
+ z-index: 10;
+ touch-action: manipulation;
+}
+
+.mobile-bottom-button {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.25rem;
+ padding: 0.5rem;
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ cursor: pointer;
+ border-radius: 0.5rem;
+ transition: background 0.2s ease;
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+ user-select: none;
+ -webkit-user-select: none;
+ min-height: 44px;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .mobile-bottom-button:hover {
+ background: var(--bg-hover, rgba(0, 0, 0, 0.05));
+ }
+}
+
+.mobile-bottom-button:active {
+ background: var(--bg-active, rgba(0, 0, 0, 0.1));
+}
+
+.mobile-bottom-button-label {
+ font-size: 0.75rem;
+ font-weight: 500;
+ color: var(--text-muted);
+}
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 26d190dfa..f283a1caa 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -1,15 +1,24 @@
+import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
-import { Group } from "@mantine/core";
+import { Group, useMantineColorScheme } from "@mantine/core";
import { useSidebarContext } from "../contexts/SidebarContext";
import { useDocumentMeta } from "../hooks/useDocumentMeta";
-import { getBaseUrl } from "../constants/app";
+import { BASE_PATH, getBaseUrl } from "../constants/app";
+import { useMediaQuery } from "@mantine/hooks";
+import AppsIcon from '@mui/icons-material/AppsRounded';
import ToolPanel from "../components/tools/ToolPanel";
import Workbench from "../components/layout/Workbench";
import QuickAccessBar from "../components/shared/QuickAccessBar";
import RightRail from "../components/shared/RightRail";
import FileManager from "../components/FileManager";
+import LocalIcon from "../components/shared/LocalIcon";
+import { useFilesModalContext } from "../contexts/FilesModalContext";
+
+import "./HomePage.css";
+
+type MobileView = "tools" | "workbench";
export default function HomePage() {
@@ -20,7 +29,84 @@ export default function HomePage() {
const { quickAccessRef } = sidebarRefs;
- const { selectedTool, selectedToolKey } = useToolWorkflow();
+ const { selectedTool, selectedToolKey, handleToolSelect, handleBackToTools } = useToolWorkflow();
+
+ const { openFilesModal } = useFilesModalContext();
+ const { colorScheme } = useMantineColorScheme();
+ const isMobile = useMediaQuery("(max-width: 1024px)");
+ const sliderRef = useRef
(null);
+ const [activeMobileView, setActiveMobileView] = useState("tools");
+ const isProgrammaticScroll = useRef(false);
+
+ const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo");
+ const brandIconSrc = `${BASE_PATH}/branding/StirlingPDFLogoNoText${
+ colorScheme === "dark" ? "Dark" : "Light"
+ }.svg`;
+ const brandTextSrc = `${BASE_PATH}/branding/StirlingPDFLogo${
+ colorScheme === "dark" ? "White" : "Black"
+ }Text.svg`;
+
+ const handleSelectMobileView = useCallback((view: MobileView) => {
+ setActiveMobileView(view);
+ }, []);
+
+ useEffect(() => {
+ if (isMobile) {
+ const container = sliderRef.current;
+ if (container) {
+ isProgrammaticScroll.current = true;
+ const offset = activeMobileView === "tools" ? 0 : container.offsetWidth;
+ container.scrollTo({ left: offset, behavior: "smooth" });
+
+ // Re-enable scroll listener after animation completes
+ setTimeout(() => {
+ isProgrammaticScroll.current = false;
+ }, 500);
+ }
+ return;
+ }
+
+ setActiveMobileView("tools");
+ const container = sliderRef.current;
+ if (container) {
+ container.scrollTo({ left: 0, behavior: "auto" });
+ }
+ }, [activeMobileView, isMobile]);
+
+ useEffect(() => {
+ if (!isMobile) return;
+
+ const container = sliderRef.current;
+ if (!container) return;
+
+ let animationFrame = 0;
+
+ const handleScroll = () => {
+ if (isProgrammaticScroll.current) {
+ return;
+ }
+
+ if (animationFrame) {
+ cancelAnimationFrame(animationFrame);
+ }
+
+ animationFrame = window.requestAnimationFrame(() => {
+ const { scrollLeft, offsetWidth } = container;
+ const threshold = offsetWidth / 2;
+ const nextView: MobileView = scrollLeft >= threshold ? "workbench" : "tools";
+ setActiveMobileView((current) => (current === nextView ? current : nextView));
+ });
+ };
+
+ container.addEventListener("scroll", handleScroll, { passive: true });
+
+ return () => {
+ container.removeEventListener("scroll", handleScroll);
+ if (animationFrame) {
+ cancelAnimationFrame(animationFrame);
+ }
+ };
+ }, [isMobile]);
const baseUrl = getBaseUrl();
@@ -38,19 +124,107 @@ export default function HomePage() {
return (
-
-
-
-
-
-
-
+ {isMobile ? (
+
+
+
+
+

+

+
+
+
+
+
+
+
+ {t('home.mobile.swipeHint', 'Swipe left or right to switch views')}
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ )}
);
}