From d1645ef829f18d9bba6d0080826a30161ac2fe5c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 28 May 2026 07:12:13 +0000 Subject: [PATCH] Revert to version f3cee3c --- backend/src/auth/auth.js | 4 +- backend/src/services/file.js | 42 ++----- backend/src/services/tour_pages.js | 6 +- frontend/public/sw.js | 2 +- .../components/Constructor/CanvasElement.tsx | 63 +++++----- .../Constructor/CreatePageModal.tsx | 18 +-- .../Constructor/ElementEditorPanel.tsx | 24 ---- .../EffectsSettingsSection.tsx | 92 --------------- .../EffectsSettingsSectionCompact.tsx | 102 ---------------- .../src/components/ElementSettings/types.ts | 9 -- .../ElementSettings/useElementSettingsForm.ts | 62 ---------- frontend/src/components/RuntimeElement.tsx | 57 +++++---- .../src/components/RuntimePresentation.tsx | 10 +- frontend/src/components/TourFlowManager.tsx | 15 ++- .../elements/VideoPlayerElement.tsx | 6 +- frontend/src/hooks/useAppearAnimation.ts | 3 - frontend/src/hooks/useCSVHandling.ts | 3 +- frontend/src/hooks/useElementEffects.ts | 109 ++---------------- frontend/src/hooks/useNetworkAware.ts | 3 +- frontend/src/hooks/usePreloadOrchestrator.ts | 20 +--- frontend/src/hooks/useTransitionPlayback.ts | 18 +-- frontend/src/hooks/video/useVideoBlobUrl.ts | 3 +- .../src/hooks/video/useVideoBufferingState.ts | 26 +---- .../src/hooks/video/useVideoErrorRecovery.ts | 23 ++-- .../src/hooks/video/useVideoFirstFrame.ts | 34 ++---- .../src/hooks/video/useVideoPlaybackCore.ts | 12 +- frontend/src/hooks/video/useVideoPlayer.ts | 8 +- frontend/src/hooks/video/useVideoTimeouts.ts | 4 +- frontend/src/lib/elementDefaults.ts | 7 -- frontend/src/lib/elementEffects.ts | 66 +---------- frontend/src/pages/constructor.tsx | 16 +-- .../src/pages/element-type-defaults/[id].tsx | 11 +- .../pages/project-element-defaults/[id].tsx | 11 +- 33 files changed, 147 insertions(+), 742 deletions(-) diff --git a/backend/src/auth/auth.js b/backend/src/auth/auth.js index d26df22..5076160 100644 --- a/backend/src/auth/auth.js +++ b/backend/src/auth/auth.js @@ -20,9 +20,7 @@ passport.use( async (req, token, done) => { try { // Use lightweight auth query - only loads essential fields + permissions - const user = await UsersDBApi.findByForAuth({ - email: token.user.email, - }); + const user = await UsersDBApi.findByForAuth({ email: token.user.email }); if (user && user.disabled) { return done(new Error(`User '${user.email}' is disabled`)); diff --git a/backend/src/services/file.js b/backend/src/services/file.js index a2cc157..3ba884b 100644 --- a/backend/src/services/file.js +++ b/backend/src/services/file.js @@ -455,16 +455,10 @@ const downloadFile = async (req, res) => { const chunkSize = end - start + 1; res.status(206); - res.setHeader( - 'Content-Range', - `bytes ${start}-${end}/${stats.size}`, - ); + res.setHeader('Content-Range', `bytes ${start}-${end}/${stats.size}`); res.setHeader('Content-Length', chunkSize); res.setHeader('ETag', etag); - res.setHeader( - 'Cache-Control', - `public, max-age=${config.s3CacheMaxAge}`, - ); + res.setHeader('Cache-Control', `public, max-age=${config.s3CacheMaxAge}`); return fs.createReadStream(cachePath, { start, end }).pipe(res); } @@ -490,22 +484,12 @@ const downloadFile = async (req, res) => { const rangeHeader = req.headers.range; if (rangeHeader) { // For Range requests, we need to get file size first via headObject - const headResult = await s3.download(privateUrl, { - signal, - headOnly: true, - }); + const headResult = await s3.download(privateUrl, { signal, headOnly: true }); const totalSize = headResult.contentLength; if (!totalSize) { - log.warn( - { privateUrl }, - 'Cannot determine file size for range request', - ); - return res - .status(500) - .send( - createErrorResponse('Cannot determine file size', 'SIZE_UNKNOWN'), - ); + log.warn({ privateUrl }, 'Cannot determine file size for range request'); + return res.status(500).send(createErrorResponse('Cannot determine file size', 'SIZE_UNKNOWN')); } const range = parseRangeHeader(rangeHeader, totalSize); @@ -526,18 +510,12 @@ const downloadFile = async (req, res) => { res.status(206); res.setHeader('Content-Range', `bytes ${start}-${end}/${totalSize}`); res.setHeader('Content-Length', chunkSize); - if (rangeResult.contentType) - res.setHeader('Content-Type', rangeResult.contentType); - res.setHeader( - 'Cache-Control', - `public, max-age=${config.s3CacheMaxAge}`, - ); + if (rangeResult.contentType) res.setHeader('Content-Type', rangeResult.contentType); + res.setHeader('Cache-Control', `public, max-age=${config.s3CacheMaxAge}`); if (typeof rangeResult.body.pipe === 'function') { return rangeResult.body.pipe(res); - } else if ( - typeof rangeResult.body.transformToByteArray === 'function' - ) { + } else if (typeof rangeResult.body.transformToByteArray === 'function') { const bytes = await rangeResult.body.transformToByteArray(); return res.send(Buffer.from(bytes)); } else { @@ -660,9 +638,7 @@ const downloadFile = async (req, res) => { const localFilePath = path.join(config.uploadDir, privateUrl); if (!fs.existsSync(localFilePath)) { - return res - .status(404) - .send(createErrorResponse('File not found', 'NOT_FOUND')); + return res.status(404).send(createErrorResponse('File not found', 'NOT_FOUND')); } const stats = fs.statSync(localFilePath); diff --git a/backend/src/services/tour_pages.js b/backend/src/services/tour_pages.js index 27f3814..2483618 100644 --- a/backend/src/services/tour_pages.js +++ b/backend/src/services/tour_pages.js @@ -725,11 +725,7 @@ class TourPagesService extends BaseService { * @returns {Promise} Status object with ready keys and their reversed URLs */ static async checkReverseVideoStatus(storageKeys) { - if ( - !storageKeys || - !Array.isArray(storageKeys) || - storageKeys.length === 0 - ) { + if (!storageKeys || !Array.isArray(storageKeys) || storageKeys.length === 0) { return { ready: {}, pending: [], allReady: true }; } diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 7cce664..03a779e 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,2 +1,2 @@ (()=>{"use strict";let e,t,a,s={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"serwist",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},r=e=>[s.prefix,e,s.suffix].filter(e=>e&&e.length>0).join("-"),n={updateDetails:e=>{var t=t=>{let a=e[t];"string"==typeof a&&(s[t]=a)};for(let e of Object.keys(s))t(e)},getGoogleAnalyticsName:e=>e||r(s.googleAnalytics),getPrecacheName:e=>e||r(s.precache),getRuntimeName:e=>e||r(s.runtime)};var i=class extends Error{details;constructor(e,t){super(((e,...t)=>{let a=e;return t.length>0&&(a+=` :: ${JSON.stringify(t)}`),a})(e,t)),this.name=e,this.details=t}};function c(e){return new Promise(t=>setTimeout(t,e))}let o=new Set;function l(e,t){let a=new URL(e);for(let e of t)a.searchParams.delete(e);return a.href}async function h(e,t,a,s){let r=l(t.url,a);if(t.url===r)return e.match(t,s);let n={...s,ignoreSearch:!0};for(let i of(await e.keys(t,n)))if(r===l(i.url,a))return e.match(i,s)}var u=class{promise;resolve;reject;constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}};let d=async()=>{for(let e of o)await e()},m="-precache-",g=async(e,t=m)=>{let a=(await self.caches.keys()).filter(a=>a.includes(t)&&a.includes(self.registration.scope)&&a!==e);return await Promise.all(a.map(e=>self.caches.delete(e))),a},p=(e,t)=>{let a=t();return e.waitUntil(a),a},f=(e,t)=>t.some(t=>e instanceof t),w=new WeakMap,y=new WeakMap,_=new WeakMap,b={get(e,t,a){if(e instanceof IDBTransaction){if("done"===t)return w.get(e);if("store"===t)return a.objectStoreNames[1]?void 0:a.objectStore(a.objectStoreNames[0])}return v(e[t])},set:(e,t,a)=>(e[t]=a,!0),has:(e,t)=>e instanceof IDBTransaction&&("done"===t||"store"===t)||t in e};function v(e){if(e instanceof IDBRequest){let t=new Promise((t,a)=>{let s=()=>{e.removeEventListener("success",r),e.removeEventListener("error",n)},r=()=>{t(v(e.result)),s()},n=()=>{a(e.error),s()};e.addEventListener("success",r),e.addEventListener("error",n)});return _.set(t,e),t}if(y.has(e))return y.get(e);let s=function(e){if("function"==typeof e)return(a||(a=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(e)?function(...t){return e.apply(R(this),t),v(this.request)}:function(...t){return v(e.apply(R(this),t))};return(e instanceof IDBTransaction&&function(e){if(w.has(e))return;let t=new Promise((t,a)=>{let s=()=>{e.removeEventListener("complete",r),e.removeEventListener("error",n),e.removeEventListener("abort",n)},r=()=>{t(),s()},n=()=>{a(e.error||new DOMException("AbortError","AbortError")),s()};e.addEventListener("complete",r),e.addEventListener("error",n),e.addEventListener("abort",n)});w.set(e,t)}(e),f(e,t||(t=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])))?new Proxy(e,b):e}(e);return s!==e&&(y.set(e,s),_.set(s,e)),s}let R=e=>_.get(e);function x(e,t,{blocked:a,upgrade:s,blocking:r,terminated:n}={}){let i=indexedDB.open(e,t),c=v(i);return s&&i.addEventListener("upgradeneeded",e=>{s(v(i.result),e.oldVersion,e.newVersion,v(i.transaction),e)}),a&&i.addEventListener("blocked",e=>a(e.oldVersion,e.newVersion,e)),c.then(e=>{n&&e.addEventListener("close",()=>n()),r&&e.addEventListener("versionchange",e=>r(e.oldVersion,e.newVersion,e))}).catch(()=>{}),c}let E=["get","getKey","getAll","getAllKeys","count"],S=["put","add","delete","clear"],q=new Map;function C(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&"string"==typeof t))return;if(q.get(t))return q.get(t);let a=t.replace(/FromIndex$/,""),s=t!==a,r=S.includes(a);if(!(a in(s?IDBIndex:IDBObjectStore).prototype)||!(r||E.includes(a)))return;let n=async function(e,...t){let n=this.transaction(e,r?"readwrite":"readonly"),i=n.store;return s&&(i=i.index(t.shift())),(await Promise.all([i[a](...t),r&&n.done]))[0]};return q.set(t,n),n}b=(e=>({...e,get:(t,a,s)=>C(t,a)||e.get(t,a,s),has:(t,a)=>!!C(t,a)||e.has(t,a)}))(b);let N=["continue","continuePrimaryKey","advance"],D={},T=new WeakMap,P=new WeakMap,U={get(e,t){if(!N.includes(t))return e[t];let a=D[t];return a||(a=D[t]=function(...e){T.set(this,P.get(this)[t](...e))}),a}};async function*A(...e){let t=this;if(t instanceof IDBCursor||(t=await t.openCursor(...e)),!t)return;let a=new Proxy(t,U);for(P.set(a,t),_.set(a,R(t));t;)yield a,t=await (T.get(a)||t.continue()),T.delete(a)}function k(e,t){return t===Symbol.asyncIterator&&f(e,[IDBIndex,IDBObjectStore,IDBCursor])||"iterate"===t&&f(e,[IDBIndex,IDBObjectStore])}b=(e=>({...e,get:(t,a,s)=>k(t,a)?A:e.get(t,a,s),has:(t,a)=>k(t,a)||e.has(t,a)}))(b);let I=async(t,a)=>{let s=null;if(t.url&&(s=new URL(t.url).origin),s!==self.location.origin)throw new i("cross-origin-copy-response",{origin:s});let r=t.clone(),n={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},c=a?a(n):n,o=!function(){if(void 0===e){let t=new Response("");if("body"in t)try{new Response(t.body),e=!0}catch{e=!1}e=!1}return e}()?await r.blob():r.body;return new Response(o,c)},L="requests",W="queueName";var F=class{_db=null;async addEntry(e){let t=(await this.getDb()).transaction(L,"readwrite",{durability:"relaxed"});await t.store.add(e),await t.done}async getFirstEntryId(){return(await (await this.getDb()).transaction(L).store.openCursor())?.value.id}async getAllEntriesByQueueName(e){return await (await this.getDb()).getAllFromIndex(L,W,IDBKeyRange.only(e))||[]}async getEntryCountByQueueName(e){return(await this.getDb()).countFromIndex(L,W,IDBKeyRange.only(e))}async deleteEntry(e){await (await this.getDb()).delete(L,e)}async getFirstEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),"next")}async getLastEntryByQueueName(e){return await this.getEndEntryFromIndex(IDBKeyRange.only(e),"prev")}async getEndEntryFromIndex(e,t){return(await (await this.getDb()).transaction(L).store.index(W).openCursor(e,t))?.value}async getDb(){return this._db||(this._db=await x("serwist-background-sync",3,{upgrade:this._upgradeDb})),this._db}_upgradeDb(e,t){t>0&&t<3&&e.objectStoreNames.contains(L)&&e.deleteObjectStore(L),e.createObjectStore(L,{autoIncrement:!0,keyPath:"id"}).createIndex(W,W,{unique:!1})}},K=class{_queueName;_queueDb;constructor(e){this._queueName=e,this._queueDb=new F}async pushEntry(e){delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async unshiftEntry(e){let t=await this._queueDb.getFirstEntryId();t?e.id=t-1:delete e.id,e.queueName=this._queueName,await this._queueDb.addEntry(e)}async popEntry(){return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName))}async shiftEntry(){return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName))}async getAll(){return await this._queueDb.getAllEntriesByQueueName(this._queueName)}async size(){return await this._queueDb.getEntryCountByQueueName(this._queueName)}async deleteEntry(e){await this._queueDb.deleteEntry(e)}async _removeEntry(e){return e&&await this.deleteEntry(e.id),e}};let B=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];var M=class e{_requestData;static async fromRequest(t){let a={url:t.url,headers:{}};for(let e of("GET"!==t.method&&(a.body=await t.clone().arrayBuffer()),t.headers.forEach((e,t)=>{a.headers[t]=e}),B))void 0!==t[e]&&(a[e]=t[e]);return new e(a)}constructor(e){"navigate"===e.mode&&(e.mode="same-origin"),this._requestData=e}toObject(){let e=Object.assign({},this._requestData);return e.headers=Object.assign({},this._requestData.headers),e.body&&(e.body=e.body.slice(0)),e}toRequest(){return new Request(this._requestData.url,this._requestData)}clone(){return new e(this.toObject())}};let O="serwist-background-sync",j=new Set,H=e=>{let t={request:new M(e.requestData).toRequest(),timestamp:e.timestamp};return e.metadata&&(t.metadata=e.metadata),t};var $=class{_name;_onSync;_maxRetentionTime;_queueStore;_forceSyncFallback;_syncInProgress=!1;_requestsAddedDuringSync=!1;constructor(e,{forceSyncFallback:t,onSync:a,maxRetentionTime:s}={}){if(j.has(e))throw new i("duplicate-queue-name",{name:e});j.add(e),this._name=e,this._onSync=a||this.replayRequests,this._maxRetentionTime=s||10080,this._forceSyncFallback=!!t,this._queueStore=new K(this._name),this._addSyncListener()}get name(){return this._name}async pushRequest(e){await this._addRequest(e,"push")}async unshiftRequest(e){await this._addRequest(e,"unshift")}async popRequest(){return this._removeRequest("pop")}async shiftRequest(){return this._removeRequest("shift")}async getAll(){let e=await this._queueStore.getAll(),t=Date.now(),a=[];for(let s of e){let e=60*this._maxRetentionTime*1e3;t-s.timestamp>e?await this._queueStore.deleteEntry(s.id):a.push(H(s))}return a}async size(){return await this._queueStore.size()}async _addRequest({request:e,metadata:t,timestamp:a=Date.now()},s){let r={requestData:(await M.fromRequest(e.clone())).toObject(),timestamp:a};switch(t&&(r.metadata=t),s){case"push":await this._queueStore.pushEntry(r);break;case"unshift":await this._queueStore.unshiftEntry(r)}this._syncInProgress?this._requestsAddedDuringSync=!0:await this.registerSync()}async _removeRequest(e){let t,a=Date.now();switch(e){case"pop":t=await this._queueStore.popEntry();break;case"shift":t=await this._queueStore.shiftEntry()}if(t){let s=60*this._maxRetentionTime*1e3;return a-t.timestamp>s?this._removeRequest(e):H(t)}}async replayRequests(){let e;for(;e=await this.shiftRequest();)try{await fetch(e.request.clone())}catch{throw await this.unshiftRequest(e),new i("queue-replay-failed",{name:this._name})}}async registerSync(){if("sync"in self.registration&&!this._forceSyncFallback)try{await self.registration.sync.register(`${O}:${this._name}`)}catch(e){}}_addSyncListener(){"sync"in self.registration&&!this._forceSyncFallback?self.addEventListener("sync",e=>{if(e.tag===`${O}:${this._name}`){let t=async()=>{let t;this._syncInProgress=!0;try{await this._onSync({queue:this})}catch(e){if(e instanceof Error)throw e}finally{this._requestsAddedDuringSync&&!(t&&!e.lastChance)&&await this.registerSync(),this._syncInProgress=!1,this._requestsAddedDuringSync=!1}};e.waitUntil(t())}}):this._onSync({queue:this})}static get _queueNames(){return j}},z=class{_queue;constructor(e,t){this._queue=new $(e,t)}async fetchDidFail({request:e}){await this._queue.pushRequest({request:e})}};let G={cacheWillUpdate:async({response:e})=>200===e.status||0===e.status?e:null};function V(e){return"string"==typeof e?new Request(e):e}var Q=class{event;request;url;params;_cacheKeys={};_strategy;_handlerDeferred;_extendLifetimePromises;_plugins;_pluginStateMap;constructor(e,t){for(let a of(this.event=t.event,this.request=t.request,t.url&&(this.url=t.url,this.params=t.params),this._strategy=e,this._handlerDeferred=new u,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map,this._plugins))this._pluginStateMap.set(a,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:t}=this,a=V(e),s=await this.getPreloadResponse();if(s)return s;let r=this.hasCallback("fetchDidFail")?a.clone():null;try{for(let e of this.iterateCallbacks("requestWillFetch"))a=await e({request:a.clone(),event:t})}catch(e){if(e instanceof Error)throw new i("plugin-error-request-will-fetch",{thrownErrorMessage:e.message})}let n=a.clone();try{let e;for(let s of(e=await fetch(a,"navigate"===a.mode?void 0:this._strategy.fetchOptions),this.iterateCallbacks("fetchDidSucceed")))e=await s({event:t,request:n,response:e});return e}catch(e){throw r&&await this.runCallbacks("fetchDidFail",{error:e,event:t,originalRequest:r.clone(),request:n.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),a=t.clone();return this.waitUntil(this.cachePut(e,a)),t}async cacheMatch(e){let t,a=V(e),{cacheName:s,matchOptions:r}=this._strategy,n=await this.getCacheKey(a,"read"),i={...r,cacheName:s};for(let e of(t=await caches.match(n,i),this.iterateCallbacks("cachedResponseWillBeUsed")))t=await e({cacheName:s,matchOptions:r,cachedResponse:t,request:n,event:this.event})||void 0;return t}async cachePut(e,t){let a=V(e);await c(0);let s=await this.getCacheKey(a,"write");if(!t)throw new i("cache-put-with-no-response",{url:new URL(String(s.url),location.href).href.replace(RegExp(`^${location.origin}`),"")});let r=await this._ensureResponseSafeToCache(t);if(!r)return!1;let{cacheName:n,matchOptions:o}=this._strategy,l=await self.caches.open(n),u=this.hasCallback("cacheDidUpdate"),m=u?await h(l,s.clone(),["__WB_REVISION__"],o):null;try{await l.put(s,u?r.clone():r)}catch(e){if(e instanceof Error)throw"QuotaExceededError"===e.name&&await d(),e}for(let e of this.iterateCallbacks("cacheDidUpdate"))await e({cacheName:n,oldResponse:m,newResponse:r.clone(),request:s,event:this.event});return!0}async getCacheKey(e,t){let a=`${e.url} | ${t}`;if(!this._cacheKeys[a]){let s=e;for(let e of this.iterateCallbacks("cacheKeyWillBeUsed"))s=V(await e({mode:t,request:s,event:this.event,params:this.params}));this._cacheKeys[a]=s}return this._cacheKeys[a]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let a of this.iterateCallbacks(e))await a(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if("function"==typeof t[e]){let a=this._pluginStateMap.get(t),s=s=>{let r={...s,state:a};return t[e](r)};yield s}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){let e;for(;e=this._extendLifetimePromises.shift();)await e}destroy(){this._handlerDeferred.resolve(null)}async getPreloadResponse(){if(this.event instanceof FetchEvent&&"navigate"===this.event.request.mode&&"preloadResponse"in this.event)try{let e=await this.event.preloadResponse;if(e)return e}catch(e){return}}async _ensureResponseSafeToCache(e){let t=e,a=!1;for(let e of this.iterateCallbacks("cacheWillUpdate"))if(t=await e({request:this.request,response:t,event:this.event})||void 0,a=!0,!t)break;return!a&&t&&200!==t.status&&(t=void 0),t}},J=class{cacheName;plugins;fetchOptions;matchOptions;constructor(e={}){this.cacheName=n.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,a="string"==typeof e.request?new Request(e.request):e.request,s=new Q(this,e.url?{event:t,request:a,url:e.url,params:e.params}:{event:t,request:a}),r=this._getResponse(s,a,t);return[r,this._awaitComplete(r,s,a,t)]}async _getResponse(e,t,a){let s;await e.runCallbacks("handlerWillStart",{event:a,request:t});try{if(s=await this._handle(t,e),void 0===s||"error"===s.type)throw new i("no-response",{url:t.url})}catch(r){if(r instanceof Error){for(let n of e.iterateCallbacks("handlerDidError"))if(void 0!==(s=await n({error:r,event:a,request:t})))break}if(!s)throw r}for(let r of e.iterateCallbacks("handlerWillRespond"))s=await r({event:a,request:t,response:s});return s}async _awaitComplete(e,t,a,s){let r,n;try{r=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:s,request:a,response:r}),await t.doneWaiting()}catch(e){e instanceof Error&&(n=e)}if(await t.runCallbacks("handlerDidComplete",{event:s,request:a,response:r,error:n}),t.destroy(),n)throw n}},X=class extends J{_networkTimeoutSeconds;constructor(e={}){super(e),this.plugins.some(e=>"cacheWillUpdate"in e)||this.plugins.unshift(G),this._networkTimeoutSeconds=e.networkTimeoutSeconds||0}async _handle(e,t){let a,s=[],r=[];if(this._networkTimeoutSeconds){let{id:n,promise:i}=this._getTimeoutPromise({request:e,logs:s,handler:t});a=n,r.push(i)}let n=this._getNetworkPromise({timeoutId:a,request:e,logs:s,handler:t});r.push(n);let c=await t.waitUntil((async()=>await t.waitUntil(Promise.race(r))||await n)());if(!c)throw new i("no-response",{url:e.url});return c}_getTimeoutPromise({request:e,logs:t,handler:a}){let s;return{promise:new Promise(t=>{s=setTimeout(async()=>{t(await a.cacheMatch(e))},1e3*this._networkTimeoutSeconds)}),id:s}}async _getNetworkPromise({timeoutId:e,request:t,logs:a,handler:s}){let r,n;try{n=await s.fetchAndCachePut(t)}catch(e){e instanceof Error&&(r=e)}return e&&clearTimeout(e),(r||!n)&&(n=await s.cacheMatch(t)),n}},Y=class extends J{_networkTimeoutSeconds;constructor(e={}){super(e),this._networkTimeoutSeconds=e.networkTimeoutSeconds||0}async _handle(e,t){let a,s;try{let a=[t.fetch(e)];if(this._networkTimeoutSeconds){let e=c(1e3*this._networkTimeoutSeconds);a.push(e)}if(!(s=await Promise.race(a)))throw Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`)}catch(e){e instanceof Error&&(a=e)}if(!s)throw new i("no-response",{url:e.url,error:a});return s}};let Z=e=>e&&"object"==typeof e?e:{handle:e};var ee=class{handler;match;method;catchHandler;constructor(e,t,a="GET"){this.handler=Z(t),this.match=e,this.method=a}setCatchHandler(e){this.catchHandler=Z(e)}},et=class e extends J{_fallbackToNetwork;static defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:e})=>!e||e.status>=400?null:e};static copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:e})=>e.redirected?await I(e):e};constructor(t={}){t.cacheName=n.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=!1!==t.fallbackToNetwork,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){let a=await t.getPreloadResponse();if(a)return a;let s=await t.cacheMatch(e);return s||(t.event&&"install"===t.event.type?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let a,s=t.params||{};if(this._fallbackToNetwork){let r=s.integrity,n=e.integrity,i=!n||n===r;a=await t.fetch(new Request(e,{integrity:"no-cors"!==e.mode?n||r:void 0})),r&&i&&"no-cors"!==e.mode&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,a.clone()))}else throw new i("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return a}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();let a=await t.fetch(e);if(!await t.cachePut(e,a.clone()))throw new i("bad-precaching-response",{url:e.url,status:a.status});return a}_useDefaultCacheabilityPluginIfNeeded(){let t=null,a=0;for(let[s,r]of this.plugins.entries())r!==e.copyRedirectedCacheableResponsesPlugin&&(r===e.defaultPrecacheCacheabilityPlugin&&(t=s),r.cacheWillUpdate&&a++);0===a?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):a>1&&null!==t&&this.plugins.splice(t,1)}},ea=class extends ee{_allowlist;_denylist;constructor(e,{allowlist:t=[/./],denylist:a=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=a}_match({url:e,request:t}){if(t&&"navigate"!==t.mode)return!1;let a=e.pathname+e.search;for(let e of this._denylist)if(e.test(a))return!1;return!!this._allowlist.some(e=>e.test(a))}},es=class extends ee{constructor(e,t,a){super(({url:t})=>{let a=e.exec(t.href);if(a)return t.origin!==location.origin&&0!==a.index?void 0:a.slice(1)},t,a)}};let er=e=>{if(!e)throw new i("add-to-cache-list-unexpected-type",{entry:e});if("string"==typeof e){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:t,url:a}=e;if(!a)throw new i("add-to-cache-list-unexpected-type",{entry:e});if(!t){let e=new URL(a,location.href);return{cacheKey:e.href,url:e.href}}let s=new URL(a,location.href),r=new URL(a,location.href);return s.searchParams.set("__WB_REVISION__",t),{cacheKey:s.href,url:r.href}};var en=class{updatedURLs=[];notUpdatedURLs=[];handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)};cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:a})=>{if("install"===e.type&&t?.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;a?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return a}};let ei=async(e,t,a)=>{let s=t.map((e,t)=>({index:t,item:e})),r=async e=>{let t=[];for(;;){let r=s.pop();if(!r)return e(t);let n=await a(r.item);t.push({result:n,index:r.index})}},n=Array.from({length:e},()=>new Promise(r));return(await Promise.all(n)).flat().sort((e,t)=>e.indexe.result)};"undefined"!=typeof navigator&&/^((?!chrome|android).)*safari/i.test(navigator.userAgent);let ec="cache-entries",eo=e=>{let t=new URL(e,location.href);return t.hash="",t.href};var el=class{_cacheName;_db=null;constructor(e){this._cacheName=e}_getId(e){return`${this._cacheName}|${eo(e)}`}_upgradeDb(e){let t=e.createObjectStore(ec,{keyPath:"id"});t.createIndex("cacheName","cacheName",{unique:!1}),t.createIndex("timestamp","timestamp",{unique:!1})}_upgradeDbAndDeleteOldDbs(e){this._upgradeDb(e),this._cacheName&&function(e,{blocked:t}={}){let a=indexedDB.deleteDatabase(e);t&&a.addEventListener("blocked",e=>t(e.oldVersion,e)),v(a).then(()=>void 0)}(this._cacheName)}async setTimestamp(e,t){e=eo(e);let a={id:this._getId(e),cacheName:this._cacheName,url:e,timestamp:t},s=(await this.getDb()).transaction(ec,"readwrite",{durability:"relaxed"});await s.store.put(a),await s.done}async getTimestamp(e){return(await (await this.getDb()).get(ec,this._getId(e)))?.timestamp}async expireEntries(e,t){let a=await (await this.getDb()).transaction(ec,"readwrite").store.index("timestamp").openCursor(null,"prev"),s=[],r=0;for(;a;){let n=a.value;n.cacheName===this._cacheName&&(e&&n.timestamp=t?(a.delete(),s.push(n.url)):r++),a=await a.continue()}return s}async getDb(){return this._db||(this._db=await x("serwist-expiration",1,{upgrade:this._upgradeDbAndDeleteOldDbs.bind(this)})),this._db}},eh=class{_isRunning=!1;_rerunRequested=!1;_maxEntries;_maxAgeSeconds;_matchOptions;_cacheName;_timestampModel;constructor(e,t={}){this._maxEntries=t.maxEntries,this._maxAgeSeconds=t.maxAgeSeconds,this._matchOptions=t.matchOptions,this._cacheName=e,this._timestampModel=new el(e)}async expireEntries(){if(this._isRunning){this._rerunRequested=!0;return}this._isRunning=!0;let e=this._maxAgeSeconds?Date.now()-1e3*this._maxAgeSeconds:0,t=await this._timestampModel.expireEntries(e,this._maxEntries),a=await self.caches.open(this._cacheName);for(let e of t)await a.delete(e,this._matchOptions);this._isRunning=!1,this._rerunRequested&&(this._rerunRequested=!1,this.expireEntries())}async updateTimestamp(e){await this._timestampModel.setTimestamp(e,Date.now())}async isURLExpired(e){if(!this._maxAgeSeconds)return!1;let t=await this._timestampModel.getTimestamp(e),a=Date.now()-1e3*this._maxAgeSeconds;return void 0===t||tthis.deleteCacheAndMetadata(),o.add(t))}_getCacheExpiration(e){if(e===n.getRuntimeName())throw new i("expire-custom-caches-only");let t=this._cacheExpirations.get(e);return t||(t=new eh(e,this._config),this._cacheExpirations.set(e,t)),t}cachedResponseWillBeUsed({event:e,cacheName:t,request:a,cachedResponse:s}){if(!s)return null;let r=this._isResponseDateFresh(s),n=this._getCacheExpiration(t),i="last-used"===this._config.maxAgeFrom,c=(async()=>{i&&await n.updateTimestamp(a.url),await n.expireEntries()})();try{e.waitUntil(c)}catch{}return r?s:null}_isResponseDateFresh(e){if("last-used"===this._config.maxAgeFrom)return!0;let t=Date.now();if(!this._config.maxAgeSeconds)return!0;let a=this._getDateHeaderTimestamp(e);return null===a||a>=t-1e3*this._config.maxAgeSeconds}_getDateHeaderTimestamp(e){if(!e.headers.has("date"))return null;let t=new Date(e.headers.get("date")).getTime();return Number.isNaN(t)?null:t}async cacheDidUpdate({cacheName:e,request:t}){let a=this._getCacheExpiration(e);await a.updateTimestamp(t.url),await a.expireEntries()}async deleteCacheAndMetadata(){for(let[e,t]of this._cacheExpirations)await self.caches.delete(e),await t.delete();this._cacheExpirations=new Map}};let ed=/^\/(\w+\/)?collect/,em=({serwist:e,cacheName:t,...a})=>{let s=n.getGoogleAnalyticsName(t),r=new z("serwist-google-analytics",{maxRetentionTime:2880,onSync:(e=>async({queue:t})=>{let a;for(;a=await t.shiftRequest();){let{request:s,timestamp:r}=a,n=new URL(s.url);try{let t="POST"===s.method?new URLSearchParams(await s.clone().text()):n.searchParams,a=r-(Number(t.get("qt"))||0),i=Date.now()-a;if(t.set("qt",String(i)),e.parameterOverrides)for(let a of Object.keys(e.parameterOverrides)){let s=e.parameterOverrides[a];t.set(a,s)}"function"==typeof e.hitFilter&&e.hitFilter.call(null,t),await fetch(new Request(n.origin+n.pathname,{body:t.toString(),method:"POST",mode:"cors",credentials:"omit",headers:{"Content-Type":"text/plain"}}))}catch(e){throw await t.unshiftRequest(a),e}}})(a)});for(let t of[new ee(({url:e})=>"www.googletagmanager.com"===e.hostname&&"/gtm.js"===e.pathname,new X({cacheName:s}),"GET"),new ee(({url:e})=>"www.google-analytics.com"===e.hostname&&"/analytics.js"===e.pathname,new X({cacheName:s}),"GET"),new ee(({url:e})=>"www.googletagmanager.com"===e.hostname&&"/gtag/js"===e.pathname,new X({cacheName:s}),"GET"),...(e=>{let t=({url:e})=>"www.google-analytics.com"===e.hostname&&ed.test(e.pathname),a=new Y({plugins:[e]});return[new ee(t,a,"GET"),new ee(t,a,"POST")]})(r)])e.registerRoute(t)};var eg=class{_fallbackUrls;_serwist;constructor({fallbackUrls:e,serwist:t}){this._fallbackUrls=e,this._serwist=t}async handlerDidError(e){for(let t of this._fallbackUrls)if("string"==typeof t){let e=await this._serwist.matchPrecache(t);if(void 0!==e)return e}else if(t.matcher(e)){let e=await this._serwist.matchPrecache(t.url);if(void 0!==e)return e}}};let ep=async(e,t)=>{try{if(206===t.status)return t;let a=e.headers.get("range");if(!a)throw new i("no-range-header");let s=(e=>{let t=e.trim().toLowerCase();if(!t.startsWith("bytes="))throw new i("unit-must-be-bytes",{normalizedRangeHeader:t});if(t.includes(","))throw new i("single-range-only",{normalizedRangeHeader:t});let a=/(\d*)-(\d*)/.exec(t);if(!a||!(a[1]||a[2]))throw new i("invalid-range-values",{normalizedRangeHeader:t});return{start:""===a[1]?void 0:Number(a[1]),end:""===a[2]?void 0:Number(a[2])}})(a),r=await t.blob(),n=((e,t,a)=>{let s,r,n=e.size;if(a&&a>n||t&&t<0)throw new i("range-not-satisfiable",{size:n,end:a,start:t});return void 0!==t&&void 0!==a?(s=t,r=a+1):void 0!==t&&void 0===a?(s=t,r=n):void 0!==a&&void 0===t&&(s=n-a,r=n),{start:s,end:r}})(r,s.start,s.end),c=r.slice(n.start,n.end),o=c.size,l=new Response(c,{status:206,statusText:"Partial Content",headers:t.headers});return l.headers.set("Content-Length",String(o)),l.headers.set("Content-Range",`bytes ${n.start}-${n.end-1}/${r.size}`),l}catch(e){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}};var ef=class{cachedResponseWillBeUsed=async({request:e,cachedResponse:t})=>t&&e.headers.has("range")?await ep(e,t):t},ew=class extends J{async _handle(e,t){let a,s=await t.cacheMatch(e);if(s);else try{s=await t.fetchAndCachePut(e)}catch(e){e instanceof Error&&(a=e)}if(!s)throw new i("no-response",{url:e.url,error:a});return s}},ey=class extends J{constructor(e={}){super(e),this.plugins.some(e=>"cacheWillUpdate"in e)||this.plugins.unshift(G)}async _handle(e,t){let a,s=t.fetchAndCachePut(e).catch(()=>{});t.waitUntil(s);let r=await t.cacheMatch(e);if(r);else try{r=await s}catch(e){e instanceof Error&&(a=e)}if(!r)throw new i("no-response",{url:e.url,error:a});return r}},e_=class extends ee{constructor(e,t){super(({request:a})=>{let s=e.getUrlsToPrecacheKeys();for(let r of function*(e,{directoryIndex:t="index.html",ignoreURLParametersMatching:a=[/^utm_/,/^fbclid$/],cleanURLs:s=!0,urlManipulation:r}={}){let n=new URL(e,location.href);n.hash="",yield n.href;let i=((e,t=[])=>{for(let a of[...e.searchParams.keys()])t.some(e=>e.test(a))&&e.searchParams.delete(a);return e})(n,a);if(yield i.href,t&&i.pathname.endsWith("/")){let e=new URL(i.href);e.pathname+=t,yield e.href}if(s){let e=new URL(i.href);e.pathname+=".html",yield e.href}if(r)for(let e of r({url:n}))yield e.href}(a.url,t)){let t=s.get(r);if(t)return{cacheKey:t,integrity:e.getIntegrityForPrecacheKey(t)}}},e.precacheStrategy)}},eb=class{_precacheController;constructor({precacheController:e}){this._precacheController=e}cacheKeyWillBeUsed=async({request:e,params:t})=>{let a=t?.cacheKey||this._precacheController.getPrecacheKeyForUrl(e.url);return a?new Request(a,{headers:e.headers}):e}},ev=class{_urlsToCacheKeys=new Map;_urlsToCacheModes=new Map;_cacheKeysToIntegrities=new Map;_concurrentPrecaching;_precacheStrategy;_routes;_defaultHandlerMap;_catchHandler;_requestRules;constructor({precacheEntries:e,precacheOptions:t,skipWaiting:a=!1,importScripts:s,navigationPreload:r=!1,cacheId:i,clientsClaim:c=!1,runtimeCaching:o,offlineAnalyticsConfig:l,disableDevLogs:h=!1,fallbacks:u,requestRules:d}={}){var m,p;let{precacheStrategyOptions:f,precacheRouteOptions:w,precacheMiscOptions:y}=((e,t={})=>{let{cacheName:a,plugins:s=[],fetchOptions:r,matchOptions:i,fallbackToNetwork:c,directoryIndex:o,ignoreURLParametersMatching:l,cleanURLs:h,urlManipulation:u,cleanupOutdatedCaches:d,concurrency:m=10,navigateFallback:g,navigateFallbackAllowlist:p,navigateFallbackDenylist:f}=t??{};return{precacheStrategyOptions:{cacheName:n.getPrecacheName(a),plugins:[...s,new eb({precacheController:e})],fetchOptions:r,matchOptions:i,fallbackToNetwork:c},precacheRouteOptions:{directoryIndex:o,ignoreURLParametersMatching:l,cleanURLs:h,urlManipulation:u},precacheMiscOptions:{cleanupOutdatedCaches:d,concurrency:m,navigateFallback:g,navigateFallbackAllowlist:p,navigateFallbackDenylist:f}}})(this,t);if(this._concurrentPrecaching=y.concurrency,this._precacheStrategy=new et(f),this._routes=new Map,this._defaultHandlerMap=new Map,this._requestRules=d,this.handleInstall=this.handleInstall.bind(this),this.handleActivate=this.handleActivate.bind(this),this.handleFetch=this.handleFetch.bind(this),this.handleCache=this.handleCache.bind(this),s&&s.length>0&&self.importScripts(...s),r&&self.registration?.navigationPreload&&self.addEventListener("activate",e=>{e.waitUntil(self.registration.navigationPreload.enable().then(()=>{}))}),void 0!==i&&(m={prefix:i},n.updateDetails(m)),a?self.skipWaiting():self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),c&&self.addEventListener("activate",()=>self.clients.claim()),e&&e.length>0&&this.addToPrecacheList(e),y.cleanupOutdatedCaches&&(p=f.cacheName,self.addEventListener("activate",e=>{e.waitUntil(g(n.getPrecacheName(p)).then(e=>{}))})),this.registerRoute(new e_(this,w)),y.navigateFallback&&this.registerRoute(new ea(this.createHandlerBoundToUrl(y.navigateFallback),{allowlist:y.navigateFallbackAllowlist,denylist:y.navigateFallbackDenylist})),void 0!==l&&("boolean"==typeof l?l&&em({serwist:this}):em({...l,serwist:this})),void 0!==o){if(void 0!==u){let e=new eg({fallbackUrls:u.entries,serwist:this});o.forEach(t=>{t.handler instanceof J&&!t.handler.plugins.some(e=>"handlerDidError"in e)&&t.handler.plugins.push(e)})}for(let e of o)this.registerCapture(e.matcher,e.handler,e.method)}h&&(self.__WB_DISABLE_DEV_LOGS=!0)}get precacheStrategy(){return this._precacheStrategy}get routes(){return this._routes}addEventListeners(){self.addEventListener("install",this.handleInstall),self.addEventListener("activate",this.handleActivate),self.addEventListener("fetch",this.handleFetch),self.addEventListener("message",this.handleCache)}addToPrecacheList(e){let t=[];for(let a of e){"string"==typeof a?t.push(a):a&&!a.integrity&&void 0===a.revision&&t.push(a.url);let{cacheKey:e,url:s}=er(a),r="string"!=typeof a&&a.revision?"reload":"default";if(this._urlsToCacheKeys.has(s)&&this._urlsToCacheKeys.get(s)!==e)throw new i("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(s),secondEntry:e});if("string"!=typeof a&&a.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==a.integrity)throw new i("add-to-cache-list-conflicting-integrities",{url:s});this._cacheKeysToIntegrities.set(e,a.integrity)}this._urlsToCacheKeys.set(s,e),this._urlsToCacheModes.set(s,r)}t.length>0&&console.warn(`Serwist is precaching URLs without revision info: ${t.join(", ")} -This is generally NOT safe. Learn more at https://bit.ly/wb-precache`)}handleInstall(e){return this.registerRequestRules(e),p(e,async()=>{let t=new en;this.precacheStrategy.plugins.push(t),await ei(this._concurrentPrecaching,Array.from(this._urlsToCacheKeys.entries()),async([t,a])=>{let s=this._cacheKeysToIntegrities.get(a),r=this._urlsToCacheModes.get(t),n=new Request(t,{integrity:s,cache:r,credentials:"same-origin"});await Promise.all(this.precacheStrategy.handleAll({event:e,request:n,url:new URL(n.url),params:{cacheKey:a}}))});let{updatedURLs:a,notUpdatedURLs:s}=t;return{updatedURLs:a,notUpdatedURLs:s}})}async registerRequestRules(e){if(this._requestRules&&e?.addRoutes)try{await e.addRoutes(this._requestRules),this._requestRules=void 0}catch(e){throw e}}handleActivate(e){return p(e,async()=>{let e=await self.caches.open(this.precacheStrategy.cacheName),t=await e.keys(),a=new Set(this._urlsToCacheKeys.values()),s=[];for(let r of t)a.has(r.url)||(await e.delete(r),s.push(r.url));return{deletedCacheRequests:s}})}handleFetch(e){let{request:t}=e,a=this.handleRequest({request:t,event:e});a&&e.respondWith(a)}handleCache(e){if(e.data&&"CACHE_URLS"===e.data.type){let{payload:t}=e.data,a=Promise.all(t.urlsToCache.map(t=>{let a;return a="string"==typeof t?new Request(t):new Request(...t),this.handleRequest({request:a,event:e})}));e.waitUntil(a),e.ports?.[0]&&a.then(()=>e.ports[0].postMessage(!0))}}setDefaultHandler(e,t="GET"){this._defaultHandlerMap.set(t,Z(e))}setCatchHandler(e){this._catchHandler=Z(e)}registerCapture(e,t,a){let s=((e,t,a)=>{if("string"==typeof e){let s=new URL(e,location.href);return new ee(({url:e})=>e.href===s.href,t,a)}if(e instanceof RegExp)return new es(e,t,a);if("function"==typeof e)return new ee(e,t,a);if(e instanceof ee)return e;throw new i("unsupported-route-type",{moduleName:"serwist",funcName:"parseRoute",paramName:"capture"})})(e,t,a);return this.registerRoute(s),s}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new i("unregister-route-but-not-found-with-method",{method:e.method});let t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new i("unregister-route-route-not-registered")}getUrlsToPrecacheKeys(){return this._urlsToCacheKeys}getPrecachedUrls(){return[...this._urlsToCacheKeys.keys()]}getPrecacheKeyForUrl(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForPrecacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,a=this.getPrecacheKeyForUrl(t);if(a)return(await self.caches.open(this.precacheStrategy.cacheName)).match(a)}createHandlerBoundToUrl(e){let t=this.getPrecacheKeyForUrl(e);if(!t)throw new i("non-precached-url",{url:e});return a=>(a.request=new Request(e),a.params={cacheKey:t,...a.params},this.precacheStrategy.handle(a))}handleRequest({request:e,event:t}){let a,s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;let r=s.origin===location.origin,{params:n,route:i}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:s}),c=i?.handler,o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;try{a=c.handle({url:s,request:e,event:t,params:n})}catch(e){a=Promise.reject(e)}let l=i?.catchHandler;return a instanceof Promise&&(this._catchHandler||l)&&(a=a.catch(async a=>{if(l)try{return await l.handle({url:s,request:e,event:t,params:n})}catch(e){e instanceof Error&&(a=e)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw a})),a}findMatchingRoute({url:e,sameOrigin:t,request:a,event:s}){for(let r of this._routes.get(a.method)||[]){let n,i=r.match({url:e,sameOrigin:t,request:a,event:s});if(i)return Array.isArray(n=i)&&0===n.length||i.constructor===Object&&0===Object.keys(i).length?n=void 0:"boolean"==typeof i&&(n=void 0),{route:r,params:n}}return{}}};let eR={rscPrefetch:"pages-rsc-prefetch",rsc:"pages-rsc",html:"pages"},ex=[{matcher:/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,handler:new ew({cacheName:"google-fonts-webfonts",plugins:[new eu({maxEntries:4,maxAgeSeconds:31536e3,maxAgeFrom:"last-used"})]})},{matcher:/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,handler:new ey({cacheName:"google-fonts-stylesheets",plugins:[new eu({maxEntries:4,maxAgeSeconds:604800,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,handler:new ey({cacheName:"static-font-assets",plugins:[new eu({maxEntries:4,maxAgeSeconds:604800,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,handler:new ey({cacheName:"static-image-assets",plugins:[new eu({maxEntries:64,maxAgeSeconds:2592e3,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/static.+\.js$/i,handler:new ew({cacheName:"next-static-js-assets",plugins:[new eu({maxEntries:64,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/image\?url=.+$/i,handler:new ey({cacheName:"next-image",plugins:[new eu({maxEntries:64,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:mp3|wav|ogg)$/i,handler:new ew({cacheName:"static-audio-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"}),new ef]})},{matcher:/\.(?:mp4|webm)$/i,handler:new ew({cacheName:"static-video-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"}),new ef]})},{matcher:/\.(?:js)$/i,handler:new ey({cacheName:"static-js-assets",plugins:[new eu({maxEntries:48,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:css|less)$/i,handler:new ey({cacheName:"static-style-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/data\/.+\/.+\.json$/i,handler:new X({cacheName:"next-data",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:json|xml|csv)$/i,handler:new X({cacheName:"static-data-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/api\/auth\/.*/,handler:new Y({networkTimeoutSeconds:10})},{matcher:({sameOrigin:e,url:{pathname:t}})=>e&&t.startsWith("/api/"),method:"GET",handler:new X({cacheName:"apis",plugins:[new eu({maxEntries:16,maxAgeSeconds:86400,maxAgeFrom:"last-used"})],networkTimeoutSeconds:10})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.rscPrefetch,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.rsc,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>e.headers.get("Content-Type")?.includes("text/html")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.html,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({url:{pathname:e},sameOrigin:t})=>t&&!e.startsWith("/api/"),handler:new X({cacheName:"others",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({sameOrigin:e})=>!e,handler:new X({cacheName:"cross-origin",plugins:[new eu({maxEntries:32,maxAgeSeconds:3600})],networkTimeoutSeconds:10})},{matcher:/.*/i,method:"GET",handler:new Y}],eE={cacheNames:{static:"tour-builder-static-v1",dynamic:"tour-builder-dynamic-v1",assets:"tour-builder-assets-v1"}},eS=[".png",".jpg",".jpeg",".gif",".webp",".svg",".ico",".mp4",".webm",".mov",".mp3",".wav",".ogg",".m4a",".woff",".woff2",".ttf",".eot",".css",".js"],eq=e=>{let t=new URL(e.url);return[".mp4",".webm",".mov"].some(e=>t.pathname.toLowerCase().endsWith(e))},eC=e=>{let t=new URL(e.url);return[".mp3",".wav",".ogg",".m4a",".aac"].some(e=>t.pathname.toLowerCase().endsWith(e))},eN=e=>{try{if(e.includes("/file/download?privateUrl=")){let t=e.match(/privateUrl=([^&]+)/);if(t)return decodeURIComponent(t[1]).replace(/^\/+/,"")}if(e.includes("X-Amz-Signature=")||e.includes("x-amz-signature=")){let t=new URL(e).pathname.split("/").filter(Boolean),a=t.findIndex(e=>"assets"===e);if(-1!==a)return t.slice(a).join("/");if(t.length>1)return t.slice(1).join("/")}if(e.startsWith("assets/"))return e;let t=e.match(/^https?:\/\/[^/]+\.s3\.[^/]+\.amazonaws\.com\/[^/]+\/(assets\/.+)$/);if(t)return t[1].split("?")[0];return null}catch(e){return null}},eD=new Map;setInterval(()=>{eD.clear(),console.log("[SW] Cleared storage path mappings")},36e5);let eT=new ev({precacheEntries:[{'revision':'737d8a2d3e57cd4f57b597e72530ed15','url':'/_next/static/UfKazOMgjEjvP7qUov4wR/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/UfKazOMgjEjvP7qUov4wR/_ssgManifest.js'},{'revision':null,'url':'/_next/static/chunks/12142dde-27563c775e4f8cf0.js'},{'revision':null,'url':'/_next/static/chunks/223-5e7611f0ad4bb7f3.js'},{'revision':null,'url':'/_next/static/chunks/3639-bf9412ffc111d3d0.js'},{'revision':null,'url':'/_next/static/chunks/3643-cd854ecad4d47645.js'},{'revision':null,'url':'/_next/static/chunks/3658-983d95bcba4cf675.js'},{'revision':null,'url':'/_next/static/chunks/400-ac29014eab06ed0b.js'},{'revision':null,'url':'/_next/static/chunks/4166-8f063655eaf7488d.js'},{'revision':null,'url':'/_next/static/chunks/4271-b6985491cfc2640a.js'},{'revision':null,'url':'/_next/static/chunks/4449-7e61220ef8d18155.js'},{'revision':null,'url':'/_next/static/chunks/4498-f6d8f8acfe886017.js'},{'revision':null,'url':'/_next/static/chunks/4587-c9e5910a896d025b.js'},{'revision':null,'url':'/_next/static/chunks/5371-df80deb16401ff8d.js'},{'revision':null,'url':'/_next/static/chunks/5541.e90be83844a6de15.js'},{'revision':null,'url':'/_next/static/chunks/6d2b60a9-eb6c7fd9a57c4f19.js'},{'revision':null,'url':'/_next/static/chunks/7e42aecb-94f8c450c54b9556.js'},{'revision':null,'url':'/_next/static/chunks/8180-20afedac42c2550d.js'},{'revision':null,'url':'/_next/static/chunks/8230-251e23b7a24ff307.js'},{'revision':null,'url':'/_next/static/chunks/8232-06043c4b3efac0d3.js'},{'revision':null,'url':'/_next/static/chunks/8317-4e9c090ccc089653.js'},{'revision':null,'url':'/_next/static/chunks/8366-e9bd140fe53326f8.js'},{'revision':null,'url':'/_next/static/chunks/8628-68b8d2bcffb6ae14.js'},{'revision':null,'url':'/_next/static/chunks/9375.2c053880845a1e8b.js'},{'revision':null,'url':'/_next/static/chunks/9807-0f16940d153e1528.js'},{'revision':null,'url':'/_next/static/chunks/9848-799d062feeef8c3c.js'},{'revision':null,'url':'/_next/static/chunks/fa3de7d5.a08b8fcddee4216b.js'},{'revision':null,'url':'/_next/static/chunks/framework-4dea986807dc9d02.js'},{'revision':null,'url':'/_next/static/chunks/main-34a5a9b298cb987a.js'},{'revision':null,'url':'/_next/static/chunks/pages/_error-530d8245847656c6.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/%5Baccess_logsId%5D-b32705e3edb82fb5.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-edit-e57f4b1578eba238.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-list-862001bdbda3ae79.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-new-47edb3b2fe5d671b.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-table-209ff63629e22410.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-view-5caa4fc126fce326.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/%5Basset_variantsId%5D-1678a1511c42bfbc.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-edit-bed469a27cd06526.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-list-e2428f84be4f6330.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-new-a9961c26e74176ee.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-table-8e31cbc9806c75e5.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-view-215e389fc57263d7.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/%5BassetsId%5D-cf12fd35a133f4f0.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-edit-fc0b0f675a8f5c91.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-list-3e66238c012f6058.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-new-c1b74fa86d2e8dca.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-table-cb888498c764cc82.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-view-4a904823c447ae56.js'},{'revision':null,'url':'/_next/static/chunks/pages/constructor-b4abcbf8eaa26ed8.js'},{'revision':null,'url':'/_next/static/chunks/pages/dashboard-e90e5fca1401925a.js'},{'revision':null,'url':'/_next/static/chunks/pages/element-type-defaults-463491004a6b0073.js'},{'revision':null,'url':'/_next/static/chunks/pages/element-type-defaults/%5Bid%5D-14686cb7fea95a1d.js'},{'revision':null,'url':'/_next/static/chunks/pages/error-078bf46bb4f4c577.js'},{'revision':null,'url':'/_next/static/chunks/pages/forgot-7c012c8aaac71432.js'},{'revision':null,'url':'/_next/static/chunks/pages/index-f7e2d9108644a6f9.js'},{'revision':null,'url':'/_next/static/chunks/pages/login-a1d399f13c626ebd.js'},{'revision':null,'url':'/_next/static/chunks/pages/p/%5BprojectSlug%5D-4f6f9985bbb9b753.js'},{'revision':null,'url':'/_next/static/chunks/pages/p/%5BprojectSlug%5D/stage-23855acceec71ac2.js'},{'revision':null,'url':'/_next/static/chunks/pages/password-reset-455af6aac51516fe.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/%5BpermissionsId%5D-ba71d986ba5cc8fc.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-edit-40c5c80e903296e5.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-list-e24373283511ad92.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-new-a5aaba465f5e06ba.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-table-3e76d14bc8cebbf6.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-view-bc146f95287c4342.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/%5Bpresigned_url_requestsId%5D-50e2c76c9e560349.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-edit-2353060256e68c18.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-list-e0ab5ebfc0640fc5.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-new-4082b5bae7b63e87.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-table-5ed2aca030d2375c.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-view-786d8c42729dfae4.js'},{'revision':null,'url':'/_next/static/chunks/pages/privacy-policy-53ea2331c015449b.js'},{'revision':null,'url':'/_next/static/chunks/pages/profile-cf3e76df64b95b2e.js'},{'revision':null,'url':'/_next/static/chunks/pages/project-element-defaults-fba2e7529ad7fa20.js'},{'revision':null,'url':'/_next/static/chunks/pages/project-element-defaults/%5Bid%5D-2a9b9ec0a5ad39c0.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/%5Bproject_audio_tracksId%5D-84505a5d1bdaca57.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-edit-603add372e82e607.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-list-229cafa01e2c6edc.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-new-c6f8092a6afca5fe.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-table-f6dd77f2938fa592.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-view-9a0e90abaa80f5c6.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/%5Bproject_membershipsId%5D-a09978b19d912f31.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-edit-357c7f7cdfb521bf.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-list-62c341e178d0d37b.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-new-4892d0814688c548.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-table-fb80cbce9701ca85.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-view-a49b17115676b9d6.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/%5BprojectsId%5D-aff87e4f3b8c7859.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-edit-f3f4a82c22449e83.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-list-63780043574a7df1.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-new-620fb97ab7b8c2ae.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-table-09a5ba65059fe42c.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-view-6ab88073d01e7234.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/%5Bpublish_eventsId%5D-1221da0d818c5fa1.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-edit-b140f5347d843d6c.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-list-9ba9952b20f103ab.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-new-7812adf22af8012d.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-table-f05bab29903c9752.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-view-989df6b2ae0f52ff.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/%5Bpwa_cachesId%5D-063158e045bb255c.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-edit-74c4434dad6ae76f.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-list-e56bc5e48062975e.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-new-427aedfbd31041cc.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-table-149653ca0aa91341.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-view-d4cd0ed163eeef15.js'},{'revision':null,'url':'/_next/static/chunks/pages/register-3fcdd8e6389f7ef5.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/%5BrolesId%5D-83556899e7280c97.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-edit-07233f706c8faaea.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-list-f67615ce75c49a4f.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-new-efab36392a595372.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-table-ab1f8a0efc39db9c.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-view-46eac6512efffec1.js'},{'revision':null,'url':'/_next/static/chunks/pages/search-d8f3264d25ac0bdb.js'},{'revision':null,'url':'/_next/static/chunks/pages/terms-of-use-7d00698d6183bde4.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/%5Btour_pagesId%5D-88b25d5abb8c9d7a.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-edit-988e4db25855470e.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-list-48c842a9c2971ded.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-new-2b06e13e70530cc0.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-table-4633b5d51a17fc5e.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-view-ddbb70192d378851.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/%5BusersId%5D-38909f3f9c0cffea.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-edit-8634a664d2794a3f.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-list-31ccd4c2c1f8cc75.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-new-7ef52cd8dafbb885.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-table-de2a889aafd936e9.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-view-436d3bcdfad89528.js'},{'revision':null,'url':'/_next/static/chunks/pages/verify-email-f94b54604550410c.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':null,'url':'/_next/static/chunks/webpack-948191860fdb5996.js'},{'revision':null,'url':'/_next/static/css/70dc945dd6c70582.css'},{'revision':null,'url':'/_next/static/css/715be398208dca58.css'},{'revision':null,'url':'/_next/static/css/de6fa09b8a0934d1.css'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-ext-wdth-normal.a718fc63.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-ext-wght-normal.7db92424.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-wdth-normal.68c3c527.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-wght-normal.ae05c57c.woff2'},{'revision':'43778b43fb039fdd5b0510561dc952ac','url':'/assets/vm-shot-2026-03-17T04-16-09-161Z.jpg'},{'revision':'3395d49fe2b96221471b4db0f5caf9e7','url':'/assets/vm-shot-2026-03-17T04-19-03-565Z.jpg'},{'revision':'62127cd8f85821f3e570a377a8a7e14b','url':'/assets/vm-shot-2026-03-17T04-36-56-252Z.jpg'},{'revision':'145c2d7e4ef298391258c6d8a8aaaece','url':'/assets/vm-shot-2026-03-17T04-45-14-111Z.jpg'},{'revision':'c6aae6521f08c847e370764d1c9c613d','url':'/assets/vm-shot-2026-03-17T04-50-58-546Z.jpg'},{'revision':'bf90fb959c1d418b01eb7e55de6672d5','url':'/assets/vm-shot-2026-03-19T06-12-36-229Z.jpg'},{'revision':'73de6cac0695249f4e59ce778a8e6e74','url':'/assets/vm-shot-2026-03-19T06-13-19-234Z.jpg'},{'revision':'bf90fb959c1d418b01eb7e55de6672d5','url':'/assets/vm-shot-2026-03-19T06-13-54-729Z.jpg'},{'revision':'ca6cbfcc74b52f00eef0f2adc8e65456','url':'/assets/vm-shot-2026-03-24T14-29-20-260Z.jpg'},{'revision':'0957c5365895c5ba31b10a84f4e45929','url':'/data-sources/clients.json'},{'revision':'5703d42f7838705ecf87510c4032b20c','url':'/data-sources/history.json'},{'revision':'5404a85badad8210a634ce41bb511545','url':'/favicon.svg'},{'revision':'fbe5fc5322bbd6ffda8bff4441b6bfa6','url':'/fonts/MapleMedium.otf'},{'revision':'508520242399a6b1fec65430901f4e6f','url':'/locales/de/common.json'},{'revision':'0a64739b954a93627749ffcb846fceaa','url':'/locales/en/common.json'},{'revision':'34715e25a3bcbab44f84232ce082d2ee','url':'/locales/es/common.json'},{'revision':'772a72f35589c06bdd8d9179c86459e6','url':'/locales/fr/common.json'},{'revision':'c67326edc61e0b5cf87e509ea7553466','url':'/manifest.json'},{'revision':'1254c9c72aa64724c203d26278712800','url':'/offline.html'}],skipWaiting:!0,clientsClaim:!0,navigationPreload:!0,runtimeCaching:[{matcher:e=>{let{request:t}=e;return"1"===new URL(t.url).searchParams.get("sw-bypass")},handler:new Y},{matcher:e=>{let{request:t}=e,a=new URL(t.url);return"image"===t.destination||"font"===t.destination||[".css",".js",".woff",".woff2"].some(e=>a.pathname.endsWith(e))},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);return s?(console.log("[SW] Using storagePath for static asset ".concat(a,":"),s.slice(-40)),new Request(s)):t},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{request:t}=e;return eq(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);if(s){let e=eD.get(s);return e?(console.log("[SW] Using storageKey for video ".concat(a,":"),e.slice(-40)),new Request(e)):(console.log("[SW] Using storagePath for video ".concat(a,":"),s.slice(-40)),new Request(s))}return t},cachedResponseWillBeUsed:async e=>{let{cachedResponse:t,request:a}=e;if(!t)return null;let s=a.headers.get("range");if(!s)return t;let r=s.match(/bytes=(\d+)-(\d*)/);if(!r)return t;let n=parseInt(r[1],10),i=r[2]?parseInt(r[2],10):void 0,c=await t.blob(),o=void 0!==i?c.slice(n,i+1):c.slice(n);return new Response(o,{status:206,statusText:"Partial Content",headers:{"Content-Type":t.headers.get("Content-Type")||"video/mp4","Content-Length":String(o.size),"Content-Range":"bytes ".concat(n,"-").concat(void 0!==i?i:c.size-1,"/").concat(c.size),"Accept-Ranges":"bytes"}})},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{request:t}=e;return eC(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);if(s){let e=eD.get(s);return e?(console.log("[SW] Using storageKey for audio ".concat(a,":"),e.slice(-40)),new Request(e)):(console.log("[SW] Using storagePath for audio ".concat(a,":"),s.slice(-40)),new Request(s))}return t},cachedResponseWillBeUsed:async e=>{let{cachedResponse:t,request:a}=e;if(!t)return null;let s=a.headers.get("range");if(!s)return t;let r=s.match(/bytes=(\d+)-(\d*)/);if(!r)return t;let n=parseInt(r[1],10),i=r[2]?parseInt(r[2],10):void 0,c=await t.blob(),o=void 0!==i?c.slice(n,i+1):c.slice(n);return new Response(o,{status:206,statusText:"Partial Content",headers:{"Content-Type":t.headers.get("Content-Type")||"audio/mpeg","Content-Length":String(o.size),"Content-Range":"bytes ".concat(n,"-").concat(void 0!==i?i:c.size-1,"/").concat(c.size),"Accept-Ranges":"bytes"}})},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{url:t}=e;return t.pathname.startsWith("/api/")},handler:new X({cacheName:"api-cache",networkTimeoutSeconds:10})},{matcher:e=>{let{request:t}=e;return(e=>{let t=new URL(e.url);return(!t.pathname.startsWith("/api/")||!!t.pathname.includes("/file/download"))&&!!(eS.some(e=>t.pathname.toLowerCase().endsWith(e))||t.pathname.includes("/file/download")||t.hostname.includes("amazonaws.com")||t.hostname.includes("cloudfront.net"))})(t)&&!eq(t)&&!eC(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);return s?(console.log("[SW] Using storagePath for dynamic asset ".concat(a,":"),s.slice(-40)),new Request(s)):t},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},...ex]});self.addEventListener("message",e=>{let{type:t,payload:a}=e.data||{};switch(t){case"REGISTER_CACHE_URL":if(null==a?void 0:a.storageKey){let e=a.presignedUrl&&eN(a.presignedUrl)||a.storageKey;eD.set(e,a.storageKey),console.log("[SW] Registered storage path for caching",{storagePath:e.slice(-50),storageKey:a.storageKey.slice(-50)})}break;case"CLEAR_URL_MAPPINGS":eD.clear(),console.log("[SW] Storage path mappings cleared");break;case"CACHE_ASSETS":Array.isArray(null==a?void 0:a.urls)&&e.waitUntil(caches.open(eE.cacheNames.assets).then(e=>Promise.all(a.urls.map(t=>fetch(t).then(a=>{if(200===a.status)return e.put(t,a)}).catch(e=>{console.warn("[SW] Failed to cache asset:",t,e)})))));break;case"CACHE_VIDEO_CHUNK":(null==a?void 0:a.url)&&(null==a?void 0:a.chunk)&&e.waitUntil(caches.open(eE.cacheNames.assets).then(e=>{let t=new Response(a.chunk,{headers:{"Content-Type":a.contentType||"video/mp4","Content-Length":String(a.chunk.byteLength)}});return e.put(a.url,t)}));break;case"CLEAR_CACHE":eD.clear(),e.waitUntil(Promise.all([caches.delete(eE.cacheNames.dynamic),caches.delete(eE.cacheNames.assets)]).then(()=>{console.log("[SW] Caches and storage path mappings cleared")}));break;case"GET_CACHE_STATUS":e.waitUntil(caches.open(eE.cacheNames.assets).then(t=>t.keys().then(t=>{let a=e.source;null==a||a.postMessage({type:"CACHE_STATUS",payload:{cachedCount:t.length,urls:t.map(e=>e.url)}})})));break;case"SKIP_WAITING":self.skipWaiting()}}),eT.addEventListeners(),console.log("[SW] Serwist service worker loaded")})(); \ No newline at end of file +This is generally NOT safe. Learn more at https://bit.ly/wb-precache`)}handleInstall(e){return this.registerRequestRules(e),p(e,async()=>{let t=new en;this.precacheStrategy.plugins.push(t),await ei(this._concurrentPrecaching,Array.from(this._urlsToCacheKeys.entries()),async([t,a])=>{let s=this._cacheKeysToIntegrities.get(a),r=this._urlsToCacheModes.get(t),n=new Request(t,{integrity:s,cache:r,credentials:"same-origin"});await Promise.all(this.precacheStrategy.handleAll({event:e,request:n,url:new URL(n.url),params:{cacheKey:a}}))});let{updatedURLs:a,notUpdatedURLs:s}=t;return{updatedURLs:a,notUpdatedURLs:s}})}async registerRequestRules(e){if(this._requestRules&&e?.addRoutes)try{await e.addRoutes(this._requestRules),this._requestRules=void 0}catch(e){throw e}}handleActivate(e){return p(e,async()=>{let e=await self.caches.open(this.precacheStrategy.cacheName),t=await e.keys(),a=new Set(this._urlsToCacheKeys.values()),s=[];for(let r of t)a.has(r.url)||(await e.delete(r),s.push(r.url));return{deletedCacheRequests:s}})}handleFetch(e){let{request:t}=e,a=this.handleRequest({request:t,event:e});a&&e.respondWith(a)}handleCache(e){if(e.data&&"CACHE_URLS"===e.data.type){let{payload:t}=e.data,a=Promise.all(t.urlsToCache.map(t=>{let a;return a="string"==typeof t?new Request(t):new Request(...t),this.handleRequest({request:a,event:e})}));e.waitUntil(a),e.ports?.[0]&&a.then(()=>e.ports[0].postMessage(!0))}}setDefaultHandler(e,t="GET"){this._defaultHandlerMap.set(t,Z(e))}setCatchHandler(e){this._catchHandler=Z(e)}registerCapture(e,t,a){let s=((e,t,a)=>{if("string"==typeof e){let s=new URL(e,location.href);return new ee(({url:e})=>e.href===s.href,t,a)}if(e instanceof RegExp)return new es(e,t,a);if("function"==typeof e)return new ee(e,t,a);if(e instanceof ee)return e;throw new i("unsupported-route-type",{moduleName:"serwist",funcName:"parseRoute",paramName:"capture"})})(e,t,a);return this.registerRoute(s),s}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new i("unregister-route-but-not-found-with-method",{method:e.method});let t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new i("unregister-route-route-not-registered")}getUrlsToPrecacheKeys(){return this._urlsToCacheKeys}getPrecachedUrls(){return[...this._urlsToCacheKeys.keys()]}getPrecacheKeyForUrl(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForPrecacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,a=this.getPrecacheKeyForUrl(t);if(a)return(await self.caches.open(this.precacheStrategy.cacheName)).match(a)}createHandlerBoundToUrl(e){let t=this.getPrecacheKeyForUrl(e);if(!t)throw new i("non-precached-url",{url:e});return a=>(a.request=new Request(e),a.params={cacheKey:t,...a.params},this.precacheStrategy.handle(a))}handleRequest({request:e,event:t}){let a,s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;let r=s.origin===location.origin,{params:n,route:i}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:s}),c=i?.handler,o=e.method;if(!c&&this._defaultHandlerMap.has(o)&&(c=this._defaultHandlerMap.get(o)),!c)return;try{a=c.handle({url:s,request:e,event:t,params:n})}catch(e){a=Promise.reject(e)}let l=i?.catchHandler;return a instanceof Promise&&(this._catchHandler||l)&&(a=a.catch(async a=>{if(l)try{return await l.handle({url:s,request:e,event:t,params:n})}catch(e){e instanceof Error&&(a=e)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw a})),a}findMatchingRoute({url:e,sameOrigin:t,request:a,event:s}){for(let r of this._routes.get(a.method)||[]){let n,i=r.match({url:e,sameOrigin:t,request:a,event:s});if(i)return Array.isArray(n=i)&&0===n.length||i.constructor===Object&&0===Object.keys(i).length?n=void 0:"boolean"==typeof i&&(n=void 0),{route:r,params:n}}return{}}};let eR={rscPrefetch:"pages-rsc-prefetch",rsc:"pages-rsc",html:"pages"},ex=[{matcher:/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,handler:new ew({cacheName:"google-fonts-webfonts",plugins:[new eu({maxEntries:4,maxAgeSeconds:31536e3,maxAgeFrom:"last-used"})]})},{matcher:/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,handler:new ey({cacheName:"google-fonts-stylesheets",plugins:[new eu({maxEntries:4,maxAgeSeconds:604800,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,handler:new ey({cacheName:"static-font-assets",plugins:[new eu({maxEntries:4,maxAgeSeconds:604800,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,handler:new ey({cacheName:"static-image-assets",plugins:[new eu({maxEntries:64,maxAgeSeconds:2592e3,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/static.+\.js$/i,handler:new ew({cacheName:"next-static-js-assets",plugins:[new eu({maxEntries:64,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/image\?url=.+$/i,handler:new ey({cacheName:"next-image",plugins:[new eu({maxEntries:64,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:mp3|wav|ogg)$/i,handler:new ew({cacheName:"static-audio-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"}),new ef]})},{matcher:/\.(?:mp4|webm)$/i,handler:new ew({cacheName:"static-video-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"}),new ef]})},{matcher:/\.(?:js)$/i,handler:new ey({cacheName:"static-js-assets",plugins:[new eu({maxEntries:48,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:css|less)$/i,handler:new ey({cacheName:"static-style-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/_next\/data\/.+\/.+\.json$/i,handler:new X({cacheName:"next-data",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\.(?:json|xml|csv)$/i,handler:new X({cacheName:"static-data-assets",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400,maxAgeFrom:"last-used"})]})},{matcher:/\/api\/auth\/.*/,handler:new Y({networkTimeoutSeconds:10})},{matcher:({sameOrigin:e,url:{pathname:t}})=>e&&t.startsWith("/api/"),method:"GET",handler:new X({cacheName:"apis",plugins:[new eu({maxEntries:16,maxAgeSeconds:86400,maxAgeFrom:"last-used"})],networkTimeoutSeconds:10})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.rscPrefetch,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.rsc,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({request:e,url:{pathname:t},sameOrigin:a})=>e.headers.get("Content-Type")?.includes("text/html")&&a&&!t.startsWith("/api/"),handler:new X({cacheName:eR.html,plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({url:{pathname:e},sameOrigin:t})=>t&&!e.startsWith("/api/"),handler:new X({cacheName:"others",plugins:[new eu({maxEntries:32,maxAgeSeconds:86400})]})},{matcher:({sameOrigin:e})=>!e,handler:new X({cacheName:"cross-origin",plugins:[new eu({maxEntries:32,maxAgeSeconds:3600})],networkTimeoutSeconds:10})},{matcher:/.*/i,method:"GET",handler:new Y}],eE={cacheNames:{static:"tour-builder-static-v1",dynamic:"tour-builder-dynamic-v1",assets:"tour-builder-assets-v1"}},eS=[".png",".jpg",".jpeg",".gif",".webp",".svg",".ico",".mp4",".webm",".mov",".mp3",".wav",".ogg",".m4a",".woff",".woff2",".ttf",".eot",".css",".js"],eq=e=>{let t=new URL(e.url);return[".mp4",".webm",".mov"].some(e=>t.pathname.toLowerCase().endsWith(e))},eC=e=>{let t=new URL(e.url);return[".mp3",".wav",".ogg",".m4a",".aac"].some(e=>t.pathname.toLowerCase().endsWith(e))},eN=e=>{try{if(e.includes("/file/download?privateUrl=")){let t=e.match(/privateUrl=([^&]+)/);if(t)return decodeURIComponent(t[1]).replace(/^\/+/,"")}if(e.includes("X-Amz-Signature=")||e.includes("x-amz-signature=")){let t=new URL(e).pathname.split("/").filter(Boolean),a=t.findIndex(e=>"assets"===e);if(-1!==a)return t.slice(a).join("/");if(t.length>1)return t.slice(1).join("/")}if(e.startsWith("assets/"))return e;let t=e.match(/^https?:\/\/[^/]+\.s3\.[^/]+\.amazonaws\.com\/[^/]+\/(assets\/.+)$/);if(t)return t[1].split("?")[0];return null}catch(e){return null}},eD=new Map;setInterval(()=>{eD.clear(),console.log("[SW] Cleared storage path mappings")},36e5);let eT=new ev({precacheEntries:[{'revision':'5986cb6d7c44a0f3bf4f94805b9c5e53','url':'/_next/static/NiPwN7sNglQYUaZU0LBCv/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/NiPwN7sNglQYUaZU0LBCv/_ssgManifest.js'},{'revision':null,'url':'/_next/static/chunks/12142dde-27563c775e4f8cf0.js'},{'revision':null,'url':'/_next/static/chunks/223-5e7611f0ad4bb7f3.js'},{'revision':null,'url':'/_next/static/chunks/3639-bf9412ffc111d3d0.js'},{'revision':null,'url':'/_next/static/chunks/3643-cd854ecad4d47645.js'},{'revision':null,'url':'/_next/static/chunks/3658-51662914717b3efe.js'},{'revision':null,'url':'/_next/static/chunks/400-ac29014eab06ed0b.js'},{'revision':null,'url':'/_next/static/chunks/4166-8f063655eaf7488d.js'},{'revision':null,'url':'/_next/static/chunks/4271-b6985491cfc2640a.js'},{'revision':null,'url':'/_next/static/chunks/4449-bdf4aeb088e38679.js'},{'revision':null,'url':'/_next/static/chunks/4498-33287c5f7f4f906f.js'},{'revision':null,'url':'/_next/static/chunks/4587-c9e5910a896d025b.js'},{'revision':null,'url':'/_next/static/chunks/4653-3f3c69151b7a2ac1.js'},{'revision':null,'url':'/_next/static/chunks/5371-df80deb16401ff8d.js'},{'revision':null,'url':'/_next/static/chunks/5541.e90be83844a6de15.js'},{'revision':null,'url':'/_next/static/chunks/6d2b60a9-eb6c7fd9a57c4f19.js'},{'revision':null,'url':'/_next/static/chunks/7e42aecb-94f8c450c54b9556.js'},{'revision':null,'url':'/_next/static/chunks/8180-20afedac42c2550d.js'},{'revision':null,'url':'/_next/static/chunks/8230-251e23b7a24ff307.js'},{'revision':null,'url':'/_next/static/chunks/8232-06043c4b3efac0d3.js'},{'revision':null,'url':'/_next/static/chunks/8317-4e9c090ccc089653.js'},{'revision':null,'url':'/_next/static/chunks/8366-e9bd140fe53326f8.js'},{'revision':null,'url':'/_next/static/chunks/8628-68b8d2bcffb6ae14.js'},{'revision':null,'url':'/_next/static/chunks/9375.2c053880845a1e8b.js'},{'revision':null,'url':'/_next/static/chunks/9848-799d062feeef8c3c.js'},{'revision':null,'url':'/_next/static/chunks/fa3de7d5.a08b8fcddee4216b.js'},{'revision':null,'url':'/_next/static/chunks/framework-4dea986807dc9d02.js'},{'revision':null,'url':'/_next/static/chunks/main-34a5a9b298cb987a.js'},{'revision':null,'url':'/_next/static/chunks/pages/_error-530d8245847656c6.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/%5Baccess_logsId%5D-b32705e3edb82fb5.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-edit-e57f4b1578eba238.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-list-862001bdbda3ae79.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-new-47edb3b2fe5d671b.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-table-209ff63629e22410.js'},{'revision':null,'url':'/_next/static/chunks/pages/access_logs/access_logs-view-5caa4fc126fce326.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/%5Basset_variantsId%5D-1678a1511c42bfbc.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-edit-bed469a27cd06526.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-list-e2428f84be4f6330.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-new-a9961c26e74176ee.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-table-8e31cbc9806c75e5.js'},{'revision':null,'url':'/_next/static/chunks/pages/asset_variants/asset_variants-view-215e389fc57263d7.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/%5BassetsId%5D-cf12fd35a133f4f0.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-edit-fc0b0f675a8f5c91.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-list-3e66238c012f6058.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-new-c1b74fa86d2e8dca.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-table-cb888498c764cc82.js'},{'revision':null,'url':'/_next/static/chunks/pages/assets/assets-view-4a904823c447ae56.js'},{'revision':null,'url':'/_next/static/chunks/pages/constructor-a6559697c964dceb.js'},{'revision':null,'url':'/_next/static/chunks/pages/dashboard-e90e5fca1401925a.js'},{'revision':null,'url':'/_next/static/chunks/pages/element-type-defaults-463491004a6b0073.js'},{'revision':null,'url':'/_next/static/chunks/pages/element-type-defaults/%5Bid%5D-14686cb7fea95a1d.js'},{'revision':null,'url':'/_next/static/chunks/pages/error-078bf46bb4f4c577.js'},{'revision':null,'url':'/_next/static/chunks/pages/forgot-7c012c8aaac71432.js'},{'revision':null,'url':'/_next/static/chunks/pages/index-f7e2d9108644a6f9.js'},{'revision':null,'url':'/_next/static/chunks/pages/login-a1d399f13c626ebd.js'},{'revision':null,'url':'/_next/static/chunks/pages/p/%5BprojectSlug%5D-6650855e300e018d.js'},{'revision':null,'url':'/_next/static/chunks/pages/p/%5BprojectSlug%5D/stage-9371075805b4ebe5.js'},{'revision':null,'url':'/_next/static/chunks/pages/password-reset-455af6aac51516fe.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/%5BpermissionsId%5D-ba71d986ba5cc8fc.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-edit-40c5c80e903296e5.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-list-e24373283511ad92.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-new-a5aaba465f5e06ba.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-table-3e76d14bc8cebbf6.js'},{'revision':null,'url':'/_next/static/chunks/pages/permissions/permissions-view-bc146f95287c4342.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/%5Bpresigned_url_requestsId%5D-50e2c76c9e560349.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-edit-2353060256e68c18.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-list-e0ab5ebfc0640fc5.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-new-4082b5bae7b63e87.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-table-5ed2aca030d2375c.js'},{'revision':null,'url':'/_next/static/chunks/pages/presigned_url_requests/presigned_url_requests-view-786d8c42729dfae4.js'},{'revision':null,'url':'/_next/static/chunks/pages/privacy-policy-53ea2331c015449b.js'},{'revision':null,'url':'/_next/static/chunks/pages/profile-cf3e76df64b95b2e.js'},{'revision':null,'url':'/_next/static/chunks/pages/project-element-defaults-fba2e7529ad7fa20.js'},{'revision':null,'url':'/_next/static/chunks/pages/project-element-defaults/%5Bid%5D-2a9b9ec0a5ad39c0.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/%5Bproject_audio_tracksId%5D-84505a5d1bdaca57.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-edit-603add372e82e607.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-list-229cafa01e2c6edc.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-new-c6f8092a6afca5fe.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-table-f6dd77f2938fa592.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_audio_tracks/project_audio_tracks-view-9a0e90abaa80f5c6.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/%5Bproject_membershipsId%5D-a09978b19d912f31.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-edit-357c7f7cdfb521bf.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-list-62c341e178d0d37b.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-new-4892d0814688c548.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-table-fb80cbce9701ca85.js'},{'revision':null,'url':'/_next/static/chunks/pages/project_memberships/project_memberships-view-a49b17115676b9d6.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/%5BprojectsId%5D-aff87e4f3b8c7859.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-edit-f3f4a82c22449e83.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-list-63780043574a7df1.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-new-620fb97ab7b8c2ae.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-table-09a5ba65059fe42c.js'},{'revision':null,'url':'/_next/static/chunks/pages/projects/projects-view-6ab88073d01e7234.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/%5Bpublish_eventsId%5D-1221da0d818c5fa1.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-edit-b140f5347d843d6c.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-list-9ba9952b20f103ab.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-new-7812adf22af8012d.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-table-f05bab29903c9752.js'},{'revision':null,'url':'/_next/static/chunks/pages/publish_events/publish_events-view-989df6b2ae0f52ff.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/%5Bpwa_cachesId%5D-063158e045bb255c.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-edit-74c4434dad6ae76f.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-list-e56bc5e48062975e.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-new-427aedfbd31041cc.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-table-149653ca0aa91341.js'},{'revision':null,'url':'/_next/static/chunks/pages/pwa_caches/pwa_caches-view-d4cd0ed163eeef15.js'},{'revision':null,'url':'/_next/static/chunks/pages/register-3fcdd8e6389f7ef5.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/%5BrolesId%5D-83556899e7280c97.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-edit-07233f706c8faaea.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-list-f67615ce75c49a4f.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-new-efab36392a595372.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-table-ab1f8a0efc39db9c.js'},{'revision':null,'url':'/_next/static/chunks/pages/roles/roles-view-46eac6512efffec1.js'},{'revision':null,'url':'/_next/static/chunks/pages/search-d8f3264d25ac0bdb.js'},{'revision':null,'url':'/_next/static/chunks/pages/terms-of-use-7d00698d6183bde4.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/%5Btour_pagesId%5D-88b25d5abb8c9d7a.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-edit-988e4db25855470e.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-list-48c842a9c2971ded.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-new-2b06e13e70530cc0.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-table-4633b5d51a17fc5e.js'},{'revision':null,'url':'/_next/static/chunks/pages/tour_pages/tour_pages-view-ddbb70192d378851.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/%5BusersId%5D-38909f3f9c0cffea.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-edit-8634a664d2794a3f.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-list-31ccd4c2c1f8cc75.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-new-7ef52cd8dafbb885.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-table-de2a889aafd936e9.js'},{'revision':null,'url':'/_next/static/chunks/pages/users/users-view-436d3bcdfad89528.js'},{'revision':null,'url':'/_next/static/chunks/pages/verify-email-f94b54604550410c.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':null,'url':'/_next/static/chunks/webpack-948191860fdb5996.js'},{'revision':null,'url':'/_next/static/css/3b16777e299e7d15.css'},{'revision':null,'url':'/_next/static/css/715be398208dca58.css'},{'revision':null,'url':'/_next/static/css/de6fa09b8a0934d1.css'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-ext-wdth-normal.a718fc63.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-ext-wght-normal.7db92424.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-wdth-normal.68c3c527.woff2'},{'revision':null,'url':'/_next/static/media/instrument-sans-latin-wght-normal.ae05c57c.woff2'},{'revision':'43778b43fb039fdd5b0510561dc952ac','url':'/assets/vm-shot-2026-03-17T04-16-09-161Z.jpg'},{'revision':'3395d49fe2b96221471b4db0f5caf9e7','url':'/assets/vm-shot-2026-03-17T04-19-03-565Z.jpg'},{'revision':'62127cd8f85821f3e570a377a8a7e14b','url':'/assets/vm-shot-2026-03-17T04-36-56-252Z.jpg'},{'revision':'145c2d7e4ef298391258c6d8a8aaaece','url':'/assets/vm-shot-2026-03-17T04-45-14-111Z.jpg'},{'revision':'c6aae6521f08c847e370764d1c9c613d','url':'/assets/vm-shot-2026-03-17T04-50-58-546Z.jpg'},{'revision':'bf90fb959c1d418b01eb7e55de6672d5','url':'/assets/vm-shot-2026-03-19T06-12-36-229Z.jpg'},{'revision':'73de6cac0695249f4e59ce778a8e6e74','url':'/assets/vm-shot-2026-03-19T06-13-19-234Z.jpg'},{'revision':'bf90fb959c1d418b01eb7e55de6672d5','url':'/assets/vm-shot-2026-03-19T06-13-54-729Z.jpg'},{'revision':'ca6cbfcc74b52f00eef0f2adc8e65456','url':'/assets/vm-shot-2026-03-24T14-29-20-260Z.jpg'},{'revision':'0957c5365895c5ba31b10a84f4e45929','url':'/data-sources/clients.json'},{'revision':'5703d42f7838705ecf87510c4032b20c','url':'/data-sources/history.json'},{'revision':'5404a85badad8210a634ce41bb511545','url':'/favicon.svg'},{'revision':'fbe5fc5322bbd6ffda8bff4441b6bfa6','url':'/fonts/MapleMedium.otf'},{'revision':'508520242399a6b1fec65430901f4e6f','url':'/locales/de/common.json'},{'revision':'0a64739b954a93627749ffcb846fceaa','url':'/locales/en/common.json'},{'revision':'34715e25a3bcbab44f84232ce082d2ee','url':'/locales/es/common.json'},{'revision':'772a72f35589c06bdd8d9179c86459e6','url':'/locales/fr/common.json'},{'revision':'c67326edc61e0b5cf87e509ea7553466','url':'/manifest.json'},{'revision':'1254c9c72aa64724c203d26278712800','url':'/offline.html'}],skipWaiting:!0,clientsClaim:!0,navigationPreload:!0,runtimeCaching:[{matcher:e=>{let{request:t}=e;return"1"===new URL(t.url).searchParams.get("sw-bypass")},handler:new Y},{matcher:e=>{let{request:t}=e,a=new URL(t.url);return"image"===t.destination||"font"===t.destination||[".css",".js",".woff",".woff2"].some(e=>a.pathname.endsWith(e))},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);return s?(console.log("[SW] Using storagePath for static asset ".concat(a,":"),s.slice(-40)),new Request(s)):t},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{request:t}=e;return eq(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);if(s){let e=eD.get(s);return e?(console.log("[SW] Using storageKey for video ".concat(a,":"),e.slice(-40)),new Request(e)):(console.log("[SW] Using storagePath for video ".concat(a,":"),s.slice(-40)),new Request(s))}return t},cachedResponseWillBeUsed:async e=>{let{cachedResponse:t,request:a}=e;if(!t)return null;let s=a.headers.get("range");if(!s)return t;let r=s.match(/bytes=(\d+)-(\d*)/);if(!r)return t;let n=parseInt(r[1],10),i=r[2]?parseInt(r[2],10):void 0,c=await t.blob(),o=void 0!==i?c.slice(n,i+1):c.slice(n);return new Response(o,{status:206,statusText:"Partial Content",headers:{"Content-Type":t.headers.get("Content-Type")||"video/mp4","Content-Length":String(o.size),"Content-Range":"bytes ".concat(n,"-").concat(void 0!==i?i:c.size-1,"/").concat(c.size),"Accept-Ranges":"bytes"}})},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{request:t}=e;return eC(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);if(s){let e=eD.get(s);return e?(console.log("[SW] Using storageKey for audio ".concat(a,":"),e.slice(-40)),new Request(e)):(console.log("[SW] Using storagePath for audio ".concat(a,":"),s.slice(-40)),new Request(s))}return t},cachedResponseWillBeUsed:async e=>{let{cachedResponse:t,request:a}=e;if(!t)return null;let s=a.headers.get("range");if(!s)return t;let r=s.match(/bytes=(\d+)-(\d*)/);if(!r)return t;let n=parseInt(r[1],10),i=r[2]?parseInt(r[2],10):void 0,c=await t.blob(),o=void 0!==i?c.slice(n,i+1):c.slice(n);return new Response(o,{status:206,statusText:"Partial Content",headers:{"Content-Type":t.headers.get("Content-Type")||"audio/mpeg","Content-Length":String(o.size),"Content-Range":"bytes ".concat(n,"-").concat(void 0!==i?i:c.size-1,"/").concat(c.size),"Accept-Ranges":"bytes"}})},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},{matcher:e=>{let{url:t}=e;return t.pathname.startsWith("/api/")},handler:new X({cacheName:"api-cache",networkTimeoutSeconds:10})},{matcher:e=>{let{request:t}=e;return(e=>{let t=new URL(e.url);return(!t.pathname.startsWith("/api/")||!!t.pathname.includes("/file/download"))&&!!(eS.some(e=>t.pathname.toLowerCase().endsWith(e))||t.pathname.includes("/file/download")||t.hostname.includes("amazonaws.com")||t.hostname.includes("cloudfront.net"))})(t)&&!eq(t)&&!eC(t)},handler:new ew({cacheName:eE.cacheNames.assets,plugins:[{cacheKeyWillBeUsed:async e=>{let{request:t,mode:a}=e,s=eN(t.url);return s?(console.log("[SW] Using storagePath for dynamic asset ".concat(a,":"),s.slice(-40)),new Request(s)):t},cacheWillUpdate:async e=>{let{response:t}=e;return t&&200===t.status?t:null}}]})},...ex]});self.addEventListener("message",e=>{let{type:t,payload:a}=e.data||{};switch(t){case"REGISTER_CACHE_URL":if(null==a?void 0:a.storageKey){let e=a.presignedUrl&&eN(a.presignedUrl)||a.storageKey;eD.set(e,a.storageKey),console.log("[SW] Registered storage path for caching",{storagePath:e.slice(-50),storageKey:a.storageKey.slice(-50)})}break;case"CLEAR_URL_MAPPINGS":eD.clear(),console.log("[SW] Storage path mappings cleared");break;case"CACHE_ASSETS":Array.isArray(null==a?void 0:a.urls)&&e.waitUntil(caches.open(eE.cacheNames.assets).then(e=>Promise.all(a.urls.map(t=>fetch(t).then(a=>{if(200===a.status)return e.put(t,a)}).catch(e=>{console.warn("[SW] Failed to cache asset:",t,e)})))));break;case"CACHE_VIDEO_CHUNK":(null==a?void 0:a.url)&&(null==a?void 0:a.chunk)&&e.waitUntil(caches.open(eE.cacheNames.assets).then(e=>{let t=new Response(a.chunk,{headers:{"Content-Type":a.contentType||"video/mp4","Content-Length":String(a.chunk.byteLength)}});return e.put(a.url,t)}));break;case"CLEAR_CACHE":eD.clear(),e.waitUntil(Promise.all([caches.delete(eE.cacheNames.dynamic),caches.delete(eE.cacheNames.assets)]).then(()=>{console.log("[SW] Caches and storage path mappings cleared")}));break;case"GET_CACHE_STATUS":e.waitUntil(caches.open(eE.cacheNames.assets).then(t=>t.keys().then(t=>{let a=e.source;null==a||a.postMessage({type:"CACHE_STATUS",payload:{cachedCount:t.length,urls:t.map(e=>e.url)}})})));break;case"SKIP_WAITING":self.skipWaiting()}}),eT.addEventListeners(),console.log("[SW] Serwist service worker loaded")})(); \ No newline at end of file diff --git a/frontend/src/components/Constructor/CanvasElement.tsx b/frontend/src/components/Constructor/CanvasElement.tsx index 3852adb..1b50c94 100644 --- a/frontend/src/components/Constructor/CanvasElement.tsx +++ b/frontend/src/components/Constructor/CanvasElement.tsx @@ -14,7 +14,7 @@ import { useAppearAnimation } from '../../hooks/useAppearAnimation'; import { buildTransitionStyle, hasAnyEffects, - extractEffectProperties, + type ElementEffectProperties, } from '../../lib/elementEffects'; import type { CanvasElement as CanvasElementType } from '../../types/constructor'; import type { ResolvedTransitionSettings } from '../../types/transition'; @@ -57,38 +57,37 @@ const CanvasElement: React.FC = ({ pageTransitionSettings, preloadCache, }) => { - // Extract effect properties from element using helper - const effectProperties = extractEffectProperties( - element as unknown as Record, + // Extract effect properties from element + const effectProperties: Partial = { + appearAnimation: element.appearAnimation, + appearAnimationDuration: element.appearAnimationDuration, + appearAnimationEasing: element.appearAnimationEasing, + hoverScale: element.hoverScale, + hoverOpacity: element.hoverOpacity, + hoverBackgroundColor: element.hoverBackgroundColor, + hoverColor: element.hoverColor, + hoverBoxShadow: element.hoverBoxShadow, + hoverTransitionDuration: element.hoverTransitionDuration, + focusScale: element.focusScale, + focusOpacity: element.focusOpacity, + focusOutline: element.focusOutline, + focusBoxShadow: element.focusBoxShadow, + activeScale: element.activeScale, + activeOpacity: element.activeOpacity, + activeBackgroundColor: element.activeBackgroundColor, + }; + + // Use effects hook - disabled in edit mode to avoid interfering with dragging + const { effectStyle, eventHandlers } = useElementEffects( + isEditMode ? {} : effectProperties, ); // Use appear animation hook (removes animation after completion to unlock properties) - const { animationStyle, onAnimationEnd, hasAnimationEnded } = - useAppearAnimation( - effectProperties, - element.id, // Reset animation when element changes - ); - - // Use effects hook - disabled in edit mode to avoid interfering with dragging - const { effectStyle, eventHandlers, onPersistClick } = useElementEffects( - isEditMode ? {} : effectProperties, - isEditMode - ? undefined - : { - resetKey: element.id, - appearAnimationCompleted: - hasAnimationEnded || !effectProperties.appearAnimation, - }, + const { animationStyle, onAnimationEnd } = useAppearAnimation( + effectProperties, + element.id, // Reset animation when element changes ); - // Combined click handler (only persist click in preview mode) - const handleClick = () => { - if (!isEditMode) { - onPersistClick(); - } - onClick(); - }; - // Clamp position to canvas bounds (0-100%) const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max); @@ -159,7 +158,7 @@ const CanvasElement: React.FC = ({ className='absolute cursor-pointer' style={positionStyle} onMouseDown={isEditMode ? onMouseDown : undefined} - onClick={handleClick} + onClick={onClick} onKeyDown={handleKeyDown} {...(!isEditMode ? eventHandlers : {})} > @@ -183,11 +182,9 @@ const CanvasElement: React.FC = ({ className='absolute cursor-pointer' style={combinedStyle} onMouseDown={isEditMode ? onMouseDown : undefined} - onClick={handleClick} + onClick={onClick} onKeyDown={handleKeyDown} - onAnimationEnd={ - effectProperties.appearAnimation ? onAnimationEnd : undefined - } + onAnimationEnd={effectProperties.appearAnimation ? onAnimationEnd : undefined} {...(!isEditMode ? eventHandlers : {})} > {content} diff --git a/frontend/src/components/Constructor/CreatePageModal.tsx b/frontend/src/components/Constructor/CreatePageModal.tsx index ad3e686..f2aa2f9 100644 --- a/frontend/src/components/Constructor/CreatePageModal.tsx +++ b/frontend/src/components/Constructor/CreatePageModal.tsx @@ -85,8 +85,8 @@ const CreatePageModal: React.FC = ({ return ( = ({ >
handleNameChange(e.target.value)} - placeholder='Enter page name' - className='w-full border border-gray-300 rounded px-3 py-2 bg-white dark:bg-dark-800' + placeholder="Enter page name" + className="w-full border border-gray-300 rounded px-3 py-2 bg-white dark:bg-dark-800" autoFocus maxLength={255} /> {(nameError || nameValidationError) && ( -

+

{nameError || nameValidationError}

)} diff --git a/frontend/src/components/Constructor/ElementEditorPanel.tsx b/frontend/src/components/Constructor/ElementEditorPanel.tsx index 4b26c91..4bc2e17 100644 --- a/frontend/src/components/Constructor/ElementEditorPanel.tsx +++ b/frontend/src/components/Constructor/ElementEditorPanel.tsx @@ -764,21 +764,6 @@ export function ElementEditorPanel({ activeOpacity: selectedElement.activeOpacity || '', activeBackgroundColor: selectedElement.activeBackgroundColor || '', - // Hover reveal values - hoverReveal: selectedElement.hoverReveal ? 'true' : '', - hoverRevealInitialOpacity: - selectedElement.hoverRevealInitialOpacity || '', - hoverRevealTargetOpacity: - selectedElement.hoverRevealTargetOpacity || '', - hoverRevealDuration: - selectedElement.hoverRevealDuration || '', - hoverRevealDelay: selectedElement.hoverRevealDelay || '', - hoverRevealPersist: selectedElement.hoverRevealPersist - ? 'true' - : '', - hoverPersistOnClick: selectedElement.hoverPersistOnClick - ? 'true' - : '', // Slide transition values (gallery/carousel) slideTransitionType: selectedElement.type === 'gallery' @@ -873,15 +858,6 @@ export function ElementEditorPanel({ value || undefined, }); } - } else if ( - prop === 'hoverReveal' || - prop === 'hoverRevealPersist' || - prop === 'hoverPersistOnClick' - ) { - // Boolean properties - convert 'true' string to boolean - updateSelectedElement({ - [prop]: value === 'true', - }); } else { // Standard effect properties updateSelectedElement({ diff --git a/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx b/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx index a8539ee..21574e2 100644 --- a/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx +++ b/frontend/src/components/ElementSettings/EffectsSettingsSection.tsx @@ -114,98 +114,6 @@ const EffectsSettingsSection: React.FC = ({ placeholder='e.g. 0.2' /> - - - -
- - {/* Hover Reveal Subsection */} -
-

Hover Reveal

-

- Element starts hidden and reveals on hover. Useful for hints or - hidden navigation. -

-
- - - - - - - - - onChange('hoverRevealInitialOpacity', e.target.value) - } - placeholder='0' - disabled={values.hoverReveal !== 'true'} - /> - - - - onChange('hoverRevealTargetOpacity', e.target.value) - } - placeholder='1' - disabled={values.hoverReveal !== 'true'} - /> - - - - onChange('hoverRevealDuration', e.target.value) - } - placeholder='0.3' - disabled={values.hoverReveal !== 'true'} - /> - - - onChange('hoverRevealDelay', e.target.value)} - placeholder='0' - disabled={values.hoverReveal !== 'true'} - /> - -
diff --git a/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx b/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx index e1dc7fa..31c6e39 100644 --- a/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx +++ b/frontend/src/components/ElementSettings/EffectsSettingsSectionCompact.tsx @@ -152,108 +152,6 @@ const EffectsSettingsSectionCompact: React.FC< placeholder='0.2' /> -
- -
- - {/* Hover Reveal Subsection */} -
-

