(function () {
"use strict";
var VERSION = 70;
if (window.__homeImmersiveVersion === VERSION) return;
window.__homeImmersiveVersion = VERSION;
var CFG = window.HOME_IMMERSIVE_CONFIG || {};
var DESKTOP_MQ = CFG.desktopMq || "(min-width: 992px) and (hover: hover) and (pointer: fine)";
var WIDTH_MQ = CFG.widthMq || "(min-width: 992px)";
var SCOPE = CFG.scope || ".hero";
var CMS_SCOPE = CFG.cmsScope || ".features_hero";
var MODE_STORAGE_KEY = CFG.modeStorageKey || "home-hero-view-mode";
var MODE_CAROUSEL = "carousel";
var MODE_FULLSCREEN = "fullscreen";
var THREE_CDN = CFG.threeCdn || "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js";
var GSAP_CDN = CFG.gsapCdn || "https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js";
var TILE_GAP = CFG.tileGap != null ? CFG.tileGap : 0.1;
var TILE_HEIGHT = CFG.tileHeight != null ? CFG.tileHeight : 2.25;
var TILE_WIDTH = CFG.tileWidth != null ? CFG.tileWidth : TILE_HEIGHT * (16 / 9);
var TILE_UNIT = TILE_WIDTH + TILE_GAP;
var CAM_FOV = CFG.cameraFov != null ? CFG.cameraFov : 50;
var CAM_Z = CFG.cameraZ != null ? CFG.cameraZ : 8;
var SCROLL_LERP = CFG.scrollLerp != null ? CFG.scrollLerp : 0.12;
var DRAG_SENS = CFG.dragSens != null ? CFG.dragSens : 0.012;
var INTRO_DUR = CFG.introDur != null ? CFG.introDur : 2.0;
var FEATURES_HIDE_DUR = CFG.featuresHideDur != null ? CFG.featuresHideDur : INTRO_DUR * 0.5;
var FEATURES_REVEAL_DUR = CFG.featuresRevealDur != null ? CFG.featuresRevealDur : INTRO_DUR * 0.52;
var SIDES_REVEAL_DUR = CFG.sidesRevealDur != null ? CFG.sidesRevealDur : INTRO_DUR * 0.38;
var ENTER_MORPH_END = CFG.enterMorphEnd != null ? CFG.enterMorphEnd : 0.78;
var ENTER_SIDES_START = CFG.enterSidesStart != null ? CFG.enterSidesStart : 0.12;
var ENTER_SIDES_END = CFG.enterSidesEnd != null ? CFG.enterSidesEnd : 0.62;
var ENTER_SPREAD_START = CFG.enterSpreadStart != null ? CFG.enterSpreadStart : 0.4;
var ENTER_HANDOFF_VIDEO_AT = CFG.enterHandoffVideoAt != null ? CFG.enterHandoffVideoAt : 0.58;
var EXIT_SIDES_END = CFG.exitSidesEnd != null ? CFG.exitSidesEnd : 0.48;
var EXIT_HANDOFF_AT = CFG.exitHandoffAt != null ? CFG.exitHandoffAt : 0.38;
var EXIT_MORPH_START = CFG.exitMorphStart != null ? CFG.exitMorphStart : 0.36;
var EXIT_SIDES_FADE_END = CFG.exitSidesFadeEnd != null ? CFG.exitSidesFadeEnd : 0.62;
var BG_COLOR = CFG.bgColor != null ? CFG.bgColor : 0x050505;
var DRAG_LERP = CFG.dragLerp != null ? CFG.dragLerp : 0.1;
var WARP_LERP = CFG.warpLerp != null ? CFG.warpLerp : 0.07;
var WARP_VEL_LERP = CFG.warpVelLerp != null ? CFG.warpVelLerp : 0.16;
var HOLD_MS = CFG.holdMs != null ? CFG.holdMs : 650;
var HOLD_MOVE_PX = CFG.holdMovePx != null ? CFG.holdMovePx : 14;
var HOLD_RING_LEN = 175.929;
var CAPTION_POS_LERP = CFG.captionPosLerp != null ? CFG.captionPosLerp : 0.16;
var CAPTION_POS_LERP_DRAG = CFG.captionPosLerpDrag != null ? CFG.captionPosLerpDrag : 0.28;
var CAPTION_POS_LERP_INTRO = CFG.captionPosLerpIntro != null ? CFG.captionPosLerpIntro : 0.24;
var CAPTION_TEXT_LERP = CFG.captionTextLerp != null ? CFG.captionTextLerp : 0.16;
var CAPTION_TEXT_TRAVEL = CFG.captionTextTravel != null ? CFG.captionTextTravel : 14;
var TILE_VERT = [
"uniform float uWarpIntensity;",
"uniform float uViewportWidth;",
"uniform float uTileHalfH;",
"varying vec2 vUv;",
"void main() {",
" vUv = uv;",
" vec4 worldPos = modelMatrix * vec4(position, 1.0);",
" float screenX = worldPos.x / (uViewportWidth * 0.5);",
" float screenXClamped = clamp(screenX, -1.0, 1.0);",
" float rollCurve = exp(-screenXClamped * screenXClamped * 6.0);",
" float edgeCurve = screenXClamped * screenXClamped;",
" float edgeBend = edgeCurve * uWarpIntensity * -0.4;",
" float zRoll = rollCurve * uWarpIntensity * 0.8 + edgeBend;",
" float scaleY = 1.0 + rollCurve * uWarpIntensity * 0.15;",
" float yNorm = position.y / max(uTileHalfH, 0.001);",
" float bowCurve = 1.0 - yNorm * yNorm;",
" float bow = bowCurve * rollCurve * uWarpIntensity * 0.15;",
" vec3 p = position;",
" p.y *= scaleY;",
" p.x += bow;",
" p.z += zRoll;",
" gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);",
"}",
].join("\n");
var TILE_FRAG = [
"uniform sampler2D uVideo;",
"uniform vec2 uCoverScale;",
"uniform vec2 uCoverOffset;",
"uniform float uGray;",
"uniform float uIntroZoom;",
"uniform float uOpacity;",
"uniform float uBright;",
"varying vec2 vUv;",
"void main() {",
" vec2 centeredUv = vUv - 0.5;",
" centeredUv /= max(uIntroZoom, 0.001);",
" centeredUv += 0.5;",
" vec2 uv = (centeredUv - 0.5) * uCoverScale + 0.5 + uCoverOffset;",
" vec4 col = texture2D(uVideo, uv);",
" float luma = dot(col.rgb, vec3(0.299, 0.587, 0.114));",
" col.rgb = mix(col.rgb, vec3(luma), clamp(uGray, 0.0, 1.0));",
" col.rgb *= uBright;",
" col.a *= clamp(uOpacity, 0.0, 1.0);",
" gl_FragColor = col;",
"}",
].join("\n");
var desktopMq = window.matchMedia(DESKTOP_MQ);
var widthMq = window.matchMedia(WIDTH_MQ);
var reduceMq = window.matchMedia("(prefers-reduced-motion: reduce)");
function isHomePage() {
var path = (window.location.pathname || "/").replace(/\/+$/, "") || "/";
return path === "/" || path === "/index.html";
}
function canShowHoldUi() {
if (CFG.enabled === false) return false;
if (!isHomePage()) return false;
if (!widthMq.matches) return false;
return true;
}
function canRunImmersive() {
return canShowHoldUi();
}
function readSavedMode() {
try {
var mode = localStorage.getItem(MODE_STORAGE_KEY);
return mode === MODE_CAROUSEL ? MODE_CAROUSEL : MODE_FULLSCREEN;
} catch (err) {
return MODE_FULLSCREEN;
}
}
function saveMode(mode) {
try {
localStorage.setItem(MODE_STORAGE_KEY, mode === MODE_CAROUSEL ? MODE_CAROUSEL : MODE_FULLSCREEN);
} catch (err) {}
}
function canRestoreCarouselMode() {
if (!canRunImmersive()) return false;
if (reduceMq.matches) return false;
return readSavedMode() === MODE_CAROUSEL;
}
function applyCarouselRestorePending() {
if (!canRestoreCarouselMode()) return;
document.documentElement.classList.add("is-home-carousel-restore");
document.documentElement.style.setProperty("--hero-list-reveal", "0");
document.documentElement.style.setProperty("--hero-overlay-reveal", "0");
}
function clearCarouselRestorePending() {
document.documentElement.classList.remove("is-home-carousel-restore");
}
function updatePageModeFlags() {
if (canShowHoldUi()) {
document.documentElement.classList.remove("home-immersive-off");
} else {
document.documentElement.classList.add("home-immersive-off");
}
}
function injectHoldUiStyles() {
if (document.getElementById("home-immersive-hold-inline")) return;
var style = document.createElement("style");
style.id = "home-immersive-hold-inline";
style.textContent =
".home-mode-hold{--hold-p:0;--glyph-morph:0;position:fixed!important;right:0.75rem!important;bottom:0.75rem!important;z-index:10050!important;display:inline-flex!important;flex-direction:row;align-items:center;gap:0.75rem;padding:0;margin:0;border:0;background:transparent;cursor:pointer;visibility:visible!important;opacity:1!important;pointer-events:auto}" +
".home-mode-hold.is-busy{pointer-events:none}" +
'.home-mode-hold-label{font:400 11px/1.2 "Fragment Mono","SFMono-Regular",ui-monospace,monospace;letter-spacing:0.04em;text-transform:uppercase;color:rgba(255,255,255,0.92);white-space:nowrap;user-select:none;text-shadow:0 1px 8px rgba(0,0,0,0.55)}' +
".home-mode-hold-dot{position:relative;flex:0 0 auto;width:56px;height:56px}" +
".home-mode-hold-ring{display:block;width:56px;height:56px;transform:rotate(-90deg)}" +
".home-mode-hold-ring circle{fill:none;stroke:rgba(255,255,255,0.28);stroke-width:1}" +
".home-mode-hold-ring .home-mode-hold-progress{stroke:rgba(255,255,255,0.92);stroke-width:1;stroke-linecap:round;stroke-dasharray:175.929;stroke-dashoffset:175.929}" +
".home-mode-hold-glyphs{--m:var(--glyph-morph,0);--spread:11px;--tight:calc(var(--spread)*(1 - var(--m)*0.52));position:absolute;left:50%;top:50%;width:22px;height:22px;margin:-11px 0 0 -11px;transform:rotate(calc(var(--m)*90deg));pointer-events:none}" +
".home-mode-hold-glyph{position:absolute;left:50%;top:50%;width:4px;height:4px;margin:-2px 0 0 -2px;border-radius:50%;background:rgba(255,255,255,0.9)}" +
".home-mode-hold-glyph--a{transform:translate(calc(-1*var(--tight)),0)}" +
".home-mode-hold-glyph--b{opacity:calc(1 - var(--m));transform:translate(0,0)}" +
".home-mode-hold-glyph--c{transform:translate(var(--tight),0)}" +
"@media(max-width:991px){.home-mode-hold{display:none!important}}";
(document.head || document.documentElement).appendChild(style);
}
updatePageModeFlags();
applyCarouselRestorePending();
function norm(s) {
return (s || "").replace(/\s+/g, " ").trim().toLowerCase();
}
function slugFromName(name) {
return norm(name)
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
function getLineName(el) {
var n = el && el.querySelector(".name");
return norm(n ? n.textContent : el ? el.textContent : "");
}
function getItemName(item) {
var n = item && item.querySelector(".name");
if (n) return norm(n.textContent);
var videos = item ? item.querySelectorAll("video") : [];
for (var i = 0; i < videos.length; i++) {
var label = norm(videos[i].textContent);
if (label) return label;
}
return "";
}
function getVideo(item) {
if (!item) return null;
var desktop = item.querySelector("video.desktop");
var mobile = item.querySelector("video.mobile");
if (desktop && mobile) {
return getComputedStyle(desktop).display !== "none" ? desktop : mobile;
}
return item.querySelector("video");
}
function playVideoEl(video) {
if (!video) return;
video.muted = true;
video.playsInline = true;
if (!video.paused && video.readyState >= 2) return;
if (typeof window.playPortfolioHeroVideo === "function" && video.closest(".cms_background .cms_item")) {
window.playPortfolioHeroVideo(video);
return;
}
if (typeof window.startPortfolioVideo === "function") {
window.startPortfolioVideo(video);
return;
}
video.play().catch(function () {});
}
function pauseVideoEl(video) {
if (video && !video.paused) video.pause();
}
function buildProjects() {
if (window.__portfolioPairs && window.__portfolioPairs.length) {
return window.__portfolioPairs
.map(function (pair, index) {
return projectFromPair(pair, index);
})
.filter(Boolean);
}
var scope = document.querySelector(SCOPE) || document.querySelector(CMS_SCOPE) || document;
var lineEls = Array.prototype.slice.call(
scope.querySelectorAll(".list_projects .line_features, .cms_features .line_features, .line_features"),
);
var items = Array.prototype.slice.call(document.querySelectorAll(".background_cases .cms_background .cms_item"));
var map = {};
var pairs = [];
items.forEach(function (item) {
var name = getItemName(item);
if (name) map[name] = item;
});
lineEls.forEach(function (line) {
var row = line.closest(".cms_line") || line;
var item = map[getLineName(line)];
if (!item) return;
pairs.push({ line: line, item: item, row: row });
});
if (!pairs.length) {
var count = Math.min(lineEls.length, items.length);
for (var i = 0; i < count; i++) {
var ln = lineEls[i];
pairs.push({ line: ln, item: items[i], row: ln.closest(".cms_line") || ln });
}
}
return pairs
.map(function (pair, index) {
return projectFromPair(pair, index);
})
.filter(Boolean);
}
function queryProjectMeta(row, line, selectors) {
var scopes = [];
if (row) scopes.push(row);
if (line && line !== row) scopes.push(line);
for (var si = 0; si < scopes.length; si++) {
var scope = scopes[si];
for (var ai = 0; ai < selectors.length; ai++) {
var sel = selectors[ai];
var el =
scope.querySelector(sel) ||
scope.querySelector(".lf-layer " + sel) ||
scope.querySelector(".lf-layer--white " + sel);
if (el) return el;
}
}
return null;
}
function projectFromPair(pair, index) {
var row = pair.row || pair.line;
var line = pair.line || row;
var item = pair.item;
if (!row || !item) return null;
var nameEl = queryProjectMeta(row, line, [".name"]);
var industryEl = queryProjectMeta(row, line, [".industry", ".description", ".tag", ".category"]);
var yearEl = queryProjectMeta(row, line, [".year"]);
var name = nameEl ? nameEl.textContent.replace(/\s+/g, " ").trim() : "";
var industry = industryEl ? industryEl.textContent.replace(/\s+/g, " ").trim() : "";
var year = yearEl ? yearEl.textContent.replace(/\s+/g, " ").trim() : "";
var slug = slugFromName(name);
return {
index: index,
name: name,
industry: industry,
year: year,
slug: slug,
href: slug ? "/work/" + slug : "",
item: item,
row: row,
line: line,
video: getVideo(item),
};
}
function getActiveProjectIndex(projects) {
var active = document.querySelector(".background_cases .cms_item.is-active");
if (!active) return 0;
for (var i = 0; i < projects.length; i++) {
if (projects[i].item === active) return i;
}
return 0;
}
function isBlockedTarget(el) {
if (!el || !el.closest) return true;
return !!el.closest(
"a, button, input, textarea, select, .fixed_menu, .bottom_fixed, .menu_expanded, .expand_btn_primary, .home-mode-hold",
);
}
// jsDelivr is a single point of failure for GSAP / three.js; fall back to unpkg, share one load per page.
function loadScriptWithFallback(urls, isReady) {
return new Promise(function (resolve, reject) {
var i = 0;
(function next() {
if (isReady()) return resolve();
if (i >= urls.length) return reject(new Error("script failed: " + urls[0]));
var s = document.createElement("script");
s.src = urls[i++];
s.async = true;
s.onload = function () {
if (isReady()) resolve();
else next();
};
s.onerror = function () {
s.remove();
next();
};
document.head.appendChild(s);
})();
});
}
function fallbackUrl(url) {
return url.replace("https://cdn.jsdelivr.net/npm/", "https://unpkg.com/");
}
function sharedGsap(url) {
if (window.gsap) return Promise.resolve(window.gsap);
if (!window.__siteGsapPromise) {
window.__siteGsapPromise = loadScriptWithFallback([url, fallbackUrl(url)], function () {
return !!window.gsap;
});
window.__siteGsapPromise.catch(function () {
window.__siteGsapPromise = null;
});
}
return window.__siteGsapPromise.then(function () {
return window.gsap;
});
}
function loadThree() {
if (typeof THREE !== "undefined") return Promise.resolve();
if (loadThree.promise) return loadThree.promise;
loadThree.promise = loadScriptWithFallback([THREE_CDN, fallbackUrl(THREE_CDN)], function () {
return typeof THREE !== "undefined";
});
loadThree.promise.catch(function () {
loadThree.promise = null;
});
return loadThree.promise;
}
function loadGsap() {
if (typeof window.gsap !== "undefined") return Promise.resolve(window.gsap);
if (loadGsap.promise) return loadGsap.promise;
loadGsap.promise = sharedGsap(GSAP_CDN);
loadGsap.promise.catch(function () {
loadGsap.promise = null;
});
return loadGsap.promise;
}
var projects = [];
var scopeEl = null;
var immersiveRoot = null;
var modeHoldEl = null;
var holdProgressEl = null;
var metaNameEl = null;
var metaYearEl = null;
var metaSubEl = null;
var metaCaptionEl = null;
var metaNameLayers = null;
var metaYearLayers = null;
var metaSubLayers = null;
var featuresHeroEl = null;
var captionVecA = null;
var captionVecB = null;
var captionVecC = null;
var hold = { active: false, startedAt: 0, originX: 0, originY: 0, raf: 0, glyphLocked: null, introGlyphDone: false };
var modeHoldBound = false;
var immersiveBooted = false;
var immersive = {
active: false,
entering: false,
exiting: false,
renderer: null,
scene: null,
camera: null,
track: null,
tiles: [],
tileW: TILE_WIDTH,
tileH: TILE_HEIGHT,
viewportWidth: 20,
raf: 0,
selectedIndex: 0,
lastSyncedCmsIndex: -1,
playbackIndex: -1,
videoTextures: null,
scrollX: 0,
targetScrollX: 0,
lastScrollX: 0,
scrollVel: 0,
warpIntensity: 0,
dragging: false,
dragX: 0,
dragScrollStart: 0,
dragMoved: false,
dragVel: 0,
dragLastX: 0,
dragLastT: 0,
intro: null,
skipEnterIntro: false,
restoreEnter: false,
introZoom: 1,
shellProgress: 0,
heroRect: null,
loopWidth: 0,
eventsBound: false,
casesEl: null,
casesMorphOrigin: null,
casesFromRect: null,
casesToRect: null,
casesHandoffDone: false,
casesExitHandoff: false,
casesExitTileRect: null,
casesPin: null,
casesHandoffFinalized: false,
handoffVideoSynced: false,
domFilterVideo: null,
domFilterSaved: "",
enterMorphLocked: false,
enterMorphFrom: null,
enterMorphTo: null,
enterMorphAspect: 0,
featuresTween: null,
featuresRevealStarted: false,
featuresPin: null,
caption: {
left: 50,
top: 85,
width: 40,
textLayer: 0,
textBlend: 1,
textIndex: -1,
swapFrom: null,
swapTo: null,
swapping: false,
swapFreezeLeft: 50,
swapFreezeTop: 85,
swapFreezeWidth: 40,
anchorLeft: 50,
anchorTop: 85,
anchorWidth: 40,
layoutDirty: true,
captionTween: null,
enterRevealed: false,
gapPx: 11,
},
};
function getFeaturesHeroEl() {
if (featuresHeroEl && featuresHeroEl.isConnected) return featuresHeroEl;
featuresHeroEl = document.querySelector(CMS_SCOPE);
return featuresHeroEl;
}
function captureFeaturesHeroRect() {
var el = getFeaturesHeroEl();
if (!el) return null;
var r = el.getBoundingClientRect();
if (r.width < 1 || r.height < 1) return null;
return { left: r.left, top: r.top, width: r.width, height: r.height };
}
function applyFeaturesPinnedLayout(rect) {
var el = getFeaturesHeroEl();
if (!el || !rect) return;
el.style.position = "fixed";
el.style.left = rect.left + "px";
el.style.top = rect.top + "px";
el.style.width = rect.width + "px";
el.style.height = rect.height + "px";
el.style.right = "auto";
el.style.bottom = "auto";
el.style.margin = "0";
el.style.boxSizing = "border-box";
el.style.zIndex = "170";
el.classList.add("is-home-features-pinned");
document.documentElement.classList.add("is-home-features-pinned");
}
function pinFeaturesHeroForTransition() {
var el = getFeaturesHeroEl();
if (!el) return false;
if (el.classList.contains("is-home-features-pinned") && el.parentNode === document.body) {
if (immersive.featuresPin && immersive.featuresPin.rect) {
applyFeaturesPinnedLayout(immersive.featuresPin.rect);
}
return true;
}
var rect = captureFeaturesHeroRect();
if (!rect) return false;
if (!immersive.featuresPin) {
immersive.featuresPin = { placeholder: document.createComment("home-features-pin"), parent: null, rect: null };
}
var pin = immersive.featuresPin;
pin.parent = el.parentNode;
pin.parent.insertBefore(pin.placeholder, el);
pin.rect = rect;
document.body.appendChild(el);
applyFeaturesPinnedLayout(rect);
return true;
}
function restoreFeaturesHeroFromPin() {
var el = getFeaturesHeroEl();
if (!el || !immersive.featuresPin || !immersive.featuresPin.parent) return;
if (el.parentNode !== document.body) return;
var pin = immersive.featuresPin;
pin.parent.insertBefore(el, pin.placeholder);
pin.placeholder.remove();
pin.parent = null;
pin.rect = null;
el.classList.remove("is-home-features-pinned");
document.documentElement.classList.remove("is-home-features-pinned");
el.style.removeProperty("position");
el.style.removeProperty("left");
el.style.removeProperty("top");
el.style.removeProperty("width");
el.style.removeProperty("height");
el.style.removeProperty("right");
el.style.removeProperty("bottom");
el.style.removeProperty("margin");
el.style.removeProperty("box-sizing");
el.style.removeProperty("z-index");
}
function killFeaturesTween() {
if (!immersive.featuresTween) return;
if (typeof window.gsap !== "undefined" && immersive.featuresTween.kill) {
immersive.featuresTween.kill();
}
immersive.featuresTween = null;
}
function setFeaturesHeroHidden(instant) {
var el = getFeaturesHeroEl();
if (!el) return;
document.documentElement.classList.add("is-home-features-hidden");
if (immersive.featuresTween && !instant) return;
killFeaturesTween();
if (typeof window.gsap !== "undefined") {
window.gsap.killTweensOf(el);
window.gsap.set(el, { autoAlpha: 0, y: instant ? 12 : 0, pointerEvents: "none" });
return;
}
el.style.opacity = "0";
el.style.visibility = "hidden";
el.style.pointerEvents = "none";
}
function resetFeaturesHeroVisible() {
var el = getFeaturesHeroEl();
if (!el) return;
killFeaturesTween();
document.documentElement.classList.remove("is-home-features-hidden");
if (typeof window.gsap !== "undefined") {
window.gsap.killTweensOf(el);
window.gsap.set(el, { clearProps: "transform,opacity,visibility,pointerEvents" });
return;
}
el.style.removeProperty("opacity");
el.style.removeProperty("transform");
el.style.removeProperty("visibility");
el.style.removeProperty("pointer-events");
}
function animateFeaturesHeroHide(introStartTime) {
var el = getFeaturesHeroEl();
if (!el) return Promise.resolve();
document.documentElement.classList.add("is-home-features-hidden");
immersive.featuresRevealStarted = false;
return loadGsap()
.then(function (gsap) {
killFeaturesTween();
gsap.killTweensOf(el);
var elapsed = introStartTime ? (performance.now() - introStartTime) / 1000 : 0;
var duration = Math.max(0.15, FEATURES_HIDE_DUR - elapsed);
immersive.featuresTween = gsap.to(el, {
autoAlpha: 0,
duration: duration,
ease: "power2.inOut",
overwrite: "auto",
onComplete: function () {
gsap.set(el, { pointerEvents: "none" });
immersive.featuresTween = null;
},
});
})
.catch(function () {
el.style.opacity = "0";
el.style.visibility = "hidden";
el.style.pointerEvents = "none";
});
}
function animateFeaturesHeroReveal() {
var el = getFeaturesHeroEl();
if (!el || immersive.featuresRevealStarted) return Promise.resolve();
immersive.featuresRevealStarted = true;
return loadGsap()
.then(function (gsap) {
killFeaturesTween();
gsap.killTweensOf(el);
immersive.featuresTween = gsap.fromTo(
el,
{ autoAlpha: 0 },
{
autoAlpha: 1,
duration: FEATURES_REVEAL_DUR,
ease: "power2.out",
overwrite: "auto",
onComplete: function () {
gsap.set(el, { clearProps: "transform,opacity,visibility,pointerEvents" });
document.documentElement.classList.remove("is-home-features-hidden");
immersive.featuresTween = null;
},
},
);
})
.catch(function () {
el.style.opacity = "1";
el.style.visibility = "visible";
el.style.transform = "";
el.style.pointerEvents = "";
document.documentElement.classList.remove("is-home-features-hidden");
});
}
function makeCubicBezier(x1, y1, x2, y2) {
var ax = 3 * x1 - 3 * x2 + 1;
var bx = 3 * x2 - 6 * x1;
var cx = 3 * x1;
var ay = 3 * y1 - 3 * y2 + 1;
var by = 3 * y2 - 6 * y1;
var cy = 3 * y1;
function sampleCurveX(t) {
return ((ax * t + bx) * t + cx) * t;
}
function sampleCurveY(t) {
return ((ay * t + by) * t + cy) * t;
}
function sampleCurveDX(t) {
return (3 * ax * t + 2 * bx) * t + cx;
}
function solveCurveX(x) {
var t2 = x;
var i;
for (i = 0; i < 8; i++) {
var dx = sampleCurveX(t2) - x;
if (Math.abs(dx) < 1e-7) return t2;
var d = sampleCurveDX(t2);
if (Math.abs(d) < 1e-6) break;
t2 -= dx / d;
}
var t0 = 0;
var t1 = 1;
t2 = x;
while (t0 < t1) {
var sx = sampleCurveX(t2);
if (Math.abs(sx - x) < 1e-7) return t2;
if (x > sx) t0 = t2;
else t1 = t2;
t2 = (t0 + t1) * 0.5;
}
return t2;
}
return function (t) {
if (t <= 0) return 0;
if (t >= 1) return 1;
return sampleCurveY(solveCurveX(t));
};
}
var EASE_LOCO = makeCubicBezier(0.22, 1, 0.36, 1);
function smoothRange(t, start, end) {
return EASE_LOCO(clamp((t - start) / Math.max(0.0001, end - start), 0, 1));
}
function applyFeaturesHeroEnterProgress(rawP) {
var el = getFeaturesHeroEl();
if (!el) return;
killFeaturesTween();
pinFeaturesHeroForTransition();
var hideP = immersive.restoreEnter ? 1 : smoothRange(rawP, 0, 0.34);
var op = 1 - hideP;
el.style.opacity = String(op);
el.style.visibility = op < 0.02 ? "hidden" : "visible";
el.style.transform = "";
el.style.pointerEvents = "none";
if (hideP > 0.02) {
document.documentElement.classList.add("is-home-features-hidden");
}
}
function applyFeaturesHeroExitProgress(rawP) {
var el = getFeaturesHeroEl();
if (!el) return;
killFeaturesTween();
pinFeaturesHeroForTransition();
var revealP = smoothRange(rawP, 0.48, 0.98);
el.style.opacity = String(revealP);
el.style.visibility = revealP < 0.02 ? "hidden" : "visible";
el.style.transform = "";
el.style.pointerEvents = revealP > 0.55 ? "auto" : "none";
if (revealP > 0.98) {
document.documentElement.classList.remove("is-home-features-hidden");
}
}
function clearFeaturesHeroInlineStyles() {
restoreFeaturesHeroFromPin();
var el = getFeaturesHeroEl();
if (!el) return;
el.style.removeProperty("opacity");
el.style.removeProperty("visibility");
el.style.removeProperty("transform");
el.style.removeProperty("pointer-events");
document.documentElement.classList.remove("is-home-features-hidden");
}
function clamp(v, lo, hi) {
return Math.max(lo, Math.min(hi, v));
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function cubicEaseInOut(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
function visibleWorldHeight(fovDeg, z) {
return 2 * Math.tan((fovDeg * Math.PI) / 180 / 2) * z;
}
function updateHoldGlyphMorph(holdP) {
if (!modeHoldEl) return;
if (hold.glyphLocked != null && !hold.active) {
modeHoldEl.style.setProperty("--glyph-morph", String(hold.glyphLocked));
return;
}
var carousel = !!immersive.active;
var morph;
if (hold.active && holdP != null) {
morph = carousel ? 1 - holdP : holdP;
} else {
morph = carousel ? 1 : 0;
}
modeHoldEl.style.setProperty("--glyph-morph", String(clamp(morph, 0, 1)));
}
function updateHoldGlyphFromIntro(rawP, closing) {
if (!modeHoldEl || hold.active) return;
if (hold.glyphLocked != null) {
modeHoldEl.style.setProperty("--glyph-morph", String(hold.glyphLocked));
return;
}
if (hold.introGlyphDone) return;
if (immersive.restoreEnter && !closing) {
modeHoldEl.style.setProperty("--glyph-morph", "1");
return;
}
var morph = closing ? 1 - rawP : rawP;
modeHoldEl.style.setProperty("--glyph-morph", String(clamp(morph, 0, 1)));
}
function updateModeHoldState() {
if (!modeHoldEl) return;
var busy = immersive.entering || immersive.exiting || (immersive.intro && immersive.intro.active);
modeHoldEl.classList.toggle("is-busy", !!busy);
modeHoldEl.classList.toggle("is-carousel-mode", !!immersive.active);
updateHoldGlyphMorph(null);
}
function ensureModeHoldUi() {
if (!canShowHoldUi()) return null;
injectHoldUiStyles();
if (
modeHoldEl &&
(!modeHoldEl.querySelector(".home-mode-hold-glyphs") || !modeHoldEl.querySelector(".home-mode-hold-label"))
) {
if (modeHoldEl.parentNode) modeHoldEl.parentNode.removeChild(modeHoldEl);
modeHoldEl = null;
holdProgressEl = null;
}
if (modeHoldEl) return modeHoldEl;
modeHoldEl = document.createElement("button");
modeHoldEl.type = "button";
modeHoldEl.className = "home-mode-hold";
modeHoldEl.setAttribute("aria-label", "Click and hold to switch view mode");
modeHoldEl.innerHTML =
'Click and Hold' +
'' +
'" +
'' +
'' +
'' +
'' +
"" +
"";
holdProgressEl = modeHoldEl.querySelector(".home-mode-hold-progress");
var mount = document.body || document.documentElement;
if (mount && !modeHoldEl.parentNode) mount.appendChild(modeHoldEl);
bindModeHold();
updateModeHoldState();
return modeHoldEl;
}
function setHoldProgress(p) {
if (!holdProgressEl) return;
var clamped = clamp(p, 0, 1);
holdProgressEl.style.strokeDashoffset = String(HOLD_RING_LEN * (1 - clamped));
modeHoldEl.style.setProperty("--hold-p", String(clamped));
updateHoldGlyphMorph(clamped);
}
function cancelHold() {
if (!hold.active) return;
hold.active = false;
hold.startedAt = 0;
hold.glyphLocked = null;
if (hold.raf) {
cancelAnimationFrame(hold.raf);
hold.raf = 0;
}
if (modeHoldEl) modeHoldEl.classList.remove("is-holding");
setHoldProgress(0);
}
function finishHoldSwitch() {
var targetMorph = immersive.active ? 0 : 1;
hold.glyphLocked = targetMorph;
hold.introGlyphDone = true;
hold.active = false;
hold.startedAt = 0;
if (hold.raf) {
cancelAnimationFrame(hold.raf);
hold.raf = 0;
}
if (modeHoldEl) {
modeHoldEl.classList.remove("is-holding");
modeHoldEl.style.setProperty("--glyph-morph", String(targetMorph));
}
if (holdProgressEl) holdProgressEl.style.strokeDashoffset = String(HOLD_RING_LEN);
if (modeHoldEl) modeHoldEl.style.setProperty("--hold-p", "0");
}
function completeHoldSwitch() {
finishHoldSwitch();
if (immersive.active) {
saveMode(MODE_FULLSCREEN);
exitImmersive();
return;
}
saveMode(MODE_CAROUSEL);
enterImmersive();
}
function tickHold() {
if (!hold.active) {
hold.raf = 0;
return;
}
var p = (performance.now() - hold.startedAt) / HOLD_MS;
setHoldProgress(p);
if (p >= 1) {
hold.active = false;
hold.raf = 0;
completeHoldSwitch();
return;
}
hold.raf = requestAnimationFrame(tickHold);
}
function startHold(e) {
if (immersive.entering || immersive.exiting) return;
if (immersive.intro && immersive.intro.active) return;
ensureModeHoldUi();
hold.active = true;
hold.startedAt = performance.now();
hold.originX = e.clientX;
hold.originY = e.clientY;
modeHoldEl.classList.add("is-holding");
if (!hold.raf) hold.raf = requestAnimationFrame(tickHold);
if (!immersive.active && !sceneCoreMatchesProjects()) {
requestAnimationFrame(function () {
if (!hold.active) return;
var t = performance.now();
warmImmersiveScene(true);
// The ring pauses for the build instead of jumping ahead.
if (hold.active) hold.startedAt += performance.now() - t;
});
}
}
function onModeHoldMove(e) {
if (!hold.active) return;
var dx = e.clientX - hold.originX;
var dy = e.clientY - hold.originY;
if (dx * dx + dy * dy > HOLD_MOVE_PX * HOLD_MOVE_PX) cancelHold();
}
function bindModeHold() {
if (modeHoldBound || !modeHoldEl) return;
modeHoldBound = true;
modeHoldEl.addEventListener("pointerdown", function (e) {
e.preventDefault();
startHold(e);
});
modeHoldEl.addEventListener("pointerenter", function (e) {
if (e.pointerType === "mouse" && !immersive.active) warmImmersiveScene();
});
modeHoldEl.addEventListener("pointermove", onModeHoldMove);
modeHoldEl.addEventListener("pointerup", cancelHold);
modeHoldEl.addEventListener("pointercancel", cancelHold);
modeHoldEl.addEventListener("pointerleave", cancelHold);
}
function scrollForRawIndex(rawIndex) {
return (-rawIndex + (projects.length - 1) / 2) * TILE_UNIT;
}
function rawIndexFromScroll(scrollX) {
return Math.round(-scrollX / TILE_UNIT + (projects.length - 1) / 2);
}
function logicalIndexFromRaw(rawIndex) {
var count = projects.length;
if (!count) return 0;
return ((rawIndex % count) + count) % count;
}
function scrollBounds() {
var count = projects.length;
if (count <= 1) {
var s = count ? scrollForRawIndex(0) : 0;
return { min: s, max: s };
}
return { min: scrollForRawIndex(count - 1), max: scrollForRawIndex(0) };
}
function clampScrollValue(scrollX) {
var b = scrollBounds();
return clamp(scrollX, b.min, b.max);
}
function indexFromScroll(scrollX) {
var count = projects.length;
if (!count) return 0;
return clamp(rawIndexFromScroll(scrollX), 0, count - 1);
}
function setSelectedIndex(index) {
var count = projects.length;
if (!count) return;
var next = clamp(index, 0, count - 1);
if (next === immersive.selectedIndex && Math.abs(immersive.targetScrollX - scrollForRawIndex(next)) < 0.001) {
return;
}
immersive.selectedIndex = next;
immersive.targetScrollX = scrollForRawIndex(next);
syncCmsIfCarouselSettled();
}
function syncCmsIfCarouselSettled() {
if (!immersive.active || (immersive.intro && immersive.intro.active)) return;
if (!immersive.casesHandoffDone) return;
if (immersive.lastSyncedCmsIndex === immersive.selectedIndex) return;
immersive.lastSyncedCmsIndex = immersive.selectedIndex;
syncCmsToProject(projects[immersive.selectedIndex], { skipAutoplayUi: true });
}
function setHeroListReveal(p) {
document.documentElement.style.setProperty("--hero-list-reveal", String(clamp(p, 0, 1)));
}
function setHeroOverlayReveal(p) {
document.documentElement.style.setProperty("--hero-overlay-reveal", String(clamp(p, 0, 1)));
}
function setHeroUiReveal(listP, overlayP) {
setHeroListReveal(listP);
setHeroOverlayReveal(overlayP != null ? overlayP : listP);
}
function captureHeroRect() {
var hero = scopeEl || document.querySelector(SCOPE);
if (hero) {
var r = hero.getBoundingClientRect();
immersive.heroRect = { left: r.left, top: r.top, width: r.width, height: r.height };
return;
}
immersive.heroRect = { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
}
function captureCasesFromRect() {
var bg = document.querySelector(".background_cases");
if (!bg) {
captureHeroRect();
immersive.casesFromRect = immersive.heroRect;
return;
}
var r = bg.getBoundingClientRect();
immersive.casesFromRect = { left: r.left, top: r.top, width: r.width, height: r.height };
}
function copyRect(r) {
return { left: r.left, top: r.top, width: r.width, height: r.height };
}
function tileAspect() {
var w = immersive.tileW || TILE_WIDTH;
var h = immersive.tileH || TILE_HEIGHT;
return w / Math.max(h, 0.001);
}
function cropRectToAspect(rect, aspect) {
if (!rect || !aspect || aspect <= 0) return rect;
var w = rect.width;
var h = rect.height;
if (w <= 0 || h <= 0) return rect;
var rectAspect = w / h;
var outW;
var outH;
if (rectAspect > aspect) {
outH = h;
outW = h * aspect;
} else {
outW = w;
outH = w / aspect;
}
return { left: rect.left + (w - outW) * 0.5, top: rect.top + (h - outH) * 0.5, width: outW, height: outH };
}
function snapRectToAspect(rect, aspect) {
if (!rect || !aspect || aspect <= 0) return rect;
var cx = rect.left + rect.width * 0.5;
var cy = rect.top + rect.height * 0.5;
var w = Math.max(8, rect.width);
var h = w / aspect;
return { left: cx - w * 0.5, top: cy - h * 0.5, width: w, height: h };
}
function clearEnterMorphRects() {
immersive.enterMorphLocked = false;
immersive.enterMorphFrom = null;
immersive.enterMorphTo = null;
immersive.enterMorphAspect = 0;
}
function lockEnterMorphRects(activeIdx, count) {
if (immersive.enterMorphLocked) return;
prepareActiveTileForProjection(activeIdx, count);
var to = projectActiveTileScreenRect();
if (!to) {
prepareCasesTargetRect();
to = immersive.casesToRect || computeCarouselCenterTileRect();
}
if (!to) return;
var fromSource = immersive.casesMorphOrigin || immersive.casesFromRect;
if (!fromSource) return;
immersive.enterMorphFrom = copyRect(fromSource);
immersive.enterMorphTo = copyRect(to);
immersive.enterMorphAspect = to.width / Math.max(to.height, 1);
immersive.enterMorphLocked = true;
}
function morphEnterRects(activeIdx, count) {
if (activeIdx != null && count != null) {
lockEnterMorphRects(activeIdx, count);
}
if (immersive.enterMorphLocked && immersive.enterMorphFrom && immersive.enterMorphTo) {
return { from: immersive.enterMorphFrom, to: immersive.enterMorphTo };
}
var fromSource = immersive.casesMorphOrigin || immersive.casesFromRect;
var to = projectActiveTileScreenRect();
if (!to) {
prepareCasesTargetRect();
to = immersive.casesToRect || computeCarouselCenterTileRect();
}
if (!to) return { from: null, to: null };
var from = fromSource ? copyRect(fromSource) : null;
return { from: from, to: to };
}
function applyCasesMorphLayout(rect) {
var el = immersive.casesEl;
if (!el || !rect) return;
el.style.position = "fixed";
el.style.left = rect.left + "px";
el.style.top = rect.top + "px";
el.style.width = rect.width + "px";
el.style.height = rect.height + "px";
el.style.right = "auto";
el.style.bottom = "auto";
el.style.margin = "0";
el.style.zIndex = "160";
el.style.overflow = "hidden";
el.style.contain = "strict";
el.style.transformOrigin = "center center";
el.style.transform = "none";
el.style.visibility = "visible";
el.style.pointerEvents = "none";
}
function applyCasesMorphTransform(from, to, t) {
var el = immersive.casesEl;
if (!el || !from || !to) return;
if (t <= 0) {
applyCasesMorphLayout(from);
return;
}
if (t >= 1) {
applyCasesMorphLayout(to);
return;
}
applyCasesMorphLayout({
left: lerp(from.left, to.left, t),
top: lerp(from.top, to.top, t),
width: lerp(from.width, to.width, t),
height: lerp(from.height, to.height, t),
});
}
function applyCasesMorph(from, to, t) {
applyCasesMorphTransform(from, to, t);
}
function projectWorldTileRect(cx, cy, cz, tileW, tileH) {
if (!immersive.renderer || !immersive.camera || typeof THREE === "undefined") return null;
if (!captionVecA) {
captionVecA = new THREE.Vector3();
captionVecB = new THREE.Vector3();
captionVecC = new THREE.Vector3();
}
var pts = [
[cx - tileW / 2, cy + tileH / 2, cz],
[cx + tileW / 2, cy + tileH / 2, cz],
[cx - tileW / 2, cy - tileH / 2, cz],
[cx + tileW / 2, cy - tileH / 2, cz],
];
var canvas = immersive.renderer.domElement;
var cr = canvas.getBoundingClientRect();
if (!cr.width || !cr.height) {
cr = { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight };
}
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
pts.forEach(function (p) {
captionVecA.set(p[0], p[1], p[2]).project(immersive.camera);
var px = (captionVecA.x * 0.5 + 0.5) * cr.width + cr.left;
var py = (-captionVecA.y * 0.5 + 0.5) * cr.height + cr.top;
minX = Math.min(minX, px);
minY = Math.min(minY, py);
maxX = Math.max(maxX, px);
maxY = Math.max(maxY, py);
});
return { left: minX, top: minY, width: Math.max(8, maxX - minX), height: Math.max(8, maxY - minY) };
}
function computeCarouselCenterTileRect() {
return projectWorldTileRect(0, 0, 0, immersive.tileW, immersive.tileH);
}
function prepareCasesTargetRect() {
if (!immersive.track) return null;
var idx = immersive.selectedIndex;
var scrollTarget = scrollForRawIndex(idx);
immersive.track.position.x = scrollTarget;
immersive.scrollX = scrollTarget;
immersive.targetScrollX = scrollTarget;
updateCamera();
immersive.casesToRect = computeCarouselCenterTileRect();
return immersive.casesToRect;
}
function setCanvasMorphOpacity(alpha) {
if (!immersive.renderer || !immersive.renderer.domElement) return;
var a = clamp(alpha, 0, 1);
immersive.renderer.domElement.style.opacity = String(a);
immersive.renderer.domElement.style.visibility = a > 0.001 ? "visible" : "hidden";
}
function setCanvasDuringMorph(visible) {
setCanvasMorphOpacity(visible ? 1 : 0);
}
function setIntroTilesVisible(visible) {
immersive.tiles.forEach(function (tile) {
setTileOpacity(tile, visible ? 1 : 0);
});
}
function detachCasesForMorph(el) {
if (!el || el.parentNode === document.body) return;
if (!immersive.casesPin) {
immersive.casesPin = { placeholder: document.createComment("home-cases-pin"), parent: null };
}
var pin = immersive.casesPin;
pin.parent = el.parentNode;
pin.parent.insertBefore(pin.placeholder, el);
document.body.appendChild(el);
}
function restoreCasesFromMorph(el) {
if (!el || !immersive.casesPin || !immersive.casesPin.parent) return;
if (el.parentNode !== document.body) return;
var pin = immersive.casesPin;
pin.parent.insertBefore(el, pin.placeholder);
pin.placeholder.remove();
pin.parent = null;
}
function pinBackgroundCases(atRect) {
var el = document.querySelector(".background_cases");
if (!el) return false;
immersive.casesEl = el;
var r = atRect
? { left: atRect.left, top: atRect.top, width: atRect.width, height: atRect.height }
: (function () {
var box = el.getBoundingClientRect();
return { left: box.left, top: box.top, width: box.width, height: box.height };
})();
immersive.casesMorphOrigin = r;
if (!atRect) immersive.casesFromRect = r;
detachCasesForMorph(el);
document.documentElement.classList.add("is-home-cases-morph");
el.classList.add("is-home-cases-morphing");
el.classList.remove("is-home-cases-in-carousel");
applyCasesMorphLayout(r);
return true;
}
function markCasesEnterHandoff() {
if (immersive.casesHandoffDone) return;
immersive.casesHandoffDone = true;
syncHandoffVideo();
immersive.handoffVideoSynced = true;
document.documentElement.classList.add("is-home-carousel-view");
pauseNonSelectedCmsVideos();
}
function finalizeCasesHandoffDom() {
if (immersive.casesHandoffFinalized || !immersive.casesEl) return;
immersive.casesHandoffFinalized = true;
immersive.casesEl.classList.remove("is-home-cases-morphing");
immersive.casesEl.classList.add("is-home-cases-in-carousel");
immersive.casesEl.style.cssText = "";
parkCasesOnScreen(immersive.casesEl);
restoreDomVideoFilter();
restoreCasesFromMorph(immersive.casesEl);
document.documentElement.classList.remove("is-home-cases-morph");
setCanvasMorphOpacity(1);
}
// Chrome stops presenting frames of a