diff --git a/src/pages/editor/utils/blurOverlayLayout.ts b/src/pages/editor/utils/blurOverlayLayout.ts index bbc5d48..d2010fb 100644 --- a/src/pages/editor/utils/blurOverlayLayout.ts +++ b/src/pages/editor/utils/blurOverlayLayout.ts @@ -33,8 +33,16 @@ export function computeBlurOverlayGeometry( ): BlurOverlayGeometry { const rotation = obj.rotation ?? 0; const shapeType = obj.shapeType ?? "square"; - const anchorX = livePosition?.x ?? (obj.x ?? 0) * scale; - const anchorY = livePosition?.y ?? (obj.y ?? 0) * scale; + const anchorX = + livePosition?.x ?? + (shapeType === "triangle" || shapeType === "abstract" + ? ((obj.x ?? 0) + (obj.width ?? 100) / 2) * scale + : (obj.x ?? 0) * scale); + const anchorY = + livePosition?.y ?? + (shapeType === "triangle" || shapeType === "abstract" + ? ((obj.y ?? 0) + (obj.height ?? 100) / 2) * scale + : (obj.y ?? 0) * scale); if (shapeType === "circle") { const sizePx = (obj.width ?? 100) * scale; diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index 5b6d9e7..2fee238 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -11,7 +11,11 @@ import { resolveTextMaxWidth, usesWrappedLayout, } from '@/pages/editor/utils/textStyle'; -import { getCssBlurStyle } from '@/pages/editor/utils/shapeBlur'; +import { + buildBlurOverlayStyle, + buildBlurStackEntries, + isBlurBackdropObject, +} from '@/pages/editor/utils/blurOverlayLayout'; import { getCssBorderRadius } from '@/pages/editor/utils/borderRadius'; import '@/pages/viewer/styles/entranceAnimations.css'; import { mergeEntranceAnimationStyle } from '@/pages/viewer/utils/entranceAnimationStyle'; @@ -213,11 +217,15 @@ const BookPage = memo(forwardRef( return null; } + // Blur shapes use backdrop-filter overlays (see blur stack below), not CSS filter on the shape. + if (isBlurBackdropObject(obj)) { + return null; + } + const objectFillStyle: React.CSSProperties = obj.fillType === 'gradient' && obj.gradient ? { backgroundImage: toCssLinearGradient(obj.gradient) } : { backgroundColor: obj.fill || 'transparent' }; - const shapeBlurStyle = getCssBlurStyle(obj.blur, scale); switch (obj.type) { case 'text': { @@ -537,7 +545,6 @@ const BookPage = memo(forwardRef( height: `${radius * 2}px`, borderRadius: '50%', ...objectFillStyle, - ...shapeBlurStyle, border: hasStroke && obj.stroke ? `${actualStrokeWidth * scale}px solid ${obj.stroke}` : 'none', @@ -610,7 +617,6 @@ const BookPage = memo(forwardRef( width: `${size}px`, height: `${size}px`, opacity: (obj.opacity ?? 100) / 100, - ...shapeBlurStyle, transform: obj.rotation ? `rotate(${obj.rotation}deg)` : undefined, transformOrigin: 'center center', zIndex: index, @@ -669,7 +675,6 @@ const BookPage = memo(forwardRef( height: `${radius * 2}px`, clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)', ...objectFillStyle, - ...shapeBlurStyle, border: obj.stroke ? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}` : 'none', @@ -692,7 +697,6 @@ const BookPage = memo(forwardRef( width: `${(obj.width || 100) * scale}px`, height: `${(obj.height || 100) * scale}px`, ...objectFillStyle, - ...shapeBlurStyle, borderRadius: `${Math.max(0, (obj.borderRadius || 0) * scale)}px`, border: hasStroke && obj.stroke ? `${actualStrokeWidth * scale}px solid ${obj.stroke}` @@ -1102,6 +1106,34 @@ const BookPage = memo(forwardRef( )}
{page.elements.map((element, index) => renderObject(element, index, page.elements))} + {buildBlurStackEntries(page.elements).map((entry) => { + const blurIndex = page.elements.findIndex((el) => el.id === entry.blurObject.id); + const blurOverlayStyle = buildBlurOverlayStyle(entry.blurObject, scale); + + return ( +
+
= 0 ? blurIndex : 0, + { + transformOrigin: blurOverlayStyle.transformOrigin, + rotationDeg: entry.blurObject.rotation ?? 0, + }, + )} + /> + {entry.occlusionObjects.map((obj) => { + const index = page.elements.findIndex((el) => el.id === obj.id); + if (index < 0) return null; + return renderObject(obj, index, page.elements); + })} +
+ ); + })}
{showLoadingOverlay && (