react-game-ui

Production-ready UI patterns for game interfaces using shadcn/ui, Tailwind, and Framer Motion.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-game-ui" with this command: npx skills add ccalebcarter/purria-skills/ccalebcarter-purria-skills-react-game-ui

React Game UI

Production-ready UI patterns for game interfaces using shadcn/ui, Tailwind, and Framer Motion.

Core Components

Resource Bar

import { cn } from '@/lib/utils'; import { motion, AnimatePresence } from 'framer-motion';

interface ResourceBarProps { icon: React.ReactNode; value: number; maxValue?: number; label: string; color?: 'gold' | 'green' | 'blue' | 'red'; }

const colorMap = { gold: 'from-amber-400 to-yellow-500', green: 'from-emerald-400 to-green-500', blue: 'from-sky-400 to-blue-500', red: 'from-rose-400 to-red-500', };

export function ResourceBar({ icon, value, maxValue, label, color = 'gold' }: ResourceBarProps) { return ( <div className="flex items-center gap-2 bg-black/40 backdrop-blur-sm rounded-lg px-3 py-2"> <div className={cn( 'w-8 h-8 rounded-full flex items-center justify-center', bg-gradient-to-br ${colorMap[color]} )}> {icon} </div> <div className="flex flex-col"> <span className="text-xs text-white/60 uppercase tracking-wide">{label}</span> <motion.span key={value} initial={{ scale: 1.2, color: '#ffd700' }} animate={{ scale: 1, color: '#ffffff' }} className="text-lg font-bold text-white tabular-nums" > {value.toLocaleString()} {maxValue && <span className="text-white/40">/{maxValue}</span>} </motion.span> </div> </div> ); }

Animated Counter

import { useEffect, useRef } from 'react'; import { motion, useSpring, useTransform } from 'framer-motion';

interface AnimatedCounterProps { value: number; duration?: number; className?: string; }

export function AnimatedCounter({ value, duration = 0.5, className }: AnimatedCounterProps) { const spring = useSpring(0, { duration: duration * 1000 }); const display = useTransform(spring, (v) => Math.floor(v).toLocaleString());

useEffect(() => { spring.set(value); }, [spring, value]);

return <motion.span className={className}>{display}</motion.span>; }

Meter/Progress Component

interface MeterProps { value: number; // 0-100 label: string; tier?: 'mini' | 'major' | 'grand'; showValue?: boolean; }

const tierStyles = { mini: 'h-2', major: 'h-3', grand: 'h-4', };

export function Meter({ value, label, tier = 'mini', showValue = true }: MeterProps) { const clampedValue = Math.max(0, Math.min(100, value));

return ( <div className="space-y-1"> <div className="flex justify-between text-sm"> <span className="text-white/80">{label}</span> {showValue && ( <span className="text-white/60 tabular-nums">{clampedValue}%</span> )} </div> <div className={cn( 'w-full bg-white/10 rounded-full overflow-hidden', tierStyles[tier] )}> <motion.div className={cn( 'h-full rounded-full', clampedValue >= 50 ? 'bg-gradient-to-r from-emerald-500 to-green-400' : 'bg-gradient-to-r from-amber-500 to-orange-400' )} initial={{ width: 0 }} animate={{ width: ${clampedValue}% }} transition={{ type: 'spring', stiffness: 100, damping: 15 }} /> </div> </div> ); }

Game Card Component

import { motion } from 'framer-motion';

interface GameCardProps { suit: 'hearts' | 'diamonds' | 'clubs' | 'spades'; rank: string; faceDown?: boolean; onClick?: () => void; }

export function GameCard({ suit, rank, faceDown = false, onClick }: GameCardProps) { const isRed = suit === 'hearts' || suit === 'diamonds';

return ( <motion.div className="relative w-[90px] h-[126px] cursor-pointer perspective-1000" onClick={onClick} whileHover={{ y: -8, transition: { duration: 0.2 } }} whileTap={{ scale: 0.95 }} > <motion.div className="w-full h-full relative" style={{ transformStyle: 'preserve-3d' }} animate={{ rotateY: faceDown ? 180 : 0 }} transition={{ duration: 0.6, type: 'spring' }} > {/* Front */} <div className={cn( 'absolute inset-0 rounded-lg shadow-lg backface-hidden', 'bg-white border-2 border-gray-200', 'flex flex-col items-center justify-center' )} > <span className={cn( 'text-2xl font-bold', isRed ? 'text-red-600' : 'text-gray-900' )}> {rank} </span> <span className="text-3xl">{suitSymbol(suit)}</span> </div>

    {/* Back */}
    &#x3C;div 
      className="absolute inset-0 rounded-lg shadow-lg backface-hidden bg-gradient-to-br from-indigo-600 to-purple-700"
      style={{ transform: 'rotateY(180deg)' }}
    >
      &#x3C;div className="w-full h-full rounded-lg border-4 border-white/20 flex items-center justify-center">
        &#x3C;div className="w-12 h-12 rounded-full bg-white/10" />
      &#x3C;/div>
    &#x3C;/div>
  &#x3C;/motion.div>
&#x3C;/motion.div>

); }

function suitSymbol(suit: string) { const symbols = { hearts: '♥', diamonds: '♦', clubs: '♣', spades: '♠' }; return symbols[suit] || ''; }

Micro-Interactions

Button with Feedback

import { Button } from '@/components/ui/button'; import { motion } from 'framer-motion';

export function GameButton({ children, onClick, variant = 'default', ...props }: React.ComponentProps<typeof Button>) { return ( <Button asChild variant={variant} onClick={onClick} {...props} > <motion.button whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} transition={{ type: 'spring', stiffness: 400, damping: 17 }} > {children} </motion.button> </Button> ); }

