1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-w5lXls15.js"; 3import { p as getPlaceholderUrl } from "./utils-CEVb7nN1.js"; 4import { b as getGeminiAI, c as getAiImageModel } from "./ai-B0Mlroaa.js"; 5import { D as DevDebugInfo } from "./AdminShared-BP_vxG_A.js"; 6import { bi as Sparkles, cf as Plus, c_ as ChevronLeft, bF as ChevronRight, f0 as PenLine, b5 as Trash2, aW as X, d9 as Image, b2 as Upload, b6 as LoaderCircle, aY as TriangleAlert, dM as Move, ds as ZoomOut, dt as ZoomIn, b7 as Check, cw as LayoutGrid, dO as Calendar, bb as User, bD as FlaskConical, bW as RefreshCcw, a_ as CircleCheck, fZ as Package, bQ as House, bP as Compass, bO as Library, bN as History, bB as ListMusic, bM as Clock, bL as ThumbsUp, b9 as Settings, be as Menu, bf as Search, bT as Mic, bh as Bell, cd as Save } from "./vendor-D0P4XgNj.js"; 7import { D as DEFAULT_TOOLS, a as DEFAULT_BRAND_SVG, H as HamieLogo } from "./labTools-HX5HYAPB.js"; 8import "./vendor-i18n-BC7ZMXWQ.js"; 9import "./vendor-ai-gQv1oOjO.js"; 10const LogoScheduler = () => { 11 const [schedules, setSchedules] = reactExports.useState([]); 12 const [currentDate, setCurrentDate] = reactExports.useState(/* @__PURE__ */ new Date()); 13 const [isModalOpen, setIsModalOpen] = reactExports.useState(false); 14 const [isSuggestModalOpen, setIsSuggestModalOpen] = reactExports.useState(false); 15 const [editingId, setEditingId] = reactExports.useState(null); 16 const [formData, setFormData] = reactExports.useState({ 17 themeName: "", 18 startDate: "", 19 endDate: "", 20 imageUrl: "", 21 isRecurring: false, 22 priority: 0, 23 aiPrompt: "", 24 aiModel: "" 25 }); 26 const [aiPrompt, setAiPrompt] = reactExports.useState(""); 27 const [generating, setGenerating] = reactExports.useState(false); 28 const [suggestions, setSuggestions] = reactExports.useState([]); 29 const [suggesting, setSuggesting] = reactExports.useState(false); 30 const [suggestionError, setSuggestionError] = reactExports.useState(null); 31 reactExports.useEffect(() => { 32 fetchSchedules(); 33 }, []); 34 const fetchSchedules = async () => { 35 try { 36 const res = await fetch("/api/logo/schedules"); 37 if (res.ok) { 38 const data = await res.json(); 39 setSchedules(data); 40 } 41 } catch { 42 } 43 }; 44 const handleSave = async () => { 45 try { 46 const url = editingId ? `/api/logo/schedules/${editingId}` : "/api/logo/schedules"; 47 const method = editingId ? "PUT" : "POST"; 48 const res = await fetch(url, { 49 method, 50 headers: { "Content-Type": "application/json" }, 51 body: JSON.stringify(formData) 52 }); 53 if (res.ok) { 54 setIsModalOpen(false); 55 fetchSchedules(); 56 resetForm(); 57 } 58 } catch { 59 alert("Failed to save schedule"); 60 } 61 }; 62 const handleDelete = async (id) => { 63 if (!confirm("Are you sure?")) return; 64 try { 65 await fetch(`/api/logo/schedules/${id}`, { method: "DELETE" }); 66 fetchSchedules(); 67 } catch { 68 alert("Failed to delete"); 69 } 70 }; 71 const handleUpload = async (e) => { 72 var _a; 73 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 74 if (!file) return; 75 const data = new FormData(); 76 data.append("image", file); 77 try { 78 const res = await fetch("/api/logo/upload", { 79 method: "POST", 80 body: data 81 }); 82 if (res.ok) { 83 const { url } = await res.json(); 84 setFormData({ ...formData, imageUrl: url, aiPrompt: "", aiModel: "" }); 85 } 86 } catch { 87 alert("Upload failed"); 88 } 89 }; 90 const handleGenerate = async () => { 91 if (!aiPrompt) return; 92 setGenerating(true); 93 try { 94 const res = await fetch("/api/logo/generate", { 95 method: "POST", 96 headers: { "Content-Type": "application/json" }, 97 body: JSON.stringify({ prompt: aiPrompt }) 98 }); 99 if (res.ok) { 100 const { url, model } = await res.json(); 101 setFormData({ ...formData, imageUrl: url, aiPrompt, aiModel: model }); 102 } else { 103 alert("Generation failed"); 104 } 105 } catch { 106 alert("Generation error"); 107 } finally { 108 setGenerating(false); 109 } 110 }; 111 const handleSuggest = async () => { 112 setSuggesting(true); 113 setSuggestionError(null); 114 try { 115 const res = await fetch("/api/logo/suggest", { 116 method: "POST", 117 headers: { "Content-Type": "application/json" }, 118 body: JSON.stringify({ context: `Month: ${currentDate.toLocaleStr
118ing("default", { month: "long" })}` }) 119 }); 120 if (res.ok) { 121 const data = await res.json(); 122 setSuggestions(data); 123 if (!data || data.length === 0) { 124 setSuggestionError("No suggestions found for the current period."); 125 } 126 } else { 127 const errData = await res.json().catch(() => ({})); 128 throw new Error(errData.error || "Failed to fetch suggestions"); 129 } 130 } catch (error) { 131 console.error("Failed to get suggestions:", error); 132 setSuggestionError(error.message || "An unexpected error occurred while fetching suggestions."); 133 api.logError(error, "LogoScheduler:handleSuggest"); 134 } finally { 135 setSuggesting(false); 136 } 137 }; 138 const resetForm = () => { 139 setEditingId(null); 140 setFormData({ 141 themeName: "", 142 startDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0], 143 endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0], 144 imageUrl: "", 145 isRecurring: false, 146 priority: 0, 147 aiPrompt: "", 148 aiModel: "" 149 }); 150 setAiPrompt(""); 151 }; 152 const openModal = (schedule) => { 153 if (schedule) { 154 setEditingId(schedule.id); 155 setFormData({ 156 themeName: schedule.themeName, 157 startDate: new Date(schedule.startDate).toISOString().split("T")[0], 158 endDate: new Date(schedule.endDate).toISOString().split("T")[0], 159 imageUrl: schedule.imageUrl, 160 isRecurring: schedule.isRecurring, 161 priority: schedule.priority, 162 aiPrompt: schedule.aiPrompt || "", 163 aiModel: schedule.aiModel || "" 164 }); 165 } else { 166 resetForm(); 167 } 168 setIsModalOpen(true); 169 }; 170 const getDaysInMonth = (date) => { 171 const year = date.getFullYear();
172 const month = date.getMonth(); 173 const days2 = new Date(year, month + 1, 0).getDate(); 174 const firstDay2 = new Date(year, month, 1).getDay(); 175 return { days: days2, firstDay: firstDay2 }; 176 }; 177 const { days, firstDay } = getDaysInMonth(currentDate); 178 const monthName = currentDate.toLocaleString("default", { month: "long", year: "numeric" }); 179 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full space-y-6", children: [ 180 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [ 181 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 182 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "Logo Scheduler" }), 183 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Manage dynamic logo changes based on dates. Ideal size: 400x100px (approx 4:1 aspect ratio)." }) 184 ] }), 185 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
186 /* @__PURE__ */ jsxRuntimeExports.jsxs( 187 "button", 188 { 189 onClick: () => { 190 setIsSuggestModalOpen(true); 191 handleSuggest(); 192 }, 193 className: "flex items-center gap-2 px-4 py-2 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 rounded-lg font-bold text-sm hover:bg-indigo-200 dark:hover:bg-indigo-800 transition-colors", 194 children: [ 195 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 16 }), 196 " Suggest Themes" 197 ] 198 } 199 ), 200 /* @__PURE__ */ jsxRuntimeExports.jsxs( 201 "button", 202 { 203 onClick: () => openModal(), 204 className: "flex items-center gap-2 px-4 py-2 bg-zinc-900 dark:bg-white text-white dark:text-black rounded-lg font-bold text-sm hover:opacity-90 transition-opacity", 205 children: [ 206 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16 }), 207 " Add Schedule" 208 ] 209 } 210 ) 211 ] }) 212 ] }), 213 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col lg:flex-row gap-6 h-full overflow-hidden", children: [
214 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-6 shadow-sm flex flex-col", children: [ 215 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6", children: [ 216 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg", children: monthName }), 217 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 218 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCurrentDate((prev) => new Date(prev.getFullYear(), prev.getMonth() - 1, 1)), className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronLeft, { size: 20 }) }), 219 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setCurrentDate((prev) => new Date(prev.getFullYear(), prev.getMonth() + 1, 1)), className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { size: 20 }) }) 220 ] }) 221 ] }),
222 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-7 gap-px bg-zinc-200 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden flex-1", children: [ 223 ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((day) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-zinc-50 dark:bg-zinc-900 p-2 text-center text-xs font-bold text-zinc-500 uppercase", children: day }, day)), 224 Array.from({ length: firstDay }).map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white dark:bg-zinc-900 min-h-[80px]" }, `empty-${i}`)), 225 Array.from({ length: days }).map((_, i) => { 226 const day = i + 1; 227 const dateStr = new Date(currentDate.getFullYear(), currentDate.getMonth(), day).toISOString().split("T")[0]; 228 const daySchedules = schedules.filter((s) => { 229 const start = new Date(s.startDate).toISOString().split("T")[0]; 230 const end = new Date(s.endDate).toISOString().split("T")[0]; 231 return dateStr >= start && dateStr <= end; 232 }); 233 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 p-2 min-h-[80px] border-t border-zinc-100 dark:border-zinc-800/50 relative group hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors", children: [
234 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-sm font-bold ${daySchedules.length > 0 ? "text-indigo-600 dark:text-indigo-400" : "text-zinc-400"}`, children: day }), 235 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 space-y-1", children: daySchedules.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx( 236 "div", 237 { 238 onClick: () => openModal(s), 239 className: "text-[10px] bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 px-1.5 py-0.5 rounded truncate cursor-pointer hover:bg-indigo-100 dark:hover:bg-indigo-900/50", 240 title: s.themeName, 241 children: s.themeName 242 }, 243 s.id 244 )) }) 245 ] }, day); 246 }) 247 ] }) 248 ] }), 249 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full lg:w-80 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-4 shadow-sm overflow-y-auto", children: [
250 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-sm uppercase text-zinc-500 mb-4", children: "Upcoming Events" }), 251 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [ 252 schedules.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()).map((s) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 p-3 bg-zinc-50 dark:bg-zinc-950/50 rounded-lg border border-zinc-100 dark:border-zinc-800 group", children: [ 253 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-white dark:bg-zinc-900 rounded border border-zinc-200 dark:border-zinc-800 flex items-center justify-center overflow-hidden shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: s.imageUrl, className: "w-full h-full object-contain", referrerPolicy: "no-referrer" }) }), 254 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [ 255 /* @__P
255URE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-sm truncate text-zinc-900 dark:text-white", children: s.themeName }), 256 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-zinc-500", children: [ 257 new Date(s.startDate).toLocaleDateString(void 0, { month: "short", day: "numeric" }), 258 s.isRecurring && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 text-indigo-500", children: "(Recurring)" }) 259 ] }) 260 ] }), 261 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity", children: [ 262 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => openModal(s), className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(PenLine, { size: 14 }) }), 263 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => handleDelete(s.id), className: "p-1.5 hover:bg-red-100 dark:hover:bg-red-900/30 rounded text-red-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) }) 264 ] }) 265 ] }, s.id)), 266 schedules.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-center text-zinc-400 text-sm py-4", children: "No upcoming schedules." }) 267 ] }) 268 ] }) 269 ] }), 270 isModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4", onClick: () => setIsModalOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 w-full max-w-2xl rounded-2xl shadow-2xl border border-zinc-200 dark:border-zinc-800 overflow-hidden flex flex-col max-h-[90vh]", onClick: (e) => e.stopPropagation(), children: [
271 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center", children: [ 272 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: editingId ? "Edit Schedule" : "New Schedule" }), 273 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsModalOpen(false), className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 24 }) }) 274 ] }), 275 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-6 overflow-y-auto space-y-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-6", children: [ 276 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 277 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
278 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-1", children: "Theme Name" }), 279 /* @__PURE__ */ jsxRuntimeExports.jsx( 280 "input", 281 { 282 value: formData.themeName, 283 onChange: (e) => setFormData({ ...formData, themeName: e.target.value }), 284 className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded-lg p-2.5 text-sm outline-none focus:border-indigo-500", 285 placeholder: "e.g. Beethoven's Birthday" 286 } 287 ) 288 ] }), 289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [ 290 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 291 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-1", children: "Start Date" }), 292 /* @__PURE__ */ jsxRuntimeExports.jsx( 293 "input", 294 { 295 type: "date", 296 value: formData.startDate, 297 onChange: (e) => setFormData({ ...formData, startDate: e.target.value }), 298 className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded-lg p-2.5 text-sm outline-none focus:border-indigo-500" 299 } 300 ) 301 ] }),
302 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 303 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-1", children: "End Date" }), 304 /* @__PURE__ */ jsxRuntimeExports.jsx( 305 "input", 306 { 307 type: "date", 308 value: formData.endDate, 309 onChange: (e) => setFormData({ ...formData, endDate: e.target.value }), 310 className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded-lg p-2.5 text-sm outline-none focus:border-indigo-500" 311 } 312 ) 313 ] }) 314 ] }), 315 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 316 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [ 317 /* @__PURE__ */ jsxRuntimeExports.jsx( 318 "input", 319 { 320 type: "checkbox", 321 checked: formData.isRecurring, 322 onChange: (e) => setFormData({ ...formData, isRecurring: e.target.checked }), 323 className: "w-4 h-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500" 324 } 325 ),
326 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium", children: "Recurring Yearly" }) 327 ] }), 328 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [ 329 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-1", children: "Priority" }), 330 /* @__PURE__ */ jsxRuntimeExports.jsx( 331 "input", 332 { 333 type: "number", 334 value: formData.priority, 335 onChange: (e) => setFormData({ ...formData, priority: parseInt(e.target.value) }), 336 className: "w-full bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded-lg p-2 text-sm outline-none focus:border-indigo-500" 337 } 338 ) 339 ] }) 340 ] }) 341 ] }), 342 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [ 343 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-1", children: "Logo Image" }),
344 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full h-32 bg-zinc-100 dark:bg-zinc-950 border-2 border-dashed border-zinc-300 dark:border-zinc-700 rounded-xl flex items-center justify-center relative overflow-hidden group", children: [ 345 formData.imageUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: formData.imageUrl, className: "w-full h-full object-contain", referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center text-zinc-400", children: [ 346 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { className: "mx-auto mb-2" }), 347 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "No Image Selected" }) 348 ] }), 349 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "p-2 bg-white text-black rounded-lg cursor-pointer hover:bg-zinc-200", children: [
350 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 16 }), 351 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: "image/*", onChange: handleUpload }) 352 ] }) }) 353 ] }), 354 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-4 border-t border-zinc-200 dark:border-zinc-800", children: [ 355 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold uppercase text-zinc-500 mb-2", children: "AI Generation" }), 356 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 357 /* @__PURE__ */ jsxRuntimeExports.jsx( 358 "input", 359 { 360 value: aiPrompt, 361 onChange: (e) => setAiPrompt(e.target.value), 362 placeholder: "Describe logo (e.g. Jazz Saxophone)...", 363 className: "flex-1 bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded-lg p-2 text-sm outline-none focus:border-indigo-500" 364 } 365 ),
366 /* @__PURE__ */ jsxRuntimeExports.jsx( 367 "button", 368 { 369 onClick: handleGenerate, 370 disabled: generating || !aiPrompt, 371 className: "px-3 py-2 bg-indigo-600 text-white rounded-lg disabled:opacity-50", 372 children: generating ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin", size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 18 }) 373 } 374 ) 375 ] }) 376 ] }) 377 ] }) 378 ] }) }), 379 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 border-t border-zinc-200 dark:border-zinc-800 flex justify-end gap-3 bg-zinc-50 dark:bg-zinc-950/50", children: [ 380 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsModalOpen(false), className: "px-4 py-2 text-zinc-500 font-bold hover:text-zinc-900 dark:hover:text-white transition-colors", children: "Cancel" }), 381 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: handleSave, className: "px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-lg shadow-lg shadow-indigo-500/20 transition-all", children: "Save Schedule" }) 382 ] }) 383 ] }) }), 384 isSuggestModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4", onClick: () => setIsSuggestModalOpen(false), children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 w-full max-w-lg rounded-2xl shadow-2xl border border-zinc-200 dark:border-zinc-800 overflow-hidden flex flex-col max-h-[80vh]", onClick: (e) => e.stopPropagation(), children: [
385 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center", children: [ 386 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-xl font-bold text-zinc-900 dark:text-white", children: "AI Theme Suggestions" }), 387 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setIsSuggestModalOpen(false), className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-full text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 24 }) }) 388 ] }), 389 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-6 overflow-y-auto flex-1", children: suggesting ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-zinc-500", children: [ 390 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin mb-4 text-indigo-500", size: 32 }), 391 /* @__P
391URE__ */ jsxRuntimeExports.jsx("p", { children: "Analyzing calendar and music history..." }) 392 ] }) : suggestionError ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [ 393 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 bg-red-50 dark:bg-red-900/20 text-red-500 rounded-full flex items-center justify-center mb-4", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 24 }) }), 394 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-900 dark:text-white mb-2", children: "Suggestion Error" }), 395 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 max-w-xs", children: suggestionError }), 396 /* @__PURE__ */ jsxRuntimeExports.jsx( 397 "button", 398 { 399 onClick: handleSuggest, 400 className: "mt-6 px-4 py-2 bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 rounded-lg text-sm font-bold transition-colors", 401 children: "Try Again" 402 } 403 ) 404 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: suggestions.length > 0 ? suggestions.map((s, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 405 "div", 406 { 407 className: "p-4 bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 rounded-xl hover:border-indigo-500 dark:hover:border-indigo-500 transition-colors cursor-pointer group", 408 onClick: () => { 409 setFormData({ 410 ...formData, 411 themeName: s.themeName, 412 startDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0], 413 // Default to today 414 endDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0] 415 }); 416 setAiPrompt(s.description || s.themeName); 417 setIsSuggestModalOpen(false); 418 setIsModalOpen(true); 419 }, 420 children: [
421 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start", children: [ 422 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-900 dark:text-white", children: s.themeName }), 423 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16, className: "text-indigo-500 opacity-0 group-hover:opacity-100 transition-opacity" }) 424 ] }), 425 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 mt-1", children: s.description }) 426 ] 427 }, 428 i 429 )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-8 text-zinc-400", children: "No suggestions available." }) }) }), 430 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-t border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-950/50 flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 431 "button", 432 { 433 onClick: () => setIsSuggestModalOpen(false), 434 className: "px-4 py-2 text-sm font-bold text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors", 435 children: "Close" 436 } 437 ) }) 438 ] }) }) 439 ] }); 440}; 441function LabImageCropper({ imageSrc, toolName, onCropComplete, onCancel }) { 442 const [zoom, setZoom] = reactExports.useState(1); 443 const [position, setPosition] = reactExports.useState({ x: 0, y: 0 }); 444 const [isDragging, setIsDragging] = reactExports.useState(false); 445 const dragStart = reactExports.useRef({ x: 0, y: 0 }); 446 const viewportRef = reactExports.useRef(null); 447 const imgRef = reactExports.useRef(null); 448 reactExports.useEffect(() => { 449 setZoom(1); 450 setPosition({ x: 0, y: 0 }); 451 }, [imageSrc]); 452 const handleMouseDown = (e) => { 453 e.preventDefault(); 454 setIsDragging(true); 455 dragStart.current = { x: e.clientX - position.x, y: e.clientY - position.y }; 456 }; 457 const handleMouseMove = (e) => { 458 if (!isDragging) return; 459 const newX = e.clientX - dragStart.current.x; 460 const newY = e.clientY - dragStart.current.y; 461 setPosition({ x: newX, y: newY }); 462 }; 463 const handleMouseUp = () => { 464 setIsDragging(false); 465 }; 466 const handleTouchStart = (e) => { 467 setIsDragging(true); 468 const touch = e.touches[0]; 469 dragStart.current = { x: touch.clientX - position.x, y: touch.clientY - position.y }; 470 }; 471 const handleTouchMove = (e) => { 472 if (!isDragging) return; 473 const touch = e.touches[0]; 474 const newX = touch.clientX - dragStart.current.x; 475 const newY = touch.clientY - dragStart.current.y; 476 setPosition({ x: newX, y: newY }); 477 }; 478 const handleApply = () => { 479 if (!imgRef.current || !viewportRef.current) return; 480 const img = imgRef.current; 481 const viewport = viewportRef.current; 482 const vWidth = viewport.clientWidth; 483 const vHeight = viewport.clientHeight; 484 const naturalWidth = img.naturalWidth; 485 const naturalHeight = img.naturalHeight; 486 const displayWidth = img.clientWidth; 487 const displayHeight = img.clientHeight; 488 const scaleX = naturalWidth / (displayWidth * zoom); 489 const scaleY = naturalHeight / (displayHeight * zoom); 490 const imgCenterX = displayWidth / 2; 491 const imgCenterY = displayHeight / 2; 492 const originX = vWidth / 2 - imgCenterX * zoom + position.x; 493 const originY = vHeight / 2 - imgCenterY * zoom + position.y; 494 const cropX = -originX * scaleX; 495 const cropY = -originY * scaleY; 496 const cropW = vWidth * scaleX; 497 const cropH = vHeight * scaleY; 498 const canvasOrig = document.createElement("canvas"); 499 canvasOrig.width = naturalWidth; 500 canvasOrig.height = naturalHeight; 501 const ctxOrig = canvasOrig.getContext("2d"); 502 if (ctxOrig) { 503 ctxOrig.drawImage(img, 0, 0); 504 } 505 const originalWebP = canvasOrig.toDataURL("image/webp", 0.92); 506 const generateCroppedWebp = (targetWidth, targetHeight) => { 507 const canvas = document.createElement("canvas"); 508 canvas.width = targetWidth; 509 canvas.height = targetHeight; 510 const ctx = canvas.getContext("2d"); 511 if (ctx) { 512 ctx.imageSmoothingEnabled = true; 513 ctx.imageSmoothingQuality = "high"; 514 ctx.drawImage( 515 img, 516 cropX, 517 cropY, 518 cropW, 519 cropH, 520 // Source crop 521 0, 522 0, 523 targetWidth, 524 targetHeight 525 // Destination bounds 526 ); 527 } 528 return canvas.toDataURL("image/webp", 0.9); 529 }; 530 const homeWebP = generateCroppedWebp(480, 270); 531 const labWebP = generateCroppedWebp(640, 360); 532 onCropComplete({ 533 original: originalWebP, 534 home: homeWebP, 535 lab: labWebP 536 }); 537 }; 538 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/85 backdrop-blur-md p-4 animate-in fade-in duration-200", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-5xl bg-zinc-900 border border-zinc-800 rounded-3xl overflow-hidden shadow-2xl flex flex-col md:flex-row h-[90vh] md:h-auto max-h-[90vh]", children: [
539 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 p-6 flex flex-col items-center justify-between border-b md:border-b-0 md:border-r border-zinc-800 min-h-0 text-white", children: [ 540 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full flex justify-between items-center mb-4", children: [ 541 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 542 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-black text-white leading-none", children: "Crop Thumbnail" }), 543 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mt-1", children: toolName }) 544 ] }), 545 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onCancel, className: "p-1.5 rounded-full hover:bg-zinc-800 text-zinc-400 hover:text-white transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20 }) }) 546 ] }), 547 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full flex-1 flex items-center justify-center bg-black/50 rounded-2xl overflow-hidden border border-zinc-800/80 my-4 min-h-[220px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs( 548 "div", 549 { 550 ref: viewportRef, 551 className: "relative w-full max-w-[560px] aspect-video bg-zinc-950 overflow-hidden shadow-lg select-none border-2 border-indigo-500/50", 552 onMouseDown: handleMouseDown, 553 onMouseMove: handleMouseMove, 554 onMouseUp: handleMouseUp, 555 onMouseLeave: handleMouseUp, 556 onTouchStart: handleTouchStart, 557 onTouchMove: handleTouchMove, 558 onTouchEnd: handleMouseUp, 559 children: [
560 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 border border-dashed border-white/20 pointer-events-none z-10" }), 561 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500/30", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 36 }) }) }), 562 /* @__PURE__ */ jsxRuntimeExports.jsx( 563 "img", 564 { 565 ref: imgRef, 566 src: imageSrc, 567 alt: "Source", 568 className: "absolute max-w-none origin-center select-none", 569 style: { 570 top: "50%", 571 left: "50%", 572 transform: `translate(-50%, -50%) translate(${position.x}px, ${position.y}px) scale(${zoom})`, 573 width: "100%", 574 height: "auto", 575 pointerEvents: "none" 576 } 577 } 578 ) 579 ] 580 } 581 ) }),
582 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full flex items-center gap-4 px-2", children: [ 583 /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { size: 16, className: "text-zinc-500" }), 584 /* @__PURE__ */ jsxRuntimeExports.jsx( 585 "input", 586 { 587 type: "range", 588 min: "1", 589 max: "4", 590 step: "0.05", 591 value: zoom, 592 onChange: (e) => setZoom(parseFloat(e.target.value)), 593 className: "flex-1 accent-indigo-500 h-1.5 bg-zinc-800 rounded-lg appearance-none cursor-pointer" 594 } 595 ), 596 /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { size: 16, className: "text-zinc-500" }), 597 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-bold text-zinc-400 w-10 text-right", children: [ 598 Math.round(zoom * 100), 599 "%" 600 ] }) 601 ] }) 602 ] }), 603 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full md:w-80 p-6 flex flex-col justify-between bg-zinc-950/40 select-none text-white border-t md:border-t-0 md:border-l border-zinc-800", children: [
604 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 605 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-black uppercase tracking-wider text-zinc-500 mb-5", children: "Live Previews" }), 606 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-6", children: [ 607 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 608 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-2", children: [ 609 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-300", children: "Home Page Card" }), 610 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-zinc-500", children: "480 x 270 (16:9)" }) 611 ] }), 612 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full aspect-video rounded-xl border border-zinc-800 overflow-hidden bg-zinc-950 shadow-inner", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 overflow-hidden", children: viewportRef.current && imgRef.current && /* @__PURE__ */ jsxRuntimeExports.jsx( 613 "img", 614 { 615 src: imageSrc, 616 alt: "Home Preview", 617 className: "absolute max-w-none origin-center", 618 style: { 619 top: "50%", 620 left: "50%", 621 transform: `translate(-50%, -50%) translate(${position.x}px, ${position.y}px) scale(${zoom})`, 622 width: "100%", 623 height: "auto" 624 } 625 } 626 ) }) }) 627 ] }),
628 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 629 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-2", children: [ 630 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-300", children: "Hamie Lab Page Card" }), 631 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-zinc-500", children: "640 x 360 (16:9)" }) 632 ] }), 633 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-full aspect-video rounded-xl border border-zinc-800 overflow-hidden bg-zinc-950 shadow-inner", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 overflow-hidden", children: viewportRef.current && imgRef.current && /* @__PURE__ */ jsxRuntimeExports.jsx( 634 "img", 635 { 636 src: imageSrc, 637 alt: "Lab Preview", 638 className: "absolute max-w-none origin-center", 639 style: { 640 top: "50%", 641 left: "50%", 642 transform: `translate(-50%, -50%) translate(${position.x}px, ${position.y}px) scale(${zoom})`, 643 width: "100%", 644 height: "auto" 645 } 646 } 647 ) }) }) 648 ] }) 649 ] }) 650 ] }),
651 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 mt-8 md:mt-0", children: [ 652 /* @__PURE__ */ jsxRuntimeExports.jsx( 653 "button", 654 { 655 onClick: onCancel, 656 className: "flex-1 py-2.5 rounded-xl border border-zinc-800 text-zinc-400 hover:text-white font-bold text-sm hover:bg-zinc-800/40 transition-all active:scale-95", 657 children: "Cancel" 658 } 659 ), 660 /* @__PURE__ */ jsxRuntimeExports.jsxs( 661 "button", 662 { 663 onClick: handleApply, 664 className: "flex-1 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-sm flex items-center justify-center gap-1.5 transition-all shadow-md active:scale-95", 665 children: [ 666 /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { size: 16 }), 667 " Apply Crop" 668 ] 669 } 670 ) 671 ] }) 672 ] }) 673 ] }) }); 674} 675const SYSTEM_ICONS = [ 676 { key: "nav_home", label: "Home", category: "Sidebar", component: House }, 677 { key: "nav_explore", label: "Explore", category: "Sidebar", component: Compass }, 678 { key: "nav_subscriptions", label: "Subscriptions", category: "Sidebar", component: Library }, 679 { key: "nav_history", label: "History", category: "Sidebar", component: History }, 680 { key: "nav_playlists", label: "Playlists", category: "Sidebar", component: ListMusic }, 681 { key: "nav_watch_later", label: "Watch Later", category: "Sidebar", component: Clock }, 682 { key: "nav_liked", label: "Liked Videos", category: "Sidebar", component: ThumbsUp }, 683 { key: "nav_settings", label: "Settings", category: "Sidebar", component: Settings }, 684 { key: "action_menu", label: "Menu", category: "Header", component: Menu }, 685 { key: "action_search", label: "Search", category: "Header", component: Search }, 686 { key: "action_mic", label: "Microphone", category: "Header", component: Mic }, 687 { key: "action_upload", label: "Upload", category: "Header", component: Plus }, 688 { key: "action_bell", label: "Notifications", category: "Header", component: Bell }, 689 { key: "action_user", label: "User Avatar", category: "Header", component: User } 690]; 691const STYLE_PRESETS = [ 692 { id: "minimalist", label: "Minimalist Flat", prompt: "minimalist, flat vector art, single color, clean lines, white background" }, 693 { id: "neon", label: "Neon Glass", prompt: "glowing neon, glassmorphism, dark background style, vibrant indigo and purple gradient" }, 694 { id: "pixel", label: "Pixel Art", prompt: "8-bit pixel art, retro game style, limited color palette" }, 695 { id: "3d", label: "3D Render", prompt: "3D cute icon, claymorphism, soft lighting, pastel colors" }, 696 { id: "sketch", label: "Hand Drawn", prompt: "hand drawn sketch, pencil style, rough edges, artistic" } 697]; 698const DEFAULT_CONFIG = { 699 mainLogo: "", 700 favicon: "", 701 defaultThumbnail: "", 702 seasonalLogos: [], 703 icons: {}, 704 iconSets: [] 705}; 706const GraphicManager = ({ activeRole }) => { 707 const [config, setConfig] = reactExports.useState(DEFAULT_CONFIG); 708 const [activeTab, setActiveTab] = reactExports.useState("assets"); 709 const [availableAssets, setAvailableAssets] = reactExports.useState([]); 710 const [loadingAssets, setLoadingAssets] = reactExports.useState(false); 711 const [defaultAvatars, setDefaultAvatars] = reactExports.useState([]);
712 const [avatarUploading, setAvatarUploading] = reactExports.useState(false); 713 const [aiPrompt, setAiPrompt] = reactExports.useState(""); 714 const [generating, setGenerating] = reactExports.useState(false); 715 const [generatedImage, setGeneratedImage] = reactExports.useState(null); 716 const [targetSlot, setTargetSlot] = reactExports.useState("mainLogo"); 717 const [activeStyle, setActiveStyle] = reactExports.useState(STYLE_PRESETS[0]); 718 const [batchResults, setBatchResults] = reactExports.useState({}); 719 const [batchProgress, setBatchProgress] = reactExports.useState(0); 720 const [isBatchMode, setIsBatchMode] = reactExports.useState(false); 721 const [visibleCounts, setVisibleCounts] = reactExports.useState({ 722 mainLogo: 10, 723 favicon: 10, 724 defaultThumbnail: 10 725 }); 726 const [labTools, setLabTools] = reactExports.useState([]); 727 const [selectedToolForCropping, setSelectedToolForCropping] = reactExports.useState(null); 728 const [croppingImageSrc, setCroppingImageSrc] = reactExports.useState(null); 729 const [activeLabToolIdForPaste, setActiveLabToolIdForPaste] = reactExports.useState(null); 730 const [loadingLab, setLoadingLab] = reactExports.useState(false); 731 reactExports.useEffect(() => { 732 loadConfig(); 733 loadDefaultAvatars(); 734 fetchAssets(); 735 loadLabTools(); 736 }, []); 737 reactExports.useEffect(() => { 738 if (activeTab !== "lab" || !activeLabToolIdForPaste) return; 739 const handleGlobalPaste = (e) => { 740 var _a; 741 const items = (_a = e.clipboardData) == null ? void 0 : _a.items; 742 if (!items) return; 743 for (let i = 0; i < items.length; i++) { 744 const item = items[i]; 745 if (item.type.indexOf("image") !== -1) { 746 const file = item.getAsFile(); 747 if (!file) continue; 748 const reader = new FileReader(); 749 reader.onloadend = () => { 750 const tool = labTools.find((t) => t.id === activeLabToolIdForPaste); 751 if (tool) { 752 setSelectedToolForCropping(tool); 753 setCroppingImageSrc(reader.result); 754 } 755 }; 756 reader.readAsDataURL(file); 757 break; 758 } 759 } 760 }; 761 window.addEventListener("paste", handleGlobalPaste); 762 return () => { 763 window.removeEventListener("paste", handleGlobalPaste); 764 }; 765 }, [activeTab, activeLabToolIdForPaste, labTools]); 766 const loadLabTools = async () => { 767 setLoadingLab(true); 768 try { 769 const config2 = await api.getLabConfig(); 770 if (config2 && config2.tools) { 771 const dbToolsMap = new Map(config2.tools.map((t) => [t.id, t])); 772 const mergedTools = DEFAULT_TOOLS.map((defTool) => { 773 const dbTool = dbToolsMap.get(defTool.id); 774 if (dbTool) { 775 return { ...defTool, enabled: dbTool.enabled }; 776 } 777 return defTool; 778 }); 779 setLabTools(mergedTools); 780 } else { 781 setLabTools(DEFAULT_TOOLS); 782 } 783 } catch (e) { 784 console.error("Failed to load lab tools:", e); 785 setLabTools(DEFAULT_TOOLS); 786 } finally { 787 setLoadingLab(false); 788 } 789 }; 790 const handleLabCropComplete = async (outputs) => { 791 if (!selectedToolForCropping) return; 792 const toolId = selectedToolForCropping.id; 793 setSelectedToolForCropping(null); 794 setCroppingImageSrc(null); 795 try { 796 const [origRes, homeRes, labRes] = await Promise.all([ 797 api.saveGeneratedAsset(outputs.original, `lab_orig_${toolId}`), 798 api.saveGeneratedAsset(outputs.home, `lab_home_${toolId}`), 799 api.saveGeneratedAsset(outputs.lab, `lab_page_${toolId}`) 800 ]); 801 const currentThumbnails = config.labThumbnails || {}; 802 const updatedThumbnails = { 803 ...currentThumbnails, 804 [toolId]: { 805 original: origRes.url, 806 home: homeRes.url, 807 lab: labRes.url 808 } 809 }; 810 await saveConfig({ 811 ...config, 812 labThumbnails: updatedThumbnails 813 }); 814 fetchAssets(); 815 } catch (err) { 816 console.error("Failed to upload cropped thumbnails:", err); 817 alert("Failed to upload cropped thumbnails. Please try again."); 818 } 819 }; 820 const handleLabImageSelect = (e, tool) => { 821 var _a; 822 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 823 if (file) { 824 const reader = new FileReader(); 825 reader.onloadend = () => { 826 setSelectedToolForCropping(tool); 827 setCroppingImageSrc(reader.result); 828 };
829 reader.readAsDataURL(file); 830 } 831 }; 832 const handleRemoveLabThumbnail = async (toolId) => { 833 if (!confirm("Are you sure you want to remove the thumbnail for this tool?")) return; 834 const currentThumbnails = config.labThumbnails || {}; 835 const updatedThumbnails = { ...currentThumbnails }; 836 delete updatedThumbnails[toolId]; 837 await saveConfig({ 838 ...config, 839 labThumbnails: updatedThumbnails 840 }); 841 }; 842 const fetchAssets = async () => { 843 setLoadingAssets(true); 844 try { 845 const assets = await api.getAvailableAssets(); 846 setAvailableAssets(assets); 847 } catch (err) { 848 console.error("Failed to fetch assets:", err); 849 } finally { 850 setLoadingAssets(false); 851 } 852 }; 853 const loadConfig = async () => { 854 const saved = await api.getSystemSetting("site_graphics"); 855 if (saved && saved.value && typeof saved.value === "object") { 856 setConfig({ ...DEFAULT_CONFIG, ...saved.value }); 857 } 858 }; 859 const loadDefaultAvatars = async () => { 860 const avatars = await api.getDefaultAvatars(); 861 setDefaultAvatars(avatars); 862 }; 863 const handleAvatarUpload = async (e) => { 864 var _a; 865 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 866 if (!file) return; 867 setAvatarUploading(true); 868 try { 869 await api.uploadDefaultAvatar(file); 870 await loadDefaultAvatars(); 871 } catch (err) { 872 console.error("Failed to upload default avatar:", err); 873 } finally { 874 setAvatarUploading(false); 875 } 876 }; 877 const handleDeleteAvatar = async (filename) => { 878 if (!confirm(`Delete default avatar "${filename}"?`)) return; 879 await api.deleteDefaultAvatar(filename); 880 await loadDefaultAvatars(); 881 }; 882 const saveConfig = async (newConfig) => { 883 try { 884 await api.post("/admin/settings/site_graphics", { value: newConfig }); 885 setConfig(newConfig); 886 } catch (err) { 887 console.error("Failed to save config:", err); 888 alert("Failed to save settings."); 889 } 890 }; 891 const handleUpload = (e, key, isIcon = false) => { 892 var _a; 893 const file = (_a = e.target.files) == null ? void 0 : _a[0]; 894 if (file) { 895 const reader = new FileReader(); 896 reader.onloadend = async () => { 897 const dataUrl = reader.result; 898 try { 899 const res = await api.saveGeneratedAsset(dataUrl, key); 900 const permanentUrl = res.url; 901 if (isIcon) { 902 const updatedIcons = { ...config.icons, [key]: permanentUrl }; 903 saveConfig({ ...config, icons: updatedIcons }); 904 } else { 905 const updated = { ...config, [key]: permanentUrl }; 906 saveConfig(updated); 907 } 908 fetchAssets(); 909 } catch (err) { 910 console.error("Failed to upload asset:", err); 911 alert("Upload failed. Using local preview temporarily."); 912 if (isIcon) { 913 const updatedIcons = { ...config.icons, [key]: dataUrl }; 914 saveConfig({ ...config, icons: updatedIcons }); 915 } else { 916 const updated = { ...config, [key]: dataUrl }; 917 saveConfig(updated); 918 } 919 } 920 }; 921 reader.readAsDataURL(file); 922 } 923 }; 924 const handleRemoveIcon = (key) => { 925 const updatedIcons = { ...config.icons }; 926 delete updatedIcons[key]; 927 saveConfig({ ...config, icons: updatedIcons }); 928 }; 929 const handleAiGenerate = async () => { 930 var _a, _b, _c; 931 if (!aiPrompt) return; 932 setGenerating(true); 933 setBatchResults({}); 934 try { 935 if (targetSlot === "all_icons") { 936 setIsBatchMode(true); 937 setBatchProgress(0); 938 const newBatch = {}; 939 const total = SYSTEM_ICONS.length; 940 for (let i = 0; i < total; i++) { 941 const icon = SYSTEM_ICONS[i]; 942 const fullPrompt = `Create a high quality, professional icon. 943 Subject: ${icon.label} (${icon.category} UI Element). 944 Theme/Description: ${aiPrompt} 945 Style: ${activeStyle.prompt} 946 947 Important: Return a clean image with a TRANSPARENT background if possible. 948 Target use: Small UI Icon (24x24px context).`; 949 try { 950 const gemini = getGeminiAI(); 951 await gemini.models.generateContent({ 952 model: getAiImageModel("gemini-2.5-flash-image"), 953 contents: [{ text: fullPrompt }] 954 }); 955 const styleColor = activeStyle.id === "neon" ? "1e1b4b" : activeStyle.id === "3d" ? "ffddd2" : "f3f4f6"; 956 const textColor = activeStyle.id === "neon" ? "ffffff" : "000000"; 957 const mockGenerated = getPlaceholderUrl(200, 200, icon.label.substring(0, 3), styleColor, textColor); 958 newBatch[icon.key] = mockGenerated; 959 await new Promise((r) => setTimeout(r, 800)); 960 } catch (e) { 961 console.error(`Failed to generate icon ${icon.key}`, e); 962 } 963 setBatchProgress(Math.round((i + 1) / total * 100)); 964 } 965 setBatchResults(newBatch); 966 } else { 967 setIsBatchMode(false); 968 const fullPrompt = `Create a high quality, professional icon/logo. 969 Subject: ${aiPrompt} 970 Style: ${activeStyle.prompt} 971 972 Important: Return a clean image with a TRANSPARENT background if possible, or a solid color that is easy to remove. 973 Target use: Website UI component.`; 974 const gemini = getGeminiAI(); 975 const response = await gemini.models.generateContent({ 976 model: getAiImageModel("gemini-2.5-flash-image"), 977 contents: [{ text: fullPrompt }] 978 }); 979 for (const part of ((_c = (_b = (_a = response.candidates) == null ? void 0 : _a[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) || []) { 980 if (part.inlineData) { 981 const imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; 982 setGeneratedImage(imageUrl); 983 return; 984 } 985 } 986 const styleColor = activeStyle.id === "neon" ? "1e1b4b" : activeStyle.id === "3d" ? "ffddd2" : "f3f4f6"; 987 const textColor = activeStyle.id === "neon" ? "white" : "black"; 988 const mockGenerated = getPlaceholderUrl(500, 500, aiPrompt.substring(0, 10), styleColor, textColor); 989 setGeneratedImage(mockGenerated); 990 } 991 } catch (e) { 992 console.error("AI Generation Error:", e); 993 alert(`Generation failed: ${e.message || "Unknown error"}. Ensure your API key supports image generation models.`); 994 api.logError(e, "GraphicManager:handleAiGenerate"); 995 } finally { 996 setGenerating(false); 997 } 998 }; 999 const applyGeneratedImage = async () => {
1000 if (targetSlot === "all_icons") { 1001 if (Object.keys(batchResults).length === 0) return; 1002 const savedBatch = {}; 1003 for (const [key, data] of Object.entries(batchResults)) { 1004 try { 1005 const res = await api.saveGeneratedAsset(data, key); 1006 savedBatch[key] = res.url; 1007 } catch (e) { 1008 console.error(`Failed to save icon ${key}:`, e); 1009 } 1010 } 1011 const updatedIcons = { ...config.icons, ...savedBatch }; 1012 saveConfig({ ...config, icons: updatedIcons }); 1013 setBatchResults({}); 1014 setActiveTab("icons"); 1015 setIsBatchMode(false); 1016 setAiPrompt(""); 1017 fetchAssets(); 1018 return; 1019 } 1020 if (!generatedImage) return; 1021 try { 1022 const res = await api.saveGeneratedAsset(generatedImage, targetSlot); 1023 const permanentUrl = res.url; 1024 if (targetSlot === "seasonal") { 1025 const newSeasonal = { 1026 id: `seas-${Date.now()}`, 1027 name: aiPrompt || "AI Generated", 1028 url: permanentUrl, 1029 startDate: "12-01", 1030 endDate: "12-31", 1031 active: true 1032 }; 1033 saveConfig({ ...config, seasonalLogos: [...config.seasonalLogos, newSeasonal] }); 1034 setActiveTab("seasonal"); 1035 } else if (SYSTEM_ICONS.some((icon) => icon.key === targetSlot)) { 1036 const updatedIcons = { ...config.icons, [targetSlot]: permanentUrl }; 1037 saveConfig({ ...config, icons: updatedIcons }); 1038 setActiveTab("icons"); 1039 } else { 1040 saveConfig({ ...config, [targetSlot]: permanentUrl }); 1041 setActiveTab("assets"); 1042 } 1043 setGeneratedImage(null); 1044 setAiPrompt(""); 1045 fetchAssets(); 1046 } catch (e) { 1047 alert("Failed to apply image: " + (e instanceof Error ? e.message : String(e))); 1048 } 1049 }; 1050 const saveBatchAsSet = () => { 1051 if (!newSetName.trim()) return; 1052 const newSet = { 1053 id: `set-${Date.now()}`, 1054 name: newSetName, 1055 icons: { ...batchResults }, 1056 createdAt: (/* @__PURE__ */ new Date()).toISOString() 1057 }; 1058 const updatedSets = [...config.iconSets || [], newSet]; 1059 saveConfig({ ...config, iconSets: updatedSets }); 1060 setNewSetName(""); 1061 alert(`Saved icon set "${newSet.name}"!`); 1062 }; 1063 const applySet = (set) => { 1064 if (confirm(`Replace current icons with "${set.name}"?`)) { 1065 saveConfig({ ...config, icons: set.icons }); 1066 } 1067 }; 1068 const deleteSet = (setId) => { 1069 if (confirm("Delete this icon set?")) { 1070 const updated = (config.iconSets || []).filter((s) => s.id !== setId); 1071 saveConfig({ ...config, iconSets: updated }); 1072 } 1073 }; 1074 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col space-y-6", children: [ 1075 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex bg-zinc-100 dark:bg-zinc-800 p-1 rounded-lg w-fit border border-zinc-200 dark:border-zinc-700", children: [
1076 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1077 "button", 1078 { 1079 onClick: () => setActiveTab("assets"), 1080 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "assets" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1081 children: [ 1082 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 16 }), 1083 " Core Assets" 1084 ] 1085 } 1086 ), 1087 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1088 "button", 1089 { 1090 onClick: () => setActiveTab("icons"), 1091 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "icons" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1092 children: [ 1093 /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutGrid, { size: 16 }), 1094 " System Icons" 1095 ] 1096 } 1097 ),
1098 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1099 "button", 1100 { 1101 onClick: () => setActiveTab("seasonal"), 1102 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "seasonal" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1103 children: [ 1104 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { size: 16 }), 1105 " Logo Scheduler" 1106 ] 1107 } 1108 ), 1109 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1110 "button", 1111 { 1112 onClick: () => setActiveTab("ai"), 1113 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "ai" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1114 children: [ 1115 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 16 }), 1116 " AI Studio" 1117 ] 1118 } 1119 ),
1120 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1121 "button", 1122 { 1123 onClick: () => setActiveTab("avatars"), 1124 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "avatars" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1125 children: [ 1126 /* @__PURE__ */ jsxRuntimeExports.jsx(User, { size: 16 }), 1127 " Default Avatars" 1128 ] 1129 } 1130 ), 1131 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1132 "button", 1133 { 1134 onClick: () => setActiveTab("lab"), 1135 className: `flex items-center gap-2 px-4 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "lab" ? "bg-white dark:bg-zinc-700 shadow text-indigo-600 dark:text-indigo-400" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-white"}`, 1136 children: [ 1137 /* @__PURE__ */ jsxRuntimeExports.jsx(FlaskConical, { size: 16 }), 1138 " Hamie Lab" 1139 ] 1140 } 1141 ) 1142 ] }), 1143 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 p-6 shadow-sm", children: [ 1144 activeTab === "assets" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-8 max-w-5xl", children: [ 1145 [ 1146 { key: "mainLogo", label: "Main Logo", desc: "Displayed in the header. Ideal size: 400x100px (approx 4:1 aspect ratio). Recommended SVG or transparent PNG." }, 1147 { key: "favicon", label: "Favicon", desc: "Browser tab icon. Recommended .ico or .png 32x32 or 64x64." }, 1148 { key: "defaultThumbnail", label: "Default Thumbnail", desc: "Fallback for videos without covers. 16:9 aspect ratio (e.g., 1280x720)." } 1149 ].map((item) => { 1150 var _a, _b; 1151 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col space-y-4 p-6 border border-zinc-200 dark:border-zinc-800 rounded-xl bg-zinc-50/50 dark:bg-zinc-950/50", children: [
1152 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [ 1153 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [ 1154 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white mb-1", children: item.label }), 1155 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-4", children: item.desc }), 1156 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1157 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 px-4 py-2 bg-white dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded-lg cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors", children: [ 1158 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14, className: "text-zinc-500" }), 1159 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-zinc-700 dark:text-zinc-300", children: "Upload New" }),
1160 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: "image/*", onChange: (e) => handleUpload(e, item.key) }) 1161 ] }), 1162 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1163 "button", 1164 { 1165 onClick: () => { 1166 setTargetSlot(item.key); 1167 setActiveTab("ai"); 1168 }, 1169 className: "flex items-center gap-2 px-4 py-2 bg-indigo-50 dark:bg-indigo-900/20 border border-indigo-200 dark:border-indigo-800 rounded-lg hover:bg-indigo-100 dark:hover:bg-indigo-900/40 transition-colors", 1170 children: [ 1171 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14, className: "text-indigo-500" }), 1172 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-indigo-700 dark:text-indigo-300", children: "Generate AI" }) 1173 ] 1174 } 1175 ) 1176 ] }),
1177 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4", children: [ 1178 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-[10px] font-black uppercase text-zinc-500 tracking-wider mb-2", children: "Custom URL or SVG Code" }), 1179 /* @__PURE__ */ jsxRuntimeExports.jsx( 1180 "textarea", 1181 { 1182 value: config[item.key] || "", 1183 onChange: (e) => saveConfig({ ...config, [item.key]: e.target.value }), 1184 placeholder: "Paste URL or <svg>...</svg> code", 1185 className: "w-full h-16 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-lg p-2 text-[10px] font-mono focus:ring-2 focus:ring-indigo-500 transition-all resize-none" 1186 } 1187 ), 1188 item.key === "mainLogo" && /* @__PURE__ */ jsxRuntimeExports.jsxs( 1189 "button", 1190 { 1191 onClick: () => saveConfig({ ...config, [item.key]: DEFAULT_BRAND_SVG }), 1192 className: "mt-1 text-[10px] text-indigo-500 hover:text-indigo-600 font-bold flex items-center gap-1", 1193 children: [
1194 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCcw, { size: 10 }), 1195 " Load Site Default Template" 1196 ] 1197 } 1198 ) 1199 ] }) 1200 ] }), 1201 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full md:w-48 h-32 bg-zinc-200 dark:bg-zinc-800 rounded-lg flex items-center justify-center border-2 border-dashed border-zinc-300 dark:border-zinc-700 overflow-hidden relative group", children: config[item.key] ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 1202 ((_b = (_a = config[item.key]) == null ? void 0 : _a.trim()) == null ? void 0 : _b.startsWith("<svg")) ? /* @__PURE__ */ jsxRuntimeExports.jsx( 1203 "div", 1204 { 1205 className: "max-w-full max-h-full p-2 flex items-center justify-center [&>svg]:max-w-full [&>svg]:max-h-full", 1206 dangerouslySetInnerHTML: { __html: config[item.key] } 1207 } 1208 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: config[item.key], className: "max-w-full max-h-full object-contain", referrerPolicy: "no-referrer" }),
1209 /* @__PURE__ */ jsxRuntimeExports.jsx( 1210 "button", 1211 { 1212 onClick: () => saveConfig({ ...config, [item.key]: "" }), 1213 className: "absolute top-2 right-2 p-1 bg-red-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity z-10", 1214 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) 1215 } 1216 ) 1217 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 font-medium", children: "No Image" }) }) 1218 ] }), 1219 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: [ 1220 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3", children: [ 1221 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-[10px] font-black uppercase text-zinc-500 tracking-wider", children: "Asset Library" }),
1222 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1223 "button", 1224 { 1225 onClick: fetchAssets, 1226 className: "text-[10px] text-zinc-400 hover:text-indigo-600 transition-colors flex items-center gap-1", 1227 children: [ 1228 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCcw, { size: 10, className: loadingAssets ? "animate-spin" : "" }), 1229 " Refresh" 1230 ] 1231 } 1232 ) 1233 ] }), 1234 (() => { 1235 const filtered = availableAssets.filter((asset) => { 1236 const name = asset.name.toLowerCase(); 1237 if (item.key === "mainLogo") return name.includes("logo") || asset.url === "DEFAULT_SVG"; 1238 if (item.key === "favicon") return (name.includes("favicon") || name.endsWith(".ico")) && !name.includes("logo"); 1239 if (item.key === "defaultThumbnail") return (name.includes("thumbnail") || name.includes("banner")) && !name.includes("logo"); 1240 return true; 1241 }); 1242 const visibleLimit = visibleCounts[item.key] || 10; 1243 const visible = filtered.slice(0, visibleLimit); 1244 const hasMore = filtered.length > visibleLimit; 1245 return filtered.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-3", children: [
1246 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-3 overflow-x-auto pb-2 custom-scrollbar mask-fade-right", children: visible.map((asset) => { 1247 const isActive = config[item.key] === asset.url; 1248 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1249 "button", 1250 { 1251 onClick: () => { 1252 const finalUrl = asset.url === "DEFAULT_SVG" ? "" : asset.url; 1253 saveConfig({ ...config, [item.key]: finalUrl }); 1254 }, 1255 className: `relative flex-shrink-0 w-24 h-24 rounded-lg border-2 transition-all overflow-hidden group ${isActive || asset.url === "DEFAULT_SVG" && !config[item.key] ? "border-indigo-500 ring-2 ring-indigo-500/20" : "border-zinc-200 dark:border-zinc-800 hover:border-zinc-400 dark:hover:border-zinc-600"}`, 1256 children: [ 1257 asset.url === "DEFAULT_SVG" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full h-full bg-zinc-100 dark:bg-zinc-800 flex flex-col items-center justify-center p-2", children: [
1258 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full aspect-[4/1] flex items-center justify-center scale-90", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HamieLogo, {}) }), 1259 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] font-black uppercase text-zinc-500 mt-1", children: "Site Default" }) 1260 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: asset.url, className: "w-full h-full object-contain bg-zinc-100 dark:bg-zinc-900", referrerPolicy: "no-referrer" }), 1261 (isActive || asset.url === "DEFAULT_SVG" && !config[item.key]) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-1 right-1 p-0.5 bg-indigo-500 text-white rounded-full shadow-lg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 10 }) }), 1262 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[8px] text-white font-black uppercase", children: "Select" }) }) 1263 ] 1264 }, 1265 asset.url 1266 ); 1267 }) }), 1268 hasMore && /* @__PURE__ */ jsxRuntimeExports.jsxs( 1269 "button", 1270 { 1271 onClick: () => setVisibleCounts({ ...visibleCounts, [item.key]: visibleLimit + 20 }), 1272 className: "text-[10px] font-bold text-zinc-500 hover:text-indigo-600 py-1 px-3 border border-zinc-200 dark:border-zinc-800 rounded-full transition-colors flex items-center gap-1 mx-auto", 1273 children: [
1274 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 10 }), 1275 " Show More (+20)" 1276 ] 1277 } 1278 ) 1279 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-zinc-400 italic", children: "No relevant assets found for this category." }); 1280 })() 1281 ] }) 1282 ] }, item.key); 1283 }), 1284 /* @__PURE__ */ jsxRuntimeExports.jsx(DevDebugInfo, { table: "SystemSetting", keyName: "site_graphics", value: config, isSuperAdmin: activeRole == null ? void 0 : activeRole.isSuperAdmin }) 1285 ] }), 1286 activeTab === "icons" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 1287 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [ 1288 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1289 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg text-zinc-900 dark:text-white", children: "Icon Registry" }),
1290 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Manage all system icons. Custom icons override defaults." }) 1291 ] }), 1292 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1293 "button", 1294 { 1295 onClick: () => { 1296 setTargetSlot("all_icons"); 1297 setActiveTab("ai"); 1298 }, 1299 className: "flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-sm font-bold shadow-md shadow-indigo-500/20", 1300 children: [ 1301 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 16 }), 1302 " Batch Generate All" 1303 ] 1304 } 1305 ) 1306 ] }), 1307 config.iconSets && config.iconSets.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8 p-4 bg-zinc-50 dark:bg-zinc-900/50 rounded-xl border border-zinc-200 dark:border-zinc-800", children: [ 1308 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-xs font-bold uppercase text-zinc-500 mb-3 flex items-center gap-2", children: [
1309 /* @__PURE__ */ jsxRuntimeExports.jsx(Package, { size: 14 }), 1310 " Saved Icon Sets" 1311 ] }), 1312 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: config.iconSets.map((set) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg p-3 shadow-sm flex flex-col justify-between group hover:border-indigo-300 dark:hover:border-indigo-700 transition-colors", children: [ 1313 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1314 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-2", children: [ 1315 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm text-zinc-900 dark:text-white truncate", children: set.name }), 1316 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => deleteSet(set.id), className: "text-zinc-400 hover:text-red-500 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) }) 1317 ] }),
1318 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1 mb-3 overflow-hidden h-6", children: Object.values(set.icons).slice(0, 5).map((src, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src, className: "w-6 h-6 object-contain bg-zinc-100 dark:bg-zinc-800 rounded p-0.5", referrerPolicy: "no-referrer" }, i)) }) 1319 ] }), 1320 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1321 "button", 1322 { 1323 onClick: () => applySet(set), 1324 className: "w-full py-1.5 bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 text-xs font-bold rounded flex items-center justify-center gap-1", 1325 children: [ 1326 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 12 }), 1327 " Apply Set" 1328 ] 1329 } 1330 ) 1331 ] }, set.id)) }) 1332 ] }), 1333 ["Sidebar", "Header"].map((category) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8", children: [
1334 /* @__PURE__ */ jsxRuntimeExports.jsxs("h4", { className: "text-xs font-bold uppercase text-zinc-500 mb-4 border-b border-zinc-200 dark:border-zinc-800 pb-2", children: [ 1335 category, 1336 " Icons" 1337 ] }), 1338 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4", children: SYSTEM_ICONS.filter((i) => i.category === category).map((icon) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-900/50 border border-zinc-200 dark:border-zinc-800 rounded-xl p-4 flex flex-col hover:border-indigo-300 dark:hover:border-indigo-800 transition-all", children: [ 1339 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between mb-3", children: [ 1340 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-sm text-zinc-900 dark:text-white", children: icon.label }), 1341 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] px-1.5 py-0.5 rounded font-mono ${config.icons[icon.key] ? "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300" : "bg-zinc-200 dark:bg-zinc-800 text-zinc-500"}`, children: config.icons[icon.key] ? "Custom" : "Default" }) 1342 ] }),
1343 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 items-center justify-center flex-1 min-h-[60px] bg-white dark:bg-zinc-900 rounded-lg border border-zinc-200 dark:border-zinc-800 mb-3 relative overflow-hidden", children: [ 1344 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col items-center gap-1 opacity-50 grayscale transition-all ${!config.icons[icon.key] ? "opacity-100 grayscale-0 scale-110" : ""}`, children: [ 1345 /* @__PURE__ */ jsxRuntimeExports.jsx(icon.component, { size: 24 }), 1346 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-400", children: "Default" }) 1347 ] }), 1348 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-8 bg-zinc-200 dark:bg-zinc-700" }), 1349 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col items-center gap-1 relative ${config.icons[icon.key] ? "opacity-100 scale-110" : "opacity-20"}`, children: [ 1350 config.icons[icon.key] ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: config.icons[icon.key], className: "w-6 h-6 object-contain", referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-6 h-6 border border-dashed border-zinc-400 rounded flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 10, className: "text-zinc-400" }) }),
1351 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-400", children: "Custom" }) 1352 ] }) 1353 ] }), 1354 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1355 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex-1 py-1.5 bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 rounded text-xs font-bold text-center cursor-pointer text-zinc-700 dark:text-zinc-300 transition-colors", children: [ 1356 "Upload", 1357 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: "image/*", onChange: (e) => handleUpload(e, icon.key, true) }) 1358 ] }), 1359 /* @__PURE__ */ jsxRuntimeExports.jsx( 1360 "button", 1361 { 1362 onClick: () => { 1363 setTargetSlot(icon.key); 1364 setActiveTab("ai"); 1365 }, 1366 className: "px-2 py-1.5 bg-indigo-50 dark:bg-indigo-900/20 hover:bg-indigo-100 dark:hover:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 rounded transition-colors", 1367 title: "Generate with AI", 1368 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }) 1369 } 1370 ), 1371 config.icons[icon.key] && /* @__PURE__ */ jsxRuntimeExports.jsx( 1372 "button", 1373 { 1374 onClick: () => handleRemoveIcon(icon.key), 1375 className: "px-2 py-1.5 bg-red-50 dark:bg-red-900/20 hover:bg-red-100 dark:hover:bg-red-900/40 text-red-500 rounded transition-colors", 1376 title: "Revert to Default", 1377 children: /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCcw, { size: 14 }) 1378 } 1379 ) 1380 ] }) 1381 ] }, icon.key)) }) 1382 ] }, category)) 1383 ] }), 1384 activeTab === "seasonal" && /* @__PURE__ */ jsxRuntimeExports.jsx(LogoScheduler, {}), 1385 activeTab === "ai" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col md:flex-row gap-8", children: [
1386 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 space-y-6", children: [ 1387 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1388 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg text-zinc-900 dark:text-white mb-2", children: "Generate Graphic" }), 1389 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 mb-4", children: "Use Gemini to create icons, logos, or seasonal banners." }), 1390 /* @__PURE__ */ jsxRuntimeExports.jsx( 1391 "textarea", 1392 { 1393 value: aiPrompt, 1394 onChange: (e) => setAiPrompt(e.target.value), 1395 className: "w-full h-24 bg-zinc-50 dark:bg-black border border-zinc-300 dark:border-zinc-800 rounded-lg p-4 text-sm focus:border-indigo-500 outline-none resize-none mb-4", 1396 placeholder: "Describe your graphic (e.g., 'A modern stylized piano key')..." 1397 } 1398 ), 1399 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 mb-4", children: [
1400 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1401 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2", children: "Target Use" }), 1402 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1403 "select", 1404 { 1405 value: targetSlot, 1406 onChange: (e) => setTargetSlot(e.target.value), 1407 className: "w-full bg-white dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded-lg p-2 text-sm outline-none", 1408 children: [ 1409 /* @__PURE__ */ jsxRuntimeExports.jsxs("optgroup", { label: "Core Assets", children: [ 1410 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "mainLogo", children: "Main Website Logo" }), 1411 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "favicon", children: "Favicon" }), 1412 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "defaultThumbnail", children: "Default Thumbnail" }),
1413 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "seasonal", children: "New Seasonal Event" }) 1414 ] }), 1415 /* @__PURE__ */ jsxRuntimeExports.jsxs("optgroup", { label: "System Icons", children: [ 1416 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "all_icons", children: "All System Icons (Batch)" }), 1417 SYSTEM_ICONS.map((icon) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: icon.key, children: icon.label }, icon.key)) 1418 ] }) 1419 ] 1420 } 1421 ) 1422 ] }), 1423 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1424 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2", children: "Style Preset" }), 1425 /* @__PURE__ */ jsxRuntimeExports.jsx( 1426 "select", 1427 { 1428 value: activeStyle.id, 1429 onChange: (e) => setActiveStyle(STYLE_PRESETS.find((s) => s.id === e.target.value) || STYLE_PRESETS[0]), 1430 className: "w-full bg-white dark:bg-zinc-800 border border-zinc-300 dark:border-zinc-700 rounded-lg p-2 text-sm outline-none", 1431 children: STYLE_PRESETS.map((style) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: style.id, children: style.label }, style.id)) 1432 } 1433 ) 1434 ] }) 1435 ] }) 1436 ] }),
1437 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1438 "button", 1439 { 1440 onClick: handleAiGenerate, 1441 disabled: !aiPrompt || generating, 1442 className: "w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50", 1443 children: [ 1444 generating ? /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, {}), 1445 generating ? targetSlot === "all_icons" ? `Generating Batch (${batchProgress}%)` : "Generating..." : targetSlot === "all_icons" ? "Generate All Icons" : "Generate Image" 1446 ] 1447 } 1448 ) 1449 ] }), 1450 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col items-center justify-center bg-zinc-50 dark:bg-black/30 border-2 border-dashed border-zinc-200 dark:border-zinc-800 rounded-xl min-h-[300px] relative overflow-hidden", children: isBatchMode && Object.keys(batchResults).length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-full h-full flex flex-col", children: [
1451 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center shrink-0 gap-3", children: [ 1452 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-bold text-zinc-900 dark:text-white truncate", children: "Batch Preview" }), 1453 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1454 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 1455 setBatchResults({}); 1456 setIsBatchMode(false); 1457 }, className: "text-xs text-zinc-500 hover:text-red-500", children: "Discard" }), 1458 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: applyGeneratedImage, className: "px-3 py-1 bg-emerald-600 hover:bg-emerald-700 text-white rounded text-xs font-bold", children: "Apply" }) 1459 ] }) 1460 ] }), 1461 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-4 custom-scrollbar", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 gap-3", children: Object.entries(batchResults).map(([key, url]) => { 1462 var _a; 1463 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-white dark:bg-black border border-zinc-200 dark:border-zinc-800 rounded-lg p-2 text-center", children: [
1464 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: url, className: "w-10 h-10 mx-auto object-contain mb-2", referrerPolicy: "no-referrer" }), 1465 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-zinc-500 truncate", children: (_a = SYSTEM_ICONS.find((i) => i.key === key)) == null ? void 0 : _a.label }) 1466 ] }, key); 1467 }) }) }), 1468 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-t border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 flex gap-2", children: [ 1469 /* @__PURE__ */ jsxRuntimeExports.jsx( 1470 "input", 1471 { 1472 value: newSetName, 1473 onChange: (e) => setNewSetName(e.target.value), 1474 placeholder: "Set Name (e.g. Neon V2)", 1475 className: "flex-1 bg-zinc-100 dark:bg-black border border-zinc-300 dark:border-zinc-700 rounded px-2 py-1 text-xs outline-none focus:border-indigo-500 text-zinc-900 dark:text-white" 1476 } 1477 ),
1478 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1479 "button", 1480 { 1481 onClick: saveBatchAsSet, 1482 disabled: !newSetName, 1483 className: "px-3 py-1 bg-indigo-100 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-300 hover:bg-indigo-200 dark:hover:bg-indigo-800 rounded text-xs font-bold disabled:opacity-50 flex items-center gap-1", 1484 children: [ 1485 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 12 }), 1486 " Save Set" 1487 ] 1488 } 1489 ) 1490 ] }) 1491 ] }) : generatedImage ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full h-full flex flex-col items-center justify-center p-6 animate-in zoom-in-95", children: [ 1492 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: generatedImage, className: "max-w-full max-h-[300px] object-contain shadow-lg rounded-lg mb-6", referrerPolicy: "no-referrer" }), 1493 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3", children: [
1494 /* @__PURE__ */ jsxRuntimeExports.jsx( 1495 "button", 1496 { 1497 onClick: () => setGeneratedImage(null), 1498 className: "px-4 py-2 bg-zinc-200 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 rounded-lg text-sm font-bold", 1499 children: "Discard" 1500 } 1501 ), 1502 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1503 "button", 1504 { 1505 onClick: applyGeneratedImage, 1506 className: "px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-sm font-bold flex items-center gap-2", 1507 children: [ 1508 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { size: 16 }), 1509 " Apply" 1510 ] 1511 } 1512 ) 1513 ] }) 1514 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center text-zinc-400", children: generating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
1515 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 32, className: "animate-spin text-indigo-500 mb-2" }), 1516 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Generating..." }), 1517 targetSlot === "all_icons" && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs mt-1 text-zinc-500", children: [ 1518 batchProgress, 1519 "% Complete" 1520 ] }) 1521 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 1522 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 48, className: "mx-auto mb-2 opacity-50" }), 1523 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Preview will appear here" }) 1524 ] }) }) }) 1525 ] }) }), 1526 activeTab === "avatars" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6 max-w-4xl", children: [ 1527 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
1528 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1529 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg text-zinc-900 dark:text-white", children: "Default Avatar Collection" }), 1530 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Manage the default avatar images available to users during registration and in profile settings." }) 1531 ] }), 1532 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: `flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-sm font-bold shadow-md shadow-indigo-500/20 cursor-pointer ${avatarUploading ? "opacity-50 pointer-events-none" : ""}`, children: [ 1533 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 16 }), 1534 " ", 1535 avatarUploading ? "Uploading..." : "Add Avatar", 1536 /* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "file", className: "hidden", accept: "image/*", onChange: handleAvatarUpload, disabled: avatarUploading }) 1537 ] }) 1538 ] }), 1539 defaultAvatars.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center py-16 text-zinc-400", children: [
1540 /* @__PURE__ */ jsxRuntimeExports.jsx(User, { size: 48, className: "mx-auto mb-3 opacity-40" }), 1541 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-medium", children: "No default avatars yet" }), 1542 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm mt-1", children: "Upload images to create a collection for users to choose from." }) 1543 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8 gap-4", children: defaultAvatars.map((filename) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "group relative aspect-square rounded-xl overflow-hidden border-2 border-zinc-200 dark:border-zinc-800 hover:border-indigo-400 dark:hover:border-indigo-600 transition-all shadow-sm hover:shadow-md", children: [ 1544 /* @__PURE__ */ jsxRuntimeExports.jsx( 1545 "img", 1546 { 1547 src: `/storage/avatars/defaults/${filename}`, 1548 alt: filename, 1549 className: "w-full h-full object-cover" 1550 } 1551 ),
1552 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 1553 "button", 1554 { 1555 onClick: () => handleDeleteAvatar(filename), 1556 className: "p-2 bg-red-500 hover:bg-red-600 text-white rounded-full shadow-lg transition-transform hover:scale-110", 1557 title: "Delete avatar", 1558 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) 1559 } 1560 ) }), 1561 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-1.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-white/80 truncate text-center font-medium", children: filename }) }) 1562 ] }, filename)) }), 1563 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-zinc-400 pt-4 border-t border-zinc-200 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [ 1564 "Total: ", 1565 defaultAvatars.length, 1566 " avatar(s) ⢠Stored in ",
1567 /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "bg-zinc-100 dark:bg-zinc-800 px-1 py-0.5 rounded", children: "public/storage/avatars/defaults/" }) 1568 ] }) }) 1569 ] }), 1570 activeTab === "lab" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6 max-w-6xl", children: [ 1571 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-between items-start", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1572 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-lg text-zinc-900 dark:text-white", children: "Hamie Lab Thumbnails" }), 1573 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500", children: "Upload or paste screenshot mockups for each application in the Hamie Lab. Previews are generated instantly in optimized WebP format." }) 1574 ] }) }), 1575 loadingLab ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin text-indigo-500" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-8", children: labTools.map((tool) => { 1576 var _a; 1577 const customThumbs = (_a = config.labThumbnails) == null ? void 0 : _a[tool.id]; 1578 const isFocusedForPaste = activeLabToolIdForPaste === tool.id; 1579 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1580 "div", 1581 { 1582 onClick: () => setActiveLabToolIdForPaste(tool.id), 1583 className: `group relative flex flex-col bg-zinc-50/50 dark:bg-zinc-950/20 border-2 rounded-2xl p-6 transition-all duration-300 ${isFocusedForPaste ? "border-indigo-500 ring-2 ring-indigo-500/10 shadow-lg" : "border-zinc-200 dark:border-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-700"}`, 1584 children: [
1585 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-start mb-4", children: [ 1586 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1587 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "px-2 py-0.5 rounded-full text-[9px] font-black uppercase bg-indigo-100 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400", children: tool.id }), 1588 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-base font-black text-zinc-900 dark:text-white mt-1 leading-none", children: tool.name }) 1589 ] }), 1590 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1591 customThumbs && /* @__PURE__ */ jsxRuntimeExports.jsx( 1592 "button", 1593 { 1594 onClick: (e) => { 1595 e.stopPropagation(); 1596 handleRemoveLabThumbnail(tool.id); 1597 }, 1598 className: "p-1.5 rounded-lg bg-red-500/10 hover:bg-red-500 text-red-500 hover:text-white transition-colors", 1599 title: "Remove Thumbnail", 1600 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) 1601 } 1602 ),
1603 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "p-1.5 rounded-lg bg-zinc-200 dark:bg-zinc-800 hover:bg-zinc-300 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 cursor-pointer transition-colors", title: "Upload Image File", children: [ 1604 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { size: 14 }), 1605 /* @__PURE__ */ jsxRuntimeExports.jsx( 1606 "input", 1607 { 1608 type: "file", 1609 className: "hidden", 1610 accept: "image/*", 1611 onChange: (e) => handleLabImageSelect(e, tool) 1612 } 1613 ) 1614 ] }) 1615 ] }) 1616 ] }), 1617 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mb-4 line-clamp-2 h-8", children: tool.description || "No description available." }), 1618 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4 mt-auto", children: [
1619 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1620 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] font-bold text-zinc-400 mb-1.5 uppercase tracking-tight flex justify-between", children: [ 1621 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Home Page (16:9)" }), 1622 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-500 font-mono", children: "webp / 480x270" }) 1623 ] }), 1624 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aspect-video rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden bg-zinc-100 dark:bg-zinc-900 flex items-center justify-center relative", children: (customThumbs == null ? void 0 : customThumbs.home) ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: customThumbs.home, alt: "Home Page Thumbnail", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center p-2", children: [
1625 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 20, className: "mx-auto mb-1 text-zinc-400 dark:text-zinc-600" }), 1626 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-400 font-medium", children: "Fallback Gradient" }) 1627 ] }) }) 1628 ] }), 1629 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1630 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-[10px] font-bold text-zinc-400 mb-1.5 uppercase tracking-tight flex justify-between", children: [ 1631 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Hamie Lab (16:9)" }), 1632 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-500 font-mono", children: "webp / 640x360" }) 1633 ] }), 1634 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "aspect-video rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden bg-zinc-100 dark:bg-zinc-900 flex items-center justify-center relative", children: (customThumbs == null ? void 0 : customThumbs.lab) ? /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: customThumbs.lab, alt: "Hamie Lab Thumbnail", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center p-2", children: [
1635 /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 20, className: "mx-auto mb-1 text-zinc-400 dark:text-zinc-600" }), 1636 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-zinc-400 font-medium", children: "Fallback Gradient" }) 1637 ] }) }) 1638 ] }) 1639 ] }), 1640 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mt-4 py-1.5 px-3 rounded-lg text-center text-[10px] font-bold transition-all ${isFocusedForPaste ? "bg-indigo-500 text-white animate-pulse" : "bg-zinc-100 dark:bg-zinc-900 text-zinc-500 group-hover:bg-zinc-200 dark:group-hover:bg-zinc-800/80 dark:text-zinc-400"}`, children: isFocusedForPaste ? "Active! Paste screenshot now (Ctrl+V)" : "Click to select and paste screenshot" }) 1641 ] 1642 }, 1643 tool.id 1644 ); 1645 }) }) 1646 ] }), 1647 selectedToolForCropping && croppingImageSrc && /* @__PURE__ */ jsxRuntimeExports.jsx( 1648 LabImageCropper, 1649 { 1650 imageSrc: croppingImageSrc, 1651 toolName: selectedToolForCropping.name, 1652 onCropComplete: handleLabCropComplete, 1653 onCancel: () => { 1654 setSelectedToolForCropping(null); 1655 setCroppingImageSrc(null); 1656 } 1657 } 1658 ) 1659 ] }) 1660 ] }); 1661}; 1662export { 1663 GraphicManager as default 1664};
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.