diff --git a/public/assets/ark-library/figma/official-recommendation-1.png b/public/assets/ark-library/figma/official-recommendation-1.png index c4a1763..1b42521 100644 Binary files a/public/assets/ark-library/figma/official-recommendation-1.png and b/public/assets/ark-library/figma/official-recommendation-1.png differ diff --git a/public/assets/ark-library/figma/official-recommendation-2.png b/public/assets/ark-library/figma/official-recommendation-2.png index 44aae48..1b42521 100644 Binary files a/public/assets/ark-library/figma/official-recommendation-2.png and b/public/assets/ark-library/figma/official-recommendation-2.png differ diff --git a/public/assets/ark-library/figma/official-recommendation-3.png b/public/assets/ark-library/figma/official-recommendation-3.png index e54eb15..1b42521 100644 Binary files a/public/assets/ark-library/figma/official-recommendation-3.png and b/public/assets/ark-library/figma/official-recommendation-3.png differ diff --git a/public/assets/ark-library/figma/official-recommendation-4.png b/public/assets/ark-library/figma/official-recommendation-4.png index 5a9c266..1b42521 100644 Binary files a/public/assets/ark-library/figma/official-recommendation-4.png and b/public/assets/ark-library/figma/official-recommendation-4.png differ diff --git a/public/assets/ark-library/figma/official-recommendation-5.png b/public/assets/ark-library/figma/official-recommendation-5.png index 808f689..1b42521 100644 Binary files a/public/assets/ark-library/figma/official-recommendation-5.png and b/public/assets/ark-library/figma/official-recommendation-5.png differ diff --git a/public/assets/ark-library/figma/official-recommendation-cover.png b/public/assets/ark-library/figma/official-recommendation-cover.png new file mode 100644 index 0000000..bf0fef6 Binary files /dev/null and b/public/assets/ark-library/figma/official-recommendation-cover.png differ diff --git a/public/assets/ark-library/media/svg/community.svg b/public/assets/ark-library/media/svg/community.svg index 0caa0bb..14e19f1 100644 --- a/public/assets/ark-library/media/svg/community.svg +++ b/public/assets/ark-library/media/svg/community.svg @@ -1,9 +1,9 @@ - - + + - - - - + + + + diff --git a/public/assets/ark-library/media/svg/directory.svg b/public/assets/ark-library/media/svg/directory.svg index 6a15d0a..6104944 100644 --- a/public/assets/ark-library/media/svg/directory.svg +++ b/public/assets/ark-library/media/svg/directory.svg @@ -1,14 +1,9 @@ - - - + + - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/education.svg b/public/assets/ark-library/media/svg/education.svg index 3627fda..16c087f 100644 --- a/public/assets/ark-library/media/svg/education.svg +++ b/public/assets/ark-library/media/svg/education.svg @@ -1,19 +1,9 @@ - - - - + + - - - - - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/educational-clips.svg b/public/assets/ark-library/media/svg/educational-clips.svg index 6e46062..04923f3 100644 --- a/public/assets/ark-library/media/svg/educational-clips.svg +++ b/public/assets/ark-library/media/svg/educational-clips.svg @@ -1,9 +1,9 @@ - - + + - - - - + + + + diff --git a/public/assets/ark-library/media/svg/everyday-class.svg b/public/assets/ark-library/media/svg/everyday-class.svg index 4e8ae41..0dea40e 100644 --- a/public/assets/ark-library/media/svg/everyday-class.svg +++ b/public/assets/ark-library/media/svg/everyday-class.svg @@ -1,24 +1,9 @@ - - - - - + + - - - - - - - - - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/general.svg b/public/assets/ark-library/media/svg/general.svg index 4095e09..74ef72e 100644 --- a/public/assets/ark-library/media/svg/general.svg +++ b/public/assets/ark-library/media/svg/general.svg @@ -1,14 +1,9 @@ - - - - + + - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/gift.svg b/public/assets/ark-library/media/svg/gift.svg index 06107d3..b62ec57 100644 --- a/public/assets/ark-library/media/svg/gift.svg +++ b/public/assets/ark-library/media/svg/gift.svg @@ -1,24 +1,9 @@ - - - - - - + + - - - - - - - - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/global-news.svg b/public/assets/ark-library/media/svg/global-news.svg index 09240bd..11b1de8 100644 --- a/public/assets/ark-library/media/svg/global-news.svg +++ b/public/assets/ark-library/media/svg/global-news.svg @@ -1,14 +1,9 @@ - - - - + + - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/guidelines.svg b/public/assets/ark-library/media/svg/guidelines.svg index 563d982..64f162d 100644 --- a/public/assets/ark-library/media/svg/guidelines.svg +++ b/public/assets/ark-library/media/svg/guidelines.svg @@ -1,24 +1,9 @@ - - - - - - + + - - - - - - - - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/news-record.svg b/public/assets/ark-library/media/svg/news-record.svg index 4e93c95..b42debe 100644 --- a/public/assets/ark-library/media/svg/news-record.svg +++ b/public/assets/ark-library/media/svg/news-record.svg @@ -1,9 +1,9 @@ - - + + - - - - + + + + diff --git a/public/assets/ark-library/media/svg/official-announcement.svg b/public/assets/ark-library/media/svg/official-announcement.svg index 3d9f46f..3d7f572 100644 --- a/public/assets/ark-library/media/svg/official-announcement.svg +++ b/public/assets/ark-library/media/svg/official-announcement.svg @@ -1,24 +1,9 @@ - - - - - + + - - - - - - - - - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/poster.svg b/public/assets/ark-library/media/svg/poster.svg index b52c12c..9192b84 100644 --- a/public/assets/ark-library/media/svg/poster.svg +++ b/public/assets/ark-library/media/svg/poster.svg @@ -1,15 +1,9 @@ - - - - + + - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/project-details.svg b/public/assets/ark-library/media/svg/project-details.svg index 466782f..1d8c819 100644 --- a/public/assets/ark-library/media/svg/project-details.svg +++ b/public/assets/ark-library/media/svg/project-details.svg @@ -1,14 +1,9 @@ - - - + + - - - - - - - - + + + + diff --git a/public/assets/ark-library/media/svg/videos.svg b/public/assets/ark-library/media/svg/videos.svg index d49559f..e45eb0a 100644 --- a/public/assets/ark-library/media/svg/videos.svg +++ b/public/assets/ark-library/media/svg/videos.svg @@ -1,14 +1,9 @@ - - - - + + - - - - - - - + + + + diff --git a/src/App.tsx b/src/App.tsx index 6d2ad9f..14cfb01 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,8 @@ import { I18nProvider } from "./i18n"; import { PublicLayout } from "./layouts/PublicLayout"; import { Home } from "./pages/Home"; import { Browse } from "./pages/Browse"; -import { CategoryPage } from "./pages/Category"; +import { CategoriesPage } from "./pages/Categories"; +import { OfficialRecommendationsPage } from "./pages/OfficialRecommendations"; import { SearchPage } from "./pages/Search"; import { PostRedirect } from "./pages/PostRedirect"; import { AboutPage } from "./pages/About"; @@ -27,7 +28,15 @@ export default function App() { }> } /> } /> - } /> + } /> + } + /> + } + /> } /> } /> } /> diff --git a/src/components/FigmaBanner.tsx b/src/components/FigmaBanner.tsx index 437c8cf..35d03a4 100644 --- a/src/components/FigmaBanner.tsx +++ b/src/components/FigmaBanner.tsx @@ -5,15 +5,13 @@ import { useState, type PointerEvent as ReactPointerEvent, } from "react"; +import { assetUrl, getJSON, itemsOrEmpty } from "../api"; +import { langQuery, useI18n, type Lang } from "../i18n"; const FIGMA_ASSET_BASE = "/assets/ark-library/figma"; export const officialRecommendationCoverFallbacks = [ - `${FIGMA_ASSET_BASE}/official-recommendation-1.png`, - `${FIGMA_ASSET_BASE}/official-recommendation-2.png`, - `${FIGMA_ASSET_BASE}/official-recommendation-3.png`, - `${FIGMA_ASSET_BASE}/official-recommendation-4.png`, - `${FIGMA_ASSET_BASE}/official-recommendation-5.png`, + `${FIGMA_ASSET_BASE}/official-recommendation-cover.png`, ] as const; type BannerSlide = { @@ -21,42 +19,46 @@ type BannerSlide = { mobile: string; desktop: string; alt: string; + linkUrl?: string; }; -const BANNERS_BASE = "/assets/ark-library/banners"; +type BannerApiItem = { + id: number | string; + imageUrl: string; + linkUrl?: string; + sortOrder?: number; +}; -const BANNER_SLIDES: BannerSlide[] = [ - { - id: "ark-banner-1", - mobile: `${BANNERS_BASE}/banner-1.png`, - desktop: `${BANNERS_BASE}/banner-1.png`, - alt: "", - }, - { - id: "ark-banner-2", - mobile: `${BANNERS_BASE}/banner-2.png`, - desktop: `${BANNERS_BASE}/banner-2.png`, - alt: "", - }, - { - id: "ark-banner-3", - mobile: `${BANNERS_BASE}/banner-3.png`, - desktop: `${BANNERS_BASE}/banner-3.png`, - alt: "", - }, - { - id: "ark-banner-4", - mobile: `${BANNERS_BASE}/banner-4.png`, - desktop: `${BANNERS_BASE}/banner-4.png`, - alt: "", - }, -]; +type BannerApiResponse = { + items?: BannerApiItem[] | null; +}; const AUTOPLAY_MS = 3000; const RESUME_AFTER_INTERACTION_MS = 8000; +function bannerLangParam(lang: Lang): string { + return langQuery(lang).toLowerCase(); +} + +function toSlides(items: BannerApiItem[]): BannerSlide[] { + return [...items] + .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) + .filter((item) => item.imageUrl) + .map((item) => { + const imageUrl = assetUrl(item.imageUrl); + return { + id: String(item.id), + mobile: imageUrl, + desktop: imageUrl, + alt: "", + linkUrl: item.linkUrl || undefined, + }; + }); +} + export function FigmaBanner() { - const slides = BANNER_SLIDES; + const { lang } = useI18n(); + const [slides, setSlides] = useState([]); const scrollerRef = useRef(null); const [activeIndex, setActiveIndex] = useState(0); const [autoplayPaused, setAutoplayPaused] = useState(false); @@ -69,6 +71,22 @@ export function FigmaBanner() { } | null>(null); const hasMultiple = slides.length > 1; + useEffect(() => { + let cancelled = false; + setActiveIndex(0); + getJSON(`/api/banners?lang=${bannerLangParam(lang)}`) + .then((res) => { + if (cancelled) return; + setSlides(toSlides(itemsOrEmpty(res.items))); + }) + .catch(() => { + if (!cancelled) setSlides([]); + }); + return () => { + cancelled = true; + }; + }, [lang]); + const goTo = useCallback((index: number, behavior: ScrollBehavior) => { const scroller = scrollerRef.current; if (!scroller) return; @@ -172,8 +190,41 @@ export function FigmaBanner() { } }; + if (slides.length === 0) return null; + + const pagination = hasMultiple ? ( +
+ {slides.map((slide, index) => { + const active = index === activeIndex; + return ( +
+ ) : null; + return ( -
+
- {slides.map((slide, index) => ( -
- + {slides.map((slide, index) => { + const image = ( + {slide.alt} -
- ))} + ); + + return ( +
+ {slide.linkUrl ? ( + + {image} + + ) : ( + image + )} +
+ ); + })}
{hasMultiple ? ( -
- {slides.map((slide, index) => { - const active = index === activeIndex; - return ( -
+ <> +
+
{pagination}
+
+
{pagination}
+ ) : null}
); diff --git a/src/components/RecommendedCard.tsx b/src/components/RecommendedCard.tsx index 9498061..6f8c122 100644 --- a/src/components/RecommendedCard.tsx +++ b/src/components/RecommendedCard.tsx @@ -5,6 +5,7 @@ import { assetUrl } from "../api"; import { useI18n } from "../i18n"; import { useMemo, useState } from "react"; import { formatDateYmd } from "../utils/format"; +import { DownloadCloudIcon } from "./icons/DownloadCloudIcon"; import { officialRecommendationCoverFallbacks } from "./FigmaBanner"; import { downloadAttachment, @@ -16,7 +17,7 @@ function isPlaceholderAsset(path: string | undefined | null) { } const CARD_CLASS = - "group flex w-[232px] shrink-0 flex-col overflow-hidden rounded-xl border border-ark-line bg-ark-panel transition hover:border-ark-gold/55 max-[439px]:w-[232px] min-[440px]:w-[230px] sm:w-[240px] lg:w-[246.4px] min-[1100px]:max-xl:w-[273px] xl:w-[246.4px]"; + "group flex w-[208px] shrink-0 flex-col overflow-hidden rounded-xl border bg-[#1D1E23] transition hover:border-ark-gold/55 md:w-[240px] lg:w-[246.4px] min-[1100px]:max-xl:w-[273px] xl:w-[246.4px]"; type RecommendedResource = Resource & { downloadPostId?: string; @@ -26,98 +27,158 @@ type RecommendedResource = Resource & { export function RecommendedCard({ r, visualIndex = 0, + useFigmaDesign = false, }: { r: RecommendedResource; visualIndex?: number; + useFigmaDesign?: boolean; }) { const { t } = useI18n(); const [isDownloading, setIsDownloading] = useState(false); + const figmaCover = + officialRecommendationCoverFallbacks[ + visualIndex % officialRecommendationCoverFallbacks.length + ]; const cover = useMemo(() => { const original = r.coverImage || r.previewUrl; if (isPlaceholderAsset(original)) { - return officialRecommendationCoverFallbacks[ - visualIndex % officialRecommendationCoverFallbacks.length - ]; + return useFigmaDesign ? "" : figmaCover; } return assetUrl(original); - }, [r.coverImage, r.previewUrl, visualIndex]); + }, [figmaCover, r.coverImage, r.previewUrl, useFigmaDesign]); + const displayTitle = r.title; + const displayCategoryName = r.categoryName; const dateStr = formatDateYmd(r.updatedAt); + const dateTime = r.updatedAt; const dl = r.isDownloadable && (r.fileUrl || r.previewUrl) ? assetUrl(r.fileUrl || r.previewUrl) : ""; + const handleDownload = async () => { + if (isDownloading) return; + setIsDownloading(true); + try { + if (r.downloadPostId && r.downloadAttachmentId) { + await downloadAttachment( + r.downloadPostId, + r.downloadAttachmentId, + displayTitle, + ); + return; + } + await downloadFile(dl, displayTitle); + } catch { + /* ignore */ + } finally { + setIsDownloading(false); + } + }; + return ( -
+
{cover ? ( ) : (
)} - {r.badgeLabel ? ( + {!useFigmaDesign && r.badgeLabel ? ( {r.badgeLabel} ) : null} -
- +
+ + {displayTitle} + + {useFigmaDesign ? ( +
+ {displayCategoryName} +
+ ) : null} +
+
- {r.title} - -
-
- {r.categoryName} - · - +
+ {useFigmaDesign ? null : ( + <> + {displayCategoryName} + · + + )} +
{dl ? ( -
-
- {middleEllipsisFilename(displayFilename)} -
-
- {isDownloading ? t("downloading") : formatBytes(att.sizeBytes)} -
-
); } @@ -80,12 +70,12 @@ export function FileDocBubble({ post }: { post: Post }) { const { lang } = useI18n(); const text = postDisplayText(post, lang); return ( -
+
{post.attachments.map((att) => ( ))} {text ? ( -
+
{text}
) : null} diff --git a/src/components/messageStream/bubbles/VideoBubble.tsx b/src/components/messageStream/bubbles/VideoBubble.tsx index c553eee..6ec6fb3 100644 --- a/src/components/messageStream/bubbles/VideoBubble.tsx +++ b/src/components/messageStream/bubbles/VideoBubble.tsx @@ -77,15 +77,23 @@ function VideoAttachmentCard({ }} > {playing && !compact ? ( -