Your Brain Was Built to Change. You Need a Neuro-Advisor.

Rewire the Source

Your neural architecture can be permanently rewired.

You have done the work — the conversations, the strategies, the weight of holding everything together while running out of capacity. Nothing has reached it. Dr. Sydney Ceruto identifies the neural patterns driving what you cannot change and rewires them at their source. She does not manage what is wrong. She eliminates the architecture that created it.

PhD, Behavioral & Cognitive Neuroscience — NYU | Master’s Degrees in Clinical Psychology & Business Psychology — Yale | Author, The Dopamine Code (Simon & Schuster)

Limited availability — Dr. Ceruto works with a select number of individuals at any given time.

Dr. Sydney Ceruto Neuro-Advisor & Author in a White Suit with Gold Buttons
Dr. Sydney Ceruto Neuro-Advisor & Author in a White Suit with Gold Buttons
Forbes
USA Today
Huffington Post
Newsweek
Associated Press
Cosmopolitan
Business Insider
Forbes
USA Today
Huffington Post
Newsweek
Associated Press
Cosmopolitan
Business Insider
THE FOUNDER

The Mind Behind MindLAB

Neuro-Advisor. Neuroscientist. Author.

At 15, Dr. Sydney Ceruto lost both her parents. What followed were years of conventional approaches that produced understanding but zero lasting change. Her brain kept looping through the same patterns — the same fear, the same anxiety, the same conviction that something was fundamentally broken.

Nothing was broken. The neural pathways were stuck. So she went to the source — behavioral and cognitive neuroscience — and rewired them herself.

That experience became the foundation of MindLAB Neuroscience and the methodology she now delivers to a select number of clients: Real-Time Neuroplasticity™.

Macro visualization of neural pathways with copper-metallic synaptic connections — MindLAB Neuroscience, Dr. Sydney Ceruto
THE PATTERN

The Problem You Came With — Is It Actually the Problem?

Most people spend years solving the wrong equation.

You arrive knowing what hurts — the anxiety, the relationship that keeps fracturing, the career pattern you cannot break, the feeling that something is wrong but you cannot name it. You have described it to professionals. You have analyzed it yourself at 3 AM. You have tried strategies designed to address exactly what you think the problem is.

 

Dr. Ceruto consistently finds something else entirely. Within the first conversation or two, she identifies the actual root — often a neural pattern so deeply embedded that no conventional approach was built to see it, let alone reach it. The issue you have been fighting is usually a symptom. The architecture underneath it is the problem. And until someone maps that architecture, every intervention you try is aimed at the wrong target.

THE MOMENT

Your Brain Rewires in the Moment, Not in Reflection

When the brain is primed for change, the window is brief.

A crisis, a decision, an emotional flashpoint — these are the moments when the brain enters a state of heightened plasticity. The window for restructuring is biologically real and brief. Real-Time Neuroplasticity™ is Dr. Ceruto’s methodology for intervening in precisely these moments, before the window closes.

 

This is why reflection alone does not produce lasting change. By the time you are recounting the moment to someone in a calm room, the pathway has already reinforced itself. The window closed. The circuit consolidated. Dr. Ceruto intervenes while the pattern is still firing — when your brain is actively choosing which circuit to strengthen next.

THE PARTNERSHIP

A Cognitive Partner Embedded in Your World

She's already inside your world when it matters most.

The crisis hits at 2 AM. The decision is live. The pattern fires fifteen minutes before something that changes everything. Most professionals are unavailable for precisely the moments when your brain is most capable of changing — because those moments do not arrive on a schedule.

 

Dr. Ceruto does not work from a distance. She does not operate on a schedule that has nothing to do with when your life actually happens. When the moment arrives, she is already there — not as a service you access, but as a cognitive partner who understands your neural architecture well enough to intervene at the exact moment when restructuring is possible. If you are looking for someone to manage the pattern and help you cope with it more gracefully, this is not that. This is a neuroscientist embedded in your life, engineering change in real time.

THE RESULT

Your Brain Doesn't Learn to Cope. It Restructures.

Not symptom management — permanent structural change.

Dr. Ceruto does not build workarounds. She does not teach you to manage what is wrong or breathe through what hurts. She eliminates the neural architecture that produced the unwanted pattern — the circuitry restructures at its source, and the behavior changes because the pathway that drove it no longer exists.

 

