Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions client/src/data.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
// Static data for rmzi.world - no backend needed

export const projects = [
{
id: 'p1',
title: '36247',
subtitle: 'Memphis Rap streaming service — 2,091 tracks, no tracking',
url: 'https://36247.rmzi.world',
},
{
id: 'p2',
title: 'Crate',
subtitle: 'Free-to-use music streaming platform',
url: 'https://crate.rmzi.world',
},
];

export const press = [
{
id: 'pr1',
title: 'Are Hackathons Worth It? Five Participants Weigh In',
source: 'WNYC',
date: '2013-09-18',
url: 'https://www.wnycstudios.org/podcasts/notetoself/segments/are-hackathons-worth-it-five-participants-weigh',
},
{
id: 'pr2',
title: 'And the Winner of the First Ever Fashion Hackathon Is...',
source: 'Fashionista',
date: '2013-02-13',
url: 'https://fashionista.com/2013/02/and-the-winner-of-the-first-ever-fashion-hackathon-is',
},
{
id: 'pr3',
title: 'Fashion Hackathon Comes to NYFW',
source: 'WWD',
date: '2013-02-13',
url: 'https://wwd.com/fashion-news/fashion-scoops/feature/fashion-hackathon-comes-to-nyfw-6621485-450231/',
},
{
id: 'pr4',
title: 'Tech is the New Black: Decoded Fashion at Mercedes Benz Fashion Week',
source: 'AlleyWatch',
date: '2013-02-13',
url: 'https://www.alleywatch.com/2013/02/tech-is-the-new-black-decoded-fashion-at-mercedes-benz-fashion-week/',
},
{
id: 'pr5',
title: 'Music Hack Day NYC — Spotify V. Rdio',
source: 'Music Machinery',
date: '2013-10-21',
url: 'https://musicmachinery.com/2013/10/21/music-hack-day-nyc/',
},
{
id: 'pr6',
title: 'Bodywerk Memphis: DJ Superteam Making Waves',
source: 'We Are Memphis',
url: 'https://wearememphis.com/play/music/musician-profiles/bodywerk-memphis-dj-superteam/',
},
{
id: 'pr7',
title: 'Ep. 23: Bodywerk',
source: 'The Shellcast: Only In Memphis',
date: '2022-03-10',
url: 'https://podcasts.apple.com/us/podcast/ep-23-bodywerk/id1558176971?i=1000553643095',
},
{
id: 'pr8',
title: 'HabibiBeats Radio',
source: 'WYXR 91.7 FM',
url: 'https://wyxr.org/past-shows/',
},
];

