diff --git a/client/src/components/preview-modal.tsx b/client/src/components/preview-modal.tsx index b47ee99..1b09afe 100644 --- a/client/src/components/preview-modal.tsx +++ b/client/src/components/preview-modal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; @@ -222,64 +222,164 @@ export function PreviewModal({ ); - const renderInstagramStory = () => ( -
- {/* Header */} -
-
- -
-
-
votre_page
-
-
+ const wrapText = (text: string, maxWidth: number, fontSize: number): string[] => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (!ctx) return [text]; - {/* Media */} - {orderedMedia.length > 0 && ( -
- {renderMedia(orderedMedia[currentPhotoIndex], "w-full h-full object-cover")} - {orderedMedia.length > 1 && ( - <> -
- {orderedMedia.map((_, idx) => ( -
- - - - )} - {/* Text overlay */} - {postText && ( -
-
-

{postText}

-
-
- )} + ctx.font = `bold ${fontSize}px Arial, sans-serif`; + const words = text.split(' '); + const lines: string[] = []; + let currentLine = ''; + + for (const word of words) { + const testLine = currentLine ? `${currentLine} ${word}` : word; + const metrics = ctx.measureText(testLine); + + if (metrics.width > maxWidth && currentLine) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine = testLine; + } + } + + if (currentLine) { + lines.push(currentLine); + } + + return lines; + }; + + const calculateAdaptiveFontSize = (text: string, containerWidth: number, containerHeight: number): { fontSize: number; lines: string[]; isTruncated: boolean } => { + const MIN_FONT_SIZE = 24; + const MAX_FONT_SIZE = 72; + const LINE_HEIGHT_MULTIPLIER = 1.2; + + let fontSize = MAX_FONT_SIZE; + let lines: string[] = []; + let totalTextHeight = 0; + + while (fontSize >= MIN_FONT_SIZE) { + lines = wrapText(text, containerWidth, fontSize); + const lineHeight = fontSize * LINE_HEIGHT_MULTIPLIER; + totalTextHeight = lines.length * lineHeight; + + if (totalTextHeight <= containerHeight) { + break; + } + + fontSize -= 2; + } + + let isTruncated = false; + + if (fontSize < MIN_FONT_SIZE) { + fontSize = MIN_FONT_SIZE; + lines = wrapText(text, containerWidth, fontSize); + const lineHeight = fontSize * LINE_HEIGHT_MULTIPLIER; + totalTextHeight = lines.length * lineHeight; + + const maxLines = Math.floor(containerHeight / lineHeight); + if (lines.length > maxLines) { + lines = lines.slice(0, maxLines); + if (lines[maxLines - 1]) { + lines[maxLines - 1] = lines[maxLines - 1].substring(0, lines[maxLines - 1].length - 3) + '...'; + } + isTruncated = true; + } + } + + return { fontSize, lines, isTruncated }; + }; + + const renderInstagramStory = () => { + const STORY_WIDTH = 360; + const STORY_HEIGHT = 640; + const TEXT_BOX_HEIGHT_RATIO = 0.25; + const PADDING = 16; + + const textBoxHeight = STORY_HEIGHT * TEXT_BOX_HEIGHT_RATIO; + const maxTextWidth = STORY_WIDTH - (PADDING * 2); + const maxTextHeight = textBoxHeight - (PADDING * 2); + + const { fontSize, lines } = postText + ? calculateAdaptiveFontSize(postText, maxTextWidth, maxTextHeight) + : { fontSize: 24, lines: [] }; + + return ( +
+ {/* Header */} +
+
+ +
+
+
votre_page
+
- )} -
- ); + + {/* Media */} + {orderedMedia.length > 0 && ( +
+ {renderMedia(orderedMedia[currentPhotoIndex], "w-full h-full object-cover")} + {orderedMedia.length > 1 && ( + <> +
+ {orderedMedia.map((_, idx) => ( +
+ + + + )} + + {/* Text overlay box - matching exact server rendering */} + {postText && ( +
+
+
+ {lines.map((line, idx) => ( +
{line}
+ ))} +
+
+ )} +
+ )} +
+ ); + }; return (