The old pattern does not gradually fade — it loses its infrastructure. The circuitry that sustained it is replaced, and the response you have been fighting simply has nowhere to live.

Book a Strategy Call →

THE PROTOCOL

Real-Time Neuroplasticity™

Precision Over Process

Every MindLAB program begins with Dr. Ceruto identifying the specific neural patterns driving your current behavior — not the symptoms you describe, but the circuitry underneath. She architects a protocol calibrated to your brain’s architecture, timed to the moments when restructuring is biologically possible.

NeuroSync™ — 90 Days

One issue. Ninety days. Dr. Ceruto isolates the root neural pattern, engineers a precise intervention, and drives measurable restructuring — not over months of exploration, but within a defined, high-intensity protocol built for resolution.

NeuroConcierge™ — 1 Year

Dr. Ceruto embeds into your life as an ongoing cognitive partner — across personal, professional, and relational domains. This is not a program you attend. It is a permanent shift in how you operate.

Success Stories

“I came to Dr. Ceruto thinking I needed help with my career, but she quickly recognized that the real roadblocks were the relationships I was choosing and how I dealt with conflict. With her support, I finally left unhealthy situations I’d struggled to end for years. She helped me identify deep-seated patterns I didn’t realize were holding me back. I never feel rushed, and she follows up with detailed written insights I reflect on for weeks. She uncovered major blockers I would never have spotted alone.”

Rachel L. — Brand Strategist Montecito, CA

“I reached out to Dr. Ceruto for help with an ongoing issue I couldn’t resolve. Having discussed it with friends and family, I thought it would be challenging for her to offer a fresh perspective. I was absolutely wrong. She asked all the right questions that pushed me to articulate my thoughts differently than anyone else had. After eight weeks, she made the answer seem so clear. Dr. Ceruto is warm, objective, and open-minded — it leaves no doubt how much she genuinely cares.”

Claudia S. — Physician Wellesley, MA

“When I started working with Dr. Ceruto, I was feeling stuck, not happy whatsoever, detached from family and friends, and definitely not confident. I’d never tried a neuroscience-based approach before, so I wasn’t sure what to expect — but I figured I had nothing to lose. My life has completely changed for the better. I don’t feel comfortable discussing publicly why I sought help, but I was made to feel safe, secure, and consistently supported. Just knowing I could reach her day or night was a relief.”

Algo R. — Fund Manager Dubai, UAE

“Working with Dr. Ceruto was one of the most transformative experiences of my life. I was stuck in a cycle of dissatisfaction, unsure of where I was headed or why I felt so unfulfilled. From the very first session, she helped me peel back the layers and uncover what truly mattered. Her ability to connect neuroscience with practical life strategies was incredible. She guided me to clarify my goals, break free from limiting beliefs, and align my actions with my values. I finally feel real purpose.”

Nichole P. — Wealth Advisor Sarasota, FL

“When I first started with Dr. Ceruto, I’d felt at a standstill for two years. Over several months, we worked through my cognitive distortions and I ultimately landed my dream job after years of rejections. She is both gentle and assertive — she tells it like it is, and you’re never second-guessing what she means. Most importantly, she takes a personal interest in my mental, emotional, and physical wellbeing. I have no doubt I’ll be in touch with Dr. Ceruto for years to come.”

Chelsea A. — Publicist Dublin, IE

“Unfortunate consequences finally forced me to deal with my anger issues. I’d read several books and even sought out a notable anger specialist, but nothing was clicking. Then I found Sydney’s approach and was intrigued. Her insightfulness and warm manner helped me through a very low point in my life. Together we worked through all my pent-up anger and rage, and she gave me real tools to manage it going forward. I now work to help others learn how to control their own anger.”

Gina P. — Trial Attorney Naples, FL

“I struggled with debilitating anxiety for years, trying countless therapies and medications with little success. Finding Dr. Ceruto and her neuroscience-based approach was truly life-changing. From our very first session, her deep knowledge of brain science and how it applies to anxiety gave me real hope. What sets her apart is that perfect blend of expertise and compassion — she genuinely cared about my progress and responded quickly even outside of our scheduled sessions. I can now enjoy social situations and excel at work.”

Brian T. — Architect Chicago, IL

“Dr. Ceruto is truly exceptional. I’ve always been skeptical about anyone being able to get through to me, but she has a unique way of bringing about profound changes. She is incredibly intuitive and often knows the answers to complex matters before you even get there. In just a couple of months, I noticed significant changes in how I live my life. Sydney is honest and direct, yet compassionate. She personally relates to you without judgment and demonstrates real investment in your success.”

