use clean 3 card design for landing page (#6084)

<img width="2056" height="1080" alt="Screenshot 2026-04-08 at 1 26
58 PM"
src="https://github.com/user-attachments/assets/e834988b-c3ab-4633-bf15-9fe0457d0029"
/>

<img width="2056" height="1080" alt="Screenshot 2026-04-08 at 1 27
12 PM"
src="https://github.com/user-attachments/assets/adfebd95-ca59-4de0-9336-b1e2dc1dc5fe"
/>
This commit is contained in:
EthanHealy01
2026-04-09 13:38:46 +01:00
committed by GitHub
parent a5b259b453
commit 11b26755a4
8 changed files with 309 additions and 274 deletions

View File

@@ -4287,6 +4287,8 @@ welcomeTitle = "You've been invited!"
[landing]
addFiles = "Add Files"
heroSubtitle = "Drop in or add an existing PDF to get started."
heroTitle = "Stirling PDF"
mobileUpload = "Upload from Mobile"
openFromComputer = "Open from computer"
uploadFromComputer = "Upload from computer"

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { Button, Group, Tooltip, ActionIcon } from '@mantine/core';
import LocalIcon from '@app/components/shared/LocalIcon';
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import { useIsMobile } from '@app/hooks/useIsMobile';
type LandingActionsProps = {
fileInputRef: React.RefObject<HTMLInputElement | null>;
onUploadClick: () => void;
onMobileUploadClick: () => void;
onFileSelect: (event: React.ChangeEvent<HTMLInputElement>) => void;
};
export function LandingActions({ fileInputRef, onUploadClick, onMobileUploadClick, onFileSelect }: LandingActionsProps) {
const terminology = useFileActionTerminology();
const { openFilesModal } = useFilesModalContext();
const icons = useFileActionIcons();
const { config } = useAppConfig();
const isMobile = useIsMobile();
return (
<>
<Group gap="sm" justify="center" wrap="wrap" mb="xs">
<Button
classNames={{ root: 'landing-btn-primary' }}
leftSection={<LocalIcon icon={icons.uploadIconName} width="1rem" height="1rem" style={{ color: 'white' }} />}
onClick={(e) => { e.stopPropagation(); onUploadClick(); }}
>
{terminology.uploadFromComputer}
</Button>
<Button
variant="default"
classNames={{ root: 'landing-btn-secondary' }}
leftSection={<LocalIcon icon="add" width="1rem" height="1rem" className="text-[var(--accent-interactive)]" />}
onClick={(e) => { e.stopPropagation(); openFilesModal(); }}
>
{terminology.addFiles}
</Button>
{config?.enableMobileScanner && !isMobile && (
<Tooltip label={terminology.mobileUpload} position="bottom">
<ActionIcon
size="lg"
variant="default"
radius="md"
aria-label={terminology.mobileUpload}
classNames={{ root: 'landing-btn-secondary landing-btn-icon' }}
onClick={(e) => { e.stopPropagation(); onMobileUploadClick(); }}
>
<LocalIcon icon="qr-code-rounded" width="1.25rem" height="1.25rem" />
</ActionIcon>
</Tooltip>
)}
</Group>
<input ref={fileInputRef} type="file" multiple onChange={onFileSelect} style={{ display: 'none' }} />
</>
);
}

View File

@@ -0,0 +1,47 @@
/** Decorative stack only: window dots + grey bars — no text or i18n (avoids keys showing in the UI). */
export function LandingDocumentStack() {
const bar = (widthPct: number, heightPx: number, marginBottom: number) => ({
width: `${widthPct}%`,
height: heightPx,
marginBottom: marginBottom || undefined,
});
return (
<div aria-hidden className="landing-stack">
<div className="landing-sheet landing-sheet--back landing-sheet--left">
<div className="landing-sheet-side-body">
<div className="landing-bar landing-bar--strong" style={bar(100, 10, 12)} />
<div className="landing-bar" style={bar(80, 8, 8)} />
<div className="landing-bar" style={bar(100, 8, 8)} />
<div className="landing-bar" style={bar(60, 8, 0)} />
</div>
</div>
<div className="landing-sheet landing-sheet--front">
<div className="landing-sheet-header">
<div className="landing-sheet-dot" style={{ backgroundColor: 'rgba(255,255,255,0.35)' }} />
<div className="landing-sheet-dot" style={{ backgroundColor: 'rgba(255,255,255,0.25)' }} />
<div className="landing-sheet-dot" style={{ backgroundColor: 'rgba(255,255,255,0.15)' }} />
</div>
<div className="landing-sheet-body">
<div className="landing-bar landing-bar--strong" style={bar(100, 10, 10)} />
<div className="landing-bar" style={bar(80, 7, 6)} />
<div className="landing-bar" style={bar(100, 7, 6)} />
<div className="landing-bar" style={bar(66, 7, 6)} />
<div className="landing-bar" style={bar(100, 7, 6)} />
<div className="landing-bar" style={bar(88, 7, 6)} />
<div className="landing-bar" style={bar(72, 7, 0)} />
</div>
</div>
<div className="landing-sheet landing-sheet--back landing-sheet--right">
<div className="landing-sheet-side-body">
<div className="landing-bar landing-bar--strong" style={bar(100, 10, 12)} />
<div className="landing-bar" style={bar(75, 8, 8)} />
<div className="landing-bar" style={bar(100, 8, 8)} />
<div className="landing-bar" style={bar(80, 8, 0)} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,137 @@
/* ============================================================
Landing Page styles.
All custom properties are defined in theme.css.
============================================================ */
/* ── Hero text ───────────────────────────────────────────── */
.landing-title {
margin: 0;
margin-top: 1.75rem;
margin-bottom: 0.5rem;
text-align: center;
font-size: 2.125rem;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text-primary);
}
.landing-subtitle {
margin: 0;
margin-bottom: 1.5rem;
text-align: center;
font-size: 0.9375rem;
line-height: 1.5;
max-width: 28rem;
color: var(--text-secondary);
}
/* ── Document stack ──────────────────────────────────────── */
.landing-stack {
position: relative;
z-index: 1;
width: var(--landing-stack-w);
min-width: var(--landing-stack-w);
height: var(--landing-stack-h);
min-height: var(--landing-stack-h);
margin-left: auto;
margin-right: auto;
flex-shrink: 0;
overflow: visible;
}
/* Sheets — static white, never change with theme */
.landing-sheet {
position: absolute;
border-radius: 12px;
background-color: #ffffff;
cursor: default;
}
.landing-sheet--back {
width: 128px;
height: 160px;
transform-origin: bottom center;
border: 1px solid #e5e7eb;
box-shadow: var(--landing-doc-shadow-back-idle);
}
.landing-sheet--left {
left: 8px;
top: 12px;
transform: rotate(-8deg);
}
.landing-sheet--right {
right: 8px;
top: 12px;
transform: rotate(8deg);
}
.landing-sheet--front {
left: 50%;
top: 0;
z-index: 10;
width: 144px;
height: 176px;
margin-left: -72px;
overflow: hidden;
box-shadow: var(--landing-doc-shadow-front-idle);
}
.landing-sheet-header {
display: flex;
height: 40px;
align-items: center;
gap: 8px;
padding: 0 12px;
border-radius: 12px 12px 0 0;
background: var(--landing-hero-gradient);
}
.landing-sheet-dot {
width: 10px;
height: 10px;
border-radius: 9999px;
}
.landing-sheet-body {
padding: 10px 12px;
}
.landing-sheet-side-body {
padding: 12px;
}
/* Bars — static light colours, never change with theme */
.landing-bar {
border-radius: 9999px;
background-color: #e5e7eb;
}
.landing-bar--strong {
background-color: #d1d5db;
}
/* ── Action buttons ──────────────────────────────────────── */
.landing-btn-primary {
background: var(--landing-hero-gradient) !important;
color: #ffffff !important;
border: none !important;
border-radius: 0.75rem !important;
font-weight: 600 !important;
}
.landing-btn-secondary {
border-radius: 0.75rem !important;
font-weight: 600 !important;
border-color: var(--landing-button-border, var(--border-default)) !important;
background-color: var(--landing-button-bg, var(--bg-surface)) !important;
color: var(--landing-button-color, var(--text-primary)) !important;
}
.landing-btn-secondary:hover {
background-color: var(--landing-button-hover-bg, var(--landing-button-bg, var(--bg-surface))) !important;
}
/* Icon-only variant: accent colour instead of button text colour */
.landing-btn-icon {
color: var(--accent-interactive) !important;
}

View File

@@ -1,51 +1,30 @@
import React, { useEffect } from 'react';
import { Container, Button, Group, useMantineColorScheme, ActionIcon, Tooltip } from '@mantine/core';
import React, { useState } from 'react';
import { Container } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import LocalIcon from '@app/components/shared/LocalIcon';
import { useTranslation } from 'react-i18next';
import { useFileHandler } from '@app/hooks/useFileHandler';
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
import { useLogoPath } from '@app/hooks/useLogoPath';
import { useLogoAssets } from '@app/hooks/useLogoAssets';
import { useLogoVariant } from '@app/hooks/useLogoVariant';
import { useFileManager } from '@app/hooks/useFileManager';
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
import { useFileActionIcons } from '@app/hooks/useFileActionIcons';
import { useAppConfig } from '@app/contexts/AppConfigContext';
import { useIsMobile } from '@app/hooks/useIsMobile';
import MobileUploadModal from '@app/components/shared/MobileUploadModal';
import { openFilesFromDisk } from '@app/services/openFilesFromDisk';
import { LandingDocumentStack } from '@app/components/shared/LandingDocumentStack';
import { LandingActions } from '@app/components/shared/LandingActions';
import '@app/components/shared/LandingPage.css';
const LandingPage = () => {
const { addFiles } = useFileHandler();
const fileInputRef = React.useRef<HTMLInputElement>(null);
const { colorScheme } = useMantineColorScheme();
const { t } = useTranslation();
const { openFilesModal } = useFilesModalContext();
const [isUploadHover, setIsUploadHover] = React.useState(false);
const logoPath = useLogoPath();
const logoVariant = useLogoVariant();
const { wordmark } = useLogoAssets();
const { loadRecentFiles } = useFileManager();
const [hasRecents, setHasRecents] = React.useState<boolean>(false);
const [mobileUploadModalOpen, setMobileUploadModalOpen] = React.useState(false);
const { addFiles } = useFileHandler();
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const terminology = useFileActionTerminology();
const icons = useFileActionIcons();
const { config } = useAppConfig();
const isMobile = useIsMobile();
const [mobileUploadModalOpen, setMobileUploadModalOpen] = useState(false);
const handleFileDrop = async (files: File[]) => {
await addFiles(files);
};
const handleOpenFilesModal = () => {
openFilesModal();
};
const handleNativeUploadClick = async () => {
const files = await openFilesFromDisk({
multiple: true,
onFallbackOpen: () => fileInputRef.current?.click()
onFallbackOpen: () => fileInputRef.current?.click(),
});
if (files.length > 0) {
await addFiles(files);
@@ -57,263 +36,48 @@ const LandingPage = () => {
if (files.length > 0) {
await addFiles(files);
}
// Reset the input so the same file can be selected again
event.target.value = '';
};
const handleMobileUploadClick = () => {
setMobileUploadModalOpen(true);
};
const handleFilesReceivedFromMobile = async (files: File[]) => {
if (files.length > 0) {
await addFiles(files);
}
};
// Determine if the user has any recent files (same source as File Manager)
useEffect(() => {
let isMounted = true;
(async () => {
try {
const files = await loadRecentFiles();
if (isMounted) {
setHasRecents((files?.length || 0) > 0);
}
} catch (_err) {
if (isMounted) setHasRecents(false);
}
})();
return () => { isMounted = false; };
}, [loadRecentFiles]);
return (
<Container size="70rem" p={0} h="100%" className="flex items-center justify-center" style={{ position: 'relative' }}>
{/* White PDF Page Background */}
<Container size="70rem" p={0} h="100%" className="flex min-h-0 flex-col" style={{ position: 'relative' }}>
<Dropzone
onDrop={handleFileDrop}
multiple={true}
className="w-4/5 flex items-center justify-center h-[95%]"
style={{
position: 'absolute',
left: '50%',
transform: 'translateX(-50%)',
bottom: 0,
borderRadius: '0.25rem 0.25rem 0 0',
filter: 'var(--drop-shadow-filter)',
backgroundColor: 'var(--landing-paper-bg)',
transition: 'background-color 0.4s ease',
}}
multiple
activateOnClick={false}
enablePointerEvents
aria-label={terminology.dropFilesHere}
className="flex min-h-0 flex-1 cursor-default flex-col items-center justify-center border-none bg-transparent px-4 py-8 shadow-none outline-none"
styles={{
root: {
'&[dataAccept]': {
backgroundColor: 'var(--landing-drop-paper-bg)',
},
border: 'none !important',
backgroundColor: 'transparent',
overflow: 'visible',
'&[data-accept]': { outline: '2px dashed var(--accent-interactive)', outlineOffset: 4 },
'&[data-reject]': { outline: '2px dashed var(--mantine-color-red-6)', outlineOffset: 4 },
},
inner: { overflow: 'visible', display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%' },
}}
>
{logoVariant === 'modern' && (
<div
style={{
position: 'absolute',
top: 0,
right: 0,
zIndex: 10,
}}
>
<img
src={logoPath}
alt="Stirling PDF Logo"
style={{
height: 'auto',
pointerEvents: 'none',
}}
/>
</div>
)}
<div
className={`min-h-[45vh] flex flex-col items-center justify-center px-8 py-8 w-full min-w-[30rem] max-w-[calc(100%-2rem)] border transition-all duration-200 dropzone-inner relative`}
style={{
borderRadius: '0.5rem',
backgroundColor: 'var(--landing-inner-paper-bg)',
borderColor: 'var(--landing-inner-paper-border)',
borderWidth: '1px',
borderStyle: 'solid',
}}
>
{/* Logo positioned absolutely in top right corner */}
<LandingDocumentStack />
<h1 className="landing-title">{t('landing.heroTitle', 'Stirling PDF')}</h1>
<p className="landing-subtitle">{t('landing.heroSubtitle', 'Drop in or add an existing PDF to get started.')}</p>
{/* Centered content container */}
<div className="flex flex-col items-center gap-4 flex-none w-full">
{/* Stirling PDF Branding */}
<Group gap="xs" align="center">
<img
src={colorScheme === 'dark' ? wordmark.white : wordmark.grey}
alt="Stirling PDF"
style={{ height: '2.2rem', width: 'auto' }}
/>
</Group>
{/* Add Files + Native Upload Buttons */}
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '0.6rem',
width: '80%',
marginTop: '0.8rem',
marginBottom: '0.8rem'
}}
onMouseLeave={() => setIsUploadHover(false)}
>
{/* Show both buttons only when recents exist; otherwise show a single Upload button */}
{hasRecents && (
<>
<Button
style={{
backgroundColor: 'var(--landing-button-bg)',
color: 'var(--landing-button-color)',
border: '1px solid var(--landing-button-border)',
borderRadius: '2rem',
height: '38px',
paddingLeft: isUploadHover ? 0 : '1rem',
paddingRight: isUploadHover ? 0 : '1rem',
width: isUploadHover ? '58px' : 'calc(100% - 58px - 0.6rem)',
minWidth: isUploadHover ? '58px' : undefined,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'width .5s ease, padding .5s ease'
}}
onClick={handleOpenFilesModal}
onMouseEnter={() => setIsUploadHover(false)}
>
<LocalIcon icon="add" width="1.5rem" height="1.5rem" className="text-[var(--accent-interactive)]" />
{!isUploadHover && (
<span>
{t('landing.addFiles', 'Add Files')}
</span>
)}
</Button>
<Button
aria-label="Upload"
style={{
backgroundColor: 'var(--landing-button-bg)',
color: 'var(--landing-button-color)',
border: '1px solid var(--landing-button-border)',
borderRadius: '1rem',
height: '38px',
width: isUploadHover ? 'calc(100% - 50px)' : '58px',
minWidth: '58px',
paddingLeft: isUploadHover ? '1rem' : 0,
paddingRight: isUploadHover ? '1rem' : 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'width .5s ease, padding .5s ease'
}}
onClick={handleNativeUploadClick}
onMouseEnter={() => setIsUploadHover(true)}
>
<LocalIcon icon={icons.uploadIconName} width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
{isUploadHover && (
<span style={{ marginLeft: '.5rem' }}>
{terminology.uploadFromComputer}
</span>
)}
</Button>
{config?.enableMobileScanner && !isMobile && (
<Tooltip label={t('landing.mobileUpload', 'Upload from Mobile')} position="bottom">
<ActionIcon
size={38}
variant="subtle"
onClick={handleMobileUploadClick}
style={{
backgroundColor: 'var(--landing-button-bg)',
color: 'var(--accent-interactive)',
border: '1px solid var(--landing-button-border)',
borderRadius: '1rem',
paddingLeft: '0.5rem',
paddingRight: '0.5rem',
}}
>
<LocalIcon icon="qr-code-rounded" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
</ActionIcon>
</Tooltip>
)}
</>
)}
{!hasRecents && (
<>
<Button
aria-label="Upload"
style={{
backgroundColor: 'var(--landing-button-bg)',
color: 'var(--landing-button-color)',
border: '1px solid var(--landing-button-border)',
borderRadius: '1rem',
height: '38px',
width: 'calc(100% - 38px - 0.6rem)',
minWidth: '58px',
paddingLeft: '1rem',
paddingRight: '1rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
onClick={handleNativeUploadClick}
>
<LocalIcon icon="upload" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
<span style={{ marginLeft: '.5rem' }}>
{t('landing.uploadFromComputer', 'Upload from computer')}
</span>
</Button>
{config?.enableMobileScanner && !isMobile && (
<Tooltip label={t('landing.mobileUpload', 'Upload from Mobile')} position="bottom">
<ActionIcon
size={38}
variant="subtle"
onClick={handleMobileUploadClick}
style={{
backgroundColor: 'var(--landing-button-bg)',
color: 'var(--accent-interactive)',
border: '1px solid var(--landing-button-border)',
borderRadius: '1rem',
paddingLeft: '0.5rem',
paddingRight: '0.5rem',
}}
>
<LocalIcon icon="qr-code-rounded" width="1.25rem" height="1.25rem" style={{ color: 'var(--accent-interactive)' }} />
</ActionIcon>
</Tooltip>
)}
</>
)}
</div>
{/* Hidden file input for native file picker */}
<input
ref={fileInputRef}
type="file"
multiple
onChange={handleFileSelect}
style={{ display: 'none' }}
/>
</div>
{/* Instruction Text */}
<span
className="text-[var(--accent-interactive)]"
style={{ fontSize: '.8rem' }}
>
{terminology.dropFilesHere}
</span>
</div>
<LandingActions
fileInputRef={fileInputRef}
onUploadClick={() => void handleNativeUploadClick()}
onMobileUploadClick={() => setMobileUploadModalOpen(true)}
onFileSelect={handleFileSelect}
/>
</Dropzone>
<MobileUploadModal
opened={mobileUploadModalOpen}
onClose={() => setMobileUploadModalOpen(false)}

View File

@@ -12,6 +12,8 @@ export function useFileActionTerminology() {
uploadFile: t('fileUpload.uploadFile', 'Upload File'),
upload: t('fileUpload.upload', 'Upload'),
dropFilesHere: t('fileUpload.dropFilesHere', 'Drop files here or click the upload button'),
addFiles: t('landing.addFiles', 'Add Files'),
mobileUpload: t('landing.mobileUpload', 'Upload from Mobile'),
uploadFromComputer: t('landing.uploadFromComputer', 'Upload from computer'),
download: t('download', 'Download'),
downloadAll: t('rightRail.downloadAll', 'Download All'),

View File

@@ -256,6 +256,25 @@
--landing-drop-inner-paper-bg: #BBDEFB;
--landing-drop-inner-paper-border: #90CAF9;
/* landing hero & stack */
--landing-hero-gradient: linear-gradient(135deg, #4c8bf5 0%, #3a7be8 100%);
--landing-stack-w: 224px;
--landing-stack-h: 176px;
--landing-stack-glow-bg: radial-gradient(circle, rgba(74,144,226,.18) 0%, transparent 70%);
/* landing doc stack shadows */
--landing-doc-shadow-back-idle: 0 4px 20px rgba(0,0,0,.08), 0 1px 3px rgba(0,0,0,.04);
--landing-doc-shadow-back-hover: 0 12px 40px rgba(0,0,0,.15), 0 4px 12px rgba(0,0,0,.08);
--landing-doc-shadow-front-idle: 0 8px 30px rgba(0,0,0,.12), 0 4px 12px rgba(0,0,0,.06), 0 0 0 1px rgba(0,0,0,.02);
--landing-doc-shadow-front-hover: 0 18px 48px rgba(0,0,0,.18), 0 8px 20px rgba(0,0,0,.1), 0 0 0 1px rgba(0,0,0,.04);
/* landing action button shadows */
--landing-action-transition: transform 0.28s cubic-bezier(0.4,0,0.2,1), box-shadow 0.32s cubic-bezier(0.4,0,0.2,1);
--landing-action-shadow-idle: 0 6px 18px rgba(0,0,0,0), 0 2px 6px rgba(0,0,0,0);
--landing-action-shadow-hover: 0 6px 18px rgba(0,0,0,.14), 0 2px 6px rgba(0,0,0,.08);
--landing-action-primary-shadow-idle: 0 10px 26px rgba(58,123,232,0), 0 4px 12px rgba(0,0,0,0);
--landing-action-primary-shadow-hover: 0 10px 26px rgba(58,123,232,.42), 0 4px 12px rgba(0,0,0,.1);
/* selected file header colors */
--header-selected-bg: #1E88E5; /* light mode selected header matches dark */
--header-selected-fg: #FFFFFF;
@@ -509,15 +528,15 @@
--landing-paper-bg: #171A1F;
--landing-inner-paper-bg: var(--bg-raised);
--landing-inner-paper-border: #2D3237;
--landing-button-bg: #2B3037;
--landing-button-color: #ffffff;
--landing-button-border: #2D3237;
--landing-button-hover-bg: #4c525b;
/* drop state */
--landing-drop-paper-bg: #1A2332;
--landing-drop-inner-paper-bg: #2A3441;
--landing-drop-inner-paper-border: #3A4451;
/* landing dark overrides */
--landing-stack-glow-bg: radial-gradient(circle, rgba(30,136,229,.22) 0%, transparent 70%);
--landing-doc-shadow-back-idle: 0 4px 20px rgba(0,0,0,.35), 0 1px 3px rgba(0,0,0,.25);
--landing-doc-shadow-back-hover: 0 14px 44px rgba(0,0,0,.55), 0 6px 16px rgba(0,0,0,.35);
--landing-doc-shadow-front-idle: 0 8px 30px rgba(0,0,0,.45), 0 4px 12px rgba(0,0,0,.3), 0 0 0 1px rgba(255,255,255,.04);
--landing-doc-shadow-front-hover: 0 20px 52px rgba(0,0,0,.6), 0 10px 24px rgba(0,0,0,.4), 0 0 0 1px rgba(255,255,255,.06);
--landing-button-color: #ffffff;
--landing-button-hover-bg: var(--bg-raised);
/* selected file header colors for dark */
--header-selected-bg: #1E88E5;

View File

@@ -12,6 +12,8 @@ export function useFileActionTerminology() {
uploadFile: t('fileUpload.openFile', 'Open File'),
upload: t('fileUpload.open', 'Open'),
dropFilesHere: t('fileUpload.dropFilesHereOpen', 'Drop files here or click the open button'),
addFiles: t('fileUpload.openFiles', 'Open Files'),
mobileUpload: t('landing.mobileUpload', 'Upload from Mobile'),
uploadFromComputer: t('landing.openFromComputer', 'Open from computer'),
download: t('save', 'Save'),
downloadAll: t('rightRail.saveAll', 'Save All'),