// SONARA — Richieste open page (artista) const RIcons = window.SonaraIcons; // ============= DATA ============= const RICHIESTE = [ { id: "r1", title: "Beat trap melodico per pezzo radio-friendly", status: "open", statusLabel: "Aperta", publishedAgo: "2 giorni fa", expiresIn: "28 giorni", urgent: false, budget: { min: 80, max: 200 }, deliveryDays: 10, offers: 7, offersUnread: 3, views: 142, tags: ["Trap", "Melodic", "Radio-friendly", "BPM 140-150"], refs: ["Lil Tjay", "Travis Scott — High Five"], brief: [ "Cerco un beat trap con linea melodica forte e drop pulito. Idealmente con archi sintetici, 808 calde e hi-hat triplet ma non aggressive.", "Deve funzionare per una hook melodica in italiano. Ho già la voce demo (allegata). Strofa rap, ritornello cantato.", "Importante: ricevo stems separati per il mix. Non interessato a beat già usati o licenze esclusive — preferisco non-esclusiva." ], attachments: [ { name: "voice-memo-demo.m4a", size: "0:48 · 320 KB", icon: "audio" }, { name: "ref-track.mp3", size: "Reference · 2.1 MB", icon: "audio" }, ], offersList: [ { id: "o1", producer: "@nightowl", initials: "NO", verified: true, rating: 4.9, completedJobs: 87, price: 145, days: 7, message: "Ho una bozza che funziona perfettamente per questo brief. Stems inclusi, 1 revisione free. Sentiamo?", featured: true, hasAttach: true, attachLabel: "snippet 0:24" }, { id: "o2", producer: "@luxx", initials: "LX", verified: true, rating: 4.8, completedJobs: 142, price: 180, days: 5, message: "Producer da 6 anni. Posso consegnare in 5gg con fino a 3 revisioni. Stems WAV 24-bit.", featured: false, hasAttach: true, attachLabel: "snippet 0:32" }, { id: "o3", producer: "@808king", initials: "8K", verified: false, rating: 4.6, completedJobs: 34, price: 95, days: 12, message: "Ho un beat in stock che combacia col brief, posso adattarlo per te. Prezzo basso perché parto da una base esistente.", featured: false, hasAttach: false, attachLabel: null }, { id: "o4", producer: "@desertcat", initials: "DC", verified: true, rating: 4.7, completedJobs: 51, price: 165, days: 8, message: "Posso lavorarci subito, ho slot libero questa settimana. Mando una demo entro 24h.", featured: false, hasAttach: true, attachLabel: "snippet 0:18" }, ], }, { id: "r2", title: "Servizio di mixing per EP 4 tracce indie alternative", status: "open", statusLabel: "Aperta", publishedAgo: "5 giorni fa", expiresIn: "3 giorni", urgent: true, budget: { min: 250, max: 500 }, deliveryDays: 14, offers: 4, offersUnread: 1, views: 89, tags: ["Mix", "Indie", "Alternative", "EP 4 tracce"], refs: ["Phoebe Bridgers", "Big Thief"], brief: [ "Sto cercando un mix engineer per finalizzare un EP indie alternative di 4 tracce. Stems multitraccia già editati, niente vocal tuning richiesto.", "Cerco un suono organico, intimo, con dinamica preservata. Niente master loud." ], attachments: [ { name: "rough-mix-track1.wav", size: "Demo · 18 MB", icon: "audio" }, ], offersList: [ { id: "o5", producer: "Hearfield Studio", initials: "HS", verified: true, rating: 4.9, completedJobs: 218, price: 380, days: 14, message: "Mix engineer dal 2014, lavorato su artisti indie italiani. Posso mandarti 1 track campione gratuita.", featured: true, hasAttach: false, attachLabel: null }, { id: "o6", producer: "Marco Vega", initials: "MV", verified: true, rating: 4.8, completedJobs: 96, price: 320, days: 10, message: "Specializzato indie/folk. 2 revisioni incluse. Lavoro in Pro Tools + analog summing.", featured: false, hasAttach: false, attachLabel: null }, ], }, { id: "r3", title: "Cerco vocal coach per registrazione di 2 brani", status: "open", statusLabel: "Aperta", publishedAgo: "1 settimana fa", expiresIn: "21 giorni", urgent: false, budget: { min: 100, max: 250 }, deliveryDays: 5, offers: 2, offersUnread: 0, views: 45, tags: ["Vocal coach", "R&B", "Soul"], refs: ["SZA", "H.E.R."], brief: [ "Cerco un vocal coach per affinare la tecnica su 2 brani prima di entrare in studio. Sessioni online ok." ], attachments: [], offersList: [ { id: "o7", producer: "Sara K.", initials: "SK", verified: true, rating: 5.0, completedJobs: 67, price: 180, days: 3, message: "Vocal coach + producer. Sessioni online via Zoom. Esperienza R&B/soul italiano.", featured: false, hasAttach: false, attachLabel: null }, { id: "o8", producer: "@voicelab", initials: "VL", verified: false, rating: 4.5, completedJobs: 22, price: 120, days: 5, message: "Coaching tecnica vocale + arrangement. Posso mandare un primo modulo gratis di 30 min.", featured: false, hasAttach: false, attachLabel: null }, ], }, { id: "r4", title: "Beat boombap classico anni '90", status: "closed", statusLabel: "Chiusa", publishedAgo: "3 settimane fa", expiresIn: null, urgent: false, budget: { min: 50, max: 120 }, deliveryDays: 7, offers: 11, offersUnread: 0, views: 220, tags: ["Boombap", "90s", "Hip-hop"], refs: [], brief: ["Richiesta chiusa senza accettazione. Nessun match con ciò che cercavo."], attachments: [], offersList: [], }, ]; // ============= ICONS ============= const VerifiedIcon = ({ size = 12 }) => ( ); const PlusIcon2 = ({ size = 16 }) => ( ); const StarIcon2 = ({ size = 12, filled = true }) => ( ); const ChatIcon = ({ size = 14 }) => ( ); const CheckIcon = ({ size = 14 }) => ( ); const PlayIcon2 = ({ size = 12 }) => ( ); const AudioFileIcon = ({ size = 16 }) => ( ); // ============= QUOTA ============= function Quota({ openCount }) { const max = 3; const pct = (openCount / max) * 100; const r = 22; const c = 2 * Math.PI * r; const offset = c * (1 - pct / 100); return (
{openCount}/{max}
Slot richieste open
Hai {openCount} richieste attive su {max} massimo. Le richieste auto-archiviate liberano lo slot.
Pubblica richiesta
); } // ============= LIST ============= function RichiestaItem({ item, active, onClick }) { return (
{item.title}
{item.publishedAgo} {item.status === "open" && ( <> · {item.offers} offerte{item.offersUnread > 0 ? ` · ${item.offersUnread} nuove` : ""} )} {item.urgent && ⏰ {item.expiresIn}} {item.status === "closed" && <>·Chiusa}
); } function ListPanel({ items, activeId, onSelect }) { const [tab, setTab] = React.useState("active"); const filtered = items.filter(i => tab === "active" ? i.status === "open" : i.status === "closed"); return ( ); } // ============= OFFER CARD ============= function OfferCard({ offer }) { return (
{offer.initials}
{offer.producer} {offer.verified && }
{offer.rating.toFixed(1)} · {offer.completedJobs} lavori
Prezzo
{offer.price}
Consegna
{offer.days}gg
{offer.message}
{offer.hasAttach ? ( ) : (
Nessuna anteprima allegata · solo testo
)}
); } // ============= DETAIL ============= function DetailPanel({ richiesta, onOpenOffers }) { if (!richiesta) { return (
Seleziona una richiesta
Vedi offerte ricevute, brief, allegati e gestisci da qui.
); } return (
{richiesta.statusLabel} 📅 Pubblicata {richiesta.publishedAgo} {richiesta.expiresIn && ( ⏱ {richiesta.urgent ? "Scade tra " : "Auto-chiude tra "}{richiesta.expiresIn} )} 👁 {richiesta.views} visite

{richiesta.title}

Budget
{richiesta.budget.min}–{richiesta.budget.max}
Consegna
{richiesta.deliveryDays}gg
Offerte
{richiesta.offers}{richiesta.offersUnread > 0 && +{richiesta.offersUnread} nuove}
Visite
{richiesta.views}
{/* Prominent offers banner → opens modal */} {richiesta.status === "open" && richiesta.offersList.length > 0 && ( )} {richiesta.status === "open" && richiesta.offersList.length === 0 && (
Ancora nessuna offerta
La tua richiesta è visibile a {richiesta.views} producer. Ti avvisiamo appena arriva la prima.
)}
Tag
{richiesta.tags.map(t => {t})}
Brief
{richiesta.brief.map((p, i) =>

{p}

)}
{richiesta.refs.length > 0 && (
Riferimenti sonori
{richiesta.refs.map(r => ♫ {r})}
)} {richiesta.attachments.length > 0 && (
Allegati
{richiesta.attachments.map(a => (
{a.name} {a.size}
))}
)}
); } // ============= OFFERS MODAL ============= function OffersModal({ richiesta, onClose }) { const [sort, setSort] = React.useState("relevance"); React.useEffect(() => { const k = (e) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", k); return () => document.removeEventListener("keydown", k); }, [onClose]); let offers = [...richiesta.offersList]; if (sort === "price-asc") offers.sort((a, b) => a.price - b.price); else if (sort === "price-desc") offers.sort((a, b) => b.price - a.price); else if (sort === "rating") offers.sort((a, b) => b.rating - a.rating); else if (sort === "fast") offers.sort((a, b) => a.days - b.days); return (
e.stopPropagation()}>
Offerte ricevute
{richiesta.title}
{richiesta.offersList.length} offerte · da €{Math.min(...richiesta.offersList.map(o => o.price))} · {richiesta.offersList.filter(o => o.hasAttach).length} con anteprima
{[["relevance","Pertinenza"],["price-asc","Prezzo ↑"],["rating","Rating"],["fast","Più rapidi"]].map(([v, l]) => ( ))}
{offers.map(o => )}
); } // ============= PAGE ============= function RichiestePage() { const [activeId, setActiveId] = React.useState("r1"); const [offersOpen, setOffersOpen] = React.useState(false); const active = RICHIESTE.find(r => r.id === activeId); const openCount = RICHIESTE.filter(r => r.status === "open").length; return (

Richieste open

Brief pubblici per ricevere offerte da producer in linea con quello che cerchi.
setOffersOpen(true)}/>
{offersOpen && active && setOffersOpen(false)}/>}
); } window.RichiestePage = RichiestePage;