diff --git a/packages/web/src/public-site/pages/landing-2026/components/CreateFutureCTA.module.css b/packages/web/src/public-site/pages/landing-2026/components/CreateFutureCTA.module.css index 82a264a954e..7135bd6050d 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/CreateFutureCTA.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/CreateFutureCTA.module.css @@ -80,7 +80,10 @@ background: transparent; cursor: pointer; text-decoration: none; - transition: box-shadow 0.25s ease; + transition: + box-shadow 0.25s ease, + background 0.2s ease, + border-color 0.2s ease; box-sizing: border-box; } @@ -92,6 +95,17 @@ inset -4px 0px 4px 0px rgba(175, 72, 255, 0.25); } +/* Figma 2738-4495: press state */ +.ctaButton:active { + background: rgba(53, 20, 75, 0.85); + border-color: rgba(175, 72, 255, 0.9); + box-shadow: none; +} + +.ctaButton:active .ctaLabel { + color: #e7c7ff; +} + .ctaLabel { font-family: 'Urbanist', sans-serif; font-weight: 700; @@ -100,12 +114,12 @@ color: #fff; text-transform: capitalize; white-space: nowrap; + transition: color 0.2s ease; } @media (max-width: 800px) { .section { padding: 32px 16px; - box-shadow: 0 10px 50px rgba(53, 5, 152, 0.25); } .content { diff --git a/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.module.css b/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.module.css index 6eed216def2..e7a635da17d 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.module.css @@ -72,9 +72,16 @@ text-shadow 0.2s ease; } -.faqItem[aria-expanded='true'] .faqQuestion, -.faqItem:hover .faqQuestion, -.faqItem:active .faqQuestion, +/* Purple hover when whole FAQ row is hovered; carets match text color */ +.faqItem:hover .faqQuestion { + color: #e7c7ff; + text-shadow: + -4px 0px 4px rgba(175, 72, 255, 0.25), + 4px 0px 4px rgba(175, 72, 255, 0.25), + 0px -4px 4px rgba(175, 72, 255, 0.25), + 0px 4px 4px rgba(175, 72, 255, 0.25); +} + .faqItem:focus-visible .faqQuestion { color: #e7c7ff; text-shadow: @@ -84,28 +91,39 @@ 0px 4px 4px rgba(175, 72, 255, 0.25); } +/* Press state: same purple as hover (Figma 2777-2986) */ +.faqItem:active .faqQuestion { + color: #e7c7ff; + text-shadow: + -4px 0px 4px rgba(175, 72, 255, 0.25), + 4px 0px 4px rgba(175, 72, 255, 0.25), + 0px -4px 4px rgba(175, 72, 255, 0.25), + 0px 4px 4px rgba(175, 72, 255, 0.25); +} + .chevron { width: 16px; height: 16px; flex-shrink: 0; - transition: transform 0.3s ease; + display: inline-flex; + align-items: center; + justify-content: center; + color: #fff; + transition: + transform 0.3s ease, + color 0.2s ease; } -.chevron svg, -.chevron svg path { - fill: #fff !important; - transition: fill 0.2s ease; +.chevron svg { + width: 100%; + height: 100%; } -.faqItem[aria-expanded='true'] .chevron svg, -.faqItem[aria-expanded='true'] .chevron svg path, -.faqItem:hover .chevron svg, -.faqItem:hover .chevron svg path, -.faqItem:active .chevron svg, -.faqItem:active .chevron svg path, -.faqItem:focus-visible .chevron svg, -.faqItem:focus-visible .chevron svg path { - fill: #e7c7ff !important; +/* Purple when whole row hover/focus/active, same as question */ +.faqItem:hover .chevron, +.faqItem:focus-visible .chevron, +.faqItem:active .chevron { + color: #e7c7ff; } .faqItem[aria-expanded='true'] .chevron { diff --git a/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.tsx b/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.tsx index 80f976bb866..07710f2f6f9 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.tsx +++ b/packages/web/src/public-site/pages/landing-2026/components/FAQ2026.tsx @@ -1,9 +1,30 @@ import { useState } from 'react' -import { IconCaretDown } from '@audius/harmony' - import styles from './FAQ2026.module.css' +/** Inline chevron so we control color (white default, purple on hover) without Harmony theme override */ +function ChevronDown({ className }: { className?: string }) { + return ( + + + + + + ) +} + const faqItems = [ { question: 'Who is Audius made for?', @@ -62,11 +83,7 @@ export const FAQ2026 = (_props: FAQ2026Props) => { >

{item.question}

- +
{isOpen ? (
diff --git a/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.module.css b/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.module.css index 8d22f778abb..676eaadce4e 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.module.css @@ -111,12 +111,30 @@ overflow: hidden; } +.artworkSkeletonInner { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.06); +} + +.artworkWrapLoaded .artworkSkeletonInner { + opacity: 0; + pointer-events: none; + transition: opacity 0.25s ease; +} + .artwork { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; + opacity: 0; + transition: opacity 0.35s ease; +} + +.artworkWrapLoaded .artwork { + opacity: 1; } .bwOverlay { diff --git a/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.tsx b/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.tsx index a4157f4aadd..c052d10fbb2 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.tsx +++ b/packages/web/src/public-site/pages/landing-2026/components/FeaturedContests2026.tsx @@ -1,4 +1,4 @@ -import { MouseEvent } from 'react' +import { MouseEvent, useState } from 'react' import { useExploreContent, @@ -6,9 +6,13 @@ import { useTrack, useUser } from '@audius/common/api' -import { ID } from '@audius/common/models' +import { imageBlank } from '@audius/common/assets' +import { useImageSize } from '@audius/common/hooks' +import { ID, SquareSizes } from '@audius/common/models' import { useLinkClickHandler } from 'react-router' +import { preload } from 'utils/image' + import featuredLines from '../assets/featured-lines.svg?url' import styles from './FeaturedContests2026.module.css' @@ -30,10 +34,19 @@ function ContestCard({ id: ID setRenderPublicSite: (v: boolean) => void }) { + const [imageLoaded, setImageLoaded] = useState(false) const { data: contest, isPending: contestPending } = useRemixContest(id) const { data: track, isPending: trackPending } = useTrack(contest?.entityId) const { data: user, isPending: userPending } = useUser(track?.owner_id) + const artwork = track?.artwork + const { imageUrl, onError } = useImageSize({ + artwork, + targetSize: SquareSizes.SIZE_480_BY_480, + defaultImage: imageBlank as string, + preloadImageFn: preload + }) + const isPending = contestPending || trackPending || userPending || !track const permalink = track?.permalink ?? '' const handleNavigate = useLinkClickHandler(permalink) @@ -43,6 +56,8 @@ function ContestCard({ handleNavigate(e as MouseEvent) } + const showImage = imageUrl != null && imageUrl !== (imageBlank as string) + if (isPending) { return (
@@ -52,10 +67,6 @@ function ContestCard({ ) } - const artworkUrl = - (track?.artwork && (track.artwork as Record)['480x480']) ?? - null - return ( - +
diff --git a/packages/web/src/public-site/pages/landing-2026/components/GrowthStartsHere.module.css b/packages/web/src/public-site/pages/landing-2026/components/GrowthStartsHere.module.css index d03ac045415..abf6461afa4 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/GrowthStartsHere.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/GrowthStartsHere.module.css @@ -77,9 +77,12 @@ } .visualWrap { - width: 612px; - flex-shrink: 0; + max-width: 612px; + width: 100%; + flex: 0 1 612px; + min-width: 0; align-self: stretch; + max-height: min(480px, 55vh); overflow: hidden; } diff --git a/packages/web/src/public-site/pages/landing-2026/components/Hero2026.module.css b/packages/web/src/public-site/pages/landing-2026/components/Hero2026.module.css index 916abe3a507..d1aecb1de47 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/Hero2026.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/Hero2026.module.css @@ -2,8 +2,6 @@ position: relative; width: 100%; height: 100vh; - min-height: 700px; - max-height: 1024px; display: flex; align-items: center; justify-content: center; @@ -67,7 +65,10 @@ background: transparent; cursor: pointer; text-decoration: none; - transition: box-shadow 0.25s ease; + transition: + box-shadow 0.25s ease, + background 0.2s ease, + border-color 0.2s ease; box-sizing: border-box; } @@ -79,6 +80,17 @@ inset -4px 0px 4px 0px rgba(175, 72, 255, 0.25); } +/* Figma 2738-4495: press state */ +.ctaButton:active { + background: rgba(53, 20, 75, 0.85); + border-color: rgba(175, 72, 255, 0.9); + box-shadow: none; +} + +.ctaButton:active .ctaLabel { + color: #e7c7ff; +} + .ctaLabel { font-family: 'Urbanist', sans-serif; font-weight: 700; @@ -87,13 +99,12 @@ color: #fff; text-transform: capitalize; white-space: nowrap; + transition: color 0.2s ease; } @media (max-width: 800px) { .section { - height: 790px; - min-height: 790px; - max-height: none; + height: 100vh; } .contentWrap { diff --git a/packages/web/src/public-site/pages/landing-2026/components/Nav2026.module.css b/packages/web/src/public-site/pages/landing-2026/components/Nav2026.module.css index f5bbe1ca551..ee749f2bb81 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/Nav2026.module.css +++ b/packages/web/src/public-site/pages/landing-2026/components/Nav2026.module.css @@ -19,7 +19,9 @@ justify-content: space-between; max-width: 1200px; width: 100%; - padding: 24px 0; + height: 88px; + padding: 0; + box-sizing: border-box; } .logoLink { @@ -56,15 +58,16 @@ .resourcesButton { display: flex; align-items: center; - gap: 4px; + gap: 6px; + height: 40px; + padding: 0 12px; background: none; border: none; cursor: pointer; - padding: 0; font-family: 'Urbanist', sans-serif; font-weight: 700; - font-size: 14px; - line-height: 16px; + font-size: 16px; + line-height: 20px; color: #fff; text-transform: capitalize; } @@ -104,6 +107,30 @@ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); z-index: 200; overflow: hidden; + animation: dropdownFadeIn 0.2s ease-out; +} + +@keyframes dropdownFadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.dropdownClosing { + animation: dropdownFadeOut 0.2s ease-out forwards; + pointer-events: none; +} + +@keyframes dropdownFadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } } .dropdownItem { @@ -200,8 +227,8 @@ display: flex; align-items: center; justify-content: center; - width: 24px; - height: 24px; + width: 40px; + height: 40px; border-radius: 8px; color: #fff; transition: @@ -220,8 +247,8 @@ } .dropdownSocialIcon { - width: 20px; - height: 20px; + width: 28px; + height: 28px; display: flex; align-items: center; justify-content: center; @@ -240,13 +267,16 @@ align-items: center; justify-content: center; gap: 8px; - height: 48px; - padding: 12px 24px; + height: 40px; + padding: 10px 20px; border: 1px solid rgba(255, 255, 255, 0.5); background: transparent; cursor: pointer; text-decoration: none; - transition: box-shadow 0.25s ease; + transition: + box-shadow 0.25s ease, + background 0.2s ease, + border-color 0.2s ease; } .ctaButton:hover { @@ -257,14 +287,26 @@ inset -4px 0px 4px 0px rgba(175, 72, 255, 0.25); } +/* Figma 2738-4495: press state – dark purple bg, light purple text, vibrant purple border */ +.ctaButton:active { + background: rgba(53, 20, 75, 0.85); + border-color: rgba(175, 72, 255, 0.9); + box-shadow: none; +} + +.ctaButton:active .ctaLabel { + color: #e7c7ff; +} + .ctaLabel { font-family: 'Urbanist', sans-serif; font-weight: 700; - font-size: 18px; + font-size: 16px; line-height: 1; color: #fff; text-transform: capitalize; white-space: nowrap; + transition: color 0.2s ease; } .mobileMenuButton { @@ -399,14 +441,17 @@ align-items: center; justify-content: center; gap: 8px; - height: 48px; + height: 40px; width: 100%; - padding: 12px 24px; + padding: 10px 20px; border: 1px solid rgba(255, 255, 255, 0.5); background: transparent; cursor: pointer; text-decoration: none; - transition: box-shadow 0.25s ease; + transition: + box-shadow 0.25s ease, + background 0.2s ease, + border-color 0.2s ease; box-sizing: border-box; } @@ -418,13 +463,25 @@ inset -4px 0px 4px 0px rgba(175, 72, 255, 0.25); } +/* Figma 2738-4495: press state */ +.overlayCtaButton:active { + background: rgba(53, 20, 75, 0.85); + border-color: rgba(175, 72, 255, 0.9); + box-shadow: none; +} + +.overlayCtaButton:active .ctaLabel { + color: #e7c7ff; +} + @media (max-width: 800px) { .nav { padding: 0 24px; } .inner { - padding: 16px 0; + height: 72px; + padding: 0; } .resourcesButton { diff --git a/packages/web/src/public-site/pages/landing-2026/components/Nav2026.tsx b/packages/web/src/public-site/pages/landing-2026/components/Nav2026.tsx index 8205bf21e88..ff7672d2257 100644 --- a/packages/web/src/public-site/pages/landing-2026/components/Nav2026.tsx +++ b/packages/web/src/public-site/pages/landing-2026/components/Nav2026.tsx @@ -1,9 +1,10 @@ -import { +import React, { type ComponentType, MouseEvent, useState, useRef, - useEffect + useEffect, + useCallback } from 'react' import { route } from '@audius/common/utils' @@ -64,7 +65,7 @@ const MENU_ITEMS: { const SOCIAL_LINKS = [ { label: 'Instagram', - href: 'https://instagram.com/audiusmusic', + href: 'https://instagram.com/audius', Icon: IconInstagram }, { @@ -75,7 +76,7 @@ const SOCIAL_LINKS = [ { label: 'TikTok', href: 'https://tiktok.com/@audius', Icon: IconTikTok }, { label: 'X (Twitter)', - href: 'https://twitter.com/audiusproject', + href: 'https://twitter.com/audius', Icon: IconX } ] @@ -90,21 +91,39 @@ export const Nav2026 = (props: Nav2026Props) => { const { isMobile, setRenderPublicSite } = props const navigate = useNavigate() const [isDropdownOpen, setIsDropdownOpen] = useState(false) + const [isDropdownClosing, setIsDropdownClosing] = useState(false) const [isMobileOverlayOpen, setIsMobileOverlayOpen] = useState(false) const dropdownRef = useRef(null) + const startCloseDropdown = useCallback(() => { + if (!isDropdownOpen) return + setIsDropdownClosing(true) + }, [isDropdownOpen]) + + const finishCloseDropdown = () => { + setIsDropdownOpen(false) + setIsDropdownClosing(false) + } + useEffect(() => { const handleClickOutside = (e: globalThis.MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(e.target as Node) ) { - setIsDropdownOpen(false) + startCloseDropdown() } } - document.addEventListener('mousedown', handleClickOutside) - return () => document.removeEventListener('mousedown', handleClickOutside) - }, []) + document.addEventListener('click', handleClickOutside) + return () => document.removeEventListener('click', handleClickOutside) + }, [isDropdownOpen, startCloseDropdown]) + + /* When dropdown unmounts while "closing", ensure we reset so it can re-open (e.g. if animationend never fired) */ + useEffect(() => { + if (!isDropdownClosing) return + const id = window.setTimeout(finishCloseDropdown, 300) + return () => window.clearTimeout(id) + }, [isDropdownClosing]) useEffect(() => { if (isMobileOverlayOpen) { @@ -165,8 +184,14 @@ export const Nav2026 = (props: Nav2026Props) => { - {isDropdownOpen ? ( + {isDropdownOpen || isDropdownClosing ? ( setIsDropdownOpen(false)} + onClose={startCloseDropdown} + onClosingComplete={finishCloseDropdown} + isClosing={isDropdownClosing} /> ) : null} @@ -297,11 +324,15 @@ function MobileNavOverlay({ function ResourcesDropdown({ setRenderPublicSite, navigate, - onClose + onClose, + onClosingComplete, + isClosing }: { setRenderPublicSite: (v: boolean) => void navigate: ReturnType onClose: () => void + onClosingComplete: () => void + isClosing: boolean }) { const handleItemClick = (href: string) => (e: MouseEvent) => { onClose() @@ -313,8 +344,18 @@ function ResourcesDropdown({ } } + const handleAnimationEnd = (e: React.AnimationEvent) => { + if (e.animationName === 'dropdownFadeOut' && isClosing) { + onClosingComplete() + } + } + return ( -
+
{MENU_ITEMS.map((item) => (