VibeCoderzVibeCoderz
Telegram
All Prompts
Motion Music Player UI Preview
ui component

Motion Music Player

UI-компонент музыкального плеера с анимациями. Интегрируйте современный дизайн и интерактивность в ваше приложение.

by Zhou JasonLive Preview

Prompt

# Motion Music Player

You are given a task to integrate an existing React component in the codebase

~~~/src/App.tsx
import React from 'react';
import { HashRouter } from 'react-router-dom';
import DeckPlayer from './components/DeckPlayer';
import './global.css';

// Setup Tailwind config for the app
if (typeof window !== 'undefined') {
  window.tailwind = window.tailwind || {};
  window.tailwind.config = {
    theme: {
      extend: {
        fontFamily: {
          sans: ['Manrope', 'sans-serif'],
          display: ['Archivo', 'sans-serif'],
        },
        colors: {
          bg: '#eff6ff',
          card: '#ffffff',
          primary: '#18181b',
        },
        animation: {
          'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
        }
      },
    },
  };
}

export default function App() {
  return (
    <HashRouter>
      <div className="w-full h-screen bg-[#eff6ff] text-zinc-900 flex items-center justify-center p-4 overflow-hidden relative selection:bg-blue-500 selection:text-white">
        {/* Ambient Background */}
        <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(255,255,255,0.8),rgba(219,234,254,1))] pointer-events-none" />
        <div className="absolute top-0 left-0 w-full h-full bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-[0.03] pointer-events-none" />
        
        {/* Decorative Blobs for Gen Z vibe */}
        <div className="absolute top-[-10%] right-[-5%] w-[500px] h-[500px] rounded-full bg-purple-300/20 blur-[100px] pointer-events-none mix-blend-multiply" />
        <div className="absolute bottom-[-10%] left-[-5%] w-[500px] h-[500px] rounded-full bg-blue-300/20 blur-[100px] pointer-events-none mix-blend-multiply" />

        <DeckPlayer />
      </div>
    </HashRouter>
  );
}
~~~

~~~/package.json
{
  "name": "motion-music-player",
  "description": "A premium music player with card stack interactions",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "framer-motion": "^10.16.4",
    "lucide-react": "^0.292.0",
    "clsx": "^2.0.0",
    "tailwind-merge": "^2.0.0",
    "react-router-dom": "^6.20.0"
  }
}
~~~

~~~/src/global.css
@import url('https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&family=Manrope:wght@200..800&display=swap');

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --color-bg: #eff6ff;
  --color-card: #ffffff;
  --color-text: #18181b;
  --color-text-muted: #71717a;
  --color-accent: #3b82f6;
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: 'Manrope', sans-serif;
  overflow: hidden; /* Prevent scrolling for the app-like feel */
  -webkit-font-smoothing: antialiased;
}

.font-display {
  font-family: 'Archivo', sans-serif;
}

/* Custom Scrollbar */
::-webkit-scrollbar {
  width: 6px;
}
::-webkit-scrollbar-track {
  background: transparent;
}
::-webkit-scrollbar-thumb {
  background: #d4d4d8;
  border-radius: 3px;
}

/* Utilities */
.glass-panel {
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(0, 0, 0, 0.05);
}

/* Animation specific */
.waveform-bar {
  transition: height 0.15s ease;
}
~~~

~~~/src/components/Card.tsx
import React from 'react';
import { motion } from 'framer-motion';
import { Play, Pause, SkipBack, SkipForward } from 'lucide-react';
import Waveform from './Waveform';

export interface Song {
  id: string;
  title: string;
  artist: string;
  cover: string;
  duration: string;
  bgGradient: string;
  headerText: string;
  subText: string;
}

interface CardProps {
  song: Song;
  isPlaying: boolean;
  onTogglePlay: () => void;
  onNext: () => void;
  onPrev: () => void;
  index: number;
  isBackground?: boolean;
}

