{"version":3,"file":"ImageOverlay.ByaJzxvP.mjs","names":["React.useRef","React.useState"],"sources":["https:/framerusercontent.com/modules/5paWM6k55Y3jvWnM7hvM/YvWB2wCGNIunPQyVAQUc/ImageOverlay.js"],"sourcesContent":["import{jsx as _jsx,jsxs as _jsxs}from\"react/jsx-runtime\";import*as React from\"react\";import{addPropertyControls,ControlType,RenderTarget}from\"framer\";const defaults={image:\"\",overlayColor:\"#000000\",overlayOpacity:.4,overlayStyle:\"flat\",focalX:50,focalY:50,radius:8,sizes:\"100vw\",priority:false,skeleton:true,placeholderColor:\"rgb(232, 227, 220)\"};/** Framer passes an image either as a plain string or a ResponsiveImage object. */function imageSrc(image){if(!image)return\"\";if(typeof image===\"string\")return image;return image.src||\"\";}/**\n * Framer's ResponsiveImage object carries a ready-made srcSet. Use it when it\n * is there — it points at the CDN's already-generated resized variants.\n */function imageSrcSet(image){if(!image||typeof image===\"string\")return\"\";return image.srcSet||\"\";}/**\n * Fallback when only a bare URL is available: build a srcSet by hand off the\n * Framer CDN's `scale-down-to` parameter. Any existing query string is dropped\n * first — an uploaded asset arrives as `...jpg?width=5824&height=3264`, which\n * describes the original and does NOT resize anything.\n *\n * Without this the browser downloads the full-resolution original for every\n * slot, which on this site meant 2-4 MB photographs behind 366px-wide cards.\n */function buildSrcSet(src){if(!src)return\"\";const base=src.split(\"?\")[0];return[512,1024,1600,2048].map(w=>`${base}?scale-down-to=${w} ${w}w`).join(\", \");}/**\n * Turn the overlay colour into an rgba() string at a given alpha.\n * Accepts #RGB, #RRGGBB, and rgb()/rgba() — Framer colour controls emit hex,\n * but tokens can resolve to rgb(), so both are handled.\n */function toRgba(color,alpha){const c=(color||\"#000000\").trim();if(c.startsWith(\"#\")){let hex=c.slice(1);if(hex.length===3)hex=hex.split(\"\").map(h=>h+h).join(\"\");const n=parseInt(hex.slice(0,6),16);const r=n>>16&255;const g=n>>8&255;const b=n&255;return`rgba(${r}, ${g}, ${b}, ${alpha})`;}const nums=c.match(/[\\d.]+/g);if(nums&&nums.length>=3){return`rgba(${nums[0]}, ${nums[1]}, ${nums[2]}, ${alpha})`;}return`rgba(0, 0, 0, ${alpha})`;}/**\n * Bottom-weighted scrim. Holds close to full strength across the lower ~30%\n * (where the title and the disclaimer sit) then clears completely by ~75%, so\n * the top of the photograph is left untouched.\n */function scrim(color,peak){return[\"linear-gradient(to top\",`${toRgba(color,peak)} 0%`,`${toRgba(color,peak)} 28%`,`${toRgba(color,peak*.55)} 48%`,`${toRgba(color,0)} 75%)`].join(\", \");}/**\n * Keyframes for the skeleton sweep. Injected once per instance as a <style>\n * tag — Framer code components have no stylesheet of their own. The sweep is\n * deliberately low-contrast and slow: this brand reads as restrained luxury,\n * and a fast high-contrast shimmer looks like a SaaS dashboard. Anyone who has\n * asked for reduced motion gets the flat placeholder tone with no movement.\n */const SKELETON_CSS=`\n@keyframes avinyaSkeletonSweep {\n  from { transform: translateX(-100%); }\n  to   { transform: translateX(100%); }\n}\n@media (prefers-reduced-motion: reduce) {\n  .avinya-skeleton-sweep { animation: none !important; }\n}\n`;export default function ImageOverlay(props){const{image,overlayColor,overlayOpacity,overlayStyle,focalX,focalY,radius,sizes,priority,skeleton,placeholderColor}=props;const src=imageSrc(image);const alt=typeof image===\"object\"&&image?.alt?image.alt:\"\";const isCanvas=RenderTarget.current()===RenderTarget.canvas;const imgRef=React.useRef(null);// Canvas never waits — the design view should always show the finished image.\nconst[loaded,setLoaded]=React.useState(isCanvas);// The fade is progressive enhancement. Server-rendered HTML must show the\n// image at full opacity, otherwise a client that never runs the JS — or a\n// crawler that does not execute it — is served a permanently invisible\n// image. Only once mounted do we take over opacity.\nconst[mounted,setMounted]=React.useState(false);React.useEffect(()=>{if(isCanvas)return;setMounted(true);// A cached image can finish decoding before React attaches onLoad,\n// which would otherwise strand the skeleton on screen forever.\nif(imgRef.current?.complete)setLoaded(true);},[isCanvas,src]);const showSkeleton=Boolean(src)&&skeleton&&mounted&&!loaded;const imageVisible=!mounted||loaded;const fill={position:\"absolute\",top:0,left:0,width:\"100%\",height:\"100%\"};return /*#__PURE__*/_jsxs(\"div\",{style:{position:\"relative\",width:\"100%\",height:\"100%\",borderRadius:radius,overflow:\"hidden\",background:\"transparent\",pointerEvents:\"none\"},children:[showSkeleton?/*#__PURE__*/_jsxs(\"div\",{style:{...fill,background:placeholderColor},children:[/*#__PURE__*/_jsx(\"style\",{children:SKELETON_CSS}),/*#__PURE__*/_jsx(\"div\",{className:\"avinya-skeleton-sweep\",style:{...fill,backgroundImage:\"linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.28) 50%, transparent 100%)\",animation:\"avinyaSkeletonSweep 1.6s ease-in-out infinite\"}})]}):null,src?/*#__PURE__*/_jsx(\"img\",{ref:imgRef,onLoad:()=>setLoaded(true),src:src,srcSet:imageSrcSet(image)||buildSrcSet(src),sizes:sizes,alt:alt,loading:priority?\"eager\":\"lazy\",// @ts-ignore -- valid HTML attribute, not in this React's typings yet\nfetchpriority:priority?\"high\":\"auto\",decoding:\"async\",style:{...fill,objectFit:\"cover\",objectPosition:`${focalX}% ${focalY}%`,display:\"block\",opacity:imageVisible?1:0,transition:\"opacity 0.45s ease\"}}):null,/*#__PURE__*/_jsx(\"div\",{style:String(overlayStyle).toLowerCase()===\"gradient\"?{...fill,backgroundImage:scrim(overlayColor,overlayOpacity)}:{...fill,background:overlayColor,opacity:overlayOpacity}})]});}ImageOverlay.defaultProps=defaults;addPropertyControls(ImageOverlay,{image:{type:ControlType.ResponsiveImage,title:\"Image\",description:\"Leave empty to use the parent frame's own background and just apply the overlay.\"},overlayStyle:{type:ControlType.Enum,title:\"Overlay style\",defaultValue:\"flat\",options:[\"flat\",\"gradient\"],optionTitles:[\"Flat\",\"Gradient\"],displaySegmentedControl:true,description:\"Flat darkens the whole image evenly. Gradient darkens only the bottom, so the top of the photo stays clear.\"},overlayOpacity:{type:ControlType.Number,title:\"Overlay\",defaultValue:.4,min:0,max:1,step:.05,displayStepper:true,description:\"Strength of the darkening. With Gradient this is the strength at the bottom edge.\"},overlayColor:{type:ControlType.Color,title:\"Overlay colour\",defaultValue:\"#000000\"},focalX:{type:ControlType.Number,title:\"Focal X\",defaultValue:50,min:0,max:100,step:1,unit:\"%\",description:\"Which part of the image to keep when cropping.\",hidden:p=>!p.image},focalY:{type:ControlType.Number,title:\"Focal Y\",defaultValue:50,min:0,max:100,step:1,unit:\"%\",hidden:p=>!p.image},radius:{type:ControlType.Number,title:\"Radius\",defaultValue:8,min:0,max:64,step:1,unit:\"px\"},sizes:{type:ControlType.String,title:\"Sizes\",defaultValue:\"100vw\",description:\"A SINGLE length, e.g. 100vw or 426px. Framer templates this value into its own per-breakpoint sizes expression, so passing a full media-query list here nests it and produces invalid CSS.\",hidden:p=>!p.image},skeleton:{type:ControlType.Boolean,title:\"Skeleton\",defaultValue:true,enabledTitle:\"On\",disabledTitle:\"Off\",description:\"Show a placeholder while the image loads, then fade it in, instead of leaving an empty hole.\",hidden:p=>!p.image},placeholderColor:{type:ControlType.Color,title:\"Placeholder\",defaultValue:\"rgb(232, 227, 220)\",description:\"Match the surface behind the image so the swap is quiet.\",hidden:p=>!p.image||!p.skeleton},priority:{type:ControlType.Boolean,title:\"Priority\",defaultValue:false,enabledTitle:\"Eager\",disabledTitle:\"Lazy\",description:\"Turn on for above-the-fold images (heroes) so they are not lazy-loaded. Leave off for everything below the fold.\",hidden:p=>!p.image}});\nexport const __FramerMetadata__ = {\"exports\":{\"default\":{\"type\":\"reactComponent\",\"name\":\"ImageOverlay\",\"slots\":[],\"annotations\":{\"framerContractVersion\":\"1\"}},\"__FramerMetadata__\":{\"type\":\"variable\"}}}\n//# sourceMappingURL=./ImageOverlay.map"],"mappings":"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"}