1const e=`import { useState } from "react"; 2import { Download, Search, Trash2 } from "lucide-react"; 3import { Input } from "@/components/ui/input"; 4import { Button } from "@/components/ui/button"; 5 6import cmykSwatches from "@/assets/cmyk-swatches.jpg"; 7import brandColors from "@/assets/brand-colors.png"; 8import pourifyAnim from "@/assets/pourify-animation.gif"; 9import illus1 from "@/assets/illustration-pourify-1.png"; 10import illus2 from "@/assets/illustration-pourify-2.png"; 11import illus3 from "@/assets/illustration-pourify-3.png"; 12import illus4 from "@/assets/illustration-pourify-4.png"; 13import illus5 from "@/assets/illustration-pourify-5.png"; 14import logoCombined from "@/assets/logo-pourify-combined.png"; 15import logoCombinedSvg from "@/assets/logo-pourify-combined.svg"; 16 17const allAssets = [ 18 { name: "Smart Pourer", file: "/src/assets/smartpourer.png", category: "Product" }, 19 { name: "Pourer Close-up", file: "/src/assets/pourer.png", category: "Product" }, 20 { name: "Hub", file: "/src/assets/hub.png", category: "Product" }, 21 { name: "Starter Kit Packaging", file: "/src/assets/packaging-starter-kit.png", category: "Packaging" }, 22 { name: "5-Pack Dark", file: "/src/assets/packaging-5pack-dark.png", category: "Packaging" }, 23 { name: "5-Pack Purple", file: "/src/assets/packaging-5pack-purple.png", category: "Packaging" }, 24 { name: "Lifestyle - Bar", file: "/src/assets/lifestyle-bar.jpg", category: "Lifestyle" }, 25 { name: "Lifestyle - Bartender", file: "/src/assets/lifestyle-bartender.jpg", category: "Lifestyle" }, 26 { name: "Lifestyle - Pouring", file: "/src/assets/lifestyle-pouring.jpg", category: "Lifestyle" }, 27 { name: "Dashboard", file: "/src/assets/app-dashboard-new.png", category: "Screenshots" }, 28 { name: "AI Insights", file: "/src/assets/app-ai-insights.png", category: "Screenshots" }, 29 { name: "POS Integration", file: "/src/assets/app-pos-products.png", category: "Screenshots" }, 30 { name: "Illustration - Bartender", file: illus1, category: "Illustrations" }, 31 { name: "Illustration - Bar Scene", file: illus2, category: "Illustrations" }, 32 { name: "Illustration - Cocktail", file: illus3, category: "Illustrations" }, 33 { name: "Illustration - Party", file: illus4, category: "Illustrations" }, 34 { name: "Illustration - Bar Shelf", file: illus5, category: "Illustrations" }, 35 { name: "Brand Animation (GIF)", file: pourifyAnim, category: "Animations" }, 36 { name: "Brand Animation (MP4)", file: "/videos/pourify-brand-animation.mp4", category: "Animations" }, 37 { name: "CMYK Swatches", file: cmykSwatches, category: "Color References" }, 38 { name: "Brand Color Strips", file: brandColors, category: "Color References" }, 39 { name: "Logo Combined (PNG)", file: logoCombined, category: "Logos" }, 40 { name: "Logo Combined (SVG)", file: logoCombinedSvg, category: "Logos" }, 41 { name: "Roobert Regular (WOFF2)", file: "/fonts/Roobert-Regular.woff2", category: "Fonts" }, 42 { name: "Roobert Regular (OTF)", file: "/fonts/Roobert-Regular.otf", category: "Fonts" }, 43 { name: "Roobert Medium (WOFF2)", file: "/fonts/Roobert-Medium.woff2", category: "Fonts" }, 44 { name: "Roobert Medium (OTF)", file: "/fonts/Roobert-Medium.otf", category: "Fonts" }, 45 { name: "Roobert SemiBold (WOFF2)", file: "/fonts/Roobert-SemiBold.woff2", category: "Fonts" }, 46 { name: "Roobert SemiBold (OTF)", file: "/fonts/Roobert-SemiBold.otf", category: "Fonts" }, 47 { name: "Reckless Regular (WOFF2)", file: "/fonts/Reckless-Regular.woff2", category: "Fonts" }, 48 { name: "Reckless Regular (OTF)", file: "/fonts/Reckless-Regular.otf", category: "Fonts" }, 49 { name: "Reckless Light (WOFF2)", file: "/fonts/Reckless-Light.woff2", category: "Fonts" }, 50 { name: "Reckless Light (OTF)", file: "/fonts/Reckless-Light.otf", category: "Fonts" }, 51]; 52 53const categories = ["All", "Product", "Packaging", "Lifestyle", "Screenshots", "Illustrations", "Animations", "Color References", "Logos", "Fonts"]; 54 55interface BrandAssetsProps { 56 isAdmin?: boolean; 57} 58 59const BrandAssets = ({ isAdmin = false }: BrandAssetsProps) => { 60 const [search, setSearch] = useState(""); 61 const [category, setCategory] = useState("All"); 62 const [hiddenAssets, setHiddenAssets] = useState<Set<string>>(() => { 63 try { 64 const stored = localStorage.getItem("brand-hidden-assets"); 65 return stored ? new Set(JSON.parse(stored)) : new Set(); 66 } catch { 67 return new Set(); 68 } 69 }); 70 71 const assets = allAssets.filter((a) => !hiddenAssets.has(a.name)); 72 73 const filtered = assets.filter((a) => { 74 const matchCat = category === "All" || a.category === category; 75 const matchSearch = a.name.toLowerCase().includes(search.toLowerCase()); 76 return matchCat && matchSearch; 77 }); 78 79 const download = (url: string, name: string) => { 80 const a = document.createElement("a"); 81 a.href = url;
82 a.download = name; 83 a.click(); 84 }; 85 86 const hideAsset = (name: string) => { 87 if (!window.confirm(\`Are you sure you want to remove "\${name}" from the asset library?\`)) return; 88 const next = new Set(hiddenAssets); 89 next.add(name); 90 setHiddenAssets(next); 91 localStorage.setItem("brand-hidden-assets", JSON.stringify([...next])); 92 }; 93 94 const restoreAll = () => { 95 setHiddenAssets(new Set()); 96 localStorage.removeItem("brand-hidden-assets"); 97 }; 98 99 return ( 100 <section> 101 <div className="flex items-center justify-between mb-1"> 102 <h2 className="text-lg font-semibold text-foreground">Marketing Assets</h2> 103 {isAdmin && hiddenAssets.size > 0 && ( 104 <Button variant="ghost" size="sm" onClick={restoreAll} className="text-xs text-muted-foreground"> 105 Restore {hiddenAssets.size} hidden 106 </Button> 107 )} 108 </div> 109 <p className="text-muted-foreground text-sm mb-6">Product photos, packaging, lifestyle imagery, and app screenshots.</p> 110 111 <div className="flex flex-col sm:flex-row gap-3 mb-6"> 112 <div className="relative flex-1"> 113 <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground/50" /> 114 <Input 115 value={search} 116 onChange={(e) => setSearch(e.target.value)} 117 placeholder="Search assetsâ¦" 118 className="pl-10 h-9 rounded-lg" 119 /> 120 </div> 121 <div className="flex gap-1.5 flex-wrap"> 122 {categories.map((c) => ( 123 <button 124 key={c} 125 onClick={() => setCategory(c)} 126 className={\`px-3 py-1.5 rounded-full text-xs font-medium transition-colors \${ 127 category === c 128 ? "bg-primary text-primary-foreground" 129 : "bg-muted text-muted-foreground hover:text-foreground" 130 }\`} 131 > 132 {c} 133 </button> 134 ))} 135 </div> 136 </div> 137 138 <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3"> 139 {filtered.map((a) => ( 140 <div key={a.name} className="bg-card border border-border rounded-xl overflow-hidden group relative"> 141 <div className="bg-muted/50 h-36 flex items-center justify-center p-4"> 142 {a.category === "Fonts" ? ( 143 <div className="text-center"> 144 <Download className="h-8 w-8 text-muted-foreground/40 mx-auto mb-1" /> 145 <p className="text-muted-foreground text-[10px]">Font file</p> 146 </div> 147 ) : a.file.endsWith(".mp4") ? ( 148 <video src={a.file} className="max-h-full max-w-full object-contain" muted autoPlay loop playsInline /> 149 ) : ( 150 <img src={a.file} alt={a.name} className="max-h-full max-w-full object-contain" /> 151 )} 152 </div> 153 <div className="p-3 flex items-center justify-between"> 154 <div className="min-w-0"> 155 <p className="text-foreground/70 text-xs font-medium truncate">{a.name}</p> 156 <p className="text-muted-foreground text-[10px] mt-0.5">{a.category}</p> 157 </div> 158 <div className="flex items-center gap-0.5 shrink-0"> 159 <Button 160 size="sm" 161 variant="ghost" 162 className="text-muted-foreground hover:text-foreground h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity" 163 onClick={() => download(a.file, a.file.split("/").pop()!)} 164 > 165 <Download className="h-3.5 w-3.5" /> 166 </Button> 167 {isAdmin && ( 168 <Button 169 size="sm" 170 variant="ghost" 171 className="text-muted-foreground hover:text-destructive h-7 w-7 p-0 opacity-0 group-hover:opacity-100 transition-opacity" 172 onClick={() => hideAsset(a.name)} 173 title="Remove asset" 174 > 175 <Trash2 className="h-3.5 w-3.5" /> 176 </Button> 177 )} 178 </div> 179 </div> 180 </div> 181 ))} 182 </div> 183 </section> 184 ); 185}; 186 187export default BrandAssets; 188`;export{e as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.