Coin Pop Animation

import { motion, AnimatePresence } from 'framer-motion';

interface CoinPopProps { amount: number; position: { x: number; y: number }; onComplete: () => void; }

export function CoinPop({ amount, position, onComplete }: CoinPopProps) { return ( <motion.div className="fixed pointer-events-none z-50 text-2xl font-bold text-yellow-400" style={{ left: position.x, top: position.y }} initial={{ opacity: 1, y: 0, scale: 0.5 }} animate={{ opacity: 0, y: -60, scale: 1.2 }} exit={{ opacity: 0 }} transition={{ duration: 1, ease: 'easeOut' }} onAnimationComplete={onComplete} > +{amount} 🪙 </motion.div> ); }

Shake on Error

const shakeAnimation = { x: [0, -10, 10, -10, 10, 0], transition: { duration: 0.4 } };

export function ShakeOnError({ error, children }) { return ( <motion.div animate={error ? shakeAnimation : {}}> {children} </motion.div> ); }

Layout Patterns

Game HUD Layout

export function GameLayout({ children }: { children: React.ReactNode }) { return ( <div className="relative w-full h-screen overflow-hidden bg-gradient-to-br from-stone-900 via-stone-950 to-black"> {/* Top Bar */} <header className="absolute top-0 left-0 right-0 z-20 p-4 flex justify-between items-start"> <ResourceBar icon={<Flower />} value={resources.tulipBulbs} label="Tulips" color="green" /> <Scoreboard day={time.day} score={score} /> </header>

  {/* Main Content */}
  &#x3C;main className="absolute inset-0 flex items-center justify-center pt-20 pb-20">
    {children}
  &#x3C;/main>
  
  {/* Bottom Bar */}
  &#x3C;footer className="absolute bottom-0 left-0 right-0 z-20 p-4 flex justify-center">
    &#x3C;PhaseControls />
  &#x3C;/footer>
  
  {/* Side Panel */}
  &#x3C;aside className="absolute top-20 right-0 bottom-20 z-10 w-80 p-4">
    &#x3C;MetaPotPanel />
  &#x3C;/aside>
&#x3C;/div>

); }

Responsive Game Board

export function GameBoard({ children }: { children: React.ReactNode }) { return ( <div className="w-full h-full flex items-center justify-center p-4"> <div className={cn( 'relative', 'w-full max-w-[min(90vw,90vh)]', 'aspect-square' )}> {children} </div> </div> ); }

Modal & Dialog Patterns

Game Modal with shadcn

import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { motion, AnimatePresence } from 'framer-motion';

export function GameModal({ open, onOpenChange, title, children }) { return ( <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="bg-gradient-to-br from-stone-800 to-stone-900 border-amber-500/30"> <DialogHeader> <DialogTitle className="text-amber-400 text-xl">{title}</DialogTitle> </DialogHeader> <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} > {children} </motion.div> </DialogContent> </Dialog> ); }

Tooltip for Game Elements

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip';

export function GameTooltip({ children, content }) { return ( <TooltipProvider delayDuration={300}> <Tooltip> <TooltipTrigger asChild>{children}</TooltipTrigger> <TooltipContent className="bg-stone-900 border-amber-500/30 text-white" sideOffset={8} > {content} </TooltipContent> </Tooltip> </TooltipProvider> ); }

Accessibility for Games

Skip Animation Preference

import { useReducedMotion } from 'framer-motion';

export function AnimatedElement({ children }) { const shouldReduceMotion = useReducedMotion();

return ( <motion.div animate={{ opacity: 1, y: 0 }} transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.3 }} > {children} </motion.div> ); }

Screen Reader Announcements

import { useEffect, useRef } from 'react';

export function useAnnounce() { const ref = useRef<HTMLDivElement>(null);

const announce = (message: string) => { if (ref.current) { ref.current.textContent = message; } };

return { announce, AnnouncerRegion: () => ( <div ref={ref} role="status" aria-live="polite" aria-atomic="true" className="sr-only" /> )}; }

// Usage const { announce, AnnouncerRegion } = useAnnounce(); announce('You won 500 coins!');

Performance Tips

  • Memoize heavy components: React.memo() for hex cells, cards

  • Use CSS transforms: GPU-accelerated vs layout-triggering properties

  • Virtualize large lists: Only render visible items

  • Debounce rapid updates: Score counters, resource bars

  • Lazy load modals: Don't mount until needed

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

game-engineering-team

No summary provided by upstream source.

Repository SourceNeeds Review
General

game-assets-team

No summary provided by upstream source.

Repository SourceNeeds Review
General

game-concept-advisor

No summary provided by upstream source.

Repository SourceNeeds Review
General

casino-math-balancer

No summary provided by upstream source.

Repository SourceNeeds Review