export default function Card({ song, isPlaying, onTogglePlay, onNext, onPrev, isBackground = false }: CardProps) {
  return (
    <div className={`relative w-full h-full bg-white rounded-[30px] overflow-hidden flex flex-col border border-zinc-200 shadow-[0_8px_30px_rgba(0,0,0,0.04)] transition-all ${isBackground ? 'brightness-95 grayscale-[0.3] shadow-none' : 'ring-1 ring-black/5'}`}>
      {/* Dynamic Background - subtle gradient */}
      
      {/* Enhanced glow effect for light mode */}
      {!isBackground && (
        <div className="absolute inset-0 rounded-[30px] shadow-[inset_0_0_20px_rgba(0,0,0,0.02)] pointer-events-none z-20" />
      )}
      <div 
        className="absolute inset-0 opacity-40 pointer-events-none transition-colors duration-500"
        style={{ background: `linear-gradient(to bottom, ${song.bgGradient}, #ffffff)` }}
      />
      
      {/* Card Content */}
      <div className="flex-1 px-8 flex flex-col relative z-10 pt-8 justify-center">
        <motion.h1 
          layoutId={`header-${song.id}`}
          className="font-display text-5xl font-black tracking-tighter text-black mb-2 leading-[0.9]"
        >
          {song.headerText}
        </motion.h1>
        
        <motion.p 
          initial={{ opacity: 0 }}
          animate={{ opacity: 0.6 }}
          className="text-base text-zinc-500 max-w-[90%] font-medium tracking-tight"
        >
          {song.subText}
        </motion.p>

        <motion.div 
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ delay: 0.2 }}
          className="mt-6"
        >
          <button className="px-6 py-3 rounded-full bg-black hover:bg-zinc-800 text-white text-xs font-bold tracking-widest transition-colors uppercase shadow-lg shadow-black/10 active:scale-95">
            Get Premium
          </button>
        </motion.div>
      </div>

      {/* Player Section */}
      <div className="p-4">
        <div className="bg-white/80 rounded-[24px] p-5 flex items-center gap-5 shadow-lg shadow-zinc-200/50 border border-white/50 backdrop-blur-md relative overflow-hidden group ring-1 ring-black/5">
          {/* Mini Cover */}
          <div className="relative w-16 h-16 rounded-xl overflow-hidden flex-shrink-0 shadow-md ring-1 ring-black/5">
             <img src={song.cover} alt="cover" className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500" />
          </div>
          
          {/* Info & Controls */}
          <div className="flex-1 flex flex-col justify-center min-w-0 gap-2">
            <div className="flex justify-between items-start">
              <div className="min-w-0">
                <h3 className="text-zinc-900 font-bold text-base truncate pr-2">{song.title}</h3>
                <p className="text-zinc-400 text-xs truncate font-medium">{song.artist}</p>
              </div>
              
              {/* Controls */}
              <div className="flex items-center gap-3">
                 <button onClick={onPrev} className="text-zinc-400 hover:text-black transition-colors">
                   <SkipBack size={18} fill="currentColor" />
                 </button>
                 <button onClick={onTogglePlay} className="text-black hover:scale-110 transition-transform bg-zinc-100 p-1.5 rounded-full">
                   {isPlaying ? <Pause size={18} fill="currentColor" /> : <Play size={18} fill="currentColor" className="ml-0.5" />}
                 </button>
                 <button onClick={onNext} className="text-zinc-400 hover:text-black transition-colors">
                   <SkipForward size={18} fill="currentColor" />
                 </button>
              </div>
            </div>
            
            {/* Waveform & Time */}
            <div className="flex items-center gap-3 mt-1">
               <div className="flex-1 h-6 flex items-center">
                  <Waveform isPlaying={isPlaying} />
               </div>
               <span className="text-[10px] font-mono text-zinc-400">{song.duration}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
~~~

~~~/src/components/Waveform.tsx
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';

export default function Waveform({ isPlaying }: { isPlaying: boolean }) {
  // Generate random heights for the bars
  const barCount = 40;
  const [bars, setBars] = useState<number[]>(Array.from({ length: barCount }, () => Math.random()));

  useEffect(() => {
    if (!isPlaying) return;

    const interval = setInterval(() => {
      setBars(prev => prev.map(h => {
        const change = (Math.random() - 0.5) * 0.5;
        return Math.max(0.1, Math.min(1, h + change));
      }));
    }, 100);

    return () => clearInterval(interval);
  }, [isPlaying]);

  return (
    <div className="flex items-center gap-[2px] h-8 w-full opacity-60">
      {bars.map((height, i) => (
        <motion.div
          key={i}
          className="rounded-full w-[3px]"
          animate={{ 
            height: `${height * 100}%`,
            opacity: isPlaying ? 0.8 + (height * 0.2) : 0.3
          }}
          transition={{ duration: 0.1 }}
          style={{
            backgroundColor: i < barCount * 0.3 ? '#000000' : '#a1a1aa' // Black and Zinc-400
          }}
        />
      ))}
    </div>
  );
}
~~~

~~~/src/components/DeckPlayer.tsx
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import Card, { Song } from './Card';

// Mock Data - Updated for Light Mode / Gen Z
const SONGS: Song[] = [
  {
    id: '1',
    title: "Can't Be Broke",
    artist: "Rick Ross feat. Yungeen Ace",
    cover: "https://images.unsplash.com/photo-1619983081563-430f63602796?q=80&w=1000&auto=format&fit=crop",
    duration: "00:48:00",
    bgGradient: "#dbeafe", // Light Blue
    headerText: "MOTION FACTORY",
    subText: "Best Suited For Freelancers, Content Creators"
  },
  {
    id: '2',
    title: "Midnight City",
    artist: "M83",
    cover: "https://images.unsplash.com/photo-1493225255756-d9584f8606e9?q=80&w=1000&auto=format&fit=crop",
    duration: "04:03:00",
    bgGradient: "#f3e8ff", // Light Purple
    headerText: "NEON DREAMS",
    subText: "Synth-pop anthems for late night drives"
  },
  {
    id: '3',
    title: "Starboy",
    artist: "The Weeknd",
    cover: "https://images.unsplash.com/photo-1614613535308-eb5fbd3d2c17?q=80&w=1000&auto=format&fit=crop",
    duration: "03:50:00",
    bgGradient: "#ffe4e6", // Light Red/Pink
    headerText: "STAR POWER",
    subText: "Chart topping hits from the modern legend"
  },
  {
    id: '4',
    title: "Levitating",
    artist: "Dua Lipa",
    cover: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745?q=80&w=1000&auto=format&fit=crop",
    duration: "03:23:00",
    bgGradient: "#e0f2fe", // Light Sky
    headerText: "FUTURE NOSTALGIA",
    subText: "Retro disco vibes reimaged for today"
  }
];

