{"version":3,"file":"ScrollRevealText.BcCXiInv.mjs","names":["React.useState","React.useRef","React.useMemo"],"sources":["https:/framerusercontent.com/modules/M7kOnL9luwTgaf57KgYU/u2nPUyqFR2rqNpPjI584/ScrollRevealText.js"],"sourcesContent":["/**\n * ScrollRevealText — word-by-word color reveal, scroll-triggered.\n *\n * Each word has its own scroll-progress threshold based on its\n * position in the text. When the user scrolls past a word's threshold\n * (going down), that word kicks off its color tween from `colorFrom`\n * to `colorTo` immediately — no waiting on neighbouring words. When\n * the user scrolls back up past the threshold, the word tweens back.\n * Each tween uses the user-supplied `transition`, so duration / ease /\n * type are configurable in Framer's property panel.\n *\n * Trigger window: scroll progress is measured from `start 0.5` (top of\n * the section at viewport centre) to `end 0.5` (bottom at centre).\n *\n * Typography is supplied via a single `font` prop (Framer's Font\n * control); the whole object is spread onto the wrapper so all fields\n * — including `fontVariationSettings` for variable-font axes — apply.\n *\n * Variable-weight workaround: Framer's \"Variable\" column doesn't always\n * write `fontVariationSettings` for fonts registered as static\n * instances (e.g. when the family name has \"Thin\" baked in). The\n * `weight` prop emits `fontVariationSettings: 'wght' <n>` so variable\n * fonts respect the chosen weight regardless.\n */import{jsx as _jsx}from\"react/jsx-runtime\";import*as React from\"react\";import{addPropertyControls,ControlType,useIsStaticRenderer}from\"framer\";import{motion,useScroll}from\"framer-motion\";/* ── Constants ────────────────────────────────────────────────────── */// useScroll offsets. \"start 0.5\" → progress = 0 when the section's TOP\n// hits viewport centre. \"end 0.5\" → progress = 1 when its BOTTOM hits\n// centre. Each word triggers at its own slice of this progress range.\nconst SCROLL_OFFSET_START=\"start 0.5\";const SCROLL_OFFSET_END=\"end 0.5\";const WHITESPACE=/^\\s+$/;/* ── Subcomponent ─────────────────────────────────────────────────── */function Word({progress,threshold,colorFrom,colorTo,transition,children}){const[isActive,setIsActive]=React.useState(()=>progress.get()>=threshold);// Subscribe to scroll progress and flip state when crossing the\n// word's threshold — in either direction. Each transition fires\n// a fresh tween via the `animate` prop below, so reverse scrolling\n// animates the colour back to `colorFrom`.\nReact.useEffect(()=>{setIsActive(progress.get()>=threshold);return progress.on(\"change\",v=>{setIsActive(v>=threshold);});},[progress,threshold]);return /*#__PURE__*/_jsx(motion.span,{initial:{color:colorFrom},animate:{color:isActive?colorTo:colorFrom},transition:transition,children:children});}/* ── Component ────────────────────────────────────────────────────── *//**\n * @framerSupportedLayoutWidth any-prefer-fixed\n * @framerSupportedLayoutHeight any\n */export default function ScrollRevealText({text,font,weight,colorFrom,colorTo,transition}){const isStatic=useIsStaticRenderer();const ref=React.useRef(null);const{scrollYProgress}=useScroll({target:ref,offset:[SCROLL_OFFSET_START,SCROLL_OFFSET_END]});// Keep whitespace runs as separate non-animated tokens so wrapping\n// flows naturally — only words become motion.spans.\nconst tokens=React.useMemo(()=>text.split(/(\\s+)/).filter(Boolean),[text]);const wordCount=tokens.filter(t=>!WHITESPACE.test(t)).length||1;// Spread the full Font object first so every field Framer writes\n// applies. Then `weight` overrides both `fontWeight` and\n// `fontVariationSettings`. `margin`, `position` win on top.\nconst baseStyle={...font,fontWeight:weight,fontVariationSettings:`'wght' ${weight}`,margin:0,position:\"relative\",// Newlines typed in the Text control survive tokenisation (whitespace runs are kept as literal spans), but a normal <p> collapses them — pre-wrap renders them as real line breaks while still wrapping long lines.\nwhiteSpace:\"pre-wrap\"};// Static render (SSR / page export) shows the fully-revealed state\n// so the snapshot matches what users see after scrolling past.\nif(isStatic){return /*#__PURE__*/_jsx(\"p\",{style:{...baseStyle,color:colorTo},children:text});}let wi=0;return /*#__PURE__*/_jsx(\"p\",{ref:ref,style:baseStyle,children:tokens.map((tok,i)=>{if(WHITESPACE.test(tok)){return /*#__PURE__*/_jsx(\"span\",{children:tok},i);}const idx=wi++;// Centre of word's slot in the [0, 1] progress range.\nconst threshold=(idx+.5)/wordCount;return /*#__PURE__*/_jsx(Word,{progress:scrollYProgress,threshold:threshold,colorFrom:colorFrom,colorTo:colorTo,transition:transition,children:tok},i);})});}/* ── Defaults ─────────────────────────────────────────────────────── */ScrollRevealText.defaultProps={text:\"Scroll to reveal each word in sequence as the line transitions from one color to the next.\",font:{fontSize:48,lineHeight:1.2,letterSpacing:-.02},weight:400,colorFrom:\"rgba(17, 17, 17, 0.2)\",colorTo:\"#111111\",transition:{duration:.6,ease:\"easeOut\"}};/* ── Property Controls ────────────────────────────────────────────── */addPropertyControls(ScrollRevealText,{text:{type:ControlType.String,title:\"Text\",defaultValue:\"Scroll to reveal each word in sequence as the line transitions from one color to the next.\",displayTextArea:true},font:{type:ControlType.Font,title:\"Font\",controls:\"extended\",defaultFontType:\"sans-serif\",defaultValue:{fontSize:48,lineHeight:1.2,letterSpacing:-.02,variant:\"Semibold\"},displayTextAlignment:true,displayFontSize:true},weight:{type:ControlType.Number,title:\"Weight\",defaultValue:400,min:100,max:900,step:1,displayStepper:true},colorFrom:{type:ControlType.Color,title:\"From\",defaultValue:\"rgba(17, 17, 17, 0.2)\"},colorTo:{type:ControlType.Color,title:\"To\",defaultValue:\"#111111\"},transition:{type:ControlType.Transition,title:\"Transition\"}});\nexport const __FramerMetadata__ = {\"exports\":{\"default\":{\"type\":\"reactComponent\",\"name\":\"ScrollRevealText\",\"slots\":[],\"annotations\":{\"framerSupportedLayoutWidth\":\"any-prefer-fixed\",\"framerContractVersion\":\"1\",\"framerSupportedLayoutHeight\":\"any\"}},\"__FramerMetadata__\":{\"type\":\"variable\"}}}\n//# sourceMappingURL=./ScrollRevealText.map"],"mappings":"0PA0B0K,SAAS,EAAK,CAAC,WAAS,YAAU,YAAU,UAAQ,aAAW,YAAU,CAAC,GAAK,CAAC,EAAS,GAAaA,MAAmB,EAAS,KAAK,EAAE,EAAU,CAI5K,OAAjJ,OAAqB,EAAY,EAAS,KAAK,EAAE,EAAU,CAAQ,EAAS,GAAG,SAAS,GAAG,CAAC,EAAY,GAAG,EAAU,EAAG,EAAG,CAAC,EAAS,EAAU,CAAC,CAAqB,EAAK,EAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAU,CAAC,QAAQ,CAAC,MAAM,EAAS,EAAQ,EAAU,CAAY,aAAoB,WAAS,CAAC,CAGlS,SAAwB,EAAiB,CAAC,OAAK,OAAK,SAAO,YAAU,UAAQ,cAAY,CAAC,IAAM,EAAS,GAAqB,CAAO,EAAIC,EAAa,KAAK,CAAM,CAAC,mBAAiB,EAAU,CAAC,OAAO,EAAI,OAAO,CAAC,EAAoB,EAAkB,CAAC,CAAC,CAEtP,EAAOC,MAAkB,EAAK,MAAM,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAAC,EAAK,CAAC,CAAO,EAAU,EAAO,OAAO,GAAG,CAAC,EAAW,KAAK,EAAE,CAAC,CAAC,QAAQ,EAGnI,EAAU,CAAC,GAAG,EAAK,WAAW,EAAO,sBAAsB,UAAU,IAAS,OAAO,EAAE,SAAS,WACtG,WAAW,WAAW,CAEtB,GAAG,EAAU,OAAoB,EAAK,IAAI,CAAC,MAAM,CAAC,GAAG,EAAU,MAAM,EAAQ,CAAC,SAAS,EAAK,CAAC,CAAE,IAAI,EAAG,EAAE,OAAoB,EAAK,IAAI,CAAK,MAAI,MAAM,EAAU,SAAS,EAAO,KAAK,EAAI,IAAQ,EAAW,KAAK,EAAI,CAAsB,EAAK,OAAO,CAAC,SAAS,EAAI,CAAC,EAAE,CAC/M,EAAK,EAAK,CAAC,SAAS,EAAgB,WADuL,IAC7P,IAAI,EAAgG,YAAkB,UAAmB,aAAW,SAAS,EAAI,CAAC,EAAE,CAAG,CAAC,CAAC,wBAnBhJ,IAA4B,IAAwE,IAA4C,CAGxL,EAAoB,YAAkB,EAAkB,UAAgB,EAAW,QAgBgL,EAAiB,aAAa,CAAC,KAAK,6FAA6F,KAAK,CAAC,SAAS,GAAG,WAAW,IAAI,cAAc,KAAK,CAAC,OAAO,IAAI,UAAU,wBAAwB,QAAQ,UAAU,WAAW,CAAC,SAAS,GAAG,KAAK,UAAU,CAAC,CAA0E,EAAoB,EAAiB,CAAC,KAAK,CAAC,KAAK,EAAY,OAAO,MAAM,OAAO,aAAa,6FAA6F,gBAAgB,GAAK,CAAC,KAAK,CAAC,KAAK,EAAY,KAAK,MAAM,OAAO,SAAS,WAAW,gBAAgB,aAAa,aAAa,CAAC,SAAS,GAAG,WAAW,IAAI,cAAc,KAAK,QAAQ,WAAW,CAAC,qBAAqB,GAAK,gBAAgB,GAAK,CAAC,OAAO,CAAC,KAAK,EAAY,OAAO,MAAM,SAAS,aAAa,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,EAAE,eAAe,GAAK,CAAC,UAAU,CAAC,KAAK,EAAY,MAAM,MAAM,OAAO,aAAa,wBAAwB,CAAC,QAAQ,CAAC,KAAK,EAAY,MAAM,MAAM,KAAK,aAAa,UAAU,CAAC,WAAW,CAAC,KAAK,EAAY,WAAW,MAAM,aAAa,CAAC,CAAC"}