// import mapboxgl from 'mapbox-gl'; // interface AnimationOptions { // duration: number; // easing: string; // transform: string; // } // interface CustomPopupOptions extends mapboxgl.PopupOptions { // openingAnimation?: AnimationOptions; // closingAnimation?: AnimationOptions; // } // // Extend the native Mapbox Popup // export class CustomAnimatedPopup extends mapboxgl.Popup { // private openingAnimation: AnimationOptions; // private closingAnimation: AnimationOptions; // private animating = false; // constructor(options: CustomPopupOptions = {}) { // // Extract animation options and pass the rest to the parent class // const { // openingAnimation, // closingAnimation, // className, // ...mapboxOptions // } = options; // // Add our custom class to the className // const customClassName = `custom-animated-popup ${className || ''}`.trim(); // // Call the parent constructor // super({ // ...mapboxOptions, // className: customClassName, // }); // // Store animation options // this.openingAnimation = openingAnimation || { // duration: 300, // easing: 'ease-out', // transform: 'scale' // }; // this.closingAnimation = closingAnimation || { // duration: 200, // easing: 'ease-in-out', // transform: 'scale' // }; // // Override the parent's add method // const parentAdd = this.addTo; // this.addTo = (map: mapboxgl.Map) => { // // Call the parent method first // parentAdd.call(this, map); // // Apply animation after a short delay to ensure the element is in the DOM // setTimeout(() => this.animateOpen(), 10); // return this; // }; // } // // Override the remove method to add animation // remove(): this { // if (this.animating) { // return this; // } // this.animateClose(() => { // super.remove(); // }); // return this; // } // // Animation methods // private animateOpen(): void { // const container = this._container; // if (!container) return; // // Apply initial state // container.style.opacity = '0'; // container.style.transform = 'scale(0.8)'; // container.style.transition = ` // opacity ${this.openingAnimation.duration}ms ${this.openingAnimation.easing}, // transform ${this.openingAnimation.duration}ms ${this.openingAnimation.easing} // `; // // Force reflow // void container.offsetHeight; // // Apply final state to trigger animation // container.style.opacity = '1'; // container.style.transform = 'scale(1)'; // } // private animateClose(callback: () => void): void { // const container = this._container; // if (!container) { // callback(); // return; // } // this.animating = true; // // Setup transition // container.style.transition = ` // opacity ${this.closingAnimation.duration}ms ${this.closingAnimation.easing}, // transform ${this.closingAnimation.duration}ms ${this.closingAnimation.easing} // `; // // Apply closing animation // container.style.opacity = '0'; // container.style.transform = 'scale(0.8)'; // // Execute callback after animation completes // setTimeout(() => { // this.animating = false; // callback(); // }, this.closingAnimation.duration); // } // // Add method to create expanding wave circles // addWaveCircles(map: mapboxgl.Map, lngLat: mapboxgl.LngLat, options: { // color?: string, // maxRadius?: number, // duration?: number, // count?: number, // showCenter?: boolean // } = {}): void { // const { // color = 'red', // maxRadius = 80, // Reduce max radius for less "over" effect // duration = 2000, // Faster animation // count = 2, // Fewer circles // showCenter = true // } = options; // const sourceId = `wave-circles-${Math.random().toString(36).substring(2, 9)}`; // if (!map.getSource(sourceId)) { // map.addSource(sourceId, { // type: 'geojson', // data: { // type: 'FeatureCollection', // features: [{ // type: 'Feature', // geometry: { // type: 'Point', // coordinates: [lngLat.lng, lngLat.lat] // }, // properties: { // radius: 0 // } // }] // } // }); // for (let i = 0; i < count; i++) { // const layerId = `${sourceId}-layer-${i}`; // const delay = i * (duration / count); // map.addLayer({ // id: layerId, // type: 'circle', // source: sourceId, // paint: { // 'circle-radius': ['interpolate', ['linear'], ['get', 'radius'], 0, 0, 100, maxRadius], // 'circle-color': 'transparent', // 'circle-opacity': ['interpolate', ['linear'], ['get', 'radius'], // 0, showCenter ? 0.15 : 0, // Lower opacity // 100, 0 // ], // 'circle-stroke-width': 1.5, // Thinner stroke // 'circle-stroke-color': color // } // }); // this.animateWaveCircle(map, sourceId, layerId, duration, delay); // } // } // } // private animateWaveCircle( // map: mapboxgl.Map, // sourceId: string, // layerId: string, // duration: number, // delay: number // ): void { // let start: number | null = null; // let animationId: number; // const animate = (timestamp: number) => { // if (!start) { // start = timestamp + delay; // } // const progress = Math.max(0, timestamp - start); // const progressPercent = Math.min(progress / duration, 1); // if (map.getSource(sourceId)) { // (map.getSource(sourceId) as mapboxgl.GeoJSONSource).setData({ // type: 'FeatureCollection', // features: [{ // type: 'Feature', // geometry: { // type: 'Point', // coordinates: (map.getSource(sourceId) as any)._data.features[0].geometry.coordinates // }, // properties: { // radius: progressPercent * 100 // } // }] // }); // } // if (progressPercent < 1) { // animationId = requestAnimationFrame(animate); // } else if (map.getLayer(layerId)) { // // Restart the animation for continuous effect // start = null; // animationId = requestAnimationFrame(animate); // } // }; // // Start the animation after delay // setTimeout(() => { // animationId = requestAnimationFrame(animate); // }, delay); // // Clean up on popup close // this.once('close', () => { // cancelAnimationFrame(animationId); // if (map.getLayer(layerId)) { // map.removeLayer(layerId); // } // if (map.getSource(sourceId) && !map.getLayer(layerId)) { // map.removeSource(sourceId); // } // }); // } // } // // Add styles to document when in browser environment // if (typeof document !== 'undefined') { // // Add styles only if they don't exist yet // if (!document.getElementById('custom-animated-popup-styles')) { // const style = document.createElement('style'); // style.id = 'custom-animated-popup-styles'; // style.textContent = ` // .custom-animated-popup { // will-change: transform, opacity; // } // .custom-animated-popup .mapboxgl-popup-content { // overflow: hidden; // } // /* Marker styles with wave circles */ // .marker-gempa { // position: relative; // width: 30px; // height: 30px; // cursor: pointer; // } // .circles { // position: relative; // width: 100%; // height: 100%; // display: flex; // align-items: center; // justify-content: center; // } // .circle1, .circle2, .circle3 { // position: absolute; // border-radius: 50%; // border: 2px solid red; // width: 100%; // height: 100%; // opacity: 0; // animation: pulse 2s infinite; // } // .circle2 { // animation-delay: 0.5s; // } // .circle3 { // animation-delay: 1s; // } // @keyframes pulse { // 0% { // transform: scale(0.5); // opacity: 0; // } // 50% { // opacity: 0.8; // } // 100% { // transform: scale(1.5); // opacity: 0; // } // } // .blink { // animation: blink 1s infinite; // color: red; // font-size: 20px; // } // @keyframes blink { // 0% { opacity: 1; } // 50% { opacity: 0.3; } // 100% { opacity: 1; } // } // `; // document.head.appendChild(style); // } // }