- Hover Reveal -

-
-
- -
-
- - - onChange('hoverRevealInitialOpacity', e.target.value) - } - placeholder='0' - disabled={values.hoverReveal !== 'true'} - /> -
-
- - - onChange('hoverRevealTargetOpacity', e.target.value) - } - placeholder='1' - disabled={values.hoverReveal !== 'true'} - /> -
-
- - onChange('hoverRevealDuration', e.target.value)} - placeholder='0.3' - disabled={values.hoverReveal !== 'true'} - /> -
-
- - onChange('hoverRevealDelay', e.target.value)} - placeholder='0' - disabled={values.hoverReveal !== 'true'} - /> -
-
- -
diff --git a/frontend/src/components/ElementSettings/types.ts b/frontend/src/components/ElementSettings/types.ts index 5b0a7db..028caf2 100644 --- a/frontend/src/components/ElementSettings/types.ts +++ b/frontend/src/components/ElementSettings/types.ts @@ -53,15 +53,6 @@ export interface EffectsSettingsFormValues { activeScale?: string; activeOpacity?: string; activeBackgroundColor?: string; - // Hover Reveal - hoverReveal?: string; - hoverRevealInitialOpacity?: string; - hoverRevealTargetOpacity?: string; - hoverRevealDuration?: string; - hoverRevealDelay?: string; - hoverRevealPersist?: string; - // Persist hover effects after click - hoverPersistOnClick?: string; // Slide transition override (Gallery/Carousel only) // These override page transition settings for this element's slides slideTransitionType?: string; diff --git a/frontend/src/components/ElementSettings/useElementSettingsForm.ts b/frontend/src/components/ElementSettings/useElementSettingsForm.ts index 1c7de09..0806505 100644 --- a/frontend/src/components/ElementSettings/useElementSettingsForm.ts +++ b/frontend/src/components/ElementSettings/useElementSettingsForm.ts @@ -87,16 +87,6 @@ interface FormState { activeOpacity: string; activeBackgroundColor: string; - // Hover Reveal settings - hoverReveal: boolean; - hoverRevealInitialOpacity: string; - hoverRevealTargetOpacity: string; - hoverRevealDuration: string; - hoverRevealDelay: string; - hoverRevealPersist: boolean; - // Persist hover effects after click - hoverPersistOnClick: boolean; - // Navigation settings iconUrl: string; navLabel: string; @@ -192,14 +182,6 @@ const initialState: FormState = { activeScale: '', activeOpacity: '', activeBackgroundColor: '', - // Hover Reveal settings - hoverReveal: false, - hoverRevealInitialOpacity: '', - hoverRevealTargetOpacity: '', - hoverRevealDuration: '', - hoverRevealDelay: '', - hoverRevealPersist: false, - hoverPersistOnClick: false, iconUrl: '', navLabel: '', navLabelFontFamily: '', @@ -302,18 +284,6 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) { activeScale: String(settings.activeScale || ''), activeOpacity: String(settings.activeOpacity || ''), activeBackgroundColor: String(settings.activeBackgroundColor || ''), - // Hover Reveal settings - hoverReveal: Boolean(settings.hoverReveal), - hoverRevealInitialOpacity: String( - settings.hoverRevealInitialOpacity || '', - ), - hoverRevealTargetOpacity: String( - settings.hoverRevealTargetOpacity || '', - ), - hoverRevealDuration: String(settings.hoverRevealDuration || ''), - hoverRevealDelay: String(settings.hoverRevealDelay || ''), - hoverRevealPersist: Boolean(settings.hoverRevealPersist), - hoverPersistOnClick: Boolean(settings.hoverPersistOnClick), appearDelaySec: String(settings.appearDelaySec ?? 0), appearDurationSec: settings.appearDurationSec === null || @@ -452,14 +422,6 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) { activeScale: state.activeScale, activeOpacity: state.activeOpacity, activeBackgroundColor: state.activeBackgroundColor, - // Convert booleans to strings for form compatibility - hoverReveal: state.hoverReveal ? 'true' : '', - hoverRevealInitialOpacity: state.hoverRevealInitialOpacity, - hoverRevealTargetOpacity: state.hoverRevealTargetOpacity, - hoverRevealDuration: state.hoverRevealDuration, - hoverRevealDelay: state.hoverRevealDelay, - hoverRevealPersist: state.hoverRevealPersist ? 'true' : '', - hoverPersistOnClick: state.hoverPersistOnClick ? 'true' : '', }; }, [state]); @@ -662,30 +624,6 @@ export function useElementSettingsForm(options: UseElementSettingsFormOptions) { if (activeBackgroundColorValue) settings.activeBackgroundColor = activeBackgroundColorValue; - // Hover Reveal properties (booleans saved directly, strings trimmed) - if (state.hoverReveal) settings.hoverReveal = true; - const hoverRevealInitialOpacityValue = toOptionalTrimmed( - state.hoverRevealInitialOpacity, - ); - const hoverRevealTargetOpacityValue = toOptionalTrimmed( - state.hoverRevealTargetOpacity, - ); - const hoverRevealDurationValue = toOptionalTrimmed( - state.hoverRevealDuration, - ); - const hoverRevealDelayValue = toOptionalTrimmed(state.hoverRevealDelay); - - if (hoverRevealInitialOpacityValue) - settings.hoverRevealInitialOpacity = hoverRevealInitialOpacityValue; - if (hoverRevealTargetOpacityValue) - settings.hoverRevealTargetOpacity = hoverRevealTargetOpacityValue; - if (hoverRevealDurationValue) - settings.hoverRevealDuration = hoverRevealDurationValue; - if (hoverRevealDelayValue) - settings.hoverRevealDelay = hoverRevealDelayValue; - if (state.hoverRevealPersist) settings.hoverRevealPersist = true; - if (state.hoverPersistOnClick) settings.hoverPersistOnClick = true; - // Navigation type settings if (isNavigationType) { settings.iconUrl = state.iconUrl.trim(); diff --git a/frontend/src/components/RuntimeElement.tsx b/frontend/src/components/RuntimeElement.tsx index ec79131..ededabe 100644 --- a/frontend/src/components/RuntimeElement.tsx +++ b/frontend/src/components/RuntimeElement.tsx @@ -13,7 +13,7 @@ import { useAppearAnimation } from '../hooks/useAppearAnimation'; import { buildTransitionStyle, hasAnyEffects, - extractEffectProperties, + type ElementEffectProperties, } from '../lib/elementEffects'; import type { CanvasElement } from '../types/constructor'; import type { ResolvedTransitionSettings } from '../types/transition'; @@ -52,34 +52,35 @@ const RuntimeElement: React.FC = ({ const yPercent = clamp(element.yPercent ?? 50, 0, 100); const rotation = element.rotation ?? 0; - // Extract effect properties from element using helper - const effectProperties = extractEffectProperties( - element as unknown as Record, - ); + // Extract effect properties from element + const effectProperties: Partial = { + appearAnimation: element.appearAnimation, + appearAnimationDuration: element.appearAnimationDuration, + appearAnimationEasing: element.appearAnimationEasing, + hoverScale: element.hoverScale, + hoverOpacity: element.hoverOpacity, + hoverBackgroundColor: element.hoverBackgroundColor, + hoverColor: element.hoverColor, + hoverBoxShadow: element.hoverBoxShadow, + hoverTransitionDuration: element.hoverTransitionDuration, + focusScale: element.focusScale, + focusOpacity: element.focusOpacity, + focusOutline: element.focusOutline, + focusBoxShadow: element.focusBoxShadow, + activeScale: element.activeScale, + activeOpacity: element.activeOpacity, + activeBackgroundColor: element.activeBackgroundColor, + }; + + // Use effects hook for interactive states + const { effectStyle, eventHandlers } = useElementEffects(effectProperties); // Use appear animation hook (removes animation after completion to unlock properties) - const { animationStyle, onAnimationEnd, hasAnimationEnded } = - useAppearAnimation( - effectProperties, - element.id, // Reset animation when element changes - ); - - // Use effects hook for interactive states with animation coordination - const { effectStyle, eventHandlers, onPersistClick } = useElementEffects( + const { animationStyle, onAnimationEnd } = useAppearAnimation( effectProperties, - { - resetKey: element.id, // Reset reveal on element change - appearAnimationCompleted: - hasAnimationEnded || !effectProperties.appearAnimation, - }, + element.id, // Reset animation when element changes ); - // Combined click handler - const handleClick = () => { - onPersistClick(); // Toggle persistence state - onClick(); // Original navigation action - }; - // Build base position style let positionStyle: React.CSSProperties = { left: `${xPercent}%`, @@ -128,7 +129,7 @@ const RuntimeElement: React.FC = ({
@@ -148,11 +149,9 @@ const RuntimeElement: React.FC = ({
{content} diff --git a/frontend/src/components/RuntimePresentation.tsx b/frontend/src/components/RuntimePresentation.tsx index 5bb6669..e4be85c 100644 --- a/frontend/src/components/RuntimePresentation.tsx +++ b/frontend/src/components/RuntimePresentation.tsx @@ -286,11 +286,7 @@ export default function RuntimePresentation({ } = navState; // Integrate useTransitionPlayback hook for smooth transitions (matches Constructor pattern) - const { - isBuffering, - isVideoReady, - phase: transitionPhase, - } = useTransitionPlayback({ + const { isBuffering, isVideoReady, phase: transitionPhase } = useTransitionPlayback({ videoRef: transitionVideoRef, transition: transitionPreview ? { @@ -553,6 +549,7 @@ export default function RuntimePresentation({ // navShowElements: true when phase is 'idle' or 'fading_in' const areTransitionsReady = preloadOrchestrator?.areTransitionsReady ?? true; + const handleElementClick = useCallback( (element: CanvasElement) => { // Block navigation while transition is actively playing or buffering @@ -852,8 +849,7 @@ export default function RuntimePresentation({ preloadOrchestrator ? { getReadyBlob: preloadOrchestrator.getReadyBlob, - getCachedBlobUrl: - preloadOrchestrator.getCachedBlobUrl, + getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl, } : undefined } diff --git a/frontend/src/components/TourFlowManager.tsx b/frontend/src/components/TourFlowManager.tsx index 882f83d..a379d42 100644 --- a/frontend/src/components/TourFlowManager.tsx +++ b/frontend/src/components/TourFlowManager.tsx @@ -901,7 +901,9 @@ const TourFlowManager = () => { maxLength={255} /> {nameValidationError && ( -

{nameValidationError}

+

+ {nameValidationError} +

)}
@@ -911,9 +913,7 @@ const TourFlowManager = () => { title='Edit page name' buttonColor='info' buttonLabel={isSavingPageName ? 'Saving...' : 'Save'} - isConfirmDisabled={ - Boolean(editNameValidationError) || isSavingPageName - } + isConfirmDisabled={Boolean(editNameValidationError) || isSavingPageName} isActive={isEditPageModalActive} onConfirm={handleSavePageName} onCancel={isSavingPageName ? undefined : closeEditPageModal} @@ -964,10 +964,9 @@ const TourFlowManager = () => { const canDelete = entry.type === 'page' ? canDeletePage : canDeleteTransition; const isDeleting = deletingId === entry.id; - const pageData = - entry.type === 'page' - ? pages.find((p) => p.id === entry.id) - : null; + const pageData = entry.type === 'page' + ? pages.find((p) => p.id === entry.id) + : null; return (
  • diff --git a/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx b/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx index 850bd5e..7ce4186 100644 --- a/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx +++ b/frontend/src/components/UiElements/elements/VideoPlayerElement.tsx @@ -27,7 +27,11 @@ const VideoPlayerElement: React.FC = ({ className, style, }) => { - const { videoRef, resolvedUrl, isBuffering } = useVideoPlayer({ + const { + videoRef, + resolvedUrl, + isBuffering, + } = useVideoPlayer({ sourceUrl: element.mediaUrl, preloadCache, autoplay: Boolean(element.mediaAutoplay), diff --git a/frontend/src/hooks/useAppearAnimation.ts b/frontend/src/hooks/useAppearAnimation.ts index 289a481..e1ad744 100644 --- a/frontend/src/hooks/useAppearAnimation.ts +++ b/frontend/src/hooks/useAppearAnimation.ts @@ -26,8 +26,6 @@ interface UseAppearAnimationResult { animationStyle: CSSProperties; /** Handler to attach to element's onAnimationEnd */ onAnimationEnd: AnimationEventHandler; - /** Whether the appear animation has completed */ - hasAnimationEnded: boolean; } /** @@ -103,6 +101,5 @@ export function useAppearAnimation( return { animationStyle, onAnimationEnd, - hasAnimationEnded, }; } diff --git a/frontend/src/hooks/useCSVHandling.ts b/frontend/src/hooks/useCSVHandling.ts index d76034e..efa9a2e 100644 --- a/frontend/src/hooks/useCSVHandling.ts +++ b/frontend/src/hooks/useCSVHandling.ts @@ -76,8 +76,7 @@ export function useCSVHandling( responseType: 'blob', }); - const contentType = - (response.headers['content-type'] as string) || 'text/csv'; + const contentType = (response.headers['content-type'] as string) || 'text/csv'; const blob = new Blob([response.data], { type: contentType }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); diff --git a/frontend/src/hooks/useElementEffects.ts b/frontend/src/hooks/useElementEffects.ts index 7c2b0f1..9839349 100644 --- a/frontend/src/hooks/useElementEffects.ts +++ b/frontend/src/hooks/useElementEffects.ts @@ -1,23 +1,21 @@ /** * useElementEffects Hook * - * Manages element interactive effects (hover, focus, active, reveal) at runtime. + * Manages element interactive effects (hover, focus, active) at runtime. * Since CSS pseudo-classes don't work with inline styles, this hook * handles state-based style application via JavaScript events. */ -import { useCallback, useState, useEffect } from 'react'; +import { useCallback, useState } from 'react'; import type { CSSProperties } from 'react'; import { buildHoverStyle, buildFocusStyle, buildActiveStyle, buildTransitionStyle, - buildHoverRevealStyle, hasHoverEffects, hasFocusEffects, hasActiveEffects, - hasHoverReveal, type ElementEffectProperties, } from '../lib/elementEffects'; @@ -25,15 +23,6 @@ interface ElementEffectState { isHovered: boolean; isFocused: boolean; isActive: boolean; - isRevealed: boolean; - isClickPersisted: boolean; -} - -interface UseElementEffectsOptions { - /** Key to reset reveal state (e.g., element.id or page slug) */ - resetKey?: string | number; - /** Whether appear animation has completed (to coordinate with reveal) */ - appearAnimationCompleted?: boolean; } interface UseElementEffectsResult { @@ -47,22 +36,17 @@ interface UseElementEffectsResult { onBlur: () => void; onMouseDown: () => void; onMouseUp: () => void; - onTouchStart: () => void; - onTouchEnd: () => void; }; - /** Call this in onClick to toggle hover persistence (if enabled) */ - onPersistClick: () => void; } /** * Hook for managing element interactive effects. * * @param effects - Element effect properties - * @param options - Optional configuration (resetKey, appearAnimationCompleted) * @returns Object with effectStyle and eventHandlers * * @example - * const { effectStyle, eventHandlers } = useElementEffects(element, { resetKey: element.id }); + * const { effectStyle, eventHandlers } = useElementEffects(element); * return ( *
    * {content} @@ -71,34 +55,16 @@ interface UseElementEffectsResult { */ export function useElementEffects( effects: Partial, - options?: UseElementEffectsOptions, ): UseElementEffectsResult { - const { resetKey, appearAnimationCompleted = true } = options ?? {}; - const [state, setState] = useState({ isHovered: false, isFocused: false, isActive: false, - isRevealed: false, - isClickPersisted: false, }); - // Reset reveal state and click-persisted state when resetKey changes (e.g., page navigation) - useEffect(() => { - setState((prev) => ({ - ...prev, - isRevealed: false, - isClickPersisted: false, - })); - }, [resetKey]); - const onMouseEnter = useCallback(() => { - setState((prev) => ({ - ...prev, - isHovered: true, - isRevealed: effects.hoverReveal ? true : prev.isRevealed, - })); - }, [effects.hoverReveal]); + setState((prev) => ({ ...prev, isHovered: true })); + }, []); const onMouseLeave = useCallback(() => { setState((prev) => ({ ...prev, isHovered: false, isActive: false })); @@ -120,74 +86,22 @@ export function useElementEffects( setState((prev) => ({ ...prev, isActive: false })); }, []); - // Touch handlers for mobile devices - const onTouchStart = useCallback(() => { - setState((prev) => ({ - ...prev, - isHovered: true, - isRevealed: effects.hoverReveal ? true : prev.isRevealed, - })); - }, [effects.hoverReveal]); - - const onTouchEnd = useCallback(() => { - // For hover reveal, keep visibility on touch (like persist mode) - if (!effects.hoverReveal) { - setState((prev) => ({ ...prev, isHovered: false, isActive: false })); - } - }, [effects.hoverReveal]); - - // Click handler for toggling hover persistence - const onClick = useCallback(() => { - if (effects.hoverPersistOnClick) { - // Toggle: first click persists, second click removes, third persists, etc. - setState((prev) => ({ - ...prev, - isClickPersisted: !prev.isClickPersisted, - })); - } - }, [effects.hoverPersistOnClick]); - // Build effective style based on current state - // Priority: active > focus > hover reveal > hover > base + // Priority: active > focus > hover > base let effectStyle: CSSProperties = {}; // Add transition for smooth effect changes if ( hasHoverEffects(effects) || hasFocusEffects(effects) || - hasActiveEffects(effects) || - hasHoverReveal(effects) + hasActiveEffects(effects) ) { effectStyle = { ...effectStyle, ...buildTransitionStyle(effects) }; } - // Apply hover reveal style (controls opacity for reveal elements) - // Only apply initial opacity AFTER appear animation completes to avoid conflict - if (hasHoverReveal(effects) && appearAnimationCompleted) { - // With persist OR click-persisted: stays visible - // Without: only visible while hovering - const shouldShow = - effects.hoverRevealPersist || state.isClickPersisted - ? state.isRevealed || state.isClickPersisted - : state.isHovered; - effectStyle = { - ...effectStyle, - ...buildHoverRevealStyle(effects, shouldShow), - }; - } - - // Apply hover effects when hovered OR click-persisted (but not active) - // Skip opacity when hoverReveal is active (reveal controls it) - const shouldApplyHover = state.isHovered || state.isClickPersisted; - if (shouldApplyHover && !state.isActive && hasHoverEffects(effects)) { - const hoverStyle = buildHoverStyle(effects); - if (hasHoverReveal(effects)) { - // Exclude opacity from hover style when reveal is active - const { opacity, ...restHoverStyle } = hoverStyle; - effectStyle = { ...effectStyle, ...restHoverStyle }; - } else { - effectStyle = { ...effectStyle, ...hoverStyle }; - } + // Apply hover effects when hovered (but not active) + if (state.isHovered && !state.isActive && hasHoverEffects(effects)) { + effectStyle = { ...effectStyle, ...buildHoverStyle(effects) }; } // Apply focus effects when focused @@ -209,9 +123,6 @@ export function useElementEffects( onBlur, onMouseDown, onMouseUp, - onTouchStart, - onTouchEnd, }, - onPersistClick: onClick, }; } diff --git a/frontend/src/hooks/useNetworkAware.ts b/frontend/src/hooks/useNetworkAware.ts index 6486edb..65badb7 100644 --- a/frontend/src/hooks/useNetworkAware.ts +++ b/frontend/src/hooks/useNetworkAware.ts @@ -131,8 +131,7 @@ export function useNetworkAware(): UseNetworkAwareResult { if (networkInfo.effectiveType === 'slow-2g') return false; if (networkInfo.effectiveType === '2g') return false; if (networkInfo.effectiveType === '3g') return false; - if (networkInfo.downlink !== undefined && networkInfo.downlink < 2) - return false; + if (networkInfo.downlink !== undefined && networkInfo.downlink < 2) return false; if (networkInfo.rtt !== undefined && networkInfo.rtt > 500) return false; return true; }, [networkInfo]); diff --git a/frontend/src/hooks/usePreloadOrchestrator.ts b/frontend/src/hooks/usePreloadOrchestrator.ts index d3f5d6c..8872588 100644 --- a/frontend/src/hooks/usePreloadOrchestrator.ts +++ b/frontend/src/hooks/usePreloadOrchestrator.ts @@ -348,10 +348,7 @@ export function usePreloadOrchestrator( const currentPageElements = elements.filter( (el) => el.pageId === currentPageId, ); - const elementAssets = extractElementAssets( - currentPageElements, - currentPageId, - ); + const elementAssets = extractElementAssets(currentPageElements, currentPageId); // Collect storage paths for presigned URL batch request const storagePaths: string[] = []; @@ -516,8 +513,7 @@ export function usePreloadOrchestrator( const job = createDownloadJob( `elem-img-${asset.storageKey}`, asset.storageKey, - PRELOAD_CONFIG.priority.currentPage + - PRELOAD_CONFIG.priority.assetType.image, + PRELOAD_CONFIG.priority.currentPage + PRELOAD_CONFIG.priority.assetType.image, 'image', ); if (job) { @@ -597,8 +593,7 @@ export function usePreloadOrchestrator( `trans-rev-${link.from_pageId}-${link.to_pageId}`, reverseVideoUrl, PRELOAD_CONFIG.priority.currentPage + - PRELOAD_CONFIG.priority.assetType.transition - - 10, + PRELOAD_CONFIG.priority.assetType.transition - 10, 'transition', ); if (job) { @@ -633,14 +628,7 @@ export function usePreloadOrchestrator( } else { addAssetsToQueue(); } - }, [ - enabled, - currentPageId, - networkInfo.isOnline, - elements, - pages, - pageLinks, - ]); + }, [enabled, currentPageId, networkInfo.isOnline, elements, pages, pageLinks]); const isCurrentPageReady = currentPhase === 'phase2_transitions' || currentPhase === 'complete'; diff --git a/frontend/src/hooks/useTransitionPlayback.ts b/frontend/src/hooks/useTransitionPlayback.ts index 01bca68..b892281 100644 --- a/frontend/src/hooks/useTransitionPlayback.ts +++ b/frontend/src/hooks/useTransitionPlayback.ts @@ -368,11 +368,7 @@ export function useTransitionPlayback( if (!Number.isFinite(durationSec) || durationSec <= 0) return; const finishBeforeEndMs = getFinishBeforeEndMs(); const finishMs = Math.max(100, durationSec * 1000 - finishBeforeEndMs); - setTimer( - 'finishByDuration', - () => finishPlayback('duration-timer'), - finishMs, - ); + setTimer('finishByDuration', () => finishPlayback('duration-timer'), finishMs); }; const attemptPlay = () => { @@ -529,20 +525,12 @@ export function useTransitionPlayback( const timeSinceProgress = Date.now() - lastProgressTimeRef.current; if (!video.paused && !isWaitingForDataRef.current) { - setTimer( - 'progressCheck', - checkProgress, - progressTimeout.checkIntervalMs, - ); + setTimer('progressCheck', checkProgress, progressTimeout.checkIntervalMs); return; } if (timeSinceProgress < actualNoProgressMs) { - setTimer( - 'progressCheck', - checkProgress, - progressTimeout.checkIntervalMs, - ); + setTimer('progressCheck', checkProgress, progressTimeout.checkIntervalMs); return; } diff --git a/frontend/src/hooks/video/useVideoBlobUrl.ts b/frontend/src/hooks/video/useVideoBlobUrl.ts index 1f34504..610b548 100644 --- a/frontend/src/hooks/video/useVideoBlobUrl.ts +++ b/frontend/src/hooks/video/useVideoBlobUrl.ts @@ -155,8 +155,7 @@ export function useVideoBlobUrl({ }) .catch((err) => { if (!cancelled) { - const resolveError = - err instanceof Error ? err : new Error(String(err)); + const resolveError = err instanceof Error ? err : new Error(String(err)); setError(resolveError); setIsResolving(false); onErrorRef.current?.(resolveError); diff --git a/frontend/src/hooks/video/useVideoBufferingState.ts b/frontend/src/hooks/video/useVideoBufferingState.ts index 7bb6597..7d91c48 100644 --- a/frontend/src/hooks/video/useVideoBufferingState.ts +++ b/frontend/src/hooks/video/useVideoBufferingState.ts @@ -7,13 +7,7 @@ * - Progress-based timeout detection */ -import { - useState, - useRef, - useCallback, - useEffect, - type RefObject, -} from 'react'; +import { useState, useRef, useCallback, useEffect, type RefObject } from 'react'; import { logger } from '../../lib/logger'; import { TRANSITION_CONFIG } from '../../config/transition.config'; @@ -94,8 +88,7 @@ export function useVideoBufferingState({ (isMobile ? progressTimeout.noProgressMs * progressTimeout.mobileMultiplier : progressTimeout.noProgressMs); - const actualCheckIntervalMs = - checkIntervalMs ?? progressTimeout.checkIntervalMs; + const actualCheckIntervalMs = checkIntervalMs ?? progressTimeout.checkIntervalMs; const stopProgressMonitor = useCallback(() => { if (progressTimeoutRef.current) { @@ -124,19 +117,13 @@ export function useVideoBufferingState({ // If playing normally (not waiting), continue monitoring if (!video.paused && !isWaitingForDataRef.current) { - progressTimeoutRef.current = setTimeout( - checkProgress, - actualCheckIntervalMs, - ); + progressTimeoutRef.current = setTimeout(checkProgress, actualCheckIntervalMs); return; } // If waiting but received data recently, keep waiting if (timeSinceProgress < actualNoProgressMs) { - progressTimeoutRef.current = setTimeout( - checkProgress, - actualCheckIntervalMs, - ); + progressTimeoutRef.current = setTimeout(checkProgress, actualCheckIntervalMs); return; } @@ -150,10 +137,7 @@ export function useVideoBufferingState({ onProgressTimeoutRef.current?.(); }; - progressTimeoutRef.current = setTimeout( - checkProgress, - actualCheckIntervalMs, - ); + progressTimeoutRef.current = setTimeout(checkProgress, actualCheckIntervalMs); }, [videoRef, actualNoProgressMs, actualCheckIntervalMs]); const reset = useCallback(() => { diff --git a/frontend/src/hooks/video/useVideoErrorRecovery.ts b/frontend/src/hooks/video/useVideoErrorRecovery.ts index 97bc38e..3c836ba 100644 --- a/frontend/src/hooks/video/useVideoErrorRecovery.ts +++ b/frontend/src/hooks/video/useVideoErrorRecovery.ts @@ -95,8 +95,7 @@ export function useVideoErrorRecovery({ if (!error) return false; const errorCode = error.code; - const errorMessage = - (error as MediaError & { message?: string }).message || ''; + const errorMessage = (error as MediaError & { message?: string }).message || ''; logger.error('useVideoErrorRecovery: Video error', { code: errorCode, @@ -113,13 +112,10 @@ export function useVideoErrorRecovery({ decodeRetryCountRef.current < actualMaxDecodeRetries ) { decodeRetryCountRef.current++; - logger.info( - 'useVideoErrorRecovery: Safari decode error, attempting reload', - { - attempt: decodeRetryCountRef.current, - maxAttempts: actualMaxDecodeRetries, - }, - ); + logger.info('useVideoErrorRecovery: Safari decode error, attempting reload', { + attempt: decodeRetryCountRef.current, + maxAttempts: actualMaxDecodeRetries, + }); video.load(); video.play().catch(() => { /* ignore play errors during recovery */ @@ -137,12 +133,9 @@ export function useVideoErrorRecovery({ !didTryProxyRef.current ) { didTryProxyRef.current = true; - logger.info( - 'useVideoErrorRecovery: Presigned URL failed, retrying with proxy', - { - storageKey: currentStorageKey.slice(-40), - }, - ); + logger.info('useVideoErrorRecovery: Presigned URL failed, retrying with proxy', { + storageKey: currentStorageKey.slice(-40), + }); markPresignedUrlFailed(currentStorageKey); const proxyUrl = buildProxyUrl(currentStorageKey); onRetryWithSourceRef.current?.(proxyUrl); diff --git a/frontend/src/hooks/video/useVideoFirstFrame.ts b/frontend/src/hooks/video/useVideoFirstFrame.ts index 58c6f23..7edcacd 100644 --- a/frontend/src/hooks/video/useVideoFirstFrame.ts +++ b/frontend/src/hooks/video/useVideoFirstFrame.ts @@ -6,13 +6,7 @@ * - requestAnimationFrame fallback (older browsers) */ -import { - useState, - useRef, - useCallback, - useEffect, - type RefObject, -} from 'react'; +import { useState, useRef, useCallback, useEffect, type RefObject } from 'react'; import { logger } from '../../lib/logger'; import { scheduleAfterPaint } from '../../lib/browserUtils'; @@ -66,11 +60,8 @@ export function useVideoFirstFrame({ if (callbackIdRef.current !== null) { const video = videoRef.current; if (video && 'cancelVideoFrameCallback' in video) { - ( - video as HTMLVideoElement & { - cancelVideoFrameCallback: (id: number) => void; - } - ).cancelVideoFrameCallback(callbackIdRef.current); + (video as HTMLVideoElement & { cancelVideoFrameCallback: (id: number) => void }) + .cancelVideoFrameCallback(callbackIdRef.current); } callbackIdRef.current = null; } @@ -86,16 +77,11 @@ export function useVideoFirstFrame({ // Use requestVideoFrameCallback for precise frame-level timing (Safari 15.4+) if ('requestVideoFrameCallback' in video) { - const rvfc = ( - video as HTMLVideoElement & { - requestVideoFrameCallback: ( - callback: ( - now: number, - metadata: VideoFrameCallbackMetadata, - ) => void, - ) => number; - } - ).requestVideoFrameCallback.bind(video); + const rvfc = (video as HTMLVideoElement & { + requestVideoFrameCallback: ( + callback: (now: number, metadata: VideoFrameCallbackMetadata) => void + ) => number; + }).requestVideoFrameCallback.bind(video); // First callback: frame is composited, safe to show overlay callbackIdRef.current = rvfc((_now, _metadata) => { @@ -113,9 +99,7 @@ export function useVideoFirstFrame({ if (!didFireRef.current) { didFireRef.current = true; setIsFirstFramePainted(true); - logger.info( - 'useVideoFirstFrame: First frame painted (rAF fallback)', - ); + logger.info('useVideoFirstFrame: First frame painted (rAF fallback)'); onFirstFrameRef.current?.(); } }); diff --git a/frontend/src/hooks/video/useVideoPlaybackCore.ts b/frontend/src/hooks/video/useVideoPlaybackCore.ts index 3301dad..5f3abcc 100644 --- a/frontend/src/hooks/video/useVideoPlaybackCore.ts +++ b/frontend/src/hooks/video/useVideoPlaybackCore.ts @@ -6,13 +6,7 @@ * and background video playback. */ -import { - useCallback, - useEffect, - useRef, - useState, - type RefObject, -} from 'react'; +import { useCallback, useEffect, useRef, useState, type RefObject } from 'react'; import { logger } from '../../lib/logger'; import { useVideoBlobUrl, type PreloadCacheProvider } from './useVideoBlobUrl'; import { useVideoBufferingState } from './useVideoBufferingState'; @@ -271,9 +265,7 @@ export function useVideoPlaybackCore({ 'playbackStart', () => { if (!didStartPlaybackRef.current) { - logger.warn( - 'useVideoPlaybackCore: Playback start timeout, retrying', - ); + logger.warn('useVideoPlaybackCore: Playback start timeout, retrying'); play(); } }, diff --git a/frontend/src/hooks/video/useVideoPlayer.ts b/frontend/src/hooks/video/useVideoPlayer.ts index aeb282e..0ae4b67 100644 --- a/frontend/src/hooks/video/useVideoPlayer.ts +++ b/frontend/src/hooks/video/useVideoPlayer.ts @@ -11,13 +11,7 @@ * not for transition or background videos which have their own hooks. */ -import { - useRef, - useCallback, - useEffect, - useState, - type RefObject, -} from 'react'; +import { useRef, useCallback, useEffect, useState, type RefObject } from 'react'; import { logger } from '../../lib/logger'; import { useVideoBlobUrl, type PreloadCacheProvider } from './useVideoBlobUrl'; import { useVideoErrorRecovery } from './useVideoErrorRecovery'; diff --git a/frontend/src/hooks/video/useVideoTimeouts.ts b/frontend/src/hooks/video/useVideoTimeouts.ts index f95e6a4..6d3b47f 100644 --- a/frontend/src/hooks/video/useVideoTimeouts.ts +++ b/frontend/src/hooks/video/useVideoTimeouts.ts @@ -39,9 +39,7 @@ export interface UseVideoTimeoutsResult { * clearTimer('watchdog'); */ export function useVideoTimeouts(): UseVideoTimeoutsResult { - const timersRef = useRef>>( - new Map(), - ); + const timersRef = useRef>>(new Map()); const clearTimer = useCallback((name: string) => { const timer = timersRef.current.get(name); diff --git a/frontend/src/lib/elementDefaults.ts b/frontend/src/lib/elementDefaults.ts index 16929c2..5ae3d1d 100644 --- a/frontend/src/lib/elementDefaults.ts +++ b/frontend/src/lib/elementDefaults.ts @@ -37,13 +37,6 @@ export const ELEMENT_EFFECT_PROPS = [ 'activeScale', 'activeOpacity', 'activeBackgroundColor', - 'hoverReveal', - 'hoverRevealInitialOpacity', - 'hoverRevealTargetOpacity', - 'hoverRevealDuration', - 'hoverRevealDelay', - 'hoverRevealPersist', - 'hoverPersistOnClick', ] as const; /** diff --git a/frontend/src/lib/elementEffects.ts b/frontend/src/lib/elementEffects.ts index a22262d..6e7882e 100644 --- a/frontend/src/lib/elementEffects.ts +++ b/frontend/src/lib/elementEffects.ts @@ -69,15 +69,6 @@ export interface ElementEffectProperties { activeScale?: string; activeOpacity?: string; activeBackgroundColor?: string; - // Hover Reveal effects - hoverReveal?: boolean; - hoverRevealInitialOpacity?: string; - hoverRevealTargetOpacity?: string; - hoverRevealDuration?: string; - hoverRevealDelay?: string; - hoverRevealPersist?: boolean; - // Persist hover effects after click (applies to all hover effects) - hoverPersistOnClick?: boolean; } /** @@ -100,13 +91,6 @@ export const EFFECT_PROPS = [ 'activeScale', 'activeOpacity', 'activeBackgroundColor', - 'hoverReveal', - 'hoverRevealInitialOpacity', - 'hoverRevealTargetOpacity', - 'hoverRevealDuration', - 'hoverRevealDelay', - 'hoverRevealPersist', - 'hoverPersistOnClick', ] as const; export type EffectPropName = (typeof EFFECT_PROPS)[number]; @@ -303,54 +287,6 @@ export function hasAnyEffects( Boolean(effects.appearAnimation) || hasHoverEffects(effects) || hasFocusEffects(effects) || - hasActiveEffects(effects) || - hasHoverReveal(effects) + hasActiveEffects(effects) ); } - -/** - * Check if element has hover reveal enabled. - */ -export function hasHoverReveal( - effects: Partial, -): boolean { - return Boolean(effects.hoverReveal); -} - -/** - * Build hover reveal style based on current reveal state. - */ -export function buildHoverRevealStyle( - effects: Partial, - isRevealed: boolean, -): CSSProperties { - if (!effects.hoverReveal) return {}; - - const initialOpacity = parseFloat(effects.hoverRevealInitialOpacity || '0'); - const targetOpacity = parseFloat(effects.hoverRevealTargetOpacity || '1'); - const duration = normalizeDuration(effects.hoverRevealDuration) || '0.3s'; - const delay = normalizeDuration(effects.hoverRevealDelay) || '0s'; - - return { - opacity: isRevealed ? targetOpacity : initialOpacity, - transition: `opacity ${duration} ease ${delay}`, - }; -} - -/** - * Extract effect properties from element to avoid duplication - * in RuntimeElement and CanvasElement. - * Uses EFFECT_PROPS array as source of truth. - */ -export function extractEffectProperties( - element: Record, -): Partial { - const props: Partial = {}; - for (const key of EFFECT_PROPS) { - const value = element[key]; - if (value !== undefined) { - (props as Record)[key] = value; - } - } - return props; -} diff --git a/frontend/src/pages/constructor.tsx b/frontend/src/pages/constructor.tsx index d86f3a9..a58be18 100644 --- a/frontend/src/pages/constructor.tsx +++ b/frontend/src/pages/constructor.tsx @@ -531,10 +531,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { [navNavigateToPage, applyPageSelection], ); - const { - isBuffering: isTransitionBuffering, - isVideoReady: isTransitionVideoReady, - } = useTransitionPlayback({ + const { isBuffering: isTransitionBuffering, isVideoReady: isTransitionVideoReady } = useTransitionPlayback({ videoRef: transitionVideoRef, transition: transitionPreview ? { @@ -1301,10 +1298,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { // Note: Background ready state is reset atomically by navigateToPage/switchToPage // Check if transition can be played using shared helper - const canPlayTransition = hasPlayableTransition( - transitionSource, - direction, - ); + const canPlayTransition = hasPlayableTransition(transitionSource, direction); // Check if video is already cached (use video even on slow network if cached) const transitionUrl = transitionSource.transitionVideoUrl; @@ -1313,8 +1307,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { // Use video if: has playable transition AND (cached OR good network) const useVideoTransition = - canPlayTransition && - (isTransitionCached || shouldUseVideoTransitions); + canPlayTransition && (isTransitionCached || shouldUseVideoTransitions); if (!useVideoTransition) { closeTransitionPreview(); @@ -1933,8 +1926,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { pageTransitionSettings={transitionSettings} preloadCache={{ getReadyBlob: preloadOrchestrator.getReadyBlob, - getCachedBlobUrl: - preloadOrchestrator.getCachedBlobUrl, + getCachedBlobUrl: preloadOrchestrator.getCachedBlobUrl, }} /> ); diff --git a/frontend/src/pages/element-type-defaults/[id].tsx b/frontend/src/pages/element-type-defaults/[id].tsx index 5e59829..c9ac6a4 100644 --- a/frontend/src/pages/element-type-defaults/[id].tsx +++ b/frontend/src/pages/element-type-defaults/[id].tsx @@ -184,16 +184,7 @@ const ElementTypeDefaultDetailsPage = () => { // Handler for effects section changes const handleEffectChange = useCallback( (prop: string, value: string) => { - // Convert string 'true'/'false' to boolean for boolean fields - if ( - prop === 'hoverReveal' || - prop === 'hoverRevealPersist' || - prop === 'hoverPersistOnClick' - ) { - setField(prop as keyof typeof form.state, (value === 'true') as never); - } else { - setField(prop as keyof typeof form.state, value as never); - } + setField(prop as keyof typeof form.state, value); }, [setField], ); diff --git a/frontend/src/pages/project-element-defaults/[id].tsx b/frontend/src/pages/project-element-defaults/[id].tsx index 20f93d6..cd015a3 100644 --- a/frontend/src/pages/project-element-defaults/[id].tsx +++ b/frontend/src/pages/project-element-defaults/[id].tsx @@ -311,16 +311,7 @@ const ProjectElementDefaultDetailsPage = () => { // Handler for effects section changes const handleEffectChange = useCallback( (prop: string, value: string) => { - // Convert string 'true'/'false' to boolean for boolean fields - if ( - prop === 'hoverReveal' || - prop === 'hoverRevealPersist' || - prop === 'hoverPersistOnClick' - ) { - setField(prop as keyof typeof form.state, (value === 'true') as never); - } else { - setField(prop as keyof typeof form.state, value as never); - } + setField(prop as keyof typeof form.state, value); }, [setField], );