Santa Ana 30
o C 46% H
San Salvador 28
o C 55% H
San Miguel 28
o C 55% H
Surf City 28
o C 55% H
// ===== Parámetros de control =====
const durDesktop = 16;
const durMobile = 10;
const easingX = "slow(0.7,0.7,false)"; const floatDesktop = { amp: 200, dur: 2.5 };
const floatMobile = { amp: 80, dur: 3.8 }; const trailConfig = {
rate: 1.0, // partículas/tick (base a 60fps, se ajusta por delta)
maxParticles: 250,
baseSize: 8,
sizeJitter: 6,
life: 0.9,
hue: 15,
saturation: 80,
lightness: 60,
alpha: 0.6
}; const shootingConfig = {
intervalMin: 1800,
intervalMax: 4200,
speedMin: 900,
speedMax: 1600,
length: 160,
thickness: 2.2,
glow: 0.35
}; // ===== Flags de sistema =====
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; // ===== Contexto y MatchMedia (limpieza segura) =====
const mm = gsap.matchMedia();
const ctx = gsap.context(() => {
const el = document.querySelector(".elemento-x");
const pista = document.querySelector(".animacion");
if (!el || !pista) return; // ====== Trail (canvas) ======
const trailCanvas = document.getElementById("trailCanvas");
const trailCtx = trailCanvas?.getContext("2d");
let trailParticles = [];
const DPR = Math.max(1, Math.min(window.devicePixelRatio || 1, 2)); // limit 2 por perf // dimensiones CSS para limpiar correctamente
let trailCssW = 0, trailCssH = 0;
function sizeTrailCanvas(){
if (!trailCanvas || !trailCtx) return;
const rect = trailCanvas.getBoundingClientRect();
trailCssW = Math.max(1, Math.round(rect.width));
trailCssH = Math.max(1, Math.round(rect.height));
trailCanvas.width = trailCssW * DPR;
trailCanvas.height = trailCssH * DPR;
trailCtx.setTransform(DPR,0,0,DPR,0,0); // dibujar en unidades CSS
trailCtx.globalCompositeOperation = "lighter";
}
sizeTrailCanvas();
window.addEventListener("resize", sizeTrailCanvas, { passive: true }); function addTrailParticle(x, y, vx, vy){
if (trailParticles.length >= trailConfig.maxParticles) trailParticles.shift();
trailParticles.push({
x, y, vx, vy,
size: trailConfig.baseSize + Math.random()*trailConfig.sizeJitter,
alpha: trailConfig.alpha
});
} // Emisión basada en posición del elemento y delta de tiempo
function emitTrail(particlesToEmit){
if (!el || !pista) return;
const elRect = el.getBoundingClientRect();
const pistaRect = pista.getBoundingClientRect(); const emitX = (elRect.left - pistaRect.left) + elRect.width * 0.15;
const emitY = (elRect.top - pistaRect.top) + elRect.height* 0.58; for (let i = 0; i < particlesToEmit; i++){
const jitterX = -1.5 - Math.random()*1.5; // un poco hacia atrás
const jitterY = (Math.random()-0.5) * 1.2; // ligera dispersión
addTrailParticle(emitX, emitY, jitterX, jitterY);
}
} function renderTrail(){
if (!trailCtx) return;
// limpieza con desvanecido
trailCtx.fillStyle = "rgba(0,0,0,0.08)";
trailCtx.fillRect(0, 0, trailCssW, trailCssH); for (let i = trailParticles.length - 1; i >= 0; i--){
const p = trailParticles[i];
p.x += p.vx;
p.y += p.vy;
p.alpha *= trailConfig.life; if (p.alpha < 0.02){
trailParticles.splice(i,1);
continue;
} const grad = trailCtx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size);
const c1 = `hsla(${trailConfig.hue}, ${trailConfig.saturation}%, ${trailConfig.lightness}%, ${p.alpha})`;
const c0 = `hsla(${trailConfig.hue}, ${trailConfig.saturation}%, ${Math.min(trailConfig.lightness+15,95)}%, ${p.alpha*0.9})`;
grad.addColorStop(0, c0);
grad.addColorStop(1, c1); trailCtx.fillStyle = grad;
trailCtx.beginPath();
trailCtx.arc(p.x, p.y, p.size, 0, Math.PI*2);
trailCtx.fill();
}
} // Ticker GSAP con deltaRatio (1 = 60fps)
let trailTicker;
let emitAccumulator = 0; // para soportar fracciones
function startTrail(){
if (!trailCanvas || !trailCtx) return;
if (prefersReduced) return; // omitir en modo reducido
if (trailTicker) return;
trailTicker = () => {
const dr = gsap.ticker.deltaRatio(); // ~1 a 60fps
const toEmit = trailConfig.rate * dr; // escala por delta
emitAccumulator += toEmit;
const emitNow = Math.floor(emitAccumulator);
if (emitNow > 0){
emitTrail(emitNow);
emitAccumulator -= emitNow;
}
renderTrail();
};
gsap.ticker.add(trailTicker);
}
function stopTrail(){
if (trailTicker){
gsap.ticker.remove(trailTicker);
trailTicker = null;
}
} // ===== Estrellas fugaces (canvas de fondo) =====
const starsCanvas = document.getElementById("starsCanvas");
const starsCtx = starsCanvas?.getContext("2d");
let starsObjects = [];
let starsReqId = null;
let starsCssW = 0, starsCssH = 0; function sizeStarsCanvas(){
if (!starsCanvas || !starsCtx) return;
starsCssW = Math.max(1, Math.round(starsCanvas.clientWidth));
starsCssH = Math.max(1, Math.round(starsCanvas.clientHeight));
starsCanvas.width = starsCssW * DPR;
starsCanvas.height = starsCssH * DPR;
starsCtx.setTransform(DPR,0,0,DPR,0,0);
}
sizeStarsCanvas();
window.addEventListener("resize", sizeStarsCanvas, { passive: true }); let shootingTimer = null;
function spawnShootingStar(){
if (!starsCanvas) return;
const w = starsCssW;
const h = starsCssH; // Origen aleatorio en el tercio superior izquierdo
const startX = -40 + Math.random()*80;
const startY = Math.random() * h * 0.35 + 10; const angle = (Math.PI/180) * (20 + Math.random()*20);
const speedMs = shootingConfig.speedMin + Math.random()*(shootingConfig.speedMax - shootingConfig.speedMin);
const pxPerSec = w / (speedMs/1000); // proporcional al ancho
const vx = Math.cos(angle) * pxPerSec;
const vy = Math.sin(angle) * pxPerSec; starsObjects.push({
x: startX, y: startY, vx, vy,
life: 1,
length: shootingConfig.length,
thickness: shootingConfig.thickness
}); // programa la siguiente
const wait = shootingConfig.intervalMin + Math.random()*(shootingConfig.intervalMax - shootingConfig.intervalMin);
shootingTimer = window.setTimeout(spawnShootingStar, wait);
} let lastTs = 0;
function renderShootingStars(ts){
if (!starsCtx) return;
const dt = lastTs ? Math.min(0.05, (ts - lastTs)/1000) : 0; // seg, cap 50ms
lastTs = ts; starsCtx.clearRect(0, 0, starsCssW, starsCssH); for (let i = starsObjects.length-1; i >= 0; i--){
const s = starsObjects[i];
s.x += s.vx * dt;
s.y += s.vy * dt;
s.life *= 0.965; if (s.life < 0.02 || s.x > starsCssW + s.length || s.y > starsCssH + s.length){
starsObjects.splice(i,1);
continue;
} const mag = Math.hypot(s.vx, s.vy) || 1;
const ux = s.vx / mag, uy = s.vy / mag;
const endX = s.x - s.length * ux;
const endY = s.y - s.length * uy; const grad = starsCtx.createLinearGradient(s.x, s.y, endX, endY);
grad.addColorStop(0, `rgba(255,255,255,${0.9 * s.life})`);
grad.addColorStop(0.3, `rgba(255,240,200,${0.6 * s.life})`);
grad.addColorStop(1, `rgba(255,255,255,0)`); starsCtx.strokeStyle = grad;
starsCtx.lineWidth = s.thickness;
starsCtx.lineCap = "round";
starsCtx.shadowColor = `rgba(255,255,255,${shootingConfig.glow})`;
starsCtx.shadowBlur = 8; starsCtx.beginPath();
starsCtx.moveTo(s.x, s.y);
starsCtx.lineTo(endX, endY);
starsCtx.stroke();
} starsReqId = requestAnimationFrame(renderShootingStars);
} function startStars(){
if (!starsCanvas || !starsCtx) return;
if (prefersReduced) return; // omitir en modo reducido
if (!starsReqId) starsReqId = requestAnimationFrame(renderShootingStars);
if (!shootingTimer) spawnShootingStar();
}
function stopStars(){
if (starsReqId){
cancelAnimationFrame(starsReqId);
starsReqId = null;
}
if (shootingTimer){
clearTimeout(shootingTimer);
shootingTimer = null;
}
starsObjects.length = 0;
lastTs = 0;
if (starsCtx) starsCtx.clearRect(0, 0, starsCssW, starsCssH);
} // ===== Animación principal (GSAP) =====
function animateSanta(durationX, ampY, durY){
if (!el) return; // Movimiento horizontal
const moveTween = gsap.to(el, {
x: "250vw",
duration: durationX,
ease: easingX,
repeat: 2
}); // Flotación
const floatTween = gsap.to(el, {
y: `-=${ampY}`,
duration: durY,
ease: "sine.inOut",
yoyo: true,
repeat: -1
}); // Arrancar trail mientras se mueve
startTrail(); // Detener trail y flotación al acabar el cruce completo
gsap.delayedCall(durationX * (moveTween.repeat() + 1), () => {
floatTween.kill();
stopTrail();
}); return { moveTween, floatTween };
} // ===== MatchMedia breakpoints =====
let currentAnim = null;
mm.add({
isDesktop: "(min-width: 992px)",
isMobile: "(max-width: 991px)"
}, (ctxMM) => {
const { isDesktop, isMobile } = ctxMM.conditions;
const dX = isDesktop ? durDesktop : durMobile;
const amp = isDesktop ? floatDesktop.amp : floatMobile.amp;
const dY = isDesktop ? floatDesktop.dur : floatMobile.dur; // En modo reducido: minimizar
if (prefersReduced){
// Posicionar sin animar, sin trail ni estrellas
gsap.set(el, { x: "0vw", y: 0 });
return () => {};
} currentAnim = animateSanta(dX, amp, dY);
startStars(); // Limpieza al cambiar media query
return () => {
if (currentAnim){
currentAnim.moveTween?.kill();
currentAnim.floatTween?.kill();
currentAnim = null;
}
stopTrail();
stopStars();
};
}); // Pausar todo al ocultar pestaña
document.addEventListener("visibilitychange", () => {
const hidden = document.hidden;
gsap.ticker.lagSmoothing( hidden ? 0 : 1000, hidden ? 0 : 16 ); // estabiliza dt
if (hidden){
gsap.globalTimeline.pause();
stopTrail();
stopStars();
} else {
gsap.globalTimeline.resume();
startTrail();
startStars();
}
}); }); // Limpieza al navegar/recargar
window.addEventListener("beforeunload", () => {
mm.revert();
ctx.revert();
});
Home - Entretenimiento - Espectaculos
Billy Calderón dedica una oración a su esposa y derrite el corazón de sus fans Billy Calderón usó una foto de antaño junto a esposa, cuando ella estaba embarazada, para acompañar una dulce oración dedicada a ella.
Por I. Rivera Publicado el 08 de diciembre de 2023
Billy Calderón realizó una nueva publicación hace algunas horas, la cual ha generado decenas de comentarios por parte de sus fans y, por qué no decirlo, también de los seguidores de su esposa.
En el post aparece una foto de la pareja cuando recién comenzaba una vida familia, ya que en ella Patricia de Calderón aparece embarazada. Ambos lucen muy felices.
Lee también: Yessica Cárcamo sufre secuelas tras su cirugía plástica: "Sí siento que me duele"
"Te amo cielo @pattycalderon11. En esta foto estabas embarazada de nuestra bella @fatimacalderon9", escribió Billy en su IG junto a un audio que emocionó a más de alguno.
Billy Calderón siempre tiene detalles con su esposa. Video de carácter ilustrativo y no comercial / https://www.instagram.com/p/C0j1fXyLyeZ/
La primera en responder el post fue la mismísima esposa del famoso salvadoreño, Patty de Calderón: "Gracias amor por esa oración, te amo con todo mi corazón, que Dios siempre habite en cada uno de nosotros".
Te puede interesar: Marito Rivera y su Grupo Bravo te ponen a bailar con el "Mix furia vacilón"
Sus followers también comentaron: "Qué hermosa mi doctora, qué linda pareja desde siempre", "casi no escribo!! Esa foto se lleva el premio de todas!! Mirar atrás es como vivir nuevamente aquello lo que hoy nos hace más fuertes!! Mil bendiciones", "qué lindo es ver una pareja en la que prevalezca tanto el amor como lo hacen ustedes, bendiciones", "a dónde he visto esa cara dije yo y es la misma cara de su hijo menor, salió idéntico a la mamá", "qué fotaza mas bella… ver atrás tantos años juntos y ver atrás todos los retos que han atravesado es una dicha", "de verdad que los admiro, preciosa pareja".
Sin duda esta es una de las parejas más queridas por los salvadoreños, hasta el momento este detalle acumula más de 2,430 Me gusta.