export const works = [
{
id: 0,
Expand Down
200 changes: 171 additions & 29 deletions client/src/pages/Work.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { works } from '../data';
import { works, projects, press } from '../data';
import { useStore } from '../store';

// Track mix/embed opens
Expand All @@ -13,6 +13,42 @@ const trackMixOpen = (mixTitle) => {
}
};

const trackOutboundClick = (url, label) => {
if (typeof window.gtag === 'function') {
window.gtag('event', 'outbound', {
event_category: 'engagement',
event_label: label,
transport_type: 'beacon',
});
}
};

const sectionLabelStyle = {
fontSize: '0.65rem',
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: '0.1em',
opacity: 0.4,
color: '#1a1a1a',
padding: '20px 20px 4px',
margin: 0,
};

const linkItemStyle = {
width: '100%',
padding: '16px 20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
background: 'transparent',
border: 'none',
color: '#1a1a1a',
cursor: 'pointer',
textAlign: 'left',
borderRadius: '8px',
textDecoration: 'none',
};

export default function Work() {
const [expandedId, setExpandedId] = useState(null);
const { setEmbedOpen } = useStore();
Expand Down Expand Up @@ -61,23 +97,75 @@ export default function Work() {
margin: '0 auto',
}}
>
{works.map((work, index) => (
<motion.div
key={work.id}
{/* Projects Section */}
<p style={sectionLabelStyle}>Projects</p>
{projects.map((project, index) => (
<motion.div
key={project.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.05 }}
style={{
borderBottom: index < works.length - 1
? '1px solid rgba(0,0,0,0.08)'
borderBottom: '1px solid rgba(0,0,0,0.08)',
}}
>
<motion.a
href={project.url}
target="_blank"
rel="noopener noreferrer"
onClick={() => trackOutboundClick(project.url, project.title)}
whileHover={{ backgroundColor: 'rgba(0,0,0,0.03)' }}
style={linkItemStyle}
>
<div style={{ flex: 1, minWidth: 0 }}>
<h3 style={{
margin: 0,
fontSize: '1rem',
fontWeight: '500',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
{project.title}
</h3>
<span style={{
fontSize: '0.75rem',
opacity: 0.5,
}}>
{project.subtitle}
</span>
</div>
<span style={{
fontSize: '0.85rem',
opacity: 0.4,
marginLeft: '12px',
flexShrink: 0,
}}>
</span>
</motion.a>
</motion.div>
))}

{/* Mixes Section */}
<p style={{ ...sectionLabelStyle, paddingTop: '28px' }}>Mixes</p>
{works.map((work, index) => (
<motion.div
key={work.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: (projects.length + index) * 0.05 }}
style={{
borderBottom: index < works.length - 1
? '1px solid rgba(0,0,0,0.08)'
: 'none',
}}
>
{/* Accordion Header */}
<motion.button
onClick={() => toggleItem(work.id, work.title)}
whileHover={{ backgroundColor: 'rgba(0,0,0,0.03)' }}
style={{
style={{
width: '100%',
padding: '16px 20px',
display: 'flex',
Expand All @@ -86,34 +174,34 @@ export default function Work() {
background: 'transparent',
border: 'none',
color: '#1a1a1a',
cursor: 'pointer',
cursor: 'pointer',
textAlign: 'left',
borderRadius: '8px',
}}
>
}}
>
<div style={{ flex: 1, minWidth: 0 }}>
<h3 style={{
margin: 0,
<h3 style={{
margin: 0,
fontSize: '1rem',
fontWeight: '500',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
{work.title}
{work.title}
</h3>
<span style={{
fontSize: '0.75rem',
<span style={{
fontSize: '0.75rem',
opacity: 0.5,
}}>
{work.date}
{work.date}
</span>
</div>

<motion.span
animate={{ rotate: expandedId === work.id ? 180 : 0 }}
transition={{ duration: 0.2 }}
style={{
style={{
fontSize: '1.2rem',
opacity: 0.5,
marginLeft: '12px',
Expand All @@ -126,24 +214,24 @@ export default function Work() {

{/* Accordion Content */}
<AnimatePresence>
{expandedId === work.id && (
<motion.div
{expandedId === work.id && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
style={{ overflow: 'hidden' }}
>
>
<div style={{ padding: '0 20px 20px' }}>
<p style={{
fontSize: '0.85rem',
opacity: 0.6,
<p style={{
fontSize: '0.85rem',
opacity: 0.6,
margin: '0 0 16px',
lineHeight: 1.5
}}>
{work.subtitle}
</p>

{work.youtubeId && (
<div style={{
position: 'relative',
Expand All @@ -170,11 +258,65 @@ export default function Work() {
</div>
)}
</div>
</motion.div>
)}
</motion.div>
)}
</AnimatePresence>
</motion.div>
))}
</motion.div>
))}

{/* Press Section */}
<div style={{ borderTop: '1px solid rgba(0,0,0,0.08)' }}>
<p style={{ ...sectionLabelStyle, paddingTop: '28px' }}>Press</p>
{press.map((item, index) => (
<motion.div
key={item.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: (projects.length + works.length + index) * 0.05 }}
style={{
borderBottom: index < press.length - 1
? '1px solid rgba(0,0,0,0.08)'
: 'none',
}}
>
<motion.a
href={item.url}
target="_blank"
rel="noopener noreferrer"
onClick={() => trackOutboundClick(item.url, item.title)}
whileHover={{ backgroundColor: 'rgba(0,0,0,0.03)' }}
style={linkItemStyle}
>
<div style={{ flex: 1, minWidth: 0 }}>
<h3 style={{
margin: 0,
fontSize: '1rem',
fontWeight: '500',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
{item.title}
</h3>
<span style={{
fontSize: '0.75rem',
opacity: 0.5,
}}>
{item.source}{item.date ? ` · ${item.date}` : ''}
</span>
</div>
<span style={{
fontSize: '0.85rem',
opacity: 0.4,
marginLeft: '12px',
flexShrink: 0,
}}>
</span>
</motion.a>
</motion.div>
))}
</div>
</motion.div>
</div>
);
Expand Down