diff --git a/index.html b/index.html
index 8677d1e..9e3c8c8 100644
--- a/index.html
+++ b/index.html
@@ -4,29 +4,29 @@
-
[
diff --git a/src/components/KindFilter/index.tsx b/src/components/KindFilter/index.tsx
index a22fa97..f745e64 100644
--- a/src/components/KindFilter/index.tsx
+++ b/src/components/KindFilter/index.tsx
@@ -20,6 +20,7 @@ const KIND_FILTER_OPTIONS = [
{ kindGroup: [ExtendedKind.POLL], label: 'Polls' },
{ kindGroup: [ExtendedKind.VOICE, ExtendedKind.VOICE_COMMENT], label: 'Voice Posts' },
{ kindGroup: [ExtendedKind.PICTURE], label: 'Photo Posts' },
+ { kindGroup: [ExtendedKind.MUSIC_TRACK], label: 'Music Posts' },
{
kindGroup: [
ExtendedKind.VIDEO,
diff --git a/src/components/Note/MusicTrackNote/index.tsx b/src/components/Note/MusicTrackNote/index.tsx
new file mode 100644
index 0000000..aabd3ab
--- /dev/null
+++ b/src/components/Note/MusicTrackNote/index.tsx
@@ -0,0 +1,173 @@
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import dayjs from 'dayjs'
+import { Event } from 'nostr-tools'
+import AudioPlayer from '@/components/AudioPlayer'
+
+interface MusicTrackNoteProps {
+ event: Event
+ className?: string
+}
+
+export default function MusicTrackNote({ event, className }: MusicTrackNoteProps) {
+ const { t } = useTranslation()
+
+ const metadata = useMemo(() => {
+ const getTagValue = (tagName: string) => {
+ const tag = event.tags.find(tag => tag[0] === tagName)
+ return tag?.[1] || null
+ }
+
+ const getTagValues = (tagName: string) => {
+ return event.tags
+ .filter(tag => tag[0] === tagName)
+ .map(tag => tag[1])
+ .filter(Boolean)
+ }
+
+
+ let lyrics = null
+ let credits = null
+
+ if (event.content) {
+ const creditsMatch = event.content.match(/Credits:\s*\n([\s\S]*)/i)
+ if (creditsMatch) {
+ credits = creditsMatch[1].trim()
+
+ const lyricsMatch = event.content.match(/^([\s\S]*?)Credits:/i)
+ lyrics = lyricsMatch ? lyricsMatch[1].trim() : null
+ } else {
+
+ lyrics = event.content
+ }
+ }
+
+ return {
+ title: getTagValue('title') || t('music.untitled'),
+ url: getTagValue('url'),
+ image: getTagValue('image'),
+ license: getTagValue('license'),
+ alt: getTagValue('alt'),
+ releaseDate: getTagValue('released'),
+ artist: getTagValue('artist'),
+ album: getTagValue('album'),
+ trackNumber: getTagValue('track_number'),
+ duration: getTagValue('duration'),
+ genres: getTagValues('t').filter(tag =>
+ !['music', 'electronic', 'lofi pop'].includes(tag)
+ ),
+ lyrics,
+ credits
+ }
+ }, [event, t])
+
+ if (!metadata.url) {
+ return (
+
+ {t('music.noAudioUrl')}
+
+ )
+ }
+
+ return (
+
+ {/* Main track container */}
+
+ {/* Cover Art */}
+ {metadata.image && (
+
+

+
+ )}
+
+ {/* Track Info */}
+
+
{metadata.title}
+
+ {metadata.artist && (
+
+ {t('music.by')} {metadata.artist}
+
+ )}
+
+ {metadata.album && (
+
+ {t('music.album')}: {metadata.album}
+ {metadata.trackNumber && ` • ${t('music.track')} ${metadata.trackNumber}`}
+
+ )}
+
+ {metadata.releaseDate && (
+
+ {t('music.released')}: {dayjs(metadata.releaseDate).format('MMMM D, YYYY')}
+
+ )}
+
+ {/* Audio Player */}
+
+
+ {/* Show lyrics if they exist */}
+ {metadata.lyrics && (
+
+
+ {metadata.lyrics}
+
+
+ )}
+
+ {/* Show credits if they exist */}
+ {metadata.credits && (
+
+
+ {t('music.credits')}:
+ {metadata.credits}
+
+
+ )}
+
+
+
+
+
+
+
+ {/* License and Metadata Footer */}
+
+ {metadata.license && (
+
+ {t('music.license')}: {metadata.license}
+
+ )}
+
+ {/* Only show alt if it's different from what we've shown
+ {metadata.alt && !metadata.credits && (
+
+ {t('music.altText')}: {metadata.alt}
+
+ )}
+ */}
+ {metadata.genres.length > 0 && (
+
+ {metadata.genres.map((genre, index) => (
+
+ {genre}
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/Note/index.tsx b/src/components/Note/index.tsx
index 0d53b7a..5ba064f 100644
--- a/src/components/Note/index.tsx
+++ b/src/components/Note/index.tsx
@@ -38,6 +38,7 @@ import Reaction from './Reaction'
import RelayReview from './RelayReview'
import UnknownNote from './UnknownNote'
import VideoNote from './VideoNote'
+import MusicTrackNote from './MusicTrackNote'
export default function Note({
event,
@@ -124,6 +125,8 @@ export default function Note({
event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO
) {
content =
+ } else if (event.kind === ExtendedKind.MUSIC_TRACK) {
+ content =
} else if (event.kind === ExtendedKind.RELAY_REVIEW) {
content =
} else if (event.kind === kinds.Emojisets) {
diff --git a/src/components/RelayInfo/index.tsx b/src/components/RelayInfo/index.tsx
index 0764117..ca18caf 100644
--- a/src/components/RelayInfo/index.tsx
+++ b/src/components/RelayInfo/index.tsx
@@ -148,7 +148,7 @@ function RelayControls({ url }: { url: string }) {
}
const handleCopyShareableUrl = () => {
- navigator.clipboard.writeText(`https://jumble.social/?r=${url}`)
+ navigator.clipboard.writeText(`https://nostr.basspistol.org/?r=${url}`)
setCopiedShareableUrl(true)
toast.success('Shareable URL copied to clipboard')
setTimeout(() => setCopiedShareableUrl(false), 2000)
diff --git a/src/constants.ts b/src/constants.ts
index 3434311..53a1b77 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -4,7 +4,7 @@ import { TRelaySet } from './types'
export const JUMBLE_API_BASE_URL = 'https://api.jumble.social'
export const RECOMMENDED_BLOSSOM_SERVERS = [
- 'https://blossom.band/',
+ 'https://basspistol.org',
'https://blossom.primal.net/',
'https://nostr.media/'
]
@@ -78,9 +78,9 @@ export const BIG_RELAY_URLS = [
'wss://offchain.pub/'
]
-export const SEARCHABLE_RELAY_URLS = ['wss://search.nos.today/', 'wss://relay.nostr.band/']
+export const SEARCHABLE_RELAY_URLS = ['wss://basspistol.org/','wss://pyramid.fiatjaf.com/','wss://spatia-arcana.com/']
-export const TRENDING_NOTES_RELAY_URLS = ['wss://trending.relays.land/']
+export const TRENDING_NOTES_RELAY_URLS = ['wss://basspistol.org/uppermost']
export const GROUP_METADATA_EVENT_KIND = 39000
@@ -101,7 +101,8 @@ export const ExtendedKind = {
RELAY_REVIEW: 31987,
GROUP_METADATA: 39000,
ADDRESSABLE_NORMAL_VIDEO: 34235,
- ADDRESSABLE_SHORT_VIDEO: 34236
+ ADDRESSABLE_SHORT_VIDEO: 34236,
+ MUSIC_TRACK: 36787
}
export const ALLOWED_FILTER_KINDS = [
@@ -118,7 +119,8 @@ export const ALLOWED_FILTER_KINDS = [
kinds.Highlights,
kinds.LongFormArticle,
ExtendedKind.ADDRESSABLE_NORMAL_VIDEO,
- ExtendedKind.ADDRESSABLE_SHORT_VIDEO
+ ExtendedKind.ADDRESSABLE_SHORT_VIDEO,
+ ExtendedKind.MUSIC_TRACK
]
export const SUPPORTED_KINDS = [
@@ -202,16 +204,16 @@ export const PRIMARY_COLORS = {
DEFAULT: {
name: 'Default',
light: {
- primary: '259 43% 56%',
- 'primary-hover': '259 43% 65%',
+ primary: '30 100% 50%',
+ 'primary-hover': '30 100% 60%',
'primary-foreground': '0 0% 98%',
- ring: '259 43% 56%'
+ ring: '30 100% 50%'
},
dark: {
- primary: '259 43% 56%',
- 'primary-hover': '259 43% 65%',
+ primary: '30 100% 50%',
+ 'primary-hover': '30 100% 60%',
'primary-foreground': '240 5.9% 10%',
- ring: '259 43% 56%'
+ ring: '30 100% 50%'
}
},
RED: {
@@ -230,18 +232,18 @@ export const PRIMARY_COLORS = {
}
},
ORANGE: {
- name: 'Orange',
+ name: 'Lavender',
light: {
- primary: '30 100% 50%',
- 'primary-hover': '30 100% 60%',
+ primary: '259 43% 56%',
+ 'primary-hover': '259 43% 65%',
'primary-foreground': '0 0% 98%',
- ring: '30 100% 50%'
+ ring: '259 43% 56%'
},
dark: {
- primary: '30 100% 50%',
- 'primary-hover': '30 100% 60%',
+ primary: '259 43% 56%',
+ 'primary-hover': '259 43% 65%',
'primary-foreground': '240 5.9% 10%',
- ring: '30 100% 50%'
+ ring: '259 43% 56%'
}
},
AMBER: {
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 6e3c18f..8703149 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -686,6 +686,18 @@ export default {
'Allow insecure connections description':
'Allow loading http:// resources and connecting to ws:// relays. May trigger browser mixed content warnings.',
'reacted to': 'reacted to',
- Reaction: 'Reaction'
+ Reaction: 'Reaction',
+ music: {
+ untitled: 'Untitled Track',
+ by: 'by',
+ album: 'Album',
+ track: 'Track',
+ released: 'Released',
+ license: 'License',
+ altText: 'Credit',
+ credits: 'Credits', // Add this
+ showLyrics: 'Show Lyrics', // Add this (optional)
+ noAudioUrl: 'No audio URL provided for this track',
+ }
}
}
diff --git a/src/index.css b/src/index.css
index 7f5901a..07b6650 100644
--- a/src/index.css
+++ b/src/index.css
@@ -133,12 +133,12 @@
--radius: 0.75rem;
}
.dark {
- --surface-background: 240 10% 3.9%;
- --background: 0 0% 9%;
+ --surface-background: 276, 61.404%, 9%;
+ --background: 276, 61.404%, 11.176%;
--foreground: 0 0% 98%;
- --card: 0 0% 12%;
+ --card: 276, 61.404%, 10%;
--card-foreground: 0 0% 98%;
- --popover: 0 0% 12%;
+ --popover: 276, 61.404%, 10%;
--popover-foreground: 0 0% 98%;
--primary: 259 43% 56%;
--primary-hover: 259 43% 65%;
diff --git a/src/lib/link.ts b/src/lib/link.ts
index c7c868d..c84b2a5 100644
--- a/src/lib/link.ts
+++ b/src/lib/link.ts
@@ -9,7 +9,7 @@ export const toNote = (eventOrId: Event | string) => {
return `/notes/${nevent}`
}
export const toJumbleNote = (eventOrId: Event | string) => {
- return `https://jumble.social${toNote(eventOrId)}`
+ return `https://nostr.basspistol.org${toNote(eventOrId)}`
}
export const toNoteList = ({
hashtag,
diff --git a/vite.config.ts b/vite.config.ts
index 18130c3..a81cdfa 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -59,8 +59,8 @@ export default defineConfig(({ mode }) => {
enabled: true
},
manifest: {
- name: 'Jumble',
- short_name: 'Jumble',
+ name: 'Bpistle',
+ short_name: 'Bpistle',
icons: [
{
src: '/pwa-512x512.png',