Ash — Neurologist La Jolla, CA

The Protocol Begins Here

Your Move

New Outcomes Require New Neural Pathways

You cannot think your way out of a biological loop. If you are ready to engineer the specific neural pathways required for what comes next, it starts here.

Limited availability

Private executive office doorway revealing navy leather chair crystal brain sculpture and walnut desk at MindLAB Neuroscience

Your brain’s default mode is counterintuitive. It wants to repeat what it has done the most — even if it’s destructive. That’s how you end up making the same decisions you swore you wouldn’t.

Dr. Sydney Ceruto

The Dopamine Code

Decode Your Drive

Why Your Brain Rewards the Wrong Things

Your brain’s reward system runs every decision, every craving, every crash — and it was never designed for the life you’re living. The Dopamine Code is Dr. Ceruto’s framework for understanding the architecture behind what drives you, drains you, and keeps you locked in patterns that willpower alone will never fix.

Published by Simon & Schuster, The Dopamine Code is Dr. Ceruto’s framework for building your own Dopamine Menu — a personalized system for motivation, focus, and enduring life satisfaction.

Ships June 9, 2026

The Dopamine Code book cover by Dr. Sydney Ceruto helps with dopamine anchoring.
Secret Link

The Intelligence Brief

Neuroscience-backed analysis on how your brain drives what you feel, what you choose, and what you can’t seem to change — direct from Dr. Ceruto.

