From 27744272ecbf999f1ab1be19a09aeb06f9eb4d5c Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Sat, 19 Apr 2025 14:19:16 -0700 Subject: the moekyun special update --- site/public/xiatian.webp | Bin 0 -> 54906 bytes site/src/components/GameNotes.tsx | 17 +++ site/src/components/NewsFeed.tsx | 214 +++++++++++++------------------------- site/src/components/TitleBar.tsx | 124 +++++++++++++++------- site/src/index.css | 3 +- site/src/pages/Homepage.tsx | 40 +++++-- 6 files changed, 204 insertions(+), 194 deletions(-) create mode 100644 site/public/xiatian.webp create mode 100644 site/src/components/GameNotes.tsx diff --git a/site/public/xiatian.webp b/site/public/xiatian.webp new file mode 100644 index 0000000..82ffb15 Binary files /dev/null and b/site/public/xiatian.webp differ diff --git a/site/src/components/GameNotes.tsx b/site/src/components/GameNotes.tsx new file mode 100644 index 0000000..0a72d01 --- /dev/null +++ b/site/src/components/GameNotes.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export const GameNotes = (isMoe: boolean): Record => ({ + sdvx: ( + <> + +

+ Official e-amusement service in NA available only at Round1 USA +
+ Cabinets in Canada/Europe/Australia are on non-official private networks which are running older data +

+ + ), +}); diff --git a/site/src/components/NewsFeed.tsx b/site/src/components/NewsFeed.tsx index 0a131a4..a5507a3 100644 --- a/site/src/components/NewsFeed.tsx +++ b/site/src/components/NewsFeed.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { getGameTitle } from "../utils.ts"; +import { useSearchParams } from "react-router-dom"; export interface NewsData { date: string; @@ -22,197 +23,122 @@ interface NewsFeedProps { } export const NewsFeed: React.FC = ({ newsItems }) => { - // Track which items are showing English content const [showEnglish, setShowEnglish] = useState>({}); - // Track which items are expanded beyond the preview const [expanded, setExpanded] = useState>({}); - // Track the current image index for each news item const [currentImageIndex, setCurrentImageIndex] = useState>({}); - // Track loading state for images const [loadingImages, setLoadingImages] = useState>({}); - - const toggleLanguage = (itemId: string) => { - setShowEnglish((prev) => ({ ...prev, [itemId]: !prev[itemId] })); - }; - - const toggleExpand = (itemId: string) => { - setExpanded((prev) => ({ ...prev, [itemId]: !prev[itemId] })); - }; - - const changeImage = (itemId: string, index: number) => { - setCurrentImageIndex((prev) => ({ ...prev, [itemId]: index })); - setLoadingImages((prev) => ({ ...prev, [itemId]: true })); // Set loading state for the image + const [searchParams] = useSearchParams(); + const isMoe = searchParams.has("moe"); + + const toggleLanguage = (id: string) => setShowEnglish((prev) => ({ ...prev, [id]: !prev[id] })); + const toggleExpand = (id: string) => setExpanded((prev) => ({ ...prev, [id]: !prev[id] })); + const changeImage = (id: string, i: number) => { + setCurrentImageIndex((p) => ({ ...p, [id]: i })); + setLoadingImages((p) => ({ ...p, [id]: true })); }; - - const handleImageLoad = (itemId: string) => { - setLoadingImages((prev) => ({ ...prev, [itemId]: false })); // Clear loading state when image loads - }; - + const handleImageLoad = (id: string) => setLoadingImages((p) => ({ ...p, [id]: false })); const PREVIEW_CHAR_LIMIT = 600; return ( -
+
{newsItems.map((news) => { - const formattedDate = new Date(news.timestamp * 1000).toLocaleDateString("ja-JP", { - year: "numeric", - month: "2-digit", - day: "2-digit", - }); - - const newsId = `${news.identifier}-${news.timestamp}-${news.content.substring(0, 20)}`; + const date = new Date(news.timestamp * 1000).toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" }); + const contentHash = news.content.split('').reduce((hash, char) => ((hash << 5) + hash) + char.charCodeAt(0), 5381) >>> 0; + const newsId = `${news.identifier}-${news.timestamp}-${contentHash.toString(16)}-${news.headline}`; const isEnglish = !!showEnglish[newsId]; const hasTranslation = news.en_headline || news.en_content; - const displayHeadline = isEnglish && news.en_headline ? news.en_headline : news.headline; - const displayContent = isEnglish && news.en_content ? news.en_content! : news.content; - - // Read‑more logic + const displayContent = isEnglish && news.en_content ? news.en_content : news.content; const isLong = displayContent.length > PREVIEW_CHAR_LIMIT; const isExpanded = !!expanded[newsId]; - const contentToShow = isLong && !isExpanded - ? displayContent.slice(0, PREVIEW_CHAR_LIMIT) + "…" - : displayContent; + const contentToShow = isLong && !isExpanded ? displayContent.slice(0, PREVIEW_CHAR_LIMIT) + "…" : displayContent; return ( -
- {/* Header (Game Icon + Info) */} +
-
+
{news.identifier.charAt(0)}
- - {getGameTitle(news.identifier)} - - {formattedDate} - {news.type && ( - {news.type} - )} + {getGameTitle(news.identifier)} + {date} + {news.type && {news.type}}
{hasTranslation && ( - )}
- {/* Content Area */}
- {displayHeadline && ( -

{displayHeadline}

- )} -

+ {displayHeadline &&

{displayHeadline}

} +

{contentToShow.split(/(\[.*?\]\(.*?\)|https?:\/\/[^\s]+)/g).map((part, idx) => { const m = part.match(/\[(.*?)\]\((.*?)\)/); const u = part.match(/https?:\/\/[^\s]+/); - if (m) { - return ( - - {m[1]} - - ); - } else if (u) { - return ( - - {u[0]} - - ); - } + if (m) return {m[1]}; + if (u) return {u[0]}; return part; })}

- {isLong && ( - )}
{/* Images */} -
- {news.images.length > 0 && ( - <> - {/* Display only the current image */} - {(() => { - const currentIdx = currentImageIndex[newsId] || 0; - const img = news.images[currentIdx]; - - return ( -
- {loadingImages[newsId] && ( -
-
-
- )} - news visual 0 && ( +
+ {(() => { + const idx = currentImageIndex[newsId] || 0; + const img = news.images[idx]; + return ( +
+ {loadingImages[newsId] && ( +
+
+
+ )} + news visual handleImageLoad(newsId)} + /> +
+ ); + })()} + + {news.images.length > 1 && ( +
+
+ {news.images.map((_, idx) => ( +
- ); - })()} - - {/* Image selector buttons with horizontal scroll for small screens */} - {news.images.length > 1 && ( -
-
- {news.images.map((_, idx) => ( - - ))} -
+ > + {idx + 1} + + ))}
- )} - - )} -
+
+ )} +
+ )} - {/* Footer */} {news.url && ( -
- + diff --git a/site/src/components/TitleBar.tsx b/site/src/components/TitleBar.tsx index 33963b7..b20beae 100644 --- a/site/src/components/TitleBar.tsx +++ b/site/src/components/TitleBar.tsx @@ -1,14 +1,57 @@ -import { Link } from "react-router-dom"; import { useState, useEffect, useRef } from "react"; +import { + Link, + useSearchParams, + useNavigate, + useLocation, +} from "react-router-dom"; interface GameCategory { name: string; games: { id: string; title: string }[]; } - const TitleBar: React.FC = () => { const [dropdownOpen, setDropdownOpen] = useState(false); const dropdownRef = useRef(null); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const location = useLocation(); + const isMoe = searchParams.has("moe"); + + const toggleTheme = () => { + const params = new URLSearchParams(searchParams); + + if (isMoe) { + params.delete("moe"); + localStorage.setItem("theme", "dark"); + } else { + params.set("moe", ""); + localStorage.setItem("theme", "light"); + } + + navigate(`${location.pathname}?${params.toString()}`); + }; + + + useEffect(() => { + const savedTheme = localStorage.getItem("theme"); + + const hasMoe = searchParams.has("moe"); + + if (!hasMoe && savedTheme === "light") { + const params = new URLSearchParams(searchParams); + params.set("moe", ""); + navigate(`${location.pathname}?${params.toString()}`, { replace: true }); + } + + if (hasMoe && savedTheme === "dark") { + const params = new URLSearchParams(searchParams); + params.delete("moe"); + navigate(`${location.pathname}?${params.toString()}`, { replace: true }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [location.pathname, navigate]); + const gameCategories: GameCategory[] = [ { @@ -16,11 +59,11 @@ const TitleBar: React.FC = () => { games: [ { id: "iidx", title: "beatmania IIDX" }, { id: "sdvx", title: "SOUND VOLTEX" }, - { id: "ddr", title: "DDR"}, - { id: "jubeat", title: "jubeat"}, - { id: "popn_music", title: "pop'n music"}, - { id: "nostalgia", title: "NOSTALGIA"}, - { id: "gitadora", title: "GITADORA"} + { id: "ddr", title: "DDR" }, + { id: "jubeat", title: "jubeat" }, + { id: "popn_music", title: "pop'n music" }, + { id: "nostalgia", title: "NOSTALGIA" }, + { id: "gitadora", title: "GITADORA" }, ], }, { @@ -35,15 +78,11 @@ const TitleBar: React.FC = () => { }, { name: "TAITO", - games: [ - { id: "music_diver", title: "MUSIC DIVER" }, - ], + games: [{ id: "music_diver", title: "MUSIC DIVER" }], }, { name: "BANDAI NAMCO", - games: [ - { id: "taiko", title: "TAIKO" }, - ], + games: [{ id: "taiko", title: "TAIKO" }], }, ]; @@ -56,46 +95,53 @@ const TitleBar: React.FC = () => { setDropdownOpen(false); } }; - - if (dropdownOpen) { + if (dropdownOpen) document.addEventListener("mousedown", handleClickOutside); - } - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; + return () => document.removeEventListener("mousedown", handleClickOutside); }, [dropdownOpen]); return ( -
+
+ 573 Updates Logo -
+
573
- - {/* Site Title */} - + UPDATES
- {/* Navigation Section */}
- + All Games - {/* Dropdown Menu */}
{dropdownOpen && ( -
+
{gameCategories.map((category, index) => (
-
+
{category.name}
3 - ? "grid grid-cols-1 sm:grid-cols-2 gap-x-2 gap-y-0.5" - : "space-y-0.5" - }`} + className={`${category.games.length > 3 ? "grid grid-cols-1 sm:grid-cols-2 gap-x-2 gap-y-0.5" : "space-y-0.5"}`} > {category.games.map((game) => ( setDropdownOpen(false)} > {game.title} diff --git a/site/src/index.css b/site/src/index.css index a461c50..bb66fd9 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -1 +1,2 @@ -@import "tailwindcss"; \ No newline at end of file +@import url('https://fonts.googleapis.com/css2?family=Zen+Maru+Gothic:wght@400;700&display=swap'); +@import "tailwindcss"; diff --git a/site/src/pages/Homepage.tsx b/site/src/pages/Homepage.tsx index 7183ebc..6cee80a 100644 --- a/site/src/pages/Homepage.tsx +++ b/site/src/pages/Homepage.tsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; import { NewsData, NewsFeed } from "../components/NewsFeed"; -import { useParams } from "react-router-dom"; +import { useParams, useSearchParams } from "react-router-dom"; import { getGameTitle } from "../utils.ts"; import TitleBar from "../components/TitleBar"; +import { GameNotes } from "../components/GameNotes"; + interface ArcadeNewsAPIData { fetch_time: number; @@ -11,6 +13,9 @@ interface ArcadeNewsAPIData { export default function Home() { const { gameId } = useParams<{ gameId?: string }>(); + const [searchParams] = useSearchParams(); + const isMoe = searchParams.has("moe"); + const [newsFeedData, setNewsFeedData] = useState(null); const [loading, setLoading] = useState(true); @@ -48,19 +53,34 @@ export default function Home() { return ( <> -
+
- {gameId && ( -

- {getGameTitle(gameId)} News -

+ {gameId ? ( +
+

+ {getGameTitle(gameId)} News +

+ {GameNotes(isMoe)[gameId] &&
{GameNotes(isMoe)[gameId]}
} +
+ ) : ( +
+

Welcome to 573-UPDATES

+ +

News and Information for various arcade games is aggregated here!

+

+ Please see the{" "} + + GitHub + {" "} + for API information +

+
)}
-
-- cgit v1.2.3