From fd961d8c0b6b2fb0783fdec13c3323ed566a7805 Mon Sep 17 00:00:00 2001 From: coder-soft Date: Sun, 3 May 2026 15:25:32 +0500 Subject: [PATCH 1/3] Add music mood filter to resources page - Add filename field to Resource type for matching music files - Update normalizeApiResource to preserve filename from API - Create MusicMoodFilter component with search and list layout - Add music_moods.json data file with mood mappings - Position mood filter as left sidebar on music category view - Match design with site's pixel/blocky style (pixel-corners, etc.) --- public/data/music_moods.json | 282 +++++++++++++++++++ src/components/resources/MusicMoodFilter.tsx | 121 ++++++++ src/lib/api.ts | 1 + src/pages/ResourcesHub.tsx | 86 ++++-- src/types/music.ts | 5 + src/types/resources.ts | 1 + 6 files changed, 476 insertions(+), 20 deletions(-) create mode 100644 public/data/music_moods.json create mode 100644 src/components/resources/MusicMoodFilter.tsx create mode 100644 src/types/music.ts diff --git a/public/data/music_moods.json b/public/data/music_moods.json new file mode 100644 index 0000000..aac5f43 --- /dev/null +++ b/public/data/music_moods.json @@ -0,0 +1,282 @@ +[ + { + "filename": "welcome to chaos - ross bugden.mp3", + "moods": ["chaotic", "intense", "dark"], + "source": "web search" + }, + { + "filename": "unstoppable force.mp3", + "moods": ["heroic", "epic", "intense", "driving", "aggressive"], + "source": "web search" + }, + { + "filename": "undertale ost spear of justice.mp3", + "moods": ["fighting", "energetic", "boss battle", "powerful"], + "source": "web search" + }, + { + "filename": "totally not a rickroll.mp3", + "moods": ["happy", "energetic", "fun"], + "source": "filename inference" + }, + { + "filename": "toad factory - mario kart wii.mp3", + "moods": ["classic", "upbeat", "racing", "energetic"], + "source": "web search" + }, + { + "filename": "synthatiger - beyond the grid.mp3", + "moods": ["synthwave", "electronic", "retro", "energetic", "adventure"], + "source": "web search" + }, + { + "filename": "sparkling stars.mp3", + "moods": ["calm", "dreamy", "relaxing"], + "source": "filename inference" + }, + { + "filename": "shake down - jules gaia.mp3", + "moods": ["energetic", "driving"], + "source": "filename inference" + }, + { + "filename": "run fast instrumental.mp3", + "moods": ["energetic", "action", "upbeat", "motivational", "intense"], + "source": "web search" + }, + { + "filename": "quo vadis.mp3", + "moods": ["classic", "dramatic", "epic", "oratorio"], + "source": "web search" + }, + { + "filename": "qbedwars music.mp3", + "moods": ["gaming", "intense", "action"], + "source": "filename inference" + }, + { + "filename": "popcorn castle.mp3", + "moods": ["playful", "happy", "whimsical"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - shädöw.mp3", + "moods": ["modern", "trap", "intense"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - sega+.mp3", + "moods": ["modern", "trap", "energetic"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - prey.mp3", + "moods": ["modern", "trap", "aggressive"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - explosion.mp3", + "moods": ["modern", "trap", "intense", "aggressive"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - enough.mp3", + "moods": ["modern", "trap", "energetic"], + "source": "filename inference" + }, + { + "filename": "playboi carti x yeat type beat - black.mp3", + "moods": ["modern", "trap", "dark", "intense"], + "source": "filename inference" + }, + { + "filename": "orange marmalade.mp3", + "moods": ["playful", "happy", "fun"], + "source": "filename inference" + }, + { + "filename": "oh what a whirl - jules gaia.mp3", + "moods": ["energetic", "upbeat"], + "source": "filename inference" + }, + { + "filename": "new world.mp3", + "moods": ["adventure", "inspiring", "epic"], + "source": "filename inference" + }, + { + "filename": "move like this - jules gaia.mp3", + "moods": ["energetic", "upbeat", "groovy"], + "source": "filename inference" + }, + { + "filename": "local forecast - kevin macleod.mp3", + "moods": ["upbeat", "jazzy", "energetic", "sprightly", "danceable"], + "source": "web search" + }, + { + "filename": "king pig and his manic minions.mp3", + "moods": ["intense", "fun", "adventure"], + "source": "filename inference" + }, + { + "filename": "journey through the woods.mp3", + "moods": ["adventure", "calm", "mysterious"], + "source": "filename inference" + }, + { + "filename": "infraction - atlas.mp3", + "moods": ["epic", "cinematic", "intense"], + "source": "filename inference" + }, + { + "filename": "iby - dj b-duke.mp3", + "moods": ["electronic", "energetic"], + "source": "filename inference" + }, + { + "filename": "haunted mansion.mp3", + "moods": ["spooky", "mysterious", "eerie", "adventure"], + "source": "web search" + }, + { + "filename": "happy energetic boogie fun.mp3", + "moods": ["happy", "energetic", "fun", "playful", "bouncy"], + "source": "web search" + }, + { + "filename": "end of a long journey.mp3", + "moods": ["adventure", "emotional", "reflective"], + "source": "filename inference" + }, + { + "filename": "dramatic violin.mp3", + "moods": ["dramatic", "intense", "emotional"], + "source": "filename inference" + }, + { + "filename": "dragon castle___makai symphony - dragon castle_ is under a creative commons (by-nc 3.0) license_ @makai-symphony music powered by breakingcopyright_ • 🔥 epic battle music .mp3", + "moods": ["epic", "fighting", "intense", "orchestral"], + "source": "filename inference" + }, + { + "filename": "deer immaterial anbr adrian berenguer.mp3", + "moods": ["calm", "relaxing", "ambient"], + "source": "filename inference" + }, + { + "filename": "deep in the forest.mp3", + "moods": ["adventure", "calm", "mysterious"], + "source": "filename inference" + }, + { + "filename": "corneria.mp3", + "moods": ["classic", "adventure", "upbeat"], + "source": "filename inference" + }, + { + "filename": "cjbeards - fire and thunder.mp3", + "moods": ["epic", "intense", "dramatic"], + "source": "filename inference" + }, + { + "filename": "chased by fate.mp3", + "moods": ["suspense", "intense", "dramatic"], + "source": "filename inference" + }, + { + "filename": "chaos construct (by azali).mp3", + "moods": ["chaotic", "intense", "dark"], + "source": "filename inference" + }, + { + "filename": "camellia - the world revolving (camellia remix).mp3", + "moods": ["intense", "energetic", "electronic"], + "source": "filename inference" + }, + { + "filename": "break free.mp3", + "moods": ["epic", "inspiring", "adventure"], + "source": "filename inference" + }, + { + "filename": "blizzard peaks - sonic rush adventure.mp3", + "moods": ["adventure", "classic", "energetic"], + "source": "filename inference" + }, + { + "filename": "black mass.mp3", + "moods": ["dark", "ominous", "intense"], + "source": "filename inference" + }, + { + "filename": "battle of birds and pigs.mp3", + "moods": ["fighting", "fun", "adventure"], + "source": "filename inference" + }, + { + "filename": "battle against an empire.mp3", + "moods": ["epic", "fighting", "dramatic"], + "source": "filename inference" + }, + { + "filename": "background music.mp3", + "moods": ["ambient", "calm", "relaxing"], + "source": "filename inference" + }, + { + "filename": "azali x arcerion - zephaniah.mp3", + "moods": ["epic", "intense"], + "source": "filename inference" + }, + { + "filename": "at the speed of light.mp3", + "moods": ["fast", "energetic", "adventure"], + "source": "filename inference" + }, + { + "filename": "apassionato orchestral dramatic music.mp3", + "moods": ["classic", "dramatic", "epic"], + "source": "filename inference" + }, + { + "filename": "anthony vega - johnny vespa.mp3", + "moods": ["classic", "energetic", "adventure"], + "source": "filename inference" + }, + { + "filename": "adventurous.mp3", + "moods": ["adventure", "inspiring", "epic"], + "source": "filename inference" + }, + { + "filename": "action suspense.mp3", + "moods": ["suspense", "intense", "dramatic"], + "source": "filename inference" + }, + { + "filename": "action suspense 2.mp3", + "moods": ["suspense", "intense", "dramatic"], + "source": "filename inference" + }, + { + "filename": "ace - verdant green swans.mp3", + "moods": ["calm", "relaxing", "ambient"], + "source": "filename inference" + }, + { + "filename": "a shattered illusion.mp3", + "moods": ["dramatic", "emotional", "mysterious"], + "source": "filename inference" + }, + { + "filename": "Windmill Isle - Day.mp3", + "moods": ["classic", "adventure", "calm"], + "source": "filename inference" + }, + { + "filename": "Last Chance - Music for FlameFrags 0.mp3", + "moods": ["intense", "action", "gaming"], + "source": "filename inference" + } +] \ No newline at end of file diff --git a/src/components/resources/MusicMoodFilter.tsx b/src/components/resources/MusicMoodFilter.tsx new file mode 100644 index 0000000..6328c27 --- /dev/null +++ b/src/components/resources/MusicMoodFilter.tsx @@ -0,0 +1,121 @@ +import { useState, useMemo } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { IconSearch, IconX, IconMoodHappy } from '@tabler/icons-react'; +import { MusicMood } from '@/types/music'; + +interface MusicMoodFilterProps { + selectedMoods: string[]; + onMoodChange: (moods: string[]) => void; + moodsData: MusicMood[]; +} + +const MusicMoodFilter = ({ selectedMoods, onMoodChange, moodsData }: MusicMoodFilterProps) => { + const [moodSearch, setMoodSearch] = useState(''); + + const allMoods = useMemo(() => { + const moodSet = new Set(); + moodsData.forEach(item => { + item.moods.forEach(mood => moodSet.add(mood)); + }); + return Array.from(moodSet).sort(); + }, [moodsData]); + + const filteredMoods = useMemo(() => { + if (!moodSearch) return allMoods; + const searchLower = moodSearch.toLowerCase(); + return allMoods.filter(mood => mood.toLowerCase().includes(searchLower)); + }, [allMoods, moodSearch]); + + const toggleMood = (mood: string) => { + if (selectedMoods.includes(mood)) { + onMoodChange(selectedMoods.filter(m => m !== mood)); + } else { + onMoodChange([...selectedMoods, mood]); + } + }; + + const clearMoods = () => { + onMoodChange([]); + setMoodSearch(''); + }; + + if (allMoods.length === 0) return null; + + return ( +
+
+

