diff --git a/.replit b/.replit index 08da511..7c5c48e 100644 --- a/.replit +++ b/.replit @@ -4,6 +4,7 @@ hidden = [".config", ".git", "generated-icon.png", "node_modules", "dist"] [nix] channel = "stable-24_05" +packages = ["cairo", "pango", "libuuid", "libjpeg", "giflib"] [deployment] deploymentTarget = "autoscale" diff --git a/attached_assets/image_1760172363482.png b/attached_assets/image_1760172363482.png new file mode 100644 index 0000000..f707f6b Binary files /dev/null and b/attached_assets/image_1760172363482.png differ diff --git a/client/src/pages/image-editor.tsx b/client/src/pages/image-editor.tsx index 73843ae..41c8d77 100644 --- a/client/src/pages/image-editor.tsx +++ b/client/src/pages/image-editor.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; +import { apiRequest } from "@/lib/queryClient"; import Sidebar from "@/components/sidebar"; import TopBar from "@/components/topbar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -81,6 +82,38 @@ export default function ImageEditor() { queryKey: ["/api/media"], }); + // State for generated ribbon overlay + const [ribbonPublicId, setRibbonPublicId] = useState(null); + const [isGeneratingRibbon, setIsGeneratingRibbon] = useState(false); + + // Generate ribbon when settings change + useEffect(() => { + if (!ribbon.enabled || !ribbon.text) { + setRibbonPublicId(null); + return; + } + + const generateRibbon = async () => { + setIsGeneratingRibbon(true); + try { + const response = await apiRequest("POST", "/api/media/generate-ribbon", { + text: ribbon.text, + color: ribbon.color, + position: ribbon.position + }); + const data = await response.json(); + setRibbonPublicId(data.publicId); + } catch (error) { + console.error("Error generating ribbon:", error); + } finally { + setIsGeneratingRibbon(false); + } + }; + + // Debounce to avoid too many requests + const timer = setTimeout(generateRibbon, 500); + return () => clearTimeout(timer); + }, [ribbon.enabled, ribbon.text, ribbon.color, ribbon.position]); // Generate Cloudinary transformation URL useEffect(() => { @@ -130,36 +163,26 @@ export default function ImageEditor() { transformations.push(`e_${filters.effect}`); } - // Ribbon overlay (small diagonal banner in corner) - if (ribbon.enabled) { - const ribbonColor = ribbon.color === "red" ? "FF0000" : "FFC107"; - // URL encode the text to handle accents and special characters - const ribbonText = encodeURIComponent(ribbon.text); - - // Diagonal banner - minimal size - const isNorthWest = ribbon.position === "north_west"; - const rotation = isNorthWest ? "-45" : "45"; - + // Ribbon overlay using generated triangular image + if (ribbon.enabled && ribbonPublicId) { + // Use the generated triangular ribbon as an overlay + const ribbonResource = ribbonPublicId.replace(/\//g, ":"); transformations.push( - // Minimal padding - just text with small background - `l_text:Arial_28_bold: ${ribbonText} ,co_white,b_rgb:${ribbonColor}`, - `a_${rotation}`, - `fl_layer_apply,g_${ribbon.position},x_10,y_10` + `l_${ribbonResource}`, + `fl_layer_apply,g_${ribbon.position},x_0,y_0` ); } - // Price badge overlay (circular background with centered price) + // Price badge overlay (Cloudinary text with circular background) if (priceBadge.enabled && priceBadge.price) { const badgeColor = priceBadge.color === "red" ? "DC2626" : "FFC107"; - // URL encode the price text const priceText = encodeURIComponent(`€${priceBadge.price}`); - // Create circular badge with equal padding to ensure circle shape + // Create circular badge with text transformations.push( - // Price with equal padding on all sides for perfect circle - `l_text:Arial_38_bold: ${priceText} ,co_white,b_rgb:${badgeColor}`, - `r_max`, // Maximum rounding for circular effect - `fl_layer_apply,g_${priceBadge.position},x_20,y_20` + `l_text:Arial_38_bold: ${priceText} ,co_white,b_rgb:${badgeColor}`, + `r_max`, + `fl_layer_apply,g_${priceBadge.position},x_30,y_30` ); } @@ -181,6 +204,66 @@ export default function ImageEditor() { return (
+ + {sidebarOpen && (
+ + {/* CSS Triangle Ribbon Overlay */} + {ribbon.enabled && ribbon.text && ( + <> +
+
+ {ribbon.text} +
+ + )} + + {/* Price Badge Overlay (CSS) */} + {priceBadge.enabled && priceBadge.price && ( +
+ €{priceBadge.price} +
+ )} +