|
| 1 | +import React, {useMemo} from 'react'; |
| 2 | +import {motion} from 'framer-motion'; |
| 3 | +import { |
| 4 | + SparkleIcon, |
| 5 | + AtomIcon, |
| 6 | +} from '@phosphor-icons/react'; |
| 7 | +import {appIcons} from '../utils/appIcons'; |
| 8 | + |
| 9 | +/** |
| 10 | + * VisionAwakeningCard |
| 11 | + * A brutalist, futuristic card component inspired by the "A New Lens on Reality" design. |
| 12 | + */ |
| 13 | +const VisionAwakeningCard = ({ |
| 14 | + variant = 'dark', |
| 15 | + colors: customColors, |
| 16 | + showFooter = true, |
| 17 | + topBadge, |
| 18 | + title = "", |
| 19 | + description, |
| 20 | + tags = [], |
| 21 | + secondaryTags = [], |
| 22 | + footerText = "", |
| 23 | + subNotes = [] |
| 24 | + }) => { |
| 25 | + const getIcon = (icon) => { |
| 26 | + if (React.isValidElement(icon)) return icon; |
| 27 | + if (typeof icon === 'string' && appIcons[icon]) { |
| 28 | + const IconComp = appIcons[icon]; |
| 29 | + return <IconComp weight="fill"/>; |
| 30 | + } |
| 31 | + return <SparkleIcon weight="fill"/>; |
| 32 | + }; |
| 33 | + const getSecIcon = (icon) => { |
| 34 | + if (React.isValidElement(icon)) return icon; |
| 35 | + if (typeof icon === 'string' && appIcons[icon]) { |
| 36 | + const IconComp = appIcons[icon]; |
| 37 | + return <IconComp weight="bold"/>; |
| 38 | + } |
| 39 | + return <AtomIcon weight="bold"/>; |
| 40 | + }; |
| 41 | + const defaultThemes = { |
| 42 | + dark: { |
| 43 | + container: "bg-[#1a1b3a] text-white border-[#2a2b5a]", |
| 44 | + pattern: "opacity-20 text-blue-400", |
| 45 | + pill: "bg-white/10 border-white/20 text-blue-100", |
| 46 | + pillIcon: "text-blue-300", |
| 47 | + footer: "text-white/60", |
| 48 | + subNote: "text-white/40", |
| 49 | + isDark: true, |
| 50 | + }, light: { |
| 51 | + container: "bg-[#e2e4ff] text-[#1a1b3a] border-white", |
| 52 | + pattern: "opacity-10 text-blue-900", |
| 53 | + pill: "bg-white border-blue-200 text-[#1a1b3a]", |
| 54 | + pillIcon: "text-blue-600", |
| 55 | + footer: "text-[#1a1b3a]/60", |
| 56 | + subNote: "text-[#1a1b3a]/40", |
| 57 | + isDark: false, |
| 58 | + }, emerald: { |
| 59 | + container: "bg-[#062c24] text-[#a7f3d0] border-[#064e3b]", |
| 60 | + pattern: "opacity-20 text-[#10b981]", |
| 61 | + pill: "bg-[#064e3b]/50 border-[#10b981]/30 text-[#a7f3d0]", |
| 62 | + pillIcon: "text-[#34d399]", |
| 63 | + footer: "text-[#a7f3d0]/60", |
| 64 | + subNote: "text-[#a7f3d0]/40", |
| 65 | + isDark: true, |
| 66 | + }, amber: { |
| 67 | + container: "bg-[#451a03] text-[#fde68a] border-[#78350f]", |
| 68 | + pattern: "opacity-20 text-[#f59e0b]", |
| 69 | + pill: "bg-[#78350f]/50 border-[#f59e0b]/30 text-[#fde68a]", |
| 70 | + pillIcon: "text-[#fbbf24]", |
| 71 | + footer: "text-[#fde68a]/60", |
| 72 | + subNote: "text-[#fde68a]/40", |
| 73 | + isDark: true, |
| 74 | + }, crimson: { |
| 75 | + container: "bg-[#450a0a] text-[#fecaca] border-[#7f1d1d]", |
| 76 | + pattern: "opacity-20 text-[#ef4444]", |
| 77 | + pill: "bg-[#7f1d1d]/50 border-[#ef4444]/30 text-[#fecaca]", |
| 78 | + pillIcon: "text-[#f87171]", |
| 79 | + footer: "text-[#fecaca]/60", |
| 80 | + subNote: "text-[#fecaca]/40", |
| 81 | + isDark: true, |
| 82 | + } |
| 83 | + }; |
| 84 | + const themeClasses = customColors || defaultThemes[variant] || defaultThemes.dark; |
| 85 | + const isCustomDark = themeClasses.isDark; |
| 86 | + // Simulate the character pattern background |
| 87 | + const patternLines = useMemo(() => { |
| 88 | + const patternChars = "X01%&*#@+=-:. ".split(""); |
| 89 | + return Array.from({length: 60}).map(() => Array.from({length: 150}).map(() => patternChars[Math.floor(Math.random() * patternChars.length)]).join("")); |
| 90 | + }, []); |
| 91 | + return (<motion.div |
| 92 | + className={`relative w-full max-w-xl h-[520px] md:h-[620px] rounded-[2rem] overflow-hidden border-8 p-6 md:p-8 flex flex-col transition-colors duration-500 group ${themeClasses.container}`} |
| 93 | + > {/* Background Character Pattern */} |
| 94 | + <div |
| 95 | + className={`absolute inset-0 font-mono text-[6px] md:text-[8px] leading-none select-none pointer-events-none overflow-hidden whitespace-pre break-all ${themeClasses.pattern}`}> |
| 96 | + {patternLines.join('\n')} |
| 97 | + </div> |
| 98 | + {/* Hover Diagonal Scanline */} |
| 99 | + <div |
| 100 | + className="absolute inset-0 z-[5] pointer-events-none overflow-hidden opacity-0 group-hover:opacity-100 transition-opacity duration-500"> |
| 101 | + <motion.div |
| 102 | + animate={{ |
| 103 | + x: ['-100%', '100%'], y: ['-100%', '100%'], |
| 104 | + }} |
| 105 | + transition={{ |
| 106 | + duration: 2, repeat: Infinity, ease: "linear" |
| 107 | + }} |
| 108 | + className="absolute inset-0 bg-gradient-to-br from-transparent via-white/10 to-transparent h-[200%] w-[200%] -top-[50%] -left-[50%]" |
| 109 | + style={{rotate: '45deg'}} |
| 110 | + /> |
| 111 | + </div> |
| 112 | + {/* Top Header Section */} |
| 113 | + <div className="relative z-10 flex justify-between items-start"> |
| 114 | + <div className="w-10 h-10 md:w-16 md:h-16 mb-8"> |
| 115 | + <AtomIcon size="100%" weight="duotone" className={isCustomDark ? "text-blue-300" : "text-blue-600"}/> |
| 116 | + </div> |
| 117 | + <div |
| 118 | + className={`px-3 py-1 rounded-sm border text-[10px] font-mono uppercase tracking-widest ${themeClasses.pill} ${topBadge ? 'opacity-100' : 'opacity-0'}`}> |
| 119 | + {topBadge || '---'} |
| 120 | + </div> |
| 121 | + </div> |
| 122 | + <div className="relative z-10 space-y-3"> |
| 123 | + <h2 className="text-2xl md:text-4xl font-outfit font-black uppercase leading-tight tracking-tight max-w-[95%]"> |
| 124 | + {title} |
| 125 | + </h2> |
| 126 | + {description && (<p |
| 127 | + className="font-mono text-[10px] md:text-xs text-current opacity-70 leading-relaxed uppercase tracking-wider"> |
| 128 | + {description} |
| 129 | + </p>)} |
| 130 | + </div> |
| 131 | + {/* Tags Grid */} |
| 132 | + <div className="relative z-10 space-y-4 mt-6 flex-grow overflow-y-auto"> |
| 133 | + <div className="flex flex-wrap gap-2 md:gap-3"> |
| 134 | + {tags.map((tag, i) => { |
| 135 | + const TagComp = tag.href ? 'a' : 'div'; |
| 136 | + return (<TagComp |
| 137 | + key={i} |
| 138 | + href={tag.href} |
| 139 | + target={tag.href ? "_blank" : undefined} |
| 140 | + rel={tag.href ? "noopener noreferrer" : undefined} |
| 141 | + className={`flex items-center gap-2 px-3 md:px-4 py-2 md:py-2.5 rounded-full border transition-all min-w-0 ${themeClasses.pill} ${tag.href ? `border-dashed hover:border-solid hover:bg-current/[0.2] cursor-pointer group/tag` : 'opacity-90'}`} |
| 142 | + > |
| 143 | + <span className={`text-base md:text-xl flex-shrink-0 ${themeClasses.pillIcon}`}> |
| 144 | + {getIcon(tag.icon)} |
| 145 | + </span> |
| 146 | + <span |
| 147 | + className="font-syne font-bold text-[9px] md:text-xs uppercase tracking-wide min-w-0 flex items-center gap-1"> |
| 148 | + {tag.label} |
| 149 | + {tag.href && <span |
| 150 | + className="text-[10px] opacity-40 group-hover/tag:opacity-100 group-hover/tag:translate-x-0.5 transition-all">↗</span>} |
| 151 | + </span> |
| 152 | + </TagComp>); |
| 153 | + })} |
| 154 | + </div> |
| 155 | + {/* Secondary Wide Tags */} |
| 156 | + <div className="space-y-2"> |
| 157 | + {secondaryTags.map((tag, i) => { |
| 158 | + const TagComp = tag.href ? 'a' : 'div'; |
| 159 | + return (<TagComp |
| 160 | + key={i} |
| 161 | + href={tag.href} |
| 162 | + target={tag.href ? "_blank" : undefined} |
| 163 | + rel={tag.href ? "noopener noreferrer" : undefined} |
| 164 | + className={`flex items-center justify-between px-5 py-2 md:py-2.5 rounded-full border text-[9px] md:text-xs font-mono uppercase tracking-widest overflow-hidden transition-all ${themeClasses.pill} ${tag.href ? `border-dashed hover:border-solid hover:bg-current/[0.1] cursor-pointer group/sectag` : ''}`} |
| 165 | + > |
| 166 | + <div className="flex items-center gap-2 min-w-0 flex-1"> |
| 167 | + <span className={`flex-shrink-0 ${themeClasses.pillIcon}`}>{getSecIcon(tag.icon)}</span> |
| 168 | + <span className="break-words"> |
| 169 | + {tag.label} |
| 170 | + {tag.href && <span |
| 171 | + className="ml-2 opacity-40 group-hover/sectag:opacity-100 inline-block group-hover/sectag:translate-x-0.5 transition-all">↗</span>} |
| 172 | + </span> |
| 173 | + </div> |
| 174 | + {!tag.href && (<div className="w-1.5 h-1.5 rounded-full bg-current opacity-50 flex-shrink-0 ml-2"/>)} |
| 175 | + </TagComp>); |
| 176 | + })} |
| 177 | + </div> |
| 178 | + </div> |
| 179 | + {/* Footer Section */} |
| 180 | + {showFooter && (<div |
| 181 | + className="relative z-10 mt-auto pt-6 border-t border-current/10 flex flex-col md:flex-row gap-6 md:gap-10 transition-opacity duration-200" |
| 182 | + > |
| 183 | + <p |
| 184 | + className={`flex-1 text-[10px] md:text-xs font-mono leading-relaxed uppercase tracking-tighter ${themeClasses.footer}`}> |
| 185 | + {footerText} |
| 186 | + </p> |
| 187 | + <div className="flex gap-4 md:w-1/3"> |
| 188 | + {subNotes.map((note, i) => (<p key={i} className={`text-[8px] font-mono leading-tight ${themeClasses.subNote}`}> |
| 189 | + {note} |
| 190 | + </p>))} |
| 191 | + </div> |
| 192 | + </div>)} |
| 193 | + {/* Subtle Overlay Glow */} |
| 194 | + <div className="absolute inset-0 bg-gradient-to-br from-white/5 to-transparent pointer-events-none"/> |
| 195 | + </motion.div>); |
| 196 | +}; |
| 197 | +export default VisionAwakeningCard; |
0 commit comments