Warning: Permanently added 'mindlabneuroscience.tempurl.host' (ED25519) to the list of known hosts. /* ============================================================ MINDLAB NEUROSCIENCE — GSAP ANIMATION ENGINE Version: 4.6 Date: 2026-05-27 (v4.6 patch — four corrections to v4.5: (1) clearProps:'transform' REMOVED from every code path. Root cause of the v4.5 regression: the CSS rule `.gsap-reveal { transform: translateY(40px) }` re-asserts every time inline transform is cleared. clearProps was supposed to clean up GSAP's inline transform, but it just made the element snap back to the 40px CSS-defined state. Fix: use explicit gsap.set(el, { opacity: 1, y: 0 }) which writes inline transform: translate(0,0) — inline wins over CSS, no clearProps needed. (2) Tier 1 reverted from gsap.to back to gsap.fromTo for predictable from-state. gsap.to's implicit current-state read was producing inconsistent inline state for sections whose triggers hadn't yet entered (Mr. Marc's S117 2de0fa57/51f223f2 regression). gsap.fromTo with explicit from-state {opacity:0, y:40} is back. onRefresh handler retained (without clearProps) to force-reveal when self.progress===1 OR self.scroll()>self.end. onEnter and onComplete dropped — they only did clearProps which was the bug. (3) Idempotency guard added per Mr. Marc S117 rec — section.dataset.gsapRevealInit prevents Tier 1 from creating a second ScrollTrigger if the engine runs twice (Elementor refresh, etc.). (4) +2s matrix-stuck sweep replaced with a polling sweep that fires every 2s for 10s and tracks "has this element ever been in viewport" (dataset.gsapSeen). When a seen element still has |ty|>0.5, sweep force-clears using direct inline (el.style.transform='none'; opacity='1') — NO gsap.set with clearProps, since that re-triggers the CSS-fallback bug from v4.5. v4.5 patch — three additions: (1) Tier 1 reveal switched from gsap.fromTo to gsap.to with onRefresh + onComplete + onEnter handlers. Fixes the case where a tall section has its ScrollTrigger start point above viewport at page load — ScrollTrigger marks it "passed" without playing the tween, so the inline transform: translateY(40px) from fromTo's from-state was never cleared. onRefresh detects this via self.progress === 1 || self.scroll() > self.end and force-reveals immediately; onEnter + onComplete also call clearProps:'transform' so every code path clears the inline transform. v4.3 past-viewport guard retained as fast-path. (2) 2-second failsafe now also sweeps .gsap-reveal for stuck Y-translate (matrix(a,b,c,d,tx,ty) where |ty|>0.5). Catches any container that somehow escaped both onRefresh and onComplete. (3) window.lenis exposed (was previously block-scoped local var). Enables console debugging + Mr. Marc's optional bridge code path against window.lenis. v4.4 patch — two additions: (1) 2-second ScrollTrigger-zero-or-undefined failsafe at end of IIFE that fires body.gsap-failed AND strips inline styles set by aborted gsap.fromTo calls. Catches the scenario where Hummingbird delay-JS releases ScrollTrigger AFTER engine init has already early-exited or where gsap.fromTo registered zero triggers because Hummingbird hadn't yet released ScrollTrigger.min.js. (2) Lenis init no longer guarded by the `!document.body.classList.contains('single')` check — single-post articles now get Lenis + the ScrollTrigger bridge, so reveal animations actually progress on scroll (without the bridge ScrollTrigger reads window.scrollY while Lenis controls a virtual position). v4.3 patch — past-viewport guards in Tier 1 + Tier 2 now force final-state via gsap.set() before returning. Previously a silent `return;` left .gsap-reveal and .gsap-stagger elements stuck at CSS pre-animation opacity:0 when autoClassify() ran after user-initiated scroll under Hummingbird delay-JS pattern. Tiers 2B/3C/4B already used gsap.set in v4.1 and remain unchanged. Entry-point fail-safe (already present in v4.2) preserved. Trigger incident: dopamine-anchoring-neuroscience-motivation production article body invisible 2026-05-27 — three .gsap-reveal containers stuck at opacity:0; transform translateY(40px). Confirmed via Chrome Extension runtime diagnostic. v4.2 patch — Tier 3B parallax refactored to transform-based (translateY on a child layer instead of background-position-y on the section). Decouples parallax from cover-fit math so movement is visible regardless of image-vs-section aspect ratio. GPU-composited via will-change:transform. v4.1 patch — past-viewport guards added to Tier 1/2/2B/3C/4B to prevent flash when init runs after user scroll under Hummingbird delay-JS pattern.) Deploy: Elementor > Custom Code > end (priority 5) Dependencies: GSAP 3.12+, ScrollTrigger, Lenis (optional) PURPOSE: Runtime animation controller for all scroll-driven motion. Pairs with CSS pre-animation states in Additional CSS. CSS handles initial hidden states. This script animates FROM those states to final visible states. ARCHITECTURE: - Auto-Classify: Detects animation targets from DOM structure - Tier 1: Section entrance reveals (auto-detected) - Tier 2: Staggered hierarchy (auto-detected) - Tier 2B: H6 eyebrow letter-spacing (auto-detected) - Tier 3A: Hero entrance sequence (auto-detected) - Tier 3B: Section background parallax (auto-detected) - Tier 3C: Counter animations (.gsap-counter + data attrs) - Tier 4A: Pin-sequence with wayfinding - Tier 4B: Image clip reveals (auto-detected) - Tier 5A: Custom cursor - Tier 5B: Magnetic buttons AUTO-DETECTION (v4.0): Elements are classified by DOM structure at runtime. JS adds .gsap-* classes before animations run. CSS pre-animation states remain class-based for fail-safe and reduced-motion compatibility. Manual classes still required: - .pin-sequence / .horizontal-scroll (section structure) - .hero-parent / .hero-text-col / .hero-image-col (hero anatomy) - .program-card (protocol/program card containers) - .no-parallax / .no-reveal (opt-out) - .gsap-counter + data-gsap-counter (target values) - .image-dk / .image-lt / .col-wrapper (image system) - .image-pt (aspect-ratio preservation) SAFETY: - Respects prefers-reduced-motion (self-disables) - 3-second fail-safe adds .gsap-failed to - CTA buttons never go to opacity:0 (SS 1.6.3) - Lenis integration optional (graceful if absent) - Cursor + magnetic skip touch devices ============================================================ */ (function () { 'use strict'; /* ---------------------------------------------------------- FAIL-SAFE: If GSAP hasn't loaded, bail and reveal all ---------------------------------------------------------- */ if (typeof gsap === 'undefined' || typeof ScrollTrigger === 'undefined') { document.body.classList.add('gsap-failed'); return; } gsap.registerPlugin(ScrollTrigger); var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; /* ========================================================== AUTO-CLASSIFY: Detect animation targets from DOM structure Runs before all animation tiers. Adds .gsap-* classes so CSS pre-animation states and GSAP selectors work without manual Elementor class assignments. Timing note: The preloader overlay covers the page until after this runs, so auto-classified elements receive their hidden CSS states before the user sees them. No FOUC. ========================================================== */ function autoClassify() { var page = document.querySelector( '.elementor-location-page, .elementor-location-single, ' + '[data-elementor-type="wp-page"], [data-elementor-type="wp-post"]' ); if (!page) return; var topSections = page.querySelectorAll(':scope > .e-con'); if (topSections.length === 0) return; var hero = topSections[0]; var specials = ['pin-sequence', 'horizontal-scroll', 'no-reveal']; function isSpecial(el) { return specials.some(function (cls) { return el.classList.contains(cls); }); } /* --- Hero (first top-level section) --- */ if (!isSpecial(hero)) { hero.classList.add('gsap-hero-bg'); var h6 = hero.querySelector('h6.elementor-heading-title'); if (h6) { var h6w = h6.closest('.elementor-widget'); if (h6w) h6w.classList.add('gsap-hero-h6'); } var h2 = hero.querySelector('h2.elementor-heading-title'); if (h2) { var h2w = h2.closest('.elementor-widget'); if (h2w) h2w.classList.add('gsap-hero-h2'); } var texts = hero.querySelectorAll('.elementor-widget-text-editor'); if (texts.length >= 1) texts[0].classList.add('gsap-hero-body'); if (texts.length >= 2) texts[1].classList.add('gsap-hero-authority'); var btn = hero.querySelector('.elementor-widget-button'); if (btn) btn.classList.add('gsap-btn-entrance'); } /* --- Eyebrows: H6 headings outside hero, pin-sequence, and loop/carousel components --- */ page.querySelectorAll('h6.elementor-heading-title').forEach(function (h6) { var w = h6.closest('.elementor-widget'); if (!w) return; if (hero && hero.contains(w)) return; if (w.closest('.pin-sequence')) return; if (w.closest('.elementor-loop__container')) return; if (w.closest('.swiper-wrapper')) return; /* Only classify if widget is a direct section child (top-level H6, not nested inside cards/carousels) */ if (!w.parentElement || !w.parentElement.closest('.e-con.e-parent')) return; w.classList.add('gsap-eyebrow'); }); /* --- Content sections + stagger children --- */ for (var i = 1; i < topSections.length; i++) { var section = topSections[i]; if (isSpecial(section)) continue; section.classList.add('gsap-reveal'); /* Stagger: all direct widget descendants of the section's container tree. Excludes widgets inside loop/carousel components (they have their own animation context). */ var widgets = section.querySelectorAll('.elementor-widget'); widgets.forEach(function (w) { if (w.classList.contains('gsap-eyebrow')) return; if (w.closest('.elementor-loop__container')) return; if (w.closest('.swiper-wrapper')) return; w.classList.add('gsap-stagger'); }); } /* --- Image clip reveals in two-column layouts --- */ page.querySelectorAll( '.col-wrapper .image-dk:not(.image-pt), ' + '.col-wrapper .image-lt:not(.image-pt)' ).forEach(function (el) { el.classList.add('gsap-clip-reveal'); }); } autoClassify(); /* ---------------------------------------------------------- REDUCED MOTION: Respect user preference If enabled, reveal everything in final state and exit. CSS handles the visual reset; JS just needs to not run. ---------------------------------------------------------- */ if (prefersReducedMotion) { document.querySelectorAll( '.gsap-reveal, .gsap-stagger, .gsap-eyebrow, .gsap-counter' ).forEach(function (el) { el.style.opacity = '1'; el.style.transform = 'none'; }); document.querySelectorAll('.gsap-clip-reveal').forEach(function (el) { el.style.clipPath = 'none'; }); initPinSequenceWayfinding(true); return; } /* ---------------------------------------------------------- FAIL-SAFE TIMER: 3 seconds to animate or reveal ---------------------------------------------------------- */ var failSafeTimer = setTimeout(function () { document.body.classList.add('gsap-failed'); }, 3000); /* ---------------------------------------------------------- LENIS INTEGRATION (optional) If Lenis is loaded, sync ScrollTrigger with its scroll. ---------------------------------------------------------- */ if (typeof Lenis !== 'undefined') { var lenis = new Lenis({ duration: 1.2, easing: function (t) { return Math.min(1, 1.001 - Math.pow(2, -10 * t)); }, orientation: 'vertical', smoothWheel: true, prevent: function (node) { return node.closest('#left-sidebar, #right-sidebar'); } }); /* v4.5: expose Lenis instance globally for console debugging + any external code that wants the bridge */ window.lenis = lenis; lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(function (time) { lenis.raf(time * 1000); }); gsap.ticker.lagSmoothing(0); } /* ========================================================== TIER 1: SECTION ENTRANCE REVEALS Target: .gsap-reveal (auto-classified) Effect: Fade up from translateY(40px) + opacity 0 ========================================================== */ gsap.utils.toArray('.gsap-reveal').forEach(function (section) { /* v4.6 idempotency guard (Mr. Marc S117 rec) — prevents the Tier 1 reveal logic from registering a second ScrollTrigger on the same section if the engine init runs twice (Elementor frontend.js re-init, window.load firing after DOMContentLoaded, etc.). */ if (section.dataset.gsapRevealInit === '1') return; section.dataset.gsapRevealInit = '1'; /* v4.3 past-viewport guard (v4.6 fix — no clearProps): Init runs after user scroll under Hummingbird delay-JS. autoClassify() has already added .gsap-reveal — which triggers the CSS pre-animation state opacity:0; translateY(40px). A silent `return;` (v4.1/v4.2) left the section invisible forever. We must explicitly reveal to final visible state when past the threshold. v4.6: REMOVED clearProps:'transform' — CSS rule `.gsap-reveal { transform: translateY(40px) }` re-asserts when inline transform is cleared, so the element falls back to the 40px state. Leave inline transform: translate(0,0) intact — inline wins. */ if (section.getBoundingClientRect().top self.end) { gsap.set(section, { opacity: 1, y: 0 }); } } } }); }); /* ========================================================== TIER 2: STAGGERED HIERARCHY Target: .gsap-stagger (auto-classified children) Effect: Stagger fade up, 0.12s between each child ========================================================== */ gsap.utils.toArray('.gsap-reveal').forEach(function (section) { var staggerChildren = section.querySelectorAll('.gsap-stagger'); if (staggerChildren.length === 0) return; /* v4.3 past-viewport guard fix (Tier 2 trigger is 'top 80%'). Same bug as Tier 1: silent `return;` left .gsap-stagger children invisible when init ran past viewport. Force final visible state instead of bailing. */ if (section.getBoundingClientRect().top < window.innerHeight * 0.80) { /* v4.6 fix: no clearProps (would fall back to CSS rule). */ gsap.set(staggerChildren, { opacity: 1, y: 0 }); return; } gsap.fromTo(staggerChildren, {opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 0.6, stagger: 0.12, ease: 'power2.out', scrollTrigger: { trigger: section, start: 'top 80%', once: true } }); }); /* ========================================================== TIER 2B: H6 EYEBROW LETTER-SPACING Target: .gsap-eyebrow (auto-classified) Effect: letter-spacing 6px → 3px, opacity 0 → 1 Brand signature animation for section labels. ========================================================== */ gsap.utils.toArray('.gsap-eyebrow').forEach(function (eyebrow) { /* Past-viewport guard (delay-JS pattern). Eyebrows above the 'top 85%' threshold get final state directly to prevent letter-spacing flash from the 6px-to-3px from-state. */ if (eyebrow.getBoundingClientRect().top .e-con, ' + '.elementor-location-single > .e-con, ' + '[data-elementor-type="wp-page"] > .e-con, ' + '[data-elementor-type="wp-post"] > .e-con, ' + /* Location page hero sections (2026-03-23) */ '.location-hero, .location-hub__hero, ' + /* Success Stories testimonials section (2026-05-27 S117 chip) */ '.location-stories'; var sections = document.querySelectorAll(selector); var processed = new WeakSet(); function applyParallax(section) { if (processed.has(section)) return false; if (section.classList.contains('no-parallax')) return false; var bgImage = window.getComputedStyle(section).backgroundImage; if (!bgImage || bgImage === 'none') return false; try { var s = JSON.parse(section.getAttribute('data-settings') || '{ }'); if (s.background_background === 'video') return false; } catch (e) { } processed.add(section); section.classList.add('gsap-parallax-bg'); /* v4.2 transform-based parallax. Replaces background-position-y animation which only worked when the bg-image happened to be taller (relative aspect) than the section. The new approach moves a child layer via transform — works for any image-vs- section aspect combination, GPU-composited, smoother scroll. */ // Make section a positioning context with clipped overflow. // isolation:isolate keeps the layer's z-index:-1 contained. section.style.overflow = 'hidden'; section.style.isolation = 'isolate'; if (window.getComputedStyle(section).position === 'static') { section.style.position = 'relative'; } // Create child layer that holds the bg-image with overflow room. // inset -20% top + bottom = layer is 140% of section height, so // the transform animation (yPercent -10 → +10) always covers the // section regardless of section height. var bgLayer = document.createElement('div'); bgLayer.className = 'gsap-parallax-bg__layer'; bgLayer.setAttribute('aria-hidden', 'true'); bgLayer.style.cssText = [ 'position:absolute', 'top:-20%', 'left:0', 'right:0', 'bottom:-20%', 'background-image:' + bgImage, 'background-size:cover', 'background-position:center', 'background-repeat:no-repeat', 'z-index:-1', 'will-change:transform', 'pointer-events:none' ].join(';'); section.insertBefore(bgLayer, section.firstChild); // Move bg-image off the section to avoid double-render. section.style.backgroundImage = 'none'; // Animate the layer transform. yPercent is % of layer's own // height. Range ±10% of layer height (= ±14% of section height) // sits well within the 40% overflow margin, so the section is // always covered. gsap.fromTo(bgLayer, { yPercent: -10 }, { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, start: 'top bottom', end: 'bottom top', scrub: 0.5 } }); return true; } sections.forEach(applyParallax); var io = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) return; var el = entry.target; var attempts = 0; var poll = setInterval(function () { var applied = applyParallax(el); attempts++; if (applied || attempts >= 10) { clearInterval(poll); if (applied) io.unobserve(el); } }, 200); }); }, {rootMargin: '300px 0px' }); sections.forEach(function (section) { if (!processed.has(section)) io.observe(section); }); } initParallax(); /* ========================================================== TIER 3C: COUNTER ANIMATIONS Target: .gsap-counter (manual — needs data attributes) Data attributes: data-gsap-counter="150" → target number data-gsap-suffix="+" → optional suffix data-gsap-prefix="$" → optional prefix data-gsap-duration="2" → optional duration (default 2s) Effect: Count from 0 → target, with fade-in + Y shift ========================================================== */ gsap.utils.toArray('.gsap-counter').forEach(function (counter) { var target = parseFloat(counter.getAttribute('data-gsap-counter')) || 0; var suffix = counter.getAttribute('data-gsap-suffix') || ''; var prefix = counter.getAttribute('data-gsap-prefix') || ''; var duration = parseFloat(counter.getAttribute('data-gsap-duration')) || 2; var isDecimal = String(target).includes('.'); var originalText = counter.textContent; counter.setAttribute('data-gsap-original', originalText); /* Past-viewport guard (delay-JS pattern). If init runs after the counter has scrolled past 'top 85%', show the final value immediately instead of resetting to 0 and animating. */ if (counter.getBoundingClientRect().top < window.innerHeight * 0.85) { counter.textContent = prefix + (isDecimal ? target.toFixed(1) : Math.round(target)) + suffix; gsap.set(counter, { opacity: 1, y: 0 }); return; } counter.textContent = prefix + '0' + suffix; var obj = {val: 0 }; var counterTl = gsap.timeline({ scrollTrigger: { trigger: counter, start: 'top 85%', once: true } }); counterTl.to(counter, { opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' }, 0); counterTl.to(obj, { val: target, duration: duration, ease: 'power1.out', onUpdate: function () { if (isDecimal) { counter.textContent = prefix + obj.val.toFixed(1) + suffix; } else { counter.textContent = prefix + Math.round(obj.val) + suffix; } } }, 0); }); /* ========================================================== TIER 4A: PIN-SEQUENCE WITH WAYFINDING Target: .pin-sequence Injects: progress dots, panel counter, scroll cue Effect: Panels crossfade on scroll, wayfinding updates ========================================================== */ function initPinSequenceWayfinding(staticOnly) { var pinSection = document.querySelector('.pin-sequence'); if (!pinSection) return; var panels = pinSection.querySelectorAll('.pin-sequence__panel'); if (panels.length === 0) return; var totalPanels = panels.length; /* --- Inject progress indicator (dots + counter) --- */ var progressEl = pinSection.querySelector('.pin-sequence__progress'); if (!progressEl) { progressEl = document.createElement('div'); progressEl.className = 'pin-sequence__progress'; for (var i = 0; i < totalPanels; i++) { var dot = document.createElement('div'); dot.className = 'pin-sequence__dot' + (i === 0 ? ' active' : ''); progressEl.appendChild(dot); } var counterEl = document.createElement('div'); counterEl.className = 'pin-sequence__counter'; counterEl.textContent = '1 of ' + totalPanels; progressEl.appendChild(counterEl); pinSection.appendChild(progressEl); } /* --- Inject scroll-for-more cue --- */ var scrollCue = pinSection.querySelector('.pin-sequence__scroll-cue'); if (!scrollCue) { scrollCue = document.createElement('div'); scrollCue.className = 'pin-sequence__scroll-cue'; scrollCue.innerHTML = '' + '' + '' + '' + '' + '' + '' + 'scroll for more'; pinSection.appendChild(scrollCue); } if (staticOnly) return; /* --- Animate the scroll wheel inside the mouse icon --- */ var scrollWheel = scrollCue.querySelector('.pin-sequence__scroll-wheel'); if (scrollWheel) { gsap.to(scrollWheel, { attr: { y1: 12, y2: 18 }, opacity: 0.5, duration: 1, ease: 'power1.inOut', repeat: -1, yoyo: true }); } /* --- Update wayfinding on panel change --- */ var dots = progressEl.querySelectorAll('.pin-sequence__dot'); var counterText = progressEl.querySelector('.pin-sequence__counter'); function updateWayfinding(index) { dots.forEach(function (dot, i) { dot.classList.toggle('active', i === index); }); if (counterText) { counterText.textContent = (index + 1) + ' of ' + totalPanels; } if (scrollCue) { scrollCue.classList.toggle('at-end', index === totalPanels - 1); } } return updateWayfinding; } /* --- Pin-sequence ScrollTrigger animation --- */ /* Viewport guard (Audit Fix #9): Pin-scroll only fires when viewport height >= 480px. On landscape phones with very short viewports (~300–400px), panels stack statically to prevent content overflowing 100vh bounds. Ref: responsive-audit-homepage.md §9 */ var pinSection = document.querySelector('.pin-sequence'); if (pinSection) { var panels = pinSection.querySelectorAll('.pin-sequence__panel'); var updateWayfinding = initPinSequenceWayfinding(false); if (panels.length > 1 && updateWayfinding) { ScrollTrigger.matchMedia({ /* Tall viewports: full pin-scroll experience */ '(min-height: 480px)': function () { var pinTl = gsap.timeline({ scrollTrigger: { trigger: pinSection, start: 'top top', end: '+=' + (panels.length * 100) + '%', pin: true, scrub: 0.8, onUpdate: function (self) { var progress = self.progress; var activeIndex = Math.min( Math.floor(progress * panels.length), panels.length - 1 ); updateWayfinding(activeIndex); } } }); panels.forEach(function (panel, i) { if (i === 0) return; pinTl.to(panels[i - 1], { opacity: 0, duration: 0.3 }, i - 0.5); pinTl.fromTo(panel, { opacity: 0 }, { opacity: 1, duration: 0.3 }, i - 0.2 ); }); }, /* Short viewports (landscape phones): static stack */ '(max-height: 479px)': function () { panels.forEach(function (panel) { gsap.set(panel, { opacity: 1, position: 'relative' }); }); updateWayfinding(0); } }); } } /* ========================================================== TIER 4B: IMAGE CLIP REVEALS Target: .gsap-clip-reveal (auto-classified) Effect: clip-path wipe from left to right ========================================================== */ gsap.utils.toArray('.gsap-clip-reveal').forEach(function (image) { /* Past-viewport guard (delay-JS pattern). Images past 'top 75%' get the revealed clip-path immediately. */ if (image.getBoundingClientRect().top 0.5 baked into computed transform, force-clear via direct inline el.style.transform='none' + opacity='1'. NO gsap.set with clearProps — clearing inline transform causes the CSS rule .gsap-reveal { transform:translateY(40px) } to re-assert, snapping the element back to 40px. ========================================================== */ var sweepCount = 0; var sweepInterval = setInterval(function () { document.querySelectorAll('.gsap-reveal').forEach(function (el) { var rect = el.getBoundingClientRect(); var wasInView = rect.top 0; if (wasInView) el.dataset.gsapSeen = '1'; var everSeen = el.dataset.gsapSeen === '1'; var t = getComputedStyle(el).transform; if (!t || t === 'none') return; var match = t.match(/matrix\(([^)]+)\)/); if (!match) return; var ty = parseFloat(match[1].split(',')[5]); if (Math.abs(ty) = 5) clearInterval(sweepInterval); }, 2000); })();