const swipeVariants = {
  enter: (direction: number) => ({
    scale: 0.95,
    y: -35,
    opacity: 0.6,
    zIndex: 2,
    x: 0,
  }),
  center: {
    zIndex: 3,
    x: 0,
    y: 0,
    scale: 1,
    opacity: 1,
    transition: {
      duration: 0.4,
      ease: [0.16, 1, 0.3, 1]
    }
  },
  exit: (direction: number) => ({
    zIndex: 3,
    x: direction > 0 ? 350 : -350,
    opacity: 0,
    scale: 1,
    rotate: direction > 0 ? 10 : -10,
    transition: {
      duration: 0.4,
      ease: [0.16, 1, 0.3, 1]
    }
  })
};

export default function DeckPlayer() {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [direction, setDirection] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);

  const handleNext = () => {
    setDirection(1);
    setCurrentIndex((prev) => (prev + 1) % SONGS.length);
  };

  const handlePrev = () => {
    setDirection(-1);
    setCurrentIndex((prev) => (prev - 1 + SONGS.length) % SONGS.length);
  };

  const togglePlay = () => setIsPlaying(!isPlaying);

  const activeSong = SONGS[currentIndex];
  const nextSong = SONGS[(currentIndex + 1) % SONGS.length];
  const nextNextSong = SONGS[(currentIndex + 2) % SONGS.length];

  return (
    <div className="relative w-[440px] h-[420px] flex items-center justify-center perspective-[1000px]">
      
      {/* Background Stack 2 */}
      <motion.div
        key={`bg2-${nextNextSong.id}`}
        className="absolute inset-0 w-full h-full pointer-events-none"
        initial={{ scale: 0.85, y: -70, opacity: 0 }}
        animate={{
          scale: 0.9,
          y: -70,
          zIndex: 1,
          opacity: 0.5 // Increased opacity for light mode
        }}
        transition={{ duration: 0.4 }}
      >
         <Card 
            song={nextNextSong} 
            isPlaying={false}
            onTogglePlay={() => {}}
            onNext={() => {}}
            onPrev={() => {}}
            index={currentIndex + 2}
            isBackground={true}
          />
      </motion.div>

      {/* Background Stack 1 */}
      <motion.div
        key={`bg1-${nextSong.id}`}
        className="absolute inset-0 w-full h-full pointer-events-none"
        initial={{ scale: 0.9, y: -35, opacity: 0.3 }}
        animate={{
          scale: 0.95,
          y: -35,
          zIndex: 2,
          opacity: 0.8 // Increased opacity for light mode
        }}
        transition={{ duration: 0.4 }}
      >
         <Card 
            song={nextSong} 
            isPlaying={false}
            onTogglePlay={() => {}}
            onNext={() => {}}
            onPrev={() => {}}
            index={currentIndex + 1}
            isBackground={true}
          />
      </motion.div>

      {/* Active Card */}
      <AnimatePresence custom={direction} mode="popLayout">
        <motion.div
          key={activeSong.id}
          custom={direction}
          variants={swipeVariants}
          initial="enter"
          animate="center"
          exit="exit"
          className="absolute inset-0 w-full h-full z-30 shadow-[0_20px_50px_-12px_rgba(0,0,0,0.15)] rounded-[30px]"
          drag="x"
          dragConstraints={{ left: 0, right: 0 }}
          dragElastic={0.7}
          onDragEnd={(e, { offset, velocity }) => {
            const swipe = offset.x;
            if (swipe < -100) {
              handlePrev();
            } else if (swipe > 100) {
              handleNext();
            }
          }}
        >
          <Card 
            song={activeSong} 
            isPlaying={isPlaying}
            onTogglePlay={togglePlay}
            onNext={handleNext}
            onPrev={handlePrev}
            index={currentIndex}
          />
        </motion.div>
      </AnimatePresence>
    </div>
  );
}
~~~

Implementation Guidelines

1. Analyze the component structure, styling, animation implementations
2. Review the component's arguments and state
3. Think through what is the best place to adopt this component/style into the design we are doing
4. Then adopt the component/design to our current system

Help me integrate this into my design
All Prompts