From 1f82d157077988b409116528a119fb49c484b058 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Tue, 24 Feb 2026 14:01:27 -0800 Subject: [PATCH 1/2] Add new download page --- packages/web/src/assets/styles/index.css | 6 +- .../download-page/DownloadPage.module.css | 356 ++++++++---------- .../pages/download-page/DownloadPage.tsx | 268 +++++++------ 3 files changed, 328 insertions(+), 302 deletions(-) diff --git a/packages/web/src/assets/styles/index.css b/packages/web/src/assets/styles/index.css index 49b1d4c4189..a203ab52f27 100644 --- a/packages/web/src/assets/styles/index.css +++ b/packages/web/src/assets/styles/index.css @@ -4,9 +4,9 @@ @import './ant-overrides.css'; @import './shadows.css'; -/* Exclude landing page 2026 so Dust Bucer / Urbanist can apply */ -#root *:not(#landing-page-2026 *), -body *:not(#landing-page-2026 *) { +/* Exclude landing page 2026 and download page so Dust Bucer / Urbanist can apply */ +#root *:not(#landing-page-2026 *):not(#download-page *), +body *:not(#landing-page-2026 *):not(#download-page *) { font-family: var(--harmony-font-family); } diff --git a/packages/web/src/public-site/pages/download-page/DownloadPage.module.css b/packages/web/src/public-site/pages/download-page/DownloadPage.module.css index 47d447a33f6..94e6bcf3ec2 100644 --- a/packages/web/src/public-site/pages/download-page/DownloadPage.module.css +++ b/packages/web/src/public-site/pages/download-page/DownloadPage.module.css @@ -1,252 +1,230 @@ -.container { - display: inline-flex; - flex-direction: column; +.page { + position: relative; + min-height: 100vh; width: 100%; - background: #fff; + min-width: 100%; + background: #111; + color: #fff; + font-family: 'Urbanist', sans-serif; + font-feature-settings: + 'liga' 0, + 'clig' 0; + overflow-x: hidden; + box-sizing: border-box; } -.content { - margin-top: 90px; +.page::before { + content: ''; + position: fixed; + inset: 0; + background: #111; + z-index: -1; } -.downloadsSection { - margin: 50px 0; +.page .main, +.page > nav, +.page > footer { + transition: opacity 0.35s ease-out; } -.title, -.appTitle { - color: #7e1bcc; - background: linear-gradient(315deg, #5b23e1 2.04%, #a22feb 100%); - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - font-size: 64px; - font-weight: 900; - text-transform: uppercase; - line-height: 78px; +.page:not([data-fonts-ready]) .main, +.page:not([data-fonts-ready]) .page > nav, +.page:not([data-fonts-ready]) .page > footer { + opacity: 0; } -.isMobile .title { - font-size: 48px; - line-height: 59px; +.page[data-fonts-ready] .main, +.page[data-fonts-ready] .page > nav, +.page[data-fonts-ready] .page > footer { + opacity: 1; } -.isMobile .appTitle { - font-size: 32px; - line-height: 39px; +.main { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; } -.titleContainer { - text-align: center; +.spacer { + width: 100%; + height: 160px; } -.isMobile .titleContainer { - margin-right: 24px; - margin-left: 24px; +.spacerSmall { + width: 100%; + height: 160px; } -.appTitle { +/* ── Hero / Title ── */ +.titleSection { + width: 100%; + max-width: 1440px; + padding: 0 96px; + box-sizing: border-box; text-align: center; - font-size: 55px; - line-height: 68px; +} + +.title { + font-family: 'Dust Bucer', sans-serif; + font-weight: normal; + font-style: normal; + font-size: 146px; + line-height: 0.72; + color: #fff; + margin: 0; + font-feature-settings: 'salt' 1; + text-transform: uppercase; +} + +.titleLine2 { + display: inline; } .subtitle { - margin-top: 8px; + font-family: 'Urbanist', sans-serif; font-weight: 500; font-size: 24px; - line-height: 34px; + line-height: 1.4; + color: rgba(255, 255, 255, 0.7); + margin: 40px 0 0; } -.isMobile .subtitle { - font-size: 18px; - line-height: 34px; - margin: 8px auto 0px; - max-width: 326px; -} - -.linksContainer { - margin-top: 40px; +/* ── Download Grid ── */ +.downloadGrid { display: flex; - justify-content: center; + width: 100%; max-width: 1240px; - margin-left: auto; - margin-right: auto; -} - -.isMobile .linksContainer { - flex-wrap: wrap-reverse; -} - -.platformHeaderIconContainer { - margin-top: 24px; - width: 140px; - height: 140px; - background: linear-gradient(315deg, #5b23e1 0%, #a22feb 100%); - padding: 25px; - border-radius: 50%; - margin-bottom: 20px; - display: inline-flex; - justify-content: center; - align-items: center; -} - -.isMobile .platformHeaderIconContainer { - width: 80px; - height: 80px; - padding: 16px; -} - -.isMobile .mobileIcon { - height: 44px; + padding: 0 100px; + box-sizing: border-box; } -.desktopDownloadsContainer, -.mobileDownloadsContainer { +.desktopColumn, +.mobileColumn { flex: 1; display: flex; flex-direction: column; align-items: center; } -.desktopDownloadsContainer { - padding-right: 85px; - border-right: solid 1px #dad9e0; -} - -.isMobile .desktopDownloadsContainer { - border-right: none; - padding-top: 44px; -} - -.mobileDownloadsContainer { - padding-left: 85px; -} - -@media (max-width: 1180px) { - .mobileDownloadsContainer { - padding-left: 20px; - } - .desktopDownloadsContainer { - padding-right: 20px; - } -} - -.isMobile .desktopDownloadsContainer, -.isMobile .mobileDownloadsContainer { - flex: unset; - width: 100%; - margin-left: 32px; - margin-right: 32px; - padding-left: 0; - padding-right: 0; -} - -.isMobile .mobileDownloadsContainer { - padding-bottom: 64px; - border-bottom: solid 1px #dad9e0; +.desktopColumn { + border-right: 1px solid rgba(255, 255, 255, 0.15); } -.appSubtitle { - font-weight: 500; - font-size: 20px; - line-height: 34px; - padding-top: 10px; - text-align: center; -} - -.downloadLinks { - margin-top: 60px; +.sectionHeading { + font-family: 'Dust Bucer', sans-serif; + font-weight: normal; + font-style: normal; + font-size: 112px; + line-height: 1; + color: #fff; + margin: 0; text-align: center; + font-feature-settings: 'salt' 1; + text-transform: uppercase; } -.isMobile .downloadLinks { - margin-top: 8px; - width: 100%; -} - -.downloadLinkWrapper { - margin-bottom: 10px; +.links { + display: flex; + flex-direction: column; + align-items: center; + gap: 21.5px; margin-top: 24px; - width: 100%; -} - -.isMobile .downloadLinkWrapper { - margin-bottom: 8px; - margin-top: 0; } .downloadLink { - cursor: pointer; + display: inline-flex; + align-items: center; + gap: 12px; background: none; border: none; - font-weight: 500; - color: #6c6780; - display: inline-block; - font-size: 24px; - line-height: 24px; - transition: all 0.15s ease-in-out; - border-bottom: solid 2px transparent; + cursor: pointer; + font-family: 'Urbanist', sans-serif; + font-weight: 600; + font-size: 28px; + line-height: 1.5; + color: rgba(255, 255, 255, 0.7); + text-decoration: none; + padding: 0; + transition: + color 0.15s ease, + transform 0.15s ease; } .downloadLink:hover { - color: #6c6780; - transform: scale3d(1.03, 1.03, 1.03); + color: #fff; + transform: scale(1.03); } .downloadLink:active { - color: #6c6780; - transform: scale3d(0.99, 0.99, 0.99); + color: rgba(255, 255, 255, 0.5); + transform: scale(0.99); } -.downloadLink:hover .platformName, -.downloadLink:active .platformName { - color: var(--harmony-secondary); +.linkIcon { + width: 25px; + height: 25px; + flex-shrink: 0; } -.isMobile .downloadLink:hover .platformName, -.isMobile .downloadLink:active .platformName { - color: #fff; +.linkIcon path { + fill: currentColor; } -.isMobile .downloadLink { - font-weight: 800; - font-size: 20px; - line-height: 25px; - color: #fff; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - width: 100%; - padding: 8px 25px; - background: #7e1bcc; - border: 1px solid #f2f2f4; - box-shadow: - 0px 0px 1px -2px rgba(133, 129, 153, 0.1), - 0px 1px 0px -2px #e3e3e3, - 1px 2px 5px rgba(133, 129, 153, 0.25); - border-radius: 6px; -} +/* ── Responsive / Mobile ── */ +@media (max-width: 800px) { + .spacer { + height: 160px; + } -.platformName { - font-weight: 800; - padding-left: 6px; -} + .spacerSmall { + height: 120px; + } -.platformIcon { - width: 25px; - height: 25px; - position: relative; - top: 4px; - margin-right: 6px; -} + .titleSection { + padding: 0 56px; + } -.platformIcon path { - fill: currentColor; -} + .title { + font-size: 104px; + line-height: 0.72; + } -.isMobile .platformIcon { - width: 22px; - height: 22px; + /* Break title into two lines: "Download" then "the App" */ + .titleLine2 { + display: block; + } + + .subtitle { + font-size: 18px; + margin-top: 40px; + } + + .downloadGrid { + flex-direction: column; + gap: 40px; + padding: 0 8px; + } + + .desktopColumn { + border-right: none; + padding-bottom: 0; + } + + .sectionHeading { + font-size: 74px; + } + + .links { + gap: 18px; + } + + .downloadLink { + font-size: 22px; + } + + .linkIcon { + width: 22px; + height: 22px; + } } diff --git a/packages/web/src/public-site/pages/download-page/DownloadPage.tsx b/packages/web/src/public-site/pages/download-page/DownloadPage.tsx index f1e7fcc31eb..eb76019632d 100644 --- a/packages/web/src/public-site/pages/download-page/DownloadPage.tsx +++ b/packages/web/src/public-site/pages/download-page/DownloadPage.tsx @@ -2,18 +2,14 @@ import { useCallback, useEffect, useState } from 'react' import { OS, MobileOS } from '@audius/common/models' import { route } from '@audius/common/utils' -import { IconCloudDownload } from '@audius/harmony' -import cn from 'classnames' +import { IconCloudDownload, ThemeProvider } from '@audius/harmony' import queryString from 'query-string' import { useLocation } from 'react-router' -import { ParallaxProvider } from 'react-scroll-parallax' -import IconDownloadDesktop from 'assets/img/publicSite/downloadDesktop.svg' -import IconDownloadMobile from 'assets/img/publicSite/downloadMobile.svg' import { CookieBanner } from 'components/cookie-banner/CookieBanner' -import Footer from 'public-site/components/Footer' -import NavBanner from 'public-site/components/NavBanner' -import CTAStartListening from 'public-site/pages/landing-page/components/CTAStartListening' +import { Nav2026 } from 'public-site/pages/landing-2026/components/Nav2026' +import { Footer2026 } from 'public-site/pages/landing-2026/components/Footer2026' +import { Partners2026 } from 'public-site/pages/landing-2026/components/Partners2026' import DownloadApp from 'services/download-app/DownloadApp' import { getIOSAppLink } from 'utils/appLinks' import { getOS } from 'utils/clientUtil' @@ -24,16 +20,25 @@ import DownloadStartingMessage from './components/DownloadStartingMessage' const { ANDROID_PLAY_STORE_LINK } = route +const MOBILE_MAX_WIDTH = 800 +const MOBILE_MEDIA_QUERY = + typeof window !== 'undefined' + ? window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH}px)`) + : null + const messages = { - downloadTitle: 'Download the Audius App', - downloadSubtitle: - 'Looking for the best experience? Check out the Audius App ', - getMobileApp: 'Get the mobile app', - getDesktopApp: 'Get the desktop app', - getDesktopAppSubtitle: 'The definitive Audius Experience', - getMobileAppSubtitle: 'Available for iOS & Android Devices', - getFor: 'Get for' + titleLine1: 'Download', + titleLine2: 'the App', + subtitle: 'For the best experience download our app.', + desktop: 'Desktop', + mobile: 'Mobile', + forMac: 'For Mac', + forWindows: 'For Windows', + forLinux: 'For Linux', + iOS: 'iOS', + android: 'Android' } + type DownloadPageProps = { isMobile: boolean openNavScreen: () => void @@ -43,55 +48,56 @@ type DownloadPageProps = { const os = getOS() const iOSDownloadLink = getIOSAppLink() -const DesktopDownloadButton = ({ os }: { os: OS }) => { - let platformName - if (os === OS.WIN) { - platformName = 'Windows' - } else if (os === OS.MAC) { - platformName = 'Mac' - } else { - platformName = 'Linux' - } +const DesktopDownloadButton = ({ + os, + label +}: { + os: OS + label: string +}) => { return ( -
- -
+ ) } -const AppDownloadLink = ({ os }: { os: MobileOS }) => { - let platformName, downloadLink - if (os === MobileOS.IOS) { - platformName = 'iOS' - downloadLink = iOSDownloadLink - } else if (os === MobileOS.ANDROID) { - platformName = 'Android' - downloadLink = ANDROID_PLAY_STORE_LINK - } +const MobileDownloadLink = ({ + os, + label +}: { + os: MobileOS + label: string +}) => { + const downloadLink = + os === MobileOS.IOS ? iOSDownloadLink : ANDROID_PLAY_STORE_LINK return ( -
- - {messages.getFor}{' '} - - - {platformName} - - -
+ + + {label} + ) } const DownloadPage = (props: DownloadPageProps) => { + const [isMobileOrNarrow, setIsMobileOrNarrow] = useState(props.isMobile) + const [showCookieBanner, setShowCookieBanner] = useState(false) + const [fontsReady, setFontsReady] = useState(false) + + useEffect(() => { + if (MOBILE_MEDIA_QUERY) { + const handler = () => setIsMobileOrNarrow(MOBILE_MEDIA_QUERY.matches) + handler() + MOBILE_MEDIA_QUERY.addListener(handler) + return () => MOBILE_MEDIA_QUERY.removeListener(handler) + } + }, []) + useEffect(() => { document.documentElement.style.height = 'auto' return () => { @@ -112,89 +118,131 @@ const DownloadPage = (props: DownloadPageProps) => { downloadDesktopApp() } }, [downloadDesktopApp, start_download]) - const [showCookieBanner, setShowCookieBanner] = useState(false) + useEffect(() => { shouldShowCookieBanner().then((show) => { setShowCookieBanner(show) }) }, []) + useEffect(() => { + const base = (import.meta.env.BASE_URL ?? '/').replace(/\/$/, '') || '' + const fontsCssHref = `${base}/fonts-landing-2026.css` + const urbanistHref = + 'https://fonts.googleapis.com/css2?family=Urbanist:wght@400;500;600;700&display=swap' + + const linkFonts = document.createElement('link') + linkFonts.rel = 'stylesheet' + linkFonts.href = fontsCssHref + + const linkUrbanist = document.createElement('link') + linkUrbanist.rel = 'stylesheet' + linkUrbanist.href = urbanistHref + + const maxWait = 1500 + const finish = () => { + const ready = document.fonts?.ready + ? Promise.race([ + document.fonts.ready, + new Promise((resolve) => setTimeout(resolve, maxWait)) + ]) + : Promise.resolve() + ready.then(() => setFontsReady(true)) + } + + let loaded = 0 + const maybeFinish = () => { + loaded += 1 + if (loaded >= 2) finish() + } + linkFonts.onload = maybeFinish + linkFonts.onerror = maybeFinish + linkUrbanist.onload = maybeFinish + linkUrbanist.onerror = maybeFinish + + document.head.appendChild(linkFonts) + document.head.appendChild(linkUrbanist) + + const fallback = setTimeout(() => setFontsReady(true), maxWait + 500) + return () => { + clearTimeout(fallback) + linkFonts.remove() + linkUrbanist.remove() + } + }, []) + const onDismissCookiePolicy = useCallback(() => { dismissCookieBanner() setShowCookieBanner(false) }, []) return ( - +
- {showCookieBanner && ( + {showCookieBanner ? ( - )} - -
+
{start_download && os ? : null} -
-
-

{messages.downloadTitle}

-

{messages.downloadSubtitle}

-
-
-
-
- -
-

{messages.getDesktopApp}

- - {messages.getDesktopAppSubtitle} - -
- - - -
+
+
+

+ {messages.titleLine1}{' '} + {messages.titleLine2} +

+

{messages.subtitle}

+
+
+
+
+

{messages.desktop}

+
+ + +
-
-
- -
-

{messages.getMobileApp}

- - {messages.getMobileAppSubtitle} - -
- - -
+
+
+

{messages.mobile}

+
+ +
- -
-
+
+ +
+
+
- + ) } From baea2505043aef9f8a2ff2c9f855dcecb7b225bd Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Tue, 24 Feb 2026 14:02:39 -0800 Subject: [PATCH 2/2] fix lint --- .../pages/download-page/DownloadPage.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/web/src/public-site/pages/download-page/DownloadPage.tsx b/packages/web/src/public-site/pages/download-page/DownloadPage.tsx index eb76019632d..d1c80fea10d 100644 --- a/packages/web/src/public-site/pages/download-page/DownloadPage.tsx +++ b/packages/web/src/public-site/pages/download-page/DownloadPage.tsx @@ -7,8 +7,8 @@ import queryString from 'query-string' import { useLocation } from 'react-router' import { CookieBanner } from 'components/cookie-banner/CookieBanner' -import { Nav2026 } from 'public-site/pages/landing-2026/components/Nav2026' import { Footer2026 } from 'public-site/pages/landing-2026/components/Footer2026' +import { Nav2026 } from 'public-site/pages/landing-2026/components/Nav2026' import { Partners2026 } from 'public-site/pages/landing-2026/components/Partners2026' import DownloadApp from 'services/download-app/DownloadApp' import { getIOSAppLink } from 'utils/appLinks' @@ -48,13 +48,7 @@ type DownloadPageProps = { const os = getOS() const iOSDownloadLink = getIOSAppLink() -const DesktopDownloadButton = ({ - os, - label -}: { - os: OS - label: string -}) => { +const DesktopDownloadButton = ({ os, label }: { os: OS; label: string }) => { return (