{"version":3,"file":"ProjectCard.-DCWYHt3.mjs","names":["useState","useRef","useLayoutEffect","useEffect"],"sources":["https:/framerusercontent.com/modules/4FYfST3M46ae6ppupJzD/GrZ7CvXArEx8NsuFhD2H/ProjectCard.js"],"sourcesContent":["import{jsx as _jsx}from\"react/jsx-runtime\";import{useState,useRef,useEffect,useLayoutEffect}from\"react\";import{motion,useAnimation}from\"framer-motion\";import{addPropertyControls,ControlType}from\"framer\";import{GlassSlab,slabControls,frontImageStyle,REF_WIDTH}from\"https://framerusercontent.com/modules/kMMYS8UBn6GnLD47s0s6/owmo08TCoV4k0Gt2mIUB/GlassSlab.js\";// Measures before the first paint in the browser; plain useEffect during\n// Framer's server-side prerender.\nconst useIsoLayoutEffect=typeof window!==\"undefined\"?useLayoutEffect:useEffect;// Framer-hosted images can be resized on the fly by the CDN. Without this,\n// a plain <img> downloads the full-resolution original (some of the project\n// photos are 5000–8000px wide), which is what made the header card slow.\nconst sizedSrc=(url,width)=>{if(typeof url!==\"string\"||!url.includes(\"framerusercontent.com\"))return url;const sep=url.includes(\"?\")?\"&\":\"?\";return`${url}${sep}scale-down-to=${width}`;};// Widths matched to the card's display size at 1x–3x screen density, so a\n// retina screen gets a sharp image and a regular screen a lighter one.\nconst buildSrcSet=(url,displayW)=>typeof url===\"string\"&&url.includes(\"framerusercontent.com\")?[1,1.5,2,3].map(m=>Math.round(displayW*m)).map(w=>`${sizedSrc(url,w)} ${w}w`).join(\", \"):undefined;export default function ProjectCard({image=null,link=\"\",cardThickness=25,borderWidth=5,borderTint=\"#ffffff\",borderOpacity=1,borderGrain=0,edgeTint=\"#ffffff\",edgeOpacity=.35,edgeGrain=.28,lightContrast=.6,edgeLines=.5,edgeLineTint=\"#ffffff\",shadowIntensity=1,cornerReveal=true,tiltX=0,tiltY=0,tiltZ=0,wiggleAngle=22,wiggleSpeed=2.2,wigglePause=.3,mobileMode=false}){const[isHovered,setIsHovered]=useState(false);const spinControls=useAnimation();// isTouchMode is controlled entirely by the mobileMode prop.\n// In Framer, set Mobile Mode = on for your tablet and phone\n// breakpoint variants of this page — Framer handles the rest.\nconst isTouchMode=mobileMode;// Measure the real rendered width and derive the scale factor from the\n// 350px reference, so thickness/border/lift/shadow stay proportional.\nconst containerRef=useRef(null);const[measuredW,setMeasuredW]=useState(REF_WIDTH);// Hidden until the real width is known, so the slab never shows with\n// the wrong thickness/border and then snaps to the right one\nconst[measured,setMeasured]=useState(false);// True once the photo (and any slab textures) are decoded, so the card\n// appears whole rather than face first\nconst[ready,setReady]=useState(false);useEffect(()=>{if(!measured||ready||!containerRef.current)return;let cancelled=false;const imgs=[...containerRef.current.querySelectorAll(\"img\")];const show=()=>{if(!cancelled)setReady(true);};// Never stay hidden longer than this, even if an image fails\nconst cap=setTimeout(show,2500);Promise.all(imgs.map(img=>img.decode?img.decode().catch(()=>{}):null)).then(show);return()=>{cancelled=true;clearTimeout(cap);};},[measured,ready]);useIsoLayoutEffect(()=>{if(!containerRef.current)return;const first=containerRef.current.offsetWidth;if(first){setMeasuredW(first);setMeasured(true);}const ro=new ResizeObserver(([entry])=>{const w=entry.contentRect.width;if(w){setMeasuredW(w);setMeasured(true);}});ro.observe(containerRef.current);return()=>ro.disconnect();},[]);const scale=measuredW/REF_WIDTH;const handleHoverStart=()=>{if(isTouchMode)return;setIsHovered(true);spinControls.start({rotateY:[0,-wiggleAngle,wiggleAngle,-wiggleAngle,wiggleAngle,0],transition:{duration:wiggleSpeed,ease:\"easeInOut\",repeat:Infinity,repeatDelay:wigglePause}});};const handleHoverEnd=()=>{if(isTouchMode)return;setIsHovered(false);spinControls.start({rotateY:0,transition:{duration:.5,ease:\"easeOut\"}});};const handleClick=()=>{if(link)window.location.href=link;};return(/*\n         * Tilt wrapper: static isometric orientation — match tiltX/Y/Z\n         * to whatever values you have set in the carousel.\n         * The spin animation plays on the inner motion.div independently.\n         */// Fade wrapper: a plain layer, so the 3D slab inside is never\n// flattened mid-fade (opacity below 1 on a 3D layer collapses the\n// box to its front face until the fade completes).\n/*#__PURE__*/_jsx(\"div\",{ref:containerRef,style:{width:\"100%\",height:\"100%\",overflow:\"visible\",opacity:ready?1:0,transition:\"opacity 0.5s ease\"},children:/*#__PURE__*/_jsx(\"div\",{style:{width:\"100%\",height:\"100%\",// overflow: visible lets the lift animate past the component's\n// own boundary — the Stack frame above becomes the actual mask\noverflow:\"visible\",// Re-establish the 3D context in case a Stack parent flattened it\ntransformStyle:\"preserve-3d\",transform:`rotateX(${tiltX}deg) rotateY(${tiltY}deg) rotateZ(${tiltZ}deg)`},children:/*#__PURE__*/_jsx(motion.div,{animate:spinControls,onHoverStart:handleHoverStart,onHoverEnd:handleHoverEnd,onClick:handleClick,style:{width:\"100%\",height:\"100%\",position:\"relative\",cursor:link?\"pointer\":\"default\",transformStyle:\"preserve-3d\",WebkitTapHighlightColor:\"transparent\",userSelect:\"none\",overflow:\"visible\"},children:/*#__PURE__*/_jsx(GlassSlab,{scale:scale,cardThickness:cardThickness,borderWidth:borderWidth,borderTint:borderTint,borderOpacity:borderOpacity,borderGrain:borderGrain,edgeTint:edgeTint,edgeOpacity:edgeOpacity,edgeGrain:edgeGrain,lightContrast:lightContrast,edgeLines:edgeLines,edgeLineTint:edgeLineTint,shadowIntensity:shadowIntensity,cornerReveal:cornerReveal,lift:isHovered?28*scale:0,isHovered:isHovered,children:image?/*#__PURE__*/_jsx(\"img\",{src:sizedSrc(image,Math.round(measuredW*2)),srcSet:buildSrcSet(image,Math.round(measuredW)),sizes:`${Math.round(measuredW)}px`,// The header card is the first thing on a\n// project page, so fetch it right away and ahead\n// of everything else\nloading:\"eager\",fetchpriority:\"high\",decoding:\"async\",draggable:false,style:frontImageStyle}):/*#__PURE__*/_jsx(\"div\",{style:{width:\"100%\",height:\"100%\",background:\"#d0d0d0\",display:\"flex\",alignItems:\"center\",justifyContent:\"center\",color:\"#888\",fontSize:13,fontFamily:\"sans-serif\",letterSpacing:\"0.05em\"},children:\"Add image\"})})})})}));}addPropertyControls(ProjectCard,{image:{type:ControlType.Image,title:\"Image\"},link:{type:ControlType.Link,title:\"Home Link\"},mobileMode:{type:ControlType.Boolean,title:\"Mobile Mode\",defaultValue:false,description:\"Force touch behaviour — auto-detected from screen width normally\"},// Shared cube controls — single source of truth in GlassSlab.jsx\n...slabControls,tiltX:{type:ControlType.Number,title:\"Tilt X\",defaultValue:0,min:-60,max:60,step:1},tiltY:{type:ControlType.Number,title:\"Tilt Y\",defaultValue:0,min:-60,max:60,step:1},tiltZ:{type:ControlType.Number,title:\"Tilt Z\",defaultValue:0,min:-45,max:45,step:1},wiggleAngle:{type:ControlType.Number,title:\"Wiggle Angle\",defaultValue:22,min:1,max:60,step:1,displayStepper:false,description:\"How far the card swings left and right\"},wiggleSpeed:{type:ControlType.Number,title:\"Wiggle Speed\",defaultValue:2.2,min:.4,max:6,step:.1,displayStepper:false,description:\"Duration of one full wiggle cycle in seconds\"},wigglePause:{type:ControlType.Number,title:\"Wiggle Pause\",defaultValue:.3,min:0,max:3,step:.1,displayStepper:false,description:\"Pause between wiggle cycles in seconds\"}});\nexport const __FramerMetadata__ = {\"exports\":{\"default\":{\"type\":\"reactComponent\",\"name\":\"ProjectCard\",\"slots\":[],\"annotations\":{\"framerContractVersion\":\"1\"}},\"__FramerMetadata__\":{\"type\":\"variable\"}}}\n//# sourceMappingURL=./ProjectCard.map"],"mappings":"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"}