+ + Mood Filter +

+ +
+ + setMoodSearch(e.target.value)} + className="pl-8 h-8 text-sm pixel-input" + /> + {moodSearch && ( + + )} +
+ + {selectedMoods.length > 0 && ( +
+ + {selectedMoods.length} selected + + +
+ )} +
+ + +
+ {filteredMoods.map(mood => ( +
toggleMood(mood)} + > + {mood} +
+ ))} + + {filteredMoods.length === 0 && moodSearch && ( +
+ No moods match your search +
+ )} +
+
+
+ ); +}; + +export default MusicMoodFilter; \ No newline at end of file diff --git a/src/lib/api.ts b/src/lib/api.ts index bd71875..09a72af 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -91,6 +91,7 @@ const normalizeApiResource = (item: ApiResource, category: string): Resource | n image_url: undefined, software: undefined, description: undefined, + filename: item.filename, }; }; diff --git a/src/pages/ResourcesHub.tsx b/src/pages/ResourcesHub.tsx index 3efa17d..efd7813 100644 --- a/src/pages/ResourcesHub.tsx +++ b/src/pages/ResourcesHub.tsx @@ -12,6 +12,7 @@ import ResourcesList from '@/components/resources/ResourcesList'; import FavoritesTab from '@/components/resources/FavoritesTab'; import CreatorPacksTab from '@/components/resources/CreatorPacksTab'; import MusicPacksTab from '@/components/resources/MusicPacksTab'; +import MusicMoodFilter from '@/components/resources/MusicMoodFilter'; import McSoundsBrowser from '@/components/resources/McSoundsBrowser'; import McIconsBrowser from '@/components/resources/McIconsBrowser'; import AuthDialog from '@/components/auth/AuthDialog'; @@ -33,6 +34,7 @@ const ResourcesHub = () => { const [showScrollTop, setShowScrollTop] = useState(false); const [authDialogOpen, setAuthDialogOpen] = useState(false); const [activeTab, setActiveTab] = useState<'resources' | 'favorites' | 'creator-packs' | 'music-packs'>('resources'); + const [selectedMoods, setSelectedMoods] = useState([]); const { @@ -66,6 +68,31 @@ const ResourcesHub = () => { const isMcSoundsView = selectedCategory === 'mcsounds'; const isMcIconsView = selectedCategory === 'minecraft-icons'; + const isMusicView = selectedCategory === 'music'; + + const [musicMoodsData, setMusicMoodsData] = useState>([]); + + useEffect(() => { + if (isMusicView && musicMoodsData.length === 0) { + fetch('/data/music_moods.json') + .then(res => res.json()) + .then(data => setMusicMoodsData(data)) + .catch(err => console.error('Failed to load music moods:', err)); + } + }, [isMusicView, musicMoodsData.length]); + + const moodFilteredResources = useMemo(() => { + if (!isMusicView || selectedMoods.length === 0) return filteredResources; + + const moodsMap = new Map(musicMoodsData.map(item => [item.filename.toLowerCase(), item.moods])); + + return filteredResources.filter(resource => { + const filename = (resource.filename || resource.title).toLowerCase(); + const resourceMoods = moodsMap.get(filename); + if (!resourceMoods) return false; + return selectedMoods.some(mood => resourceMoods.includes(mood)); + }); + }, [filteredResources, isMusicView, selectedMoods, musicMoodsData]); const mcsoundsResourceCount = useMemo(() => { if (!isMcSoundsView) return {}; @@ -108,6 +135,12 @@ const ResourcesHub = () => { }; }, []); + useEffect(() => { + if (selectedCategory !== 'music') { + setSelectedMoods([]); + } + }, [selectedCategory]); + useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const tabParam = urlParams.get('tab'); @@ -163,7 +196,7 @@ const ResourcesHub = () => { { exit={{ opacity: 0, x: 20 }} transition={{ duration: 0.3 }} > - {(isMcSoundsView || isMcIconsView) && !isMobile ? ( + {(isMcSoundsView || isMcIconsView || isMusicView) && !isMobile ? (
+ {isMusicView && ( +
+
+ +
+
+ )}
{renderContent()}
-
-
- {isMcSoundsView ? ( - - ) : ( - - )} + {(isMcSoundsView || isMcIconsView) && ( +
+
+ {isMcSoundsView ? ( + + ) : ( + + )} +
-
+ )}
) : (
diff --git a/src/types/music.ts b/src/types/music.ts new file mode 100644 index 0000000..78f9ae2 --- /dev/null +++ b/src/types/music.ts @@ -0,0 +1,5 @@ +export interface MusicMood { + filename: string; + moods: string[]; + source: string; +} diff --git a/src/types/resources.ts b/src/types/resources.ts index dcb45cf..3cd9c0c 100644 --- a/src/types/resources.ts +++ b/src/types/resources.ts @@ -15,6 +15,7 @@ export interface Resource { download_count?: number; created_at?: string; updated_at?: string; + filename?: string; } export const getResourceUrl = (resource: Resource): string => { From 4a4b08d74a0546e85fdfcd22c1ecec92536f2c30 Mon Sep 17 00:00:00 2001 From: coder-soft Date: Sun, 3 May 2026 15:31:25 +0500 Subject: [PATCH 2/3] Add mobile mood filter sheet for music category - Add Sheet component for mobile users to access mood filter - Toggle button shows filter count when moods are selected - Matches site's pixel/blocky styling (pixel-corners) --- src/pages/ResourcesHub.tsx | 40 +++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/pages/ResourcesHub.tsx b/src/pages/ResourcesHub.tsx index efd7813..99cbe83 100644 --- a/src/pages/ResourcesHub.tsx +++ b/src/pages/ResourcesHub.tsx @@ -17,7 +17,8 @@ import McSoundsBrowser from '@/components/resources/McSoundsBrowser'; import McIconsBrowser from '@/components/resources/McIconsBrowser'; import AuthDialog from '@/components/auth/AuthDialog'; import { Button } from '@/components/ui/button'; -import { IconArrowUp, IconHeart, IconSearch, IconPackage, IconMusic } from '@tabler/icons-react'; +import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import { IconArrowUp, IconHeart, IconSearch, IconPackage, IconMusic, IconMoodHappy, IconFilter } from '@tabler/icons-react'; import { Helmet } from "react-helmet-async"; @@ -35,6 +36,7 @@ const ResourcesHub = () => { const [authDialogOpen, setAuthDialogOpen] = useState(false); const [activeTab, setActiveTab] = useState<'resources' | 'favorites' | 'creator-packs' | 'music-packs'>('resources'); const [selectedMoods, setSelectedMoods] = useState([]); + const [mobileMoodFilterOpen, setMobileMoodFilterOpen] = useState(false); const { @@ -194,6 +196,42 @@ const ResourcesHub = () => {

)} + {isMusicView && isMobile && ( +
+ + + + + +
+

+ + Filter by Mood +

+ { + setSelectedMoods(moods); + if (moods.length === 0) { + setMobileMoodFilterOpen(false); + } + }} + moodsData={musicMoodsData} + /> +
+
+
+
+ )} + Date: Sun, 3 May 2026 15:33:39 +0500 Subject: [PATCH 3/3] Fix: use MusicMood[] type for musicMoodsData state --- src/pages/ResourcesHub.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ResourcesHub.tsx b/src/pages/ResourcesHub.tsx index 99cbe83..e711eb8 100644 --- a/src/pages/ResourcesHub.tsx +++ b/src/pages/ResourcesHub.tsx @@ -6,6 +6,7 @@ import { useIsMobile } from '@/hooks/use-mobile'; import { toast } from 'sonner'; import { useResources } from '@/hooks/useResources'; import { Resource } from '@/types/resources'; +import { MusicMood } from '@/types/music'; import ResourceFilters from '@/components/resources/ResourceFilters'; import SortSelector from '@/components/resources/SortSelector'; import ResourcesList from '@/components/resources/ResourcesList'; @@ -72,7 +73,7 @@ const ResourcesHub = () => { const isMcIconsView = selectedCategory === 'minecraft-icons'; const isMusicView = selectedCategory === 'music'; - const [musicMoodsData, setMusicMoodsData] = useState>([]); + const [musicMoodsData, setMusicMoodsData] = useState([]); useEffect(() => { if (isMusicView && musicMoodsData.length === 0) {